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/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.49.4";
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}/tmp`;
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 TURNS_DIR = `${PENTESTING_ROOT}/memory/turns`;
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
- /** Temporary files */
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
- /** Full tool outputs */
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
- /** Persistent per-turn journal (§13 memo system) */
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 record files */
900
+ /** Turn archive root: .pentesting/archive/ */
887
901
  get TURNS() {
888
- return path.resolve(TURNS_DIR);
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, "debug.log");
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
- const primaryBinary = extractBinary(subCommands[0].trim());
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
- const patterns = [
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
- const missing = patterns.some((p) => p.test(combined));
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(command, args, { ...options, timeout });
1762
+ const result2 = await executeCommandOnce(fullCommand, { ...options, timeout });
1683
1763
  if (result2.success) {
1684
1764
  return result2;
1685
1765
  }
1686
- const { missing, toolName } = isCommandNotFound(result2.error || "", result2.output);
1687
- if (!missing || !toolName || attempt >= maxRetries) {
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, args = [], options = {}) {
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/shared/constants/files.ts
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, mkdirSync as mkdirSync2 } from "fs";
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, "persistent-knowledge.json");
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
- mkdirSync2(WORKSPACE.MEMORY, { recursive: true });
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://open.bigmodel.cn/api/paas/v4/tools/web-search-pro";
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 isBrowserHeadless() {
5426
- return true;
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
- const result2 = await new Promise((resolve) => {
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: ${headlessMode},
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(join6(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME), `screenshot-${Date.now()}.png`) : void 0;
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: ${headlessMode} });
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
- model: "web-search-pro",
5932
- messages: [{ role: LLM_ROLES.USER, content: query }],
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
- try {
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
- return searchExploitDB(service, version);
6186
- } catch (error) {
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: false,
6189
- output: "",
6190
- error: getErrorMessage2(error)
6251
+ success: true,
6252
+ output: lines.join("\n") || `No exploits found for ${query}`
6191
6253
  };
6192
- }
6193
- }
6194
- async function searchExploitDB(service, version) {
6195
- try {
6196
- const query = version ? `${service} ${version}` : service;
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: stdout || `No exploits found for ${query}`
6261
+ output: `No exploits found for ${query}`
6221
6262
  };
6222
6263
  }
6223
- } catch (error) {
6224
6264
  return {
6225
- success: false,
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, _engine) {
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) || apiUrl.includes(SEARCH_URL_PATTERN.Z_AI)) {
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 = (_state) => [
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 result2 = await browseUrl(p.url, {
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 result2 = await fillAndSubmitForm(p.url, p.fields);
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
- var createAttackTools = (_state) => [
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\\n"];
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 = statSync3(fullPath);
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 || !existsSync12(dirPath)) return;
7497
+ if (depth > maxDepth || !existsSync6(dirPath)) return;
7260
7498
  let entries;
7261
7499
  try {
7262
- entries = readdirSync4(dirPath, { withFileTypes: true });
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 = join13(dirPath, entry.name);
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(`\\n## ${basePath}\\n`);
7519
+ results.push(`
7520
+ ## ${basePath}
7521
+ `);
7282
7522
  scanDir(basePath);
7283
7523
  }
7284
7524
  if (totalCount === 0) {
7285
- results.push(`\\nNo wordlists found matching your criteria.`);
7286
- results.push(`\\nTips:`);
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(`\\n---`);
7292
- results.push(`\\nTotal wordlists found: ${totalCount}`);
7293
- results.push(`\\n## Commonly Used Wordlists (check if available)`);
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("\\n")
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(state),
7312
- ...createAttackTools(state)
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
- const { writeFileSync: writeFileSync10 } = await import("fs");
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
- type: "object",
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
- type: "object",
8085
- properties: {
8086
- processId: {
8087
- type: "string",
8088
- description: "Process ID to clean up (optional)"
8089
- },
8090
- killOrphans: {
8091
- type: "boolean",
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
- type: "object",
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 output = healthMonitor.generateSummary();
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
- // src/domains/database/tools.ts
8346
- var DATABASE_TOOLS = [
8347
- {
8348
- name: TOOL_NAMES.SQLMAP_BASIC,
8349
- description: "SQL injection testing with sqlmap - basic scan",
8350
- parameters: {
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
- required: ["target"],
8354
- execute: async (params) => {
8355
- const target = params.target;
8356
- return await runCommand("sqlmap", ["-u", target, "--batch", "--risk=1", "--level=1"]);
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
- required: ["target"],
8366
- execute: async (params) => {
8367
- const target = params.target;
8368
- return await runCommand("sqlmap", ["-u", target, "--batch", "--risk=3", "--level=5", "--dbs", "--tables"]);
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
- required: ["target"],
8379
- execute: async (params) => {
8380
- const target = params.target;
8381
- const port = params.port || String(SERVICE_PORTS.MYSQL);
8382
- return await runCommand("mysql", ["-h", target, "-P", port, "-e", "SELECT VERSION(), USER(), DATABASE();"]);
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
- name: TOOL_NAMES.POSTGRES_ENUM,
8387
- description: "PostgreSQL enumeration",
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
- name: TOOL_NAMES.REDIS_ENUM,
8399
- description: "Redis enumeration",
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
- name: TOOL_NAMES.DB_BRUTE,
8413
- description: "Brute force database credentials",
8414
- parameters: {
8415
- target: { type: "string", description: "Target IP" },
8416
- service: { type: "string", description: "Service (mysql, postgres, etc.)" }
8417
- },
8418
- required: ["target", "service"],
8419
- execute: async (params) => {
8420
- const target = params.target;
8421
- const service = params.service || SERVICES.MYSQL;
8422
- return await runCommand("hydra", [
8423
- "-L",
8424
- WORDLISTS.USERNAMES,
8425
- "-P",
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 AD_TOOLS = [
8601
+ var BLOODHOUND_COLLECT = createTool(
8602
+ TOOL_NAMES.BLOODHOUND_COLLECT,
8603
+ "BloodHound data collection",
8445
8604
  {
8446
- name: TOOL_NAMES.BLOODHOUND_COLLECT,
8447
- description: "BloodHound data collection",
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
- name: TOOL_NAMES.KERBEROAST,
8461
- description: "Kerberoasting attack",
8462
- parameters: {
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
- name: TOOL_NAMES.LDAP_ENUM,
8475
- description: "LDAP enumeration",
8476
- parameters: {
8477
- target: { type: "string", description: "LDAP Server" }
8478
- },
8479
- required: ["target"],
8480
- execute: async (params) => {
8481
- const target = params.target;
8482
- return await runCommand("ldapsearch", ["-x", "-H", `ldap://${target}`, "-b", ""]);
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 EMAIL_TOOLS = [
8498
- {
8499
- name: TOOL_NAMES.SMTP_ENUM,
8500
- description: "SMTP user enumeration",
8501
- parameters: {
8502
- target: { type: "string", description: "SMTP server" }
8503
- },
8504
- required: ["target"],
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 REMOTE_ACCESS_TOOLS = [
8522
- {
8523
- name: TOOL_NAMES.SSH_ENUM,
8524
- description: "SSH enumeration",
8525
- parameters: {
8526
- target: { type: "string", description: "SSH Server" }
8527
- },
8528
- required: ["target"],
8529
- execute: async (params) => {
8530
- return await runCommand("ssh-audit", [params.target]);
8531
- }
8532
- },
8533
- {
8534
- name: TOOL_NAMES.RDP_ENUM,
8535
- description: "RDP enumeration",
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 FILE_SHARING_TOOLS = [
8557
- {
8558
- name: TOOL_NAMES.FTP_ENUM,
8559
- description: "FTP enumeration",
8560
- parameters: {
8561
- target: { type: "string", description: "FTP Server" }
8562
- },
8563
- required: ["target"],
8564
- execute: async (params) => {
8565
- return await runCommand("nmap", ["-p", String(SERVICE_PORTS.FTP), "--script", "ftp-anon,ftp-syst", params.target]);
8566
- }
8567
- },
8568
- {
8569
- name: TOOL_NAMES.SMB_ENUM,
8570
- description: "SMB enumeration",
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 CLOUD_TOOLS = [
8597
- {
8598
- name: TOOL_NAMES.AWS_S3_CHECK,
8599
- description: "S3 bucket security check",
8600
- parameters: {
8601
- bucket: { type: "string", description: "Bucket name" }
8602
- },
8603
- required: ["bucket"],
8604
- execute: async (params) => {
8605
- const bucket = params.bucket;
8606
- return await runCommand("aws", ["s3", "ls", `s3://${bucket}`, "--no-sign-request"]);
8607
- }
8608
- },
8609
- {
8610
- name: TOOL_NAMES.CLOUD_META_CHECK,
8611
- description: "Check cloud metadata service access",
8612
- parameters: {
8613
- provider: { type: "string", description: "aws, azure, or gcp" }
8614
- },
8615
- required: ["provider"],
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 CONTAINER_TOOLS = [
8636
- {
8637
- name: TOOL_NAMES.DOCKER_PS,
8638
- description: "List running Docker containers",
8639
- parameters: {
8640
- host: { type: "string", description: "Docker host" }
8641
- },
8642
- required: ["host"],
8643
- execute: async (params) => {
8644
- return await runCommand("docker", ["-H", params.host, "ps"]);
8645
- }
8646
- },
8647
- {
8648
- name: TOOL_NAMES.KUBE_GET_PODS,
8649
- description: "List Kubernetes pods",
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 API_TOOLS = [
8671
- {
8672
- name: TOOL_NAMES.API_DISCOVER,
8673
- description: "API endpoint discovery - using Arjun",
8674
- parameters: {
8675
- target: { type: "string", description: "Target URL" }
8676
- },
8677
- required: ["target"],
8678
- execute: async (params) => {
8679
- const target = params.target;
8680
- return await runCommand("arjun", ["-u", target, "-t", "20"]);
8681
- }
8682
- },
8683
- {
8684
- name: TOOL_NAMES.GRAPHQL_INTROSPECT,
8685
- description: "GraphQL introspection - schema discovery",
8686
- parameters: {
8687
- target: { type: "string", description: "GQL Endpoint" }
8688
- },
8689
- required: ["target"],
8690
- execute: async (params) => {
8691
- const target = params.target;
8692
- return await runCommand("curl", ["-X", "POST", target, "-H", "Content-Type: application/json", "-d", '{"query":"{__schema {queryType {name}}}"}']);
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
- name: TOOL_NAMES.SWAGGER_PARSE,
8697
- description: "Parse Swagger/OpenAPI specification",
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
- name: TOOL_NAMES.API_FUZZ,
8709
- description: "General API fuzzing",
8710
- parameters: {
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 WIRELESS_TOOLS = [
8734
- {
8735
- name: TOOL_NAMES.WIFI_SCAN,
8736
- description: "WiFi network scanning",
8737
- parameters: {
8738
- interface: { type: "string", description: "Wireless interface" }
8739
- },
8740
- required: ["interface"],
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 ICS_TOOLS = [
8758
- {
8759
- name: TOOL_NAMES.MODBUS_ENUM,
8760
- description: "Modbus enumeration",
8761
- parameters: {
8762
- target: { type: "string", description: "ICS Device" }
8763
- },
8764
- required: ["target"],
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 join7, dirname as dirname3 } from "path";
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: join7(__dirname, "network/prompt.md")
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: join7(__dirname, "web/prompt.md")
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: join7(__dirname, "database/prompt.md")
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: join7(__dirname, "ad/prompt.md")
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: join7(__dirname, "email/prompt.md")
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: join7(__dirname, "remote-access/prompt.md")
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: join7(__dirname, "file-sharing/prompt.md")
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: join7(__dirname, "cloud/prompt.md")
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: join7(__dirname, "container/prompt.md")
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: join7(__dirname, "api/prompt.md")
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: join7(__dirname, "wireless/prompt.md")
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: join7(__dirname, "ics/prompt.md")
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, onTextStart, onReasoningStart, onTextEnd, onReasoningEnd, onContent, onReasoning, onUsage, getTotalChars, currentBlockRef } = context;
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 writeFileSync6, readFileSync as readFileSync4, existsSync as existsSync6, readdirSync, statSync, unlinkSync as unlinkSync4, rmSync } from "fs";
9737
- import { join as join8 } from "path";
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 sessionId = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
9755
- const sessionFile = join8(sessionsDir, `${sessionId}.json`);
9756
- writeFileSync6(sessionFile, JSON.stringify(snapshot, null, 2), "utf-8");
9757
- const latestFile = join8(sessionsDir, "latest.json");
9758
- writeFileSync6(latestFile, JSON.stringify(snapshot, null, 2), "utf-8");
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 = readdirSync(sessionsDir).filter((f) => f.endsWith(FILE_EXTENSIONS.JSON) && f !== SPECIAL_FILES.LATEST_STATE).map((f) => ({
9765
- name: f,
9766
- path: join8(sessionsDir, f),
9767
- mtime: statSync(join8(sessionsDir, f)).mtimeMs
9768
- })).sort((a, b) => b.mtime - a.mtime);
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 = join8(WORKSPACE.SESSIONS, "latest.json");
9778
- if (!existsSync6(latestFile)) {
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
- const id = state.addTodo(item.content, item.priority);
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.addMissionChecklistItems(snapshot.missionChecklist.map((c) => c.text));
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 (existsSync6(dir.path)) {
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 writeFileSync7, mkdirSync as mkdirSync3, existsSync as existsSync7 } from "fs";
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 getOutputDir = () => WORKSPACE.OUTPUTS;
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["key findings"] || []),
10158
- credentials: filterNone(sections["credentials/secrets"] || []),
10159
- attackVectors: filterNone(sections["attack vectors"] || []),
10160
- failures: filterNone(sections["failures/errors"] || []),
10161
- suspicions: filterNone(sections["suspicious signals"] || []),
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["next steps"] || []),
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
- const outputDir = getOutputDir();
10209
- if (!existsSync7(outputDir)) {
10210
- mkdirSync3(outputDir, { recursive: true });
10211
- }
10212
- const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
10213
- const safeName = toolName.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 30);
10214
- const filePath = `${outputDir}/${timestamp}_${safeName}.txt`;
10215
- writeFileSync7(filePath, output, "utf-8");
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/core-agent.ts
10245
- var CoreAgent = class _CoreAgent {
10246
- llm;
10338
+ // src/agents/tool-executor.ts
10339
+ var ToolExecutor = class _ToolExecutor {
10247
10340
  state;
10248
10341
  events;
10249
10342
  toolRegistry;
10250
- agentType;
10251
- maxIterations;
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 = { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
10261
- /** Analyst reflections collected during this turn (1-line assessments) */
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
- constructor(agentType, state, events, toolRegistry, maxIterations) {
10264
- this.agentType = agentType;
10265
- this.state = state;
10266
- this.events = events;
10267
- this.toolRegistry = toolRegistry;
10268
- this.llm = getLLMClient();
10269
- this.maxIterations = maxIterations || AGENT_LIMITS.MAX_ITERATIONS;
10270
- }
10271
- /** Set or update the tool registry (for delayed initialization) */
10272
- setToolRegistry(registry) {
10273
- this.toolRegistry = registry;
10274
- }
10275
- /** Abort the current execution — immediately cancels LLM calls and retries */
10276
- abort() {
10277
- this.abortController?.abort();
10278
- }
10279
- // ─────────────────────────────────────────────────────────────────
10280
- // SUBSECTION: Main Run Loop
10281
- // ─────────────────────────────────────────────────────────────────
10282
- /** The core loop: Think → Act → Observe */
10283
- async run(task, systemPrompt) {
10284
- this.abortController = new AbortController();
10285
- const messages = [{ role: LLM_ROLES.USER, content: task }];
10286
- let consecutiveLLMErrors = 0;
10287
- const maxConsecutiveLLMErrors = AGENT_LIMITS.MAX_CONSECUTIVE_LLM_ERRORS;
10288
- const progress = {
10289
- totalToolsExecuted: 0,
10290
- consecutiveIdleIterations: 0,
10291
- lastToolExecutedAt: Date.now(),
10292
- toolErrors: 0,
10293
- toolSuccesses: 0,
10294
- blockedCommandPatterns: /* @__PURE__ */ new Map(),
10295
- totalBlockedCommands: 0
10296
- };
10297
- for (let iteration = 0; iteration < this.maxIterations; iteration++) {
10298
- if (this.abortController.signal.aborted) {
10299
- return this.buildCancelledResult(iteration, progress.totalToolsExecuted);
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 Processing
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.executeToolCallsSequentially(toolCalls, progress);
10401
+ return this.executeSequentially(toolCalls, progress);
10669
10402
  }
10670
- const allParallelSafe = toolCalls.every((c) => _CoreAgent.PARALLEL_SAFE_TOOLS.has(c.name));
10403
+ const allParallelSafe = toolCalls.every((c) => _ToolExecutor.PARALLEL_SAFE_TOOLS.has(c.name));
10671
10404
  if (allParallelSafe) {
10672
- return this.executeToolCallsInParallel(toolCalls, progress);
10405
+ return this.executeInParallel(toolCalls, progress);
10673
10406
  }
10674
- const parallelCalls = toolCalls.filter((c) => _CoreAgent.PARALLEL_SAFE_TOOLS.has(c.name));
10675
- const sequentialCalls = toolCalls.filter((c) => !_CoreAgent.PARALLEL_SAFE_TOOLS.has(c.name));
10676
- const parallelResults = parallelCalls.length > 0 ? await this.executeToolCallsInParallel(parallelCalls, progress) : [];
10677
- const sequentialResults = sequentialCalls.length > 0 ? await this.executeToolCallsSequentially(sequentialCalls, progress) : [];
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
- /** Execute tool calls in parallel via Promise.allSettled */
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.executeSingleTool(call, progress));
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
- /** Execute tool calls sequentially */
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.executeSingleTool(call, progress);
10433
+ const result2 = await this.executeSingle(call, progress);
10703
10434
  results.push(result2);
10704
10435
  }
10705
10436
  return results;
10706
10437
  }
10707
- /** Execute a single tool call with error enrichment and event emission */
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 = this.enrichToolError({ toolName: call.name, input: call.input, error: errorMsg, originalOutput: "", progress });
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
- * Handle tool result: enrich errors or track success.
10737
- * @returns Possibly enriched output text.
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 this.enrichToolError({ toolName: call.name, input: call.input, error: result2.error, originalOutput: outputText, progress });
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} characters for context hygiene] ...
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}` : ""}` : digestFallbackOutput.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY);
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({ type: LOOT_TYPES.CREDENTIAL, host: "auto-extracted", detail: cred, obtainedAt: Date.now() });
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
- * Enrich tool errordelegates to extracted module (§3-1)
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
- enrichToolError(ctx) {
10848
- return enrichToolErrorContext(ctx);
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
- return this.toolRegistry.getAll().map((t) => ({
10855
- name: t.name,
10856
- description: t.description,
10857
- input_schema: {
10858
- type: "object",
10859
- properties: t.parameters,
10860
- required: t.required || []
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: CTF Flag Detection
10799
+ // SUBSECTION: Step Execution
10866
10800
  // ─────────────────────────────────────────────────────────────────
10867
- /**
10868
- * Scan tool output for CTF flag patterns.
10869
- * When a new flag is detected, store it in state and emit event.
10870
- *
10871
- * @remarks
10872
- * WHY: Automatic flag detection eliminates manual flag hunting in CTF competitions.
10873
- * Called after every tool execution — zero overhead when CTF mode is OFF.
10874
- */
10875
- scanForFlags(output) {
10876
- if (!this.state.isCtfMode()) return;
10877
- const flags = detectFlags(output);
10878
- for (const flag of flags) {
10879
- const isNew = this.state.addFlag(flag);
10880
- if (isNew) {
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.FLAG_FOUND,
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 existsSync9, readdirSync as readdirSync3 } from "fs";
10911
- import { join as join10, dirname as dirname4 } from "path";
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 writeFileSync8, readFileSync as readFileSync5, existsSync as existsSync8, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync5 } from "fs";
11186
- import { join as join9 } from "path";
11187
- var MAX_JOURNAL_ENTRIES = 50;
11188
- var MAX_OUTPUT_FILES = 30;
11189
- var TURN_PREFIX = "turn-";
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 summaryPath = join9(WORKSPACE.JOURNAL, SUMMARY_FILE);
11207
- if (!existsSync8(summaryPath)) return "";
11208
- return readFileSync5(summaryPath, "utf-8");
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 = MAX_JOURNAL_ENTRIES) {
11310
+ function getRecentEntries(count = MEMORY_LIMITS.MAX_TURN_ENTRIES) {
11214
11311
  try {
11215
- const journalDir = WORKSPACE.JOURNAL;
11216
- if (!existsSync8(journalDir)) return [];
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 file of files) {
11315
+ for (const turn of turnDirs) {
11220
11316
  try {
11221
- const raw = readFileSync5(join9(journalDir, file), "utf-8");
11222
- entries.push(JSON.parse(raw));
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 journalDir = WORKSPACE.JOURNAL;
11234
- if (!existsSync8(journalDir)) return 1;
11235
- const files = readdirSync2(journalDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".json")).sort();
11236
- if (files.length === 0) return 1;
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 journalDir = WORKSPACE.JOURNAL;
11249
- ensureDirExists(journalDir);
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 = join9(journalDir, SUMMARY_FILE);
11252
- writeFileSync8(summaryPath, summary, "utf-8");
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
- `# Session Journal Summary`,
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("## Techniques Tried (by attack value)");
11341
- sections.push("> \u26A1HIGH=keep drilling \u26A1MED=worth exploring \u26A1LOW=low priority \u26A1NONE=abandon");
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("\u{1F9E0} Analyst Analysis (attack value rationale)", reflections);
11346
- addSection("\u{1F50D} Suspicious Signals (unconfirmed, needs investigation)", suspicions);
11347
- addSection("\u{1F4CB} Key Findings", findings);
11348
- addSection("\u{1F511} Credentials Obtained", credentials);
11349
- addSection("\u2705 Successful Attack Vectors", successes);
11350
- addSection("\u274C Failure Causes (do not repeat)", failures);
11351
- addSection("\u27A1\uFE0F Next Recommendations", nextSteps);
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 (!existsSync8(turnsDir)) return;
11402
- const files = readdirSync2(turnsDir).filter((f) => f.startsWith(TURN_PREFIX) && f.endsWith(".md")).sort();
11403
- if (files.length <= MAX_JOURNAL_ENTRIES) return;
11404
- const toDelete = files.slice(0, files.length - MAX_JOURNAL_ENTRIES);
11405
- for (const file of toDelete) {
11406
- try {
11407
- unlinkSync5(join9(turnsDir, file));
11408
- } catch {
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 = join10(__dirname2, "prompts");
11422
- var TECHNIQUES_DIR = join10(PROMPTS_DIR, PROMPT_PATHS.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 = join10(PROMPTS_DIR, filename);
11556
- return existsSync9(path2) ? readFileSync6(path2, PROMPT_CONFIG.ENCODING) : "";
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 (!existsSync9(TECHNIQUES_DIR)) return "";
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 = join10(TECHNIQUES_DIR, `${technique}.md`);
11662
+ const filePath = join11(TECHNIQUES_DIR, `${technique}.md`);
11608
11663
  try {
11609
- if (!existsSync9(filePath)) continue;
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 = readdirSync3(TECHNIQUES_DIR).filter((f) => f.endsWith(".md") && f !== "README.md" && !loadedSet.has(f));
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 = join10(TECHNIQUES_DIR, file);
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 prefers Summary Regenerator (⑥) output,
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 existsSync10 } from "fs";
11765
- import { join as join11, dirname as dirname5 } from "path";
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 = join11(__dirname3, "prompts", "strategist-system.md");
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
- let journalSummary = "";
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 (existsSync10(STRATEGIST_PROMPT_PATH)) {
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("## \uC2E4\uD589 \uB3C4\uAD6C");
12187
+ sections.push(`## ${TURN_SECTIONS.TOOLS_EXECUTED}`);
12150
12188
  if (tools.length === 0) {
12151
- sections.push("- (\uB3C4\uAD6C \uC2E4\uD589 \uC5C6\uC74C)");
12189
+ sections.push(`- ${TURN_EMPTY_MESSAGES.NO_TOOLS}`);
12152
12190
  } else {
12153
12191
  for (const tool of tools) {
12154
- const status = tool.success ? "\u2705" : "\u274C";
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("## \uD575\uC2EC \uC778\uC0AC\uC774\uD2B8");
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(`- DEAD END: ${f}`);
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("- (\uD2B9\uC774\uC0AC\uD56D \uC5C6\uC74C)");
12218
+ sections.push(`- ${TURN_EMPTY_MESSAGES.NO_INSIGHTS}`);
12181
12219
  }
12182
12220
  sections.push("");
12183
- sections.push("## \uC790\uAE30\uBC18\uC131");
12184
- sections.push(reflection || "- (\uBC18\uC131 \uC5C6\uC74C)");
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 writeFileSync9, existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
12229
- import { join as join12 } from "path";
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 (this.turnToolJournal.length > 0) {
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: this.turnToolJournal,
12323
- memo: this.turnMemo,
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
- this.turnReflections.push(reflection.content.trim());
12370
+ turnReflections.push(reflection.content.trim());
12332
12371
  }
12333
12372
  }
12334
12373
  } catch {
12335
12374
  }
12336
- if (this.turnToolJournal.length > 0) {
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: this.turnToolJournal,
12343
- memo: this.turnMemo,
12344
- reflection: this.turnReflections.length > 0 ? this.turnReflections.join(" | ") : this.turnMemo.nextSteps.join("; ")
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
- ensureDirExists(WORKSPACE.TURNS);
12349
- const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
12350
- const turnFileName = `turn-${String(this.turnCounter).padStart(4, "0")}_${ts}.md`;
12351
- const turnPath = join12(WORKSPACE.TURNS, turnFileName);
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: this.turnToolJournal,
12357
- memo: this.turnMemo,
12394
+ tools: turnToolJournal,
12395
+ memo: turnMemo,
12358
12396
  reflection: entry.reflection
12359
12397
  });
12360
- writeFileSync9(turnPath, turnContent, "utf-8");
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 summaryPath = join12(WORKSPACE.TURNS, "summary.md");
12365
- const existingSummary = existsSync11(summaryPath) ? readFileSync8(summaryPath, "utf-8") : "";
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: this.turnToolJournal,
12371
- memo: this.turnMemo,
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
- writeFileSync9(summaryPath, summaryResponse.content.trim(), "utf-8");
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
- var AgentFactory = class {
12526
- /**
12527
- * Create a fully initialized MainAgent system.
12528
- *
12529
- * Architecture: Single agent with all tools.
12530
- * No sub-agents, no spawn_sub, no nested loops.
12531
- */
12532
- static createMainAgent(shouldAutoApprove = false) {
12533
- const state = new SharedState();
12534
- const events = new AgentEventEmitter();
12535
- const approvalGate = new ApprovalGate(shouldAutoApprove);
12536
- const scopeGuard = new ScopeGuard(state);
12537
- const toolRegistry = new CategorizedToolRegistry(
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(() => AgentFactory.createMainAgent(shouldAutoApprove));
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
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
13577
- /* @__PURE__ */ jsxs3(Box3, { children: [
13578
- /* @__PURE__ */ jsx4(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: isThinkingStatus ? THEME.cyan : THEME.primary }) }),
13579
- /* @__PURE__ */ jsxs3(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, bold: true, children: [
13580
- " ",
13581
- statusMain
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
- isThinkingStatus && statusLines.length > 1 && statusLines.slice(1).slice(-MAX_THINKING_LINES).map((line, i) => /* @__PURE__ */ jsxs3(Box3, { children: [
13589
- /* @__PURE__ */ jsx4(Text4, { color: THEME.cyan, children: "\u2502 " }),
13590
- /* @__PURE__ */ jsx4(Text4, { color: THEME.gray, children: line })
13591
- ] }, i)),
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__ */ jsx5(Text5, { color: THEME.yellow, children: "[auth]" }),
13686
- /* @__PURE__ */ jsxs4(Text5, { color: THEME.gray, children: [
13687
- " ",
13688
- inputRequest.prompt
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 = AgentFactory.createMainAgent(skipPermissions);
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 = AgentFactory.createMainAgent(skipPermissions);
14212
+ const agent = createMainAgent(skipPermissions);
14170
14213
  agent.addTarget(target);
14171
14214
  agent.setScope([target]);
14172
14215
  const shutdown = async (exitCode = 0) => {