pentesting 0.50.0 → 0.51.0

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