pentesting 0.50.0 → 0.51.1

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
@@ -228,7 +228,9 @@ var EXIT_CODES = {
228
228
  /** Process killed by SIGTERM */
229
229
  SIGTERM: 143,
230
230
  /** Process killed by SIGKILL */
231
- SIGKILL: 137
231
+ SIGKILL: 137,
232
+ /** Invalid or missing configuration (Unix EX_CONFIG convention = 78) */
233
+ CONFIG_ERROR: 78
232
234
  };
233
235
  var PROCESS_ACTIONS = {
234
236
  LIST: "list",
@@ -343,7 +345,7 @@ var ORPHAN_PROCESS_NAMES = [
343
345
 
344
346
  // src/shared/constants/agent.ts
345
347
  var APP_NAME = "Pentest AI";
346
- var APP_VERSION = "0.50.0";
348
+ var APP_VERSION = "0.51.1";
347
349
  var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
348
350
  var LLM_ROLES = {
349
351
  SYSTEM: "system",
@@ -532,14 +534,7 @@ var PHASES = {
532
534
  REPORT: "report"
533
535
  };
534
536
  var AGENT_ROLES = {
535
- ORCHESTRATOR: "orchestrator",
536
- RECON: "recon",
537
- WEB: "web",
538
- EXPLOITER: "exploit",
539
- DATABASE: "database",
540
- INFRA: "infra",
541
- VULN: "vulnerability_analysis",
542
- POST_EXPLOIT: "post_exploitation"
537
+ ORCHESTRATOR: "orchestrator"
543
538
  };
544
539
  var SERVICES = {
545
540
  HTTP: "http",
@@ -772,7 +767,6 @@ var EVENT_TYPES = {
772
767
  RETRY: "retry",
773
768
  USAGE_UPDATE: "usage_update",
774
769
  INPUT_REQUEST: "input_request",
775
- LOG: "log",
776
770
  FLAG_FOUND: "flag_found"
777
771
  };
778
772
  var UI_COMMANDS = {
@@ -823,7 +817,7 @@ var DEFAULTS = {
823
817
  PORT_STATE_OPEN: "open"
824
818
  };
825
819
 
826
- // src/engine/process-manager.ts
820
+ // src/engine/process/process-manager.ts
827
821
  import { spawn as spawn3, execSync as execSync2 } from "child_process";
828
822
  import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2 } from "fs";
829
823
 
@@ -1333,12 +1327,7 @@ function validateCommand(command) {
1333
1327
  return result2;
1334
1328
  }
1335
1329
  }
1336
- const primaryBinary = extractBinary(subCommands[0].trim());
1337
- return {
1338
- isSafe: true,
1339
- binary: primaryBinary || void 0,
1340
- args: normalizedCommand.split(/\s+/).slice(1)
1341
- };
1330
+ return { isSafe: true };
1342
1331
  }
1343
1332
  function splitChainedCommands(command) {
1344
1333
  const parts = [];
@@ -1596,6 +1585,7 @@ var TOOL_PACKAGE_MAP = {
1596
1585
  "seclists": { apt: "seclists", brew: "seclists" },
1597
1586
  "wfuzz": { apt: "wfuzz", brew: "wfuzz", pip: "wfuzz" },
1598
1587
  "dirsearch": { apt: "dirsearch", brew: "dirsearch", pip: "dirsearch" },
1588
+ "wafw00f": { apt: "wafw00f", brew: "wafw00f", pip: "wafw00f" },
1599
1589
  "feroxbuster": { apt: "feroxbuster", brew: "feroxbuster" },
1600
1590
  "subfinder": { apt: "subfinder", brew: "subfinder" },
1601
1591
  "amass": { apt: "amass", brew: "amass" },
@@ -1637,20 +1627,16 @@ async function detectPackageManager() {
1637
1627
  }
1638
1628
  function isCommandNotFound(stderr, stdout) {
1639
1629
  const combined = (stderr + stdout).toLowerCase();
1640
- const patterns = [
1630
+ return [
1641
1631
  /command not found/,
1642
- /not found$/,
1632
+ /not found/,
1643
1633
  /no such file or directory/,
1644
1634
  /is not recognized/,
1645
1635
  /cannot find/,
1646
1636
  /not installed/,
1647
- /unable to locate package/
1648
- ];
1649
- const missing = patterns.some((p) => p.test(combined));
1650
- if (!missing) return { missing: false, toolName: null };
1651
- const toolMatch = combined.match(/(?:command not found|not found):\s*([a-zA-Z0-9_-]+)/i);
1652
- const toolName = toolMatch ? toolMatch[1] : null;
1653
- return { missing: true, toolName };
1637
+ /unable to locate package/,
1638
+ /enoent/
1639
+ ].some((p) => p.test(combined));
1654
1640
  }
1655
1641
  async function installTool(toolName, eventEmitter, inputHandler) {
1656
1642
  const pkgInfo = TOOL_PACKAGE_MAP[toolName];
@@ -1769,14 +1755,15 @@ async function runCommand(command, args = [], options = {}) {
1769
1755
  }
1770
1756
  const timeout = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
1771
1757
  const maxRetries = options.maxRetries ?? AGENT_LIMITS.MAX_INSTALL_RETRIES;
1758
+ const toolName = command.split(/[\s/]/).pop() || command;
1772
1759
  let lastResult = { success: false, output: "", error: "Unknown error" };
1773
1760
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
1774
- const result2 = await executeCommandOnce(command, args, { ...options, timeout });
1761
+ const result2 = await executeCommandOnce(fullCommand, { ...options, timeout });
1775
1762
  if (result2.success) {
1776
1763
  return result2;
1777
1764
  }
1778
- const { missing, toolName } = isCommandNotFound(result2.error || "", result2.output);
1779
- if (!missing || !toolName || attempt >= maxRetries) {
1765
+ const missing = isCommandNotFound(result2.error || "", result2.output);
1766
+ if (!missing || attempt >= maxRetries) {
1780
1767
  return result2;
1781
1768
  }
1782
1769
  lastResult = result2;
@@ -1803,7 +1790,7 @@ async function runCommand(command, args = [], options = {}) {
1803
1790
  }
1804
1791
  return lastResult;
1805
1792
  }
1806
- async function executeCommandOnce(command, args = [], options = {}) {
1793
+ async function executeCommandOnce(command, options = {}) {
1807
1794
  return new Promise((resolve) => {
1808
1795
  const timeout = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
1809
1796
  globalEventEmitter?.({
@@ -1934,10 +1921,10 @@ function createTempFile(suffix = "") {
1934
1921
  return join2(tmpdir(), generateTempFilename(suffix));
1935
1922
  }
1936
1923
 
1937
- // src/engine/process-cleanup.ts
1924
+ // src/engine/process/process-cleanup.ts
1938
1925
  import { unlinkSync } from "fs";
1939
1926
 
1940
- // src/engine/process-tree.ts
1927
+ // src/engine/process/process-tree.ts
1941
1928
  import { execSync } from "child_process";
1942
1929
  function discoverChildPids(parentPid) {
1943
1930
  try {
@@ -2021,7 +2008,7 @@ function killProcessTreeSync(pid, childPids) {
2021
2008
  }
2022
2009
  }
2023
2010
 
2024
- // src/engine/process-cleanup.ts
2011
+ // src/engine/process/process-cleanup.ts
2025
2012
  function syncCleanupAllProcesses(processMap) {
2026
2013
  for (const [, proc] of processMap) {
2027
2014
  if (!proc.hasExited) {
@@ -2057,7 +2044,7 @@ function registerExitHandlers(processMap) {
2057
2044
  });
2058
2045
  }
2059
2046
 
2060
- // src/engine/process-detector.ts
2047
+ // src/engine/process/process-detector.ts
2061
2048
  function detectProcessRole(command) {
2062
2049
  const tags = [];
2063
2050
  let port;
@@ -2110,7 +2097,7 @@ function detectConnection(stdout) {
2110
2097
  return DETECTION_PATTERNS.CONNECTION.some((p) => p.test(stdout));
2111
2098
  }
2112
2099
 
2113
- // src/engine/process-manager.ts
2100
+ // src/engine/process/process-manager.ts
2114
2101
  var backgroundProcesses = /* @__PURE__ */ new Map();
2115
2102
  var cleanupDone = false;
2116
2103
  registerExitHandlers(backgroundProcesses);
@@ -3666,6 +3653,22 @@ var SharedState = class {
3666
3653
  this.data.missionChecklist.push({ id, text, isCompleted: false });
3667
3654
  }
3668
3655
  }
3656
+ /**
3657
+ * Restore mission checklist from persistence (direct injection).
3658
+ * WHY: avoids the add-then-update roundtrip that loadState previously needed.
3659
+ */
3660
+ restoreMissionChecklist(items) {
3661
+ for (const item of items) {
3662
+ this.data.missionChecklist.push({ ...item });
3663
+ }
3664
+ }
3665
+ /**
3666
+ * Restore a todo item from persistence (direct injection with original status).
3667
+ * WHY: addTodo always creates PENDING; this preserves the saved status.
3668
+ */
3669
+ restoreTodoItem(item) {
3670
+ this.data.todo.push({ ...item });
3671
+ }
3669
3672
  // --- Engagement & Scope ---
3670
3673
  setEngagement(engagement) {
3671
3674
  this.data.engagement = engagement;
@@ -3708,26 +3711,6 @@ var SharedState = class {
3708
3711
  getTargets() {
3709
3712
  return this.data.targets;
3710
3713
  }
3711
- addServiceFingerprint(ip, fingerprint) {
3712
- const target = this.getTarget(ip);
3713
- if (!target) return;
3714
- target.services = target.services || [];
3715
- target.services.push(fingerprint);
3716
- target.primaryCategory = this.calculatePrimaryCategory(target.services);
3717
- }
3718
- calculatePrimaryCategory(services) {
3719
- const counts = {};
3720
- let winner = SERVICE_CATEGORIES.NETWORK;
3721
- let max = 0;
3722
- for (const s of services) {
3723
- counts[s.category] = (counts[s.category] || 0) + 1;
3724
- if (counts[s.category] > max) {
3725
- max = counts[s.category];
3726
- winner = s.category;
3727
- }
3728
- }
3729
- return winner;
3730
- }
3731
3714
  // --- Findings & Loot ---
3732
3715
  addFinding(finding) {
3733
3716
  this.data.findings.push(finding);
@@ -3738,14 +3721,6 @@ var SharedState = class {
3738
3721
  getFindingsBySeverity(severity) {
3739
3722
  return this.data.findings.filter((f) => f.severity === severity);
3740
3723
  }
3741
- /** Returns findings with confidence >= threshold (default: CONFIRMED = 80) */
3742
- getFindingsByConfidence(threshold = CONFIDENCE_THRESHOLDS.CONFIRMED) {
3743
- return this.data.findings.filter((f) => f.confidence >= threshold);
3744
- }
3745
- /** True if confidence >= CONFIRMED (80) */
3746
- isConfirmedFinding(finding) {
3747
- return finding.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED;
3748
- }
3749
3724
  addLoot(loot) {
3750
3725
  this.data.loot.push(loot);
3751
3726
  }
@@ -5460,7 +5435,8 @@ var ENV_KEYS = {
5460
5435
  THINKING: "PENTEST_THINKING",
5461
5436
  THINKING_BUDGET: "PENTEST_THINKING_BUDGET"
5462
5437
  };
5463
- var DEFAULT_SEARCH_API_URL = "https://open.bigmodel.cn/api/paas/v4/tools/web-search-pro";
5438
+ var DEFAULT_SEARCH_API_URL = "https://api.search.brave.com/res/v1/web/search";
5439
+ var DEFAULT_MODEL = "glm-4.7";
5464
5440
  function getApiKey() {
5465
5441
  return process.env[ENV_KEYS.API_KEY] || "";
5466
5442
  }
@@ -5479,6 +5455,10 @@ function getSearchApiKey() {
5479
5455
  function getSearchApiUrl() {
5480
5456
  return process.env[ENV_KEYS.SEARCH_API_URL] || DEFAULT_SEARCH_API_URL;
5481
5457
  }
5458
+ function isZaiProvider() {
5459
+ const baseUrl = getBaseUrl() || "";
5460
+ return baseUrl.includes("z.ai");
5461
+ }
5482
5462
  function isThinkingEnabled() {
5483
5463
  return process.env[ENV_KEYS.THINKING] === "true";
5484
5464
  }
@@ -5486,8 +5466,26 @@ function getThinkingBudget() {
5486
5466
  const val = parseInt(process.env[ENV_KEYS.THINKING_BUDGET] || "", 10);
5487
5467
  return isNaN(val) ? 8e3 : Math.max(1024, val);
5488
5468
  }
5489
- function isBrowserHeadless() {
5490
- return true;
5469
+ function validateRequiredConfig() {
5470
+ const errors = [];
5471
+ if (!getApiKey()) {
5472
+ errors.push(
5473
+ `[config] PENTEST_API_KEY is required.
5474
+ Export it before running:
5475
+ export PENTEST_API_KEY=your_api_key
5476
+ For z.ai: get your key at https://api.z.ai`
5477
+ );
5478
+ }
5479
+ const budgetRaw = process.env[ENV_KEYS.THINKING_BUDGET];
5480
+ if (budgetRaw !== void 0 && budgetRaw !== "") {
5481
+ const parsed = parseInt(budgetRaw, 10);
5482
+ if (isNaN(parsed) || parsed < 1024) {
5483
+ errors.push(
5484
+ `[config] PENTEST_THINKING_BUDGET must be an integer \u2265 1024 (got: "${budgetRaw}")`
5485
+ );
5486
+ }
5487
+ }
5488
+ return errors;
5491
5489
  }
5492
5490
 
5493
5491
  // src/shared/constants/search-api.const.ts
@@ -5582,10 +5580,8 @@ function getPlaywrightPath() {
5582
5580
  }
5583
5581
  async function checkPlaywright() {
5584
5582
  try {
5585
- const result2 = await new Promise((resolve) => {
5583
+ return await new Promise((resolve) => {
5586
5584
  const child = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_ACTION.VERSION], { shell: true });
5587
- let stdout = "";
5588
- child.stdout.on("data", (data) => stdout += data);
5589
5585
  child.on("close", (code) => {
5590
5586
  if (code === 0) {
5591
5587
  const browserCheck = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_BROWSER.CHROMIUM, PLAYWRIGHT_ACTION.HELP], { shell: true });
@@ -5599,7 +5595,6 @@ async function checkPlaywright() {
5599
5595
  });
5600
5596
  child.on("error", () => resolve({ installed: false, browserInstalled: false }));
5601
5597
  });
5602
- return result2;
5603
5598
  } catch {
5604
5599
  return { installed: false, browserInstalled: false };
5605
5600
  }
@@ -5710,13 +5705,12 @@ function buildBrowseScript(url, options, screenshotPath) {
5710
5705
  const safeExtraHeaders = JSON.stringify(options.extraHeaders || {});
5711
5706
  const playwrightPath = getPlaywrightPath();
5712
5707
  const safePlaywrightPath = safeJsString(playwrightPath);
5713
- const headlessMode = isBrowserHeadless();
5714
5708
  return `
5715
5709
  const { chromium } = require(${safePlaywrightPath});
5716
5710
 
5717
5711
  (async () => {
5718
5712
  const browser = await chromium.launch({
5719
- headless: ${headlessMode},
5713
+ headless: true,
5720
5714
  args: ['${PLAYWRIGHT_ARG.NO_SANDBOX}', '${PLAYWRIGHT_ARG.DISABLE_SETUID_SANDBOX}']
5721
5715
  });
5722
5716
 
@@ -5860,7 +5854,7 @@ async function browseUrl(url, options = {}) {
5860
5854
  };
5861
5855
  }
5862
5856
  }
5863
- const screenshotPath = browserOptions.screenshot ? join6(join6(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME), `screenshot-${Date.now()}.png`) : void 0;
5857
+ const screenshotPath = browserOptions.screenshot ? join6(tmpdir3(), BROWSER_PATHS.TEMP_DIR_NAME, `screenshot-${Date.now()}.png`) : void 0;
5864
5858
  const script = buildBrowseScript(url, browserOptions, screenshotPath);
5865
5859
  const result2 = await runPlaywrightScript(script, browserOptions.timeout, "browse");
5866
5860
  if (!result2.success) {
@@ -5899,12 +5893,11 @@ async function fillAndSubmitForm(url, formData, options = {}) {
5899
5893
  const safeFormData = JSON.stringify(formData);
5900
5894
  const playwrightPath = getPlaywrightPath();
5901
5895
  const safePlaywrightPath = safeJsString(playwrightPath);
5902
- const headlessMode = process.env.HEADLESS !== "false";
5903
5896
  const script = `
5904
5897
  const { chromium } = require(${safePlaywrightPath});
5905
5898
 
5906
5899
  (async () => {
5907
- const browser = await chromium.launch({ headless: ${headlessMode} });
5900
+ const browser = await chromium.launch({ headless: true });
5908
5901
  const page = await browser.newPage();
5909
5902
 
5910
5903
  try {
@@ -5989,12 +5982,16 @@ var SEARCH_TIMEOUT_MS = 15e3;
5989
5982
  function getErrorMessage(error) {
5990
5983
  return error instanceof Error ? error.message : String(error);
5991
5984
  }
5985
+ function generateRequestId() {
5986
+ return crypto.randomUUID();
5987
+ }
5992
5988
  async function searchWithGLM(query, apiKey, apiUrl) {
5993
5989
  debugLog("search", "GLM request START", { apiUrl, query });
5994
5990
  const requestBody = {
5995
- model: "web-search-pro",
5996
- messages: [{ role: LLM_ROLES.USER, content: query }],
5997
- stream: false
5991
+ request_id: generateRequestId(),
5992
+ tool: "web-search-pro",
5993
+ stream: false,
5994
+ messages: [{ role: LLM_ROLES.USER, content: query }]
5998
5995
  };
5999
5996
  debugLog("search", "GLM request body", requestBody);
6000
5997
  let response;
@@ -6039,11 +6036,7 @@ async function searchWithGLM(query, apiKey, apiUrl) {
6039
6036
  debugLog("search", "GLM has tool_calls", { count: data.choices[0].message.tool_calls.length });
6040
6037
  for (const tc of data.choices[0].message.tool_calls) {
6041
6038
  if (tc.function?.arguments) {
6042
- try {
6043
- const args = JSON.parse(tc.function.arguments);
6044
- results += JSON.stringify(args, null, 2) + "\n";
6045
- } catch {
6046
- }
6039
+ results += tc.function.arguments + "\n";
6047
6040
  }
6048
6041
  }
6049
6042
  }
@@ -6245,55 +6238,40 @@ async function parseNmap(xmlPath) {
6245
6238
  }
6246
6239
  }
6247
6240
  async function searchCVE(service, version) {
6241
+ const query = version ? `${service} ${version}` : service;
6248
6242
  try {
6249
- return searchExploitDB(service, version);
6250
- } catch (error) {
6243
+ const output = execFileSync("searchsploit", [query, "--color", "never"], {
6244
+ encoding: "utf-8",
6245
+ stdio: ["ignore", "pipe", "pipe"],
6246
+ timeout: SEARCH_LIMIT.TIMEOUT_MS
6247
+ });
6248
+ const lines = output.trim().split("\n").slice(0, SEARCH_LIMIT.MAX_OUTPUT_LINES);
6251
6249
  return {
6252
- success: false,
6253
- output: "",
6254
- error: getErrorMessage2(error)
6250
+ success: true,
6251
+ output: lines.join("\n") || `No exploits found for ${query}`
6255
6252
  };
6256
- }
6257
- }
6258
- async function searchExploitDB(service, version) {
6259
- try {
6260
- const query = version ? `${service} ${version}` : service;
6261
- try {
6262
- const output = execFileSync("searchsploit", [query, "--color", "never"], {
6263
- encoding: "utf-8",
6264
- stdio: ["ignore", "pipe", "pipe"],
6265
- timeout: SEARCH_LIMIT.TIMEOUT_MS
6266
- });
6267
- const lines = output.trim().split("\n").slice(0, SEARCH_LIMIT.MAX_OUTPUT_LINES);
6268
- return {
6269
- success: true,
6270
- output: lines.join("\n") || `No exploits found for ${query}`
6271
- };
6272
- } catch (e) {
6273
- const execError = e;
6274
- const stderr = String(execError.stderr || "");
6275
- const stdout = String(execError.stdout || "");
6276
- if (stderr.includes("No results")) {
6277
- return {
6278
- success: true,
6279
- output: `No exploits found for ${query}`
6280
- };
6281
- }
6253
+ } catch (e) {
6254
+ const execError = e;
6255
+ const stderr = String(execError.stderr || "");
6256
+ const stdout = String(execError.stdout || "");
6257
+ if (stderr.includes("No results")) {
6282
6258
  return {
6283
6259
  success: true,
6284
- output: stdout || `No exploits found for ${query}`
6260
+ output: `No exploits found for ${query}`
6285
6261
  };
6286
6262
  }
6287
- } catch (error) {
6288
6263
  return {
6289
- success: false,
6290
- output: "",
6291
- error: getErrorMessage2(error)
6264
+ success: true,
6265
+ output: stdout || `No exploits found for ${query}`
6292
6266
  };
6293
6267
  }
6294
6268
  }
6295
- async function webSearch(query, _engine) {
6269
+ async function webSearch(query) {
6296
6270
  debugLog("search", "webSearch START", { query });
6271
+ if (isZaiProvider()) {
6272
+ debugLog("search", "Using z.ai LLM-native web search (web_search_20250305)");
6273
+ return await searchWithZai(query);
6274
+ }
6297
6275
  const apiKey = getSearchApiKey();
6298
6276
  const apiUrl = getSearchApiUrl();
6299
6277
  debugLog("search", "Search API config", {
@@ -6321,7 +6299,7 @@ async function webSearch(query, _engine) {
6321
6299
  };
6322
6300
  }
6323
6301
  try {
6324
- if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU) || apiUrl.includes(SEARCH_URL_PATTERN.Z_AI)) {
6302
+ if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU)) {
6325
6303
  debugLog("search", "Using GLM search");
6326
6304
  return await searchWithGLM(query, apiKey, apiUrl);
6327
6305
  } else if (apiUrl.includes(SEARCH_URL_PATTERN.BRAVE)) {
@@ -6343,6 +6321,111 @@ async function webSearch(query, _engine) {
6343
6321
  };
6344
6322
  }
6345
6323
  }
6324
+ var ZAI_SEARCH = {
6325
+ TIMEOUT_MS: 2e4,
6326
+ MAX_TOKENS: 1024,
6327
+ MAX_USES: 3,
6328
+ TOOL_TYPE: "web_search_20250305",
6329
+ TOOL_NAME: "web_search",
6330
+ ANTHROPIC_VERSION: "2023-06-01",
6331
+ // §25-1 Retry constants
6332
+ MAX_RETRIES: 2,
6333
+ RETRY_BASE_MS: 500
6334
+ };
6335
+ function isTransientHttpError(status) {
6336
+ return status === 429 || status >= 500;
6337
+ }
6338
+ async function searchWithZai(query) {
6339
+ const apiKey = getApiKey();
6340
+ const baseUrl = (getBaseUrl() || "https://api.z.ai/api/anthropic").replace(/\/+$/, "");
6341
+ const url = `${baseUrl}/v1/messages`;
6342
+ const requestBody = JSON.stringify({
6343
+ model: getModel() || DEFAULT_MODEL,
6344
+ max_tokens: ZAI_SEARCH.MAX_TOKENS,
6345
+ tools: [{ type: ZAI_SEARCH.TOOL_TYPE, name: ZAI_SEARCH.TOOL_NAME, max_uses: ZAI_SEARCH.MAX_USES }],
6346
+ messages: [{ role: "user", content: `Search the web for: ${query}` }]
6347
+ });
6348
+ let lastError = "";
6349
+ for (let attempt = 0; attempt <= ZAI_SEARCH.MAX_RETRIES; attempt++) {
6350
+ if (attempt > 0) {
6351
+ const baseWait = ZAI_SEARCH.RETRY_BASE_MS * Math.pow(2, attempt - 1);
6352
+ const jitter = baseWait * 0.25 * (Math.random() * 2 - 1);
6353
+ await new Promise((r) => setTimeout(r, Math.round(baseWait + jitter)));
6354
+ debugLog("search", `z.ai web search RETRY attempt=${attempt}`);
6355
+ }
6356
+ let response;
6357
+ try {
6358
+ response = await fetch(url, {
6359
+ method: "POST",
6360
+ headers: {
6361
+ "Content-Type": "application/json",
6362
+ "x-api-key": apiKey,
6363
+ "anthropic-version": ZAI_SEARCH.ANTHROPIC_VERSION
6364
+ },
6365
+ body: requestBody,
6366
+ signal: AbortSignal.timeout(ZAI_SEARCH.TIMEOUT_MS)
6367
+ });
6368
+ } catch (err) {
6369
+ lastError = err instanceof Error ? err.message : String(err);
6370
+ debugLog("search", "z.ai web search fetch FAILED", { attempt, error: lastError });
6371
+ continue;
6372
+ }
6373
+ if (response.ok) {
6374
+ let data;
6375
+ try {
6376
+ data = await response.json();
6377
+ } catch (err) {
6378
+ return { success: false, output: "", error: `z.ai web search invalid JSON: ${err instanceof Error ? err.message : String(err)}` };
6379
+ }
6380
+ const lines = [];
6381
+ for (const block of data.content || []) {
6382
+ if (block.type === "text" && block.text) {
6383
+ lines.push(block.text);
6384
+ } else if (block.type === "server_tool_use" && block.input?.results) {
6385
+ const refs = block.input.results.filter((r) => !!r.url && !!r.title).map((r, i) => `[${i + 1}] ${r.title}
6386
+ ${r.url}`);
6387
+ if (refs.length > 0) {
6388
+ lines.push("\n--- Search Sources ---\n" + refs.join("\n"));
6389
+ }
6390
+ }
6391
+ }
6392
+ const output = lines.join("\n").trim();
6393
+ debugLog("search", "z.ai web search COMPLETE", { attempt, outputLength: output.length });
6394
+ return { success: true, output: output || `No results found for: ${query}` };
6395
+ }
6396
+ let errorText = "";
6397
+ try {
6398
+ errorText = await response.text();
6399
+ } catch {
6400
+ }
6401
+ lastError = `z.ai web search API error ${response.status}: ${errorText.slice(0, 500)}`;
6402
+ debugLog("search", "z.ai web search ERROR", { attempt, status: response.status, error: errorText });
6403
+ if (!isTransientHttpError(response.status)) {
6404
+ break;
6405
+ }
6406
+ }
6407
+ return { success: false, output: "", error: lastError };
6408
+ }
6409
+ async function extractURLs(text) {
6410
+ try {
6411
+ const urlRegex = /https?:\/\/[^\s<>"{}|\\^`\[\]]+/g;
6412
+ const urls = text.match(urlRegex) || [];
6413
+ const uniqueURLs = [...new Set(urls)];
6414
+ return {
6415
+ success: true,
6416
+ output: JSON.stringify({
6417
+ count: uniqueURLs.length,
6418
+ urls: uniqueURLs
6419
+ }, null, 2)
6420
+ };
6421
+ } catch (error) {
6422
+ return {
6423
+ success: false,
6424
+ output: "",
6425
+ error: getErrorMessage2(error)
6426
+ };
6427
+ }
6428
+ }
6346
6429
 
6347
6430
  // src/shared/utils/owasp-knowledge.ts
6348
6431
  var OWASP_2017 = {
@@ -6543,8 +6626,83 @@ Use 'get_owasp_knowledge' with edition="2023" or similar to see specific details
6543
6626
  `.trim();
6544
6627
  }
6545
6628
 
6629
+ // src/shared/utils/domain-tools-factory.ts
6630
+ function param(params, name, defaultValue) {
6631
+ const value = params[name];
6632
+ if (value === void 0 || value === null) {
6633
+ if (defaultValue !== void 0) return defaultValue;
6634
+ throw new Error(`Missing required parameter: ${name}`);
6635
+ }
6636
+ return String(value);
6637
+ }
6638
+ function paramNumber(params, name, defaultValue) {
6639
+ const value = params[name];
6640
+ if (value === void 0 || value === null) {
6641
+ if (defaultValue !== void 0) return defaultValue;
6642
+ throw new Error(`Missing required parameter: ${name}`);
6643
+ }
6644
+ return Number(value);
6645
+ }
6646
+ function ensureUrlProtocol(target) {
6647
+ if (/^https?:\/\//i.test(target)) return target;
6648
+ return `http://${target}`;
6649
+ }
6650
+ function createCommandTool(def) {
6651
+ const parameters = {};
6652
+ for (const paramName of def.params) {
6653
+ parameters[paramName] = { type: "string", description: paramName };
6654
+ }
6655
+ if (def.optionalParams) {
6656
+ for (const paramName of Object.keys(def.optionalParams)) {
6657
+ parameters[paramName] = { type: "string", description: `${paramName} (optional)` };
6658
+ }
6659
+ }
6660
+ return {
6661
+ name: def.name,
6662
+ description: def.description,
6663
+ parameters,
6664
+ required: def.params,
6665
+ execute: async (params) => {
6666
+ const resolvedArgs = def.args.map((arg) => {
6667
+ const match = arg.match(/^\{(\w+)\}$/);
6668
+ if (match) {
6669
+ const paramName = match[1];
6670
+ let value;
6671
+ if (def.optionalParams && def.optionalParams[paramName] !== void 0) {
6672
+ value = param(params, paramName, String(def.optionalParams[paramName]));
6673
+ } else {
6674
+ value = param(params, paramName);
6675
+ }
6676
+ if (def.requiresUrl && paramName === "target") {
6677
+ value = ensureUrlProtocol(value);
6678
+ }
6679
+ return value;
6680
+ }
6681
+ return arg;
6682
+ });
6683
+ return await runCommand(def.cmd, resolvedArgs);
6684
+ }
6685
+ };
6686
+ }
6687
+ function createTool(name, description, parameters, required, execute) {
6688
+ return { name, description, parameters, required, execute };
6689
+ }
6690
+ function createDomain(def) {
6691
+ const tools = def.commands?.map(createCommandTool) ?? [];
6692
+ const config = {
6693
+ name: def.category,
6694
+ description: def.description,
6695
+ tools,
6696
+ dangerLevel: def.dangerLevel,
6697
+ defaultApproval: def.defaultApproval,
6698
+ commonPorts: def.commonPorts,
6699
+ commonServices: def.commonServices
6700
+ };
6701
+ return { tools, config };
6702
+ }
6703
+
6546
6704
  // src/engine/tools/pentest-intel-tools.ts
6547
- var createIntelTools = (_state) => [
6705
+ var createIntelTools = () => [
6548
6706
  {
6549
6707
  name: TOOL_NAMES.PARSE_NMAP,
6550
6708
  description: "Parse nmap XML output to structured JSON",
@@ -6630,7 +6788,8 @@ Can extract forms and inputs for security testing.`,
6630
6788
  },
6631
6789
  required: ["url"],
6632
6790
  execute: async (p) => {
6633
- const result2 = await browseUrl(p.url, {
6791
+ const url = ensureUrlProtocol(p.url);
6792
+ const result2 = await browseUrl(url, {
6634
6793
  extractForms: p.extract_forms,
6635
6794
  extractLinks: p.extract_links,
6636
6795
  screenshot: p.screenshot,
@@ -6660,7 +6819,8 @@ Can extract forms and inputs for security testing.`,
6660
6819
  },
6661
6820
  required: ["url", "fields"],
6662
6821
  execute: async (p) => {
6663
- const result2 = await fillAndSubmitForm(p.url, p.fields);
6822
+ const url = ensureUrlProtocol(p.url);
6823
+ const result2 = await fillAndSubmitForm(url, p.fields);
6664
6824
  return {
6665
6825
  success: result2.success,
6666
6826
  output: result2.output,
@@ -6803,6 +6963,19 @@ For CVEs not in this list, use web_search to find them.`
6803
6963
  output: `CVE ${cveId} not in local database. Use web_search({ query: "${cveId} exploit POC" }) to find more information online.`
6804
6964
  };
6805
6965
  }
6966
+ },
6967
+ {
6968
+ name: TOOL_NAMES.EXTRACT_URLS,
6969
+ description: `Extract all URLs from a block of text.
6970
+ Use this to:
6971
+ - Pull links from command output, HTML, or tool results
6972
+ - Discover subdomains and endpoints from scan results
6973
+ - Find API endpoints buried in verbose output`,
6974
+ parameters: {
6975
+ text: { type: "string", description: "Text to extract URLs from" }
6976
+ },
6977
+ required: ["text"],
6978
+ execute: async (p) => extractURLs(p.text)
6806
6979
  }
6807
6980
  ];
6808
6981
 
@@ -7154,7 +7327,9 @@ function getContextRecommendations(context, variantCount) {
7154
7327
  }
7155
7328
 
7156
7329
  // src/engine/tools/pentest-attack-tools.ts
7157
- var createAttackTools = (_state) => [
7330
+ import { existsSync as existsSync6, statSync, readdirSync } from "fs";
7331
+ import { join as join7 } from "path";
7332
+ var createAttackTools = () => [
7158
7333
  {
7159
7334
  name: TOOL_NAMES.HASH_CRACK,
7160
7335
  description: `Crack password hashes using hashcat or john.
@@ -7273,8 +7448,6 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
7273
7448
  }
7274
7449
  },
7275
7450
  execute: async (p) => {
7276
- const { existsSync: existsSync12, statSync: statSync3, readdirSync: readdirSync4 } = await import("fs");
7277
- const { join: join13 } = await import("path");
7278
7451
  const category = p.category || "";
7279
7452
  const search = p.search || "";
7280
7453
  const minSize = p.min_size || 0;
@@ -7305,12 +7478,12 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
7305
7478
  const q = search.toLowerCase();
7306
7479
  return fileName.toLowerCase().includes(q) || filePath.toLowerCase().includes(q);
7307
7480
  };
7308
- const results = ["# Available Wordlists on System\\n"];
7481
+ const results = ["# Available Wordlists on System\n"];
7309
7482
  let totalCount = 0;
7310
7483
  const processFile = (fullPath, fileName) => {
7311
7484
  const ext = fileName.split(".").pop()?.toLowerCase();
7312
7485
  if (!WORDLIST_EXTENSIONS.has(ext || "")) return;
7313
- const stats = statSync3(fullPath);
7486
+ const stats = statSync(fullPath);
7314
7487
  if (stats.size < minSize) return;
7315
7488
  if (!matchesCategory(fullPath)) return;
7316
7489
  if (!matchesSearch(fullPath, fileName)) return;
@@ -7320,16 +7493,16 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
7320
7493
  results.push("");
7321
7494
  };
7322
7495
  const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
7323
- if (depth > maxDepth || !existsSync12(dirPath)) return;
7496
+ if (depth > maxDepth || !existsSync6(dirPath)) return;
7324
7497
  let entries;
7325
7498
  try {
7326
- entries = readdirSync4(dirPath, { withFileTypes: true });
7499
+ entries = readdirSync(dirPath, { withFileTypes: true });
7327
7500
  } catch {
7328
7501
  return;
7329
7502
  }
7330
7503
  for (const entry of entries) {
7331
7504
  if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
7332
- const fullPath = join13(dirPath, entry.name);
7505
+ const fullPath = join7(dirPath, entry.name);
7333
7506
  if (entry.isDirectory()) {
7334
7507
  scanDir(fullPath, maxDepth, depth + 1);
7335
7508
  continue;
@@ -7342,19 +7515,26 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
7342
7515
  }
7343
7516
  };
7344
7517
  for (const basePath of scanPaths) {
7345
- results.push(`\\n## ${basePath}\\n`);
7518
+ results.push(`
7519
+ ## ${basePath}
7520
+ `);
7346
7521
  scanDir(basePath);
7347
7522
  }
7348
7523
  if (totalCount === 0) {
7349
- results.push(`\\nNo wordlists found matching your criteria.`);
7350
- results.push(`\\nTips:`);
7524
+ results.push(`
7525
+ No wordlists found matching your criteria.`);
7526
+ results.push(`
7527
+ Tips:`);
7351
7528
  results.push(`- Try without filters to see all available wordlists`);
7352
7529
  results.push(`- Ensure seclists/wordlists packages are installed`);
7353
7530
  results.push(`- Common locations: /usr/share/seclists/, /usr/share/wordlists/`);
7354
7531
  }
7355
- results.push(`\\n---`);
7356
- results.push(`\\nTotal wordlists found: ${totalCount}`);
7357
- results.push(`\\n## Commonly Used Wordlists (check if available)`);
7532
+ results.push(`
7533
+ ---`);
7534
+ results.push(`
7535
+ Total wordlists found: ${totalCount}`);
7536
+ results.push(`
7537
+ ## Commonly Used Wordlists (check if available)`);
7358
7538
  results.push(`- RockYou: /usr/share/wordlists/rockyou.txt (14M passwords)`);
7359
7539
  results.push(`- Dirb common: /usr/share/wordlists/dirb/common.txt`);
7360
7540
  results.push(`- SecLists passwords: /usr/share/seclists/Passwords/Common-Credentials/`);
@@ -7362,7 +7542,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
7362
7542
  results.push(`- SecLists DNS: /usr/share/seclists/Discovery/DNS/`);
7363
7543
  return {
7364
7544
  success: true,
7365
- output: results.join("\\n")
7545
+ output: results.join("\n")
7366
7546
  };
7367
7547
  }
7368
7548
  }
@@ -7372,8 +7552,8 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
7372
7552
  var createPentestTools = (state, events) => [
7373
7553
  ...createStateTools(state, events),
7374
7554
  ...createTargetTools(state),
7375
- ...createIntelTools(state),
7376
- ...createAttackTools(state)
7555
+ ...createIntelTools(),
7556
+ ...createAttackTools()
7377
7557
  ];
7378
7558
 
7379
7559
  // src/engine/tools/agents.ts
@@ -7531,6 +7711,7 @@ var NETWORK_FILTERS = {
7531
7711
  };
7532
7712
 
7533
7713
  // src/engine/tools/network-attack.ts
7714
+ import { writeFileSync as writeFileSync6 } from "fs";
7534
7715
  var createNetworkAttackTools = () => [
7535
7716
  {
7536
7717
  name: TOOL_NAMES.ARP_SPOOF,
@@ -7706,8 +7887,7 @@ Requires root/sudo privileges.`,
7706
7887
  const iface = p.interface || "";
7707
7888
  const duration = p.duration || NETWORK_CONFIG.DEFAULT_SPOOF_DURATION;
7708
7889
  const hostsFile = createTempFile(FILE_EXTENSIONS.HOSTS);
7709
- const { writeFileSync: writeFileSync10 } = await import("fs");
7710
- writeFileSync10(hostsFile, `${spoofIp} ${domain}
7890
+ writeFileSync6(hostsFile, `${spoofIp} ${domain}
7711
7891
  ${spoofIp} *.${domain}
7712
7892
  `);
7713
7893
  const ifaceFlag = iface ? `-i ${iface}` : "";
@@ -7957,19 +8137,6 @@ var ZombieHunter = class {
7957
8137
  }
7958
8138
  return lines.join("\n");
7959
8139
  }
7960
- /**
7961
- * Get cleanup command suggestions for Orchestrator
7962
- */
7963
- getCleanupCommands(zombies) {
7964
- return zombies.map((z) => `bg_cleanup({ processId: "${z.processId}", killOrphans: true })`);
7965
- }
7966
- /**
7967
- * Check if any critical processes are among the zombies
7968
- * (shells, listeners that might be active shells)
7969
- */
7970
- hasCriticalZombies(zombies) {
7971
- return false;
7972
- }
7973
8140
  };
7974
8141
 
7975
8142
  // src/engine/resource/health-monitor.ts
@@ -8054,28 +8221,6 @@ var HealthMonitor = class {
8054
8221
  if (data.recommendations.length > MAX_RECOMMENDATIONS_FOR_HEALTHY) return HEALTH_STATUS.WARNING;
8055
8222
  return HEALTH_STATUS.HEALTHY;
8056
8223
  }
8057
- /**
8058
- * Generate summary string for Orchestrator context
8059
- */
8060
- generateSummary() {
8061
- const status = this.check();
8062
- const lines = [];
8063
- lines.push(`Health: ${status.overall.toUpperCase()}`);
8064
- lines.push(`Processes: ${status.processes.running}/${status.processes.total} running`);
8065
- if (status.ports.inUse.length > 0) {
8066
- lines.push(`Ports in use: ${status.ports.inUse.join(", ")}`);
8067
- }
8068
- if (status.ports.conflicts.length > 0) {
8069
- lines.push(`Port conflicts: ${status.ports.conflicts.join("; ")}`);
8070
- }
8071
- if (status.recommendations.length > 0) {
8072
- lines.push(`Recommendations:`);
8073
- for (const r of status.recommendations) {
8074
- lines.push(` - ${r}`);
8075
- }
8076
- }
8077
- return lines.join("\n");
8078
- }
8079
8224
  };
8080
8225
 
8081
8226
  // src/engine/tools/resource.ts
@@ -8090,10 +8235,8 @@ var resourceTools = [
8090
8235
  description: `Query the status of all background processes.
8091
8236
  Shows running tasks, port usage, and health status.
8092
8237
  Used by the Orchestrator for resource management.`,
8093
- parameters: {
8094
- type: "object",
8095
- properties: {}
8096
- },
8238
+ parameters: {},
8239
+ // No parameters needed
8097
8240
  async execute() {
8098
8241
  const processes = listBackgroundProcesses();
8099
8242
  const health = healthMonitor.check();
@@ -8145,19 +8288,17 @@ Used by the Orchestrator for resource management.`,
8145
8288
  If processId is specified, terminates that process and its children.
8146
8289
  If killOrphans is true, also cleans up child processes whose parent has died.`,
8147
8290
  parameters: {
8148
- type: "object",
8149
- properties: {
8150
- processId: {
8151
- type: "string",
8152
- description: "Process ID to clean up (optional)"
8153
- },
8154
- killOrphans: {
8155
- type: "boolean",
8156
- description: "Also clean up orphan child processes (default: true)",
8157
- default: true
8158
- }
8291
+ processId: {
8292
+ type: "string",
8293
+ description: "Process ID to clean up (optional)"
8294
+ },
8295
+ killOrphans: {
8296
+ type: "boolean",
8297
+ description: "Also clean up orphan child processes (default: true)",
8298
+ default: true
8159
8299
  }
8160
8300
  },
8301
+ required: [],
8161
8302
  async execute(params) {
8162
8303
  const results = [];
8163
8304
  if (params.processId) {
@@ -8198,14 +8339,27 @@ If killOrphans is true, also cleans up child processes whose parent has died.`,
8198
8339
  name: TOOL_NAMES.HEALTH_CHECK,
8199
8340
  description: `Check system resource health.
8200
8341
  Returns recommendations on process status, port conflicts, long-running tasks, etc.`,
8201
- parameters: {
8202
- type: "object",
8203
- properties: {}
8204
- },
8342
+ parameters: {},
8343
+ // No parameters needed
8205
8344
  async execute() {
8206
8345
  const status = healthMonitor.check();
8207
8346
  const zombies = zombieHunter.scan();
8208
- const output = healthMonitor.generateSummary();
8347
+ const lines = [];
8348
+ lines.push(`Health: ${status.overall.toUpperCase()}`);
8349
+ lines.push(`Processes: ${status.processes.running}/${status.processes.total} running`);
8350
+ if (status.ports.inUse.length > 0) {
8351
+ lines.push(`Ports in use: ${status.ports.inUse.join(", ")}`);
8352
+ }
8353
+ if (status.ports.conflicts.length > 0) {
8354
+ lines.push(`Port conflicts: ${status.ports.conflicts.join("; ")}`);
8355
+ }
8356
+ if (status.recommendations.length > 0) {
8357
+ lines.push(`Recommendations:`);
8358
+ for (const r of status.recommendations) {
8359
+ lines.push(` - ${r}`);
8360
+ }
8361
+ }
8362
+ const output = lines.join("\n");
8209
8363
  if (zombies.length > 0) {
8210
8364
  return result(false, output + "\n\n" + zombieHunter.generateReport(zombies));
8211
8365
  }
@@ -8295,258 +8449,183 @@ var SMB_PORTS = [
8295
8449
  ];
8296
8450
 
8297
8451
  // src/domains/network/tools.ts
8298
- var NETWORK_TOOLS = [
8299
- {
8300
- name: TOOL_NAMES.NMAP_QUICK,
8301
- description: "Quick nmap scan - fast discovery",
8302
- parameters: {
8303
- target: { type: "string", description: "Target IP/CIDR" }
8304
- },
8305
- required: ["target"],
8306
- execute: async (params) => {
8307
- const target = params.target;
8308
- return await runCommand("nmap", ["-Pn", "-T4", "-F", target]);
8309
- }
8310
- },
8311
- {
8312
- name: TOOL_NAMES.NMAP_FULL,
8313
- description: "Full nmap scan - comprehensive",
8314
- parameters: {
8315
- target: { type: "string", description: "Target IP/CIDR" }
8316
- },
8317
- required: ["target"],
8318
- execute: async (params) => {
8319
- const target = params.target;
8320
- return await runCommand("nmap", ["-Pn", "-T4", "-sV", "-O", "-p-", target]);
8321
- }
8322
- },
8323
- {
8324
- name: TOOL_NAMES.RUSTSCAN,
8325
- description: "RustScan - very fast port discovery",
8326
- parameters: {
8327
- target: { type: "string", description: "Target IP" }
8328
- },
8329
- required: ["target"],
8330
- execute: async (params) => {
8331
- const target = params.target;
8332
- return await runCommand("rustscan", ["-a", target, "--range", "1-65535"]);
8333
- }
8334
- }
8335
- ];
8336
- var NETWORK_CONFIG2 = {
8337
- name: SERVICE_CATEGORIES.NETWORK,
8452
+ var { tools: NETWORK_TOOLS, config: NETWORK_CONFIG2 } = createDomain({
8453
+ category: SERVICE_CATEGORIES.NETWORK,
8338
8454
  description: "Network reconnaissance - scanning, OS fingerprinting",
8339
- tools: NETWORK_TOOLS,
8340
8455
  dangerLevel: DANGER_LEVELS.ACTIVE,
8341
8456
  defaultApproval: APPROVAL_LEVELS.CONFIRM,
8342
8457
  commonPorts: [SERVICE_PORTS.FTP, SERVICE_PORTS.SSH, SERVICE_PORTS.HTTP, SERVICE_PORTS.HTTPS, SERVICE_PORTS.SMB, SERVICE_PORTS.RDP, SERVICE_PORTS.HTTP_ALT],
8343
- commonServices: [SERVICES.FTP, SERVICES.SSH, SERVICES.HTTP, SERVICES.HTTPS, SERVICES.SMB]
8344
- };
8345
-
8346
- // src/domains/web/tools.ts
8347
- var WEB_TOOLS = [
8348
- {
8349
- name: TOOL_NAMES.HTTP_FINGERPRINT,
8350
- description: "HTTP fingerprinting - detect WAF, server type",
8351
- parameters: {
8352
- target: { type: "string", description: "Target URL" }
8353
- },
8354
- required: ["target"],
8355
- execute: async (params) => {
8356
- const target = params.target;
8357
- return await runCommand("curl", ["-sI", target]);
8358
- }
8359
- },
8360
- {
8361
- name: TOOL_NAMES.WAF_DETECT,
8362
- description: "WAF detection using wafw00f",
8363
- parameters: {
8364
- target: { type: "string", description: "Target URL" }
8365
- },
8366
- required: ["target"],
8367
- execute: async (params) => {
8368
- const target = params.target;
8369
- return await runCommand("wafw00f", [target]);
8370
- }
8371
- },
8372
- {
8373
- name: TOOL_NAMES.DIRSEARCH,
8374
- description: "Directory bruteforcing",
8375
- parameters: {
8376
- target: { type: "string", description: "Target URL" }
8458
+ commonServices: [SERVICES.FTP, SERVICES.SSH, SERVICES.HTTP, SERVICES.HTTPS, SERVICES.SMB],
8459
+ commands: [
8460
+ {
8461
+ name: TOOL_NAMES.NMAP_QUICK,
8462
+ description: "Quick nmap scan - fast discovery",
8463
+ cmd: "nmap",
8464
+ args: ["-Pn", "-T4", "-F", "{target}"],
8465
+ params: ["target"]
8377
8466
  },
8378
- required: ["target"],
8379
- execute: async (params) => {
8380
- const target = params.target;
8381
- return await runCommand("dirsearch", ["-u", target, "--quiet"]);
8382
- }
8383
- },
8384
- {
8385
- name: TOOL_NAMES.NUCLEI_WEB,
8386
- description: "Nuclei web vulnerability scanner",
8387
- parameters: {
8388
- target: { type: "string", description: "Target URL" },
8389
- severity: { type: "string", description: "Severities to check" }
8467
+ {
8468
+ name: TOOL_NAMES.NMAP_FULL,
8469
+ description: "Full nmap scan - comprehensive",
8470
+ cmd: "nmap",
8471
+ args: ["-Pn", "-T4", "-sV", "-O", "-p-", "{target}"],
8472
+ params: ["target"]
8390
8473
  },
8391
- required: ["target"],
8392
- execute: async (params) => {
8393
- const target = params.target;
8394
- const severity = params.severity || "medium,critical,high";
8395
- return await runCommand("nuclei", ["-u", target, "-s", severity, "-silent"]);
8474
+ {
8475
+ name: TOOL_NAMES.RUSTSCAN,
8476
+ description: "RustScan - very fast port discovery",
8477
+ cmd: "rustscan",
8478
+ args: ["-a", "{target}", "--range", "1-65535"],
8479
+ params: ["target"]
8396
8480
  }
8397
- }
8398
- ];
8399
- var WEB_CONFIG = {
8400
- name: SERVICE_CATEGORIES.WEB,
8481
+ ]
8482
+ });
8483
+
8484
+ // src/domains/web/tools.ts
8485
+ var { tools: WEB_TOOLS, config: WEB_CONFIG } = createDomain({
8486
+ category: SERVICE_CATEGORIES.WEB,
8401
8487
  description: "Web application testing - HTTP/HTTPS, APIs",
8402
- tools: WEB_TOOLS,
8403
8488
  dangerLevel: DANGER_LEVELS.ACTIVE,
8404
8489
  defaultApproval: APPROVAL_LEVELS.CONFIRM,
8405
8490
  commonPorts: [SERVICE_PORTS.HTTP, SERVICE_PORTS.HTTPS, SERVICE_PORTS.HTTP_ALT],
8406
- commonServices: [SERVICES.HTTP, SERVICES.HTTPS]
8407
- };
8491
+ commonServices: [SERVICES.HTTP, SERVICES.HTTPS],
8492
+ commands: [
8493
+ {
8494
+ name: TOOL_NAMES.HTTP_FINGERPRINT,
8495
+ description: "HTTP fingerprinting - detect WAF, server type",
8496
+ cmd: "curl",
8497
+ args: ["-sI", "{target}"],
8498
+ params: ["target"],
8499
+ requiresUrl: true
8500
+ },
8501
+ {
8502
+ name: TOOL_NAMES.WAF_DETECT,
8503
+ description: "WAF detection using wafw00f",
8504
+ cmd: "wafw00f",
8505
+ args: ["{target}"],
8506
+ params: ["target"],
8507
+ requiresUrl: true
8508
+ },
8509
+ {
8510
+ name: TOOL_NAMES.DIRSEARCH,
8511
+ description: "Directory bruteforcing",
8512
+ cmd: "dirsearch",
8513
+ args: ["-u", "{target}", "--quiet"],
8514
+ params: ["target"],
8515
+ requiresUrl: true
8516
+ },
8517
+ {
8518
+ name: TOOL_NAMES.NUCLEI_WEB,
8519
+ description: "Nuclei web vulnerability scanner",
8520
+ cmd: "nuclei",
8521
+ args: ["-u", "{target}", "-s", "{severity}", "-silent"],
8522
+ params: ["target"],
8523
+ optionalParams: { severity: "medium,critical,high" },
8524
+ requiresUrl: true
8525
+ }
8526
+ ]
8527
+ });
8408
8528
 
8409
8529
  // src/domains/database/tools.ts
8410
- var DATABASE_TOOLS = [
8530
+ var SQLMAP_BASIC = createTool(
8531
+ TOOL_NAMES.SQLMAP_BASIC,
8532
+ "SQL injection testing with sqlmap - basic scan",
8533
+ { target: { type: "string", description: "Target URL" } },
8534
+ ["target"],
8535
+ async (params) => runCommand("sqlmap", ["-u", param(params, "target"), "--batch", "--risk=1", "--level=1"])
8536
+ );
8537
+ var SQLMAP_ADVANCED = createTool(
8538
+ TOOL_NAMES.SQLMAP_ADVANCED,
8539
+ "SQL injection with sqlmap - full enumeration",
8540
+ { target: { type: "string", description: "Target URL" } },
8541
+ ["target"],
8542
+ async (params) => runCommand("sqlmap", ["-u", param(params, "target"), "--batch", "--risk=3", "--level=5", "--dbs", "--tables"])
8543
+ );
8544
+ var MYSQL_ENUM = createTool(
8545
+ TOOL_NAMES.MYSQL_ENUM,
8546
+ "MySQL enumeration - version, users, databases",
8411
8547
  {
8412
- name: TOOL_NAMES.SQLMAP_BASIC,
8413
- description: "SQL injection testing with sqlmap - basic scan",
8414
- parameters: {
8415
- target: { type: "string", description: "Target URL" }
8416
- },
8417
- required: ["target"],
8418
- execute: async (params) => {
8419
- const target = params.target;
8420
- return await runCommand("sqlmap", ["-u", target, "--batch", "--risk=1", "--level=1"]);
8421
- }
8548
+ target: { type: "string", description: "Target IP/hostname" },
8549
+ port: { type: "number", description: `Port (default ${SERVICE_PORTS.MYSQL})` }
8422
8550
  },
8551
+ ["target"],
8552
+ async (params) => runCommand("mysql", ["-h", param(params, "target"), "-P", String(paramNumber(params, "port", SERVICE_PORTS.MYSQL)), "-e", "SELECT VERSION(), USER(), DATABASE();"])
8553
+ );
8554
+ var POSTGRES_ENUM = createTool(
8555
+ TOOL_NAMES.POSTGRES_ENUM,
8556
+ "PostgreSQL enumeration",
8557
+ { target: { type: "string", description: "Target IP" } },
8558
+ ["target"],
8559
+ async (params) => runCommand("psql", ["-h", param(params, "target"), "-U", "postgres", "-c", "SELECT version(); SELECT datname FROM pg_database;"])
8560
+ );
8561
+ var REDIS_ENUM = createTool(
8562
+ TOOL_NAMES.REDIS_ENUM,
8563
+ "Redis enumeration",
8423
8564
  {
8424
- name: TOOL_NAMES.SQLMAP_ADVANCED,
8425
- description: "SQL injection with sqlmap - full enumeration",
8426
- parameters: {
8427
- target: { type: "string", description: "Target URL" }
8428
- },
8429
- required: ["target"],
8430
- execute: async (params) => {
8431
- const target = params.target;
8432
- return await runCommand("sqlmap", ["-u", target, "--batch", "--risk=3", "--level=5", "--dbs", "--tables"]);
8433
- }
8565
+ target: { type: "string", description: "Target IP" },
8566
+ port: { type: "number", description: `Port (default ${SERVICE_PORTS.REDIS})` }
8434
8567
  },
8568
+ ["target"],
8569
+ async (params) => runCommand("redis-cli", ["-h", param(params, "target"), "-p", String(paramNumber(params, "port", SERVICE_PORTS.REDIS)), "INFO"])
8570
+ );
8571
+ var DB_BRUTE = createTool(
8572
+ TOOL_NAMES.DB_BRUTE,
8573
+ "Brute force database credentials",
8435
8574
  {
8436
- name: TOOL_NAMES.MYSQL_ENUM,
8437
- description: "MySQL enumeration - version, users, databases",
8438
- parameters: {
8439
- target: { type: "string", description: "Target IP/hostname" },
8440
- port: { type: "string", description: `Port (default ${SERVICE_PORTS.MYSQL})` }
8441
- },
8442
- required: ["target"],
8443
- execute: async (params) => {
8444
- const target = params.target;
8445
- const port = params.port || String(SERVICE_PORTS.MYSQL);
8446
- return await runCommand("mysql", ["-h", target, "-P", port, "-e", "SELECT VERSION(), USER(), DATABASE();"]);
8447
- }
8575
+ target: { type: "string", description: "Target IP" },
8576
+ service: { type: "string", description: "Service (mysql, postgres, etc.)" }
8448
8577
  },
8578
+ ["target", "service"],
8579
+ async (params) => runCommand("hydra", [
8580
+ "-L",
8581
+ WORDLISTS.USERNAMES,
8582
+ "-P",
8583
+ WORDLISTS.COMMON_PASSWORDS,
8584
+ param(params, "target"),
8585
+ param(params, "service")
8586
+ ])
8587
+ );
8588
+ var DATABASE_TOOLS = [SQLMAP_BASIC, SQLMAP_ADVANCED, MYSQL_ENUM, POSTGRES_ENUM, REDIS_ENUM, DB_BRUTE];
8589
+ var DATABASE_CONFIG = {
8590
+ name: SERVICE_CATEGORIES.DATABASE,
8591
+ description: "Database exploitation - SQL injection, credential extraction",
8592
+ tools: DATABASE_TOOLS,
8593
+ dangerLevel: DANGER_LEVELS.EXPLOIT,
8594
+ defaultApproval: APPROVAL_LEVELS.REVIEW,
8595
+ commonPorts: [SERVICE_PORTS.MSSQL, SERVICE_PORTS.MYSQL, SERVICE_PORTS.POSTGRESQL, SERVICE_PORTS.REDIS, SERVICE_PORTS.MONGODB],
8596
+ commonServices: [SERVICES.MYSQL, SERVICES.POSTGRES, SERVICES.REDIS, SERVICES.MONGODB]
8597
+ };
8598
+
8599
+ // src/domains/ad/tools.ts
8600
+ var BLOODHOUND_COLLECT = createTool(
8601
+ TOOL_NAMES.BLOODHOUND_COLLECT,
8602
+ "BloodHound data collection",
8449
8603
  {
8450
- name: TOOL_NAMES.POSTGRES_ENUM,
8451
- description: "PostgreSQL enumeration",
8452
- parameters: {
8453
- target: { type: "string", description: "Target IP" }
8454
- },
8455
- required: ["target"],
8456
- execute: async (params) => {
8457
- const target = params.target;
8458
- return await runCommand("psql", ["-h", target, "-U", "postgres", "-c", "SELECT version(); SELECT datname FROM pg_database;"]);
8459
- }
8604
+ target: { type: "string", description: "Target DC IP" },
8605
+ domain: { type: "string", description: "Domain name" }
8460
8606
  },
8607
+ ["target"],
8608
+ async (params) => runCommand("bloodhound-python", ["-c", "All", "-d", param(params, "domain", "DOMAIN"), param(params, "target"), "--zip"])
8609
+ );
8610
+ var KERBEROAST = createTool(
8611
+ TOOL_NAMES.KERBEROAST,
8612
+ "Kerberoasting attack",
8461
8613
  {
8462
- name: TOOL_NAMES.REDIS_ENUM,
8463
- description: "Redis enumeration",
8464
- parameters: {
8465
- target: { type: "string", description: "Target IP" },
8466
- port: { type: "string", description: `Port (default ${SERVICE_PORTS.REDIS})` }
8467
- },
8468
- required: ["target"],
8469
- execute: async (params) => {
8470
- const target = params.target;
8471
- const port = params.port || String(SERVICE_PORTS.REDIS);
8472
- return await runCommand("redis-cli", ["-h", target, "-p", port, "INFO"]);
8473
- }
8474
- },
8475
- {
8476
- name: TOOL_NAMES.DB_BRUTE,
8477
- description: "Brute force database credentials",
8478
- parameters: {
8479
- target: { type: "string", description: "Target IP" },
8480
- service: { type: "string", description: "Service (mysql, postgres, etc.)" }
8481
- },
8482
- required: ["target", "service"],
8483
- execute: async (params) => {
8484
- const target = params.target;
8485
- const service = params.service || SERVICES.MYSQL;
8486
- return await runCommand("hydra", [
8487
- "-L",
8488
- WORDLISTS.USERNAMES,
8489
- "-P",
8490
- WORDLISTS.COMMON_PASSWORDS,
8491
- target,
8492
- service
8493
- ]);
8494
- }
8495
- }
8496
- ];
8497
- var DATABASE_CONFIG = {
8498
- name: SERVICE_CATEGORIES.DATABASE,
8499
- description: "Database exploitation - SQL injection, credential extraction",
8500
- tools: DATABASE_TOOLS,
8501
- dangerLevel: DANGER_LEVELS.EXPLOIT,
8502
- defaultApproval: APPROVAL_LEVELS.REVIEW,
8503
- commonPorts: [SERVICE_PORTS.MSSQL, SERVICE_PORTS.MYSQL, SERVICE_PORTS.POSTGRESQL, SERVICE_PORTS.REDIS, SERVICE_PORTS.MONGODB],
8504
- commonServices: [SERVICES.MYSQL, SERVICES.POSTGRES, SERVICES.REDIS, SERVICES.MONGODB]
8505
- };
8506
-
8507
- // src/domains/ad/tools.ts
8508
- var AD_TOOLS = [
8509
- {
8510
- name: TOOL_NAMES.BLOODHOUND_COLLECT,
8511
- description: "BloodHound data collection",
8512
- parameters: {
8513
- target: { type: "string", description: "Target DC IP" },
8514
- domain: { type: "string", description: "Domain name" }
8515
- },
8516
- required: ["target"],
8517
- execute: async (params) => {
8518
- const target = params.target;
8519
- const domain = params.domain || "DOMAIN";
8520
- return await runCommand("bloodhound-python", ["-c", "All", "-d", domain, target, "--zip"]);
8521
- }
8614
+ target: { type: "string", description: "DC IP" },
8615
+ user: { type: "string", description: "Domain user" },
8616
+ password: { type: "string", description: "Password" }
8522
8617
  },
8523
- {
8524
- name: TOOL_NAMES.KERBEROAST,
8525
- description: "Kerberoasting attack",
8526
- parameters: {
8527
- target: { type: "string", description: "DC IP" },
8528
- user: { type: "string", description: "Domain user" },
8529
- password: { type: "string", description: "Password" }
8530
- },
8531
- required: ["target", "user", "password"],
8532
- execute: async (params) => {
8533
- const target = params.target;
8534
- return await runCommand("GetUserSPNs.py", ["-dc-ip", target, `${params.user}:${params.password}`]);
8535
- }
8536
- },
8537
- {
8538
- name: TOOL_NAMES.LDAP_ENUM,
8539
- description: "LDAP enumeration",
8540
- parameters: {
8541
- target: { type: "string", description: "LDAP Server" }
8542
- },
8543
- required: ["target"],
8544
- execute: async (params) => {
8545
- const target = params.target;
8546
- return await runCommand("ldapsearch", ["-x", "-H", `ldap://${target}`, "-b", ""]);
8547
- }
8548
- }
8549
- ];
8618
+ ["target", "user", "password"],
8619
+ async (params) => runCommand("GetUserSPNs.py", ["-dc-ip", param(params, "target"), `${param(params, "user")}:${param(params, "password")}`])
8620
+ );
8621
+ var LDAP_ENUM = createTool(
8622
+ TOOL_NAMES.LDAP_ENUM,
8623
+ "LDAP enumeration",
8624
+ { target: { type: "string", description: "LDAP Server" } },
8625
+ ["target"],
8626
+ async (params) => runCommand("ldapsearch", ["-x", "-H", `ldap://${param(params, "target")}`, "-b", ""])
8627
+ );
8628
+ var AD_TOOLS = [BLOODHOUND_COLLECT, KERBEROAST, LDAP_ENUM];
8550
8629
  var AD_CONFIG = {
8551
8630
  name: SERVICE_CATEGORIES.AD,
8552
8631
  description: "Active Directory and Windows domain",
@@ -8558,19 +8637,14 @@ var AD_CONFIG = {
8558
8637
  };
8559
8638
 
8560
8639
  // src/domains/email/tools.ts
8561
- var EMAIL_TOOLS = [
8562
- {
8563
- name: TOOL_NAMES.SMTP_ENUM,
8564
- description: "SMTP user enumeration",
8565
- parameters: {
8566
- target: { type: "string", description: "SMTP server" }
8567
- },
8568
- required: ["target"],
8569
- execute: async (params) => {
8570
- return await runCommand("smtp-user-enum", ["-M", "VRFY", "-t", params.target]);
8571
- }
8572
- }
8573
- ];
8640
+ var SMTP_ENUM = createTool(
8641
+ TOOL_NAMES.SMTP_ENUM,
8642
+ "SMTP user enumeration",
8643
+ { target: { type: "string", description: "SMTP server" } },
8644
+ ["target"],
8645
+ async (params) => runCommand("smtp-user-enum", ["-M", "VRFY", "-t", param(params, "target")])
8646
+ );
8647
+ var EMAIL_TOOLS = [SMTP_ENUM];
8574
8648
  var EMAIL_CONFIG = {
8575
8649
  name: SERVICE_CATEGORIES.EMAIL,
8576
8650
  description: "Email services - SMTP, IMAP, POP3",
@@ -8582,30 +8656,21 @@ var EMAIL_CONFIG = {
8582
8656
  };
8583
8657
 
8584
8658
  // src/domains/remote-access/tools.ts
8585
- var REMOTE_ACCESS_TOOLS = [
8586
- {
8587
- name: TOOL_NAMES.SSH_ENUM,
8588
- description: "SSH enumeration",
8589
- parameters: {
8590
- target: { type: "string", description: "SSH Server" }
8591
- },
8592
- required: ["target"],
8593
- execute: async (params) => {
8594
- return await runCommand("ssh-audit", [params.target]);
8595
- }
8596
- },
8597
- {
8598
- name: TOOL_NAMES.RDP_ENUM,
8599
- description: "RDP enumeration",
8600
- parameters: {
8601
- target: { type: "string", description: "RDP Server" }
8602
- },
8603
- required: ["target"],
8604
- execute: async (params) => {
8605
- return await runCommand("nmap", ["-p", String(SERVICE_PORTS.RDP), "--script", "rdp-enum-encryption,rdp-ntlm-info", params.target]);
8606
- }
8607
- }
8608
- ];
8659
+ var SSH_ENUM = createTool(
8660
+ TOOL_NAMES.SSH_ENUM,
8661
+ "SSH enumeration",
8662
+ { target: { type: "string", description: "SSH Server" } },
8663
+ ["target"],
8664
+ async (params) => runCommand("ssh-audit", [param(params, "target")])
8665
+ );
8666
+ var RDP_ENUM = createTool(
8667
+ TOOL_NAMES.RDP_ENUM,
8668
+ "RDP enumeration",
8669
+ { target: { type: "string", description: "RDP Server" } },
8670
+ ["target"],
8671
+ async (params) => runCommand("nmap", ["-p", String(SERVICE_PORTS.RDP), "--script", "rdp-enum-encryption,rdp-ntlm-info", param(params, "target")])
8672
+ );
8673
+ var REMOTE_ACCESS_TOOLS = [SSH_ENUM, RDP_ENUM];
8609
8674
  var REMOTE_ACCESS_CONFIG = {
8610
8675
  name: SERVICE_CATEGORIES.REMOTE_ACCESS,
8611
8676
  description: "Remote access services - SSH, RDP, VNC",
@@ -8617,30 +8682,21 @@ var REMOTE_ACCESS_CONFIG = {
8617
8682
  };
8618
8683
 
8619
8684
  // src/domains/file-sharing/tools.ts
8620
- var FILE_SHARING_TOOLS = [
8621
- {
8622
- name: TOOL_NAMES.FTP_ENUM,
8623
- description: "FTP enumeration",
8624
- parameters: {
8625
- target: { type: "string", description: "FTP Server" }
8626
- },
8627
- required: ["target"],
8628
- execute: async (params) => {
8629
- return await runCommand("nmap", ["-p", String(SERVICE_PORTS.FTP), "--script", "ftp-anon,ftp-syst", params.target]);
8630
- }
8631
- },
8632
- {
8633
- name: TOOL_NAMES.SMB_ENUM,
8634
- description: "SMB enumeration",
8635
- parameters: {
8636
- target: { type: "string", description: "SMB Server" }
8637
- },
8638
- required: ["target"],
8639
- execute: async (params) => {
8640
- return await runCommand("enum4linux", ["-a", params.target]);
8641
- }
8642
- }
8643
- ];
8685
+ var FTP_ENUM = createTool(
8686
+ TOOL_NAMES.FTP_ENUM,
8687
+ "FTP enumeration",
8688
+ { target: { type: "string", description: "FTP Server" } },
8689
+ ["target"],
8690
+ async (params) => runCommand("nmap", ["-p", String(SERVICE_PORTS.FTP), "--script", "ftp-anon,ftp-syst", param(params, "target")])
8691
+ );
8692
+ var SMB_ENUM = createTool(
8693
+ TOOL_NAMES.SMB_ENUM,
8694
+ "SMB enumeration",
8695
+ { target: { type: "string", description: "SMB Server" } },
8696
+ ["target"],
8697
+ async (params) => runCommand("enum4linux", ["-a", param(params, "target")])
8698
+ );
8699
+ var FILE_SHARING_TOOLS = [FTP_ENUM, SMB_ENUM];
8644
8700
  var FILE_SHARING_CONFIG = {
8645
8701
  name: SERVICE_CATEGORIES.FILE_SHARING,
8646
8702
  description: "File sharing protocols - SMB, FTP, NFS",
@@ -8657,34 +8713,26 @@ var CLOUD_PROVIDER = {
8657
8713
  AZURE: "azure",
8658
8714
  GCP: "gcp"
8659
8715
  };
8660
- var CLOUD_TOOLS = [
8661
- {
8662
- name: TOOL_NAMES.AWS_S3_CHECK,
8663
- description: "S3 bucket security check",
8664
- parameters: {
8665
- bucket: { type: "string", description: "Bucket name" }
8666
- },
8667
- required: ["bucket"],
8668
- execute: async (params) => {
8669
- const bucket = params.bucket;
8670
- return await runCommand("aws", ["s3", "ls", `s3://${bucket}`, "--no-sign-request"]);
8671
- }
8672
- },
8673
- {
8674
- name: TOOL_NAMES.CLOUD_META_CHECK,
8675
- description: "Check cloud metadata service access",
8676
- parameters: {
8677
- provider: { type: "string", description: "aws, azure, or gcp" }
8678
- },
8679
- required: ["provider"],
8680
- execute: async (params) => {
8681
- const provider = params.provider;
8682
- if (provider === CLOUD_PROVIDER.AWS) return await runCommand("curl", ["http://169.254.169.254/latest/meta-data/"]);
8683
- if (provider === CLOUD_PROVIDER.AZURE) return await runCommand("curl", ["-H", "Metadata:true", "http://169.254.169.254/metadata/instance?api-version=2021-02-01"]);
8684
- return await runCommand("curl", ["-H", "Metadata-Flavor: Google", "http://metadata.google.internal/computeMetadata/v1/"]);
8685
- }
8686
- }
8687
- ];
8716
+ var AWS_S3_CHECK = createTool(
8717
+ TOOL_NAMES.AWS_S3_CHECK,
8718
+ "S3 bucket security check",
8719
+ { bucket: { type: "string", description: "Bucket name" } },
8720
+ ["bucket"],
8721
+ async (params) => runCommand("aws", ["s3", "ls", `s3://${param(params, "bucket")}`, "--no-sign-request"])
8722
+ );
8723
+ var CLOUD_META_CHECK = createTool(
8724
+ TOOL_NAMES.CLOUD_META_CHECK,
8725
+ "Check cloud metadata service access",
8726
+ { provider: { type: "string", description: "aws, azure, or gcp" } },
8727
+ ["provider"],
8728
+ async (params) => {
8729
+ const provider = param(params, "provider");
8730
+ if (provider === CLOUD_PROVIDER.AWS) return runCommand("curl", ["http://169.254.169.254/latest/meta-data/"]);
8731
+ if (provider === CLOUD_PROVIDER.AZURE) return runCommand("curl", ["-H", "Metadata:true", "http://169.254.169.254/metadata/instance?api-version=2021-02-01"]);
8732
+ return runCommand("curl", ["-H", "Metadata-Flavor: Google", "http://metadata.google.internal/computeMetadata/v1/"]);
8733
+ }
8734
+ );
8735
+ var CLOUD_TOOLS = [AWS_S3_CHECK, CLOUD_META_CHECK];
8688
8736
  var CLOUD_CONFIG = {
8689
8737
  name: SERVICE_CATEGORIES.CLOUD,
8690
8738
  description: "Cloud infrastructure - AWS, Azure, GCP",
@@ -8696,30 +8744,21 @@ var CLOUD_CONFIG = {
8696
8744
  };
8697
8745
 
8698
8746
  // src/domains/container/tools.ts
8699
- var CONTAINER_TOOLS = [
8700
- {
8701
- name: TOOL_NAMES.DOCKER_PS,
8702
- description: "List running Docker containers",
8703
- parameters: {
8704
- host: { type: "string", description: "Docker host" }
8705
- },
8706
- required: ["host"],
8707
- execute: async (params) => {
8708
- return await runCommand("docker", ["-H", params.host, "ps"]);
8709
- }
8710
- },
8711
- {
8712
- name: TOOL_NAMES.KUBE_GET_PODS,
8713
- description: "List Kubernetes pods",
8714
- parameters: {
8715
- context: { type: "string", description: "Kube context" }
8716
- },
8717
- required: ["context"],
8718
- execute: async (params) => {
8719
- return await runCommand("kubectl", ["--context", params.context, "get", "pods"]);
8720
- }
8721
- }
8722
- ];
8747
+ var DOCKER_PS = createTool(
8748
+ TOOL_NAMES.DOCKER_PS,
8749
+ "List running Docker containers",
8750
+ { host: { type: "string", description: "Docker host" } },
8751
+ ["host"],
8752
+ async (params) => runCommand("docker", ["-H", param(params, "host"), "ps"])
8753
+ );
8754
+ var KUBE_GET_PODS = createTool(
8755
+ TOOL_NAMES.KUBE_GET_PODS,
8756
+ "List Kubernetes pods",
8757
+ { context: { type: "string", description: "Kube context" } },
8758
+ ["context"],
8759
+ async (params) => runCommand("kubectl", ["--context", param(params, "context"), "get", "pods"])
8760
+ );
8761
+ var CONTAINER_TOOLS = [DOCKER_PS, KUBE_GET_PODS];
8723
8762
  var CONTAINER_CONFIG = {
8724
8763
  name: SERVICE_CATEGORIES.CONTAINER,
8725
8764
  description: "Container platforms - Docker, Kubernetes",
@@ -8731,58 +8770,38 @@ var CONTAINER_CONFIG = {
8731
8770
  };
8732
8771
 
8733
8772
  // src/domains/api/tools.ts
8734
- var API_TOOLS = [
8735
- {
8736
- name: TOOL_NAMES.API_DISCOVER,
8737
- description: "API endpoint discovery - using Arjun",
8738
- parameters: {
8739
- target: { type: "string", description: "Target URL" }
8740
- },
8741
- required: ["target"],
8742
- execute: async (params) => {
8743
- const target = params.target;
8744
- return await runCommand("arjun", ["-u", target, "-t", "20"]);
8745
- }
8746
- },
8747
- {
8748
- name: TOOL_NAMES.GRAPHQL_INTROSPECT,
8749
- description: "GraphQL introspection - schema discovery",
8750
- parameters: {
8751
- target: { type: "string", description: "GQL Endpoint" }
8752
- },
8753
- required: ["target"],
8754
- execute: async (params) => {
8755
- const target = params.target;
8756
- return await runCommand("curl", ["-X", "POST", target, "-H", "Content-Type: application/json", "-d", '{"query":"{__schema {queryType {name}}}"}']);
8757
- }
8758
- },
8773
+ var API_DISCOVER = createTool(
8774
+ TOOL_NAMES.API_DISCOVER,
8775
+ "API endpoint discovery - using Arjun",
8776
+ { target: { type: "string", description: "Target URL" } },
8777
+ ["target"],
8778
+ async (params) => runCommand("arjun", ["-u", param(params, "target"), "-t", "20"])
8779
+ );
8780
+ var GRAPHQL_INTROSPECT = createTool(
8781
+ TOOL_NAMES.GRAPHQL_INTROSPECT,
8782
+ "GraphQL introspection - schema discovery",
8783
+ { target: { type: "string", description: "GQL Endpoint" } },
8784
+ ["target"],
8785
+ async (params) => runCommand("curl", ["-X", "POST", param(params, "target"), "-H", "Content-Type: application/json", "-d", '{"query":"{__schema {queryType {name}}}"}'])
8786
+ );
8787
+ var SWAGGER_PARSE = createTool(
8788
+ TOOL_NAMES.SWAGGER_PARSE,
8789
+ "Parse Swagger/OpenAPI specification",
8790
+ { spec: { type: "string", description: "URL to swagger.json/yaml" } },
8791
+ ["spec"],
8792
+ async (params) => runCommand("swagger-codegen", ["generate", "-i", param(params, "spec"), "-l", "html2"])
8793
+ );
8794
+ var API_FUZZ = createTool(
8795
+ TOOL_NAMES.API_FUZZ,
8796
+ "General API fuzzing",
8759
8797
  {
8760
- name: TOOL_NAMES.SWAGGER_PARSE,
8761
- description: "Parse Swagger/OpenAPI specification",
8762
- parameters: {
8763
- spec: { type: "string", description: "URL to swagger.json/yaml" }
8764
- },
8765
- required: ["spec"],
8766
- execute: async (params) => {
8767
- const spec = params.spec;
8768
- return await runCommand("swagger-codegen", ["generate", "-i", spec, "-l", "html2"]);
8769
- }
8798
+ target: { type: "string", description: "Base API URL" },
8799
+ wordlist: { type: "string", description: "Wordlist path" }
8770
8800
  },
8771
- {
8772
- name: TOOL_NAMES.API_FUZZ,
8773
- description: "General API fuzzing",
8774
- parameters: {
8775
- target: { type: "string", description: "Base API URL" },
8776
- wordlist: { type: "string", description: "Wordlist path" }
8777
- },
8778
- required: ["target"],
8779
- execute: async (params) => {
8780
- const target = params.target;
8781
- const wordlist = params.wordlist || WORDLISTS.API_FUZZ;
8782
- return await runCommand("ffuf", ["-u", `${target}/FUZZ`, "-w", wordlist, "-mc", "all"]);
8783
- }
8784
- }
8785
- ];
8801
+ ["target"],
8802
+ async (params) => runCommand("ffuf", ["-u", `${param(params, "target")}/FUZZ`, "-w", param(params, "wordlist", WORDLISTS.API_FUZZ), "-mc", "all"])
8803
+ );
8804
+ var API_TOOLS = [API_DISCOVER, GRAPHQL_INTROSPECT, SWAGGER_PARSE, API_FUZZ];
8786
8805
  var API_CONFIG = {
8787
8806
  name: SERVICE_CATEGORIES.API,
8788
8807
  description: "API testing - REST, GraphQL, SOAP",
@@ -8794,19 +8813,14 @@ var API_CONFIG = {
8794
8813
  };
8795
8814
 
8796
8815
  // src/domains/wireless/tools.ts
8797
- var WIRELESS_TOOLS = [
8798
- {
8799
- name: TOOL_NAMES.WIFI_SCAN,
8800
- description: "WiFi network scanning",
8801
- parameters: {
8802
- interface: { type: "string", description: "Wireless interface" }
8803
- },
8804
- required: ["interface"],
8805
- execute: async (params) => {
8806
- return await runCommand("iwlist", [params.interface, "scanning"]);
8807
- }
8808
- }
8809
- ];
8816
+ var WIFI_SCAN = createTool(
8817
+ TOOL_NAMES.WIFI_SCAN,
8818
+ "WiFi network scanning",
8819
+ { interface: { type: "string", description: "Wireless interface" } },
8820
+ ["interface"],
8821
+ async (params) => runCommand("iwlist", [param(params, "interface"), "scanning"])
8822
+ );
8823
+ var WIRELESS_TOOLS = [WIFI_SCAN];
8810
8824
  var WIRELESS_CONFIG = {
8811
8825
  name: SERVICE_CATEGORIES.WIRELESS,
8812
8826
  description: "Wireless security - WiFi, Bluetooth",
@@ -8818,19 +8832,14 @@ var WIRELESS_CONFIG = {
8818
8832
  };
8819
8833
 
8820
8834
  // src/domains/ics/tools.ts
8821
- var ICS_TOOLS = [
8822
- {
8823
- name: TOOL_NAMES.MODBUS_ENUM,
8824
- description: "Modbus enumeration",
8825
- parameters: {
8826
- target: { type: "string", description: "ICS Device" }
8827
- },
8828
- required: ["target"],
8829
- execute: async (params) => {
8830
- return await runCommand("nmap", ["-p", String(SERVICE_PORTS.MODBUS), "--script", "modbus-discover", params.target]);
8831
- }
8832
- }
8833
- ];
8835
+ var MODBUS_ENUM = createTool(
8836
+ TOOL_NAMES.MODBUS_ENUM,
8837
+ "Modbus enumeration",
8838
+ { target: { type: "string", description: "ICS Device" } },
8839
+ ["target"],
8840
+ async (params) => runCommand("nmap", ["-p", String(SERVICE_PORTS.MODBUS), "--script", "modbus-discover", param(params, "target")])
8841
+ );
8842
+ var ICS_TOOLS = [MODBUS_ENUM];
8834
8843
  var ICS_CONFIG = {
8835
8844
  name: SERVICE_CATEGORIES.ICS,
8836
8845
  description: "Industrial control systems - Modbus, DNP3, EtherNet/IP",
@@ -9083,7 +9092,7 @@ var ServiceParser = class {
9083
9092
  };
9084
9093
 
9085
9094
  // src/domains/registry.ts
9086
- import { join as join7, dirname as dirname3 } from "path";
9095
+ import { join as join8, dirname as dirname3 } from "path";
9087
9096
  import { fileURLToPath } from "url";
9088
9097
  var __dirname = dirname3(fileURLToPath(import.meta.url));
9089
9098
  var DOMAINS = {
@@ -9091,73 +9100,73 @@ var DOMAINS = {
9091
9100
  id: SERVICE_CATEGORIES.NETWORK,
9092
9101
  name: "Network Infrastructure",
9093
9102
  description: "Vulnerability scanning, port mapping, and network service exploitation.",
9094
- promptPath: join7(__dirname, "network/prompt.md")
9103
+ promptPath: join8(__dirname, "network/prompt.md")
9095
9104
  },
9096
9105
  [SERVICE_CATEGORIES.WEB]: {
9097
9106
  id: SERVICE_CATEGORIES.WEB,
9098
9107
  name: "Web Application",
9099
9108
  description: "Web app security testing, injection attacks, and auth bypass.",
9100
- promptPath: join7(__dirname, "web/prompt.md")
9109
+ promptPath: join8(__dirname, "web/prompt.md")
9101
9110
  },
9102
9111
  [SERVICE_CATEGORIES.DATABASE]: {
9103
9112
  id: SERVICE_CATEGORIES.DATABASE,
9104
9113
  name: "Database Security",
9105
9114
  description: "SQL injection, database enumeration, and data extraction.",
9106
- promptPath: join7(__dirname, "database/prompt.md")
9115
+ promptPath: join8(__dirname, "database/prompt.md")
9107
9116
  },
9108
9117
  [SERVICE_CATEGORIES.AD]: {
9109
9118
  id: SERVICE_CATEGORIES.AD,
9110
9119
  name: "Active Directory",
9111
9120
  description: "Kerberos, LDAP, and Windows domain privilege escalation.",
9112
- promptPath: join7(__dirname, "ad/prompt.md")
9121
+ promptPath: join8(__dirname, "ad/prompt.md")
9113
9122
  },
9114
9123
  [SERVICE_CATEGORIES.EMAIL]: {
9115
9124
  id: SERVICE_CATEGORIES.EMAIL,
9116
9125
  name: "Email Services",
9117
9126
  description: "SMTP, IMAP, POP3 security and user enumeration.",
9118
- promptPath: join7(__dirname, "email/prompt.md")
9127
+ promptPath: join8(__dirname, "email/prompt.md")
9119
9128
  },
9120
9129
  [SERVICE_CATEGORIES.REMOTE_ACCESS]: {
9121
9130
  id: SERVICE_CATEGORIES.REMOTE_ACCESS,
9122
9131
  name: "Remote Access",
9123
9132
  description: "SSH, RDP, VNC and other remote control protocols.",
9124
- promptPath: join7(__dirname, "remote-access/prompt.md")
9133
+ promptPath: join8(__dirname, "remote-access/prompt.md")
9125
9134
  },
9126
9135
  [SERVICE_CATEGORIES.FILE_SHARING]: {
9127
9136
  id: SERVICE_CATEGORIES.FILE_SHARING,
9128
9137
  name: "File Sharing",
9129
9138
  description: "SMB, NFS, FTP and shared resource security.",
9130
- promptPath: join7(__dirname, "file-sharing/prompt.md")
9139
+ promptPath: join8(__dirname, "file-sharing/prompt.md")
9131
9140
  },
9132
9141
  [SERVICE_CATEGORIES.CLOUD]: {
9133
9142
  id: SERVICE_CATEGORIES.CLOUD,
9134
9143
  name: "Cloud Infrastructure",
9135
9144
  description: "AWS, Azure, and GCP security and misconfiguration.",
9136
- promptPath: join7(__dirname, "cloud/prompt.md")
9145
+ promptPath: join8(__dirname, "cloud/prompt.md")
9137
9146
  },
9138
9147
  [SERVICE_CATEGORIES.CONTAINER]: {
9139
9148
  id: SERVICE_CATEGORIES.CONTAINER,
9140
9149
  name: "Container Systems",
9141
9150
  description: "Docker and Kubernetes security testing.",
9142
- promptPath: join7(__dirname, "container/prompt.md")
9151
+ promptPath: join8(__dirname, "container/prompt.md")
9143
9152
  },
9144
9153
  [SERVICE_CATEGORIES.API]: {
9145
9154
  id: SERVICE_CATEGORIES.API,
9146
9155
  name: "API Security",
9147
9156
  description: "REST, GraphQL, and SOAP API security testing.",
9148
- promptPath: join7(__dirname, "api/prompt.md")
9157
+ promptPath: join8(__dirname, "api/prompt.md")
9149
9158
  },
9150
9159
  [SERVICE_CATEGORIES.WIRELESS]: {
9151
9160
  id: SERVICE_CATEGORIES.WIRELESS,
9152
9161
  name: "Wireless Networks",
9153
9162
  description: "WiFi and Bluetooth security testing.",
9154
- promptPath: join7(__dirname, "wireless/prompt.md")
9163
+ promptPath: join8(__dirname, "wireless/prompt.md")
9155
9164
  },
9156
9165
  [SERVICE_CATEGORIES.ICS]: {
9157
9166
  id: SERVICE_CATEGORIES.ICS,
9158
9167
  name: "Industrial Systems",
9159
9168
  description: "Critical infrastructure - Modbus, DNP3, ENIP.",
9160
- promptPath: join7(__dirname, "ics/prompt.md")
9169
+ promptPath: join8(__dirname, "ics/prompt.md")
9161
9170
  }
9162
9171
  };
9163
9172
 
@@ -9234,22 +9243,6 @@ var CategorizedToolRegistry = class extends ToolRegistry {
9234
9243
  }
9235
9244
  return results;
9236
9245
  }
9237
- suggestSubAgent(target) {
9238
- const suggestions = this.suggestTools(target);
9239
- const priority = [
9240
- SERVICE_CATEGORIES.ICS,
9241
- SERVICE_CATEGORIES.AD,
9242
- SERVICE_CATEGORIES.DATABASE,
9243
- SERVICE_CATEGORIES.CLOUD,
9244
- SERVICE_CATEGORIES.CONTAINER,
9245
- SERVICE_CATEGORIES.WEB,
9246
- SERVICE_CATEGORIES.NETWORK
9247
- ];
9248
- for (const cat of priority) {
9249
- if (suggestions.some((s) => s.category === cat)) return cat;
9250
- }
9251
- return AGENT_ROLES.RECON;
9252
- }
9253
9246
  fingerprintService(port) {
9254
9247
  const category = ServiceParser.detectCategory(port.port, port.service);
9255
9248
  if (!category) return null;
@@ -9578,14 +9571,6 @@ var LLMClient = class {
9578
9571
  this.processStreamEvent(event, requestId, {
9579
9572
  toolCallsMap,
9580
9573
  callbacks,
9581
- onTextStart: () => {
9582
- },
9583
- onReasoningStart: () => {
9584
- },
9585
- onTextEnd: () => {
9586
- },
9587
- onReasoningEnd: () => {
9588
- },
9589
9574
  onContent: (text) => {
9590
9575
  fullContent += text;
9591
9576
  totalChars += text.length;
@@ -9660,18 +9645,18 @@ var LLMClient = class {
9660
9645
  return { cleanText, extractedReasoning };
9661
9646
  }
9662
9647
  processStreamEvent(event, requestId, context) {
9663
- const { toolCallsMap, callbacks, onTextStart, onReasoningStart, onTextEnd, onReasoningEnd, onContent, onReasoning, onUsage, getTotalChars, currentBlockRef } = context;
9648
+ const { toolCallsMap, callbacks, onContent, onReasoning, onUsage, getTotalChars, currentBlockRef } = context;
9664
9649
  switch (event.type) {
9665
9650
  case LLM_SSE_EVENT.CONTENT_BLOCK_START:
9666
9651
  if (event.content_block) {
9667
9652
  const blockType = event.content_block.type;
9668
9653
  if (blockType === LLM_BLOCK_TYPE.TEXT) {
9669
9654
  currentBlockRef.value = "text";
9670
- onTextStart();
9655
+ context.onTextStart?.();
9671
9656
  callbacks?.onOutputStart?.();
9672
9657
  } else if (blockType === LLM_BLOCK_TYPE.THINKING || blockType === LLM_BLOCK_TYPE.REASONING) {
9673
9658
  currentBlockRef.value = "reasoning";
9674
- onReasoningStart();
9659
+ context.onReasoningStart?.();
9675
9660
  callbacks?.onReasoningStart?.();
9676
9661
  } else if (blockType === LLM_BLOCK_TYPE.TOOL_USE) {
9677
9662
  currentBlockRef.value = "tool_use";
@@ -9717,10 +9702,10 @@ var LLMClient = class {
9717
9702
  const stoppedType = currentBlockRef.value;
9718
9703
  currentBlockRef.value = null;
9719
9704
  if (stoppedType === "text") {
9720
- onTextEnd();
9705
+ context.onTextEnd?.();
9721
9706
  callbacks?.onOutputEnd?.();
9722
9707
  } else if (stoppedType === "reasoning") {
9723
- onReasoningEnd();
9708
+ context.onReasoningEnd?.();
9724
9709
  callbacks?.onReasoningEnd?.();
9725
9710
  }
9726
9711
  break;
@@ -9792,13 +9777,10 @@ function getLLMClient() {
9792
9777
  }
9793
9778
  return llmInstance;
9794
9779
  }
9795
- function logLLM(message, data) {
9796
- debugLog("llm", message, data);
9797
- }
9798
9780
 
9799
9781
  // src/engine/state-persistence.ts
9800
- import { writeFileSync as writeFileSync6, readFileSync as readFileSync4, existsSync as existsSync6, readdirSync, statSync, unlinkSync as unlinkSync4, rmSync } from "fs";
9801
- import { join as join8 } from "path";
9782
+ import { writeFileSync as writeFileSync7, readFileSync as readFileSync4, existsSync as existsSync7, readdirSync as readdirSync2, statSync as statSync2, unlinkSync as unlinkSync4, rmSync } from "fs";
9783
+ import { join as join9 } from "path";
9802
9784
  function saveState(state) {
9803
9785
  const sessionsDir = WORKSPACE.SESSIONS;
9804
9786
  ensureDirExists(sessionsDir);
@@ -9815,20 +9797,24 @@ function saveState(state) {
9815
9797
  missionSummary: state.getMissionSummary(),
9816
9798
  missionChecklist: state.getMissionChecklist()
9817
9799
  };
9818
- const sessionFile = join8(sessionsDir, FILE_PATTERNS.session());
9819
- writeFileSync6(sessionFile, JSON.stringify(snapshot, null, 2), "utf-8");
9820
- const latestFile = join8(sessionsDir, "latest.json");
9821
- writeFileSync6(latestFile, JSON.stringify(snapshot, null, 2), "utf-8");
9800
+ const sessionFile = join9(sessionsDir, FILE_PATTERNS.session());
9801
+ const json = JSON.stringify(snapshot, null, 2);
9802
+ writeFileSync7(sessionFile, json, "utf-8");
9803
+ const latestFile = join9(sessionsDir, "latest.json");
9804
+ writeFileSync7(latestFile, json, "utf-8");
9822
9805
  pruneOldSessions(sessionsDir);
9823
9806
  return sessionFile;
9824
9807
  }
9825
9808
  function pruneOldSessions(sessionsDir) {
9826
9809
  try {
9827
- const sessionFiles = readdirSync(sessionsDir).filter((f) => f.endsWith(FILE_EXTENSIONS.JSON) && f !== SPECIAL_FILES.LATEST_STATE).map((f) => ({
9828
- name: f,
9829
- path: join8(sessionsDir, f),
9830
- mtime: statSync(join8(sessionsDir, f)).mtimeMs
9831
- })).sort((a, b) => b.mtime - a.mtime);
9810
+ const sessionFiles = readdirSync2(sessionsDir).filter((f) => f.endsWith(FILE_EXTENSIONS.JSON) && f !== SPECIAL_FILES.LATEST_STATE).map((f) => {
9811
+ const filePath = join9(sessionsDir, f);
9812
+ return {
9813
+ name: f,
9814
+ path: filePath,
9815
+ mtime: statSync2(filePath).mtimeMs
9816
+ };
9817
+ }).sort((a, b) => b.mtime - a.mtime);
9832
9818
  const toDelete = sessionFiles.slice(AGENT_LIMITS.MAX_SESSION_FILES);
9833
9819
  for (const file of toDelete) {
9834
9820
  unlinkSync4(file.path);
@@ -9837,8 +9823,8 @@ function pruneOldSessions(sessionsDir) {
9837
9823
  }
9838
9824
  }
9839
9825
  function loadState(state) {
9840
- const latestFile = join8(WORKSPACE.SESSIONS, "latest.json");
9841
- if (!existsSync6(latestFile)) {
9826
+ const latestFile = join9(WORKSPACE.SESSIONS, "latest.json");
9827
+ if (!existsSync7(latestFile)) {
9842
9828
  return false;
9843
9829
  }
9844
9830
  try {
@@ -9865,10 +9851,7 @@ function loadState(state) {
9865
9851
  state.addLoot(loot);
9866
9852
  }
9867
9853
  for (const item of snapshot.todo) {
9868
- const id = state.addTodo(item.content, item.priority);
9869
- if (item.status && item.status !== "pending") {
9870
- state.updateTodo(id, { status: item.status });
9871
- }
9854
+ state.restoreTodoItem(item);
9872
9855
  }
9873
9856
  const validPhases = new Set(Object.values(PHASES));
9874
9857
  const restoredPhase = validPhases.has(snapshot.currentPhase) ? snapshot.currentPhase : PHASES.RECON;
@@ -9877,21 +9860,7 @@ function loadState(state) {
9877
9860
  state.setMissionSummary(snapshot.missionSummary);
9878
9861
  }
9879
9862
  if (snapshot.missionChecklist?.length > 0) {
9880
- state.addMissionChecklistItems(snapshot.missionChecklist.map((c) => c.text));
9881
- const restoredChecklist = state.getMissionChecklist();
9882
- const baseIndex = restoredChecklist.length - snapshot.missionChecklist.length;
9883
- const completedUpdates = [];
9884
- for (let i = 0; i < snapshot.missionChecklist.length; i++) {
9885
- if (snapshot.missionChecklist[i].isCompleted && restoredChecklist[baseIndex + i]) {
9886
- completedUpdates.push({
9887
- id: restoredChecklist[baseIndex + i].id,
9888
- isCompleted: true
9889
- });
9890
- }
9891
- }
9892
- if (completedUpdates.length > 0) {
9893
- state.updateMissionChecklist(completedUpdates);
9894
- }
9863
+ state.restoreMissionChecklist(snapshot.missionChecklist);
9895
9864
  }
9896
9865
  return true;
9897
9866
  } catch (err) {
@@ -9914,7 +9883,7 @@ function clearWorkspace() {
9914
9883
  ];
9915
9884
  for (const dir of dirsToClean) {
9916
9885
  try {
9917
- if (existsSync6(dir.path)) {
9886
+ if (existsSync7(dir.path)) {
9918
9887
  rmSync(dir.path, { recursive: true, force: true });
9919
9888
  ensureDirExists(dir.path);
9920
9889
  cleared.push(dir.label);
@@ -10004,7 +9973,7 @@ function appendBlockedCommandHints(lines, errorLower) {
10004
9973
  }
10005
9974
 
10006
9975
  // src/shared/utils/context-digest.ts
10007
- import { writeFileSync as writeFileSync7 } from "fs";
9976
+ import { writeFileSync as writeFileSync8 } from "fs";
10008
9977
 
10009
9978
  // src/shared/constants/document-schema.ts
10010
9979
  var MEMO_SECTIONS = {
@@ -10336,7 +10305,7 @@ function saveFullOutput(output, toolName) {
10336
10305
  const toolsDir = WORKSPACE.turnToolsPath(_currentTurn);
10337
10306
  ensureDirExists(toolsDir);
10338
10307
  const filePath = `${toolsDir}/${FILE_PATTERNS.toolOutput(toolName)}`;
10339
- writeFileSync7(filePath, output, "utf-8");
10308
+ writeFileSync8(filePath, output, "utf-8");
10340
10309
  return filePath;
10341
10310
  } catch (err) {
10342
10311
  debugLog("general", "Failed to save full output to file", { toolName, error: String(err) });
@@ -10365,8 +10334,306 @@ ${text}
10365
10334
  };
10366
10335
  }
10367
10336
 
10337
+ // src/agents/tool-executor.ts
10338
+ var ToolExecutor = class _ToolExecutor {
10339
+ state;
10340
+ events;
10341
+ toolRegistry;
10342
+ llm;
10343
+ /** Collected tool execution records for the current turn */
10344
+ turnToolJournal = [];
10345
+ /** Aggregated memo from all tools in the current turn */
10346
+ turnMemo = {
10347
+ keyFindings: [],
10348
+ credentials: [],
10349
+ attackVectors: [],
10350
+ failures: [],
10351
+ suspicions: [],
10352
+ attackValue: "LOW",
10353
+ nextSteps: []
10354
+ };
10355
+ /** Analyst reflections collected during this turn */
10356
+ turnReflections = [];
10357
+ /** Tools safe to run in parallel */
10358
+ static PARALLEL_SAFE_TOOLS = /* @__PURE__ */ new Set([
10359
+ TOOL_NAMES.GET_STATE,
10360
+ TOOL_NAMES.PARSE_NMAP,
10361
+ TOOL_NAMES.SEARCH_CVE,
10362
+ TOOL_NAMES.WEB_SEARCH,
10363
+ TOOL_NAMES.BROWSE_URL,
10364
+ TOOL_NAMES.READ_FILE,
10365
+ TOOL_NAMES.GET_OWASP_KNOWLEDGE,
10366
+ TOOL_NAMES.GET_WEB_ATTACK_SURFACE,
10367
+ TOOL_NAMES.GET_CVE_INFO,
10368
+ TOOL_NAMES.FILL_FORM,
10369
+ TOOL_NAMES.ADD_TARGET,
10370
+ TOOL_NAMES.ADD_FINDING,
10371
+ TOOL_NAMES.ADD_LOOT,
10372
+ TOOL_NAMES.UPDATE_MISSION,
10373
+ TOOL_NAMES.UPDATE_TODO
10374
+ ]);
10375
+ constructor(deps) {
10376
+ this.state = deps.state;
10377
+ this.events = deps.events;
10378
+ this.toolRegistry = deps.toolRegistry;
10379
+ this.llm = deps.llm;
10380
+ }
10381
+ /** Clear turn-level state at the start of each step */
10382
+ clearTurnState() {
10383
+ this.turnToolJournal = [];
10384
+ this.turnMemo = {
10385
+ keyFindings: [],
10386
+ credentials: [],
10387
+ attackVectors: [],
10388
+ failures: [],
10389
+ suspicions: [],
10390
+ attackValue: "LOW",
10391
+ nextSteps: []
10392
+ };
10393
+ this.turnReflections = [];
10394
+ }
10395
+ // ─────────────────────────────────────────────────────────────────
10396
+ // SUBSECTION: Tool Execution
10397
+ // ─────────────────────────────────────────────────────────────────
10398
+ async processToolCalls(toolCalls, progress) {
10399
+ if (toolCalls.length <= 1) {
10400
+ return this.executeSequentially(toolCalls, progress);
10401
+ }
10402
+ const allParallelSafe = toolCalls.every((c) => _ToolExecutor.PARALLEL_SAFE_TOOLS.has(c.name));
10403
+ if (allParallelSafe) {
10404
+ return this.executeInParallel(toolCalls, progress);
10405
+ }
10406
+ const parallelCalls = toolCalls.filter((c) => _ToolExecutor.PARALLEL_SAFE_TOOLS.has(c.name));
10407
+ const sequentialCalls = toolCalls.filter((c) => !_ToolExecutor.PARALLEL_SAFE_TOOLS.has(c.name));
10408
+ const parallelResults = parallelCalls.length > 0 ? await this.executeInParallel(parallelCalls, progress) : [];
10409
+ const sequentialResults = sequentialCalls.length > 0 ? await this.executeSequentially(sequentialCalls, progress) : [];
10410
+ const resultMap = /* @__PURE__ */ new Map();
10411
+ for (const r of [...parallelResults, ...sequentialResults]) {
10412
+ resultMap.set(r.toolCallId, r);
10413
+ }
10414
+ return toolCalls.map((c) => resultMap.get(c.id)).filter(Boolean);
10415
+ }
10416
+ async executeInParallel(toolCalls, progress) {
10417
+ for (const call of toolCalls) {
10418
+ this.emitToolCall(call.name, call.input);
10419
+ }
10420
+ const promises = toolCalls.map((call) => this.executeSingle(call, progress));
10421
+ const settled = await Promise.allSettled(promises);
10422
+ return settled.map((s, i) => {
10423
+ if (s.status === "fulfilled") return s.value;
10424
+ const errorMsg = String(s.reason);
10425
+ return { toolCallId: toolCalls[i].id, output: errorMsg, error: errorMsg };
10426
+ });
10427
+ }
10428
+ async executeSequentially(toolCalls, progress) {
10429
+ const results = [];
10430
+ for (const call of toolCalls) {
10431
+ this.emitToolCall(call.name, call.input);
10432
+ const result2 = await this.executeSingle(call, progress);
10433
+ results.push(result2);
10434
+ }
10435
+ return results;
10436
+ }
10437
+ async executeSingle(call, progress) {
10438
+ const toolStartTime = Date.now();
10439
+ try {
10440
+ const result2 = await this.toolRegistry.execute({ name: call.name, input: call.input });
10441
+ let outputText = result2.output ?? "";
10442
+ this.scanForFlags(outputText);
10443
+ outputText = this.handleToolResult(result2, call, outputText, progress);
10444
+ const { digestedOutputForLLM, digestResult } = await this.digestAndEmit(
10445
+ call,
10446
+ outputText,
10447
+ result2,
10448
+ toolStartTime
10449
+ );
10450
+ this.recordJournalMemo(call, result2, digestedOutputForLLM, digestResult);
10451
+ return { toolCallId: call.id, output: digestedOutputForLLM, error: result2.error };
10452
+ } catch (error) {
10453
+ const errorMsg = String(error);
10454
+ const enrichedError = enrichToolErrorContext({
10455
+ toolName: call.name,
10456
+ input: call.input,
10457
+ error: errorMsg,
10458
+ originalOutput: "",
10459
+ progress
10460
+ });
10461
+ if (progress) progress.toolErrors++;
10462
+ this.emitToolResult(call.name, false, enrichedError, errorMsg, Date.now() - toolStartTime);
10463
+ return { toolCallId: call.id, output: enrichedError, error: errorMsg };
10464
+ }
10465
+ }
10466
+ // ─────────────────────────────────────────────────────────────────
10467
+ // SUBSECTION: Result Handling
10468
+ // ─────────────────────────────────────────────────────────────────
10469
+ handleToolResult(result2, call, outputText, progress) {
10470
+ if (result2.error) {
10471
+ if (progress) progress.toolErrors++;
10472
+ return enrichToolErrorContext({
10473
+ toolName: call.name,
10474
+ input: call.input,
10475
+ error: result2.error,
10476
+ originalOutput: outputText,
10477
+ progress
10478
+ });
10479
+ }
10480
+ if (progress) {
10481
+ progress.toolSuccesses++;
10482
+ progress.blockedCommandPatterns.clear();
10483
+ }
10484
+ return outputText;
10485
+ }
10486
+ async digestAndEmit(call, outputText, result2, toolStartTime) {
10487
+ let digestedOutputForLLM = outputText;
10488
+ let digestResult = null;
10489
+ try {
10490
+ const llmDigestFn = createLLMDigestFn(this.llm);
10491
+ digestResult = await digestToolOutput(
10492
+ outputText,
10493
+ call.name,
10494
+ JSON.stringify(call.input).slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
10495
+ llmDigestFn
10496
+ );
10497
+ digestedOutputForLLM = digestResult.digestedOutput;
10498
+ } catch {
10499
+ if (digestedOutputForLLM.length > AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH) {
10500
+ const truncated = digestedOutputForLLM.slice(0, AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH);
10501
+ const remaining = digestedOutputForLLM.length - AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH;
10502
+ digestedOutputForLLM = `${truncated}
10503
+
10504
+ ... [TRUNCATED ${remaining} chars] ...`;
10505
+ }
10506
+ }
10507
+ const outputFilePath = digestResult?.fullOutputPath ?? null;
10508
+ const tuiOutput = digestResult?.digestedOutput ? `${digestResult.digestedOutput}${outputFilePath ? `
10509
+ \u{1F4C4} Full output: ${outputFilePath}` : ""}` : outputText.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY);
10510
+ this.emitToolResult(call.name, result2.success, tuiOutput, result2.error, Date.now() - toolStartTime);
10511
+ return { digestedOutputForLLM, digestResult };
10512
+ }
10513
+ recordJournalMemo(call, result2, digestedOutputForLLM, digestResult) {
10514
+ this.turnToolJournal.push({
10515
+ name: call.name,
10516
+ inputSummary: JSON.stringify(call.input),
10517
+ success: result2.success,
10518
+ analystSummary: digestResult?.memo ? digestResult.memo.keyFindings.join("; ") || "No key findings" : digestedOutputForLLM,
10519
+ outputFile: digestResult?.fullOutputPath ?? null
10520
+ });
10521
+ if (digestResult?.memo) {
10522
+ const m = digestResult.memo;
10523
+ this.turnMemo.keyFindings.push(...m.keyFindings);
10524
+ this.turnMemo.credentials.push(...m.credentials);
10525
+ this.turnMemo.attackVectors.push(...m.attackVectors);
10526
+ this.turnMemo.failures.push(...m.failures);
10527
+ this.turnMemo.suspicions.push(...m.suspicions);
10528
+ if ((ATTACK_VALUE_RANK[m.attackValue] ?? 0) > (ATTACK_VALUE_RANK[this.turnMemo.attackValue] ?? 0)) {
10529
+ this.turnMemo.attackValue = m.attackValue;
10530
+ }
10531
+ this.turnMemo.nextSteps.push(...m.nextSteps);
10532
+ if (m.reflection) this.turnReflections.push(m.reflection);
10533
+ }
10534
+ if (digestResult?.memo?.credentials.length) {
10535
+ for (const cred of digestResult.memo.credentials) {
10536
+ this.state.addLoot({
10537
+ type: LOOT_TYPES.CREDENTIAL,
10538
+ host: "auto-extracted",
10539
+ detail: cred,
10540
+ obtainedAt: Date.now()
10541
+ });
10542
+ }
10543
+ }
10544
+ if (digestResult?.memo?.attackVectors.length && digestResult.memo.attackValue === "HIGH") {
10545
+ const existingTitles = new Set(this.state.getFindings().map((f) => f.title));
10546
+ for (const vector of digestResult.memo.attackVectors) {
10547
+ const title = `[Auto] ${vector.slice(0, 100)}`;
10548
+ if (!existingTitles.has(title)) {
10549
+ this.state.addFinding({
10550
+ id: generateId(),
10551
+ title,
10552
+ severity: "high",
10553
+ confidence: CONFIDENCE_THRESHOLDS.POSSIBLE,
10554
+ affected: [],
10555
+ description: `Auto-extracted by Analyst LLM: ${vector}`,
10556
+ evidence: digestResult.memo.keyFindings.slice(0, 5),
10557
+ remediation: "",
10558
+ foundAt: Date.now()
10559
+ });
10560
+ this.state.attackGraph.addVulnerability(title, "auto-detected", "high", false);
10561
+ existingTitles.add(title);
10562
+ }
10563
+ }
10564
+ }
10565
+ if (this.state.getFindings().length > 0 && this.state.getPhase() === PHASES.RECON) {
10566
+ this.state.setPhase(PHASES.VULN_ANALYSIS);
10567
+ }
10568
+ }
10569
+ // ─────────────────────────────────────────────────────────────────
10570
+ // SUBSECTION: CTF Flag Detection
10571
+ // ─────────────────────────────────────────────────────────────────
10572
+ scanForFlags(output) {
10573
+ if (!this.state.isCtfMode()) return;
10574
+ const flags = detectFlags(output);
10575
+ for (const flag of flags) {
10576
+ const isNew = this.state.addFlag(flag);
10577
+ if (isNew) {
10578
+ this.events.emit({
10579
+ type: EVENT_TYPES.FLAG_FOUND,
10580
+ timestamp: Date.now(),
10581
+ data: {
10582
+ flag,
10583
+ totalFlags: this.state.getFlags().length,
10584
+ phase: this.state.getPhase()
10585
+ }
10586
+ });
10587
+ }
10588
+ }
10589
+ }
10590
+ // ─────────────────────────────────────────────────────────────────
10591
+ // SUBSECTION: Event Emitters
10592
+ // ─────────────────────────────────────────────────────────────────
10593
+ emitToolCall(toolName, input) {
10594
+ this.events.emit({
10595
+ type: EVENT_TYPES.TOOL_CALL,
10596
+ timestamp: Date.now(),
10597
+ data: {
10598
+ toolName,
10599
+ input,
10600
+ approvalLevel: APPROVAL_LEVELS.AUTO,
10601
+ needsApproval: false
10602
+ }
10603
+ });
10604
+ }
10605
+ emitToolResult(toolName, success, output, error, duration) {
10606
+ this.events.emit({
10607
+ type: EVENT_TYPES.TOOL_RESULT,
10608
+ timestamp: Date.now(),
10609
+ data: {
10610
+ toolName,
10611
+ success,
10612
+ output,
10613
+ outputSummary: output.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
10614
+ error,
10615
+ duration
10616
+ }
10617
+ });
10618
+ }
10619
+ // ─────────────────────────────────────────────────────────────────
10620
+ // SUBSECTION: Schema Helper
10621
+ // ─────────────────────────────────────────────────────────────────
10622
+ getToolSchemas() {
10623
+ return this.toolRegistry.getAll().map((t) => ({
10624
+ name: t.name,
10625
+ description: t.description,
10626
+ input_schema: {
10627
+ type: "object",
10628
+ properties: t.parameters,
10629
+ required: t.required || []
10630
+ }
10631
+ }));
10632
+ }
10633
+ };
10634
+
10368
10635
  // src/agents/core-agent.ts
10369
- var CoreAgent = class _CoreAgent {
10636
+ var CoreAgent = class {
10370
10637
  llm;
10371
10638
  state;
10372
10639
  events;
@@ -10374,16 +10641,7 @@ var CoreAgent = class _CoreAgent {
10374
10641
  agentType;
10375
10642
  maxIterations;
10376
10643
  abortController = null;
10377
- /**
10378
- * Collected tool execution records for the current turn.
10379
- * MainAgent reads this after each step to write journal entries.
10380
- * Cleared at the start of each step.
10381
- */
10382
- turnToolJournal = [];
10383
- /** Aggregated memo from all tools in the current turn */
10384
- turnMemo = { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
10385
- /** Analyst reflections collected during this turn (1-line assessments) */
10386
- turnReflections = [];
10644
+ toolExecutor = null;
10387
10645
  constructor(agentType, state, events, toolRegistry, maxIterations) {
10388
10646
  this.agentType = agentType;
10389
10647
  this.state = state;
@@ -10396,10 +10654,20 @@ var CoreAgent = class _CoreAgent {
10396
10654
  setToolRegistry(registry) {
10397
10655
  this.toolRegistry = registry;
10398
10656
  }
10399
- /** Abort the current execution — immediately cancels LLM calls and retries */
10657
+ /** Abort the current execution */
10400
10658
  abort() {
10401
10659
  this.abortController?.abort();
10402
10660
  }
10661
+ /** Get turn tool journal (for MainAgent) */
10662
+ getTurnToolJournal() {
10663
+ return this.toolExecutor?.turnToolJournal ?? [];
10664
+ }
10665
+ getTurnMemo() {
10666
+ return this.toolExecutor?.turnMemo ?? { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
10667
+ }
10668
+ getTurnReflections() {
10669
+ return this.toolExecutor?.turnReflections ?? [];
10670
+ }
10403
10671
  // ─────────────────────────────────────────────────────────────────
10404
10672
  // SUBSECTION: Main Run Loop
10405
10673
  // ─────────────────────────────────────────────────────────────────
@@ -10445,110 +10713,106 @@ var CoreAgent = class _CoreAgent {
10445
10713
  messages.push({ role: LLM_ROLES.USER, content: this.buildDeadlockNudge(progress) });
10446
10714
  }
10447
10715
  } catch (error) {
10448
- if (this.isAbortError(error)) {
10449
- return this.buildCancelledResult(iteration, progress.totalToolsExecuted);
10450
- }
10451
- if (error instanceof LLMError) {
10452
- const errorInfo = error.errorInfo;
10453
- this.events.emit({
10454
- type: EVENT_TYPES.ERROR,
10455
- timestamp: Date.now(),
10456
- data: {
10457
- message: `LLM Error: ${errorInfo.message}`,
10458
- phase: this.state.getPhase(),
10459
- isRecoverable: errorInfo.isRetryable,
10460
- errorInfo
10461
- }
10462
- });
10463
- if (errorInfo.type === LLM_ERROR_TYPES.RATE_LIMIT) {
10716
+ const action = this.handleLoopError(
10717
+ error,
10718
+ messages,
10719
+ progress,
10720
+ iteration,
10721
+ consecutiveLLMErrors,
10722
+ maxConsecutiveLLMErrors
10723
+ );
10724
+ if (action.action === "return") return action.result;
10725
+ if (action.action === "continue") {
10726
+ if (error instanceof LLMError && error.errorInfo.type === LLM_ERROR_TYPES.RATE_LIMIT) {
10464
10727
  consecutiveLLMErrors++;
10465
- if (maxConsecutiveLLMErrors !== Infinity && consecutiveLLMErrors >= maxConsecutiveLLMErrors) {
10466
- return {
10467
- output: `LLM rate limit errors exceeded limit. Last error: ${errorInfo.message}`,
10468
- iterations: iteration + 1,
10469
- toolsExecuted: progress.totalToolsExecuted,
10470
- isCompleted: false
10471
- };
10472
- }
10473
- continue;
10728
+ } else {
10729
+ consecutiveLLMErrors = 0;
10474
10730
  }
10475
- consecutiveLLMErrors = 0;
10476
- const errorMessage = this.formatLLMErrorForAgent(errorInfo);
10477
- messages.push({
10478
- role: LLM_ROLES.USER,
10479
- content: errorMessage
10480
- });
10481
10731
  continue;
10482
10732
  }
10483
- const unexpectedMsg = error instanceof Error ? error.message : String(error);
10484
- this.events.emit({
10485
- type: EVENT_TYPES.ERROR,
10486
- timestamp: Date.now(),
10487
- data: {
10488
- message: `Unexpected error: ${unexpectedMsg}`,
10489
- phase: this.state.getPhase(),
10490
- isRecoverable: true
10491
- }
10492
- });
10493
- messages.push({
10494
- role: LLM_ROLES.USER,
10495
- content: `\u26A0\uFE0F UNEXPECTED ERROR: ${unexpectedMsg}
10496
- This may be a transient issue. Continue your task \u2014 retry the last action or try an alternative approach.`
10497
- });
10498
- continue;
10499
10733
  }
10500
10734
  }
10501
- 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"}.`;
10502
10735
  return {
10503
- output: summary,
10736
+ output: `Max iterations (${this.maxIterations}) reached. Tools: ${progress.totalToolsExecuted}.`,
10504
10737
  iterations: this.maxIterations,
10505
10738
  toolsExecuted: progress.totalToolsExecuted,
10506
10739
  isCompleted: false
10507
10740
  };
10508
10741
  }
10509
- // ─────────────────────────────────────────────────────────────────
10510
- // SUBSECTION: Error Formatting
10511
- // ─────────────────────────────────────────────────────────────────
10512
10742
  /**
10513
- * Format LLM error as a message for the agent to understand and act on
10743
+ * §4-1: Handle errors caught in the run loop extracted to reduce nesting depth.
10744
+ *
10745
+ * Returns a LoopErrorAction so the caller controls `continue` / `return`.
10746
+ * WHY separate: The catch block in run() had nesting depth 8. By moving error
10747
+ * dispatch here, run() stays at depth 3 (for → try → if) throughout.
10514
10748
  */
10515
- formatLLMErrorForAgent(errorInfo) {
10516
- const actionHints = {
10517
- [LLM_ERROR_TYPES.RATE_LIMIT]: "Wait a moment and try again with a simpler query.",
10518
- [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.",
10519
- [LLM_ERROR_TYPES.INVALID_REQUEST]: "Simplify your request or modify the parameters.",
10520
- [LLM_ERROR_TYPES.NETWORK_ERROR]: "Check network connectivity and retry.",
10521
- [LLM_ERROR_TYPES.TIMEOUT]: "Retry with a shorter or simpler request.",
10522
- [LLM_ERROR_TYPES.UNKNOWN]: "Analyze the error and decide the best course of action."
10523
- };
10524
- return `[SYSTEM ERROR - LLM API Issue]
10525
- Error Type: ${errorInfo.type}
10526
- Message: ${errorInfo.message}
10527
- Status Code: ${errorInfo.statusCode || "N/A"}
10528
- Retryable: ${errorInfo.isRetryable ? "Yes" : "No"}
10529
-
10530
- Suggested Action: ${errorInfo.suggestedAction || actionHints[errorInfo.type] || "Decide appropriate action"}
10531
-
10532
- How to proceed:
10533
- 1. If retryable: Wait briefly and continue your task
10534
- 2. If auth error: Use ask_user tool to request API key from user
10535
- 3. If invalid request: Simplify or modify your approach
10536
- 4. If persistent: Report to user and await instructions
10537
-
10538
- Please decide how to handle this error and continue.`;
10749
+ handleLoopError(error, messages, progress, iteration, consecutiveLLMErrors, maxConsecutiveLLMErrors) {
10750
+ if (this.isAbortError(error)) {
10751
+ return { action: "return", result: this.buildCancelledResult(iteration, progress.totalToolsExecuted) };
10752
+ }
10753
+ if (error instanceof LLMError) {
10754
+ return this.handleLLMError(error, messages, progress, iteration, consecutiveLLMErrors, maxConsecutiveLLMErrors);
10755
+ }
10756
+ const unexpectedMsg = error instanceof Error ? error.message : String(error);
10757
+ this.events.emit({
10758
+ type: EVENT_TYPES.ERROR,
10759
+ timestamp: Date.now(),
10760
+ data: { message: `Unexpected error: ${unexpectedMsg}`, phase: this.state.getPhase(), isRecoverable: true }
10761
+ });
10762
+ messages.push({ role: LLM_ROLES.USER, content: `\u26A0\uFE0F UNEXPECTED ERROR: ${unexpectedMsg}
10763
+ Continue your task.` });
10764
+ return { action: "continue" };
10765
+ }
10766
+ /** Handle LLMError specifically — rate limits vs other LLM errors. */
10767
+ handleLLMError(error, messages, progress, iteration, consecutiveLLMErrors, maxConsecutiveLLMErrors) {
10768
+ const errorInfo = error.errorInfo;
10769
+ this.events.emit({
10770
+ type: EVENT_TYPES.ERROR,
10771
+ timestamp: Date.now(),
10772
+ data: {
10773
+ message: `LLM Error: ${errorInfo.message}`,
10774
+ phase: this.state.getPhase(),
10775
+ isRecoverable: errorInfo.isRetryable,
10776
+ errorInfo
10777
+ }
10778
+ });
10779
+ if (errorInfo.type !== LLM_ERROR_TYPES.RATE_LIMIT) {
10780
+ messages.push({ role: LLM_ROLES.USER, content: this.formatLLMErrorForAgent(errorInfo) });
10781
+ return { action: "continue" };
10782
+ }
10783
+ const newCount = consecutiveLLMErrors + 1;
10784
+ if (maxConsecutiveLLMErrors !== Infinity && newCount >= maxConsecutiveLLMErrors) {
10785
+ return {
10786
+ action: "return",
10787
+ result: {
10788
+ output: `LLM rate limit errors exceeded. Last error: ${errorInfo.message}`,
10789
+ iterations: iteration + 1,
10790
+ toolsExecuted: progress.totalToolsExecuted,
10791
+ isCompleted: false
10792
+ }
10793
+ };
10794
+ }
10795
+ return { action: "continue" };
10539
10796
  }
10540
10797
  // ─────────────────────────────────────────────────────────────────
10541
10798
  // SUBSECTION: Step Execution
10542
10799
  // ─────────────────────────────────────────────────────────────────
10543
- /** Execute a single Think → Act → Observe iteration */
10544
10800
  async step(iteration, messages, systemPrompt, progress) {
10545
10801
  const phase = this.state.getPhase();
10546
- const stepStartTime = Date.now();
10547
10802
  this.emitThink(iteration, progress);
10803
+ if (this.toolRegistry && !this.toolExecutor) {
10804
+ this.toolExecutor = new ToolExecutor({
10805
+ state: this.state,
10806
+ events: this.events,
10807
+ toolRegistry: this.toolRegistry,
10808
+ llm: this.llm
10809
+ });
10810
+ }
10811
+ this.toolExecutor?.clearTurnState();
10548
10812
  const callbacks = this.buildStreamCallbacks(phase);
10549
10813
  const response = await this.llm.generateResponseStream(
10550
10814
  messages,
10551
- this.getToolSchemas(),
10815
+ this.toolExecutor?.getToolSchemas() ?? [],
10552
10816
  systemPrompt,
10553
10817
  callbacks
10554
10818
  );
@@ -10571,12 +10835,10 @@ Please decide how to handle this error and continue.`;
10571
10835
  }
10572
10836
  }
10573
10837
  messages.push({ role: LLM_ROLES.ASSISTANT, content: response.content });
10574
- const stepDuration = Date.now() - stepStartTime;
10575
- const tokens = response.usage ? { input: response.usage.input_tokens, output: response.usage.output_tokens } : void 0;
10576
10838
  if (!response.toolCalls?.length) {
10577
10839
  return { output: response.content, toolsExecuted: 0, isCompleted: false };
10578
10840
  }
10579
- const results = await this.processToolCalls(response.toolCalls, progress);
10841
+ const results = await this.toolExecutor.processToolCalls(response.toolCalls, progress);
10580
10842
  this.addToolResultsToMessages(messages, results);
10581
10843
  return { output: "", toolsExecuted: results.length, isCompleted: false };
10582
10844
  }
@@ -10586,15 +10848,13 @@ Please decide how to handle this error and continue.`;
10586
10848
  buildStreamCallbacks(phase) {
10587
10849
  let _reasoningEndFired = false;
10588
10850
  let _outputBuffer = "";
10589
- const callbacks = {
10851
+ return {
10590
10852
  onReasoningStart: () => this.emitReasoningStart(phase),
10591
10853
  onReasoningDelta: (content) => this.emitReasoningDelta(content, phase),
10592
10854
  onReasoningEnd: () => {
10593
10855
  _reasoningEndFired = true;
10594
10856
  this.emitReasoningEnd(phase);
10595
10857
  },
10596
- // WHY: Show AI text as it streams, not after completion.
10597
- // The user sees what the AI is writing in real-time via the status bar.
10598
10858
  onOutputDelta: (text) => {
10599
10859
  _outputBuffer += text;
10600
10860
  const firstLine = _outputBuffer.split("\n")[0]?.slice(0, 120) || "";
@@ -10620,57 +10880,63 @@ ${firstLine}`, phase }
10620
10880
  });
10621
10881
  },
10622
10882
  abortSignal: this.abortController?.signal,
10623
- // WHY: Used by step() to detect if SSE reasoning blocks fired.
10624
- // If not, we know it was an inline <think> model and must emit post-stream.
10625
10883
  hadReasoningEnd: () => _reasoningEndFired
10626
10884
  };
10627
- return callbacks;
10628
10885
  }
10629
10886
  // ─────────────────────────────────────────────────────────────────
10630
- // SUBSECTION: Deadlock Nudge Builder
10887
+ // SUBSECTION: Deadlock Nudge
10631
10888
  // ─────────────────────────────────────────────────────────────────
10632
- /**
10633
- * Build a deadlock nudge message for the agent.
10634
- *
10635
- * WHY separated: The nudge template is ~30 lines of prompt engineering.
10636
- * Keeping it in run() obscures the iteration control logic.
10637
- * Philosophy §12: Nudge is a safety net, not a driver —
10638
- * it reminds the agent to ACT, but never prescribes HOW.
10639
- */
10640
10889
  buildDeadlockNudge(progress) {
10641
10890
  const phase = this.state.getPhase();
10642
10891
  const targets = this.state.getTargets().size;
10643
10892
  const findings = this.state.getFindings().length;
10644
10893
  const phaseDirection = {
10645
- [PHASES.RECON]: `RECON: Scan targets. Enumerate services and versions.`,
10646
- [PHASES.VULN_ANALYSIS]: `VULN ANALYSIS: ${targets} target(s) discovered. Search for CVEs and known exploits.`,
10647
- [PHASES.EXPLOIT]: `EXPLOIT: ${findings} finding(s) available. Attack the highest-severity one.`,
10648
- [PHASES.POST_EXPLOIT]: `POST-EXPLOIT: Escalate privileges. Search for escalation paths.`,
10649
- [PHASES.PRIV_ESC]: `PRIVESC: Find and exploit privilege escalation vectors.`,
10650
- [PHASES.LATERAL]: `LATERAL: Reuse discovered credentials on other hosts.`,
10651
- [PHASES.WEB]: `WEB: Enumerate the attack surface. Test every input for injection.`
10894
+ [PHASES.RECON]: `RECON: Scan targets. Enumerate services.`,
10895
+ [PHASES.VULN_ANALYSIS]: `VULN: ${targets} target(s). Search for CVEs.`,
10896
+ [PHASES.EXPLOIT]: `EXPLOIT: ${findings} finding(s). Attack the highest-severity one.`,
10897
+ [PHASES.POST_EXPLOIT]: `POST-EXPLOIT: Escalate privileges.`,
10898
+ [PHASES.PRIV_ESC]: `PRIVESC: Find privilege escalation vectors.`,
10899
+ [PHASES.LATERAL]: `LATERAL: Reuse credentials on other hosts.`,
10900
+ [PHASES.WEB]: `WEB: Enumerate attack surface. Test every input.`
10652
10901
  };
10653
10902
  const direction = phaseDirection[phase] || phaseDirection[PHASES.RECON];
10654
10903
  return `\u26A1 DEADLOCK: ${AGENT_LIMITS.MAX_CONSECUTIVE_IDLE} turns with ZERO tool calls.
10655
- Phase: ${phase} | Targets: ${targets} | Findings: ${findings} | Tools executed: ${progress.totalToolsExecuted} (${progress.toolSuccesses}\u2713 ${progress.toolErrors}\u2717)
10904
+ Phase: ${phase} | Targets: ${targets} | Findings: ${findings}
10656
10905
 
10657
10906
  ${direction}
10658
10907
 
10659
- ESCALATION CHAIN \u2014 follow this order:
10660
- 1. web_search: Search for techniques, bypasses, default creds, CVEs, HackTricks
10661
- 2. BYPASS: Try alternative approaches \u2014 different protocols, ports, encodings, methods
10662
- 3. ZERO-DAY EXPLORATION: Probe for unknown vulns \u2014 fuzz parameters, test edge cases, analyze error responses for leaks
10663
- 4. BRUTE-FORCE: Wordlists, credential stuffing, common passwords, custom password lists from context
10664
- 5. ask_user: ONLY as last resort \u2014 ask the user for hints, wordlists, or guidance
10908
+ ESCALATION:
10909
+ 1. web_search for techniques
10910
+ 2. Try alternative approaches
10911
+ 3. Probe for unknown vulns
10912
+ 4. Brute-force with wordlists
10913
+ 5. ask_user for hints
10914
+
10915
+ ACT NOW \u2014 EXECUTE.`;
10916
+ }
10917
+ // ─────────────────────────────────────────────────────────────────
10918
+ // SUBSECTION: Error Formatting
10919
+ // ─────────────────────────────────────────────────────────────────
10920
+ formatLLMErrorForAgent(errorInfo) {
10921
+ const actionHints = {
10922
+ [LLM_ERROR_TYPES.RATE_LIMIT]: "Wait and retry.",
10923
+ [LLM_ERROR_TYPES.AUTH_ERROR]: "Use ask_user to request API key.",
10924
+ [LLM_ERROR_TYPES.INVALID_REQUEST]: "Simplify your request.",
10925
+ [LLM_ERROR_TYPES.NETWORK_ERROR]: "Check network and retry.",
10926
+ [LLM_ERROR_TYPES.TIMEOUT]: "Retry with simpler request.",
10927
+ [LLM_ERROR_TYPES.UNKNOWN]: "Analyze and decide."
10928
+ };
10929
+ return `[SYSTEM ERROR - LLM API Issue]
10930
+ Error Type: ${errorInfo.type}
10931
+ Message: ${errorInfo.message}
10932
+ Status Code: ${errorInfo.statusCode || "N/A"}
10933
+ Retryable: ${errorInfo.isRetryable ? "Yes" : "No"}
10665
10934
 
10666
- RULES:
10667
- - Every turn MUST have tool calls
10668
- - NEVER silently give up \u2014 exhaust ALL 5 steps above first
10669
- - ACT NOW \u2014 do not plan, do not explain, do not summarize. EXECUTE.`;
10935
+ Suggested Action: ${errorInfo.suggestedAction || actionHints[errorInfo.type] || "Decide appropriate action"}`;
10670
10936
  }
10671
10937
  // ─────────────────────────────────────────────────────────────────
10672
10938
  // SUBSECTION: Event Emitters
10673
- // ─────────────────════════════════════════════════════════════
10939
+ // ─────────────────────────────────────────────────────────────────
10674
10940
  emitThink(iteration, progress) {
10675
10941
  const phase = this.state.getPhase();
10676
10942
  const targets = this.state.getTargets().size;
@@ -10679,26 +10945,22 @@ RULES:
10679
10945
  const hasErrors = (progress?.toolErrors ?? 0) > 0;
10680
10946
  let thought;
10681
10947
  if (iteration === 0) {
10682
- thought = targets > 0 ? `Analyzing ${targets} target${targets > 1 ? "s" : ""} \xB7 Planning ${phase} approach` : `Reviewing task \xB7 Building ${phase} strategy`;
10948
+ thought = targets > 0 ? `Analyzing ${targets} target(s) \xB7 Planning ${phase} approach` : `Reviewing task \xB7 Building ${phase} strategy`;
10683
10949
  } else if (toolsUsed === 0) {
10684
- thought = `Iteration ${iteration + 1} \xB7 No actions yet \xB7 Reconsidering approach`;
10950
+ thought = `Iteration ${iteration + 1} \xB7 No actions yet \xB7 Reconsidering`;
10685
10951
  } else if (hasErrors) {
10686
- thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tools \xB7 ${progress?.toolErrors} error${(progress?.toolErrors ?? 0) > 1 ? "s" : ""} \xB7 Adapting strategy`;
10952
+ thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tools \xB7 ${progress?.toolErrors} error(s)`;
10687
10953
  } else if (findings > 0) {
10688
- thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${findings} finding${findings > 1 ? "s" : ""} \xB7 ${toolsUsed} tools \xB7 Continuing`;
10954
+ thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${findings} finding(s) \xB7 ${toolsUsed} tools`;
10689
10955
  } else {
10690
- thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tool${toolsUsed !== 1 ? "s" : ""} executed \xB7 Evaluating results`;
10956
+ thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tool(s) executed`;
10691
10957
  }
10692
10958
  this.events.emit({
10693
10959
  type: EVENT_TYPES.THINK,
10694
10960
  timestamp: Date.now(),
10695
- data: {
10696
- thought,
10697
- phase
10698
- }
10961
+ data: { thought, phase }
10699
10962
  });
10700
10963
  }
10701
- /** Emit reasoning lifecycle events for extended thinking */
10702
10964
  emitReasoningStart(phase) {
10703
10965
  this.events.emit({ type: EVENT_TYPES.REASONING_START, timestamp: Date.now(), data: { phase } });
10704
10966
  }
@@ -10712,308 +10974,25 @@ RULES:
10712
10974
  this.events.emit({
10713
10975
  type: EVENT_TYPES.COMPLETE,
10714
10976
  timestamp: Date.now(),
10715
- data: {
10716
- finalOutput: output,
10717
- iterations: iteration + 1,
10718
- toolsExecuted,
10719
- durationMs,
10720
- tokens
10721
- }
10722
- });
10723
- }
10724
- /** Emit tool call event for TUI tracking */
10725
- emitToolCall(toolName, input) {
10726
- this.events.emit({
10727
- type: EVENT_TYPES.TOOL_CALL,
10728
- timestamp: Date.now(),
10729
- data: {
10730
- toolName,
10731
- input,
10732
- approvalLevel: APPROVAL_LEVELS.AUTO,
10733
- needsApproval: false
10734
- }
10735
- });
10736
- }
10737
- /** Emit tool result event for TUI tracking */
10738
- emitToolResult(toolName, success, output, error, duration) {
10739
- this.events.emit({
10740
- type: EVENT_TYPES.TOOL_RESULT,
10741
- timestamp: Date.now(),
10742
- data: {
10743
- toolName,
10744
- success,
10745
- output,
10746
- outputSummary: output.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
10747
- error,
10748
- duration
10749
- }
10977
+ data: { finalOutput: output, iterations: iteration + 1, toolsExecuted, durationMs, tokens }
10750
10978
  });
10751
10979
  }
10752
10980
  // ─────────────────────────────────────────────────────────────────
10753
- // SUBSECTION: Tool Processing
10981
+ // SUBSECTION: Message Helpers
10754
10982
  // ─────────────────────────────────────────────────────────────────
10755
10983
  addToolResultsToMessages(messages, results) {
10756
10984
  for (const res of results) {
10757
10985
  messages.push({
10758
10986
  role: LLM_ROLES.USER,
10759
- content: [
10760
- {
10761
- type: LLM_BLOCK_TYPE.TOOL_RESULT,
10762
- tool_use_id: res.toolCallId,
10763
- content: res.output,
10764
- is_error: !!res.error
10765
- }
10766
- ]
10987
+ content: [{
10988
+ type: LLM_BLOCK_TYPE.TOOL_RESULT,
10989
+ tool_use_id: res.toolCallId,
10990
+ content: res.output,
10991
+ is_error: !!res.error
10992
+ }]
10767
10993
  });
10768
10994
  }
10769
10995
  }
10770
- /** Tools that are safe to run in parallel (read-only or per-key state mutations) */
10771
- static PARALLEL_SAFE_TOOLS = /* @__PURE__ */ new Set([
10772
- // Read-only intelligence tools
10773
- TOOL_NAMES.GET_STATE,
10774
- TOOL_NAMES.PARSE_NMAP,
10775
- TOOL_NAMES.SEARCH_CVE,
10776
- TOOL_NAMES.WEB_SEARCH,
10777
- TOOL_NAMES.BROWSE_URL,
10778
- TOOL_NAMES.READ_FILE,
10779
- TOOL_NAMES.GET_OWASP_KNOWLEDGE,
10780
- TOOL_NAMES.GET_WEB_ATTACK_SURFACE,
10781
- TOOL_NAMES.GET_CVE_INFO,
10782
- TOOL_NAMES.FILL_FORM,
10783
- // State recording (per-key mutations, no external side effects)
10784
- TOOL_NAMES.ADD_TARGET,
10785
- TOOL_NAMES.ADD_FINDING,
10786
- TOOL_NAMES.ADD_LOOT,
10787
- TOOL_NAMES.UPDATE_MISSION,
10788
- TOOL_NAMES.UPDATE_TODO
10789
- ]);
10790
- async processToolCalls(toolCalls, progress) {
10791
- if (toolCalls.length <= 1) {
10792
- return this.executeToolCallsSequentially(toolCalls, progress);
10793
- }
10794
- const allParallelSafe = toolCalls.every((c) => _CoreAgent.PARALLEL_SAFE_TOOLS.has(c.name));
10795
- if (allParallelSafe) {
10796
- return this.executeToolCallsInParallel(toolCalls, progress);
10797
- }
10798
- const parallelCalls = toolCalls.filter((c) => _CoreAgent.PARALLEL_SAFE_TOOLS.has(c.name));
10799
- const sequentialCalls = toolCalls.filter((c) => !_CoreAgent.PARALLEL_SAFE_TOOLS.has(c.name));
10800
- const parallelResults = parallelCalls.length > 0 ? await this.executeToolCallsInParallel(parallelCalls, progress) : [];
10801
- const sequentialResults = sequentialCalls.length > 0 ? await this.executeToolCallsSequentially(sequentialCalls, progress) : [];
10802
- const resultMap = /* @__PURE__ */ new Map();
10803
- for (const r of [...parallelResults, ...sequentialResults]) {
10804
- resultMap.set(r.toolCallId, r);
10805
- }
10806
- return toolCalls.map((c) => resultMap.get(c.id)).filter(Boolean);
10807
- }
10808
- /** Execute tool calls in parallel via Promise.allSettled */
10809
- async executeToolCallsInParallel(toolCalls, progress) {
10810
- for (const call of toolCalls) {
10811
- this.emitToolCall(call.name, call.input);
10812
- }
10813
- const promises = toolCalls.map((call) => this.executeSingleTool(call, progress));
10814
- const settled = await Promise.allSettled(promises);
10815
- return settled.map((s, i) => {
10816
- if (s.status === "fulfilled") return s.value;
10817
- const errorMsg = String(s.reason);
10818
- return { toolCallId: toolCalls[i].id, output: errorMsg, error: errorMsg };
10819
- });
10820
- }
10821
- /** Execute tool calls sequentially */
10822
- async executeToolCallsSequentially(toolCalls, progress) {
10823
- const results = [];
10824
- for (const call of toolCalls) {
10825
- this.emitToolCall(call.name, call.input);
10826
- const result2 = await this.executeSingleTool(call, progress);
10827
- results.push(result2);
10828
- }
10829
- return results;
10830
- }
10831
- /** Execute a single tool call with error enrichment and event emission */
10832
- async executeSingleTool(call, progress) {
10833
- const toolStartTime = Date.now();
10834
- logLLM("CoreAgent executing tool", { id: call.id, name: call.name, input: call.input });
10835
- if (!this.toolRegistry) {
10836
- return { toolCallId: call.id, output: "", error: "Tool registry not initialized. Call setToolRegistry() first." };
10837
- }
10838
- try {
10839
- const result2 = await this.toolRegistry.execute({ name: call.name, input: call.input });
10840
- let outputText = result2.output ?? "";
10841
- this.scanForFlags(outputText);
10842
- outputText = this.handleToolResult(result2, call, outputText, progress);
10843
- const { digestedOutputForLLM, digestResult } = await this.digestAndEmit(
10844
- call,
10845
- outputText,
10846
- result2,
10847
- toolStartTime
10848
- );
10849
- this.recordJournalMemo(call, result2, digestedOutputForLLM, digestResult);
10850
- return { toolCallId: call.id, output: digestedOutputForLLM, error: result2.error };
10851
- } catch (error) {
10852
- const errorMsg = String(error);
10853
- const enrichedError = this.enrichToolError({ toolName: call.name, input: call.input, error: errorMsg, originalOutput: "", progress });
10854
- if (progress) progress.toolErrors++;
10855
- this.emitToolResult(call.name, false, enrichedError, errorMsg, Date.now() - toolStartTime);
10856
- return { toolCallId: call.id, output: enrichedError, error: errorMsg };
10857
- }
10858
- }
10859
- /**
10860
- * Handle tool result: enrich errors or track success.
10861
- * @returns Possibly enriched output text.
10862
- */
10863
- handleToolResult(result2, call, outputText, progress) {
10864
- if (result2.error) {
10865
- if (progress) progress.toolErrors++;
10866
- return this.enrichToolError({ toolName: call.name, input: call.input, error: result2.error, originalOutput: outputText, progress });
10867
- }
10868
- if (progress) {
10869
- progress.toolSuccesses++;
10870
- progress.blockedCommandPatterns.clear();
10871
- }
10872
- return outputText;
10873
- }
10874
- /**
10875
- * Digest tool output via Analyst LLM (§13 ③) and emit TUI event.
10876
- *
10877
- * WHY separated: Digest + emit is a self-contained pipeline:
10878
- * raw output → Analyst → digest + file → TUI event.
10879
- * Isolating it makes the pipeline testable without running actual tools.
10880
- */
10881
- async digestAndEmit(call, outputText, result2, toolStartTime) {
10882
- const digestFallbackOutput = outputText;
10883
- let digestedOutputForLLM = outputText;
10884
- let digestResult = null;
10885
- try {
10886
- const llmDigestFn = createLLMDigestFn(this.llm);
10887
- digestResult = await digestToolOutput(
10888
- outputText,
10889
- call.name,
10890
- JSON.stringify(call.input).slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
10891
- llmDigestFn
10892
- );
10893
- digestedOutputForLLM = digestResult.digestedOutput;
10894
- } catch {
10895
- if (digestedOutputForLLM.length > AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH) {
10896
- const truncated = digestedOutputForLLM.slice(0, AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH);
10897
- const remaining = digestedOutputForLLM.length - AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH;
10898
- digestedOutputForLLM = `${truncated}
10899
-
10900
- ... [TRUNCATED ${remaining} characters for context hygiene] ...
10901
- \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.`;
10902
- }
10903
- }
10904
- const outputFilePath = digestResult?.fullOutputPath ?? null;
10905
- const tuiOutput = digestResult?.digestedOutput ? `${digestResult.digestedOutput}${outputFilePath ? `
10906
- \u{1F4C4} Full output: ${outputFilePath}` : ""}` : digestFallbackOutput.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY);
10907
- this.emitToolResult(call.name, result2.success, tuiOutput, result2.error, Date.now() - toolStartTime);
10908
- return { digestedOutputForLLM, digestResult };
10909
- }
10910
- /**
10911
- * Record tool execution results to Journal and aggregate memos.
10912
- *
10913
- * WHY no truncation on inputSummary: Strategist needs full context —
10914
- * "hydra -l admin -P rockyou.txt ssh://10.0.0.1" must survive intact.
10915
- */
10916
- recordJournalMemo(call, result2, digestedOutputForLLM, digestResult) {
10917
- this.turnToolJournal.push({
10918
- name: call.name,
10919
- inputSummary: JSON.stringify(call.input),
10920
- success: result2.success,
10921
- analystSummary: digestResult?.memo ? digestResult.memo.keyFindings.join("; ") || "No key findings" : digestedOutputForLLM,
10922
- outputFile: digestResult?.fullOutputPath ?? null
10923
- });
10924
- if (digestResult?.memo) {
10925
- const m = digestResult.memo;
10926
- this.turnMemo.keyFindings.push(...m.keyFindings);
10927
- this.turnMemo.credentials.push(...m.credentials);
10928
- this.turnMemo.attackVectors.push(...m.attackVectors);
10929
- this.turnMemo.failures.push(...m.failures);
10930
- this.turnMemo.suspicions.push(...m.suspicions);
10931
- if ((ATTACK_VALUE_RANK[m.attackValue] ?? 0) > (ATTACK_VALUE_RANK[this.turnMemo.attackValue] ?? 0)) {
10932
- this.turnMemo.attackValue = m.attackValue;
10933
- }
10934
- this.turnMemo.nextSteps.push(...m.nextSteps);
10935
- if (m.reflection) this.turnReflections.push(m.reflection);
10936
- }
10937
- if (digestResult?.memo?.credentials.length) {
10938
- for (const cred of digestResult.memo.credentials) {
10939
- this.state.addLoot({ type: LOOT_TYPES.CREDENTIAL, host: "auto-extracted", detail: cred, obtainedAt: Date.now() });
10940
- }
10941
- }
10942
- if (digestResult?.memo?.attackVectors.length && digestResult.memo.attackValue === "HIGH") {
10943
- const existingTitles = new Set(this.state.getFindings().map((f) => f.title));
10944
- for (const vector of digestResult.memo.attackVectors) {
10945
- const title = `[Auto] ${vector.slice(0, 100)}`;
10946
- if (!existingTitles.has(title)) {
10947
- this.state.addFinding({
10948
- id: generateId(),
10949
- title,
10950
- severity: "high",
10951
- // Auto-extracted findings are unverified signals — score POSSIBLE (25)
10952
- confidence: CONFIDENCE_THRESHOLDS.POSSIBLE,
10953
- affected: [],
10954
- description: `Auto-extracted by Analyst LLM: ${vector}`,
10955
- evidence: digestResult.memo.keyFindings.slice(0, 5),
10956
- remediation: "",
10957
- foundAt: Date.now()
10958
- });
10959
- this.state.attackGraph.addVulnerability(title, "auto-detected", "high", false);
10960
- existingTitles.add(title);
10961
- }
10962
- }
10963
- }
10964
- if (this.state.getFindings().length > 0 && this.state.getPhase() === PHASES.RECON) {
10965
- this.state.setPhase(PHASES.VULN_ANALYSIS);
10966
- }
10967
- }
10968
- /**
10969
- * Enrich tool error — delegates to extracted module (§3-1)
10970
- */
10971
- enrichToolError(ctx) {
10972
- return enrichToolErrorContext(ctx);
10973
- }
10974
- getToolSchemas() {
10975
- if (!this.toolRegistry) {
10976
- return [];
10977
- }
10978
- return this.toolRegistry.getAll().map((t) => ({
10979
- name: t.name,
10980
- description: t.description,
10981
- input_schema: {
10982
- type: "object",
10983
- properties: t.parameters,
10984
- required: t.required || []
10985
- }
10986
- }));
10987
- }
10988
- // ─────────────────────────────────────────────────────────────────
10989
- // SUBSECTION: CTF Flag Detection
10990
- // ─────────────────────────────────────────────────────────────────
10991
- /**
10992
- * Scan tool output for CTF flag patterns.
10993
- * When a new flag is detected, store it in state and emit event.
10994
- *
10995
- * @remarks
10996
- * WHY: Automatic flag detection eliminates manual flag hunting in CTF competitions.
10997
- * Called after every tool execution — zero overhead when CTF mode is OFF.
10998
- */
10999
- scanForFlags(output) {
11000
- if (!this.state.isCtfMode()) return;
11001
- const flags = detectFlags(output);
11002
- for (const flag of flags) {
11003
- const isNew = this.state.addFlag(flag);
11004
- if (isNew) {
11005
- this.events.emit({
11006
- type: EVENT_TYPES.FLAG_FOUND,
11007
- timestamp: Date.now(),
11008
- data: {
11009
- flag,
11010
- totalFlags: this.state.getFlags().length,
11011
- phase: this.state.getPhase()
11012
- }
11013
- });
11014
- }
11015
- }
11016
- }
11017
10996
  // ─────────────────────────────────────────────────────────────────
11018
10997
  // SUBSECTION: Abort Helpers
11019
10998
  // ─────────────────────────────────────────────────────────────────
@@ -11031,8 +11010,8 @@ RULES:
11031
11010
  };
11032
11011
 
11033
11012
  // src/agents/prompt-builder.ts
11034
- import { readFileSync as readFileSync6, existsSync as existsSync9, readdirSync as readdirSync3 } from "fs";
11035
- import { join as join10, dirname as dirname4 } from "path";
11013
+ import { readFileSync as readFileSync6, existsSync as existsSync10, readdirSync as readdirSync4 } from "fs";
11014
+ import { join as join11, dirname as dirname4 } from "path";
11036
11015
  import { fileURLToPath as fileURLToPath2 } from "url";
11037
11016
 
11038
11017
  // src/shared/constants/prompts.ts
@@ -11306,19 +11285,19 @@ function getAttacksForService(service, port) {
11306
11285
  }
11307
11286
 
11308
11287
  // src/shared/utils/journal.ts
11309
- import { writeFileSync as writeFileSync8, readFileSync as readFileSync5, existsSync as existsSync8, readdirSync as readdirSync2, statSync as statSync2, rmSync as rmSync2 } from "fs";
11310
- import { join as join9 } from "path";
11288
+ import { writeFileSync as writeFileSync9, readFileSync as readFileSync5, existsSync as existsSync9, readdirSync as readdirSync3, statSync as statSync3, rmSync as rmSync2 } from "fs";
11289
+ import { join as join10 } from "path";
11311
11290
  function parseTurnNumbers(turnsDir) {
11312
- if (!existsSync8(turnsDir)) return [];
11313
- return readdirSync2(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)));
11291
+ if (!existsSync9(turnsDir)) return [];
11292
+ 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)));
11314
11293
  }
11315
11294
  function readJournalSummary() {
11316
11295
  try {
11317
11296
  const turnsDir = WORKSPACE.TURNS;
11318
11297
  const turnDirs = parseTurnNumbers(turnsDir).sort((a, b) => b - a);
11319
11298
  for (const turn of turnDirs) {
11320
- const summaryPath = join9(WORKSPACE.turnPath(turn), TURN_FILES.SUMMARY);
11321
- if (existsSync8(summaryPath)) {
11299
+ const summaryPath = join10(WORKSPACE.turnPath(turn), TURN_FILES.SUMMARY);
11300
+ if (existsSync9(summaryPath)) {
11322
11301
  return readFileSync5(summaryPath, "utf-8");
11323
11302
  }
11324
11303
  }
@@ -11334,8 +11313,8 @@ function getRecentEntries(count = MEMORY_LIMITS.MAX_TURN_ENTRIES) {
11334
11313
  const entries = [];
11335
11314
  for (const turn of turnDirs) {
11336
11315
  try {
11337
- const filePath = join9(WORKSPACE.turnPath(turn), TURN_FILES.STRUCTURED);
11338
- if (existsSync8(filePath)) {
11316
+ const filePath = join10(WORKSPACE.turnPath(turn), TURN_FILES.STRUCTURED);
11317
+ if (existsSync9(filePath)) {
11339
11318
  const raw = readFileSync5(filePath, "utf-8");
11340
11319
  entries.push(JSON.parse(raw));
11341
11320
  }
@@ -11366,8 +11345,8 @@ function regenerateJournalSummary() {
11366
11345
  const turnDir = WORKSPACE.turnPath(latestTurn);
11367
11346
  ensureDirExists(turnDir);
11368
11347
  const summary = buildSummaryFromEntries(entries);
11369
- const summaryPath = join9(turnDir, TURN_FILES.SUMMARY);
11370
- writeFileSync8(summaryPath, summary, "utf-8");
11348
+ const summaryPath = join10(turnDir, TURN_FILES.SUMMARY);
11349
+ writeFileSync9(summaryPath, summary, "utf-8");
11371
11350
  debugLog("general", "Journal summary regenerated", {
11372
11351
  entries: entries.length,
11373
11352
  summaryLength: summary.length
@@ -11472,13 +11451,13 @@ function formatSummaryMarkdown(buckets, entries) {
11472
11451
  function rotateTurnRecords() {
11473
11452
  try {
11474
11453
  const turnsDir = WORKSPACE.TURNS;
11475
- if (!existsSync8(turnsDir)) return;
11476
- const turnDirs = parseTurnNumbers(turnsDir).map((n) => `${TURN_FOLDER_PREFIX}${n}`).filter((e) => statSync2(join9(turnsDir, e)).isDirectory()).sort((a, b) => Number(a.slice(TURN_FOLDER_PREFIX.length)) - Number(b.slice(TURN_FOLDER_PREFIX.length)));
11454
+ if (!existsSync9(turnsDir)) return;
11455
+ 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)));
11477
11456
  if (turnDirs.length > MEMORY_LIMITS.MAX_TURN_ENTRIES) {
11478
11457
  const dirsToDel = turnDirs.slice(0, turnDirs.length - MEMORY_LIMITS.MAX_TURN_ENTRIES);
11479
11458
  for (const dir of dirsToDel) {
11480
11459
  try {
11481
- rmSync2(join9(turnsDir, dir), { recursive: true, force: true });
11460
+ rmSync2(join10(turnsDir, dir), { recursive: true, force: true });
11482
11461
  } catch {
11483
11462
  }
11484
11463
  }
@@ -11493,8 +11472,8 @@ function rotateTurnRecords() {
11493
11472
 
11494
11473
  // src/agents/prompt-builder.ts
11495
11474
  var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
11496
- var PROMPTS_DIR = join10(__dirname2, "prompts");
11497
- var TECHNIQUES_DIR = join10(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
11475
+ var PROMPTS_DIR = join11(__dirname2, "prompts");
11476
+ var TECHNIQUES_DIR = join11(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
11498
11477
  var { AGENT_FILES } = PROMPT_PATHS;
11499
11478
  var PHASE_PROMPT_MAP = {
11500
11479
  // Direct mappings — phase has its own prompt file
@@ -11627,8 +11606,8 @@ ${content}
11627
11606
  * Load a prompt file from src/agents/prompts/
11628
11607
  */
11629
11608
  loadPromptFile(filename) {
11630
- const path2 = join10(PROMPTS_DIR, filename);
11631
- return existsSync9(path2) ? readFileSync6(path2, PROMPT_CONFIG.ENCODING) : "";
11609
+ const path2 = join11(PROMPTS_DIR, filename);
11610
+ return existsSync10(path2) ? readFileSync6(path2, PROMPT_CONFIG.ENCODING) : "";
11632
11611
  }
11633
11612
  /**
11634
11613
  * Load phase-specific prompt.
@@ -11674,14 +11653,14 @@ ${content}
11674
11653
  * "Drop a markdown file in the folder, PromptBuilder auto-discovers and loads it."
11675
11654
  */
11676
11655
  loadPhaseRelevantTechniques(phase) {
11677
- if (!existsSync9(TECHNIQUES_DIR)) return "";
11656
+ if (!existsSync10(TECHNIQUES_DIR)) return "";
11678
11657
  const priorityTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
11679
11658
  const loadedSet = /* @__PURE__ */ new Set();
11680
11659
  const fragments = [];
11681
11660
  for (const technique of priorityTechniques) {
11682
- const filePath = join10(TECHNIQUES_DIR, `${technique}.md`);
11661
+ const filePath = join11(TECHNIQUES_DIR, `${technique}.md`);
11683
11662
  try {
11684
- if (!existsSync9(filePath)) continue;
11663
+ if (!existsSync10(filePath)) continue;
11685
11664
  const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
11686
11665
  if (content) {
11687
11666
  fragments.push(`<technique-reference category="${technique}">
@@ -11693,9 +11672,9 @@ ${content}
11693
11672
  }
11694
11673
  }
11695
11674
  try {
11696
- const allFiles = readdirSync3(TECHNIQUES_DIR).filter((f) => f.endsWith(".md") && f !== "README.md" && !loadedSet.has(f));
11675
+ const allFiles = readdirSync4(TECHNIQUES_DIR).filter((f) => f.endsWith(".md") && f !== "README.md" && !loadedSet.has(f));
11697
11676
  for (const file of allFiles) {
11698
- const filePath = join10(TECHNIQUES_DIR, file);
11677
+ const filePath = join11(TECHNIQUES_DIR, file);
11699
11678
  const content = readFileSync6(filePath, PROMPT_CONFIG.ENCODING);
11700
11679
  if (content) {
11701
11680
  const category = file.replace(".md", "");
@@ -11826,11 +11805,11 @@ ${summary}
11826
11805
  };
11827
11806
 
11828
11807
  // src/agents/strategist.ts
11829
- import { readFileSync as readFileSync7, existsSync as existsSync10 } from "fs";
11830
- import { join as join11, dirname as dirname5 } from "path";
11808
+ import { readFileSync as readFileSync7, existsSync as existsSync11 } from "fs";
11809
+ import { join as join12, dirname as dirname5 } from "path";
11831
11810
  import { fileURLToPath as fileURLToPath3 } from "url";
11832
11811
  var __dirname3 = dirname5(fileURLToPath3(import.meta.url));
11833
- var STRATEGIST_PROMPT_PATH = join11(__dirname3, "prompts", "strategist-system.md");
11812
+ var STRATEGIST_PROMPT_PATH = join12(__dirname3, "prompts", "strategist-system.md");
11834
11813
  var Strategist = class {
11835
11814
  llm;
11836
11815
  state;
@@ -11969,7 +11948,7 @@ NOTE: This directive is from ${age}min ago (Strategist call failed this turn). V
11969
11948
  // ─── System Prompt Loading ──────────────────────────────────
11970
11949
  loadSystemPrompt() {
11971
11950
  try {
11972
- if (existsSync10(STRATEGIST_PROMPT_PATH)) {
11951
+ if (existsSync11(STRATEGIST_PROMPT_PATH)) {
11973
11952
  return readFileSync7(STRATEGIST_PROMPT_PATH, "utf-8");
11974
11953
  }
11975
11954
  } catch {
@@ -12283,8 +12262,8 @@ function formatReflectionInput(input) {
12283
12262
  }
12284
12263
 
12285
12264
  // src/agents/main-agent.ts
12286
- import { writeFileSync as writeFileSync9, existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
12287
- import { join as join12 } from "path";
12265
+ import { writeFileSync as writeFileSync10, existsSync as existsSync12, readFileSync as readFileSync8 } from "fs";
12266
+ import { join as join13 } from "path";
12288
12267
  var MainAgent = class extends CoreAgent {
12289
12268
  promptBuilder;
12290
12269
  strategist;
@@ -12348,11 +12327,11 @@ var MainAgent = class extends CoreAgent {
12348
12327
  });
12349
12328
  }
12350
12329
  }
12351
- this.turnToolJournal = [];
12352
- this.turnMemo = { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
12353
- this.turnReflections = [];
12354
12330
  const dynamicPrompt = await this.getCurrentPrompt();
12355
12331
  const result2 = await super.step(iteration, messages, dynamicPrompt, progress);
12332
+ const turnToolJournal = this.getTurnToolJournal();
12333
+ const turnMemo = this.getTurnMemo();
12334
+ const turnReflections = this.getTurnReflections();
12356
12335
  try {
12357
12336
  if (messages.length > 2) {
12358
12337
  const extraction = await this.llm.generateResponse(
@@ -12373,13 +12352,13 @@ ${extraction.content.trim()}
12373
12352
  } catch {
12374
12353
  }
12375
12354
  try {
12376
- if (this.turnToolJournal.length > 0) {
12355
+ if (turnToolJournal.length > 0) {
12377
12356
  const reflection = await this.llm.generateResponse(
12378
12357
  [{
12379
12358
  role: "user",
12380
12359
  content: formatReflectionInput({
12381
- tools: this.turnToolJournal,
12382
- memo: this.turnMemo,
12360
+ tools: turnToolJournal,
12361
+ memo: turnMemo,
12383
12362
  phase: this.state.getPhase()
12384
12363
  })
12385
12364
  }],
@@ -12387,20 +12366,20 @@ ${extraction.content.trim()}
12387
12366
  REFLECTION_PROMPT
12388
12367
  );
12389
12368
  if (reflection.content?.trim()) {
12390
- this.turnReflections.push(reflection.content.trim());
12369
+ turnReflections.push(reflection.content.trim());
12391
12370
  }
12392
12371
  }
12393
12372
  } catch {
12394
12373
  }
12395
- if (this.turnToolJournal.length > 0) {
12374
+ if (turnToolJournal.length > 0) {
12396
12375
  try {
12397
12376
  const entry = {
12398
12377
  turn: this.turnCounter,
12399
12378
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12400
12379
  phase: this.state.getPhase(),
12401
- tools: this.turnToolJournal,
12402
- memo: this.turnMemo,
12403
- reflection: this.turnReflections.length > 0 ? this.turnReflections.join(" | ") : this.turnMemo.nextSteps.join("; ")
12380
+ tools: turnToolJournal,
12381
+ memo: turnMemo,
12382
+ reflection: turnReflections.length > 0 ? turnReflections.join(" | ") : turnMemo.nextSteps.join("; ")
12404
12383
  };
12405
12384
  try {
12406
12385
  const turnDir = WORKSPACE.turnPath(this.turnCounter);
@@ -12411,32 +12390,32 @@ ${extraction.content.trim()}
12411
12390
  turn: this.turnCounter,
12412
12391
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12413
12392
  phase: this.state.getPhase(),
12414
- tools: this.turnToolJournal,
12415
- memo: this.turnMemo,
12393
+ tools: turnToolJournal,
12394
+ memo: turnMemo,
12416
12395
  reflection: entry.reflection
12417
12396
  });
12418
- writeFileSync9(join12(turnDir, TURN_FILES.RECORD), turnContent, "utf-8");
12419
- writeFileSync9(join12(turnDir, TURN_FILES.STRUCTURED), JSON.stringify(entry, null, 2), "utf-8");
12397
+ writeFileSync10(join13(turnDir, TURN_FILES.RECORD), turnContent, "utf-8");
12398
+ writeFileSync10(join13(turnDir, TURN_FILES.STRUCTURED), JSON.stringify(entry, null, 2), "utf-8");
12420
12399
  const memoLines = [];
12421
- if (this.turnMemo.keyFindings.length > 0) memoLines.push("## Key Findings", ...this.turnMemo.keyFindings.map((f) => `- ${f}`), "");
12422
- if (this.turnMemo.credentials.length > 0) memoLines.push("## Credentials", ...this.turnMemo.credentials.map((c) => `- ${c}`), "");
12423
- if (this.turnMemo.attackVectors.length > 0) memoLines.push("## Attack Vectors", ...this.turnMemo.attackVectors.map((v) => `- ${v}`), "");
12424
- if (this.turnMemo.failures.length > 0) memoLines.push("## Failures", ...this.turnMemo.failures.map((f) => `- ${f}`), "");
12425
- if (this.turnMemo.suspicions.length > 0) memoLines.push("## Suspicious", ...this.turnMemo.suspicions.map((s) => `- ${s}`), "");
12426
- if (this.turnMemo.nextSteps.length > 0) memoLines.push("## Next Steps", ...this.turnMemo.nextSteps.map((n) => `- ${n}`), "");
12400
+ if (turnMemo.keyFindings.length > 0) memoLines.push("## Key Findings", ...turnMemo.keyFindings.map((f) => `- ${f}`), "");
12401
+ if (turnMemo.credentials.length > 0) memoLines.push("## Credentials", ...turnMemo.credentials.map((c) => `- ${c}`), "");
12402
+ if (turnMemo.attackVectors.length > 0) memoLines.push("## Attack Vectors", ...turnMemo.attackVectors.map((v) => `- ${v}`), "");
12403
+ if (turnMemo.failures.length > 0) memoLines.push("## Failures", ...turnMemo.failures.map((f) => `- ${f}`), "");
12404
+ if (turnMemo.suspicions.length > 0) memoLines.push("## Suspicious", ...turnMemo.suspicions.map((s) => `- ${s}`), "");
12405
+ if (turnMemo.nextSteps.length > 0) memoLines.push("## Next Steps", ...turnMemo.nextSteps.map((n) => `- ${n}`), "");
12427
12406
  if (memoLines.length > 0) {
12428
- writeFileSync9(join12(turnDir, TURN_FILES.ANALYST), memoLines.join("\n"), "utf-8");
12407
+ writeFileSync10(join13(turnDir, TURN_FILES.ANALYST), memoLines.join("\n"), "utf-8");
12429
12408
  }
12430
12409
  } catch {
12431
12410
  }
12432
12411
  try {
12433
12412
  const turnDir = WORKSPACE.turnPath(this.turnCounter);
12434
- const summaryPath = join12(turnDir, TURN_FILES.SUMMARY);
12413
+ const summaryPath = join13(turnDir, TURN_FILES.SUMMARY);
12435
12414
  const prevTurn = this.turnCounter - 1;
12436
12415
  let existingSummary = "";
12437
12416
  if (prevTurn >= 1) {
12438
- const prevSummaryPath = join12(WORKSPACE.turnPath(prevTurn), TURN_FILES.SUMMARY);
12439
- if (existsSync11(prevSummaryPath)) {
12417
+ const prevSummaryPath = join13(WORKSPACE.turnPath(prevTurn), TURN_FILES.SUMMARY);
12418
+ if (existsSync12(prevSummaryPath)) {
12440
12419
  existingSummary = readFileSync8(prevSummaryPath, "utf-8");
12441
12420
  }
12442
12421
  }
@@ -12444,8 +12423,8 @@ ${extraction.content.trim()}
12444
12423
  turn: this.turnCounter,
12445
12424
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12446
12425
  phase: this.state.getPhase(),
12447
- tools: this.turnToolJournal,
12448
- memo: this.turnMemo,
12426
+ tools: turnToolJournal,
12427
+ memo: turnMemo,
12449
12428
  reflection: entry.reflection
12450
12429
  });
12451
12430
  const summaryResponse = await this.llm.generateResponse(
@@ -12463,7 +12442,7 @@ ${turnData}`
12463
12442
  );
12464
12443
  if (summaryResponse.content?.trim()) {
12465
12444
  ensureDirExists(turnDir);
12466
- writeFileSync9(summaryPath, summaryResponse.content.trim(), "utf-8");
12445
+ writeFileSync10(summaryPath, summaryResponse.content.trim(), "utf-8");
12467
12446
  }
12468
12447
  } catch {
12469
12448
  regenerateJournalSummary();
@@ -12598,27 +12577,19 @@ ${turnData}`
12598
12577
  };
12599
12578
 
12600
12579
  // src/agents/factory.ts
12601
- var AgentFactory = class {
12602
- /**
12603
- * Create a fully initialized MainAgent system.
12604
- *
12605
- * Architecture: Single agent with all tools.
12606
- * No sub-agents, no spawn_sub, no nested loops.
12607
- */
12608
- static createMainAgent(shouldAutoApprove = false) {
12609
- const state = new SharedState();
12610
- const events = new AgentEventEmitter();
12611
- const approvalGate = new ApprovalGate(shouldAutoApprove);
12612
- const scopeGuard = new ScopeGuard(state);
12613
- const toolRegistry = new CategorizedToolRegistry(
12614
- state,
12615
- scopeGuard,
12616
- approvalGate,
12617
- events
12618
- );
12619
- return new MainAgent(state, events, toolRegistry, approvalGate, scopeGuard);
12620
- }
12621
- };
12580
+ function createMainAgent(shouldAutoApprove = false) {
12581
+ const state = new SharedState();
12582
+ const events = new AgentEventEmitter();
12583
+ const approvalGate = new ApprovalGate(shouldAutoApprove);
12584
+ const scopeGuard = new ScopeGuard(state);
12585
+ const toolRegistry = new CategorizedToolRegistry(
12586
+ state,
12587
+ scopeGuard,
12588
+ approvalGate,
12589
+ events
12590
+ );
12591
+ return new MainAgent(state, events, toolRegistry, approvalGate, scopeGuard);
12592
+ }
12622
12593
 
12623
12594
  // src/platform/tui/utils/format.ts
12624
12595
  var formatDuration2 = (ms) => {
@@ -13242,7 +13213,7 @@ function getCommandEventIcon(eventType) {
13242
13213
 
13243
13214
  // src/platform/tui/hooks/useAgent.ts
13244
13215
  var useAgent = (shouldAutoApprove, target) => {
13245
- const [agent] = useState2(() => AgentFactory.createMainAgent(shouldAutoApprove));
13216
+ const [agent] = useState2(() => createMainAgent(shouldAutoApprove));
13246
13217
  const eventsRef = useRef3(agent.getEventEmitter());
13247
13218
  const state = useAgentState();
13248
13219
  const {
@@ -13616,7 +13587,6 @@ var MusicSpinner = memo2(({ color }) => {
13616
13587
 
13617
13588
  // src/platform/tui/components/StatusDisplay.tsx
13618
13589
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
13619
- var MAX_THINKING_LINES = 15;
13620
13590
  var StatusDisplay = memo3(({
13621
13591
  retryState,
13622
13592
  isProcessing,
@@ -13649,26 +13619,17 @@ var StatusDisplay = memo3(({
13649
13619
  ] });
13650
13620
  }
13651
13621
  if (isProcessing) {
13652
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
13653
- /* @__PURE__ */ jsxs3(Box3, { children: [
13654
- /* @__PURE__ */ jsx4(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: isThinkingStatus ? THEME.cyan : THEME.primary }) }),
13655
- /* @__PURE__ */ jsxs3(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, bold: true, children: [
13656
- " ",
13657
- statusMain
13658
- ] }),
13659
- /* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
13660
- " ",
13661
- meta
13662
- ] })
13622
+ const previewText = isThinkingStatus && statusLines.length > 1 ? `${statusMain} \u2014 ${statusLines[statusLines.length - 1]}` : statusMain;
13623
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
13624
+ /* @__PURE__ */ jsx4(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: isThinkingStatus ? THEME.cyan : THEME.primary }) }),
13625
+ /* @__PURE__ */ jsxs3(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, bold: true, children: [
13626
+ " ",
13627
+ previewText
13663
13628
  ] }),
13664
- isThinkingStatus && statusLines.length > 1 && statusLines.slice(1).slice(-MAX_THINKING_LINES).map((line, i) => /* @__PURE__ */ jsxs3(Box3, { children: [
13665
- /* @__PURE__ */ jsx4(Text4, { color: THEME.cyan, children: "\u2502 " }),
13666
- /* @__PURE__ */ jsx4(Text4, { color: THEME.gray, children: line })
13667
- ] }, i)),
13668
- !isThinkingStatus && statusLines.length > 1 && /* @__PURE__ */ jsx4(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
13669
- "\u2502 ",
13670
- statusLines.slice(1).join(" ")
13671
- ] }) })
13629
+ /* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
13630
+ " ",
13631
+ meta
13632
+ ] })
13672
13633
  ] });
13673
13634
  }
13674
13635
  return /* @__PURE__ */ jsx4(Box3, { height: 1, children: /* @__PURE__ */ jsx4(Text4, { children: " " }) });
@@ -13758,10 +13719,10 @@ var ChatInput = memo4(({
13758
13719
  paddingX: 1,
13759
13720
  overflowX: "hidden",
13760
13721
  children: inputRequest.status === "active" ? /* @__PURE__ */ jsxs4(Box4, { children: [
13761
- /* @__PURE__ */ jsx5(Text5, { color: THEME.yellow, children: "[auth]" }),
13762
- /* @__PURE__ */ jsxs4(Text5, { color: THEME.gray, children: [
13763
- " ",
13764
- inputRequest.prompt
13722
+ /* @__PURE__ */ jsxs4(Text5, { color: THEME.yellow, children: [
13723
+ "\u25B8 ",
13724
+ inputRequest.prompt,
13725
+ " "
13765
13726
  ] }),
13766
13727
  /* @__PURE__ */ jsx5(
13767
13728
  TextInput,
@@ -13769,7 +13730,7 @@ var ChatInput = memo4(({
13769
13730
  value: secretInput,
13770
13731
  onChange: setSecretInput,
13771
13732
  onSubmit: onSecretSubmit,
13772
- placeholder: "...",
13733
+ placeholder: inputRequest.isPassword ? "(hidden)" : "...",
13773
13734
  mask: inputRequest.isPassword ? "\u2022" : void 0
13774
13735
  }
13775
13736
  )
@@ -14104,7 +14065,7 @@ ${procData.stdout || "(no output)"}
14104
14065
  }, [handleCtrlC]);
14105
14066
  return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: [
14106
14067
  /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsx7(MessageList, { messages }) }),
14107
- /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
14068
+ /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: 6, children: [
14108
14069
  /* @__PURE__ */ jsx7(
14109
14070
  StatusDisplay,
14110
14071
  {
@@ -14170,6 +14131,11 @@ var CLI_SCAN_TYPES = Object.freeze([
14170
14131
  import gradient from "gradient-string";
14171
14132
  import { jsx as jsx8 } from "react/jsx-runtime";
14172
14133
  initDebugLogger();
14134
+ var _configErrors = validateRequiredConfig();
14135
+ if (_configErrors.length > 0) {
14136
+ _configErrors.forEach((e) => console.error(chalk.hex(HEX.red)(e)));
14137
+ process.exit(EXIT_CODES.CONFIG_ERROR);
14138
+ }
14173
14139
  var program = new Command();
14174
14140
  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");
14175
14141
  program.command("interactive", { isDefault: true }).alias("i").description("Start interactive TUI mode").action(async () => {
@@ -14204,7 +14170,7 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
14204
14170
  }
14205
14171
  console.log(chalk.hex(HEX.primary)(`[target] Objective: ${objective}
14206
14172
  `));
14207
- const agent = AgentFactory.createMainAgent(skipPermissions);
14173
+ const agent = createMainAgent(skipPermissions);
14208
14174
  if (skipPermissions) {
14209
14175
  agent.setAutoApprove(true);
14210
14176
  }
@@ -14242,7 +14208,7 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
14242
14208
  console.log(chalk.hex(HEX.primary)(`
14243
14209
  [scan] Target: ${target} (${options.scanType})
14244
14210
  `));
14245
- const agent = AgentFactory.createMainAgent(skipPermissions);
14211
+ const agent = createMainAgent(skipPermissions);
14246
14212
  agent.addTarget(target);
14247
14213
  agent.setScope([target]);
14248
14214
  const shutdown = async (exitCode = 0) => {