pentesting 0.49.4 → 0.51.0
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/README.md +33 -7
- package/dist/cloud/prompt.md +1 -1
- package/dist/file-sharing/prompt.md +2 -2
- package/dist/main.js +1506 -1463
- package/dist/network/prompt.md +1 -1
- package/dist/prompts/base.md +24 -37
- package/dist/prompts/infra.md +1 -1
- package/dist/prompts/vuln.md +3 -3
- package/dist/web/prompt.md +2 -2
- package/dist/wireless/prompt.md +8 -8
- package/package.json +1 -1
- package/dist/orchestrator/orchestrator.md +0 -71
package/dist/main.js
CHANGED
|
@@ -187,7 +187,19 @@ var MEMORY_LIMITS = {
|
|
|
187
187
|
/** Number of leading words to match for duplicate command detection */
|
|
188
188
|
COMMAND_MATCH_WORDS: 3,
|
|
189
189
|
/** Maximum unverified techniques to show in prompt */
|
|
190
|
-
PROMPT_UNVERIFIED_TECHNIQUES: 10
|
|
190
|
+
PROMPT_UNVERIFIED_TECHNIQUES: 10,
|
|
191
|
+
/**
|
|
192
|
+
* Maximum turn entries and turn records kept on disk.
|
|
193
|
+
* WHY: journal.ts had this as a module-local constant (50).
|
|
194
|
+
* Centralizing enables consistent rotation across journal JSON + turn MD files.
|
|
195
|
+
*/
|
|
196
|
+
MAX_TURN_ENTRIES: 50,
|
|
197
|
+
/**
|
|
198
|
+
* Maximum raw output files kept in .pentesting/outputs/.
|
|
199
|
+
* WHY: journal.ts had this as a module-local constant (30).
|
|
200
|
+
* Output files are large raw dumps; the journal entries retain their summaries.
|
|
201
|
+
*/
|
|
202
|
+
MAX_OUTPUT_FILES: 30
|
|
191
203
|
};
|
|
192
204
|
|
|
193
205
|
// src/shared/constants/patterns.ts
|
|
@@ -216,7 +228,9 @@ var EXIT_CODES = {
|
|
|
216
228
|
/** Process killed by SIGTERM */
|
|
217
229
|
SIGTERM: 143,
|
|
218
230
|
/** Process killed by SIGKILL */
|
|
219
|
-
SIGKILL: 137
|
|
231
|
+
SIGKILL: 137,
|
|
232
|
+
/** Invalid or missing configuration (Unix EX_CONFIG convention = 78) */
|
|
233
|
+
CONFIG_ERROR: 78
|
|
220
234
|
};
|
|
221
235
|
var PROCESS_ACTIONS = {
|
|
222
236
|
LIST: "list",
|
|
@@ -331,7 +345,7 @@ var ORPHAN_PROCESS_NAMES = [
|
|
|
331
345
|
|
|
332
346
|
// src/shared/constants/agent.ts
|
|
333
347
|
var APP_NAME = "Pentest AI";
|
|
334
|
-
var APP_VERSION = "0.
|
|
348
|
+
var APP_VERSION = "0.51.0";
|
|
335
349
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
336
350
|
var LLM_ROLES = {
|
|
337
351
|
SYSTEM: "system",
|
|
@@ -520,14 +534,7 @@ var PHASES = {
|
|
|
520
534
|
REPORT: "report"
|
|
521
535
|
};
|
|
522
536
|
var AGENT_ROLES = {
|
|
523
|
-
ORCHESTRATOR: "orchestrator"
|
|
524
|
-
RECON: "recon",
|
|
525
|
-
WEB: "web",
|
|
526
|
-
EXPLOITER: "exploit",
|
|
527
|
-
DATABASE: "database",
|
|
528
|
-
INFRA: "infra",
|
|
529
|
-
VULN: "vulnerability_analysis",
|
|
530
|
-
POST_EXPLOIT: "post_exploitation"
|
|
537
|
+
ORCHESTRATOR: "orchestrator"
|
|
531
538
|
};
|
|
532
539
|
var SERVICES = {
|
|
533
540
|
HTTP: "http",
|
|
@@ -811,7 +818,7 @@ var DEFAULTS = {
|
|
|
811
818
|
PORT_STATE_OPEN: "open"
|
|
812
819
|
};
|
|
813
820
|
|
|
814
|
-
// src/engine/process-manager.ts
|
|
821
|
+
// src/engine/process/process-manager.ts
|
|
815
822
|
import { spawn as spawn3, execSync as execSync2 } from "child_process";
|
|
816
823
|
import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2 } from "fs";
|
|
817
824
|
|
|
@@ -828,6 +835,12 @@ function ensureDirExists(dirPath) {
|
|
|
828
835
|
mkdirSync(dirPath, { recursive: true });
|
|
829
836
|
}
|
|
830
837
|
}
|
|
838
|
+
function fileTimestamp() {
|
|
839
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
840
|
+
}
|
|
841
|
+
function sanitizeFilename(name, maxLength = 30) {
|
|
842
|
+
return name.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, maxLength);
|
|
843
|
+
}
|
|
831
844
|
|
|
832
845
|
// src/shared/constants/time.ts
|
|
833
846
|
var MS_PER_MINUTE = 6e4;
|
|
@@ -837,7 +850,7 @@ var SECONDS_PER_HOUR = 3600;
|
|
|
837
850
|
// src/shared/constants/paths.ts
|
|
838
851
|
import path from "path";
|
|
839
852
|
var PENTESTING_ROOT = ".pentesting";
|
|
840
|
-
var WORK_DIR = `${PENTESTING_ROOT}/
|
|
853
|
+
var WORK_DIR = `${PENTESTING_ROOT}/workspace`;
|
|
841
854
|
var MEMORY_DIR = `${PENTESTING_ROOT}/memory`;
|
|
842
855
|
var REPORTS_DIR = `${PENTESTING_ROOT}/reports`;
|
|
843
856
|
var SESSIONS_DIR = `${PENTESTING_ROOT}/sessions`;
|
|
@@ -845,13 +858,14 @@ var LOOT_DIR = `${PENTESTING_ROOT}/loot`;
|
|
|
845
858
|
var OUTPUTS_DIR = `${PENTESTING_ROOT}/outputs`;
|
|
846
859
|
var DEBUG_DIR = `${PENTESTING_ROOT}/debug`;
|
|
847
860
|
var JOURNAL_DIR = `${PENTESTING_ROOT}/journal`;
|
|
848
|
-
var
|
|
861
|
+
var ARCHIVE_DIR = `${PENTESTING_ROOT}/archive`;
|
|
862
|
+
var TURN_FOLDER_PREFIX = "turn-";
|
|
849
863
|
var WORKSPACE = {
|
|
850
864
|
/** Root directory */
|
|
851
865
|
get ROOT() {
|
|
852
866
|
return path.resolve(PENTESTING_ROOT);
|
|
853
867
|
},
|
|
854
|
-
/**
|
|
868
|
+
/** Working directory (scripts, redirects, staging) */
|
|
855
869
|
get TMP() {
|
|
856
870
|
return path.resolve(WORK_DIR);
|
|
857
871
|
},
|
|
@@ -871,7 +885,7 @@ var WORKSPACE = {
|
|
|
871
885
|
get LOOT() {
|
|
872
886
|
return path.resolve(LOOT_DIR);
|
|
873
887
|
},
|
|
874
|
-
/**
|
|
888
|
+
/** DEPRECATED — tool outputs now in archive/turn-N/tools/ (kept for clearWorkspace cleanup) */
|
|
875
889
|
get OUTPUTS() {
|
|
876
890
|
return path.resolve(OUTPUTS_DIR);
|
|
877
891
|
},
|
|
@@ -879,13 +893,26 @@ var WORKSPACE = {
|
|
|
879
893
|
get DEBUG() {
|
|
880
894
|
return path.resolve(DEBUG_DIR);
|
|
881
895
|
},
|
|
882
|
-
/**
|
|
896
|
+
/** DEPRECATED — journal/ no longer written to (kept for /clear cleanup) */
|
|
883
897
|
get JOURNAL() {
|
|
884
898
|
return path.resolve(JOURNAL_DIR);
|
|
885
899
|
},
|
|
886
|
-
/** Turn
|
|
900
|
+
/** Turn archive root: .pentesting/archive/ */
|
|
887
901
|
get TURNS() {
|
|
888
|
-
return path.resolve(
|
|
902
|
+
return path.resolve(ARCHIVE_DIR);
|
|
903
|
+
},
|
|
904
|
+
/**
|
|
905
|
+
* Resolve a specific turn directory: .pentesting/archive/turn-{N}/
|
|
906
|
+
* Each turn is a named folder containing record.md, structured.json, analyst.md, summary.md, tools/
|
|
907
|
+
*/
|
|
908
|
+
turnPath(turn) {
|
|
909
|
+
return path.resolve(ARCHIVE_DIR, `${TURN_FOLDER_PREFIX}${turn}`);
|
|
910
|
+
},
|
|
911
|
+
/**
|
|
912
|
+
* Resolve the tools output directory for a specific turn: .pentesting/archive/turn-{N}/tools/
|
|
913
|
+
*/
|
|
914
|
+
turnToolsPath(turn) {
|
|
915
|
+
return path.resolve(ARCHIVE_DIR, `${TURN_FOLDER_PREFIX}${turn}`, "tools");
|
|
889
916
|
}
|
|
890
917
|
};
|
|
891
918
|
|
|
@@ -1129,6 +1156,66 @@ var BLOCKED_BINARIES = /* @__PURE__ */ new Set([
|
|
|
1129
1156
|
// src/shared/utils/debug-logger.ts
|
|
1130
1157
|
import { appendFileSync, writeFileSync } from "fs";
|
|
1131
1158
|
import { join } from "path";
|
|
1159
|
+
|
|
1160
|
+
// src/shared/constants/files.ts
|
|
1161
|
+
var FILE_EXTENSIONS = {
|
|
1162
|
+
// Data formats
|
|
1163
|
+
JSON: ".json",
|
|
1164
|
+
MARKDOWN: ".md",
|
|
1165
|
+
TXT: ".txt",
|
|
1166
|
+
XML: ".xml",
|
|
1167
|
+
// Network capture
|
|
1168
|
+
PCAP: ".pcap",
|
|
1169
|
+
HOSTS: ".hosts",
|
|
1170
|
+
MITM: ".mitm",
|
|
1171
|
+
// Process I/O
|
|
1172
|
+
STDOUT: ".stdout",
|
|
1173
|
+
STDERR: ".stderr",
|
|
1174
|
+
STDIN: ".stdin",
|
|
1175
|
+
// Scripts
|
|
1176
|
+
SH: ".sh",
|
|
1177
|
+
PY: ".py",
|
|
1178
|
+
CJS: ".cjs",
|
|
1179
|
+
// Config
|
|
1180
|
+
ENV: ".env",
|
|
1181
|
+
BAK: ".bak"
|
|
1182
|
+
};
|
|
1183
|
+
var SPECIAL_FILES = {
|
|
1184
|
+
/** Latest state snapshot */
|
|
1185
|
+
LATEST_STATE: "latest.json",
|
|
1186
|
+
/** README */
|
|
1187
|
+
README: "README.md",
|
|
1188
|
+
/** Session summary (single source — LLM primary, deterministic fallback) */
|
|
1189
|
+
SUMMARY: "summary.md",
|
|
1190
|
+
/** Persistent knowledge store */
|
|
1191
|
+
PERSISTENT_KNOWLEDGE: "persistent-knowledge.json",
|
|
1192
|
+
/** Debug log file */
|
|
1193
|
+
DEBUG_LOG: "debug.log"
|
|
1194
|
+
};
|
|
1195
|
+
var TURN_FILES = {
|
|
1196
|
+
/** Turn record (Markdown) — what happened this turn */
|
|
1197
|
+
RECORD: "record.md",
|
|
1198
|
+
/** Structured turn data (JSON) */
|
|
1199
|
+
STRUCTURED: "structured.json",
|
|
1200
|
+
/** Analyst LLM analysis output */
|
|
1201
|
+
ANALYST: "analyst.md",
|
|
1202
|
+
/** Session summary as of this turn (immutable once written) */
|
|
1203
|
+
SUMMARY: "summary.md",
|
|
1204
|
+
/** Directory for raw tool outputs */
|
|
1205
|
+
TOOLS_DIR: "tools"
|
|
1206
|
+
};
|
|
1207
|
+
var FILE_PATTERNS = {
|
|
1208
|
+
/** Tool output filename: nmap.txt, gobuster.txt (sanitized) */
|
|
1209
|
+
toolOutput(toolName) {
|
|
1210
|
+
return `${sanitizeFilename(toolName)}.txt`;
|
|
1211
|
+
},
|
|
1212
|
+
/** Generate session snapshot filename: 2026-02-21T08-30-15.json */
|
|
1213
|
+
session() {
|
|
1214
|
+
return `${fileTimestamp()}.json`;
|
|
1215
|
+
}
|
|
1216
|
+
};
|
|
1217
|
+
|
|
1218
|
+
// src/shared/utils/debug-logger.ts
|
|
1132
1219
|
var DebugLogger = class _DebugLogger {
|
|
1133
1220
|
static instance;
|
|
1134
1221
|
logPath;
|
|
@@ -1137,7 +1224,7 @@ var DebugLogger = class _DebugLogger {
|
|
|
1137
1224
|
const debugDir = WORKSPACE.DEBUG;
|
|
1138
1225
|
try {
|
|
1139
1226
|
ensureDirExists(debugDir);
|
|
1140
|
-
this.logPath = join(debugDir,
|
|
1227
|
+
this.logPath = join(debugDir, SPECIAL_FILES.DEBUG_LOG);
|
|
1141
1228
|
if (clearOnInit) {
|
|
1142
1229
|
this.clear();
|
|
1143
1230
|
}
|
|
@@ -1241,12 +1328,7 @@ function validateCommand(command) {
|
|
|
1241
1328
|
return result2;
|
|
1242
1329
|
}
|
|
1243
1330
|
}
|
|
1244
|
-
|
|
1245
|
-
return {
|
|
1246
|
-
isSafe: true,
|
|
1247
|
-
binary: primaryBinary || void 0,
|
|
1248
|
-
args: normalizedCommand.split(/\s+/).slice(1)
|
|
1249
|
-
};
|
|
1331
|
+
return { isSafe: true };
|
|
1250
1332
|
}
|
|
1251
1333
|
function splitChainedCommands(command) {
|
|
1252
1334
|
const parts = [];
|
|
@@ -1504,6 +1586,7 @@ var TOOL_PACKAGE_MAP = {
|
|
|
1504
1586
|
"seclists": { apt: "seclists", brew: "seclists" },
|
|
1505
1587
|
"wfuzz": { apt: "wfuzz", brew: "wfuzz", pip: "wfuzz" },
|
|
1506
1588
|
"dirsearch": { apt: "dirsearch", brew: "dirsearch", pip: "dirsearch" },
|
|
1589
|
+
"wafw00f": { apt: "wafw00f", brew: "wafw00f", pip: "wafw00f" },
|
|
1507
1590
|
"feroxbuster": { apt: "feroxbuster", brew: "feroxbuster" },
|
|
1508
1591
|
"subfinder": { apt: "subfinder", brew: "subfinder" },
|
|
1509
1592
|
"amass": { apt: "amass", brew: "amass" },
|
|
@@ -1545,20 +1628,16 @@ async function detectPackageManager() {
|
|
|
1545
1628
|
}
|
|
1546
1629
|
function isCommandNotFound(stderr, stdout) {
|
|
1547
1630
|
const combined = (stderr + stdout).toLowerCase();
|
|
1548
|
-
|
|
1631
|
+
return [
|
|
1549
1632
|
/command not found/,
|
|
1550
|
-
/not found
|
|
1633
|
+
/not found/,
|
|
1551
1634
|
/no such file or directory/,
|
|
1552
1635
|
/is not recognized/,
|
|
1553
1636
|
/cannot find/,
|
|
1554
1637
|
/not installed/,
|
|
1555
|
-
/unable to locate package
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
if (!missing) return { missing: false, toolName: null };
|
|
1559
|
-
const toolMatch = combined.match(/(?:command not found|not found):\s*([a-zA-Z0-9_-]+)/i);
|
|
1560
|
-
const toolName = toolMatch ? toolMatch[1] : null;
|
|
1561
|
-
return { missing: true, toolName };
|
|
1638
|
+
/unable to locate package/,
|
|
1639
|
+
/enoent/
|
|
1640
|
+
].some((p) => p.test(combined));
|
|
1562
1641
|
}
|
|
1563
1642
|
async function installTool(toolName, eventEmitter, inputHandler) {
|
|
1564
1643
|
const pkgInfo = TOOL_PACKAGE_MAP[toolName];
|
|
@@ -1677,14 +1756,15 @@ async function runCommand(command, args = [], options = {}) {
|
|
|
1677
1756
|
}
|
|
1678
1757
|
const timeout = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
|
|
1679
1758
|
const maxRetries = options.maxRetries ?? AGENT_LIMITS.MAX_INSTALL_RETRIES;
|
|
1759
|
+
const toolName = command.split(/[\s/]/).pop() || command;
|
|
1680
1760
|
let lastResult = { success: false, output: "", error: "Unknown error" };
|
|
1681
1761
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
1682
|
-
const result2 = await executeCommandOnce(
|
|
1762
|
+
const result2 = await executeCommandOnce(fullCommand, { ...options, timeout });
|
|
1683
1763
|
if (result2.success) {
|
|
1684
1764
|
return result2;
|
|
1685
1765
|
}
|
|
1686
|
-
const
|
|
1687
|
-
if (!missing ||
|
|
1766
|
+
const missing = isCommandNotFound(result2.error || "", result2.output);
|
|
1767
|
+
if (!missing || attempt >= maxRetries) {
|
|
1688
1768
|
return result2;
|
|
1689
1769
|
}
|
|
1690
1770
|
lastResult = result2;
|
|
@@ -1711,7 +1791,7 @@ async function runCommand(command, args = [], options = {}) {
|
|
|
1711
1791
|
}
|
|
1712
1792
|
return lastResult;
|
|
1713
1793
|
}
|
|
1714
|
-
async function executeCommandOnce(command,
|
|
1794
|
+
async function executeCommandOnce(command, options = {}) {
|
|
1715
1795
|
return new Promise((resolve) => {
|
|
1716
1796
|
const timeout = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
|
|
1717
1797
|
globalEventEmitter?.({
|
|
@@ -1842,38 +1922,10 @@ function createTempFile(suffix = "") {
|
|
|
1842
1922
|
return join2(tmpdir(), generateTempFilename(suffix));
|
|
1843
1923
|
}
|
|
1844
1924
|
|
|
1845
|
-
// src/
|
|
1846
|
-
var FILE_EXTENSIONS = {
|
|
1847
|
-
// Data formats
|
|
1848
|
-
JSON: ".json",
|
|
1849
|
-
MARKDOWN: ".md",
|
|
1850
|
-
TXT: ".txt",
|
|
1851
|
-
XML: ".xml",
|
|
1852
|
-
// Network capture
|
|
1853
|
-
PCAP: ".pcap",
|
|
1854
|
-
HOSTS: ".hosts",
|
|
1855
|
-
MITM: ".mitm",
|
|
1856
|
-
// Process I/O
|
|
1857
|
-
STDOUT: ".stdout",
|
|
1858
|
-
STDERR: ".stderr",
|
|
1859
|
-
STDIN: ".stdin",
|
|
1860
|
-
// Scripts
|
|
1861
|
-
SH: ".sh",
|
|
1862
|
-
PY: ".py",
|
|
1863
|
-
CJS: ".cjs",
|
|
1864
|
-
// Config
|
|
1865
|
-
ENV: ".env",
|
|
1866
|
-
BAK: ".bak"
|
|
1867
|
-
};
|
|
1868
|
-
var SPECIAL_FILES = {
|
|
1869
|
-
LATEST_STATE: "latest.json",
|
|
1870
|
-
README: "README.md"
|
|
1871
|
-
};
|
|
1872
|
-
|
|
1873
|
-
// src/engine/process-cleanup.ts
|
|
1925
|
+
// src/engine/process/process-cleanup.ts
|
|
1874
1926
|
import { unlinkSync } from "fs";
|
|
1875
1927
|
|
|
1876
|
-
// src/engine/process-tree.ts
|
|
1928
|
+
// src/engine/process/process-tree.ts
|
|
1877
1929
|
import { execSync } from "child_process";
|
|
1878
1930
|
function discoverChildPids(parentPid) {
|
|
1879
1931
|
try {
|
|
@@ -1957,7 +2009,7 @@ function killProcessTreeSync(pid, childPids) {
|
|
|
1957
2009
|
}
|
|
1958
2010
|
}
|
|
1959
2011
|
|
|
1960
|
-
// src/engine/process-cleanup.ts
|
|
2012
|
+
// src/engine/process/process-cleanup.ts
|
|
1961
2013
|
function syncCleanupAllProcesses(processMap) {
|
|
1962
2014
|
for (const [, proc] of processMap) {
|
|
1963
2015
|
if (!proc.hasExited) {
|
|
@@ -1993,7 +2045,7 @@ function registerExitHandlers(processMap) {
|
|
|
1993
2045
|
});
|
|
1994
2046
|
}
|
|
1995
2047
|
|
|
1996
|
-
// src/engine/process-detector.ts
|
|
2048
|
+
// src/engine/process/process-detector.ts
|
|
1997
2049
|
function detectProcessRole(command) {
|
|
1998
2050
|
const tags = [];
|
|
1999
2051
|
let port;
|
|
@@ -2046,7 +2098,7 @@ function detectConnection(stdout) {
|
|
|
2046
2098
|
return DETECTION_PATTERNS.CONNECTION.some((p) => p.test(stdout));
|
|
2047
2099
|
}
|
|
2048
2100
|
|
|
2049
|
-
// src/engine/process-manager.ts
|
|
2101
|
+
// src/engine/process/process-manager.ts
|
|
2050
2102
|
var backgroundProcesses = /* @__PURE__ */ new Map();
|
|
2051
2103
|
var cleanupDone = false;
|
|
2052
2104
|
registerExitHandlers(backgroundProcesses);
|
|
@@ -3091,7 +3143,7 @@ var AttackGraph = class {
|
|
|
3091
3143
|
};
|
|
3092
3144
|
|
|
3093
3145
|
// src/shared/utils/agent-memory.ts
|
|
3094
|
-
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync4
|
|
3146
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
3095
3147
|
import { join as join3 } from "path";
|
|
3096
3148
|
var WorkingMemory = class {
|
|
3097
3149
|
entries = [];
|
|
@@ -3247,7 +3299,7 @@ var EpisodicMemory = class {
|
|
|
3247
3299
|
this.events = [];
|
|
3248
3300
|
}
|
|
3249
3301
|
};
|
|
3250
|
-
var MEMORY_FILE = join3(WORKSPACE.MEMORY,
|
|
3302
|
+
var MEMORY_FILE = join3(WORKSPACE.MEMORY, SPECIAL_FILES.PERSISTENT_KNOWLEDGE);
|
|
3251
3303
|
var PersistentMemory = class {
|
|
3252
3304
|
knowledge;
|
|
3253
3305
|
constructor() {
|
|
@@ -3338,7 +3390,7 @@ var PersistentMemory = class {
|
|
|
3338
3390
|
}
|
|
3339
3391
|
save() {
|
|
3340
3392
|
try {
|
|
3341
|
-
|
|
3393
|
+
ensureDirExists(WORKSPACE.MEMORY);
|
|
3342
3394
|
writeFileSync4(MEMORY_FILE, JSON.stringify(this.knowledge, null, 2));
|
|
3343
3395
|
} catch {
|
|
3344
3396
|
}
|
|
@@ -3602,6 +3654,22 @@ var SharedState = class {
|
|
|
3602
3654
|
this.data.missionChecklist.push({ id, text, isCompleted: false });
|
|
3603
3655
|
}
|
|
3604
3656
|
}
|
|
3657
|
+
/**
|
|
3658
|
+
* Restore mission checklist from persistence (direct injection).
|
|
3659
|
+
* WHY: avoids the add-then-update roundtrip that loadState previously needed.
|
|
3660
|
+
*/
|
|
3661
|
+
restoreMissionChecklist(items) {
|
|
3662
|
+
for (const item of items) {
|
|
3663
|
+
this.data.missionChecklist.push({ ...item });
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
/**
|
|
3667
|
+
* Restore a todo item from persistence (direct injection with original status).
|
|
3668
|
+
* WHY: addTodo always creates PENDING; this preserves the saved status.
|
|
3669
|
+
*/
|
|
3670
|
+
restoreTodoItem(item) {
|
|
3671
|
+
this.data.todo.push({ ...item });
|
|
3672
|
+
}
|
|
3605
3673
|
// --- Engagement & Scope ---
|
|
3606
3674
|
setEngagement(engagement) {
|
|
3607
3675
|
this.data.engagement = engagement;
|
|
@@ -3644,26 +3712,6 @@ var SharedState = class {
|
|
|
3644
3712
|
getTargets() {
|
|
3645
3713
|
return this.data.targets;
|
|
3646
3714
|
}
|
|
3647
|
-
addServiceFingerprint(ip, fingerprint) {
|
|
3648
|
-
const target = this.getTarget(ip);
|
|
3649
|
-
if (!target) return;
|
|
3650
|
-
target.services = target.services || [];
|
|
3651
|
-
target.services.push(fingerprint);
|
|
3652
|
-
target.primaryCategory = this.calculatePrimaryCategory(target.services);
|
|
3653
|
-
}
|
|
3654
|
-
calculatePrimaryCategory(services) {
|
|
3655
|
-
const counts = {};
|
|
3656
|
-
let winner = SERVICE_CATEGORIES.NETWORK;
|
|
3657
|
-
let max = 0;
|
|
3658
|
-
for (const s of services) {
|
|
3659
|
-
counts[s.category] = (counts[s.category] || 0) + 1;
|
|
3660
|
-
if (counts[s.category] > max) {
|
|
3661
|
-
max = counts[s.category];
|
|
3662
|
-
winner = s.category;
|
|
3663
|
-
}
|
|
3664
|
-
}
|
|
3665
|
-
return winner;
|
|
3666
|
-
}
|
|
3667
3715
|
// --- Findings & Loot ---
|
|
3668
3716
|
addFinding(finding) {
|
|
3669
3717
|
this.data.findings.push(finding);
|
|
@@ -3674,14 +3722,6 @@ var SharedState = class {
|
|
|
3674
3722
|
getFindingsBySeverity(severity) {
|
|
3675
3723
|
return this.data.findings.filter((f) => f.severity === severity);
|
|
3676
3724
|
}
|
|
3677
|
-
/** Returns findings with confidence >= threshold (default: CONFIRMED = 80) */
|
|
3678
|
-
getFindingsByConfidence(threshold = CONFIDENCE_THRESHOLDS.CONFIRMED) {
|
|
3679
|
-
return this.data.findings.filter((f) => f.confidence >= threshold);
|
|
3680
|
-
}
|
|
3681
|
-
/** True if confidence >= CONFIRMED (80) */
|
|
3682
|
-
isConfirmedFinding(finding) {
|
|
3683
|
-
return finding.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED;
|
|
3684
|
-
}
|
|
3685
3725
|
addLoot(loot) {
|
|
3686
3726
|
this.data.loot.push(loot);
|
|
3687
3727
|
}
|
|
@@ -5396,7 +5436,8 @@ var ENV_KEYS = {
|
|
|
5396
5436
|
THINKING: "PENTEST_THINKING",
|
|
5397
5437
|
THINKING_BUDGET: "PENTEST_THINKING_BUDGET"
|
|
5398
5438
|
};
|
|
5399
|
-
var DEFAULT_SEARCH_API_URL = "https://
|
|
5439
|
+
var DEFAULT_SEARCH_API_URL = "https://api.search.brave.com/res/v1/web/search";
|
|
5440
|
+
var DEFAULT_MODEL = "glm-4.7";
|
|
5400
5441
|
function getApiKey() {
|
|
5401
5442
|
return process.env[ENV_KEYS.API_KEY] || "";
|
|
5402
5443
|
}
|
|
@@ -5415,6 +5456,10 @@ function getSearchApiKey() {
|
|
|
5415
5456
|
function getSearchApiUrl() {
|
|
5416
5457
|
return process.env[ENV_KEYS.SEARCH_API_URL] || DEFAULT_SEARCH_API_URL;
|
|
5417
5458
|
}
|
|
5459
|
+
function isZaiProvider() {
|
|
5460
|
+
const baseUrl = getBaseUrl() || "";
|
|
5461
|
+
return baseUrl.includes("z.ai");
|
|
5462
|
+
}
|
|
5418
5463
|
function isThinkingEnabled() {
|
|
5419
5464
|
return process.env[ENV_KEYS.THINKING] === "true";
|
|
5420
5465
|
}
|
|
@@ -5422,8 +5467,26 @@ function getThinkingBudget() {
|
|
|
5422
5467
|
const val = parseInt(process.env[ENV_KEYS.THINKING_BUDGET] || "", 10);
|
|
5423
5468
|
return isNaN(val) ? 8e3 : Math.max(1024, val);
|
|
5424
5469
|
}
|
|
5425
|
-
function
|
|
5426
|
-
|
|
5470
|
+
function validateRequiredConfig() {
|
|
5471
|
+
const errors = [];
|
|
5472
|
+
if (!getApiKey()) {
|
|
5473
|
+
errors.push(
|
|
5474
|
+
`[config] PENTEST_API_KEY is required.
|
|
5475
|
+
Export it before running:
|
|
5476
|
+
export PENTEST_API_KEY=your_api_key
|
|
5477
|
+
For z.ai: get your key at https://api.z.ai`
|
|
5478
|
+
);
|
|
5479
|
+
}
|
|
5480
|
+
const budgetRaw = process.env[ENV_KEYS.THINKING_BUDGET];
|
|
5481
|
+
if (budgetRaw !== void 0 && budgetRaw !== "") {
|
|
5482
|
+
const parsed = parseInt(budgetRaw, 10);
|
|
5483
|
+
if (isNaN(parsed) || parsed < 1024) {
|
|
5484
|
+
errors.push(
|
|
5485
|
+
`[config] PENTEST_THINKING_BUDGET must be an integer \u2265 1024 (got: "${budgetRaw}")`
|
|
5486
|
+
);
|
|
5487
|
+
}
|
|
5488
|
+
}
|
|
5489
|
+
return errors;
|
|
5427
5490
|
}
|
|
5428
5491
|
|
|
5429
5492
|
// src/shared/constants/search-api.const.ts
|
|
@@ -5518,10 +5581,8 @@ function getPlaywrightPath() {
|
|
|
5518
5581
|
}
|
|
5519
5582
|
async function checkPlaywright() {
|
|
5520
5583
|
try {
|
|
5521
|
-
|
|
5584
|
+
return await new Promise((resolve) => {
|
|
5522
5585
|
const child = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_ACTION.VERSION], { shell: true });
|
|
5523
|
-
let stdout = "";
|
|
5524
|
-
child.stdout.on("data", (data) => stdout += data);
|
|
5525
5586
|
child.on("close", (code) => {
|
|
5526
5587
|
if (code === 0) {
|
|
5527
5588
|
const browserCheck = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_BROWSER.CHROMIUM, PLAYWRIGHT_ACTION.HELP], { shell: true });
|
|
@@ -5535,7 +5596,6 @@ async function checkPlaywright() {
|
|
|
5535
5596
|
});
|
|
5536
5597
|
child.on("error", () => resolve({ installed: false, browserInstalled: false }));
|
|
5537
5598
|
});
|
|
5538
|
-
return result2;
|
|
5539
5599
|
} catch {
|
|
5540
5600
|
return { installed: false, browserInstalled: false };
|
|
5541
5601
|
}
|
|
@@ -5646,13 +5706,12 @@ function buildBrowseScript(url, options, screenshotPath) {
|
|
|
5646
5706
|
const safeExtraHeaders = JSON.stringify(options.extraHeaders || {});
|
|
5647
5707
|
const playwrightPath = getPlaywrightPath();
|
|
5648
5708
|
const safePlaywrightPath = safeJsString(playwrightPath);
|
|
5649
|
-
const headlessMode = isBrowserHeadless();
|
|
5650
5709
|
return `
|
|
5651
5710
|
const { chromium } = require(${safePlaywrightPath});
|
|
5652
5711
|
|
|
5653
5712
|
(async () => {
|
|
5654
5713
|
const browser = await chromium.launch({
|
|
5655
|
-
headless:
|
|
5714
|
+
headless: true,
|
|
5656
5715
|
args: ['${PLAYWRIGHT_ARG.NO_SANDBOX}', '${PLAYWRIGHT_ARG.DISABLE_SETUID_SANDBOX}']
|
|
5657
5716
|
});
|
|
5658
5717
|
|
|
@@ -5796,7 +5855,7 @@ async function browseUrl(url, options = {}) {
|
|
|
5796
5855
|
};
|
|
5797
5856
|
}
|
|
5798
5857
|
}
|
|
5799
|
-
const screenshotPath = browserOptions.screenshot ? join6(
|
|
5858
|
+
const screenshotPath = browserOptions.screenshot ? join6(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME, `screenshot-${Date.now()}.png`) : void 0;
|
|
5800
5859
|
const script = buildBrowseScript(url, browserOptions, screenshotPath);
|
|
5801
5860
|
const result2 = await runPlaywrightScript(script, browserOptions.timeout, "browse");
|
|
5802
5861
|
if (!result2.success) {
|
|
@@ -5835,12 +5894,11 @@ async function fillAndSubmitForm(url, formData, options = {}) {
|
|
|
5835
5894
|
const safeFormData = JSON.stringify(formData);
|
|
5836
5895
|
const playwrightPath = getPlaywrightPath();
|
|
5837
5896
|
const safePlaywrightPath = safeJsString(playwrightPath);
|
|
5838
|
-
const headlessMode = process.env.HEADLESS !== "false";
|
|
5839
5897
|
const script = `
|
|
5840
5898
|
const { chromium } = require(${safePlaywrightPath});
|
|
5841
5899
|
|
|
5842
5900
|
(async () => {
|
|
5843
|
-
const browser = await chromium.launch({ headless:
|
|
5901
|
+
const browser = await chromium.launch({ headless: true });
|
|
5844
5902
|
const page = await browser.newPage();
|
|
5845
5903
|
|
|
5846
5904
|
try {
|
|
@@ -5925,12 +5983,16 @@ var SEARCH_TIMEOUT_MS = 15e3;
|
|
|
5925
5983
|
function getErrorMessage(error) {
|
|
5926
5984
|
return error instanceof Error ? error.message : String(error);
|
|
5927
5985
|
}
|
|
5986
|
+
function generateRequestId() {
|
|
5987
|
+
return crypto.randomUUID();
|
|
5988
|
+
}
|
|
5928
5989
|
async function searchWithGLM(query, apiKey, apiUrl) {
|
|
5929
5990
|
debugLog("search", "GLM request START", { apiUrl, query });
|
|
5930
5991
|
const requestBody = {
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
stream: false
|
|
5992
|
+
request_id: generateRequestId(),
|
|
5993
|
+
tool: "web-search-pro",
|
|
5994
|
+
stream: false,
|
|
5995
|
+
messages: [{ role: LLM_ROLES.USER, content: query }]
|
|
5934
5996
|
};
|
|
5935
5997
|
debugLog("search", "GLM request body", requestBody);
|
|
5936
5998
|
let response;
|
|
@@ -5975,11 +6037,7 @@ async function searchWithGLM(query, apiKey, apiUrl) {
|
|
|
5975
6037
|
debugLog("search", "GLM has tool_calls", { count: data.choices[0].message.tool_calls.length });
|
|
5976
6038
|
for (const tc of data.choices[0].message.tool_calls) {
|
|
5977
6039
|
if (tc.function?.arguments) {
|
|
5978
|
-
|
|
5979
|
-
const args = JSON.parse(tc.function.arguments);
|
|
5980
|
-
results += JSON.stringify(args, null, 2) + "\n";
|
|
5981
|
-
} catch {
|
|
5982
|
-
}
|
|
6040
|
+
results += tc.function.arguments + "\n";
|
|
5983
6041
|
}
|
|
5984
6042
|
}
|
|
5985
6043
|
}
|
|
@@ -6181,55 +6239,40 @@ async function parseNmap(xmlPath) {
|
|
|
6181
6239
|
}
|
|
6182
6240
|
}
|
|
6183
6241
|
async function searchCVE(service, version) {
|
|
6242
|
+
const query = version ? `${service} ${version}` : service;
|
|
6184
6243
|
try {
|
|
6185
|
-
|
|
6186
|
-
|
|
6244
|
+
const output = execFileSync("searchsploit", [query, "--color", "never"], {
|
|
6245
|
+
encoding: "utf-8",
|
|
6246
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
6247
|
+
timeout: SEARCH_LIMIT.TIMEOUT_MS
|
|
6248
|
+
});
|
|
6249
|
+
const lines = output.trim().split("\n").slice(0, SEARCH_LIMIT.MAX_OUTPUT_LINES);
|
|
6187
6250
|
return {
|
|
6188
|
-
success:
|
|
6189
|
-
output: ""
|
|
6190
|
-
error: getErrorMessage2(error)
|
|
6251
|
+
success: true,
|
|
6252
|
+
output: lines.join("\n") || `No exploits found for ${query}`
|
|
6191
6253
|
};
|
|
6192
|
-
}
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
|
|
6196
|
-
|
|
6197
|
-
try {
|
|
6198
|
-
const output = execFileSync("searchsploit", [query, "--color", "never"], {
|
|
6199
|
-
encoding: "utf-8",
|
|
6200
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
6201
|
-
timeout: SEARCH_LIMIT.TIMEOUT_MS
|
|
6202
|
-
});
|
|
6203
|
-
const lines = output.trim().split("\n").slice(0, SEARCH_LIMIT.MAX_OUTPUT_LINES);
|
|
6204
|
-
return {
|
|
6205
|
-
success: true,
|
|
6206
|
-
output: lines.join("\n") || `No exploits found for ${query}`
|
|
6207
|
-
};
|
|
6208
|
-
} catch (e) {
|
|
6209
|
-
const execError = e;
|
|
6210
|
-
const stderr = String(execError.stderr || "");
|
|
6211
|
-
const stdout = String(execError.stdout || "");
|
|
6212
|
-
if (stderr.includes("No results")) {
|
|
6213
|
-
return {
|
|
6214
|
-
success: true,
|
|
6215
|
-
output: `No exploits found for ${query}`
|
|
6216
|
-
};
|
|
6217
|
-
}
|
|
6254
|
+
} catch (e) {
|
|
6255
|
+
const execError = e;
|
|
6256
|
+
const stderr = String(execError.stderr || "");
|
|
6257
|
+
const stdout = String(execError.stdout || "");
|
|
6258
|
+
if (stderr.includes("No results")) {
|
|
6218
6259
|
return {
|
|
6219
6260
|
success: true,
|
|
6220
|
-
output:
|
|
6261
|
+
output: `No exploits found for ${query}`
|
|
6221
6262
|
};
|
|
6222
6263
|
}
|
|
6223
|
-
} catch (error) {
|
|
6224
6264
|
return {
|
|
6225
|
-
success:
|
|
6226
|
-
output:
|
|
6227
|
-
error: getErrorMessage2(error)
|
|
6265
|
+
success: true,
|
|
6266
|
+
output: stdout || `No exploits found for ${query}`
|
|
6228
6267
|
};
|
|
6229
6268
|
}
|
|
6230
6269
|
}
|
|
6231
|
-
async function webSearch(query
|
|
6270
|
+
async function webSearch(query) {
|
|
6232
6271
|
debugLog("search", "webSearch START", { query });
|
|
6272
|
+
if (isZaiProvider()) {
|
|
6273
|
+
debugLog("search", "Using z.ai LLM-native web search (web_search_20250305)");
|
|
6274
|
+
return await searchWithZai(query);
|
|
6275
|
+
}
|
|
6233
6276
|
const apiKey = getSearchApiKey();
|
|
6234
6277
|
const apiUrl = getSearchApiUrl();
|
|
6235
6278
|
debugLog("search", "Search API config", {
|
|
@@ -6257,7 +6300,7 @@ async function webSearch(query, _engine) {
|
|
|
6257
6300
|
};
|
|
6258
6301
|
}
|
|
6259
6302
|
try {
|
|
6260
|
-
if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU)
|
|
6303
|
+
if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU)) {
|
|
6261
6304
|
debugLog("search", "Using GLM search");
|
|
6262
6305
|
return await searchWithGLM(query, apiKey, apiUrl);
|
|
6263
6306
|
} else if (apiUrl.includes(SEARCH_URL_PATTERN.BRAVE)) {
|
|
@@ -6279,6 +6322,111 @@ async function webSearch(query, _engine) {
|
|
|
6279
6322
|
};
|
|
6280
6323
|
}
|
|
6281
6324
|
}
|
|
6325
|
+
var ZAI_SEARCH = {
|
|
6326
|
+
TIMEOUT_MS: 2e4,
|
|
6327
|
+
MAX_TOKENS: 1024,
|
|
6328
|
+
MAX_USES: 3,
|
|
6329
|
+
TOOL_TYPE: "web_search_20250305",
|
|
6330
|
+
TOOL_NAME: "web_search",
|
|
6331
|
+
ANTHROPIC_VERSION: "2023-06-01",
|
|
6332
|
+
// §25-1 Retry constants
|
|
6333
|
+
MAX_RETRIES: 2,
|
|
6334
|
+
RETRY_BASE_MS: 500
|
|
6335
|
+
};
|
|
6336
|
+
function isTransientHttpError(status) {
|
|
6337
|
+
return status === 429 || status >= 500;
|
|
6338
|
+
}
|
|
6339
|
+
async function searchWithZai(query) {
|
|
6340
|
+
const apiKey = getApiKey();
|
|
6341
|
+
const baseUrl = (getBaseUrl() || "https://api.z.ai/api/anthropic").replace(/\/+$/, "");
|
|
6342
|
+
const url = `${baseUrl}/v1/messages`;
|
|
6343
|
+
const requestBody = JSON.stringify({
|
|
6344
|
+
model: getModel() || DEFAULT_MODEL,
|
|
6345
|
+
max_tokens: ZAI_SEARCH.MAX_TOKENS,
|
|
6346
|
+
tools: [{ type: ZAI_SEARCH.TOOL_TYPE, name: ZAI_SEARCH.TOOL_NAME, max_uses: ZAI_SEARCH.MAX_USES }],
|
|
6347
|
+
messages: [{ role: "user", content: `Search the web for: ${query}` }]
|
|
6348
|
+
});
|
|
6349
|
+
let lastError = "";
|
|
6350
|
+
for (let attempt = 0; attempt <= ZAI_SEARCH.MAX_RETRIES; attempt++) {
|
|
6351
|
+
if (attempt > 0) {
|
|
6352
|
+
const baseWait = ZAI_SEARCH.RETRY_BASE_MS * Math.pow(2, attempt - 1);
|
|
6353
|
+
const jitter = baseWait * 0.25 * (Math.random() * 2 - 1);
|
|
6354
|
+
await new Promise((r) => setTimeout(r, Math.round(baseWait + jitter)));
|
|
6355
|
+
debugLog("search", `z.ai web search RETRY attempt=${attempt}`);
|
|
6356
|
+
}
|
|
6357
|
+
let response;
|
|
6358
|
+
try {
|
|
6359
|
+
response = await fetch(url, {
|
|
6360
|
+
method: "POST",
|
|
6361
|
+
headers: {
|
|
6362
|
+
"Content-Type": "application/json",
|
|
6363
|
+
"x-api-key": apiKey,
|
|
6364
|
+
"anthropic-version": ZAI_SEARCH.ANTHROPIC_VERSION
|
|
6365
|
+
},
|
|
6366
|
+
body: requestBody,
|
|
6367
|
+
signal: AbortSignal.timeout(ZAI_SEARCH.TIMEOUT_MS)
|
|
6368
|
+
});
|
|
6369
|
+
} catch (err) {
|
|
6370
|
+
lastError = err instanceof Error ? err.message : String(err);
|
|
6371
|
+
debugLog("search", "z.ai web search fetch FAILED", { attempt, error: lastError });
|
|
6372
|
+
continue;
|
|
6373
|
+
}
|
|
6374
|
+
if (response.ok) {
|
|
6375
|
+
let data;
|
|
6376
|
+
try {
|
|
6377
|
+
data = await response.json();
|
|
6378
|
+
} catch (err) {
|
|
6379
|
+
return { success: false, output: "", error: `z.ai web search invalid JSON: ${err instanceof Error ? err.message : String(err)}` };
|
|
6380
|
+
}
|
|
6381
|
+
const lines = [];
|
|
6382
|
+
for (const block of data.content || []) {
|
|
6383
|
+
if (block.type === "text" && block.text) {
|
|
6384
|
+
lines.push(block.text);
|
|
6385
|
+
} else if (block.type === "server_tool_use" && block.input?.results) {
|
|
6386
|
+
const refs = block.input.results.filter((r) => !!r.url && !!r.title).map((r, i) => `[${i + 1}] ${r.title}
|
|
6387
|
+
${r.url}`);
|
|
6388
|
+
if (refs.length > 0) {
|
|
6389
|
+
lines.push("\n--- Search Sources ---\n" + refs.join("\n"));
|
|
6390
|
+
}
|
|
6391
|
+
}
|
|
6392
|
+
}
|
|
6393
|
+
const output = lines.join("\n").trim();
|
|
6394
|
+
debugLog("search", "z.ai web search COMPLETE", { attempt, outputLength: output.length });
|
|
6395
|
+
return { success: true, output: output || `No results found for: ${query}` };
|
|
6396
|
+
}
|
|
6397
|
+
let errorText = "";
|
|
6398
|
+
try {
|
|
6399
|
+
errorText = await response.text();
|
|
6400
|
+
} catch {
|
|
6401
|
+
}
|
|
6402
|
+
lastError = `z.ai web search API error ${response.status}: ${errorText.slice(0, 500)}`;
|
|
6403
|
+
debugLog("search", "z.ai web search ERROR", { attempt, status: response.status, error: errorText });
|
|
6404
|
+
if (!isTransientHttpError(response.status)) {
|
|
6405
|
+
break;
|
|
6406
|
+
}
|
|
6407
|
+
}
|
|
6408
|
+
return { success: false, output: "", error: lastError };
|
|
6409
|
+
}
|
|
6410
|
+
async function extractURLs(text) {
|
|
6411
|
+
try {
|
|
6412
|
+
const urlRegex = /https?:\/\/[^\s<>"{}|\\^`\[\]]+/g;
|
|
6413
|
+
const urls = text.match(urlRegex) || [];
|
|
6414
|
+
const uniqueURLs = [...new Set(urls)];
|
|
6415
|
+
return {
|
|
6416
|
+
success: true,
|
|
6417
|
+
output: JSON.stringify({
|
|
6418
|
+
count: uniqueURLs.length,
|
|
6419
|
+
urls: uniqueURLs
|
|
6420
|
+
}, null, 2)
|
|
6421
|
+
};
|
|
6422
|
+
} catch (error) {
|
|
6423
|
+
return {
|
|
6424
|
+
success: false,
|
|
6425
|
+
output: "",
|
|
6426
|
+
error: getErrorMessage2(error)
|
|
6427
|
+
};
|
|
6428
|
+
}
|
|
6429
|
+
}
|
|
6282
6430
|
|
|
6283
6431
|
// src/shared/utils/owasp-knowledge.ts
|
|
6284
6432
|
var OWASP_2017 = {
|
|
@@ -6479,8 +6627,83 @@ Use 'get_owasp_knowledge' with edition="2023" or similar to see specific details
|
|
|
6479
6627
|
`.trim();
|
|
6480
6628
|
}
|
|
6481
6629
|
|
|
6630
|
+
// src/shared/utils/domain-tools-factory.ts
|
|
6631
|
+
function param(params, name, defaultValue) {
|
|
6632
|
+
const value = params[name];
|
|
6633
|
+
if (value === void 0 || value === null) {
|
|
6634
|
+
if (defaultValue !== void 0) return defaultValue;
|
|
6635
|
+
throw new Error(`Missing required parameter: ${name}`);
|
|
6636
|
+
}
|
|
6637
|
+
return String(value);
|
|
6638
|
+
}
|
|
6639
|
+
function paramNumber(params, name, defaultValue) {
|
|
6640
|
+
const value = params[name];
|
|
6641
|
+
if (value === void 0 || value === null) {
|
|
6642
|
+
if (defaultValue !== void 0) return defaultValue;
|
|
6643
|
+
throw new Error(`Missing required parameter: ${name}`);
|
|
6644
|
+
}
|
|
6645
|
+
return Number(value);
|
|
6646
|
+
}
|
|
6647
|
+
function ensureUrlProtocol(target) {
|
|
6648
|
+
if (/^https?:\/\//i.test(target)) return target;
|
|
6649
|
+
return `http://${target}`;
|
|
6650
|
+
}
|
|
6651
|
+
function createCommandTool(def) {
|
|
6652
|
+
const parameters = {};
|
|
6653
|
+
for (const paramName of def.params) {
|
|
6654
|
+
parameters[paramName] = { type: "string", description: paramName };
|
|
6655
|
+
}
|
|
6656
|
+
if (def.optionalParams) {
|
|
6657
|
+
for (const paramName of Object.keys(def.optionalParams)) {
|
|
6658
|
+
parameters[paramName] = { type: "string", description: `${paramName} (optional)` };
|
|
6659
|
+
}
|
|
6660
|
+
}
|
|
6661
|
+
return {
|
|
6662
|
+
name: def.name,
|
|
6663
|
+
description: def.description,
|
|
6664
|
+
parameters,
|
|
6665
|
+
required: def.params,
|
|
6666
|
+
execute: async (params) => {
|
|
6667
|
+
const resolvedArgs = def.args.map((arg) => {
|
|
6668
|
+
const match = arg.match(/^\{(\w+)\}$/);
|
|
6669
|
+
if (match) {
|
|
6670
|
+
const paramName = match[1];
|
|
6671
|
+
let value;
|
|
6672
|
+
if (def.optionalParams && def.optionalParams[paramName] !== void 0) {
|
|
6673
|
+
value = param(params, paramName, String(def.optionalParams[paramName]));
|
|
6674
|
+
} else {
|
|
6675
|
+
value = param(params, paramName);
|
|
6676
|
+
}
|
|
6677
|
+
if (def.requiresUrl && paramName === "target") {
|
|
6678
|
+
value = ensureUrlProtocol(value);
|
|
6679
|
+
}
|
|
6680
|
+
return value;
|
|
6681
|
+
}
|
|
6682
|
+
return arg;
|
|
6683
|
+
});
|
|
6684
|
+
return await runCommand(def.cmd, resolvedArgs);
|
|
6685
|
+
}
|
|
6686
|
+
};
|
|
6687
|
+
}
|
|
6688
|
+
function createTool(name, description, parameters, required, execute) {
|
|
6689
|
+
return { name, description, parameters, required, execute };
|
|
6690
|
+
}
|
|
6691
|
+
function createDomain(def) {
|
|
6692
|
+
const tools = def.commands?.map(createCommandTool) ?? [];
|
|
6693
|
+
const config = {
|
|
6694
|
+
name: def.category,
|
|
6695
|
+
description: def.description,
|
|
6696
|
+
tools,
|
|
6697
|
+
dangerLevel: def.dangerLevel,
|
|
6698
|
+
defaultApproval: def.defaultApproval,
|
|
6699
|
+
commonPorts: def.commonPorts,
|
|
6700
|
+
commonServices: def.commonServices
|
|
6701
|
+
};
|
|
6702
|
+
return { tools, config };
|
|
6703
|
+
}
|
|
6704
|
+
|
|
6482
6705
|
// src/engine/tools/pentest-intel-tools.ts
|
|
6483
|
-
var createIntelTools = (
|
|
6706
|
+
var createIntelTools = () => [
|
|
6484
6707
|
{
|
|
6485
6708
|
name: TOOL_NAMES.PARSE_NMAP,
|
|
6486
6709
|
description: "Parse nmap XML output to structured JSON",
|
|
@@ -6566,7 +6789,8 @@ Can extract forms and inputs for security testing.`,
|
|
|
6566
6789
|
},
|
|
6567
6790
|
required: ["url"],
|
|
6568
6791
|
execute: async (p) => {
|
|
6569
|
-
const
|
|
6792
|
+
const url = ensureUrlProtocol(p.url);
|
|
6793
|
+
const result2 = await browseUrl(url, {
|
|
6570
6794
|
extractForms: p.extract_forms,
|
|
6571
6795
|
extractLinks: p.extract_links,
|
|
6572
6796
|
screenshot: p.screenshot,
|
|
@@ -6596,7 +6820,8 @@ Can extract forms and inputs for security testing.`,
|
|
|
6596
6820
|
},
|
|
6597
6821
|
required: ["url", "fields"],
|
|
6598
6822
|
execute: async (p) => {
|
|
6599
|
-
const
|
|
6823
|
+
const url = ensureUrlProtocol(p.url);
|
|
6824
|
+
const result2 = await fillAndSubmitForm(url, p.fields);
|
|
6600
6825
|
return {
|
|
6601
6826
|
success: result2.success,
|
|
6602
6827
|
output: result2.output,
|
|
@@ -6739,6 +6964,19 @@ For CVEs not in this list, use web_search to find them.`
|
|
|
6739
6964
|
output: `CVE ${cveId} not in local database. Use web_search({ query: "${cveId} exploit POC" }) to find more information online.`
|
|
6740
6965
|
};
|
|
6741
6966
|
}
|
|
6967
|
+
},
|
|
6968
|
+
{
|
|
6969
|
+
name: TOOL_NAMES.EXTRACT_URLS,
|
|
6970
|
+
description: `Extract all URLs from a block of text.
|
|
6971
|
+
Use this to:
|
|
6972
|
+
- Pull links from command output, HTML, or tool results
|
|
6973
|
+
- Discover subdomains and endpoints from scan results
|
|
6974
|
+
- Find API endpoints buried in verbose output`,
|
|
6975
|
+
parameters: {
|
|
6976
|
+
text: { type: "string", description: "Text to extract URLs from" }
|
|
6977
|
+
},
|
|
6978
|
+
required: ["text"],
|
|
6979
|
+
execute: async (p) => extractURLs(p.text)
|
|
6742
6980
|
}
|
|
6743
6981
|
];
|
|
6744
6982
|
|
|
@@ -7090,7 +7328,9 @@ function getContextRecommendations(context, variantCount) {
|
|
|
7090
7328
|
}
|
|
7091
7329
|
|
|
7092
7330
|
// src/engine/tools/pentest-attack-tools.ts
|
|
7093
|
-
|
|
7331
|
+
import { existsSync as existsSync6, statSync, readdirSync } from "fs";
|
|
7332
|
+
import { join as join7 } from "path";
|
|
7333
|
+
var createAttackTools = () => [
|
|
7094
7334
|
{
|
|
7095
7335
|
name: TOOL_NAMES.HASH_CRACK,
|
|
7096
7336
|
description: `Crack password hashes using hashcat or john.
|
|
@@ -7209,8 +7449,6 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7209
7449
|
}
|
|
7210
7450
|
},
|
|
7211
7451
|
execute: async (p) => {
|
|
7212
|
-
const { existsSync: existsSync12, statSync: statSync3, readdirSync: readdirSync4 } = await import("fs");
|
|
7213
|
-
const { join: join13 } = await import("path");
|
|
7214
7452
|
const category = p.category || "";
|
|
7215
7453
|
const search = p.search || "";
|
|
7216
7454
|
const minSize = p.min_size || 0;
|
|
@@ -7241,12 +7479,12 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7241
7479
|
const q = search.toLowerCase();
|
|
7242
7480
|
return fileName.toLowerCase().includes(q) || filePath.toLowerCase().includes(q);
|
|
7243
7481
|
};
|
|
7244
|
-
const results = ["# Available Wordlists on System
|
|
7482
|
+
const results = ["# Available Wordlists on System\n"];
|
|
7245
7483
|
let totalCount = 0;
|
|
7246
7484
|
const processFile = (fullPath, fileName) => {
|
|
7247
7485
|
const ext = fileName.split(".").pop()?.toLowerCase();
|
|
7248
7486
|
if (!WORDLIST_EXTENSIONS.has(ext || "")) return;
|
|
7249
|
-
const stats =
|
|
7487
|
+
const stats = statSync(fullPath);
|
|
7250
7488
|
if (stats.size < minSize) return;
|
|
7251
7489
|
if (!matchesCategory(fullPath)) return;
|
|
7252
7490
|
if (!matchesSearch(fullPath, fileName)) return;
|
|
@@ -7256,16 +7494,16 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7256
7494
|
results.push("");
|
|
7257
7495
|
};
|
|
7258
7496
|
const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
|
|
7259
|
-
if (depth > maxDepth || !
|
|
7497
|
+
if (depth > maxDepth || !existsSync6(dirPath)) return;
|
|
7260
7498
|
let entries;
|
|
7261
7499
|
try {
|
|
7262
|
-
entries =
|
|
7500
|
+
entries = readdirSync(dirPath, { withFileTypes: true });
|
|
7263
7501
|
} catch {
|
|
7264
7502
|
return;
|
|
7265
7503
|
}
|
|
7266
7504
|
for (const entry of entries) {
|
|
7267
7505
|
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
|
|
7268
|
-
const fullPath =
|
|
7506
|
+
const fullPath = join7(dirPath, entry.name);
|
|
7269
7507
|
if (entry.isDirectory()) {
|
|
7270
7508
|
scanDir(fullPath, maxDepth, depth + 1);
|
|
7271
7509
|
continue;
|
|
@@ -7278,19 +7516,26 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7278
7516
|
}
|
|
7279
7517
|
};
|
|
7280
7518
|
for (const basePath of scanPaths) {
|
|
7281
|
-
results.push(
|
|
7519
|
+
results.push(`
|
|
7520
|
+
## ${basePath}
|
|
7521
|
+
`);
|
|
7282
7522
|
scanDir(basePath);
|
|
7283
7523
|
}
|
|
7284
7524
|
if (totalCount === 0) {
|
|
7285
|
-
results.push(
|
|
7286
|
-
|
|
7525
|
+
results.push(`
|
|
7526
|
+
No wordlists found matching your criteria.`);
|
|
7527
|
+
results.push(`
|
|
7528
|
+
Tips:`);
|
|
7287
7529
|
results.push(`- Try without filters to see all available wordlists`);
|
|
7288
7530
|
results.push(`- Ensure seclists/wordlists packages are installed`);
|
|
7289
7531
|
results.push(`- Common locations: /usr/share/seclists/, /usr/share/wordlists/`);
|
|
7290
7532
|
}
|
|
7291
|
-
results.push(
|
|
7292
|
-
|
|
7293
|
-
results.push(
|
|
7533
|
+
results.push(`
|
|
7534
|
+
---`);
|
|
7535
|
+
results.push(`
|
|
7536
|
+
Total wordlists found: ${totalCount}`);
|
|
7537
|
+
results.push(`
|
|
7538
|
+
## Commonly Used Wordlists (check if available)`);
|
|
7294
7539
|
results.push(`- RockYou: /usr/share/wordlists/rockyou.txt (14M passwords)`);
|
|
7295
7540
|
results.push(`- Dirb common: /usr/share/wordlists/dirb/common.txt`);
|
|
7296
7541
|
results.push(`- SecLists passwords: /usr/share/seclists/Passwords/Common-Credentials/`);
|
|
@@ -7298,7 +7543,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7298
7543
|
results.push(`- SecLists DNS: /usr/share/seclists/Discovery/DNS/`);
|
|
7299
7544
|
return {
|
|
7300
7545
|
success: true,
|
|
7301
|
-
output: results.join("
|
|
7546
|
+
output: results.join("\n")
|
|
7302
7547
|
};
|
|
7303
7548
|
}
|
|
7304
7549
|
}
|
|
@@ -7308,8 +7553,8 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
7308
7553
|
var createPentestTools = (state, events) => [
|
|
7309
7554
|
...createStateTools(state, events),
|
|
7310
7555
|
...createTargetTools(state),
|
|
7311
|
-
...createIntelTools(
|
|
7312
|
-
...createAttackTools(
|
|
7556
|
+
...createIntelTools(),
|
|
7557
|
+
...createAttackTools()
|
|
7313
7558
|
];
|
|
7314
7559
|
|
|
7315
7560
|
// src/engine/tools/agents.ts
|
|
@@ -7467,6 +7712,7 @@ var NETWORK_FILTERS = {
|
|
|
7467
7712
|
};
|
|
7468
7713
|
|
|
7469
7714
|
// src/engine/tools/network-attack.ts
|
|
7715
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
7470
7716
|
var createNetworkAttackTools = () => [
|
|
7471
7717
|
{
|
|
7472
7718
|
name: TOOL_NAMES.ARP_SPOOF,
|
|
@@ -7642,8 +7888,7 @@ Requires root/sudo privileges.`,
|
|
|
7642
7888
|
const iface = p.interface || "";
|
|
7643
7889
|
const duration = p.duration || NETWORK_CONFIG.DEFAULT_SPOOF_DURATION;
|
|
7644
7890
|
const hostsFile = createTempFile(FILE_EXTENSIONS.HOSTS);
|
|
7645
|
-
|
|
7646
|
-
writeFileSync10(hostsFile, `${spoofIp} ${domain}
|
|
7891
|
+
writeFileSync6(hostsFile, `${spoofIp} ${domain}
|
|
7647
7892
|
${spoofIp} *.${domain}
|
|
7648
7893
|
`);
|
|
7649
7894
|
const ifaceFlag = iface ? `-i ${iface}` : "";
|
|
@@ -7893,19 +8138,6 @@ var ZombieHunter = class {
|
|
|
7893
8138
|
}
|
|
7894
8139
|
return lines.join("\n");
|
|
7895
8140
|
}
|
|
7896
|
-
/**
|
|
7897
|
-
* Get cleanup command suggestions for Orchestrator
|
|
7898
|
-
*/
|
|
7899
|
-
getCleanupCommands(zombies) {
|
|
7900
|
-
return zombies.map((z) => `bg_cleanup({ processId: "${z.processId}", killOrphans: true })`);
|
|
7901
|
-
}
|
|
7902
|
-
/**
|
|
7903
|
-
* Check if any critical processes are among the zombies
|
|
7904
|
-
* (shells, listeners that might be active shells)
|
|
7905
|
-
*/
|
|
7906
|
-
hasCriticalZombies(zombies) {
|
|
7907
|
-
return false;
|
|
7908
|
-
}
|
|
7909
8141
|
};
|
|
7910
8142
|
|
|
7911
8143
|
// src/engine/resource/health-monitor.ts
|
|
@@ -7990,28 +8222,6 @@ var HealthMonitor = class {
|
|
|
7990
8222
|
if (data.recommendations.length > MAX_RECOMMENDATIONS_FOR_HEALTHY) return HEALTH_STATUS.WARNING;
|
|
7991
8223
|
return HEALTH_STATUS.HEALTHY;
|
|
7992
8224
|
}
|
|
7993
|
-
/**
|
|
7994
|
-
* Generate summary string for Orchestrator context
|
|
7995
|
-
*/
|
|
7996
|
-
generateSummary() {
|
|
7997
|
-
const status = this.check();
|
|
7998
|
-
const lines = [];
|
|
7999
|
-
lines.push(`Health: ${status.overall.toUpperCase()}`);
|
|
8000
|
-
lines.push(`Processes: ${status.processes.running}/${status.processes.total} running`);
|
|
8001
|
-
if (status.ports.inUse.length > 0) {
|
|
8002
|
-
lines.push(`Ports in use: ${status.ports.inUse.join(", ")}`);
|
|
8003
|
-
}
|
|
8004
|
-
if (status.ports.conflicts.length > 0) {
|
|
8005
|
-
lines.push(`Port conflicts: ${status.ports.conflicts.join("; ")}`);
|
|
8006
|
-
}
|
|
8007
|
-
if (status.recommendations.length > 0) {
|
|
8008
|
-
lines.push(`Recommendations:`);
|
|
8009
|
-
for (const r of status.recommendations) {
|
|
8010
|
-
lines.push(` - ${r}`);
|
|
8011
|
-
}
|
|
8012
|
-
}
|
|
8013
|
-
return lines.join("\n");
|
|
8014
|
-
}
|
|
8015
8225
|
};
|
|
8016
8226
|
|
|
8017
8227
|
// src/engine/tools/resource.ts
|
|
@@ -8026,10 +8236,8 @@ var resourceTools = [
|
|
|
8026
8236
|
description: `Query the status of all background processes.
|
|
8027
8237
|
Shows running tasks, port usage, and health status.
|
|
8028
8238
|
Used by the Orchestrator for resource management.`,
|
|
8029
|
-
parameters: {
|
|
8030
|
-
|
|
8031
|
-
properties: {}
|
|
8032
|
-
},
|
|
8239
|
+
parameters: {},
|
|
8240
|
+
// No parameters needed
|
|
8033
8241
|
async execute() {
|
|
8034
8242
|
const processes = listBackgroundProcesses();
|
|
8035
8243
|
const health = healthMonitor.check();
|
|
@@ -8081,19 +8289,17 @@ Used by the Orchestrator for resource management.`,
|
|
|
8081
8289
|
If processId is specified, terminates that process and its children.
|
|
8082
8290
|
If killOrphans is true, also cleans up child processes whose parent has died.`,
|
|
8083
8291
|
parameters: {
|
|
8084
|
-
|
|
8085
|
-
|
|
8086
|
-
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
description: "Also clean up orphan child processes (default: true)",
|
|
8093
|
-
default: true
|
|
8094
|
-
}
|
|
8292
|
+
processId: {
|
|
8293
|
+
type: "string",
|
|
8294
|
+
description: "Process ID to clean up (optional)"
|
|
8295
|
+
},
|
|
8296
|
+
killOrphans: {
|
|
8297
|
+
type: "boolean",
|
|
8298
|
+
description: "Also clean up orphan child processes (default: true)",
|
|
8299
|
+
default: true
|
|
8095
8300
|
}
|
|
8096
8301
|
},
|
|
8302
|
+
required: [],
|
|
8097
8303
|
async execute(params) {
|
|
8098
8304
|
const results = [];
|
|
8099
8305
|
if (params.processId) {
|
|
@@ -8134,14 +8340,27 @@ If killOrphans is true, also cleans up child processes whose parent has died.`,
|
|
|
8134
8340
|
name: TOOL_NAMES.HEALTH_CHECK,
|
|
8135
8341
|
description: `Check system resource health.
|
|
8136
8342
|
Returns recommendations on process status, port conflicts, long-running tasks, etc.`,
|
|
8137
|
-
parameters: {
|
|
8138
|
-
|
|
8139
|
-
properties: {}
|
|
8140
|
-
},
|
|
8343
|
+
parameters: {},
|
|
8344
|
+
// No parameters needed
|
|
8141
8345
|
async execute() {
|
|
8142
8346
|
const status = healthMonitor.check();
|
|
8143
8347
|
const zombies = zombieHunter.scan();
|
|
8144
|
-
const
|
|
8348
|
+
const lines = [];
|
|
8349
|
+
lines.push(`Health: ${status.overall.toUpperCase()}`);
|
|
8350
|
+
lines.push(`Processes: ${status.processes.running}/${status.processes.total} running`);
|
|
8351
|
+
if (status.ports.inUse.length > 0) {
|
|
8352
|
+
lines.push(`Ports in use: ${status.ports.inUse.join(", ")}`);
|
|
8353
|
+
}
|
|
8354
|
+
if (status.ports.conflicts.length > 0) {
|
|
8355
|
+
lines.push(`Port conflicts: ${status.ports.conflicts.join("; ")}`);
|
|
8356
|
+
}
|
|
8357
|
+
if (status.recommendations.length > 0) {
|
|
8358
|
+
lines.push(`Recommendations:`);
|
|
8359
|
+
for (const r of status.recommendations) {
|
|
8360
|
+
lines.push(` - ${r}`);
|
|
8361
|
+
}
|
|
8362
|
+
}
|
|
8363
|
+
const output = lines.join("\n");
|
|
8145
8364
|
if (zombies.length > 0) {
|
|
8146
8365
|
return result(false, output + "\n\n" + zombieHunter.generateReport(zombies));
|
|
8147
8366
|
}
|
|
@@ -8231,205 +8450,143 @@ var SMB_PORTS = [
|
|
|
8231
8450
|
];
|
|
8232
8451
|
|
|
8233
8452
|
// src/domains/network/tools.ts
|
|
8234
|
-
var NETWORK_TOOLS =
|
|
8235
|
-
|
|
8236
|
-
name: TOOL_NAMES.NMAP_QUICK,
|
|
8237
|
-
description: "Quick nmap scan - fast discovery",
|
|
8238
|
-
parameters: {
|
|
8239
|
-
target: { type: "string", description: "Target IP/CIDR" }
|
|
8240
|
-
},
|
|
8241
|
-
required: ["target"],
|
|
8242
|
-
execute: async (params) => {
|
|
8243
|
-
const target = params.target;
|
|
8244
|
-
return await runCommand("nmap", ["-Pn", "-T4", "-F", target]);
|
|
8245
|
-
}
|
|
8246
|
-
},
|
|
8247
|
-
{
|
|
8248
|
-
name: TOOL_NAMES.NMAP_FULL,
|
|
8249
|
-
description: "Full nmap scan - comprehensive",
|
|
8250
|
-
parameters: {
|
|
8251
|
-
target: { type: "string", description: "Target IP/CIDR" }
|
|
8252
|
-
},
|
|
8253
|
-
required: ["target"],
|
|
8254
|
-
execute: async (params) => {
|
|
8255
|
-
const target = params.target;
|
|
8256
|
-
return await runCommand("nmap", ["-Pn", "-T4", "-sV", "-O", "-p-", target]);
|
|
8257
|
-
}
|
|
8258
|
-
},
|
|
8259
|
-
{
|
|
8260
|
-
name: TOOL_NAMES.RUSTSCAN,
|
|
8261
|
-
description: "RustScan - very fast port discovery",
|
|
8262
|
-
parameters: {
|
|
8263
|
-
target: { type: "string", description: "Target IP" }
|
|
8264
|
-
},
|
|
8265
|
-
required: ["target"],
|
|
8266
|
-
execute: async (params) => {
|
|
8267
|
-
const target = params.target;
|
|
8268
|
-
return await runCommand("rustscan", ["-a", target, "--range", "1-65535"]);
|
|
8269
|
-
}
|
|
8270
|
-
}
|
|
8271
|
-
];
|
|
8272
|
-
var NETWORK_CONFIG2 = {
|
|
8273
|
-
name: SERVICE_CATEGORIES.NETWORK,
|
|
8453
|
+
var { tools: NETWORK_TOOLS, config: NETWORK_CONFIG2 } = createDomain({
|
|
8454
|
+
category: SERVICE_CATEGORIES.NETWORK,
|
|
8274
8455
|
description: "Network reconnaissance - scanning, OS fingerprinting",
|
|
8275
|
-
tools: NETWORK_TOOLS,
|
|
8276
8456
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8277
8457
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8278
8458
|
commonPorts: [SERVICE_PORTS.FTP, SERVICE_PORTS.SSH, SERVICE_PORTS.HTTP, SERVICE_PORTS.HTTPS, SERVICE_PORTS.SMB, SERVICE_PORTS.RDP, SERVICE_PORTS.HTTP_ALT],
|
|
8279
|
-
commonServices: [SERVICES.FTP, SERVICES.SSH, SERVICES.HTTP, SERVICES.HTTPS, SERVICES.SMB]
|
|
8280
|
-
|
|
8459
|
+
commonServices: [SERVICES.FTP, SERVICES.SSH, SERVICES.HTTP, SERVICES.HTTPS, SERVICES.SMB],
|
|
8460
|
+
commands: [
|
|
8461
|
+
{
|
|
8462
|
+
name: TOOL_NAMES.NMAP_QUICK,
|
|
8463
|
+
description: "Quick nmap scan - fast discovery",
|
|
8464
|
+
cmd: "nmap",
|
|
8465
|
+
args: ["-Pn", "-T4", "-F", "{target}"],
|
|
8466
|
+
params: ["target"]
|
|
8467
|
+
},
|
|
8468
|
+
{
|
|
8469
|
+
name: TOOL_NAMES.NMAP_FULL,
|
|
8470
|
+
description: "Full nmap scan - comprehensive",
|
|
8471
|
+
cmd: "nmap",
|
|
8472
|
+
args: ["-Pn", "-T4", "-sV", "-O", "-p-", "{target}"],
|
|
8473
|
+
params: ["target"]
|
|
8474
|
+
},
|
|
8475
|
+
{
|
|
8476
|
+
name: TOOL_NAMES.RUSTSCAN,
|
|
8477
|
+
description: "RustScan - very fast port discovery",
|
|
8478
|
+
cmd: "rustscan",
|
|
8479
|
+
args: ["-a", "{target}", "--range", "1-65535"],
|
|
8480
|
+
params: ["target"]
|
|
8481
|
+
}
|
|
8482
|
+
]
|
|
8483
|
+
});
|
|
8281
8484
|
|
|
8282
8485
|
// src/domains/web/tools.ts
|
|
8283
|
-
var WEB_TOOLS =
|
|
8284
|
-
|
|
8285
|
-
name: TOOL_NAMES.HTTP_FINGERPRINT,
|
|
8286
|
-
description: "HTTP fingerprinting - detect WAF, server type",
|
|
8287
|
-
parameters: {
|
|
8288
|
-
target: { type: "string", description: "Target URL" }
|
|
8289
|
-
},
|
|
8290
|
-
required: ["target"],
|
|
8291
|
-
execute: async (params) => {
|
|
8292
|
-
const target = params.target;
|
|
8293
|
-
return await runCommand("curl", ["-sI", target]);
|
|
8294
|
-
}
|
|
8295
|
-
},
|
|
8296
|
-
{
|
|
8297
|
-
name: TOOL_NAMES.WAF_DETECT,
|
|
8298
|
-
description: "WAF detection using wafw00f",
|
|
8299
|
-
parameters: {
|
|
8300
|
-
target: { type: "string", description: "Target URL" }
|
|
8301
|
-
},
|
|
8302
|
-
required: ["target"],
|
|
8303
|
-
execute: async (params) => {
|
|
8304
|
-
const target = params.target;
|
|
8305
|
-
return await runCommand("wafw00f", [target]);
|
|
8306
|
-
}
|
|
8307
|
-
},
|
|
8308
|
-
{
|
|
8309
|
-
name: TOOL_NAMES.DIRSEARCH,
|
|
8310
|
-
description: "Directory bruteforcing",
|
|
8311
|
-
parameters: {
|
|
8312
|
-
target: { type: "string", description: "Target URL" }
|
|
8313
|
-
},
|
|
8314
|
-
required: ["target"],
|
|
8315
|
-
execute: async (params) => {
|
|
8316
|
-
const target = params.target;
|
|
8317
|
-
return await runCommand("dirsearch", ["-u", target, "--quiet"]);
|
|
8318
|
-
}
|
|
8319
|
-
},
|
|
8320
|
-
{
|
|
8321
|
-
name: TOOL_NAMES.NUCLEI_WEB,
|
|
8322
|
-
description: "Nuclei web vulnerability scanner",
|
|
8323
|
-
parameters: {
|
|
8324
|
-
target: { type: "string", description: "Target URL" },
|
|
8325
|
-
severity: { type: "string", description: "Severities to check" }
|
|
8326
|
-
},
|
|
8327
|
-
required: ["target"],
|
|
8328
|
-
execute: async (params) => {
|
|
8329
|
-
const target = params.target;
|
|
8330
|
-
const severity = params.severity || "medium,critical,high";
|
|
8331
|
-
return await runCommand("nuclei", ["-u", target, "-s", severity, "-silent"]);
|
|
8332
|
-
}
|
|
8333
|
-
}
|
|
8334
|
-
];
|
|
8335
|
-
var WEB_CONFIG = {
|
|
8336
|
-
name: SERVICE_CATEGORIES.WEB,
|
|
8486
|
+
var { tools: WEB_TOOLS, config: WEB_CONFIG } = createDomain({
|
|
8487
|
+
category: SERVICE_CATEGORIES.WEB,
|
|
8337
8488
|
description: "Web application testing - HTTP/HTTPS, APIs",
|
|
8338
|
-
tools: WEB_TOOLS,
|
|
8339
8489
|
dangerLevel: DANGER_LEVELS.ACTIVE,
|
|
8340
8490
|
defaultApproval: APPROVAL_LEVELS.CONFIRM,
|
|
8341
8491
|
commonPorts: [SERVICE_PORTS.HTTP, SERVICE_PORTS.HTTPS, SERVICE_PORTS.HTTP_ALT],
|
|
8342
|
-
commonServices: [SERVICES.HTTP, SERVICES.HTTPS]
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
target: { type: "string", description: "Target URL" }
|
|
8492
|
+
commonServices: [SERVICES.HTTP, SERVICES.HTTPS],
|
|
8493
|
+
commands: [
|
|
8494
|
+
{
|
|
8495
|
+
name: TOOL_NAMES.HTTP_FINGERPRINT,
|
|
8496
|
+
description: "HTTP fingerprinting - detect WAF, server type",
|
|
8497
|
+
cmd: "curl",
|
|
8498
|
+
args: ["-sI", "{target}"],
|
|
8499
|
+
params: ["target"],
|
|
8500
|
+
requiresUrl: true
|
|
8352
8501
|
},
|
|
8353
|
-
|
|
8354
|
-
|
|
8355
|
-
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8359
|
-
|
|
8360
|
-
name: TOOL_NAMES.SQLMAP_ADVANCED,
|
|
8361
|
-
description: "SQL injection with sqlmap - full enumeration",
|
|
8362
|
-
parameters: {
|
|
8363
|
-
target: { type: "string", description: "Target URL" }
|
|
8502
|
+
{
|
|
8503
|
+
name: TOOL_NAMES.WAF_DETECT,
|
|
8504
|
+
description: "WAF detection using wafw00f",
|
|
8505
|
+
cmd: "wafw00f",
|
|
8506
|
+
args: ["{target}"],
|
|
8507
|
+
params: ["target"],
|
|
8508
|
+
requiresUrl: true
|
|
8364
8509
|
},
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
name: TOOL_NAMES.MYSQL_ENUM,
|
|
8373
|
-
description: "MySQL enumeration - version, users, databases",
|
|
8374
|
-
parameters: {
|
|
8375
|
-
target: { type: "string", description: "Target IP/hostname" },
|
|
8376
|
-
port: { type: "string", description: `Port (default ${SERVICE_PORTS.MYSQL})` }
|
|
8510
|
+
{
|
|
8511
|
+
name: TOOL_NAMES.DIRSEARCH,
|
|
8512
|
+
description: "Directory bruteforcing",
|
|
8513
|
+
cmd: "dirsearch",
|
|
8514
|
+
args: ["-u", "{target}", "--quiet"],
|
|
8515
|
+
params: ["target"],
|
|
8516
|
+
requiresUrl: true
|
|
8377
8517
|
},
|
|
8378
|
-
|
|
8379
|
-
|
|
8380
|
-
|
|
8381
|
-
|
|
8382
|
-
|
|
8518
|
+
{
|
|
8519
|
+
name: TOOL_NAMES.NUCLEI_WEB,
|
|
8520
|
+
description: "Nuclei web vulnerability scanner",
|
|
8521
|
+
cmd: "nuclei",
|
|
8522
|
+
args: ["-u", "{target}", "-s", "{severity}", "-silent"],
|
|
8523
|
+
params: ["target"],
|
|
8524
|
+
optionalParams: { severity: "medium,critical,high" },
|
|
8525
|
+
requiresUrl: true
|
|
8383
8526
|
}
|
|
8384
|
-
|
|
8527
|
+
]
|
|
8528
|
+
});
|
|
8529
|
+
|
|
8530
|
+
// src/domains/database/tools.ts
|
|
8531
|
+
var SQLMAP_BASIC = createTool(
|
|
8532
|
+
TOOL_NAMES.SQLMAP_BASIC,
|
|
8533
|
+
"SQL injection testing with sqlmap - basic scan",
|
|
8534
|
+
{ target: { type: "string", description: "Target URL" } },
|
|
8535
|
+
["target"],
|
|
8536
|
+
async (params) => runCommand("sqlmap", ["-u", param(params, "target"), "--batch", "--risk=1", "--level=1"])
|
|
8537
|
+
);
|
|
8538
|
+
var SQLMAP_ADVANCED = createTool(
|
|
8539
|
+
TOOL_NAMES.SQLMAP_ADVANCED,
|
|
8540
|
+
"SQL injection with sqlmap - full enumeration",
|
|
8541
|
+
{ target: { type: "string", description: "Target URL" } },
|
|
8542
|
+
["target"],
|
|
8543
|
+
async (params) => runCommand("sqlmap", ["-u", param(params, "target"), "--batch", "--risk=3", "--level=5", "--dbs", "--tables"])
|
|
8544
|
+
);
|
|
8545
|
+
var MYSQL_ENUM = createTool(
|
|
8546
|
+
TOOL_NAMES.MYSQL_ENUM,
|
|
8547
|
+
"MySQL enumeration - version, users, databases",
|
|
8385
8548
|
{
|
|
8386
|
-
|
|
8387
|
-
|
|
8388
|
-
parameters: {
|
|
8389
|
-
target: { type: "string", description: "Target IP" }
|
|
8390
|
-
},
|
|
8391
|
-
required: ["target"],
|
|
8392
|
-
execute: async (params) => {
|
|
8393
|
-
const target = params.target;
|
|
8394
|
-
return await runCommand("psql", ["-h", target, "-U", "postgres", "-c", "SELECT version(); SELECT datname FROM pg_database;"]);
|
|
8395
|
-
}
|
|
8549
|
+
target: { type: "string", description: "Target IP/hostname" },
|
|
8550
|
+
port: { type: "number", description: `Port (default ${SERVICE_PORTS.MYSQL})` }
|
|
8396
8551
|
},
|
|
8552
|
+
["target"],
|
|
8553
|
+
async (params) => runCommand("mysql", ["-h", param(params, "target"), "-P", String(paramNumber(params, "port", SERVICE_PORTS.MYSQL)), "-e", "SELECT VERSION(), USER(), DATABASE();"])
|
|
8554
|
+
);
|
|
8555
|
+
var POSTGRES_ENUM = createTool(
|
|
8556
|
+
TOOL_NAMES.POSTGRES_ENUM,
|
|
8557
|
+
"PostgreSQL enumeration",
|
|
8558
|
+
{ target: { type: "string", description: "Target IP" } },
|
|
8559
|
+
["target"],
|
|
8560
|
+
async (params) => runCommand("psql", ["-h", param(params, "target"), "-U", "postgres", "-c", "SELECT version(); SELECT datname FROM pg_database;"])
|
|
8561
|
+
);
|
|
8562
|
+
var REDIS_ENUM = createTool(
|
|
8563
|
+
TOOL_NAMES.REDIS_ENUM,
|
|
8564
|
+
"Redis enumeration",
|
|
8397
8565
|
{
|
|
8398
|
-
|
|
8399
|
-
|
|
8400
|
-
parameters: {
|
|
8401
|
-
target: { type: "string", description: "Target IP" },
|
|
8402
|
-
port: { type: "string", description: `Port (default ${SERVICE_PORTS.REDIS})` }
|
|
8403
|
-
},
|
|
8404
|
-
required: ["target"],
|
|
8405
|
-
execute: async (params) => {
|
|
8406
|
-
const target = params.target;
|
|
8407
|
-
const port = params.port || String(SERVICE_PORTS.REDIS);
|
|
8408
|
-
return await runCommand("redis-cli", ["-h", target, "-p", port, "INFO"]);
|
|
8409
|
-
}
|
|
8566
|
+
target: { type: "string", description: "Target IP" },
|
|
8567
|
+
port: { type: "number", description: `Port (default ${SERVICE_PORTS.REDIS})` }
|
|
8410
8568
|
},
|
|
8569
|
+
["target"],
|
|
8570
|
+
async (params) => runCommand("redis-cli", ["-h", param(params, "target"), "-p", String(paramNumber(params, "port", SERVICE_PORTS.REDIS)), "INFO"])
|
|
8571
|
+
);
|
|
8572
|
+
var DB_BRUTE = createTool(
|
|
8573
|
+
TOOL_NAMES.DB_BRUTE,
|
|
8574
|
+
"Brute force database credentials",
|
|
8411
8575
|
{
|
|
8412
|
-
|
|
8413
|
-
description: "
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
WORDLISTS.COMMON_PASSWORDS,
|
|
8427
|
-
target,
|
|
8428
|
-
service
|
|
8429
|
-
]);
|
|
8430
|
-
}
|
|
8431
|
-
}
|
|
8432
|
-
];
|
|
8576
|
+
target: { type: "string", description: "Target IP" },
|
|
8577
|
+
service: { type: "string", description: "Service (mysql, postgres, etc.)" }
|
|
8578
|
+
},
|
|
8579
|
+
["target", "service"],
|
|
8580
|
+
async (params) => runCommand("hydra", [
|
|
8581
|
+
"-L",
|
|
8582
|
+
WORDLISTS.USERNAMES,
|
|
8583
|
+
"-P",
|
|
8584
|
+
WORDLISTS.COMMON_PASSWORDS,
|
|
8585
|
+
param(params, "target"),
|
|
8586
|
+
param(params, "service")
|
|
8587
|
+
])
|
|
8588
|
+
);
|
|
8589
|
+
var DATABASE_TOOLS = [SQLMAP_BASIC, SQLMAP_ADVANCED, MYSQL_ENUM, POSTGRES_ENUM, REDIS_ENUM, DB_BRUTE];
|
|
8433
8590
|
var DATABASE_CONFIG = {
|
|
8434
8591
|
name: SERVICE_CATEGORIES.DATABASE,
|
|
8435
8592
|
description: "Database exploitation - SQL injection, credential extraction",
|
|
@@ -8441,48 +8598,35 @@ var DATABASE_CONFIG = {
|
|
|
8441
8598
|
};
|
|
8442
8599
|
|
|
8443
8600
|
// src/domains/ad/tools.ts
|
|
8444
|
-
var
|
|
8601
|
+
var BLOODHOUND_COLLECT = createTool(
|
|
8602
|
+
TOOL_NAMES.BLOODHOUND_COLLECT,
|
|
8603
|
+
"BloodHound data collection",
|
|
8445
8604
|
{
|
|
8446
|
-
|
|
8447
|
-
description: "
|
|
8448
|
-
parameters: {
|
|
8449
|
-
target: { type: "string", description: "Target DC IP" },
|
|
8450
|
-
domain: { type: "string", description: "Domain name" }
|
|
8451
|
-
},
|
|
8452
|
-
required: ["target"],
|
|
8453
|
-
execute: async (params) => {
|
|
8454
|
-
const target = params.target;
|
|
8455
|
-
const domain = params.domain || "DOMAIN";
|
|
8456
|
-
return await runCommand("bloodhound-python", ["-c", "All", "-d", domain, target, "--zip"]);
|
|
8457
|
-
}
|
|
8605
|
+
target: { type: "string", description: "Target DC IP" },
|
|
8606
|
+
domain: { type: "string", description: "Domain name" }
|
|
8458
8607
|
},
|
|
8608
|
+
["target"],
|
|
8609
|
+
async (params) => runCommand("bloodhound-python", ["-c", "All", "-d", param(params, "domain", "DOMAIN"), param(params, "target"), "--zip"])
|
|
8610
|
+
);
|
|
8611
|
+
var KERBEROAST = createTool(
|
|
8612
|
+
TOOL_NAMES.KERBEROAST,
|
|
8613
|
+
"Kerberoasting attack",
|
|
8459
8614
|
{
|
|
8460
|
-
|
|
8461
|
-
description: "
|
|
8462
|
-
|
|
8463
|
-
target: { type: "string", description: "DC IP" },
|
|
8464
|
-
user: { type: "string", description: "Domain user" },
|
|
8465
|
-
password: { type: "string", description: "Password" }
|
|
8466
|
-
},
|
|
8467
|
-
required: ["target", "user", "password"],
|
|
8468
|
-
execute: async (params) => {
|
|
8469
|
-
const target = params.target;
|
|
8470
|
-
return await runCommand("GetUserSPNs.py", ["-dc-ip", target, `${params.user}:${params.password}`]);
|
|
8471
|
-
}
|
|
8615
|
+
target: { type: "string", description: "DC IP" },
|
|
8616
|
+
user: { type: "string", description: "Domain user" },
|
|
8617
|
+
password: { type: "string", description: "Password" }
|
|
8472
8618
|
},
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
}
|
|
8485
|
-
];
|
|
8619
|
+
["target", "user", "password"],
|
|
8620
|
+
async (params) => runCommand("GetUserSPNs.py", ["-dc-ip", param(params, "target"), `${param(params, "user")}:${param(params, "password")}`])
|
|
8621
|
+
);
|
|
8622
|
+
var LDAP_ENUM = createTool(
|
|
8623
|
+
TOOL_NAMES.LDAP_ENUM,
|
|
8624
|
+
"LDAP enumeration",
|
|
8625
|
+
{ target: { type: "string", description: "LDAP Server" } },
|
|
8626
|
+
["target"],
|
|
8627
|
+
async (params) => runCommand("ldapsearch", ["-x", "-H", `ldap://${param(params, "target")}`, "-b", ""])
|
|
8628
|
+
);
|
|
8629
|
+
var AD_TOOLS = [BLOODHOUND_COLLECT, KERBEROAST, LDAP_ENUM];
|
|
8486
8630
|
var AD_CONFIG = {
|
|
8487
8631
|
name: SERVICE_CATEGORIES.AD,
|
|
8488
8632
|
description: "Active Directory and Windows domain",
|
|
@@ -8494,19 +8638,14 @@ var AD_CONFIG = {
|
|
|
8494
8638
|
};
|
|
8495
8639
|
|
|
8496
8640
|
// src/domains/email/tools.ts
|
|
8497
|
-
var
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
|
|
8503
|
-
|
|
8504
|
-
|
|
8505
|
-
execute: async (params) => {
|
|
8506
|
-
return await runCommand("smtp-user-enum", ["-M", "VRFY", "-t", params.target]);
|
|
8507
|
-
}
|
|
8508
|
-
}
|
|
8509
|
-
];
|
|
8641
|
+
var SMTP_ENUM = createTool(
|
|
8642
|
+
TOOL_NAMES.SMTP_ENUM,
|
|
8643
|
+
"SMTP user enumeration",
|
|
8644
|
+
{ target: { type: "string", description: "SMTP server" } },
|
|
8645
|
+
["target"],
|
|
8646
|
+
async (params) => runCommand("smtp-user-enum", ["-M", "VRFY", "-t", param(params, "target")])
|
|
8647
|
+
);
|
|
8648
|
+
var EMAIL_TOOLS = [SMTP_ENUM];
|
|
8510
8649
|
var EMAIL_CONFIG = {
|
|
8511
8650
|
name: SERVICE_CATEGORIES.EMAIL,
|
|
8512
8651
|
description: "Email services - SMTP, IMAP, POP3",
|
|
@@ -8518,30 +8657,21 @@ var EMAIL_CONFIG = {
|
|
|
8518
8657
|
};
|
|
8519
8658
|
|
|
8520
8659
|
// src/domains/remote-access/tools.ts
|
|
8521
|
-
var
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
parameters: {
|
|
8537
|
-
target: { type: "string", description: "RDP Server" }
|
|
8538
|
-
},
|
|
8539
|
-
required: ["target"],
|
|
8540
|
-
execute: async (params) => {
|
|
8541
|
-
return await runCommand("nmap", ["-p", String(SERVICE_PORTS.RDP), "--script", "rdp-enum-encryption,rdp-ntlm-info", params.target]);
|
|
8542
|
-
}
|
|
8543
|
-
}
|
|
8544
|
-
];
|
|
8660
|
+
var SSH_ENUM = createTool(
|
|
8661
|
+
TOOL_NAMES.SSH_ENUM,
|
|
8662
|
+
"SSH enumeration",
|
|
8663
|
+
{ target: { type: "string", description: "SSH Server" } },
|
|
8664
|
+
["target"],
|
|
8665
|
+
async (params) => runCommand("ssh-audit", [param(params, "target")])
|
|
8666
|
+
);
|
|
8667
|
+
var RDP_ENUM = createTool(
|
|
8668
|
+
TOOL_NAMES.RDP_ENUM,
|
|
8669
|
+
"RDP enumeration",
|
|
8670
|
+
{ target: { type: "string", description: "RDP Server" } },
|
|
8671
|
+
["target"],
|
|
8672
|
+
async (params) => runCommand("nmap", ["-p", String(SERVICE_PORTS.RDP), "--script", "rdp-enum-encryption,rdp-ntlm-info", param(params, "target")])
|
|
8673
|
+
);
|
|
8674
|
+
var REMOTE_ACCESS_TOOLS = [SSH_ENUM, RDP_ENUM];
|
|
8545
8675
|
var REMOTE_ACCESS_CONFIG = {
|
|
8546
8676
|
name: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
8547
8677
|
description: "Remote access services - SSH, RDP, VNC",
|
|
@@ -8553,30 +8683,21 @@ var REMOTE_ACCESS_CONFIG = {
|
|
|
8553
8683
|
};
|
|
8554
8684
|
|
|
8555
8685
|
// src/domains/file-sharing/tools.ts
|
|
8556
|
-
var
|
|
8557
|
-
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8561
|
-
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8565
|
-
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
parameters: {
|
|
8572
|
-
target: { type: "string", description: "SMB Server" }
|
|
8573
|
-
},
|
|
8574
|
-
required: ["target"],
|
|
8575
|
-
execute: async (params) => {
|
|
8576
|
-
return await runCommand("enum4linux", ["-a", params.target]);
|
|
8577
|
-
}
|
|
8578
|
-
}
|
|
8579
|
-
];
|
|
8686
|
+
var FTP_ENUM = createTool(
|
|
8687
|
+
TOOL_NAMES.FTP_ENUM,
|
|
8688
|
+
"FTP enumeration",
|
|
8689
|
+
{ target: { type: "string", description: "FTP Server" } },
|
|
8690
|
+
["target"],
|
|
8691
|
+
async (params) => runCommand("nmap", ["-p", String(SERVICE_PORTS.FTP), "--script", "ftp-anon,ftp-syst", param(params, "target")])
|
|
8692
|
+
);
|
|
8693
|
+
var SMB_ENUM = createTool(
|
|
8694
|
+
TOOL_NAMES.SMB_ENUM,
|
|
8695
|
+
"SMB enumeration",
|
|
8696
|
+
{ target: { type: "string", description: "SMB Server" } },
|
|
8697
|
+
["target"],
|
|
8698
|
+
async (params) => runCommand("enum4linux", ["-a", param(params, "target")])
|
|
8699
|
+
);
|
|
8700
|
+
var FILE_SHARING_TOOLS = [FTP_ENUM, SMB_ENUM];
|
|
8580
8701
|
var FILE_SHARING_CONFIG = {
|
|
8581
8702
|
name: SERVICE_CATEGORIES.FILE_SHARING,
|
|
8582
8703
|
description: "File sharing protocols - SMB, FTP, NFS",
|
|
@@ -8593,34 +8714,26 @@ var CLOUD_PROVIDER = {
|
|
|
8593
8714
|
AZURE: "azure",
|
|
8594
8715
|
GCP: "gcp"
|
|
8595
8716
|
};
|
|
8596
|
-
var
|
|
8597
|
-
|
|
8598
|
-
|
|
8599
|
-
|
|
8600
|
-
|
|
8601
|
-
|
|
8602
|
-
|
|
8603
|
-
|
|
8604
|
-
|
|
8605
|
-
|
|
8606
|
-
|
|
8607
|
-
|
|
8608
|
-
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
execute: async (params) => {
|
|
8617
|
-
const provider = params.provider;
|
|
8618
|
-
if (provider === CLOUD_PROVIDER.AWS) return await runCommand("curl", ["http://169.254.169.254/latest/meta-data/"]);
|
|
8619
|
-
if (provider === CLOUD_PROVIDER.AZURE) return await runCommand("curl", ["-H", "Metadata:true", "http://169.254.169.254/metadata/instance?api-version=2021-02-01"]);
|
|
8620
|
-
return await runCommand("curl", ["-H", "Metadata-Flavor: Google", "http://metadata.google.internal/computeMetadata/v1/"]);
|
|
8621
|
-
}
|
|
8622
|
-
}
|
|
8623
|
-
];
|
|
8717
|
+
var AWS_S3_CHECK = createTool(
|
|
8718
|
+
TOOL_NAMES.AWS_S3_CHECK,
|
|
8719
|
+
"S3 bucket security check",
|
|
8720
|
+
{ bucket: { type: "string", description: "Bucket name" } },
|
|
8721
|
+
["bucket"],
|
|
8722
|
+
async (params) => runCommand("aws", ["s3", "ls", `s3://${param(params, "bucket")}`, "--no-sign-request"])
|
|
8723
|
+
);
|
|
8724
|
+
var CLOUD_META_CHECK = createTool(
|
|
8725
|
+
TOOL_NAMES.CLOUD_META_CHECK,
|
|
8726
|
+
"Check cloud metadata service access",
|
|
8727
|
+
{ provider: { type: "string", description: "aws, azure, or gcp" } },
|
|
8728
|
+
["provider"],
|
|
8729
|
+
async (params) => {
|
|
8730
|
+
const provider = param(params, "provider");
|
|
8731
|
+
if (provider === CLOUD_PROVIDER.AWS) return runCommand("curl", ["http://169.254.169.254/latest/meta-data/"]);
|
|
8732
|
+
if (provider === CLOUD_PROVIDER.AZURE) return runCommand("curl", ["-H", "Metadata:true", "http://169.254.169.254/metadata/instance?api-version=2021-02-01"]);
|
|
8733
|
+
return runCommand("curl", ["-H", "Metadata-Flavor: Google", "http://metadata.google.internal/computeMetadata/v1/"]);
|
|
8734
|
+
}
|
|
8735
|
+
);
|
|
8736
|
+
var CLOUD_TOOLS = [AWS_S3_CHECK, CLOUD_META_CHECK];
|
|
8624
8737
|
var CLOUD_CONFIG = {
|
|
8625
8738
|
name: SERVICE_CATEGORIES.CLOUD,
|
|
8626
8739
|
description: "Cloud infrastructure - AWS, Azure, GCP",
|
|
@@ -8632,30 +8745,21 @@ var CLOUD_CONFIG = {
|
|
|
8632
8745
|
};
|
|
8633
8746
|
|
|
8634
8747
|
// src/domains/container/tools.ts
|
|
8635
|
-
var
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
parameters: {
|
|
8651
|
-
context: { type: "string", description: "Kube context" }
|
|
8652
|
-
},
|
|
8653
|
-
required: ["context"],
|
|
8654
|
-
execute: async (params) => {
|
|
8655
|
-
return await runCommand("kubectl", ["--context", params.context, "get", "pods"]);
|
|
8656
|
-
}
|
|
8657
|
-
}
|
|
8658
|
-
];
|
|
8748
|
+
var DOCKER_PS = createTool(
|
|
8749
|
+
TOOL_NAMES.DOCKER_PS,
|
|
8750
|
+
"List running Docker containers",
|
|
8751
|
+
{ host: { type: "string", description: "Docker host" } },
|
|
8752
|
+
["host"],
|
|
8753
|
+
async (params) => runCommand("docker", ["-H", param(params, "host"), "ps"])
|
|
8754
|
+
);
|
|
8755
|
+
var KUBE_GET_PODS = createTool(
|
|
8756
|
+
TOOL_NAMES.KUBE_GET_PODS,
|
|
8757
|
+
"List Kubernetes pods",
|
|
8758
|
+
{ context: { type: "string", description: "Kube context" } },
|
|
8759
|
+
["context"],
|
|
8760
|
+
async (params) => runCommand("kubectl", ["--context", param(params, "context"), "get", "pods"])
|
|
8761
|
+
);
|
|
8762
|
+
var CONTAINER_TOOLS = [DOCKER_PS, KUBE_GET_PODS];
|
|
8659
8763
|
var CONTAINER_CONFIG = {
|
|
8660
8764
|
name: SERVICE_CATEGORIES.CONTAINER,
|
|
8661
8765
|
description: "Container platforms - Docker, Kubernetes",
|
|
@@ -8667,58 +8771,38 @@ var CONTAINER_CONFIG = {
|
|
|
8667
8771
|
};
|
|
8668
8772
|
|
|
8669
8773
|
// src/domains/api/tools.ts
|
|
8670
|
-
var
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
|
|
8677
|
-
|
|
8678
|
-
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
}
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
|
|
8688
|
-
|
|
8689
|
-
|
|
8690
|
-
|
|
8691
|
-
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
},
|
|
8774
|
+
var API_DISCOVER = createTool(
|
|
8775
|
+
TOOL_NAMES.API_DISCOVER,
|
|
8776
|
+
"API endpoint discovery - using Arjun",
|
|
8777
|
+
{ target: { type: "string", description: "Target URL" } },
|
|
8778
|
+
["target"],
|
|
8779
|
+
async (params) => runCommand("arjun", ["-u", param(params, "target"), "-t", "20"])
|
|
8780
|
+
);
|
|
8781
|
+
var GRAPHQL_INTROSPECT = createTool(
|
|
8782
|
+
TOOL_NAMES.GRAPHQL_INTROSPECT,
|
|
8783
|
+
"GraphQL introspection - schema discovery",
|
|
8784
|
+
{ target: { type: "string", description: "GQL Endpoint" } },
|
|
8785
|
+
["target"],
|
|
8786
|
+
async (params) => runCommand("curl", ["-X", "POST", param(params, "target"), "-H", "Content-Type: application/json", "-d", '{"query":"{__schema {queryType {name}}}"}'])
|
|
8787
|
+
);
|
|
8788
|
+
var SWAGGER_PARSE = createTool(
|
|
8789
|
+
TOOL_NAMES.SWAGGER_PARSE,
|
|
8790
|
+
"Parse Swagger/OpenAPI specification",
|
|
8791
|
+
{ spec: { type: "string", description: "URL to swagger.json/yaml" } },
|
|
8792
|
+
["spec"],
|
|
8793
|
+
async (params) => runCommand("swagger-codegen", ["generate", "-i", param(params, "spec"), "-l", "html2"])
|
|
8794
|
+
);
|
|
8795
|
+
var API_FUZZ = createTool(
|
|
8796
|
+
TOOL_NAMES.API_FUZZ,
|
|
8797
|
+
"General API fuzzing",
|
|
8695
8798
|
{
|
|
8696
|
-
|
|
8697
|
-
description: "
|
|
8698
|
-
parameters: {
|
|
8699
|
-
spec: { type: "string", description: "URL to swagger.json/yaml" }
|
|
8700
|
-
},
|
|
8701
|
-
required: ["spec"],
|
|
8702
|
-
execute: async (params) => {
|
|
8703
|
-
const spec = params.spec;
|
|
8704
|
-
return await runCommand("swagger-codegen", ["generate", "-i", spec, "-l", "html2"]);
|
|
8705
|
-
}
|
|
8799
|
+
target: { type: "string", description: "Base API URL" },
|
|
8800
|
+
wordlist: { type: "string", description: "Wordlist path" }
|
|
8706
8801
|
},
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
|
|
8711
|
-
target: { type: "string", description: "Base API URL" },
|
|
8712
|
-
wordlist: { type: "string", description: "Wordlist path" }
|
|
8713
|
-
},
|
|
8714
|
-
required: ["target"],
|
|
8715
|
-
execute: async (params) => {
|
|
8716
|
-
const target = params.target;
|
|
8717
|
-
const wordlist = params.wordlist || WORDLISTS.API_FUZZ;
|
|
8718
|
-
return await runCommand("ffuf", ["-u", `${target}/FUZZ`, "-w", wordlist, "-mc", "all"]);
|
|
8719
|
-
}
|
|
8720
|
-
}
|
|
8721
|
-
];
|
|
8802
|
+
["target"],
|
|
8803
|
+
async (params) => runCommand("ffuf", ["-u", `${param(params, "target")}/FUZZ`, "-w", param(params, "wordlist", WORDLISTS.API_FUZZ), "-mc", "all"])
|
|
8804
|
+
);
|
|
8805
|
+
var API_TOOLS = [API_DISCOVER, GRAPHQL_INTROSPECT, SWAGGER_PARSE, API_FUZZ];
|
|
8722
8806
|
var API_CONFIG = {
|
|
8723
8807
|
name: SERVICE_CATEGORIES.API,
|
|
8724
8808
|
description: "API testing - REST, GraphQL, SOAP",
|
|
@@ -8730,19 +8814,14 @@ var API_CONFIG = {
|
|
|
8730
8814
|
};
|
|
8731
8815
|
|
|
8732
8816
|
// src/domains/wireless/tools.ts
|
|
8733
|
-
var
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
8738
|
-
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
execute: async (params) => {
|
|
8742
|
-
return await runCommand("iwlist", [params.interface, "scanning"]);
|
|
8743
|
-
}
|
|
8744
|
-
}
|
|
8745
|
-
];
|
|
8817
|
+
var WIFI_SCAN = createTool(
|
|
8818
|
+
TOOL_NAMES.WIFI_SCAN,
|
|
8819
|
+
"WiFi network scanning",
|
|
8820
|
+
{ interface: { type: "string", description: "Wireless interface" } },
|
|
8821
|
+
["interface"],
|
|
8822
|
+
async (params) => runCommand("iwlist", [param(params, "interface"), "scanning"])
|
|
8823
|
+
);
|
|
8824
|
+
var WIRELESS_TOOLS = [WIFI_SCAN];
|
|
8746
8825
|
var WIRELESS_CONFIG = {
|
|
8747
8826
|
name: SERVICE_CATEGORIES.WIRELESS,
|
|
8748
8827
|
description: "Wireless security - WiFi, Bluetooth",
|
|
@@ -8754,19 +8833,14 @@ var WIRELESS_CONFIG = {
|
|
|
8754
8833
|
};
|
|
8755
8834
|
|
|
8756
8835
|
// src/domains/ics/tools.ts
|
|
8757
|
-
var
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8763
|
-
|
|
8764
|
-
|
|
8765
|
-
execute: async (params) => {
|
|
8766
|
-
return await runCommand("nmap", ["-p", String(SERVICE_PORTS.MODBUS), "--script", "modbus-discover", params.target]);
|
|
8767
|
-
}
|
|
8768
|
-
}
|
|
8769
|
-
];
|
|
8836
|
+
var MODBUS_ENUM = createTool(
|
|
8837
|
+
TOOL_NAMES.MODBUS_ENUM,
|
|
8838
|
+
"Modbus enumeration",
|
|
8839
|
+
{ target: { type: "string", description: "ICS Device" } },
|
|
8840
|
+
["target"],
|
|
8841
|
+
async (params) => runCommand("nmap", ["-p", String(SERVICE_PORTS.MODBUS), "--script", "modbus-discover", param(params, "target")])
|
|
8842
|
+
);
|
|
8843
|
+
var ICS_TOOLS = [MODBUS_ENUM];
|
|
8770
8844
|
var ICS_CONFIG = {
|
|
8771
8845
|
name: SERVICE_CATEGORIES.ICS,
|
|
8772
8846
|
description: "Industrial control systems - Modbus, DNP3, EtherNet/IP",
|
|
@@ -9019,7 +9093,7 @@ var ServiceParser = class {
|
|
|
9019
9093
|
};
|
|
9020
9094
|
|
|
9021
9095
|
// src/domains/registry.ts
|
|
9022
|
-
import { join as
|
|
9096
|
+
import { join as join8, dirname as dirname3 } from "path";
|
|
9023
9097
|
import { fileURLToPath } from "url";
|
|
9024
9098
|
var __dirname = dirname3(fileURLToPath(import.meta.url));
|
|
9025
9099
|
var DOMAINS = {
|
|
@@ -9027,73 +9101,73 @@ var DOMAINS = {
|
|
|
9027
9101
|
id: SERVICE_CATEGORIES.NETWORK,
|
|
9028
9102
|
name: "Network Infrastructure",
|
|
9029
9103
|
description: "Vulnerability scanning, port mapping, and network service exploitation.",
|
|
9030
|
-
promptPath:
|
|
9104
|
+
promptPath: join8(__dirname, "network/prompt.md")
|
|
9031
9105
|
},
|
|
9032
9106
|
[SERVICE_CATEGORIES.WEB]: {
|
|
9033
9107
|
id: SERVICE_CATEGORIES.WEB,
|
|
9034
9108
|
name: "Web Application",
|
|
9035
9109
|
description: "Web app security testing, injection attacks, and auth bypass.",
|
|
9036
|
-
promptPath:
|
|
9110
|
+
promptPath: join8(__dirname, "web/prompt.md")
|
|
9037
9111
|
},
|
|
9038
9112
|
[SERVICE_CATEGORIES.DATABASE]: {
|
|
9039
9113
|
id: SERVICE_CATEGORIES.DATABASE,
|
|
9040
9114
|
name: "Database Security",
|
|
9041
9115
|
description: "SQL injection, database enumeration, and data extraction.",
|
|
9042
|
-
promptPath:
|
|
9116
|
+
promptPath: join8(__dirname, "database/prompt.md")
|
|
9043
9117
|
},
|
|
9044
9118
|
[SERVICE_CATEGORIES.AD]: {
|
|
9045
9119
|
id: SERVICE_CATEGORIES.AD,
|
|
9046
9120
|
name: "Active Directory",
|
|
9047
9121
|
description: "Kerberos, LDAP, and Windows domain privilege escalation.",
|
|
9048
|
-
promptPath:
|
|
9122
|
+
promptPath: join8(__dirname, "ad/prompt.md")
|
|
9049
9123
|
},
|
|
9050
9124
|
[SERVICE_CATEGORIES.EMAIL]: {
|
|
9051
9125
|
id: SERVICE_CATEGORIES.EMAIL,
|
|
9052
9126
|
name: "Email Services",
|
|
9053
9127
|
description: "SMTP, IMAP, POP3 security and user enumeration.",
|
|
9054
|
-
promptPath:
|
|
9128
|
+
promptPath: join8(__dirname, "email/prompt.md")
|
|
9055
9129
|
},
|
|
9056
9130
|
[SERVICE_CATEGORIES.REMOTE_ACCESS]: {
|
|
9057
9131
|
id: SERVICE_CATEGORIES.REMOTE_ACCESS,
|
|
9058
9132
|
name: "Remote Access",
|
|
9059
9133
|
description: "SSH, RDP, VNC and other remote control protocols.",
|
|
9060
|
-
promptPath:
|
|
9134
|
+
promptPath: join8(__dirname, "remote-access/prompt.md")
|
|
9061
9135
|
},
|
|
9062
9136
|
[SERVICE_CATEGORIES.FILE_SHARING]: {
|
|
9063
9137
|
id: SERVICE_CATEGORIES.FILE_SHARING,
|
|
9064
9138
|
name: "File Sharing",
|
|
9065
9139
|
description: "SMB, NFS, FTP and shared resource security.",
|
|
9066
|
-
promptPath:
|
|
9140
|
+
promptPath: join8(__dirname, "file-sharing/prompt.md")
|
|
9067
9141
|
},
|
|
9068
9142
|
[SERVICE_CATEGORIES.CLOUD]: {
|
|
9069
9143
|
id: SERVICE_CATEGORIES.CLOUD,
|
|
9070
9144
|
name: "Cloud Infrastructure",
|
|
9071
9145
|
description: "AWS, Azure, and GCP security and misconfiguration.",
|
|
9072
|
-
promptPath:
|
|
9146
|
+
promptPath: join8(__dirname, "cloud/prompt.md")
|
|
9073
9147
|
},
|
|
9074
9148
|
[SERVICE_CATEGORIES.CONTAINER]: {
|
|
9075
9149
|
id: SERVICE_CATEGORIES.CONTAINER,
|
|
9076
9150
|
name: "Container Systems",
|
|
9077
9151
|
description: "Docker and Kubernetes security testing.",
|
|
9078
|
-
promptPath:
|
|
9152
|
+
promptPath: join8(__dirname, "container/prompt.md")
|
|
9079
9153
|
},
|
|
9080
9154
|
[SERVICE_CATEGORIES.API]: {
|
|
9081
9155
|
id: SERVICE_CATEGORIES.API,
|
|
9082
9156
|
name: "API Security",
|
|
9083
9157
|
description: "REST, GraphQL, and SOAP API security testing.",
|
|
9084
|
-
promptPath:
|
|
9158
|
+
promptPath: join8(__dirname, "api/prompt.md")
|
|
9085
9159
|
},
|
|
9086
9160
|
[SERVICE_CATEGORIES.WIRELESS]: {
|
|
9087
9161
|
id: SERVICE_CATEGORIES.WIRELESS,
|
|
9088
9162
|
name: "Wireless Networks",
|
|
9089
9163
|
description: "WiFi and Bluetooth security testing.",
|
|
9090
|
-
promptPath:
|
|
9164
|
+
promptPath: join8(__dirname, "wireless/prompt.md")
|
|
9091
9165
|
},
|
|
9092
9166
|
[SERVICE_CATEGORIES.ICS]: {
|
|
9093
9167
|
id: SERVICE_CATEGORIES.ICS,
|
|
9094
9168
|
name: "Industrial Systems",
|
|
9095
9169
|
description: "Critical infrastructure - Modbus, DNP3, ENIP.",
|
|
9096
|
-
promptPath:
|
|
9170
|
+
promptPath: join8(__dirname, "ics/prompt.md")
|
|
9097
9171
|
}
|
|
9098
9172
|
};
|
|
9099
9173
|
|
|
@@ -9170,22 +9244,6 @@ var CategorizedToolRegistry = class extends ToolRegistry {
|
|
|
9170
9244
|
}
|
|
9171
9245
|
return results;
|
|
9172
9246
|
}
|
|
9173
|
-
suggestSubAgent(target) {
|
|
9174
|
-
const suggestions = this.suggestTools(target);
|
|
9175
|
-
const priority = [
|
|
9176
|
-
SERVICE_CATEGORIES.ICS,
|
|
9177
|
-
SERVICE_CATEGORIES.AD,
|
|
9178
|
-
SERVICE_CATEGORIES.DATABASE,
|
|
9179
|
-
SERVICE_CATEGORIES.CLOUD,
|
|
9180
|
-
SERVICE_CATEGORIES.CONTAINER,
|
|
9181
|
-
SERVICE_CATEGORIES.WEB,
|
|
9182
|
-
SERVICE_CATEGORIES.NETWORK
|
|
9183
|
-
];
|
|
9184
|
-
for (const cat of priority) {
|
|
9185
|
-
if (suggestions.some((s) => s.category === cat)) return cat;
|
|
9186
|
-
}
|
|
9187
|
-
return AGENT_ROLES.RECON;
|
|
9188
|
-
}
|
|
9189
9247
|
fingerprintService(port) {
|
|
9190
9248
|
const category = ServiceParser.detectCategory(port.port, port.service);
|
|
9191
9249
|
if (!category) return null;
|
|
@@ -9514,14 +9572,6 @@ var LLMClient = class {
|
|
|
9514
9572
|
this.processStreamEvent(event, requestId, {
|
|
9515
9573
|
toolCallsMap,
|
|
9516
9574
|
callbacks,
|
|
9517
|
-
onTextStart: () => {
|
|
9518
|
-
},
|
|
9519
|
-
onReasoningStart: () => {
|
|
9520
|
-
},
|
|
9521
|
-
onTextEnd: () => {
|
|
9522
|
-
},
|
|
9523
|
-
onReasoningEnd: () => {
|
|
9524
|
-
},
|
|
9525
9575
|
onContent: (text) => {
|
|
9526
9576
|
fullContent += text;
|
|
9527
9577
|
totalChars += text.length;
|
|
@@ -9596,18 +9646,18 @@ var LLMClient = class {
|
|
|
9596
9646
|
return { cleanText, extractedReasoning };
|
|
9597
9647
|
}
|
|
9598
9648
|
processStreamEvent(event, requestId, context) {
|
|
9599
|
-
const { toolCallsMap, callbacks,
|
|
9649
|
+
const { toolCallsMap, callbacks, onContent, onReasoning, onUsage, getTotalChars, currentBlockRef } = context;
|
|
9600
9650
|
switch (event.type) {
|
|
9601
9651
|
case LLM_SSE_EVENT.CONTENT_BLOCK_START:
|
|
9602
9652
|
if (event.content_block) {
|
|
9603
9653
|
const blockType = event.content_block.type;
|
|
9604
9654
|
if (blockType === LLM_BLOCK_TYPE.TEXT) {
|
|
9605
9655
|
currentBlockRef.value = "text";
|
|
9606
|
-
onTextStart();
|
|
9656
|
+
context.onTextStart?.();
|
|
9607
9657
|
callbacks?.onOutputStart?.();
|
|
9608
9658
|
} else if (blockType === LLM_BLOCK_TYPE.THINKING || blockType === LLM_BLOCK_TYPE.REASONING) {
|
|
9609
9659
|
currentBlockRef.value = "reasoning";
|
|
9610
|
-
onReasoningStart();
|
|
9660
|
+
context.onReasoningStart?.();
|
|
9611
9661
|
callbacks?.onReasoningStart?.();
|
|
9612
9662
|
} else if (blockType === LLM_BLOCK_TYPE.TOOL_USE) {
|
|
9613
9663
|
currentBlockRef.value = "tool_use";
|
|
@@ -9653,10 +9703,10 @@ var LLMClient = class {
|
|
|
9653
9703
|
const stoppedType = currentBlockRef.value;
|
|
9654
9704
|
currentBlockRef.value = null;
|
|
9655
9705
|
if (stoppedType === "text") {
|
|
9656
|
-
onTextEnd();
|
|
9706
|
+
context.onTextEnd?.();
|
|
9657
9707
|
callbacks?.onOutputEnd?.();
|
|
9658
9708
|
} else if (stoppedType === "reasoning") {
|
|
9659
|
-
onReasoningEnd();
|
|
9709
|
+
context.onReasoningEnd?.();
|
|
9660
9710
|
callbacks?.onReasoningEnd?.();
|
|
9661
9711
|
}
|
|
9662
9712
|
break;
|
|
@@ -9728,13 +9778,10 @@ function getLLMClient() {
|
|
|
9728
9778
|
}
|
|
9729
9779
|
return llmInstance;
|
|
9730
9780
|
}
|
|
9731
|
-
function logLLM(message, data) {
|
|
9732
|
-
debugLog("llm", message, data);
|
|
9733
|
-
}
|
|
9734
9781
|
|
|
9735
9782
|
// src/engine/state-persistence.ts
|
|
9736
|
-
import { writeFileSync as
|
|
9737
|
-
import { join as
|
|
9783
|
+
import { writeFileSync as writeFileSync7, readFileSync as readFileSync4, existsSync as existsSync7, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync4, rmSync } from "fs";
|
|
9784
|
+
import { join as join9 } from "path";
|
|
9738
9785
|
function saveState(state) {
|
|
9739
9786
|
const sessionsDir = WORKSPACE.SESSIONS;
|
|
9740
9787
|
ensureDirExists(sessionsDir);
|
|
@@ -9751,21 +9798,24 @@ function saveState(state) {
|
|
|
9751
9798
|
missionSummary: state.getMissionSummary(),
|
|
9752
9799
|
missionChecklist: state.getMissionChecklist()
|
|
9753
9800
|
};
|
|
9754
|
-
const
|
|
9755
|
-
const
|
|
9756
|
-
|
|
9757
|
-
const latestFile =
|
|
9758
|
-
|
|
9801
|
+
const sessionFile = join9(sessionsDir, FILE_PATTERNS.session());
|
|
9802
|
+
const json = JSON.stringify(snapshot, null, 2);
|
|
9803
|
+
writeFileSync7(sessionFile, json, "utf-8");
|
|
9804
|
+
const latestFile = join9(sessionsDir, "latest.json");
|
|
9805
|
+
writeFileSync7(latestFile, json, "utf-8");
|
|
9759
9806
|
pruneOldSessions(sessionsDir);
|
|
9760
9807
|
return sessionFile;
|
|
9761
9808
|
}
|
|
9762
9809
|
function pruneOldSessions(sessionsDir) {
|
|
9763
9810
|
try {
|
|
9764
|
-
const sessionFiles =
|
|
9765
|
-
|
|
9766
|
-
|
|
9767
|
-
|
|
9768
|
-
|
|
9811
|
+
const sessionFiles = readdirSync2(sessionsDir).filter((f) => f.endsWith(FILE_EXTENSIONS.JSON) && f !== SPECIAL_FILES.LATEST_STATE).map((f) => {
|
|
9812
|
+
const filePath = join9(sessionsDir, f);
|
|
9813
|
+
return {
|
|
9814
|
+
name: f,
|
|
9815
|
+
path: filePath,
|
|
9816
|
+
mtime: statSync2(filePath).mtimeMs
|
|
9817
|
+
};
|
|
9818
|
+
}).sort((a, b) => b.mtime - a.mtime);
|
|
9769
9819
|
const toDelete = sessionFiles.slice(AGENT_LIMITS.MAX_SESSION_FILES);
|
|
9770
9820
|
for (const file of toDelete) {
|
|
9771
9821
|
unlinkSync4(file.path);
|
|
@@ -9774,8 +9824,8 @@ function pruneOldSessions(sessionsDir) {
|
|
|
9774
9824
|
}
|
|
9775
9825
|
}
|
|
9776
9826
|
function loadState(state) {
|
|
9777
|
-
const latestFile =
|
|
9778
|
-
if (!
|
|
9827
|
+
const latestFile = join9(WORKSPACE.SESSIONS, "latest.json");
|
|
9828
|
+
if (!existsSync7(latestFile)) {
|
|
9779
9829
|
return false;
|
|
9780
9830
|
}
|
|
9781
9831
|
try {
|
|
@@ -9802,10 +9852,7 @@ function loadState(state) {
|
|
|
9802
9852
|
state.addLoot(loot);
|
|
9803
9853
|
}
|
|
9804
9854
|
for (const item of snapshot.todo) {
|
|
9805
|
-
|
|
9806
|
-
if (item.status && item.status !== "pending") {
|
|
9807
|
-
state.updateTodo(id, { status: item.status });
|
|
9808
|
-
}
|
|
9855
|
+
state.restoreTodoItem(item);
|
|
9809
9856
|
}
|
|
9810
9857
|
const validPhases = new Set(Object.values(PHASES));
|
|
9811
9858
|
const restoredPhase = validPhases.has(snapshot.currentPhase) ? snapshot.currentPhase : PHASES.RECON;
|
|
@@ -9814,21 +9861,7 @@ function loadState(state) {
|
|
|
9814
9861
|
state.setMissionSummary(snapshot.missionSummary);
|
|
9815
9862
|
}
|
|
9816
9863
|
if (snapshot.missionChecklist?.length > 0) {
|
|
9817
|
-
state.
|
|
9818
|
-
const restoredChecklist = state.getMissionChecklist();
|
|
9819
|
-
const baseIndex = restoredChecklist.length - snapshot.missionChecklist.length;
|
|
9820
|
-
const completedUpdates = [];
|
|
9821
|
-
for (let i = 0; i < snapshot.missionChecklist.length; i++) {
|
|
9822
|
-
if (snapshot.missionChecklist[i].isCompleted && restoredChecklist[baseIndex + i]) {
|
|
9823
|
-
completedUpdates.push({
|
|
9824
|
-
id: restoredChecklist[baseIndex + i].id,
|
|
9825
|
-
isCompleted: true
|
|
9826
|
-
});
|
|
9827
|
-
}
|
|
9828
|
-
}
|
|
9829
|
-
if (completedUpdates.length > 0) {
|
|
9830
|
-
state.updateMissionChecklist(completedUpdates);
|
|
9831
|
-
}
|
|
9864
|
+
state.restoreMissionChecklist(snapshot.missionChecklist);
|
|
9832
9865
|
}
|
|
9833
9866
|
return true;
|
|
9834
9867
|
} catch (err) {
|
|
@@ -9851,7 +9884,7 @@ function clearWorkspace() {
|
|
|
9851
9884
|
];
|
|
9852
9885
|
for (const dir of dirsToClean) {
|
|
9853
9886
|
try {
|
|
9854
|
-
if (
|
|
9887
|
+
if (existsSync7(dir.path)) {
|
|
9855
9888
|
rmSync(dir.path, { recursive: true, force: true });
|
|
9856
9889
|
ensureDirExists(dir.path);
|
|
9857
9890
|
cleared.push(dir.label);
|
|
@@ -9941,11 +9974,72 @@ function appendBlockedCommandHints(lines, errorLower) {
|
|
|
9941
9974
|
}
|
|
9942
9975
|
|
|
9943
9976
|
// src/shared/utils/context-digest.ts
|
|
9944
|
-
import { writeFileSync as
|
|
9977
|
+
import { writeFileSync as writeFileSync8 } from "fs";
|
|
9978
|
+
|
|
9979
|
+
// src/shared/constants/document-schema.ts
|
|
9980
|
+
var MEMO_SECTIONS = {
|
|
9981
|
+
KEY_FINDINGS: "Key Findings",
|
|
9982
|
+
CREDENTIALS: "Credentials/Secrets",
|
|
9983
|
+
ATTACK_VECTORS: "Attack Vectors",
|
|
9984
|
+
FAILURES: "Failures/Errors",
|
|
9985
|
+
SUSPICIONS: "Suspicious Signals",
|
|
9986
|
+
ATTACK_VALUE: "Attack Value",
|
|
9987
|
+
NEXT_STEPS: "Next Steps",
|
|
9988
|
+
REFLECTION: "Reflection"
|
|
9989
|
+
};
|
|
9990
|
+
var MEMO_PARSE_KEYS = {
|
|
9991
|
+
KEY_FINDINGS: MEMO_SECTIONS.KEY_FINDINGS.toLowerCase(),
|
|
9992
|
+
CREDENTIALS: MEMO_SECTIONS.CREDENTIALS.toLowerCase(),
|
|
9993
|
+
ATTACK_VECTORS: MEMO_SECTIONS.ATTACK_VECTORS.toLowerCase(),
|
|
9994
|
+
FAILURES: MEMO_SECTIONS.FAILURES.toLowerCase(),
|
|
9995
|
+
SUSPICIONS: MEMO_SECTIONS.SUSPICIONS.toLowerCase(),
|
|
9996
|
+
ATTACK_VALUE: MEMO_SECTIONS.ATTACK_VALUE.toLowerCase(),
|
|
9997
|
+
NEXT_STEPS: MEMO_SECTIONS.NEXT_STEPS.toLowerCase(),
|
|
9998
|
+
REFLECTION: MEMO_SECTIONS.REFLECTION.toLowerCase()
|
|
9999
|
+
};
|
|
10000
|
+
var TURN_SECTIONS = {
|
|
10001
|
+
TOOLS_EXECUTED: "\uC2E4\uD589 \uB3C4\uAD6C",
|
|
10002
|
+
KEY_INSIGHTS: "\uD575\uC2EC \uC778\uC0AC\uC774\uD2B8",
|
|
10003
|
+
SELF_REFLECTION: "\uC790\uAE30\uBC18\uC131"
|
|
10004
|
+
};
|
|
10005
|
+
var INSIGHT_TAGS = {
|
|
10006
|
+
DISCOVERED: "DISCOVERED",
|
|
10007
|
+
CREDENTIAL: "CREDENTIAL",
|
|
10008
|
+
CONFIRMED: "CONFIRMED",
|
|
10009
|
+
DEAD_END: "DEAD END",
|
|
10010
|
+
SUSPICIOUS: "SUSPICIOUS",
|
|
10011
|
+
NEXT: "NEXT"
|
|
10012
|
+
};
|
|
10013
|
+
var TURN_EMPTY_MESSAGES = {
|
|
10014
|
+
NO_TOOLS: "(\uB3C4\uAD6C \uC2E4\uD589 \uC5C6\uC74C)",
|
|
10015
|
+
NO_INSIGHTS: "(\uD2B9\uC774\uC0AC\uD56D \uC5C6\uC74C)",
|
|
10016
|
+
NO_REFLECTION: "(\uBC18\uC131 \uC5C6\uC74C)"
|
|
10017
|
+
};
|
|
10018
|
+
var SUMMARY_SECTIONS = {
|
|
10019
|
+
TITLE: "Session Journal Summary",
|
|
10020
|
+
TECHNIQUES_TRIED: "Techniques Tried (by attack value)",
|
|
10021
|
+
TECHNIQUES_LEGEND: "> \u26A1HIGH=keep drilling \u26A1MED=worth exploring \u26A1LOW=low priority \u26A1NONE=abandon",
|
|
10022
|
+
ANALYST_ANALYSIS: "\u{1F9E0} Analyst Analysis (attack value rationale)",
|
|
10023
|
+
SUSPICIOUS: "\u{1F50D} Suspicious Signals (unconfirmed, needs investigation)",
|
|
10024
|
+
KEY_FINDINGS: "\u{1F4CB} Key Findings",
|
|
10025
|
+
CREDENTIALS: "\u{1F511} Credentials Obtained",
|
|
10026
|
+
SUCCESS_VECTORS: "\u2705 Successful Attack Vectors",
|
|
10027
|
+
FAILURE_CAUSES: "\u274C Failure Causes (do not repeat)",
|
|
10028
|
+
NEXT_RECS: "\u27A1\uFE0F Next Recommendations"
|
|
10029
|
+
};
|
|
10030
|
+
var STATUS_ICONS = {
|
|
10031
|
+
SUCCESS: "\u2705",
|
|
10032
|
+
FAILURE: "\u274C"
|
|
10033
|
+
};
|
|
10034
|
+
|
|
10035
|
+
// src/shared/utils/context-digest.ts
|
|
9945
10036
|
var PASSTHROUGH_THRESHOLD = 500;
|
|
9946
10037
|
var PREPROCESS_THRESHOLD = 3e3;
|
|
9947
10038
|
var MAX_PREPROCESSED_LINES = 800;
|
|
9948
|
-
var
|
|
10039
|
+
var _currentTurn = null;
|
|
10040
|
+
function setCurrentTurn(turn) {
|
|
10041
|
+
_currentTurn = turn;
|
|
10042
|
+
}
|
|
9949
10043
|
var MAX_DUPLICATE_DISPLAY = 3;
|
|
9950
10044
|
var ANALYST_MAX_INPUT_CHARS = 8e4;
|
|
9951
10045
|
var FALLBACK_MAX_CHARS = 3e4;
|
|
@@ -10154,13 +10248,13 @@ function parseAnalystMemo(response) {
|
|
|
10154
10248
|
const rawValue = attackValueLine.toUpperCase();
|
|
10155
10249
|
const attackValue = ["HIGH", "MED", "LOW", "NONE"].includes(rawValue) ? rawValue : "LOW";
|
|
10156
10250
|
return {
|
|
10157
|
-
keyFindings: filterNone(sections[
|
|
10158
|
-
credentials: filterNone(sections[
|
|
10159
|
-
attackVectors: filterNone(sections[
|
|
10160
|
-
failures: filterNone(sections[
|
|
10161
|
-
suspicions: filterNone(sections[
|
|
10251
|
+
keyFindings: filterNone(sections[MEMO_PARSE_KEYS.KEY_FINDINGS] || []),
|
|
10252
|
+
credentials: filterNone(sections[MEMO_PARSE_KEYS.CREDENTIALS] || []),
|
|
10253
|
+
attackVectors: filterNone(sections[MEMO_PARSE_KEYS.ATTACK_VECTORS] || []),
|
|
10254
|
+
failures: filterNone(sections[MEMO_PARSE_KEYS.FAILURES] || []),
|
|
10255
|
+
suspicions: filterNone(sections[MEMO_PARSE_KEYS.SUSPICIONS] || []),
|
|
10162
10256
|
attackValue,
|
|
10163
|
-
nextSteps: filterNone(sections[
|
|
10257
|
+
nextSteps: filterNone(sections[MEMO_PARSE_KEYS.NEXT_STEPS] || []),
|
|
10164
10258
|
reflection: [
|
|
10165
10259
|
attackValueReasoning ? `[${attackValue}] ${attackValueReasoning}` : "",
|
|
10166
10260
|
...reflectionLines
|
|
@@ -10205,14 +10299,14 @@ function normalizeLine(line) {
|
|
|
10205
10299
|
}
|
|
10206
10300
|
function saveFullOutput(output, toolName) {
|
|
10207
10301
|
try {
|
|
10208
|
-
|
|
10209
|
-
|
|
10210
|
-
|
|
10211
|
-
}
|
|
10212
|
-
const
|
|
10213
|
-
|
|
10214
|
-
const filePath = `${
|
|
10215
|
-
|
|
10302
|
+
if (_currentTurn === null) {
|
|
10303
|
+
debugLog("general", "saveFullOutput called without current turn set", { toolName });
|
|
10304
|
+
return "(no current turn \u2014 output not saved)";
|
|
10305
|
+
}
|
|
10306
|
+
const toolsDir = WORKSPACE.turnToolsPath(_currentTurn);
|
|
10307
|
+
ensureDirExists(toolsDir);
|
|
10308
|
+
const filePath = `${toolsDir}/${FILE_PATTERNS.toolOutput(toolName)}`;
|
|
10309
|
+
writeFileSync8(filePath, output, "utf-8");
|
|
10216
10310
|
return filePath;
|
|
10217
10311
|
} catch (err) {
|
|
10218
10312
|
debugLog("general", "Failed to save full output to file", { toolName, error: String(err) });
|
|
@@ -10241,452 +10335,90 @@ ${text}
|
|
|
10241
10335
|
};
|
|
10242
10336
|
}
|
|
10243
10337
|
|
|
10244
|
-
// src/agents/
|
|
10245
|
-
var
|
|
10246
|
-
llm;
|
|
10338
|
+
// src/agents/tool-executor.ts
|
|
10339
|
+
var ToolExecutor = class _ToolExecutor {
|
|
10247
10340
|
state;
|
|
10248
10341
|
events;
|
|
10249
10342
|
toolRegistry;
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
abortController = null;
|
|
10253
|
-
/**
|
|
10254
|
-
* Collected tool execution records for the current turn.
|
|
10255
|
-
* MainAgent reads this after each step to write journal entries.
|
|
10256
|
-
* Cleared at the start of each step.
|
|
10257
|
-
*/
|
|
10343
|
+
llm;
|
|
10344
|
+
/** Collected tool execution records for the current turn */
|
|
10258
10345
|
turnToolJournal = [];
|
|
10259
10346
|
/** Aggregated memo from all tools in the current turn */
|
|
10260
|
-
turnMemo = {
|
|
10261
|
-
|
|
10347
|
+
turnMemo = {
|
|
10348
|
+
keyFindings: [],
|
|
10349
|
+
credentials: [],
|
|
10350
|
+
attackVectors: [],
|
|
10351
|
+
failures: [],
|
|
10352
|
+
suspicions: [],
|
|
10353
|
+
attackValue: "LOW",
|
|
10354
|
+
nextSteps: []
|
|
10355
|
+
};
|
|
10356
|
+
/** Analyst reflections collected during this turn */
|
|
10262
10357
|
turnReflections = [];
|
|
10263
|
-
|
|
10264
|
-
|
|
10265
|
-
|
|
10266
|
-
|
|
10267
|
-
|
|
10268
|
-
|
|
10269
|
-
|
|
10270
|
-
|
|
10271
|
-
|
|
10272
|
-
|
|
10273
|
-
|
|
10274
|
-
|
|
10275
|
-
|
|
10276
|
-
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
this.
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10290
|
-
|
|
10291
|
-
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
|
|
10296
|
-
|
|
10297
|
-
|
|
10298
|
-
|
|
10299
|
-
|
|
10300
|
-
}
|
|
10301
|
-
try {
|
|
10302
|
-
const result2 = await this.step(iteration, messages, systemPrompt, progress);
|
|
10303
|
-
progress.totalToolsExecuted += result2.toolsExecuted;
|
|
10304
|
-
consecutiveLLMErrors = 0;
|
|
10305
|
-
if (result2.toolsExecuted > 0) {
|
|
10306
|
-
progress.consecutiveIdleIterations = 0;
|
|
10307
|
-
progress.lastToolExecutedAt = Date.now();
|
|
10308
|
-
} else if (!result2.isCompleted) {
|
|
10309
|
-
progress.consecutiveIdleIterations++;
|
|
10310
|
-
}
|
|
10311
|
-
if (result2.isCompleted) {
|
|
10312
|
-
return {
|
|
10313
|
-
output: result2.output,
|
|
10314
|
-
iterations: iteration + 1,
|
|
10315
|
-
toolsExecuted: progress.totalToolsExecuted,
|
|
10316
|
-
isCompleted: true
|
|
10317
|
-
};
|
|
10318
|
-
}
|
|
10319
|
-
if (progress.consecutiveIdleIterations >= AGENT_LIMITS.MAX_CONSECUTIVE_IDLE) {
|
|
10320
|
-
progress.consecutiveIdleIterations = 0;
|
|
10321
|
-
messages.push({ role: LLM_ROLES.USER, content: this.buildDeadlockNudge(progress) });
|
|
10322
|
-
}
|
|
10323
|
-
} catch (error) {
|
|
10324
|
-
if (this.isAbortError(error)) {
|
|
10325
|
-
return this.buildCancelledResult(iteration, progress.totalToolsExecuted);
|
|
10326
|
-
}
|
|
10327
|
-
if (error instanceof LLMError) {
|
|
10328
|
-
const errorInfo = error.errorInfo;
|
|
10329
|
-
this.events.emit({
|
|
10330
|
-
type: EVENT_TYPES.ERROR,
|
|
10331
|
-
timestamp: Date.now(),
|
|
10332
|
-
data: {
|
|
10333
|
-
message: `LLM Error: ${errorInfo.message}`,
|
|
10334
|
-
phase: this.state.getPhase(),
|
|
10335
|
-
isRecoverable: errorInfo.isRetryable,
|
|
10336
|
-
errorInfo
|
|
10337
|
-
}
|
|
10338
|
-
});
|
|
10339
|
-
if (errorInfo.type === LLM_ERROR_TYPES.RATE_LIMIT) {
|
|
10340
|
-
consecutiveLLMErrors++;
|
|
10341
|
-
if (maxConsecutiveLLMErrors !== Infinity && consecutiveLLMErrors >= maxConsecutiveLLMErrors) {
|
|
10342
|
-
return {
|
|
10343
|
-
output: `LLM rate limit errors exceeded limit. Last error: ${errorInfo.message}`,
|
|
10344
|
-
iterations: iteration + 1,
|
|
10345
|
-
toolsExecuted: progress.totalToolsExecuted,
|
|
10346
|
-
isCompleted: false
|
|
10347
|
-
};
|
|
10348
|
-
}
|
|
10349
|
-
continue;
|
|
10350
|
-
}
|
|
10351
|
-
consecutiveLLMErrors = 0;
|
|
10352
|
-
const errorMessage = this.formatLLMErrorForAgent(errorInfo);
|
|
10353
|
-
messages.push({
|
|
10354
|
-
role: LLM_ROLES.USER,
|
|
10355
|
-
content: errorMessage
|
|
10356
|
-
});
|
|
10357
|
-
continue;
|
|
10358
|
-
}
|
|
10359
|
-
const unexpectedMsg = error instanceof Error ? error.message : String(error);
|
|
10360
|
-
this.events.emit({
|
|
10361
|
-
type: EVENT_TYPES.ERROR,
|
|
10362
|
-
timestamp: Date.now(),
|
|
10363
|
-
data: {
|
|
10364
|
-
message: `Unexpected error: ${unexpectedMsg}`,
|
|
10365
|
-
phase: this.state.getPhase(),
|
|
10366
|
-
isRecoverable: true
|
|
10367
|
-
}
|
|
10368
|
-
});
|
|
10369
|
-
messages.push({
|
|
10370
|
-
role: LLM_ROLES.USER,
|
|
10371
|
-
content: `\u26A0\uFE0F UNEXPECTED ERROR: ${unexpectedMsg}
|
|
10372
|
-
This may be a transient issue. Continue your task \u2014 retry the last action or try an alternative approach.`
|
|
10373
|
-
});
|
|
10374
|
-
continue;
|
|
10375
|
-
}
|
|
10376
|
-
}
|
|
10377
|
-
const summary = `Max iterations (${this.maxIterations}) reached. Progress: ${progress.totalToolsExecuted} tools executed (${progress.toolSuccesses} succeeded, ${progress.toolErrors} failed). Current phase: ${this.state.getPhase()}. Findings: ${this.state.getFindings?.()?.length ?? "unknown"}.`;
|
|
10378
|
-
return {
|
|
10379
|
-
output: summary,
|
|
10380
|
-
iterations: this.maxIterations,
|
|
10381
|
-
toolsExecuted: progress.totalToolsExecuted,
|
|
10382
|
-
isCompleted: false
|
|
10383
|
-
};
|
|
10384
|
-
}
|
|
10385
|
-
// ─────────────────────────────────────────────────────────────────
|
|
10386
|
-
// SUBSECTION: Error Formatting
|
|
10387
|
-
// ─────────────────────────────────────────────────────────────────
|
|
10388
|
-
/**
|
|
10389
|
-
* Format LLM error as a message for the agent to understand and act on
|
|
10390
|
-
*/
|
|
10391
|
-
formatLLMErrorForAgent(errorInfo) {
|
|
10392
|
-
const actionHints = {
|
|
10393
|
-
[LLM_ERROR_TYPES.RATE_LIMIT]: "Wait a moment and try again with a simpler query.",
|
|
10394
|
-
[LLM_ERROR_TYPES.AUTH_ERROR]: "Use the ask_user tool to request a valid API key from the user. Set the PENTEST_API_KEY environment variable.",
|
|
10395
|
-
[LLM_ERROR_TYPES.INVALID_REQUEST]: "Simplify your request or modify the parameters.",
|
|
10396
|
-
[LLM_ERROR_TYPES.NETWORK_ERROR]: "Check network connectivity and retry.",
|
|
10397
|
-
[LLM_ERROR_TYPES.TIMEOUT]: "Retry with a shorter or simpler request.",
|
|
10398
|
-
[LLM_ERROR_TYPES.UNKNOWN]: "Analyze the error and decide the best course of action."
|
|
10399
|
-
};
|
|
10400
|
-
return `[SYSTEM ERROR - LLM API Issue]
|
|
10401
|
-
Error Type: ${errorInfo.type}
|
|
10402
|
-
Message: ${errorInfo.message}
|
|
10403
|
-
Status Code: ${errorInfo.statusCode || "N/A"}
|
|
10404
|
-
Retryable: ${errorInfo.isRetryable ? "Yes" : "No"}
|
|
10405
|
-
|
|
10406
|
-
Suggested Action: ${errorInfo.suggestedAction || actionHints[errorInfo.type] || "Decide appropriate action"}
|
|
10407
|
-
|
|
10408
|
-
How to proceed:
|
|
10409
|
-
1. If retryable: Wait briefly and continue your task
|
|
10410
|
-
2. If auth error: Use ask_user tool to request API key from user
|
|
10411
|
-
3. If invalid request: Simplify or modify your approach
|
|
10412
|
-
4. If persistent: Report to user and await instructions
|
|
10413
|
-
|
|
10414
|
-
Please decide how to handle this error and continue.`;
|
|
10415
|
-
}
|
|
10416
|
-
// ─────────────────────────────────────────────────────────────────
|
|
10417
|
-
// SUBSECTION: Step Execution
|
|
10418
|
-
// ─────────────────────────────────────────────────────────────────
|
|
10419
|
-
/** Execute a single Think → Act → Observe iteration */
|
|
10420
|
-
async step(iteration, messages, systemPrompt, progress) {
|
|
10421
|
-
const phase = this.state.getPhase();
|
|
10422
|
-
const stepStartTime = Date.now();
|
|
10423
|
-
this.emitThink(iteration, progress);
|
|
10424
|
-
const callbacks = this.buildStreamCallbacks(phase);
|
|
10425
|
-
const response = await this.llm.generateResponseStream(
|
|
10426
|
-
messages,
|
|
10427
|
-
this.getToolSchemas(),
|
|
10428
|
-
systemPrompt,
|
|
10429
|
-
callbacks
|
|
10430
|
-
);
|
|
10431
|
-
if (response.reasoning && !callbacks.hadReasoningEnd()) {
|
|
10432
|
-
this.emitReasoningStart(phase);
|
|
10433
|
-
this.emitReasoningDelta(response.reasoning, phase);
|
|
10434
|
-
this.emitReasoningEnd(phase);
|
|
10435
|
-
}
|
|
10436
|
-
if (response.content?.trim()) {
|
|
10437
|
-
if (!response.reasoning && !callbacks.hadReasoningEnd()) {
|
|
10438
|
-
this.emitReasoningStart(phase);
|
|
10439
|
-
this.emitReasoningDelta(response.content.trim(), phase);
|
|
10440
|
-
this.emitReasoningEnd(phase);
|
|
10441
|
-
} else {
|
|
10442
|
-
this.events.emit({
|
|
10443
|
-
type: EVENT_TYPES.AI_RESPONSE,
|
|
10444
|
-
timestamp: Date.now(),
|
|
10445
|
-
data: { content: response.content.trim(), phase }
|
|
10446
|
-
});
|
|
10447
|
-
}
|
|
10448
|
-
}
|
|
10449
|
-
messages.push({ role: LLM_ROLES.ASSISTANT, content: response.content });
|
|
10450
|
-
const stepDuration = Date.now() - stepStartTime;
|
|
10451
|
-
const tokens = response.usage ? { input: response.usage.input_tokens, output: response.usage.output_tokens } : void 0;
|
|
10452
|
-
if (!response.toolCalls?.length) {
|
|
10453
|
-
return { output: response.content, toolsExecuted: 0, isCompleted: false };
|
|
10454
|
-
}
|
|
10455
|
-
const results = await this.processToolCalls(response.toolCalls, progress);
|
|
10456
|
-
this.addToolResultsToMessages(messages, results);
|
|
10457
|
-
return { output: "", toolsExecuted: results.length, isCompleted: false };
|
|
10458
|
-
}
|
|
10459
|
-
// ─────────────────────────────────────────────────────────────────
|
|
10460
|
-
// SUBSECTION: Callback Builder
|
|
10461
|
-
// ─────────────────────────────────────────────────────────────────
|
|
10462
|
-
buildStreamCallbacks(phase) {
|
|
10463
|
-
let _reasoningEndFired = false;
|
|
10464
|
-
let _outputBuffer = "";
|
|
10465
|
-
const callbacks = {
|
|
10466
|
-
onReasoningStart: () => this.emitReasoningStart(phase),
|
|
10467
|
-
onReasoningDelta: (content) => this.emitReasoningDelta(content, phase),
|
|
10468
|
-
onReasoningEnd: () => {
|
|
10469
|
-
_reasoningEndFired = true;
|
|
10470
|
-
this.emitReasoningEnd(phase);
|
|
10471
|
-
},
|
|
10472
|
-
// WHY: Show AI text as it streams, not after completion.
|
|
10473
|
-
// The user sees what the AI is writing in real-time via the status bar.
|
|
10474
|
-
onOutputDelta: (text) => {
|
|
10475
|
-
_outputBuffer += text;
|
|
10476
|
-
const firstLine = _outputBuffer.split("\n")[0]?.slice(0, 120) || "";
|
|
10477
|
-
this.events.emit({
|
|
10478
|
-
type: EVENT_TYPES.THINK,
|
|
10479
|
-
timestamp: Date.now(),
|
|
10480
|
-
data: { thought: `Writing\u2026 ${_outputBuffer.length} chars
|
|
10481
|
-
${firstLine}`, phase }
|
|
10482
|
-
});
|
|
10483
|
-
},
|
|
10484
|
-
onRetry: (attempt, maxRetries, delayMs, error) => {
|
|
10485
|
-
this.events.emit({
|
|
10486
|
-
type: EVENT_TYPES.RETRY,
|
|
10487
|
-
timestamp: Date.now(),
|
|
10488
|
-
data: { attempt, maxRetries, delayMs, error, phase }
|
|
10489
|
-
});
|
|
10490
|
-
},
|
|
10491
|
-
onUsageUpdate: (usage) => {
|
|
10492
|
-
this.events.emit({
|
|
10493
|
-
type: EVENT_TYPES.USAGE_UPDATE,
|
|
10494
|
-
timestamp: Date.now(),
|
|
10495
|
-
data: { inputTokens: usage.input_tokens, outputTokens: usage.output_tokens }
|
|
10496
|
-
});
|
|
10497
|
-
},
|
|
10498
|
-
abortSignal: this.abortController?.signal,
|
|
10499
|
-
// WHY: Used by step() to detect if SSE reasoning blocks fired.
|
|
10500
|
-
// If not, we know it was an inline <think> model and must emit post-stream.
|
|
10501
|
-
hadReasoningEnd: () => _reasoningEndFired
|
|
10502
|
-
};
|
|
10503
|
-
return callbacks;
|
|
10504
|
-
}
|
|
10505
|
-
// ─────────────────────────────────────────────────────────────────
|
|
10506
|
-
// SUBSECTION: Deadlock Nudge Builder
|
|
10507
|
-
// ─────────────────────────────────────────────────────────────────
|
|
10508
|
-
/**
|
|
10509
|
-
* Build a deadlock nudge message for the agent.
|
|
10510
|
-
*
|
|
10511
|
-
* WHY separated: The nudge template is ~30 lines of prompt engineering.
|
|
10512
|
-
* Keeping it in run() obscures the iteration control logic.
|
|
10513
|
-
* Philosophy §12: Nudge is a safety net, not a driver —
|
|
10514
|
-
* it reminds the agent to ACT, but never prescribes HOW.
|
|
10515
|
-
*/
|
|
10516
|
-
buildDeadlockNudge(progress) {
|
|
10517
|
-
const phase = this.state.getPhase();
|
|
10518
|
-
const targets = this.state.getTargets().size;
|
|
10519
|
-
const findings = this.state.getFindings().length;
|
|
10520
|
-
const phaseDirection = {
|
|
10521
|
-
[PHASES.RECON]: `RECON: Scan targets. Enumerate services and versions.`,
|
|
10522
|
-
[PHASES.VULN_ANALYSIS]: `VULN ANALYSIS: ${targets} target(s) discovered. Search for CVEs and known exploits.`,
|
|
10523
|
-
[PHASES.EXPLOIT]: `EXPLOIT: ${findings} finding(s) available. Attack the highest-severity one.`,
|
|
10524
|
-
[PHASES.POST_EXPLOIT]: `POST-EXPLOIT: Escalate privileges. Search for escalation paths.`,
|
|
10525
|
-
[PHASES.PRIV_ESC]: `PRIVESC: Find and exploit privilege escalation vectors.`,
|
|
10526
|
-
[PHASES.LATERAL]: `LATERAL: Reuse discovered credentials on other hosts.`,
|
|
10527
|
-
[PHASES.WEB]: `WEB: Enumerate the attack surface. Test every input for injection.`
|
|
10528
|
-
};
|
|
10529
|
-
const direction = phaseDirection[phase] || phaseDirection[PHASES.RECON];
|
|
10530
|
-
return `\u26A1 DEADLOCK: ${AGENT_LIMITS.MAX_CONSECUTIVE_IDLE} turns with ZERO tool calls.
|
|
10531
|
-
Phase: ${phase} | Targets: ${targets} | Findings: ${findings} | Tools executed: ${progress.totalToolsExecuted} (${progress.toolSuccesses}\u2713 ${progress.toolErrors}\u2717)
|
|
10532
|
-
|
|
10533
|
-
${direction}
|
|
10534
|
-
|
|
10535
|
-
ESCALATION CHAIN \u2014 follow this order:
|
|
10536
|
-
1. web_search: Search for techniques, bypasses, default creds, CVEs, HackTricks
|
|
10537
|
-
2. BYPASS: Try alternative approaches \u2014 different protocols, ports, encodings, methods
|
|
10538
|
-
3. ZERO-DAY EXPLORATION: Probe for unknown vulns \u2014 fuzz parameters, test edge cases, analyze error responses for leaks
|
|
10539
|
-
4. BRUTE-FORCE: Wordlists, credential stuffing, common passwords, custom password lists from context
|
|
10540
|
-
5. ask_user: ONLY as last resort \u2014 ask the user for hints, wordlists, or guidance
|
|
10541
|
-
|
|
10542
|
-
RULES:
|
|
10543
|
-
- Every turn MUST have tool calls
|
|
10544
|
-
- NEVER silently give up \u2014 exhaust ALL 5 steps above first
|
|
10545
|
-
- ACT NOW \u2014 do not plan, do not explain, do not summarize. EXECUTE.`;
|
|
10546
|
-
}
|
|
10547
|
-
// ─────────────────────────────────────────────────────────────────
|
|
10548
|
-
// SUBSECTION: Event Emitters
|
|
10549
|
-
// ─────────────────════════════════════════════════════════════
|
|
10550
|
-
emitThink(iteration, progress) {
|
|
10551
|
-
const phase = this.state.getPhase();
|
|
10552
|
-
const targets = this.state.getTargets().size;
|
|
10553
|
-
const findings = this.state.getFindings().length;
|
|
10554
|
-
const toolsUsed = progress?.totalToolsExecuted ?? 0;
|
|
10555
|
-
const hasErrors = (progress?.toolErrors ?? 0) > 0;
|
|
10556
|
-
let thought;
|
|
10557
|
-
if (iteration === 0) {
|
|
10558
|
-
thought = targets > 0 ? `Analyzing ${targets} target${targets > 1 ? "s" : ""} \xB7 Planning ${phase} approach` : `Reviewing task \xB7 Building ${phase} strategy`;
|
|
10559
|
-
} else if (toolsUsed === 0) {
|
|
10560
|
-
thought = `Iteration ${iteration + 1} \xB7 No actions yet \xB7 Reconsidering approach`;
|
|
10561
|
-
} else if (hasErrors) {
|
|
10562
|
-
thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tools \xB7 ${progress?.toolErrors} error${(progress?.toolErrors ?? 0) > 1 ? "s" : ""} \xB7 Adapting strategy`;
|
|
10563
|
-
} else if (findings > 0) {
|
|
10564
|
-
thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${findings} finding${findings > 1 ? "s" : ""} \xB7 ${toolsUsed} tools \xB7 Continuing`;
|
|
10565
|
-
} else {
|
|
10566
|
-
thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tool${toolsUsed !== 1 ? "s" : ""} executed \xB7 Evaluating results`;
|
|
10567
|
-
}
|
|
10568
|
-
this.events.emit({
|
|
10569
|
-
type: EVENT_TYPES.THINK,
|
|
10570
|
-
timestamp: Date.now(),
|
|
10571
|
-
data: {
|
|
10572
|
-
thought,
|
|
10573
|
-
phase
|
|
10574
|
-
}
|
|
10575
|
-
});
|
|
10576
|
-
}
|
|
10577
|
-
/** Emit reasoning lifecycle events for extended thinking */
|
|
10578
|
-
emitReasoningStart(phase) {
|
|
10579
|
-
this.events.emit({ type: EVENT_TYPES.REASONING_START, timestamp: Date.now(), data: { phase } });
|
|
10580
|
-
}
|
|
10581
|
-
emitReasoningDelta(content, phase) {
|
|
10582
|
-
this.events.emit({ type: EVENT_TYPES.REASONING_DELTA, timestamp: Date.now(), data: { content, phase } });
|
|
10583
|
-
}
|
|
10584
|
-
emitReasoningEnd(phase) {
|
|
10585
|
-
this.events.emit({ type: EVENT_TYPES.REASONING_END, timestamp: Date.now(), data: { phase } });
|
|
10586
|
-
}
|
|
10587
|
-
emitComplete(output, iteration, toolsExecuted, durationMs, tokens) {
|
|
10588
|
-
this.events.emit({
|
|
10589
|
-
type: EVENT_TYPES.COMPLETE,
|
|
10590
|
-
timestamp: Date.now(),
|
|
10591
|
-
data: {
|
|
10592
|
-
finalOutput: output,
|
|
10593
|
-
iterations: iteration + 1,
|
|
10594
|
-
toolsExecuted,
|
|
10595
|
-
durationMs,
|
|
10596
|
-
tokens
|
|
10597
|
-
}
|
|
10598
|
-
});
|
|
10599
|
-
}
|
|
10600
|
-
/** Emit tool call event for TUI tracking */
|
|
10601
|
-
emitToolCall(toolName, input) {
|
|
10602
|
-
this.events.emit({
|
|
10603
|
-
type: EVENT_TYPES.TOOL_CALL,
|
|
10604
|
-
timestamp: Date.now(),
|
|
10605
|
-
data: {
|
|
10606
|
-
toolName,
|
|
10607
|
-
input,
|
|
10608
|
-
approvalLevel: APPROVAL_LEVELS.AUTO,
|
|
10609
|
-
needsApproval: false
|
|
10610
|
-
}
|
|
10611
|
-
});
|
|
10612
|
-
}
|
|
10613
|
-
/** Emit tool result event for TUI tracking */
|
|
10614
|
-
emitToolResult(toolName, success, output, error, duration) {
|
|
10615
|
-
this.events.emit({
|
|
10616
|
-
type: EVENT_TYPES.TOOL_RESULT,
|
|
10617
|
-
timestamp: Date.now(),
|
|
10618
|
-
data: {
|
|
10619
|
-
toolName,
|
|
10620
|
-
success,
|
|
10621
|
-
output,
|
|
10622
|
-
outputSummary: output.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
|
|
10623
|
-
error,
|
|
10624
|
-
duration
|
|
10625
|
-
}
|
|
10626
|
-
});
|
|
10358
|
+
/** Tools safe to run in parallel */
|
|
10359
|
+
static PARALLEL_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
10360
|
+
TOOL_NAMES.GET_STATE,
|
|
10361
|
+
TOOL_NAMES.PARSE_NMAP,
|
|
10362
|
+
TOOL_NAMES.SEARCH_CVE,
|
|
10363
|
+
TOOL_NAMES.WEB_SEARCH,
|
|
10364
|
+
TOOL_NAMES.BROWSE_URL,
|
|
10365
|
+
TOOL_NAMES.READ_FILE,
|
|
10366
|
+
TOOL_NAMES.GET_OWASP_KNOWLEDGE,
|
|
10367
|
+
TOOL_NAMES.GET_WEB_ATTACK_SURFACE,
|
|
10368
|
+
TOOL_NAMES.GET_CVE_INFO,
|
|
10369
|
+
TOOL_NAMES.FILL_FORM,
|
|
10370
|
+
TOOL_NAMES.ADD_TARGET,
|
|
10371
|
+
TOOL_NAMES.ADD_FINDING,
|
|
10372
|
+
TOOL_NAMES.ADD_LOOT,
|
|
10373
|
+
TOOL_NAMES.UPDATE_MISSION,
|
|
10374
|
+
TOOL_NAMES.UPDATE_TODO
|
|
10375
|
+
]);
|
|
10376
|
+
constructor(deps) {
|
|
10377
|
+
this.state = deps.state;
|
|
10378
|
+
this.events = deps.events;
|
|
10379
|
+
this.toolRegistry = deps.toolRegistry;
|
|
10380
|
+
this.llm = deps.llm;
|
|
10381
|
+
}
|
|
10382
|
+
/** Clear turn-level state at the start of each step */
|
|
10383
|
+
clearTurnState() {
|
|
10384
|
+
this.turnToolJournal = [];
|
|
10385
|
+
this.turnMemo = {
|
|
10386
|
+
keyFindings: [],
|
|
10387
|
+
credentials: [],
|
|
10388
|
+
attackVectors: [],
|
|
10389
|
+
failures: [],
|
|
10390
|
+
suspicions: [],
|
|
10391
|
+
attackValue: "LOW",
|
|
10392
|
+
nextSteps: []
|
|
10393
|
+
};
|
|
10394
|
+
this.turnReflections = [];
|
|
10627
10395
|
}
|
|
10628
10396
|
// ─────────────────────────────────────────────────────────────────
|
|
10629
|
-
// SUBSECTION: Tool
|
|
10397
|
+
// SUBSECTION: Tool Execution
|
|
10630
10398
|
// ─────────────────────────────────────────────────────────────────
|
|
10631
|
-
addToolResultsToMessages(messages, results) {
|
|
10632
|
-
for (const res of results) {
|
|
10633
|
-
messages.push({
|
|
10634
|
-
role: LLM_ROLES.USER,
|
|
10635
|
-
content: [
|
|
10636
|
-
{
|
|
10637
|
-
type: LLM_BLOCK_TYPE.TOOL_RESULT,
|
|
10638
|
-
tool_use_id: res.toolCallId,
|
|
10639
|
-
content: res.output,
|
|
10640
|
-
is_error: !!res.error
|
|
10641
|
-
}
|
|
10642
|
-
]
|
|
10643
|
-
});
|
|
10644
|
-
}
|
|
10645
|
-
}
|
|
10646
|
-
/** Tools that are safe to run in parallel (read-only or per-key state mutations) */
|
|
10647
|
-
static PARALLEL_SAFE_TOOLS = /* @__PURE__ */ new Set([
|
|
10648
|
-
// Read-only intelligence tools
|
|
10649
|
-
TOOL_NAMES.GET_STATE,
|
|
10650
|
-
TOOL_NAMES.PARSE_NMAP,
|
|
10651
|
-
TOOL_NAMES.SEARCH_CVE,
|
|
10652
|
-
TOOL_NAMES.WEB_SEARCH,
|
|
10653
|
-
TOOL_NAMES.BROWSE_URL,
|
|
10654
|
-
TOOL_NAMES.READ_FILE,
|
|
10655
|
-
TOOL_NAMES.GET_OWASP_KNOWLEDGE,
|
|
10656
|
-
TOOL_NAMES.GET_WEB_ATTACK_SURFACE,
|
|
10657
|
-
TOOL_NAMES.GET_CVE_INFO,
|
|
10658
|
-
TOOL_NAMES.FILL_FORM,
|
|
10659
|
-
// State recording (per-key mutations, no external side effects)
|
|
10660
|
-
TOOL_NAMES.ADD_TARGET,
|
|
10661
|
-
TOOL_NAMES.ADD_FINDING,
|
|
10662
|
-
TOOL_NAMES.ADD_LOOT,
|
|
10663
|
-
TOOL_NAMES.UPDATE_MISSION,
|
|
10664
|
-
TOOL_NAMES.UPDATE_TODO
|
|
10665
|
-
]);
|
|
10666
10399
|
async processToolCalls(toolCalls, progress) {
|
|
10667
10400
|
if (toolCalls.length <= 1) {
|
|
10668
|
-
return this.
|
|
10401
|
+
return this.executeSequentially(toolCalls, progress);
|
|
10669
10402
|
}
|
|
10670
|
-
const allParallelSafe = toolCalls.every((c) =>
|
|
10403
|
+
const allParallelSafe = toolCalls.every((c) => _ToolExecutor.PARALLEL_SAFE_TOOLS.has(c.name));
|
|
10671
10404
|
if (allParallelSafe) {
|
|
10672
|
-
return this.
|
|
10405
|
+
return this.executeInParallel(toolCalls, progress);
|
|
10673
10406
|
}
|
|
10674
|
-
const parallelCalls = toolCalls.filter((c) =>
|
|
10675
|
-
const sequentialCalls = toolCalls.filter((c) => !
|
|
10676
|
-
const parallelResults = parallelCalls.length > 0 ? await this.
|
|
10677
|
-
const sequentialResults = sequentialCalls.length > 0 ? await this.
|
|
10407
|
+
const parallelCalls = toolCalls.filter((c) => _ToolExecutor.PARALLEL_SAFE_TOOLS.has(c.name));
|
|
10408
|
+
const sequentialCalls = toolCalls.filter((c) => !_ToolExecutor.PARALLEL_SAFE_TOOLS.has(c.name));
|
|
10409
|
+
const parallelResults = parallelCalls.length > 0 ? await this.executeInParallel(parallelCalls, progress) : [];
|
|
10410
|
+
const sequentialResults = sequentialCalls.length > 0 ? await this.executeSequentially(sequentialCalls, progress) : [];
|
|
10678
10411
|
const resultMap = /* @__PURE__ */ new Map();
|
|
10679
10412
|
for (const r of [...parallelResults, ...sequentialResults]) {
|
|
10680
10413
|
resultMap.set(r.toolCallId, r);
|
|
10681
10414
|
}
|
|
10682
10415
|
return toolCalls.map((c) => resultMap.get(c.id)).filter(Boolean);
|
|
10683
10416
|
}
|
|
10684
|
-
|
|
10685
|
-
async executeToolCallsInParallel(toolCalls, progress) {
|
|
10417
|
+
async executeInParallel(toolCalls, progress) {
|
|
10686
10418
|
for (const call of toolCalls) {
|
|
10687
10419
|
this.emitToolCall(call.name, call.input);
|
|
10688
10420
|
}
|
|
10689
|
-
const promises = toolCalls.map((call) => this.
|
|
10421
|
+
const promises = toolCalls.map((call) => this.executeSingle(call, progress));
|
|
10690
10422
|
const settled = await Promise.allSettled(promises);
|
|
10691
10423
|
return settled.map((s, i) => {
|
|
10692
10424
|
if (s.status === "fulfilled") return s.value;
|
|
@@ -10694,23 +10426,17 @@ RULES:
|
|
|
10694
10426
|
return { toolCallId: toolCalls[i].id, output: errorMsg, error: errorMsg };
|
|
10695
10427
|
});
|
|
10696
10428
|
}
|
|
10697
|
-
|
|
10698
|
-
async executeToolCallsSequentially(toolCalls, progress) {
|
|
10429
|
+
async executeSequentially(toolCalls, progress) {
|
|
10699
10430
|
const results = [];
|
|
10700
10431
|
for (const call of toolCalls) {
|
|
10701
10432
|
this.emitToolCall(call.name, call.input);
|
|
10702
|
-
const result2 = await this.
|
|
10433
|
+
const result2 = await this.executeSingle(call, progress);
|
|
10703
10434
|
results.push(result2);
|
|
10704
10435
|
}
|
|
10705
10436
|
return results;
|
|
10706
10437
|
}
|
|
10707
|
-
|
|
10708
|
-
async executeSingleTool(call, progress) {
|
|
10438
|
+
async executeSingle(call, progress) {
|
|
10709
10439
|
const toolStartTime = Date.now();
|
|
10710
|
-
logLLM("CoreAgent executing tool", { id: call.id, name: call.name, input: call.input });
|
|
10711
|
-
if (!this.toolRegistry) {
|
|
10712
|
-
return { toolCallId: call.id, output: "", error: "Tool registry not initialized. Call setToolRegistry() first." };
|
|
10713
|
-
}
|
|
10714
10440
|
try {
|
|
10715
10441
|
const result2 = await this.toolRegistry.execute({ name: call.name, input: call.input });
|
|
10716
10442
|
let outputText = result2.output ?? "";
|
|
@@ -10726,20 +10452,31 @@ RULES:
|
|
|
10726
10452
|
return { toolCallId: call.id, output: digestedOutputForLLM, error: result2.error };
|
|
10727
10453
|
} catch (error) {
|
|
10728
10454
|
const errorMsg = String(error);
|
|
10729
|
-
const enrichedError =
|
|
10455
|
+
const enrichedError = enrichToolErrorContext({
|
|
10456
|
+
toolName: call.name,
|
|
10457
|
+
input: call.input,
|
|
10458
|
+
error: errorMsg,
|
|
10459
|
+
originalOutput: "",
|
|
10460
|
+
progress
|
|
10461
|
+
});
|
|
10730
10462
|
if (progress) progress.toolErrors++;
|
|
10731
10463
|
this.emitToolResult(call.name, false, enrichedError, errorMsg, Date.now() - toolStartTime);
|
|
10732
10464
|
return { toolCallId: call.id, output: enrichedError, error: errorMsg };
|
|
10733
10465
|
}
|
|
10734
10466
|
}
|
|
10735
|
-
|
|
10736
|
-
|
|
10737
|
-
|
|
10738
|
-
*/
|
|
10467
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10468
|
+
// SUBSECTION: Result Handling
|
|
10469
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10739
10470
|
handleToolResult(result2, call, outputText, progress) {
|
|
10740
10471
|
if (result2.error) {
|
|
10741
10472
|
if (progress) progress.toolErrors++;
|
|
10742
|
-
return
|
|
10473
|
+
return enrichToolErrorContext({
|
|
10474
|
+
toolName: call.name,
|
|
10475
|
+
input: call.input,
|
|
10476
|
+
error: result2.error,
|
|
10477
|
+
originalOutput: outputText,
|
|
10478
|
+
progress
|
|
10479
|
+
});
|
|
10743
10480
|
}
|
|
10744
10481
|
if (progress) {
|
|
10745
10482
|
progress.toolSuccesses++;
|
|
@@ -10747,15 +10484,7 @@ RULES:
|
|
|
10747
10484
|
}
|
|
10748
10485
|
return outputText;
|
|
10749
10486
|
}
|
|
10750
|
-
/**
|
|
10751
|
-
* Digest tool output via Analyst LLM (§13 ③) and emit TUI event.
|
|
10752
|
-
*
|
|
10753
|
-
* WHY separated: Digest + emit is a self-contained pipeline:
|
|
10754
|
-
* raw output → Analyst → digest + file → TUI event.
|
|
10755
|
-
* Isolating it makes the pipeline testable without running actual tools.
|
|
10756
|
-
*/
|
|
10757
10487
|
async digestAndEmit(call, outputText, result2, toolStartTime) {
|
|
10758
|
-
const digestFallbackOutput = outputText;
|
|
10759
10488
|
let digestedOutputForLLM = outputText;
|
|
10760
10489
|
let digestResult = null;
|
|
10761
10490
|
try {
|
|
@@ -10773,22 +10502,15 @@ RULES:
|
|
|
10773
10502
|
const remaining = digestedOutputForLLM.length - AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH;
|
|
10774
10503
|
digestedOutputForLLM = `${truncated}
|
|
10775
10504
|
|
|
10776
|
-
... [TRUNCATED ${remaining}
|
|
10777
|
-
\u{1F4A1} TIP: If you need to see the full output, use a tool to read the file directly or run the command with | head, | tail, or | grep.`;
|
|
10505
|
+
... [TRUNCATED ${remaining} chars] ...`;
|
|
10778
10506
|
}
|
|
10779
10507
|
}
|
|
10780
10508
|
const outputFilePath = digestResult?.fullOutputPath ?? null;
|
|
10781
10509
|
const tuiOutput = digestResult?.digestedOutput ? `${digestResult.digestedOutput}${outputFilePath ? `
|
|
10782
|
-
\u{1F4C4} Full output: ${outputFilePath}` : ""}` :
|
|
10510
|
+
\u{1F4C4} Full output: ${outputFilePath}` : ""}` : outputText.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY);
|
|
10783
10511
|
this.emitToolResult(call.name, result2.success, tuiOutput, result2.error, Date.now() - toolStartTime);
|
|
10784
10512
|
return { digestedOutputForLLM, digestResult };
|
|
10785
10513
|
}
|
|
10786
|
-
/**
|
|
10787
|
-
* Record tool execution results to Journal and aggregate memos.
|
|
10788
|
-
*
|
|
10789
|
-
* WHY no truncation on inputSummary: Strategist needs full context —
|
|
10790
|
-
* "hydra -l admin -P rockyou.txt ssh://10.0.0.1" must survive intact.
|
|
10791
|
-
*/
|
|
10792
10514
|
recordJournalMemo(call, result2, digestedOutputForLLM, digestResult) {
|
|
10793
10515
|
this.turnToolJournal.push({
|
|
10794
10516
|
name: call.name,
|
|
@@ -10812,7 +10534,12 @@ RULES:
|
|
|
10812
10534
|
}
|
|
10813
10535
|
if (digestResult?.memo?.credentials.length) {
|
|
10814
10536
|
for (const cred of digestResult.memo.credentials) {
|
|
10815
|
-
this.state.addLoot({
|
|
10537
|
+
this.state.addLoot({
|
|
10538
|
+
type: LOOT_TYPES.CREDENTIAL,
|
|
10539
|
+
host: "auto-extracted",
|
|
10540
|
+
detail: cred,
|
|
10541
|
+
obtainedAt: Date.now()
|
|
10542
|
+
});
|
|
10816
10543
|
}
|
|
10817
10544
|
}
|
|
10818
10545
|
if (digestResult?.memo?.attackVectors.length && digestResult.memo.attackValue === "HIGH") {
|
|
@@ -10824,7 +10551,6 @@ RULES:
|
|
|
10824
10551
|
id: generateId(),
|
|
10825
10552
|
title,
|
|
10826
10553
|
severity: "high",
|
|
10827
|
-
// Auto-extracted findings are unverified signals — score POSSIBLE (25)
|
|
10828
10554
|
confidence: CONFIDENCE_THRESHOLDS.POSSIBLE,
|
|
10829
10555
|
affected: [],
|
|
10830
10556
|
description: `Auto-extracted by Analyst LLM: ${vector}`,
|
|
@@ -10841,54 +10567,432 @@ RULES:
|
|
|
10841
10567
|
this.state.setPhase(PHASES.VULN_ANALYSIS);
|
|
10842
10568
|
}
|
|
10843
10569
|
}
|
|
10570
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10571
|
+
// SUBSECTION: CTF Flag Detection
|
|
10572
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10573
|
+
scanForFlags(output) {
|
|
10574
|
+
if (!this.state.isCtfMode()) return;
|
|
10575
|
+
const flags = detectFlags(output);
|
|
10576
|
+
for (const flag of flags) {
|
|
10577
|
+
const isNew = this.state.addFlag(flag);
|
|
10578
|
+
if (isNew) {
|
|
10579
|
+
this.events.emit({
|
|
10580
|
+
type: EVENT_TYPES.FLAG_FOUND,
|
|
10581
|
+
timestamp: Date.now(),
|
|
10582
|
+
data: {
|
|
10583
|
+
flag,
|
|
10584
|
+
totalFlags: this.state.getFlags().length,
|
|
10585
|
+
phase: this.state.getPhase()
|
|
10586
|
+
}
|
|
10587
|
+
});
|
|
10588
|
+
}
|
|
10589
|
+
}
|
|
10590
|
+
}
|
|
10591
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10592
|
+
// SUBSECTION: Event Emitters
|
|
10593
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10594
|
+
emitToolCall(toolName, input) {
|
|
10595
|
+
this.events.emit({
|
|
10596
|
+
type: EVENT_TYPES.TOOL_CALL,
|
|
10597
|
+
timestamp: Date.now(),
|
|
10598
|
+
data: {
|
|
10599
|
+
toolName,
|
|
10600
|
+
input,
|
|
10601
|
+
approvalLevel: APPROVAL_LEVELS.AUTO,
|
|
10602
|
+
needsApproval: false
|
|
10603
|
+
}
|
|
10604
|
+
});
|
|
10605
|
+
}
|
|
10606
|
+
emitToolResult(toolName, success, output, error, duration) {
|
|
10607
|
+
this.events.emit({
|
|
10608
|
+
type: EVENT_TYPES.TOOL_RESULT,
|
|
10609
|
+
timestamp: Date.now(),
|
|
10610
|
+
data: {
|
|
10611
|
+
toolName,
|
|
10612
|
+
success,
|
|
10613
|
+
output,
|
|
10614
|
+
outputSummary: output.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
|
|
10615
|
+
error,
|
|
10616
|
+
duration
|
|
10617
|
+
}
|
|
10618
|
+
});
|
|
10619
|
+
}
|
|
10620
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10621
|
+
// SUBSECTION: Schema Helper
|
|
10622
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10623
|
+
getToolSchemas() {
|
|
10624
|
+
return this.toolRegistry.getAll().map((t) => ({
|
|
10625
|
+
name: t.name,
|
|
10626
|
+
description: t.description,
|
|
10627
|
+
input_schema: {
|
|
10628
|
+
type: "object",
|
|
10629
|
+
properties: t.parameters,
|
|
10630
|
+
required: t.required || []
|
|
10631
|
+
}
|
|
10632
|
+
}));
|
|
10633
|
+
}
|
|
10634
|
+
};
|
|
10635
|
+
|
|
10636
|
+
// src/agents/core-agent.ts
|
|
10637
|
+
var CoreAgent = class {
|
|
10638
|
+
llm;
|
|
10639
|
+
state;
|
|
10640
|
+
events;
|
|
10641
|
+
toolRegistry;
|
|
10642
|
+
agentType;
|
|
10643
|
+
maxIterations;
|
|
10644
|
+
abortController = null;
|
|
10645
|
+
toolExecutor = null;
|
|
10646
|
+
constructor(agentType, state, events, toolRegistry, maxIterations) {
|
|
10647
|
+
this.agentType = agentType;
|
|
10648
|
+
this.state = state;
|
|
10649
|
+
this.events = events;
|
|
10650
|
+
this.toolRegistry = toolRegistry;
|
|
10651
|
+
this.llm = getLLMClient();
|
|
10652
|
+
this.maxIterations = maxIterations || AGENT_LIMITS.MAX_ITERATIONS;
|
|
10653
|
+
}
|
|
10654
|
+
/** Set or update the tool registry (for delayed initialization) */
|
|
10655
|
+
setToolRegistry(registry) {
|
|
10656
|
+
this.toolRegistry = registry;
|
|
10657
|
+
}
|
|
10658
|
+
/** Abort the current execution */
|
|
10659
|
+
abort() {
|
|
10660
|
+
this.abortController?.abort();
|
|
10661
|
+
}
|
|
10662
|
+
/** Get turn tool journal (for MainAgent) */
|
|
10663
|
+
getTurnToolJournal() {
|
|
10664
|
+
return this.toolExecutor?.turnToolJournal ?? [];
|
|
10665
|
+
}
|
|
10666
|
+
getTurnMemo() {
|
|
10667
|
+
return this.toolExecutor?.turnMemo ?? { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
|
|
10668
|
+
}
|
|
10669
|
+
getTurnReflections() {
|
|
10670
|
+
return this.toolExecutor?.turnReflections ?? [];
|
|
10671
|
+
}
|
|
10672
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10673
|
+
// SUBSECTION: Main Run Loop
|
|
10674
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10675
|
+
/** The core loop: Think → Act → Observe */
|
|
10676
|
+
async run(task, systemPrompt) {
|
|
10677
|
+
this.abortController = new AbortController();
|
|
10678
|
+
const messages = [{ role: LLM_ROLES.USER, content: task }];
|
|
10679
|
+
let consecutiveLLMErrors = 0;
|
|
10680
|
+
const maxConsecutiveLLMErrors = AGENT_LIMITS.MAX_CONSECUTIVE_LLM_ERRORS;
|
|
10681
|
+
const progress = {
|
|
10682
|
+
totalToolsExecuted: 0,
|
|
10683
|
+
consecutiveIdleIterations: 0,
|
|
10684
|
+
lastToolExecutedAt: Date.now(),
|
|
10685
|
+
toolErrors: 0,
|
|
10686
|
+
toolSuccesses: 0,
|
|
10687
|
+
blockedCommandPatterns: /* @__PURE__ */ new Map(),
|
|
10688
|
+
totalBlockedCommands: 0
|
|
10689
|
+
};
|
|
10690
|
+
for (let iteration = 0; iteration < this.maxIterations; iteration++) {
|
|
10691
|
+
if (this.abortController.signal.aborted) {
|
|
10692
|
+
return this.buildCancelledResult(iteration, progress.totalToolsExecuted);
|
|
10693
|
+
}
|
|
10694
|
+
try {
|
|
10695
|
+
const result2 = await this.step(iteration, messages, systemPrompt, progress);
|
|
10696
|
+
progress.totalToolsExecuted += result2.toolsExecuted;
|
|
10697
|
+
consecutiveLLMErrors = 0;
|
|
10698
|
+
if (result2.toolsExecuted > 0) {
|
|
10699
|
+
progress.consecutiveIdleIterations = 0;
|
|
10700
|
+
progress.lastToolExecutedAt = Date.now();
|
|
10701
|
+
} else if (!result2.isCompleted) {
|
|
10702
|
+
progress.consecutiveIdleIterations++;
|
|
10703
|
+
}
|
|
10704
|
+
if (result2.isCompleted) {
|
|
10705
|
+
return {
|
|
10706
|
+
output: result2.output,
|
|
10707
|
+
iterations: iteration + 1,
|
|
10708
|
+
toolsExecuted: progress.totalToolsExecuted,
|
|
10709
|
+
isCompleted: true
|
|
10710
|
+
};
|
|
10711
|
+
}
|
|
10712
|
+
if (progress.consecutiveIdleIterations >= AGENT_LIMITS.MAX_CONSECUTIVE_IDLE) {
|
|
10713
|
+
progress.consecutiveIdleIterations = 0;
|
|
10714
|
+
messages.push({ role: LLM_ROLES.USER, content: this.buildDeadlockNudge(progress) });
|
|
10715
|
+
}
|
|
10716
|
+
} catch (error) {
|
|
10717
|
+
const action = this.handleLoopError(
|
|
10718
|
+
error,
|
|
10719
|
+
messages,
|
|
10720
|
+
progress,
|
|
10721
|
+
iteration,
|
|
10722
|
+
consecutiveLLMErrors,
|
|
10723
|
+
maxConsecutiveLLMErrors
|
|
10724
|
+
);
|
|
10725
|
+
if (action.action === "return") return action.result;
|
|
10726
|
+
if (action.action === "continue") {
|
|
10727
|
+
if (error instanceof LLMError && error.errorInfo.type === LLM_ERROR_TYPES.RATE_LIMIT) {
|
|
10728
|
+
consecutiveLLMErrors++;
|
|
10729
|
+
} else {
|
|
10730
|
+
consecutiveLLMErrors = 0;
|
|
10731
|
+
}
|
|
10732
|
+
continue;
|
|
10733
|
+
}
|
|
10734
|
+
}
|
|
10735
|
+
}
|
|
10736
|
+
return {
|
|
10737
|
+
output: `Max iterations (${this.maxIterations}) reached. Tools: ${progress.totalToolsExecuted}.`,
|
|
10738
|
+
iterations: this.maxIterations,
|
|
10739
|
+
toolsExecuted: progress.totalToolsExecuted,
|
|
10740
|
+
isCompleted: false
|
|
10741
|
+
};
|
|
10742
|
+
}
|
|
10844
10743
|
/**
|
|
10845
|
-
*
|
|
10744
|
+
* §4-1: Handle errors caught in the run loop — extracted to reduce nesting depth.
|
|
10745
|
+
*
|
|
10746
|
+
* Returns a LoopErrorAction so the caller controls `continue` / `return`.
|
|
10747
|
+
* WHY separate: The catch block in run() had nesting depth 8. By moving error
|
|
10748
|
+
* dispatch here, run() stays at depth 3 (for → try → if) throughout.
|
|
10846
10749
|
*/
|
|
10847
|
-
|
|
10848
|
-
|
|
10849
|
-
|
|
10850
|
-
getToolSchemas() {
|
|
10851
|
-
if (!this.toolRegistry) {
|
|
10852
|
-
return [];
|
|
10750
|
+
handleLoopError(error, messages, progress, iteration, consecutiveLLMErrors, maxConsecutiveLLMErrors) {
|
|
10751
|
+
if (this.isAbortError(error)) {
|
|
10752
|
+
return { action: "return", result: this.buildCancelledResult(iteration, progress.totalToolsExecuted) };
|
|
10853
10753
|
}
|
|
10854
|
-
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
|
|
10754
|
+
if (error instanceof LLMError) {
|
|
10755
|
+
return this.handleLLMError(error, messages, progress, iteration, consecutiveLLMErrors, maxConsecutiveLLMErrors);
|
|
10756
|
+
}
|
|
10757
|
+
const unexpectedMsg = error instanceof Error ? error.message : String(error);
|
|
10758
|
+
this.events.emit({
|
|
10759
|
+
type: EVENT_TYPES.ERROR,
|
|
10760
|
+
timestamp: Date.now(),
|
|
10761
|
+
data: { message: `Unexpected error: ${unexpectedMsg}`, phase: this.state.getPhase(), isRecoverable: true }
|
|
10762
|
+
});
|
|
10763
|
+
messages.push({ role: LLM_ROLES.USER, content: `\u26A0\uFE0F UNEXPECTED ERROR: ${unexpectedMsg}
|
|
10764
|
+
Continue your task.` });
|
|
10765
|
+
return { action: "continue" };
|
|
10766
|
+
}
|
|
10767
|
+
/** Handle LLMError specifically — rate limits vs other LLM errors. */
|
|
10768
|
+
handleLLMError(error, messages, progress, iteration, consecutiveLLMErrors, maxConsecutiveLLMErrors) {
|
|
10769
|
+
const errorInfo = error.errorInfo;
|
|
10770
|
+
this.events.emit({
|
|
10771
|
+
type: EVENT_TYPES.ERROR,
|
|
10772
|
+
timestamp: Date.now(),
|
|
10773
|
+
data: {
|
|
10774
|
+
message: `LLM Error: ${errorInfo.message}`,
|
|
10775
|
+
phase: this.state.getPhase(),
|
|
10776
|
+
isRecoverable: errorInfo.isRetryable,
|
|
10777
|
+
errorInfo
|
|
10861
10778
|
}
|
|
10862
|
-
})
|
|
10779
|
+
});
|
|
10780
|
+
if (errorInfo.type !== LLM_ERROR_TYPES.RATE_LIMIT) {
|
|
10781
|
+
messages.push({ role: LLM_ROLES.USER, content: this.formatLLMErrorForAgent(errorInfo) });
|
|
10782
|
+
return { action: "continue" };
|
|
10783
|
+
}
|
|
10784
|
+
const newCount = consecutiveLLMErrors + 1;
|
|
10785
|
+
if (maxConsecutiveLLMErrors !== Infinity && newCount >= maxConsecutiveLLMErrors) {
|
|
10786
|
+
return {
|
|
10787
|
+
action: "return",
|
|
10788
|
+
result: {
|
|
10789
|
+
output: `LLM rate limit errors exceeded. Last error: ${errorInfo.message}`,
|
|
10790
|
+
iterations: iteration + 1,
|
|
10791
|
+
toolsExecuted: progress.totalToolsExecuted,
|
|
10792
|
+
isCompleted: false
|
|
10793
|
+
}
|
|
10794
|
+
};
|
|
10795
|
+
}
|
|
10796
|
+
return { action: "continue" };
|
|
10863
10797
|
}
|
|
10864
10798
|
// ─────────────────────────────────────────────────────────────────
|
|
10865
|
-
// SUBSECTION:
|
|
10799
|
+
// SUBSECTION: Step Execution
|
|
10866
10800
|
// ─────────────────────────────────────────────────────────────────
|
|
10867
|
-
|
|
10868
|
-
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10878
|
-
|
|
10879
|
-
|
|
10880
|
-
|
|
10801
|
+
async step(iteration, messages, systemPrompt, progress) {
|
|
10802
|
+
const phase = this.state.getPhase();
|
|
10803
|
+
this.emitThink(iteration, progress);
|
|
10804
|
+
if (this.toolRegistry && !this.toolExecutor) {
|
|
10805
|
+
this.toolExecutor = new ToolExecutor({
|
|
10806
|
+
state: this.state,
|
|
10807
|
+
events: this.events,
|
|
10808
|
+
toolRegistry: this.toolRegistry,
|
|
10809
|
+
llm: this.llm
|
|
10810
|
+
});
|
|
10811
|
+
}
|
|
10812
|
+
this.toolExecutor?.clearTurnState();
|
|
10813
|
+
const callbacks = this.buildStreamCallbacks(phase);
|
|
10814
|
+
const response = await this.llm.generateResponseStream(
|
|
10815
|
+
messages,
|
|
10816
|
+
this.toolExecutor?.getToolSchemas() ?? [],
|
|
10817
|
+
systemPrompt,
|
|
10818
|
+
callbacks
|
|
10819
|
+
);
|
|
10820
|
+
if (response.reasoning && !callbacks.hadReasoningEnd()) {
|
|
10821
|
+
this.emitReasoningStart(phase);
|
|
10822
|
+
this.emitReasoningDelta(response.reasoning, phase);
|
|
10823
|
+
this.emitReasoningEnd(phase);
|
|
10824
|
+
}
|
|
10825
|
+
if (response.content?.trim()) {
|
|
10826
|
+
if (!response.reasoning && !callbacks.hadReasoningEnd()) {
|
|
10827
|
+
this.emitReasoningStart(phase);
|
|
10828
|
+
this.emitReasoningDelta(response.content.trim(), phase);
|
|
10829
|
+
this.emitReasoningEnd(phase);
|
|
10830
|
+
} else {
|
|
10881
10831
|
this.events.emit({
|
|
10882
|
-
type: EVENT_TYPES.
|
|
10832
|
+
type: EVENT_TYPES.AI_RESPONSE,
|
|
10883
10833
|
timestamp: Date.now(),
|
|
10884
|
-
data: {
|
|
10885
|
-
flag,
|
|
10886
|
-
totalFlags: this.state.getFlags().length,
|
|
10887
|
-
phase: this.state.getPhase()
|
|
10888
|
-
}
|
|
10834
|
+
data: { content: response.content.trim(), phase }
|
|
10889
10835
|
});
|
|
10890
10836
|
}
|
|
10891
10837
|
}
|
|
10838
|
+
messages.push({ role: LLM_ROLES.ASSISTANT, content: response.content });
|
|
10839
|
+
if (!response.toolCalls?.length) {
|
|
10840
|
+
return { output: response.content, toolsExecuted: 0, isCompleted: false };
|
|
10841
|
+
}
|
|
10842
|
+
const results = await this.toolExecutor.processToolCalls(response.toolCalls, progress);
|
|
10843
|
+
this.addToolResultsToMessages(messages, results);
|
|
10844
|
+
return { output: "", toolsExecuted: results.length, isCompleted: false };
|
|
10845
|
+
}
|
|
10846
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10847
|
+
// SUBSECTION: Callback Builder
|
|
10848
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10849
|
+
buildStreamCallbacks(phase) {
|
|
10850
|
+
let _reasoningEndFired = false;
|
|
10851
|
+
let _outputBuffer = "";
|
|
10852
|
+
return {
|
|
10853
|
+
onReasoningStart: () => this.emitReasoningStart(phase),
|
|
10854
|
+
onReasoningDelta: (content) => this.emitReasoningDelta(content, phase),
|
|
10855
|
+
onReasoningEnd: () => {
|
|
10856
|
+
_reasoningEndFired = true;
|
|
10857
|
+
this.emitReasoningEnd(phase);
|
|
10858
|
+
},
|
|
10859
|
+
onOutputDelta: (text) => {
|
|
10860
|
+
_outputBuffer += text;
|
|
10861
|
+
const firstLine = _outputBuffer.split("\n")[0]?.slice(0, 120) || "";
|
|
10862
|
+
this.events.emit({
|
|
10863
|
+
type: EVENT_TYPES.THINK,
|
|
10864
|
+
timestamp: Date.now(),
|
|
10865
|
+
data: { thought: `Writing\u2026 ${_outputBuffer.length} chars
|
|
10866
|
+
${firstLine}`, phase }
|
|
10867
|
+
});
|
|
10868
|
+
},
|
|
10869
|
+
onRetry: (attempt, maxRetries, delayMs, error) => {
|
|
10870
|
+
this.events.emit({
|
|
10871
|
+
type: EVENT_TYPES.RETRY,
|
|
10872
|
+
timestamp: Date.now(),
|
|
10873
|
+
data: { attempt, maxRetries, delayMs, error, phase }
|
|
10874
|
+
});
|
|
10875
|
+
},
|
|
10876
|
+
onUsageUpdate: (usage) => {
|
|
10877
|
+
this.events.emit({
|
|
10878
|
+
type: EVENT_TYPES.USAGE_UPDATE,
|
|
10879
|
+
timestamp: Date.now(),
|
|
10880
|
+
data: { inputTokens: usage.input_tokens, outputTokens: usage.output_tokens }
|
|
10881
|
+
});
|
|
10882
|
+
},
|
|
10883
|
+
abortSignal: this.abortController?.signal,
|
|
10884
|
+
hadReasoningEnd: () => _reasoningEndFired
|
|
10885
|
+
};
|
|
10886
|
+
}
|
|
10887
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10888
|
+
// SUBSECTION: Deadlock Nudge
|
|
10889
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10890
|
+
buildDeadlockNudge(progress) {
|
|
10891
|
+
const phase = this.state.getPhase();
|
|
10892
|
+
const targets = this.state.getTargets().size;
|
|
10893
|
+
const findings = this.state.getFindings().length;
|
|
10894
|
+
const phaseDirection = {
|
|
10895
|
+
[PHASES.RECON]: `RECON: Scan targets. Enumerate services.`,
|
|
10896
|
+
[PHASES.VULN_ANALYSIS]: `VULN: ${targets} target(s). Search for CVEs.`,
|
|
10897
|
+
[PHASES.EXPLOIT]: `EXPLOIT: ${findings} finding(s). Attack the highest-severity one.`,
|
|
10898
|
+
[PHASES.POST_EXPLOIT]: `POST-EXPLOIT: Escalate privileges.`,
|
|
10899
|
+
[PHASES.PRIV_ESC]: `PRIVESC: Find privilege escalation vectors.`,
|
|
10900
|
+
[PHASES.LATERAL]: `LATERAL: Reuse credentials on other hosts.`,
|
|
10901
|
+
[PHASES.WEB]: `WEB: Enumerate attack surface. Test every input.`
|
|
10902
|
+
};
|
|
10903
|
+
const direction = phaseDirection[phase] || phaseDirection[PHASES.RECON];
|
|
10904
|
+
return `\u26A1 DEADLOCK: ${AGENT_LIMITS.MAX_CONSECUTIVE_IDLE} turns with ZERO tool calls.
|
|
10905
|
+
Phase: ${phase} | Targets: ${targets} | Findings: ${findings}
|
|
10906
|
+
|
|
10907
|
+
${direction}
|
|
10908
|
+
|
|
10909
|
+
ESCALATION:
|
|
10910
|
+
1. web_search for techniques
|
|
10911
|
+
2. Try alternative approaches
|
|
10912
|
+
3. Probe for unknown vulns
|
|
10913
|
+
4. Brute-force with wordlists
|
|
10914
|
+
5. ask_user for hints
|
|
10915
|
+
|
|
10916
|
+
ACT NOW \u2014 EXECUTE.`;
|
|
10917
|
+
}
|
|
10918
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10919
|
+
// SUBSECTION: Error Formatting
|
|
10920
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10921
|
+
formatLLMErrorForAgent(errorInfo) {
|
|
10922
|
+
const actionHints = {
|
|
10923
|
+
[LLM_ERROR_TYPES.RATE_LIMIT]: "Wait and retry.",
|
|
10924
|
+
[LLM_ERROR_TYPES.AUTH_ERROR]: "Use ask_user to request API key.",
|
|
10925
|
+
[LLM_ERROR_TYPES.INVALID_REQUEST]: "Simplify your request.",
|
|
10926
|
+
[LLM_ERROR_TYPES.NETWORK_ERROR]: "Check network and retry.",
|
|
10927
|
+
[LLM_ERROR_TYPES.TIMEOUT]: "Retry with simpler request.",
|
|
10928
|
+
[LLM_ERROR_TYPES.UNKNOWN]: "Analyze and decide."
|
|
10929
|
+
};
|
|
10930
|
+
return `[SYSTEM ERROR - LLM API Issue]
|
|
10931
|
+
Error Type: ${errorInfo.type}
|
|
10932
|
+
Message: ${errorInfo.message}
|
|
10933
|
+
Status Code: ${errorInfo.statusCode || "N/A"}
|
|
10934
|
+
Retryable: ${errorInfo.isRetryable ? "Yes" : "No"}
|
|
10935
|
+
|
|
10936
|
+
Suggested Action: ${errorInfo.suggestedAction || actionHints[errorInfo.type] || "Decide appropriate action"}`;
|
|
10937
|
+
}
|
|
10938
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10939
|
+
// SUBSECTION: Event Emitters
|
|
10940
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10941
|
+
emitThink(iteration, progress) {
|
|
10942
|
+
const phase = this.state.getPhase();
|
|
10943
|
+
const targets = this.state.getTargets().size;
|
|
10944
|
+
const findings = this.state.getFindings().length;
|
|
10945
|
+
const toolsUsed = progress?.totalToolsExecuted ?? 0;
|
|
10946
|
+
const hasErrors = (progress?.toolErrors ?? 0) > 0;
|
|
10947
|
+
let thought;
|
|
10948
|
+
if (iteration === 0) {
|
|
10949
|
+
thought = targets > 0 ? `Analyzing ${targets} target(s) \xB7 Planning ${phase} approach` : `Reviewing task \xB7 Building ${phase} strategy`;
|
|
10950
|
+
} else if (toolsUsed === 0) {
|
|
10951
|
+
thought = `Iteration ${iteration + 1} \xB7 No actions yet \xB7 Reconsidering`;
|
|
10952
|
+
} else if (hasErrors) {
|
|
10953
|
+
thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tools \xB7 ${progress?.toolErrors} error(s)`;
|
|
10954
|
+
} else if (findings > 0) {
|
|
10955
|
+
thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${findings} finding(s) \xB7 ${toolsUsed} tools`;
|
|
10956
|
+
} else {
|
|
10957
|
+
thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tool(s) executed`;
|
|
10958
|
+
}
|
|
10959
|
+
this.events.emit({
|
|
10960
|
+
type: EVENT_TYPES.THINK,
|
|
10961
|
+
timestamp: Date.now(),
|
|
10962
|
+
data: { thought, phase }
|
|
10963
|
+
});
|
|
10964
|
+
}
|
|
10965
|
+
emitReasoningStart(phase) {
|
|
10966
|
+
this.events.emit({ type: EVENT_TYPES.REASONING_START, timestamp: Date.now(), data: { phase } });
|
|
10967
|
+
}
|
|
10968
|
+
emitReasoningDelta(content, phase) {
|
|
10969
|
+
this.events.emit({ type: EVENT_TYPES.REASONING_DELTA, timestamp: Date.now(), data: { content, phase } });
|
|
10970
|
+
}
|
|
10971
|
+
emitReasoningEnd(phase) {
|
|
10972
|
+
this.events.emit({ type: EVENT_TYPES.REASONING_END, timestamp: Date.now(), data: { phase } });
|
|
10973
|
+
}
|
|
10974
|
+
emitComplete(output, iteration, toolsExecuted, durationMs, tokens) {
|
|
10975
|
+
this.events.emit({
|
|
10976
|
+
type: EVENT_TYPES.COMPLETE,
|
|
10977
|
+
timestamp: Date.now(),
|
|
10978
|
+
data: { finalOutput: output, iterations: iteration + 1, toolsExecuted, durationMs, tokens }
|
|
10979
|
+
});
|
|
10980
|
+
}
|
|
10981
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10982
|
+
// SUBSECTION: Message Helpers
|
|
10983
|
+
// ─────────────────────────────────────────────────────────────────
|
|
10984
|
+
addToolResultsToMessages(messages, results) {
|
|
10985
|
+
for (const res of results) {
|
|
10986
|
+
messages.push({
|
|
10987
|
+
role: LLM_ROLES.USER,
|
|
10988
|
+
content: [{
|
|
10989
|
+
type: LLM_BLOCK_TYPE.TOOL_RESULT,
|
|
10990
|
+
tool_use_id: res.toolCallId,
|
|
10991
|
+
content: res.output,
|
|
10992
|
+
is_error: !!res.error
|
|
10993
|
+
}]
|
|
10994
|
+
});
|
|
10995
|
+
}
|
|
10892
10996
|
}
|
|
10893
10997
|
// ─────────────────────────────────────────────────────────────────
|
|
10894
10998
|
// SUBSECTION: Abort Helpers
|
|
@@ -10907,8 +11011,8 @@ RULES:
|
|
|
10907
11011
|
};
|
|
10908
11012
|
|
|
10909
11013
|
// src/agents/prompt-builder.ts
|
|
10910
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
10911
|
-
import { join as
|
|
11014
|
+
import { readFileSync as readFileSync6, existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
|
|
11015
|
+
import { join as join11, dirname as dirname4 } from "path";
|
|
10912
11016
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10913
11017
|
|
|
10914
11018
|
// src/shared/constants/prompts.ts
|
|
@@ -11182,44 +11286,39 @@ function getAttacksForService(service, port) {
|
|
|
11182
11286
|
}
|
|
11183
11287
|
|
|
11184
11288
|
// src/shared/utils/journal.ts
|
|
11185
|
-
import { writeFileSync as
|
|
11186
|
-
import { join as
|
|
11187
|
-
|
|
11188
|
-
|
|
11189
|
-
|
|
11190
|
-
var SUMMARY_FILE = "summary.md";
|
|
11191
|
-
function writeJournalEntry(entry) {
|
|
11192
|
-
try {
|
|
11193
|
-
const journalDir = WORKSPACE.JOURNAL;
|
|
11194
|
-
ensureDirExists(journalDir);
|
|
11195
|
-
const padded = String(entry.turn).padStart(4, "0");
|
|
11196
|
-
const filePath = join9(journalDir, `${TURN_PREFIX}${padded}.json`);
|
|
11197
|
-
writeFileSync8(filePath, JSON.stringify(entry, null, 2), "utf-8");
|
|
11198
|
-
return filePath;
|
|
11199
|
-
} catch (err) {
|
|
11200
|
-
debugLog("general", "Failed to write journal entry", { turn: entry.turn, error: String(err) });
|
|
11201
|
-
return null;
|
|
11202
|
-
}
|
|
11289
|
+
import { writeFileSync as writeFileSync9, readFileSync as readFileSync5, existsSync as existsSync9, readdirSync as readdirSync3, statSync as statSync3, rmSync as rmSync2 } from "fs";
|
|
11290
|
+
import { join as join10 } from "path";
|
|
11291
|
+
function parseTurnNumbers(turnsDir) {
|
|
11292
|
+
if (!existsSync9(turnsDir)) return [];
|
|
11293
|
+
return readdirSync3(turnsDir).filter((e) => e.startsWith(TURN_FOLDER_PREFIX) && /^\d+$/.test(e.slice(TURN_FOLDER_PREFIX.length))).map((e) => Number(e.slice(TURN_FOLDER_PREFIX.length)));
|
|
11203
11294
|
}
|
|
11204
11295
|
function readJournalSummary() {
|
|
11205
11296
|
try {
|
|
11206
|
-
const
|
|
11207
|
-
|
|
11208
|
-
|
|
11297
|
+
const turnsDir = WORKSPACE.TURNS;
|
|
11298
|
+
const turnDirs = parseTurnNumbers(turnsDir).sort((a, b) => b - a);
|
|
11299
|
+
for (const turn of turnDirs) {
|
|
11300
|
+
const summaryPath = join10(WORKSPACE.turnPath(turn), TURN_FILES.SUMMARY);
|
|
11301
|
+
if (existsSync9(summaryPath)) {
|
|
11302
|
+
return readFileSync5(summaryPath, "utf-8");
|
|
11303
|
+
}
|
|
11304
|
+
}
|
|
11305
|
+
return "";
|
|
11209
11306
|
} catch {
|
|
11210
11307
|
return "";
|
|
11211
11308
|
}
|
|
11212
11309
|
}
|
|
11213
|
-
function getRecentEntries(count =
|
|
11310
|
+
function getRecentEntries(count = MEMORY_LIMITS.MAX_TURN_ENTRIES) {
|
|
11214
11311
|
try {
|
|
11215
|
-
const
|
|
11216
|
-
|
|
11217
|
-
const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort().slice(-count);
|
|
11312
|
+
const turnsDir = WORKSPACE.TURNS;
|
|
11313
|
+
const turnDirs = parseTurnNumbers(turnsDir).sort((a, b) => a - b).slice(-count);
|
|
11218
11314
|
const entries = [];
|
|
11219
|
-
for (const
|
|
11315
|
+
for (const turn of turnDirs) {
|
|
11220
11316
|
try {
|
|
11221
|
-
const
|
|
11222
|
-
|
|
11317
|
+
const filePath = join10(WORKSPACE.turnPath(turn), TURN_FILES.STRUCTURED);
|
|
11318
|
+
if (existsSync9(filePath)) {
|
|
11319
|
+
const raw = readFileSync5(filePath, "utf-8");
|
|
11320
|
+
entries.push(JSON.parse(raw));
|
|
11321
|
+
}
|
|
11223
11322
|
} catch {
|
|
11224
11323
|
}
|
|
11225
11324
|
}
|
|
@@ -11230,13 +11329,10 @@ function getRecentEntries(count = MAX_JOURNAL_ENTRIES) {
|
|
|
11230
11329
|
}
|
|
11231
11330
|
function getNextTurnNumber() {
|
|
11232
11331
|
try {
|
|
11233
|
-
const
|
|
11234
|
-
|
|
11235
|
-
|
|
11236
|
-
|
|
11237
|
-
const lastFile = files[files.length - 1];
|
|
11238
|
-
const match = lastFile.match(/turn-(\d+)\.json/);
|
|
11239
|
-
return match ? parseInt(match[1], 10) + 1 : 1;
|
|
11332
|
+
const turnsDir = WORKSPACE.TURNS;
|
|
11333
|
+
const turnDirs = parseTurnNumbers(turnsDir);
|
|
11334
|
+
if (turnDirs.length === 0) return 1;
|
|
11335
|
+
return Math.max(...turnDirs) + 1;
|
|
11240
11336
|
} catch {
|
|
11241
11337
|
return 1;
|
|
11242
11338
|
}
|
|
@@ -11245,11 +11341,13 @@ function regenerateJournalSummary() {
|
|
|
11245
11341
|
try {
|
|
11246
11342
|
const entries = getRecentEntries();
|
|
11247
11343
|
if (entries.length === 0) return;
|
|
11248
|
-
const
|
|
11249
|
-
|
|
11344
|
+
const latestTurn = entries.reduce((max, e) => Math.max(max, e.turn), 0);
|
|
11345
|
+
if (latestTurn === 0) return;
|
|
11346
|
+
const turnDir = WORKSPACE.turnPath(latestTurn);
|
|
11347
|
+
ensureDirExists(turnDir);
|
|
11250
11348
|
const summary = buildSummaryFromEntries(entries);
|
|
11251
|
-
const summaryPath =
|
|
11252
|
-
|
|
11349
|
+
const summaryPath = join10(turnDir, TURN_FILES.SUMMARY);
|
|
11350
|
+
writeFileSync9(summaryPath, summary, "utf-8");
|
|
11253
11351
|
debugLog("general", "Journal summary regenerated", {
|
|
11254
11352
|
entries: entries.length,
|
|
11255
11353
|
summaryLength: summary.length
|
|
@@ -11326,7 +11424,7 @@ function formatSummaryMarkdown(buckets, entries) {
|
|
|
11326
11424
|
);
|
|
11327
11425
|
const lastTurn = entries[entries.length - 1]?.turn || 0;
|
|
11328
11426
|
const sections = [
|
|
11329
|
-
`#
|
|
11427
|
+
`# ${SUMMARY_SECTIONS.TITLE}`,
|
|
11330
11428
|
`> Turn ${lastTurn} / ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 19)}`,
|
|
11331
11429
|
""
|
|
11332
11430
|
];
|
|
@@ -11337,89 +11435,46 @@ function formatSummaryMarkdown(buckets, entries) {
|
|
|
11337
11435
|
sections.push("");
|
|
11338
11436
|
};
|
|
11339
11437
|
if (attemptLines.length > 0) {
|
|
11340
|
-
sections.push(
|
|
11341
|
-
sections.push(
|
|
11438
|
+
sections.push(`## ${SUMMARY_SECTIONS.TECHNIQUES_TRIED}`);
|
|
11439
|
+
sections.push(SUMMARY_SECTIONS.TECHNIQUES_LEGEND);
|
|
11342
11440
|
sections.push(...attemptLines);
|
|
11343
11441
|
sections.push("");
|
|
11344
11442
|
}
|
|
11345
|
-
addSection(
|
|
11346
|
-
addSection(
|
|
11347
|
-
addSection(
|
|
11348
|
-
addSection(
|
|
11349
|
-
addSection(
|
|
11350
|
-
addSection(
|
|
11351
|
-
addSection(
|
|
11443
|
+
addSection(SUMMARY_SECTIONS.ANALYST_ANALYSIS, reflections);
|
|
11444
|
+
addSection(SUMMARY_SECTIONS.SUSPICIOUS, suspicions);
|
|
11445
|
+
addSection(SUMMARY_SECTIONS.KEY_FINDINGS, findings);
|
|
11446
|
+
addSection(SUMMARY_SECTIONS.CREDENTIALS, credentials);
|
|
11447
|
+
addSection(SUMMARY_SECTIONS.SUCCESS_VECTORS, successes);
|
|
11448
|
+
addSection(SUMMARY_SECTIONS.FAILURE_CAUSES, failures);
|
|
11449
|
+
addSection(SUMMARY_SECTIONS.NEXT_RECS, nextSteps);
|
|
11352
11450
|
return sections.join("\n");
|
|
11353
11451
|
}
|
|
11354
|
-
function rotateJournalEntries() {
|
|
11355
|
-
try {
|
|
11356
|
-
const journalDir = WORKSPACE.JOURNAL;
|
|
11357
|
-
if (!existsSync8(journalDir)) return;
|
|
11358
|
-
const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort();
|
|
11359
|
-
if (files.length <= MAX_JOURNAL_ENTRIES) return;
|
|
11360
|
-
const toDelete = files.slice(0, files.length - MAX_JOURNAL_ENTRIES);
|
|
11361
|
-
for (const file of toDelete) {
|
|
11362
|
-
try {
|
|
11363
|
-
unlinkSync5(join9(journalDir, file));
|
|
11364
|
-
} catch {
|
|
11365
|
-
}
|
|
11366
|
-
}
|
|
11367
|
-
debugLog("general", "Journal entries rotated", {
|
|
11368
|
-
deleted: toDelete.length,
|
|
11369
|
-
remaining: MAX_JOURNAL_ENTRIES
|
|
11370
|
-
});
|
|
11371
|
-
} catch {
|
|
11372
|
-
}
|
|
11373
|
-
}
|
|
11374
|
-
function rotateOutputFiles() {
|
|
11375
|
-
try {
|
|
11376
|
-
const outputDir = WORKSPACE.OUTPUTS;
|
|
11377
|
-
if (!existsSync8(outputDir)) return;
|
|
11378
|
-
const files = readdirSync2(outputDir).filter((f) => f.endsWith(".txt")).map((f) => ({
|
|
11379
|
-
name: f,
|
|
11380
|
-
path: join9(outputDir, f),
|
|
11381
|
-
mtime: statSync2(join9(outputDir, f)).mtimeMs
|
|
11382
|
-
})).sort((a, b) => b.mtime - a.mtime);
|
|
11383
|
-
if (files.length <= MAX_OUTPUT_FILES) return;
|
|
11384
|
-
const toDelete = files.slice(MAX_OUTPUT_FILES);
|
|
11385
|
-
for (const file of toDelete) {
|
|
11386
|
-
try {
|
|
11387
|
-
unlinkSync5(file.path);
|
|
11388
|
-
} catch {
|
|
11389
|
-
}
|
|
11390
|
-
}
|
|
11391
|
-
debugLog("general", "Output files rotated", {
|
|
11392
|
-
deleted: toDelete.length,
|
|
11393
|
-
remaining: MAX_OUTPUT_FILES
|
|
11394
|
-
});
|
|
11395
|
-
} catch {
|
|
11396
|
-
}
|
|
11397
|
-
}
|
|
11398
11452
|
function rotateTurnRecords() {
|
|
11399
11453
|
try {
|
|
11400
11454
|
const turnsDir = WORKSPACE.TURNS;
|
|
11401
|
-
if (!
|
|
11402
|
-
const
|
|
11403
|
-
if (
|
|
11404
|
-
|
|
11405
|
-
|
|
11406
|
-
|
|
11407
|
-
|
|
11408
|
-
|
|
11455
|
+
if (!existsSync9(turnsDir)) return;
|
|
11456
|
+
const turnDirs = parseTurnNumbers(turnsDir).map((n) => `${TURN_FOLDER_PREFIX}${n}`).filter((e) => statSync3(join10(turnsDir, e)).isDirectory()).sort((a, b) => Number(a.slice(TURN_FOLDER_PREFIX.length)) - Number(b.slice(TURN_FOLDER_PREFIX.length)));
|
|
11457
|
+
if (turnDirs.length > MEMORY_LIMITS.MAX_TURN_ENTRIES) {
|
|
11458
|
+
const dirsToDel = turnDirs.slice(0, turnDirs.length - MEMORY_LIMITS.MAX_TURN_ENTRIES);
|
|
11459
|
+
for (const dir of dirsToDel) {
|
|
11460
|
+
try {
|
|
11461
|
+
rmSync2(join10(turnsDir, dir), { recursive: true, force: true });
|
|
11462
|
+
} catch {
|
|
11463
|
+
}
|
|
11409
11464
|
}
|
|
11465
|
+
debugLog("general", "Turn folders rotated", {
|
|
11466
|
+
deleted: dirsToDel.length,
|
|
11467
|
+
remaining: MEMORY_LIMITS.MAX_TURN_ENTRIES
|
|
11468
|
+
});
|
|
11410
11469
|
}
|
|
11411
|
-
debugLog("general", "Turn records rotated", {
|
|
11412
|
-
deleted: toDelete.length,
|
|
11413
|
-
remaining: MAX_JOURNAL_ENTRIES
|
|
11414
|
-
});
|
|
11415
11470
|
} catch {
|
|
11416
11471
|
}
|
|
11417
11472
|
}
|
|
11418
11473
|
|
|
11419
11474
|
// src/agents/prompt-builder.ts
|
|
11420
11475
|
var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
|
|
11421
|
-
var PROMPTS_DIR =
|
|
11422
|
-
var TECHNIQUES_DIR =
|
|
11476
|
+
var PROMPTS_DIR = join11(__dirname2, "prompts");
|
|
11477
|
+
var TECHNIQUES_DIR = join11(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
|
|
11423
11478
|
var { AGENT_FILES } = PROMPT_PATHS;
|
|
11424
11479
|
var PHASE_PROMPT_MAP = {
|
|
11425
11480
|
// Direct mappings — phase has its own prompt file
|
|
@@ -11552,8 +11607,8 @@ ${content}
|
|
|
11552
11607
|
* Load a prompt file from src/agents/prompts/
|
|
11553
11608
|
*/
|
|
11554
11609
|
loadPromptFile(filename) {
|
|
11555
|
-
const path2 =
|
|
11556
|
-
return
|
|
11610
|
+
const path2 = join11(PROMPTS_DIR, filename);
|
|
11611
|
+
return existsSync10(path2) ? readFileSync6(path2, PROMPT_CONFIG.ENCODING) : "";
|
|
11557
11612
|
}
|
|
11558
11613
|
/**
|
|
11559
11614
|
* Load phase-specific prompt.
|
|
@@ -11599,14 +11654,14 @@ ${content}
|
|
|
11599
11654
|
* "Drop a markdown file in the folder, PromptBuilder auto-discovers and loads it."
|
|
11600
11655
|
*/
|
|
11601
11656
|
loadPhaseRelevantTechniques(phase) {
|
|
11602
|
-
if (!
|
|
11657
|
+
if (!existsSync10(TECHNIQUES_DIR)) return "";
|
|
11603
11658
|
const priorityTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
|
|
11604
11659
|
const loadedSet = /* @__PURE__ */ new Set();
|
|
11605
11660
|
const fragments = [];
|
|
11606
11661
|
for (const technique of priorityTechniques) {
|
|
11607
|
-
const filePath =
|
|
11662
|
+
const filePath = join11(TECHNIQUES_DIR, `${technique}.md`);
|
|
11608
11663
|
try {
|
|
11609
|
-
if (!
|
|
11664
|
+
if (!existsSync10(filePath)) continue;
|
|
11610
11665
|
const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
|
|
11611
11666
|
if (content) {
|
|
11612
11667
|
fragments.push(`<technique-reference category="${technique}">
|
|
@@ -11618,9 +11673,9 @@ ${content}
|
|
|
11618
11673
|
}
|
|
11619
11674
|
}
|
|
11620
11675
|
try {
|
|
11621
|
-
const allFiles =
|
|
11676
|
+
const allFiles = readdirSync4(TECHNIQUES_DIR).filter((f) => f.endsWith(".md") && f !== "README.md" && !loadedSet.has(f));
|
|
11622
11677
|
for (const file of allFiles) {
|
|
11623
|
-
const filePath =
|
|
11678
|
+
const filePath = join11(TECHNIQUES_DIR, file);
|
|
11624
11679
|
const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
|
|
11625
11680
|
if (content) {
|
|
11626
11681
|
const category = file.replace(".md", "");
|
|
@@ -11726,20 +11781,10 @@ ${lines.join("\n")}
|
|
|
11726
11781
|
}
|
|
11727
11782
|
// --- §13: Session Journal Summary ---
|
|
11728
11783
|
/**
|
|
11729
|
-
* Load journal summary
|
|
11730
|
-
* falls back to deterministic journal summary.
|
|
11784
|
+
* Load journal summary from the latest turn folder.
|
|
11731
11785
|
*/
|
|
11732
11786
|
getJournalFragment() {
|
|
11733
11787
|
try {
|
|
11734
|
-
const summaryPath = join10(WORKSPACE.TURNS, "summary.md");
|
|
11735
|
-
if (existsSync9(summaryPath)) {
|
|
11736
|
-
const summary2 = readFileSync6(summaryPath, "utf-8");
|
|
11737
|
-
if (summary2.trim()) {
|
|
11738
|
-
return `<session-journal>
|
|
11739
|
-
${summary2}
|
|
11740
|
-
</session-journal>`;
|
|
11741
|
-
}
|
|
11742
|
-
}
|
|
11743
11788
|
const summary = readJournalSummary();
|
|
11744
11789
|
if (!summary) return "";
|
|
11745
11790
|
return `<session-journal>
|
|
@@ -11761,11 +11806,11 @@ ${summary}
|
|
|
11761
11806
|
};
|
|
11762
11807
|
|
|
11763
11808
|
// src/agents/strategist.ts
|
|
11764
|
-
import { readFileSync as readFileSync7, existsSync as
|
|
11765
|
-
import { join as
|
|
11809
|
+
import { readFileSync as readFileSync7, existsSync as existsSync11 } from "fs";
|
|
11810
|
+
import { join as join12, dirname as dirname5 } from "path";
|
|
11766
11811
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
11767
11812
|
var __dirname3 = dirname5(fileURLToPath3(import.meta.url));
|
|
11768
|
-
var STRATEGIST_PROMPT_PATH =
|
|
11813
|
+
var STRATEGIST_PROMPT_PATH = join12(__dirname3, "prompts", "strategist-system.md");
|
|
11769
11814
|
var Strategist = class {
|
|
11770
11815
|
llm;
|
|
11771
11816
|
state;
|
|
@@ -11823,14 +11868,7 @@ var Strategist = class {
|
|
|
11823
11868
|
sections.push(failures);
|
|
11824
11869
|
}
|
|
11825
11870
|
try {
|
|
11826
|
-
|
|
11827
|
-
const summaryPath = join11(WORKSPACE.TURNS, "summary.md");
|
|
11828
|
-
if (existsSync10(summaryPath)) {
|
|
11829
|
-
journalSummary = readFileSync7(summaryPath, "utf-8").trim();
|
|
11830
|
-
}
|
|
11831
|
-
if (!journalSummary) {
|
|
11832
|
-
journalSummary = readJournalSummary();
|
|
11833
|
-
}
|
|
11871
|
+
const journalSummary = readJournalSummary();
|
|
11834
11872
|
if (journalSummary) {
|
|
11835
11873
|
sections.push("");
|
|
11836
11874
|
sections.push("## Session Journal (past turns summary)");
|
|
@@ -11911,7 +11949,7 @@ NOTE: This directive is from ${age}min ago (Strategist call failed this turn). V
|
|
|
11911
11949
|
// ─── System Prompt Loading ──────────────────────────────────
|
|
11912
11950
|
loadSystemPrompt() {
|
|
11913
11951
|
try {
|
|
11914
|
-
if (
|
|
11952
|
+
if (existsSync11(STRATEGIST_PROMPT_PATH)) {
|
|
11915
11953
|
return readFileSync7(STRATEGIST_PROMPT_PATH, "utf-8");
|
|
11916
11954
|
}
|
|
11917
11955
|
} catch {
|
|
@@ -12146,42 +12184,42 @@ function formatTurnRecord(input) {
|
|
|
12146
12184
|
const sections = [];
|
|
12147
12185
|
sections.push(`# Turn ${turn} | ${time} | Phase: ${phase}`);
|
|
12148
12186
|
sections.push("");
|
|
12149
|
-
sections.push(
|
|
12187
|
+
sections.push(`## ${TURN_SECTIONS.TOOLS_EXECUTED}`);
|
|
12150
12188
|
if (tools.length === 0) {
|
|
12151
|
-
sections.push(
|
|
12189
|
+
sections.push(`- ${TURN_EMPTY_MESSAGES.NO_TOOLS}`);
|
|
12152
12190
|
} else {
|
|
12153
12191
|
for (const tool of tools) {
|
|
12154
|
-
const status = tool.success ?
|
|
12192
|
+
const status = tool.success ? STATUS_ICONS.SUCCESS : STATUS_ICONS.FAILURE;
|
|
12155
12193
|
const line = `- ${tool.name}(${tool.inputSummary}) \u2192 ${status} ${tool.analystSummary}`;
|
|
12156
12194
|
sections.push(line);
|
|
12157
12195
|
}
|
|
12158
12196
|
}
|
|
12159
12197
|
sections.push("");
|
|
12160
|
-
sections.push(
|
|
12198
|
+
sections.push(`## ${TURN_SECTIONS.KEY_INSIGHTS}`);
|
|
12161
12199
|
if (memo6.keyFindings.length > 0) {
|
|
12162
|
-
for (const f of memo6.keyFindings) sections.push(`- DISCOVERED: ${f}`);
|
|
12200
|
+
for (const f of memo6.keyFindings) sections.push(`- ${INSIGHT_TAGS.DISCOVERED}: ${f}`);
|
|
12163
12201
|
}
|
|
12164
12202
|
if (memo6.credentials.length > 0) {
|
|
12165
|
-
for (const c of memo6.credentials) sections.push(`- CREDENTIAL: ${c}`);
|
|
12203
|
+
for (const c of memo6.credentials) sections.push(`- ${INSIGHT_TAGS.CREDENTIAL}: ${c}`);
|
|
12166
12204
|
}
|
|
12167
12205
|
if (memo6.attackVectors.length > 0) {
|
|
12168
|
-
for (const v of memo6.attackVectors) sections.push(`- CONFIRMED: ${v}`);
|
|
12206
|
+
for (const v of memo6.attackVectors) sections.push(`- ${INSIGHT_TAGS.CONFIRMED}: ${v}`);
|
|
12169
12207
|
}
|
|
12170
12208
|
if (memo6.failures.length > 0) {
|
|
12171
|
-
for (const f of memo6.failures) sections.push(`-
|
|
12209
|
+
for (const f of memo6.failures) sections.push(`- ${INSIGHT_TAGS.DEAD_END}: ${f}`);
|
|
12172
12210
|
}
|
|
12173
12211
|
if (memo6.suspicions.length > 0) {
|
|
12174
|
-
for (const s of memo6.suspicions) sections.push(`- SUSPICIOUS: ${s}`);
|
|
12212
|
+
for (const s of memo6.suspicions) sections.push(`- ${INSIGHT_TAGS.SUSPICIOUS}: ${s}`);
|
|
12175
12213
|
}
|
|
12176
12214
|
if (memo6.nextSteps.length > 0) {
|
|
12177
|
-
for (const n of memo6.nextSteps) sections.push(`- NEXT: ${n}`);
|
|
12215
|
+
for (const n of memo6.nextSteps) sections.push(`- ${INSIGHT_TAGS.NEXT}: ${n}`);
|
|
12178
12216
|
}
|
|
12179
12217
|
if (memo6.keyFindings.length === 0 && memo6.failures.length === 0 && memo6.credentials.length === 0) {
|
|
12180
|
-
sections.push(
|
|
12218
|
+
sections.push(`- ${TURN_EMPTY_MESSAGES.NO_INSIGHTS}`);
|
|
12181
12219
|
}
|
|
12182
12220
|
sections.push("");
|
|
12183
|
-
sections.push(
|
|
12184
|
-
sections.push(reflection ||
|
|
12221
|
+
sections.push(`## ${TURN_SECTIONS.SELF_REFLECTION}`);
|
|
12222
|
+
sections.push(reflection || `- ${TURN_EMPTY_MESSAGES.NO_REFLECTION}`);
|
|
12185
12223
|
sections.push("");
|
|
12186
12224
|
return sections.join("\n");
|
|
12187
12225
|
}
|
|
@@ -12225,8 +12263,8 @@ function formatReflectionInput(input) {
|
|
|
12225
12263
|
}
|
|
12226
12264
|
|
|
12227
12265
|
// src/agents/main-agent.ts
|
|
12228
|
-
import { writeFileSync as
|
|
12229
|
-
import { join as
|
|
12266
|
+
import { writeFileSync as writeFileSync10, existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
|
|
12267
|
+
import { join as join13 } from "path";
|
|
12230
12268
|
var MainAgent = class extends CoreAgent {
|
|
12231
12269
|
promptBuilder;
|
|
12232
12270
|
strategist;
|
|
@@ -12280,6 +12318,7 @@ var MainAgent = class extends CoreAgent {
|
|
|
12280
12318
|
if (this.turnCounter === 0) {
|
|
12281
12319
|
this.turnCounter = getNextTurnNumber();
|
|
12282
12320
|
}
|
|
12321
|
+
setCurrentTurn(this.turnCounter);
|
|
12283
12322
|
if (this.userInputQueue.hasPending()) {
|
|
12284
12323
|
const userMessage = this.userInputQueue.drainAndFormat();
|
|
12285
12324
|
if (userMessage) {
|
|
@@ -12289,11 +12328,11 @@ var MainAgent = class extends CoreAgent {
|
|
|
12289
12328
|
});
|
|
12290
12329
|
}
|
|
12291
12330
|
}
|
|
12292
|
-
this.turnToolJournal = [];
|
|
12293
|
-
this.turnMemo = { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
|
|
12294
|
-
this.turnReflections = [];
|
|
12295
12331
|
const dynamicPrompt = await this.getCurrentPrompt();
|
|
12296
12332
|
const result2 = await super.step(iteration, messages, dynamicPrompt, progress);
|
|
12333
|
+
const turnToolJournal = this.getTurnToolJournal();
|
|
12334
|
+
const turnMemo = this.getTurnMemo();
|
|
12335
|
+
const turnReflections = this.getTurnReflections();
|
|
12297
12336
|
try {
|
|
12298
12337
|
if (messages.length > 2) {
|
|
12299
12338
|
const extraction = await this.llm.generateResponse(
|
|
@@ -12314,13 +12353,13 @@ ${extraction.content.trim()}
|
|
|
12314
12353
|
} catch {
|
|
12315
12354
|
}
|
|
12316
12355
|
try {
|
|
12317
|
-
if (
|
|
12356
|
+
if (turnToolJournal.length > 0) {
|
|
12318
12357
|
const reflection = await this.llm.generateResponse(
|
|
12319
12358
|
[{
|
|
12320
12359
|
role: "user",
|
|
12321
12360
|
content: formatReflectionInput({
|
|
12322
|
-
tools:
|
|
12323
|
-
memo:
|
|
12361
|
+
tools: turnToolJournal,
|
|
12362
|
+
memo: turnMemo,
|
|
12324
12363
|
phase: this.state.getPhase()
|
|
12325
12364
|
})
|
|
12326
12365
|
}],
|
|
@@ -12328,47 +12367,65 @@ ${extraction.content.trim()}
|
|
|
12328
12367
|
REFLECTION_PROMPT
|
|
12329
12368
|
);
|
|
12330
12369
|
if (reflection.content?.trim()) {
|
|
12331
|
-
|
|
12370
|
+
turnReflections.push(reflection.content.trim());
|
|
12332
12371
|
}
|
|
12333
12372
|
}
|
|
12334
12373
|
} catch {
|
|
12335
12374
|
}
|
|
12336
|
-
if (
|
|
12375
|
+
if (turnToolJournal.length > 0) {
|
|
12337
12376
|
try {
|
|
12338
12377
|
const entry = {
|
|
12339
12378
|
turn: this.turnCounter,
|
|
12340
12379
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12341
12380
|
phase: this.state.getPhase(),
|
|
12342
|
-
tools:
|
|
12343
|
-
memo:
|
|
12344
|
-
reflection:
|
|
12381
|
+
tools: turnToolJournal,
|
|
12382
|
+
memo: turnMemo,
|
|
12383
|
+
reflection: turnReflections.length > 0 ? turnReflections.join(" | ") : turnMemo.nextSteps.join("; ")
|
|
12345
12384
|
};
|
|
12346
|
-
writeJournalEntry(entry);
|
|
12347
12385
|
try {
|
|
12348
|
-
|
|
12349
|
-
const
|
|
12350
|
-
|
|
12351
|
-
|
|
12386
|
+
const turnDir = WORKSPACE.turnPath(this.turnCounter);
|
|
12387
|
+
const toolsDir = WORKSPACE.turnToolsPath(this.turnCounter);
|
|
12388
|
+
ensureDirExists(turnDir);
|
|
12389
|
+
ensureDirExists(toolsDir);
|
|
12352
12390
|
const turnContent = formatTurnRecord({
|
|
12353
12391
|
turn: this.turnCounter,
|
|
12354
12392
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12355
12393
|
phase: this.state.getPhase(),
|
|
12356
|
-
tools:
|
|
12357
|
-
memo:
|
|
12394
|
+
tools: turnToolJournal,
|
|
12395
|
+
memo: turnMemo,
|
|
12358
12396
|
reflection: entry.reflection
|
|
12359
12397
|
});
|
|
12360
|
-
|
|
12398
|
+
writeFileSync10(join13(turnDir, TURN_FILES.RECORD), turnContent, "utf-8");
|
|
12399
|
+
writeFileSync10(join13(turnDir, TURN_FILES.STRUCTURED), JSON.stringify(entry, null, 2), "utf-8");
|
|
12400
|
+
const memoLines = [];
|
|
12401
|
+
if (turnMemo.keyFindings.length > 0) memoLines.push("## Key Findings", ...turnMemo.keyFindings.map((f) => `- ${f}`), "");
|
|
12402
|
+
if (turnMemo.credentials.length > 0) memoLines.push("## Credentials", ...turnMemo.credentials.map((c) => `- ${c}`), "");
|
|
12403
|
+
if (turnMemo.attackVectors.length > 0) memoLines.push("## Attack Vectors", ...turnMemo.attackVectors.map((v) => `- ${v}`), "");
|
|
12404
|
+
if (turnMemo.failures.length > 0) memoLines.push("## Failures", ...turnMemo.failures.map((f) => `- ${f}`), "");
|
|
12405
|
+
if (turnMemo.suspicions.length > 0) memoLines.push("## Suspicious", ...turnMemo.suspicions.map((s) => `- ${s}`), "");
|
|
12406
|
+
if (turnMemo.nextSteps.length > 0) memoLines.push("## Next Steps", ...turnMemo.nextSteps.map((n) => `- ${n}`), "");
|
|
12407
|
+
if (memoLines.length > 0) {
|
|
12408
|
+
writeFileSync10(join13(turnDir, TURN_FILES.ANALYST), memoLines.join("\n"), "utf-8");
|
|
12409
|
+
}
|
|
12361
12410
|
} catch {
|
|
12362
12411
|
}
|
|
12363
12412
|
try {
|
|
12364
|
-
const
|
|
12365
|
-
const
|
|
12413
|
+
const turnDir = WORKSPACE.turnPath(this.turnCounter);
|
|
12414
|
+
const summaryPath = join13(turnDir, TURN_FILES.SUMMARY);
|
|
12415
|
+
const prevTurn = this.turnCounter - 1;
|
|
12416
|
+
let existingSummary = "";
|
|
12417
|
+
if (prevTurn >= 1) {
|
|
12418
|
+
const prevSummaryPath = join13(WORKSPACE.turnPath(prevTurn), TURN_FILES.SUMMARY);
|
|
12419
|
+
if (existsSync12(prevSummaryPath)) {
|
|
12420
|
+
existingSummary = readFileSync8(prevSummaryPath, "utf-8");
|
|
12421
|
+
}
|
|
12422
|
+
}
|
|
12366
12423
|
const turnData = formatTurnRecord({
|
|
12367
12424
|
turn: this.turnCounter,
|
|
12368
12425
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12369
12426
|
phase: this.state.getPhase(),
|
|
12370
|
-
tools:
|
|
12371
|
-
memo:
|
|
12427
|
+
tools: turnToolJournal,
|
|
12428
|
+
memo: turnMemo,
|
|
12372
12429
|
reflection: entry.reflection
|
|
12373
12430
|
});
|
|
12374
12431
|
const summaryResponse = await this.llm.generateResponse(
|
|
@@ -12385,13 +12442,12 @@ ${turnData}`
|
|
|
12385
12442
|
SUMMARY_REGENERATOR_PROMPT
|
|
12386
12443
|
);
|
|
12387
12444
|
if (summaryResponse.content?.trim()) {
|
|
12388
|
-
|
|
12445
|
+
ensureDirExists(turnDir);
|
|
12446
|
+
writeFileSync10(summaryPath, summaryResponse.content.trim(), "utf-8");
|
|
12389
12447
|
}
|
|
12390
12448
|
} catch {
|
|
12391
12449
|
regenerateJournalSummary();
|
|
12392
12450
|
}
|
|
12393
|
-
rotateJournalEntries();
|
|
12394
|
-
rotateOutputFiles();
|
|
12395
12451
|
rotateTurnRecords();
|
|
12396
12452
|
} catch {
|
|
12397
12453
|
}
|
|
@@ -12522,27 +12578,19 @@ ${turnData}`
|
|
|
12522
12578
|
};
|
|
12523
12579
|
|
|
12524
12580
|
// src/agents/factory.ts
|
|
12525
|
-
|
|
12526
|
-
|
|
12527
|
-
|
|
12528
|
-
|
|
12529
|
-
|
|
12530
|
-
|
|
12531
|
-
|
|
12532
|
-
|
|
12533
|
-
|
|
12534
|
-
|
|
12535
|
-
|
|
12536
|
-
|
|
12537
|
-
|
|
12538
|
-
state,
|
|
12539
|
-
scopeGuard,
|
|
12540
|
-
approvalGate,
|
|
12541
|
-
events
|
|
12542
|
-
);
|
|
12543
|
-
return new MainAgent(state, events, toolRegistry, approvalGate, scopeGuard);
|
|
12544
|
-
}
|
|
12545
|
-
};
|
|
12581
|
+
function createMainAgent(shouldAutoApprove = false) {
|
|
12582
|
+
const state = new SharedState();
|
|
12583
|
+
const events = new AgentEventEmitter();
|
|
12584
|
+
const approvalGate = new ApprovalGate(shouldAutoApprove);
|
|
12585
|
+
const scopeGuard = new ScopeGuard(state);
|
|
12586
|
+
const toolRegistry = new CategorizedToolRegistry(
|
|
12587
|
+
state,
|
|
12588
|
+
scopeGuard,
|
|
12589
|
+
approvalGate,
|
|
12590
|
+
events
|
|
12591
|
+
);
|
|
12592
|
+
return new MainAgent(state, events, toolRegistry, approvalGate, scopeGuard);
|
|
12593
|
+
}
|
|
12546
12594
|
|
|
12547
12595
|
// src/platform/tui/utils/format.ts
|
|
12548
12596
|
var formatDuration2 = (ms) => {
|
|
@@ -13166,7 +13214,7 @@ function getCommandEventIcon(eventType) {
|
|
|
13166
13214
|
|
|
13167
13215
|
// src/platform/tui/hooks/useAgent.ts
|
|
13168
13216
|
var useAgent = (shouldAutoApprove, target) => {
|
|
13169
|
-
const [agent] = useState2(() =>
|
|
13217
|
+
const [agent] = useState2(() => createMainAgent(shouldAutoApprove));
|
|
13170
13218
|
const eventsRef = useRef3(agent.getEventEmitter());
|
|
13171
13219
|
const state = useAgentState();
|
|
13172
13220
|
const {
|
|
@@ -13540,7 +13588,6 @@ var MusicSpinner = memo2(({ color }) => {
|
|
|
13540
13588
|
|
|
13541
13589
|
// src/platform/tui/components/StatusDisplay.tsx
|
|
13542
13590
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
13543
|
-
var MAX_THINKING_LINES = 15;
|
|
13544
13591
|
var StatusDisplay = memo3(({
|
|
13545
13592
|
retryState,
|
|
13546
13593
|
isProcessing,
|
|
@@ -13573,26 +13620,17 @@ var StatusDisplay = memo3(({
|
|
|
13573
13620
|
] });
|
|
13574
13621
|
}
|
|
13575
13622
|
if (isProcessing) {
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
] }),
|
|
13583
|
-
/* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
|
|
13584
|
-
" ",
|
|
13585
|
-
meta
|
|
13586
|
-
] })
|
|
13623
|
+
const previewText = isThinkingStatus && statusLines.length > 1 ? `${statusMain} \u2014 ${statusLines[statusLines.length - 1]}` : statusMain;
|
|
13624
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
13625
|
+
/* @__PURE__ */ jsx4(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: isThinkingStatus ? THEME.cyan : THEME.primary }) }),
|
|
13626
|
+
/* @__PURE__ */ jsxs3(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, bold: true, children: [
|
|
13627
|
+
" ",
|
|
13628
|
+
previewText
|
|
13587
13629
|
] }),
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
] }
|
|
13592
|
-
!isThinkingStatus && statusLines.length > 1 && /* @__PURE__ */ jsx4(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
|
|
13593
|
-
"\u2502 ",
|
|
13594
|
-
statusLines.slice(1).join(" ")
|
|
13595
|
-
] }) })
|
|
13630
|
+
/* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
|
|
13631
|
+
" ",
|
|
13632
|
+
meta
|
|
13633
|
+
] })
|
|
13596
13634
|
] });
|
|
13597
13635
|
}
|
|
13598
13636
|
return /* @__PURE__ */ jsx4(Box3, { height: 1, children: /* @__PURE__ */ jsx4(Text4, { children: " " }) });
|
|
@@ -13682,10 +13720,10 @@ var ChatInput = memo4(({
|
|
|
13682
13720
|
paddingX: 1,
|
|
13683
13721
|
overflowX: "hidden",
|
|
13684
13722
|
children: inputRequest.status === "active" ? /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
13685
|
-
/* @__PURE__ */
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13723
|
+
/* @__PURE__ */ jsxs4(Text5, { color: THEME.yellow, children: [
|
|
13724
|
+
"\u25B8 ",
|
|
13725
|
+
inputRequest.prompt,
|
|
13726
|
+
" "
|
|
13689
13727
|
] }),
|
|
13690
13728
|
/* @__PURE__ */ jsx5(
|
|
13691
13729
|
TextInput,
|
|
@@ -13693,7 +13731,7 @@ var ChatInput = memo4(({
|
|
|
13693
13731
|
value: secretInput,
|
|
13694
13732
|
onChange: setSecretInput,
|
|
13695
13733
|
onSubmit: onSecretSubmit,
|
|
13696
|
-
placeholder: "...",
|
|
13734
|
+
placeholder: inputRequest.isPassword ? "(hidden)" : "...",
|
|
13697
13735
|
mask: inputRequest.isPassword ? "\u2022" : void 0
|
|
13698
13736
|
}
|
|
13699
13737
|
)
|
|
@@ -14028,7 +14066,7 @@ ${procData.stdout || "(no output)"}
|
|
|
14028
14066
|
}, [handleCtrlC]);
|
|
14029
14067
|
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: [
|
|
14030
14068
|
/* @__PURE__ */ jsx7(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsx7(MessageList, { messages }) }),
|
|
14031
|
-
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
14069
|
+
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: 6, children: [
|
|
14032
14070
|
/* @__PURE__ */ jsx7(
|
|
14033
14071
|
StatusDisplay,
|
|
14034
14072
|
{
|
|
@@ -14094,6 +14132,11 @@ var CLI_SCAN_TYPES = Object.freeze([
|
|
|
14094
14132
|
import gradient from "gradient-string";
|
|
14095
14133
|
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
14096
14134
|
initDebugLogger();
|
|
14135
|
+
var _configErrors = validateRequiredConfig();
|
|
14136
|
+
if (_configErrors.length > 0) {
|
|
14137
|
+
_configErrors.forEach((e) => console.error(chalk.hex(HEX.red)(e)));
|
|
14138
|
+
process.exit(EXIT_CODES.CONFIG_ERROR);
|
|
14139
|
+
}
|
|
14097
14140
|
var program = new Command();
|
|
14098
14141
|
program.name("pentesting").version(APP_VERSION).description(APP_DESCRIPTION).option("--dangerously-skip-permissions", "Skip all permission prompts (dangerous!)").option("-t, --target <target>", "Set initial target");
|
|
14099
14142
|
program.command("interactive", { isDefault: true }).alias("i").description("Start interactive TUI mode").action(async () => {
|
|
@@ -14128,7 +14171,7 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
|
|
|
14128
14171
|
}
|
|
14129
14172
|
console.log(chalk.hex(HEX.primary)(`[target] Objective: ${objective}
|
|
14130
14173
|
`));
|
|
14131
|
-
const agent =
|
|
14174
|
+
const agent = createMainAgent(skipPermissions);
|
|
14132
14175
|
if (skipPermissions) {
|
|
14133
14176
|
agent.setAutoApprove(true);
|
|
14134
14177
|
}
|
|
@@ -14166,7 +14209,7 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
|
|
|
14166
14209
|
console.log(chalk.hex(HEX.primary)(`
|
|
14167
14210
|
[scan] Target: ${target} (${options.scanType})
|
|
14168
14211
|
`));
|
|
14169
|
-
const agent =
|
|
14212
|
+
const agent = createMainAgent(skipPermissions);
|
|
14170
14213
|
agent.addTarget(target);
|
|
14171
14214
|
agent.setScope([target]);
|
|
14172
14215
|
const shutdown = async (exitCode = 0) => {
|