pentesting 0.22.0 → 0.24.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
@@ -90,7 +90,21 @@ var DISPLAY_LIMITS = {
90
90
  /** Max endpoints to sample */
91
91
  ENDPOINT_SAMPLE: 5,
92
92
  /** Purpose truncation length */
93
- PURPOSE_TRUNCATE: 27
93
+ PURPOSE_TRUNCATE: 27,
94
+ /** Max characters for command description in background process */
95
+ COMMAND_DESCRIPTION_PREVIEW: 80,
96
+ /** Max characters for hash preview in tool output */
97
+ HASH_PREVIEW_LENGTH: 20,
98
+ /** Max characters for loot detail preview */
99
+ LOOT_DETAIL_PREVIEW: 30,
100
+ /** Max characters for link text preview in browser */
101
+ LINK_TEXT_PREVIEW: 100,
102
+ /** Prefix length for API key redaction in logs */
103
+ API_KEY_PREFIX: 8,
104
+ /** Prefix length for sensitive value redaction */
105
+ REDACT_PREFIX: 4,
106
+ /** Max characters for raw JSON error preview */
107
+ RAW_JSON_ERROR_PREVIEW: 500
94
108
  };
95
109
  var AGENT_LIMITS = {
96
110
  /** Maximum agent loop iterations — generous to allow complex tasks.
@@ -113,17 +127,27 @@ var AGENT_LIMITS = {
113
127
  /** ID radix for generation */
114
128
  ID_RADIX: 36,
115
129
  /** Maximum token budget for LLM response (matches LLM_LIMITS.streamMaxTokens) */
116
- MAX_TOKENS: 16384,
130
+ MAX_TOKENS: 32768,
117
131
  /** Maximum consecutive idle iterations before nudging agent (deadlock prevention) */
118
132
  MAX_CONSECUTIVE_IDLE: 3,
119
133
  /** Maximum tool output length before truncation (context hygiene) */
120
- MAX_TOOL_OUTPUT_LENGTH: 1e4,
134
+ MAX_TOOL_OUTPUT_LENGTH: 2e4,
121
135
  /** Max chars to include in blocked pattern tracking key (loop detection) */
122
136
  BLOCKED_PATTERN_KEY_SLICE: 80,
123
137
  /** Max chars of error text to include in web_search suggestion */
124
138
  ERROR_SEARCH_PREVIEW_SLICE: 50,
125
139
  /** How many consecutive identical blocks before warning + forcing alternative */
126
- MAX_BLOCKED_BEFORE_WARN: 2
140
+ MAX_BLOCKED_BEFORE_WARN: 2,
141
+ /** Maximum action log entries to retain in memory.
142
+ * WHY: actionLog grows unboundedly during long engagements.
143
+ * Older entries are pruned to prevent memory bloat and oversized session files.
144
+ */
145
+ MAX_ACTION_LOG: 200,
146
+ /** Maximum session files to keep on disk.
147
+ * WHY: Each save creates a timestamped .json in .pentesting/sessions/.
148
+ * Without rotation, disk usage grows unboundedly across engagements.
149
+ */
150
+ MAX_SESSION_FILES: 10
127
151
  };
128
152
 
129
153
  // src/shared/constants/patterns.ts
@@ -150,14 +174,16 @@ var EXIT_CODES = {
150
174
  /** Process killed by SIGINT (Ctrl+C) */
151
175
  SIGINT: 130,
152
176
  /** Process killed by SIGTERM */
153
- SIGTERM: 143
177
+ SIGTERM: 143,
178
+ /** Process killed by SIGKILL */
179
+ SIGKILL: 137
154
180
  };
155
181
 
156
182
  // src/shared/constants/agent.ts
157
183
  var ID_LENGTH = AGENT_LIMITS.ID_LENGTH;
158
184
  var ID_RADIX = AGENT_LIMITS.ID_RADIX;
159
185
  var APP_NAME = "Pentest AI";
160
- var APP_VERSION = "0.22.0";
186
+ var APP_VERSION = "0.24.0";
161
187
  var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
162
188
  var LLM_ROLES = {
163
189
  SYSTEM: "system",
@@ -456,13 +482,13 @@ var DEFAULTS = {
456
482
  };
457
483
 
458
484
  // src/engine/process-manager.ts
459
- import { spawn as spawn2, execSync as execSync2 } from "child_process";
460
- import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync, writeFileSync as writeFileSync2, appendFileSync } from "fs";
485
+ import { spawn as spawn3, execSync as execSync2 } from "child_process";
486
+ import { readFileSync as readFileSync2, existsSync as existsSync3, unlinkSync, writeFileSync as writeFileSync3, appendFileSync as appendFileSync2 } from "fs";
461
487
 
462
488
  // src/engine/tools-base.ts
463
- import { spawn } from "child_process";
464
- import { readFileSync, existsSync as existsSync2, writeFileSync } from "fs";
465
- import { join, dirname } from "path";
489
+ import { spawn as spawn2 } from "child_process";
490
+ import { readFileSync, existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
491
+ import { join as join2, dirname } from "path";
466
492
  import { tmpdir } from "os";
467
493
 
468
494
  // src/shared/utils/file-utils.ts
@@ -571,7 +597,7 @@ var ORPHAN_PROCESS_NAMES = [
571
597
  "python"
572
598
  ];
573
599
 
574
- // src/shared/utils/command-validator.ts
600
+ // src/shared/utils/command-security-lists.ts
575
601
  var ALLOWED_BINARIES = /* @__PURE__ */ new Set([
576
602
  // Network scanning
577
603
  "nmap",
@@ -799,6 +825,126 @@ var BLOCKED_BINARIES = /* @__PURE__ */ new Set([
799
825
  "nft",
800
826
  "crontab"
801
827
  ]);
828
+
829
+ // src/shared/utils/debug-logger.ts
830
+ import { appendFileSync, writeFileSync } from "fs";
831
+ import { join } from "path";
832
+
833
+ // src/shared/constants/paths.ts
834
+ import path from "path";
835
+ import { fileURLToPath } from "url";
836
+ import { homedir } from "os";
837
+ var __filename = fileURLToPath(import.meta.url);
838
+ var __dirname = path.dirname(__filename);
839
+ var PROJECT_ROOT = path.resolve(__dirname, "../../../");
840
+ var WORKSPACE_DIR_NAME = ".pentesting";
841
+ function getWorkspaceRoot() {
842
+ return path.join(homedir(), WORKSPACE_DIR_NAME);
843
+ }
844
+ var WORKSPACE = {
845
+ /** Root directory (resolved lazily via getWorkspaceRoot) */
846
+ get ROOT() {
847
+ return getWorkspaceRoot();
848
+ },
849
+ /** Per-session state snapshots */
850
+ get SESSIONS() {
851
+ return path.join(getWorkspaceRoot(), "sessions");
852
+ },
853
+ /** Debug logs */
854
+ get DEBUG() {
855
+ return path.join(getWorkspaceRoot(), "debug");
856
+ },
857
+ /** Generated reports */
858
+ get REPORTS() {
859
+ return path.join(getWorkspaceRoot(), "reports");
860
+ },
861
+ /** Downloaded loot, captured files */
862
+ get LOOT() {
863
+ return path.join(getWorkspaceRoot(), "loot");
864
+ },
865
+ /** Temporary files for active operations */
866
+ get TEMP() {
867
+ return path.join(getWorkspaceRoot(), "temp");
868
+ }
869
+ };
870
+
871
+ // src/shared/utils/debug-logger.ts
872
+ var DebugLogger = class _DebugLogger {
873
+ static instance;
874
+ logPath;
875
+ initialized = false;
876
+ constructor(clearOnInit = false) {
877
+ const debugDir = WORKSPACE.DEBUG;
878
+ try {
879
+ ensureDirExists(debugDir);
880
+ this.logPath = join(debugDir, "debug.log");
881
+ if (clearOnInit) {
882
+ this.clear();
883
+ }
884
+ this.initialized = true;
885
+ this.log("general", "=== DEBUG LOGGER INITIALIZED ===");
886
+ this.log("general", `Log file: ${this.logPath}`);
887
+ } catch (e) {
888
+ console.error("[DebugLogger] Failed to initialize:", e);
889
+ this.logPath = "";
890
+ }
891
+ }
892
+ static getInstance(clearOnInit = false) {
893
+ if (!_DebugLogger.instance) {
894
+ _DebugLogger.instance = new _DebugLogger(clearOnInit);
895
+ }
896
+ return _DebugLogger.instance;
897
+ }
898
+ /** Reset the singleton instance (used by initDebugLogger) */
899
+ static resetInstance(clearOnInit = false) {
900
+ _DebugLogger.instance = new _DebugLogger(clearOnInit);
901
+ return _DebugLogger.instance;
902
+ }
903
+ log(category, message, data) {
904
+ if (!this.initialized || !this.logPath) return;
905
+ try {
906
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
907
+ let logLine = `[${timestamp}] [${category.toUpperCase()}] ${message}`;
908
+ if (data !== void 0) {
909
+ logLine += ` | ${JSON.stringify(data)}`;
910
+ }
911
+ logLine += "\n";
912
+ appendFileSync(this.logPath, logLine);
913
+ } catch (e) {
914
+ console.error("[DebugLogger] Write error:", e);
915
+ }
916
+ }
917
+ logRaw(category, label, raw) {
918
+ if (!this.initialized || !this.logPath) return;
919
+ try {
920
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
921
+ const logLine = `[${timestamp}] [${category.toUpperCase()}] ${label}:
922
+ ${raw}
923
+ ---
924
+ `;
925
+ appendFileSync(this.logPath, logLine);
926
+ } catch (e) {
927
+ console.error("[DebugLogger] Write error:", e);
928
+ }
929
+ }
930
+ clear() {
931
+ if (!this.logPath) return;
932
+ try {
933
+ writeFileSync(this.logPath, "");
934
+ } catch (e) {
935
+ console.error("[DebugLogger] Clear error:", e);
936
+ }
937
+ }
938
+ };
939
+ var logger = DebugLogger.getInstance(false);
940
+ function initDebugLogger() {
941
+ DebugLogger.resetInstance(true);
942
+ }
943
+ function debugLog(category, message, data) {
944
+ logger.log(category, message, data);
945
+ }
946
+
947
+ // src/shared/utils/command-validator.ts
802
948
  function validateCommand(command) {
803
949
  if (!command || typeof command !== "string") {
804
950
  return { safe: false, error: "Empty or invalid command" };
@@ -893,7 +1039,7 @@ function validateSingleCommand(command) {
893
1039
  };
894
1040
  }
895
1041
  if (!ALLOWED_BINARIES.has(binary)) {
896
- console.warn(`[Security] Unknown binary '${binary}' - use with caution`);
1042
+ debugLog("security", `Unknown binary '${binary}' - use with caution`);
897
1043
  }
898
1044
  }
899
1045
  const redirectResult = validateRedirects(command);
@@ -992,7 +1138,8 @@ function extractBinary(command) {
992
1138
  return binary.toLowerCase();
993
1139
  }
994
1140
 
995
- // src/engine/tools-base.ts
1141
+ // src/engine/tool-auto-installer.ts
1142
+ import { spawn } from "child_process";
996
1143
  var TOOL_PACKAGE_MAP = {
997
1144
  "nmap": { apt: "nmap", brew: "nmap" },
998
1145
  "masscan": { apt: "masscan", brew: "masscan" },
@@ -1046,15 +1193,7 @@ var TOOL_PACKAGE_MAP = {
1046
1193
  "evil-winrm": { apt: "evil-winrm", brew: "evil-winrm" },
1047
1194
  "netexec": { apt: "netexec", brew: "netexec" }
1048
1195
  };
1049
- var globalEventEmitter = null;
1050
- function setCommandEventEmitter(emitter) {
1051
- globalEventEmitter = emitter;
1052
- }
1053
1196
  var installedTools = /* @__PURE__ */ new Set();
1054
- var globalInputHandler = null;
1055
- function setInputHandler(handler) {
1056
- globalInputHandler = handler;
1057
- }
1058
1197
  async function detectPackageManager() {
1059
1198
  const managers = [
1060
1199
  { name: "apt", check: "which apt-get" },
@@ -1091,7 +1230,7 @@ function isCommandNotFound(stderr, stdout) {
1091
1230
  const toolName = toolMatch ? toolMatch[1] : null;
1092
1231
  return { missing: true, toolName };
1093
1232
  }
1094
- async function installTool(toolName) {
1233
+ async function installTool(toolName, eventEmitter, inputHandler) {
1095
1234
  const pkgInfo = TOOL_PACKAGE_MAP[toolName];
1096
1235
  if (!pkgInfo) {
1097
1236
  return {
@@ -1114,7 +1253,7 @@ async function installTool(toolName) {
1114
1253
  };
1115
1254
  }
1116
1255
  const packageName = pkgInfo[pkgManager] || pkgInfo.apt;
1117
- globalEventEmitter?.({
1256
+ eventEmitter?.({
1118
1257
  type: COMMAND_EVENT_TYPES.TOOL_INSTALL,
1119
1258
  message: `Installing missing tool: ${toolName}`,
1120
1259
  detail: `Using ${pkgManager} to install ${packageName}`
@@ -1134,10 +1273,10 @@ async function installTool(toolName) {
1134
1273
  let stdout = "";
1135
1274
  let stderr = "";
1136
1275
  const checkForSudoPrompt = async (data) => {
1137
- if (!globalInputHandler) return;
1276
+ if (!inputHandler) return;
1138
1277
  for (const pattern of INPUT_PROMPT_PATTERNS) {
1139
1278
  if (pattern.test(data)) {
1140
- const userInput = await globalInputHandler(data.trim());
1279
+ const userInput = await inputHandler(data.trim());
1141
1280
  if (userInput !== null && child.stdin.writable) {
1142
1281
  child.stdin.write(userInput + "\n");
1143
1282
  }
@@ -1157,14 +1296,14 @@ async function installTool(toolName) {
1157
1296
  });
1158
1297
  child.on("close", (code) => {
1159
1298
  if (code === 0) {
1160
- globalEventEmitter?.({
1299
+ eventEmitter?.({
1161
1300
  type: COMMAND_EVENT_TYPES.TOOL_INSTALLED,
1162
1301
  message: `Successfully installed: ${toolName}`,
1163
1302
  detail: `Package: ${packageName}`
1164
1303
  });
1165
1304
  resolve({ success: true, output: `Successfully installed ${toolName}` });
1166
1305
  } else {
1167
- globalEventEmitter?.({
1306
+ eventEmitter?.({
1168
1307
  type: COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED,
1169
1308
  message: `Failed to install: ${toolName}`,
1170
1309
  detail: stderr.slice(0, SYSTEM_LIMITS.MAX_ERROR_DETAIL_SLICE)
@@ -1180,6 +1319,16 @@ async function installTool(toolName) {
1180
1319
  });
1181
1320
  });
1182
1321
  }
1322
+
1323
+ // src/engine/tools-base.ts
1324
+ var globalEventEmitter = null;
1325
+ function setCommandEventEmitter(emitter) {
1326
+ globalEventEmitter = emitter;
1327
+ }
1328
+ var globalInputHandler = null;
1329
+ function setInputHandler(handler) {
1330
+ globalInputHandler = handler;
1331
+ }
1183
1332
  async function runCommand(command, args = [], options = {}) {
1184
1333
  const fullCommand = args.length > 0 ? `${command} ${args.join(" ")}` : command;
1185
1334
  const validation = validateCommand(fullCommand);
@@ -1208,7 +1357,7 @@ async function runCommand(command, args = [], options = {}) {
1208
1357
  message: `${STATUS_MARKERS.WARNING} Tool not found: ${toolName}`,
1209
1358
  detail: `Attempting to install...`
1210
1359
  });
1211
- const installResult = await installTool(toolName);
1360
+ const installResult = await installTool(toolName, globalEventEmitter, globalInputHandler);
1212
1361
  if (!installResult.success) {
1213
1362
  return {
1214
1363
  success: false,
@@ -1233,7 +1382,7 @@ async function executeCommandOnce(command, args = [], options = {}) {
1233
1382
  type: COMMAND_EVENT_TYPES.COMMAND_START,
1234
1383
  message: `Executing: ${command.slice(0, DISPLAY_LIMITS.COMMAND_PREVIEW)}${command.length > DISPLAY_LIMITS.COMMAND_PREVIEW ? "..." : ""}`
1235
1384
  });
1236
- const child = spawn("sh", ["-c", command], {
1385
+ const child = spawn2("sh", ["-c", command], {
1237
1386
  timeout,
1238
1387
  env: { ...process.env, ...options.env },
1239
1388
  cwd: options.cwd
@@ -1339,7 +1488,7 @@ async function writeFileContent(filePath, content) {
1339
1488
  try {
1340
1489
  const dir = dirname(filePath);
1341
1490
  ensureDirExists(dir);
1342
- writeFileSync(filePath, content, "utf-8");
1491
+ writeFileSync2(filePath, content, "utf-8");
1343
1492
  return {
1344
1493
  success: true,
1345
1494
  output: `Written to ${filePath}`
@@ -1354,7 +1503,7 @@ async function writeFileContent(filePath, content) {
1354
1503
  }
1355
1504
  }
1356
1505
  function createTempFile(suffix = "") {
1357
- return join(tmpdir(), generateTempFilename(suffix));
1506
+ return join2(tmpdir(), generateTempFilename(suffix));
1358
1507
  }
1359
1508
 
1360
1509
  // src/engine/process-detector.ts
@@ -1512,12 +1661,12 @@ function startBackgroundProcess(command, options = {}) {
1512
1661
  const { tags, port, role, isInteractive } = detectProcessRole(command);
1513
1662
  let wrappedCmd;
1514
1663
  if (isInteractive) {
1515
- writeFileSync2(stdinFile, "", "utf-8");
1664
+ writeFileSync3(stdinFile, "", "utf-8");
1516
1665
  wrappedCmd = `tail -f ${stdinFile} | ${command} > ${stdoutFile} 2> ${stderrFile}`;
1517
1666
  } else {
1518
1667
  wrappedCmd = `${command} > ${stdoutFile} 2> ${stderrFile}`;
1519
1668
  }
1520
- const child = spawn2("sh", ["-c", wrappedCmd], {
1669
+ const child = spawn3("sh", ["-c", wrappedCmd], {
1521
1670
  detached: true,
1522
1671
  stdio: "ignore",
1523
1672
  cwd: options.cwd,
@@ -1571,7 +1720,7 @@ async function sendToProcess(processId, input, waitMs = SYSTEM_LIMITS.DEFAULT_WA
1571
1720
  } catch {
1572
1721
  }
1573
1722
  try {
1574
- appendFileSync(proc.stdinFile, input + "\n", "utf-8");
1723
+ appendFileSync2(proc.stdinFile, input + "\n", "utf-8");
1575
1724
  } catch (error) {
1576
1725
  return { success: false, output: `Failed to send input: ${error}`, newOutput: "" };
1577
1726
  }
@@ -2140,6 +2289,9 @@ var SharedState = class {
2140
2289
  timestamp: Date.now(),
2141
2290
  ...action
2142
2291
  });
2292
+ if (this.data.actionLog.length > AGENT_LIMITS.MAX_ACTION_LOG) {
2293
+ this.data.actionLog.splice(0, this.data.actionLog.length - AGENT_LIMITS.MAX_ACTION_LOG);
2294
+ }
2143
2295
  }
2144
2296
  getRecentActions(count = DISPLAY_LIMITS.COMPACT_LIST_ITEMS) {
2145
2297
  return this.data.actionLog.slice(-count);
@@ -2564,7 +2716,7 @@ Used ports: ${usedPorts.join(", ")}
2564
2716
  }
2565
2717
  try {
2566
2718
  const proc = startBackgroundProcess(command, {
2567
- description: command.slice(0, 80),
2719
+ description: command.slice(0, DISPLAY_LIMITS.COMMAND_DESCRIPTION_PREVIEW),
2568
2720
  purpose: params.purpose
2569
2721
  });
2570
2722
  const portInfo = proc.listeningPort ? `
@@ -2937,7 +3089,7 @@ Types: credential, hash, token, ssh_key, api_key, file, session, ticket, certifi
2937
3089
  success: true,
2938
3090
  output: `Loot recorded: [${lootType}] from ${p.host}
2939
3091
  Detail: ${p.detail}
2940
- ` + (crackableTypes.includes(lootType) ? `This is crackable. Consider: hash_crack({ hashes: "${p.detail.slice(0, 30)}..." })` : `Consider credential reuse / lateral movement with this loot.`)
3092
+ ` + (crackableTypes.includes(lootType) ? `This is crackable. Consider: hash_crack({ hashes: "${p.detail.slice(0, DISPLAY_LIMITS.LOOT_DETAIL_PREVIEW)}..." })` : `Consider credential reuse / lateral movement with this loot.`)
2941
3093
  };
2942
3094
  }
2943
3095
  },
@@ -2971,10 +3123,6 @@ Detail: ${p.detail}
2971
3123
  import { execFileSync } from "child_process";
2972
3124
 
2973
3125
  // src/shared/utils/config.ts
2974
- import path from "path";
2975
- import { fileURLToPath } from "url";
2976
- var __filename = fileURLToPath(import.meta.url);
2977
- var __dirname = path.dirname(__filename);
2978
3126
  var ENV_KEYS = {
2979
3127
  API_KEY: "PENTEST_API_KEY",
2980
3128
  BASE_URL: "PENTEST_BASE_URL",
@@ -3022,129 +3170,6 @@ var SEARCH_LIMIT = {
3022
3170
  TIMEOUT_MS: 1e4
3023
3171
  };
3024
3172
 
3025
- // src/shared/utils/debug-logger.ts
3026
- import { appendFileSync as appendFileSync2, writeFileSync as writeFileSync3 } from "fs";
3027
- import { join as join2 } from "path";
3028
-
3029
- // src/shared/constants/paths.ts
3030
- import path2 from "path";
3031
- import { fileURLToPath as fileURLToPath2 } from "url";
3032
- import { homedir } from "os";
3033
- var __filename2 = fileURLToPath2(import.meta.url);
3034
- var __dirname2 = path2.dirname(__filename2);
3035
- var PROJECT_ROOT = path2.resolve(__dirname2, "../../../");
3036
- var WORKSPACE_DIR_NAME = ".pentesting";
3037
- function getWorkspaceRoot() {
3038
- return path2.join(homedir(), WORKSPACE_DIR_NAME);
3039
- }
3040
- var WORKSPACE = {
3041
- /** Root directory (resolved lazily via getWorkspaceRoot) */
3042
- get ROOT() {
3043
- return getWorkspaceRoot();
3044
- },
3045
- /** Per-session state snapshots */
3046
- get SESSIONS() {
3047
- return path2.join(getWorkspaceRoot(), "sessions");
3048
- },
3049
- /** Debug logs */
3050
- get DEBUG() {
3051
- return path2.join(getWorkspaceRoot(), "debug");
3052
- },
3053
- /** Generated reports */
3054
- get REPORTS() {
3055
- return path2.join(getWorkspaceRoot(), "reports");
3056
- },
3057
- /** Downloaded loot, captured files */
3058
- get LOOT() {
3059
- return path2.join(getWorkspaceRoot(), "loot");
3060
- },
3061
- /** Temporary files for active operations */
3062
- get TEMP() {
3063
- return path2.join(getWorkspaceRoot(), "temp");
3064
- }
3065
- };
3066
- var PATHS = {
3067
- ROOT: PROJECT_ROOT,
3068
- SRC: path2.join(PROJECT_ROOT, "src"),
3069
- DIST: path2.join(PROJECT_ROOT, "dist")
3070
- };
3071
-
3072
- // src/shared/utils/debug-logger.ts
3073
- var DebugLogger = class _DebugLogger {
3074
- static instance;
3075
- logPath;
3076
- initialized = false;
3077
- constructor(clearOnInit = false) {
3078
- const debugDir = WORKSPACE.DEBUG;
3079
- try {
3080
- ensureDirExists(debugDir);
3081
- this.logPath = join2(debugDir, "debug.log");
3082
- if (clearOnInit) {
3083
- this.clear();
3084
- }
3085
- this.initialized = true;
3086
- this.log("general", "=== DEBUG LOGGER INITIALIZED ===");
3087
- this.log("general", `Log file: ${this.logPath}`);
3088
- } catch (e) {
3089
- console.error("[DebugLogger] Failed to initialize:", e);
3090
- this.logPath = "";
3091
- }
3092
- }
3093
- static getInstance(clearOnInit = false) {
3094
- if (!_DebugLogger.instance) {
3095
- _DebugLogger.instance = new _DebugLogger(clearOnInit);
3096
- }
3097
- return _DebugLogger.instance;
3098
- }
3099
- /** Reset the singleton instance (used by initDebugLogger) */
3100
- static resetInstance(clearOnInit = false) {
3101
- _DebugLogger.instance = new _DebugLogger(clearOnInit);
3102
- return _DebugLogger.instance;
3103
- }
3104
- log(category, message, data) {
3105
- if (!this.initialized || !this.logPath) return;
3106
- try {
3107
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3108
- let logLine = `[${timestamp}] [${category.toUpperCase()}] ${message}`;
3109
- if (data !== void 0) {
3110
- logLine += ` | ${JSON.stringify(data)}`;
3111
- }
3112
- logLine += "\n";
3113
- appendFileSync2(this.logPath, logLine);
3114
- } catch (e) {
3115
- console.error("[DebugLogger] Write error:", e);
3116
- }
3117
- }
3118
- logRaw(category, label, raw) {
3119
- if (!this.initialized || !this.logPath) return;
3120
- try {
3121
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3122
- const logLine = `[${timestamp}] [${category.toUpperCase()}] ${label}:
3123
- ${raw}
3124
- ---
3125
- `;
3126
- appendFileSync2(this.logPath, logLine);
3127
- } catch (e) {
3128
- console.error("[DebugLogger] Write error:", e);
3129
- }
3130
- }
3131
- clear() {
3132
- if (!this.logPath) return;
3133
- try {
3134
- writeFileSync3(this.logPath, "");
3135
- } catch (e) {
3136
- console.error("[DebugLogger] Clear error:", e);
3137
- }
3138
- }
3139
- };
3140
- var logger = DebugLogger.getInstance(false);
3141
- function initDebugLogger() {
3142
- DebugLogger.resetInstance(true);
3143
- }
3144
- function debugLog(category, message, data) {
3145
- logger.log(category, message, data);
3146
- }
3147
-
3148
3173
  // src/engine/tools/web-browser.ts
3149
3174
  import { join as join5 } from "path";
3150
3175
  import { tmpdir as tmpdir3 } from "os";
@@ -3196,7 +3221,7 @@ var PLAYWRIGHT_SCRIPT = {
3196
3221
  };
3197
3222
 
3198
3223
  // src/engine/tools/web-browser-setup.ts
3199
- import { spawn as spawn3 } from "child_process";
3224
+ import { spawn as spawn4 } from "child_process";
3200
3225
  import { existsSync as existsSync4 } from "fs";
3201
3226
  import { join as join3, dirname as dirname2 } from "path";
3202
3227
  function getPlaywrightPath() {
@@ -3217,12 +3242,12 @@ function getPlaywrightPath() {
3217
3242
  async function checkPlaywright() {
3218
3243
  try {
3219
3244
  const result2 = await new Promise((resolve) => {
3220
- const child = spawn3(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_ACTION.VERSION], { shell: true });
3245
+ const child = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_ACTION.VERSION], { shell: true });
3221
3246
  let stdout = "";
3222
3247
  child.stdout.on("data", (data) => stdout += data);
3223
3248
  child.on("close", (code) => {
3224
3249
  if (code === 0) {
3225
- const browserCheck = spawn3(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_BROWSER.CHROMIUM, PLAYWRIGHT_ACTION.HELP], { shell: true });
3250
+ const browserCheck = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_BROWSER.CHROMIUM, PLAYWRIGHT_ACTION.HELP], { shell: true });
3226
3251
  browserCheck.on("close", (browserCode) => {
3227
3252
  resolve({ installed: true, browserInstalled: browserCode === 0 });
3228
3253
  });
@@ -3240,7 +3265,7 @@ async function checkPlaywright() {
3240
3265
  }
3241
3266
  async function installPlaywright() {
3242
3267
  return new Promise((resolve) => {
3243
- const child = spawn3(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_ACTION.INSTALL, PLAYWRIGHT_BROWSER.CHROMIUM], {
3268
+ const child = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_ACTION.INSTALL, PLAYWRIGHT_BROWSER.CHROMIUM], {
3244
3269
  shell: true,
3245
3270
  stdio: ["ignore", "pipe", "pipe"]
3246
3271
  });
@@ -3262,7 +3287,7 @@ async function installPlaywright() {
3262
3287
  }
3263
3288
 
3264
3289
  // src/engine/tools/web-browser-script.ts
3265
- import { spawn as spawn4 } from "child_process";
3290
+ import { spawn as spawn5 } from "child_process";
3266
3291
  import { writeFileSync as writeFileSync4, unlinkSync as unlinkSync2 } from "fs";
3267
3292
  import { join as join4 } from "path";
3268
3293
  import { tmpdir as tmpdir2 } from "os";
@@ -3289,7 +3314,7 @@ function runPlaywrightScript(script, timeout, scriptPrefix) {
3289
3314
  join4(process.cwd(), "node_modules"),
3290
3315
  process.env.NODE_PATH || ""
3291
3316
  ].filter(Boolean).join(":");
3292
- const child = spawn4(PLAYWRIGHT_CMD.NODE, [scriptPath], {
3317
+ const child = spawn5(PLAYWRIGHT_CMD.NODE, [scriptPath], {
3293
3318
  timeout: timeout + PLAYWRIGHT_SCRIPT.SPAWN_TIMEOUT_BUFFER_MS,
3294
3319
  env: { ...process.env, NODE_PATH: nodePathDirs }
3295
3320
  });
@@ -3392,7 +3417,7 @@ const { chromium } = require(${safePlaywrightPath});
3392
3417
  result.links = await page.evaluate(() => {
3393
3418
  return Array.from(document.querySelectorAll('a[href]')).map(a => ({
3394
3419
  href: a.href,
3395
- text: a.textContent.trim().slice(0, 100)
3420
+ text: a.textContent.trim().slice(0, ${DISPLAY_LIMITS.LINK_TEXT_PREVIEW})
3396
3421
  })).slice(0, ${BROWSER_LIMITS.MAX_LINKS_EXTRACTION});
3397
3422
  });` : ""}
3398
3423
 
@@ -3441,11 +3466,11 @@ function formatBrowserOutput(data, options) {
3441
3466
  }
3442
3467
  if (data.links && options.extractLinks && data.links.length > 0) {
3443
3468
  lines.push(`## Links (${data.links.length} found)`);
3444
- data.links.slice(0, 20).forEach((link) => {
3469
+ data.links.slice(0, DISPLAY_LIMITS.LINKS_PREVIEW).forEach((link) => {
3445
3470
  lines.push(`- [${link.text || "no text"}](${link.href})`);
3446
3471
  });
3447
- if (data.links.length > 20) {
3448
- lines.push(`... and ${data.links.length - 20} more`);
3472
+ if (data.links.length > DISPLAY_LIMITS.LINKS_PREVIEW) {
3473
+ lines.push(`... and ${data.links.length - DISPLAY_LIMITS.LINKS_PREVIEW} more`);
3449
3474
  }
3450
3475
  lines.push("");
3451
3476
  }
@@ -3600,146 +3625,10 @@ async function webSearchWithBrowser(query, engine = "google") {
3600
3625
  });
3601
3626
  }
3602
3627
 
3603
- // src/engine/tools-mid.ts
3628
+ // src/engine/web-search-providers.ts
3604
3629
  function getErrorMessage(error) {
3605
3630
  return error instanceof Error ? error.message : String(error);
3606
3631
  }
3607
- async function parseNmap(xmlPath) {
3608
- try {
3609
- const fileResult = await readFileContent(xmlPath);
3610
- if (!fileResult.success) {
3611
- return fileResult;
3612
- }
3613
- const xmlContent = fileResult.output;
3614
- const results = {
3615
- targets: [],
3616
- summary: { totalTargets: 0, openPorts: 0, servicesFound: 0 }
3617
- };
3618
- const hostRegex = /<host[^>]*>[\s\S]*?<\/host>/g;
3619
- const hosts = xmlContent.match(hostRegex) || [];
3620
- for (const hostBlock of hosts) {
3621
- const ipMatch = hostBlock.match(/<address[^>]*addr="([^"]+)"/);
3622
- const ip = ipMatch ? ipMatch[1] : "";
3623
- const hostnameMatch = hostBlock.match(/<hostname[^>]*name="([^"]+)"/);
3624
- const hostname = hostnameMatch ? hostnameMatch[1] : void 0;
3625
- const ports = [];
3626
- const portRegex = /<port[^>]*protocol="([^"]*)"[^>]*portid="(\d+)">[\s\S]*?<\/port>/g;
3627
- let portMatch;
3628
- while ((portMatch = portRegex.exec(hostBlock)) !== null) {
3629
- const protocol = portMatch[1];
3630
- const port = parseInt(portMatch[2]);
3631
- const stateMatch = portMatch[0].match(/<state[^>]*state="([^"]+)"/);
3632
- const state = stateMatch ? stateMatch[1] : "";
3633
- const serviceMatch = portMatch[0].match(/<service[^>]*name="([^"]*)"(?:[^>]*version="([^"]*)")?/);
3634
- const service = serviceMatch ? serviceMatch[1] : void 0;
3635
- const version = serviceMatch && serviceMatch[2] ? serviceMatch[2] : void 0;
3636
- if (state === "open") {
3637
- ports.push({ port, protocol, state, service, version });
3638
- results.summary.openPorts++;
3639
- if (service) results.summary.servicesFound++;
3640
- }
3641
- }
3642
- if (ip) {
3643
- results.targets.push({ ip, hostname, ports });
3644
- results.summary.totalTargets++;
3645
- }
3646
- }
3647
- return {
3648
- success: true,
3649
- output: JSON.stringify(results, null, 2)
3650
- };
3651
- } catch (error) {
3652
- return {
3653
- success: false,
3654
- output: "",
3655
- error: getErrorMessage(error)
3656
- };
3657
- }
3658
- }
3659
- async function searchCVE(service, version) {
3660
- try {
3661
- return searchExploitDB(service, version);
3662
- } catch (error) {
3663
- return {
3664
- success: false,
3665
- output: "",
3666
- error: getErrorMessage(error)
3667
- };
3668
- }
3669
- }
3670
- async function searchExploitDB(service, version) {
3671
- try {
3672
- const query = version ? `${service} ${version}` : service;
3673
- try {
3674
- const output = execFileSync("searchsploit", [query, "--color", "never"], {
3675
- encoding: "utf-8",
3676
- stdio: ["ignore", "pipe", "pipe"],
3677
- timeout: SEARCH_LIMIT.TIMEOUT_MS
3678
- });
3679
- const lines = output.trim().split("\n").slice(0, SEARCH_LIMIT.MAX_OUTPUT_LINES);
3680
- return {
3681
- success: true,
3682
- output: lines.join("\n") || `No exploits found for ${query}`
3683
- };
3684
- } catch (e) {
3685
- const execError = e;
3686
- const stderr = String(execError.stderr || "");
3687
- const stdout = String(execError.stdout || "");
3688
- if (stderr.includes("No results")) {
3689
- return {
3690
- success: true,
3691
- output: `No exploits found for ${query}`
3692
- };
3693
- }
3694
- return {
3695
- success: true,
3696
- output: stdout || `No exploits found for ${query}`
3697
- };
3698
- }
3699
- } catch (error) {
3700
- return {
3701
- success: false,
3702
- output: "",
3703
- error: getErrorMessage(error)
3704
- };
3705
- }
3706
- }
3707
- async function webSearch(query, _engine) {
3708
- debugLog("search", "webSearch START", { query });
3709
- try {
3710
- const apiKey = getSearchApiKey();
3711
- const apiUrl = getSearchApiUrl();
3712
- debugLog("search", "Search API config", {
3713
- hasApiKey: !!apiKey,
3714
- apiUrl,
3715
- apiKeyPrefix: apiKey ? apiKey.slice(0, 8) + "..." : null
3716
- });
3717
- if (apiKey) {
3718
- if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU)) {
3719
- debugLog("search", "Using GLM search");
3720
- return await searchWithGLM(query, apiKey, apiUrl);
3721
- } else if (apiUrl.includes(SEARCH_URL_PATTERN.BRAVE)) {
3722
- debugLog("search", "Using Brave search");
3723
- return await searchWithBrave(query, apiKey, apiUrl);
3724
- } else if (apiUrl.includes(SEARCH_URL_PATTERN.SERPER)) {
3725
- debugLog("search", "Using Serper search");
3726
- return await searchWithSerper(query, apiKey, apiUrl);
3727
- } else {
3728
- debugLog("search", "Using generic search API");
3729
- return await searchWithGenericApi(query, apiKey, apiUrl);
3730
- }
3731
- }
3732
- debugLog("search", "SEARCH_API_KEY not set \u2014 falling back to Playwright Google search");
3733
- return await searchWithPlaywright(query);
3734
- } catch (error) {
3735
- debugLog("search", "webSearch ERROR", { error: getErrorMessage(error) });
3736
- return {
3737
- success: false,
3738
- output: "",
3739
- error: getErrorMessage(error)
3740
- };
3741
- }
3742
- }
3743
3632
  async function searchWithGLM(query, apiKey, apiUrl) {
3744
3633
  debugLog("search", "GLM request START", { apiUrl, query });
3745
3634
  const requestBody = {
@@ -3838,52 +3727,193 @@ async function searchWithSerper(query, apiKey, apiUrl) {
3838
3727
  if (results.length === 0) {
3839
3728
  return { success: true, output: `No results found for: ${query}` };
3840
3729
  }
3841
- const formatted = results.map(
3842
- (r, i) => `${i + 1}. ${r.title || "No title"}
3843
- ${r.link || ""}
3844
- ${r.snippet || ""}`
3845
- ).join("\n\n");
3846
- debugLog("search", "Serper search COMPLETE", { resultsLength: formatted.length });
3847
- return { success: true, output: formatted };
3730
+ const formatted = results.map(
3731
+ (r, i) => `${i + 1}. ${r.title || "No title"}
3732
+ ${r.link || ""}
3733
+ ${r.snippet || ""}`
3734
+ ).join("\n\n");
3735
+ debugLog("search", "Serper search COMPLETE", { resultsLength: formatted.length });
3736
+ return { success: true, output: formatted };
3737
+ }
3738
+ async function searchWithGenericApi(query, apiKey, apiUrl) {
3739
+ const response = await fetch(apiUrl, {
3740
+ method: "POST",
3741
+ headers: {
3742
+ [SEARCH_HEADER.CONTENT_TYPE]: "application/json",
3743
+ [SEARCH_HEADER.AUTHORIZATION]: `Bearer ${apiKey}`,
3744
+ [SEARCH_HEADER.X_API_KEY]: apiKey
3745
+ },
3746
+ body: JSON.stringify({ query, q: query })
3747
+ });
3748
+ if (!response.ok) {
3749
+ const errorText = await response.text();
3750
+ throw new Error(`Search API error: ${response.status} - ${errorText}`);
3751
+ }
3752
+ const data = await response.json();
3753
+ return { success: true, output: JSON.stringify(data, null, 2) };
3754
+ }
3755
+ async function searchWithPlaywright(query) {
3756
+ debugLog("search", "Playwright Google search START", { query });
3757
+ try {
3758
+ const result2 = await webSearchWithBrowser(query, "google");
3759
+ if (!result2.success) {
3760
+ return {
3761
+ success: false,
3762
+ output: "",
3763
+ error: `Playwright search failed: ${result2.error}. Set SEARCH_API_KEY for reliable search (Brave API by default).`
3764
+ };
3765
+ }
3766
+ debugLog("search", "Playwright Google search COMPLETE", { outputLength: result2.output.length });
3767
+ return {
3768
+ success: true,
3769
+ output: result2.output || `No results found for: ${query}`
3770
+ };
3771
+ } catch (error) {
3772
+ return {
3773
+ success: false,
3774
+ output: "",
3775
+ error: `Playwright search error: ${getErrorMessage(error)}. Set SEARCH_API_KEY for reliable search (Brave API by default).`
3776
+ };
3777
+ }
3778
+ }
3779
+
3780
+ // src/engine/tools-mid.ts
3781
+ function getErrorMessage2(error) {
3782
+ return error instanceof Error ? error.message : String(error);
3783
+ }
3784
+ async function parseNmap(xmlPath) {
3785
+ try {
3786
+ const fileResult = await readFileContent(xmlPath);
3787
+ if (!fileResult.success) {
3788
+ return fileResult;
3789
+ }
3790
+ const xmlContent = fileResult.output;
3791
+ const results = {
3792
+ targets: [],
3793
+ summary: { totalTargets: 0, openPorts: 0, servicesFound: 0 }
3794
+ };
3795
+ const hostRegex = /<host[^>]*>[\s\S]*?<\/host>/g;
3796
+ const hosts = xmlContent.match(hostRegex) || [];
3797
+ for (const hostBlock of hosts) {
3798
+ const ipMatch = hostBlock.match(/<address[^>]*addr="([^"]+)"/);
3799
+ const ip = ipMatch ? ipMatch[1] : "";
3800
+ const hostnameMatch = hostBlock.match(/<hostname[^>]*name="([^"]+)"/);
3801
+ const hostname = hostnameMatch ? hostnameMatch[1] : void 0;
3802
+ const ports = [];
3803
+ const portRegex = /<port[^>]*protocol="([^"]*)"[^>]*portid="(\d+)">[\s\S]*?<\/port>/g;
3804
+ let portMatch;
3805
+ while ((portMatch = portRegex.exec(hostBlock)) !== null) {
3806
+ const protocol = portMatch[1];
3807
+ const port = parseInt(portMatch[2]);
3808
+ const stateMatch = portMatch[0].match(/<state[^>]*state="([^"]+)"/);
3809
+ const state = stateMatch ? stateMatch[1] : "";
3810
+ const serviceMatch = portMatch[0].match(/<service[^>]*name="([^"]*)"(?:[^>]*version="([^"]*)")?/);
3811
+ const service = serviceMatch ? serviceMatch[1] : void 0;
3812
+ const version = serviceMatch && serviceMatch[2] ? serviceMatch[2] : void 0;
3813
+ if (state === "open") {
3814
+ ports.push({ port, protocol, state, service, version });
3815
+ results.summary.openPorts++;
3816
+ if (service) results.summary.servicesFound++;
3817
+ }
3818
+ }
3819
+ if (ip) {
3820
+ results.targets.push({ ip, hostname, ports });
3821
+ results.summary.totalTargets++;
3822
+ }
3823
+ }
3824
+ return {
3825
+ success: true,
3826
+ output: JSON.stringify(results, null, 2)
3827
+ };
3828
+ } catch (error) {
3829
+ return {
3830
+ success: false,
3831
+ output: "",
3832
+ error: getErrorMessage2(error)
3833
+ };
3834
+ }
3848
3835
  }
3849
- async function searchWithGenericApi(query, apiKey, apiUrl) {
3850
- const response = await fetch(apiUrl, {
3851
- method: "POST",
3852
- headers: {
3853
- [SEARCH_HEADER.CONTENT_TYPE]: "application/json",
3854
- [SEARCH_HEADER.AUTHORIZATION]: `Bearer ${apiKey}`,
3855
- [SEARCH_HEADER.X_API_KEY]: apiKey
3856
- },
3857
- body: JSON.stringify({ query, q: query })
3858
- });
3859
- if (!response.ok) {
3860
- const errorText = await response.text();
3861
- throw new Error(`Search API error: ${response.status} - ${errorText}`);
3836
+ async function searchCVE(service, version) {
3837
+ try {
3838
+ return searchExploitDB(service, version);
3839
+ } catch (error) {
3840
+ return {
3841
+ success: false,
3842
+ output: "",
3843
+ error: getErrorMessage2(error)
3844
+ };
3862
3845
  }
3863
- const data = await response.json();
3864
- return { success: true, output: JSON.stringify(data, null, 2) };
3865
3846
  }
3866
- async function searchWithPlaywright(query) {
3867
- debugLog("search", "Playwright Google search START", { query });
3847
+ async function searchExploitDB(service, version) {
3868
3848
  try {
3869
- const result2 = await webSearchWithBrowser(query, "google");
3870
- if (!result2.success) {
3849
+ const query = version ? `${service} ${version}` : service;
3850
+ try {
3851
+ const output = execFileSync("searchsploit", [query, "--color", "never"], {
3852
+ encoding: "utf-8",
3853
+ stdio: ["ignore", "pipe", "pipe"],
3854
+ timeout: SEARCH_LIMIT.TIMEOUT_MS
3855
+ });
3856
+ const lines = output.trim().split("\n").slice(0, SEARCH_LIMIT.MAX_OUTPUT_LINES);
3871
3857
  return {
3872
- success: false,
3873
- output: "",
3874
- error: `Playwright search failed: ${result2.error}. Set SEARCH_API_KEY for reliable search (Brave API by default).`
3858
+ success: true,
3859
+ output: lines.join("\n") || `No exploits found for ${query}`
3860
+ };
3861
+ } catch (e) {
3862
+ const execError = e;
3863
+ const stderr = String(execError.stderr || "");
3864
+ const stdout = String(execError.stdout || "");
3865
+ if (stderr.includes("No results")) {
3866
+ return {
3867
+ success: true,
3868
+ output: `No exploits found for ${query}`
3869
+ };
3870
+ }
3871
+ return {
3872
+ success: true,
3873
+ output: stdout || `No exploits found for ${query}`
3875
3874
  };
3876
3875
  }
3877
- debugLog("search", "Playwright Google search COMPLETE", { outputLength: result2.output.length });
3876
+ } catch (error) {
3878
3877
  return {
3879
- success: true,
3880
- output: result2.output || `No results found for: ${query}`
3878
+ success: false,
3879
+ output: "",
3880
+ error: getErrorMessage2(error)
3881
3881
  };
3882
+ }
3883
+ }
3884
+ async function webSearch(query, _engine) {
3885
+ debugLog("search", "webSearch START", { query });
3886
+ try {
3887
+ const apiKey = getSearchApiKey();
3888
+ const apiUrl = getSearchApiUrl();
3889
+ debugLog("search", "Search API config", {
3890
+ hasApiKey: !!apiKey,
3891
+ apiUrl,
3892
+ apiKeyPrefix: apiKey ? apiKey.slice(0, DISPLAY_LIMITS.API_KEY_PREFIX) + "..." : null
3893
+ });
3894
+ if (apiKey) {
3895
+ if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU)) {
3896
+ debugLog("search", "Using GLM search");
3897
+ return await searchWithGLM(query, apiKey, apiUrl);
3898
+ } else if (apiUrl.includes(SEARCH_URL_PATTERN.BRAVE)) {
3899
+ debugLog("search", "Using Brave search");
3900
+ return await searchWithBrave(query, apiKey, apiUrl);
3901
+ } else if (apiUrl.includes(SEARCH_URL_PATTERN.SERPER)) {
3902
+ debugLog("search", "Using Serper search");
3903
+ return await searchWithSerper(query, apiKey, apiUrl);
3904
+ } else {
3905
+ debugLog("search", "Using generic search API");
3906
+ return await searchWithGenericApi(query, apiKey, apiUrl);
3907
+ }
3908
+ }
3909
+ debugLog("search", "SEARCH_API_KEY not set \u2014 falling back to Playwright Google search");
3910
+ return await searchWithPlaywright(query);
3882
3911
  } catch (error) {
3912
+ debugLog("search", "webSearch ERROR", { error: getErrorMessage2(error) });
3883
3913
  return {
3884
3914
  success: false,
3885
3915
  output: "",
3886
- error: `Playwright search error: ${getErrorMessage(error)}. Set SEARCH_API_KEY for reliable search (Brave API by default).`
3916
+ error: getErrorMessage2(error)
3887
3917
  };
3888
3918
  }
3889
3919
  }
@@ -4215,6 +4245,10 @@ Can extract forms and inputs for security testing.`,
4215
4245
  {
4216
4246
  name: TOOL_NAMES.GET_OWASP_KNOWLEDGE,
4217
4247
  description: `Get OWASP Top 10 vulnerability knowledge (2017, 2021, 2025 editions).
4248
+ This is BOOTSTRAP knowledge for initial direction. Use it to quickly
4249
+ orient your attack approach, then try attacks based on what you learn.
4250
+ If built-in knowledge isn't enough, use web_search for version-specific details.
4251
+
4218
4252
  Returns structured information about:
4219
4253
  - OWASP Top 10 category details (detection methods, test payloads, tools)
4220
4254
  - Common vulnerability patterns
@@ -4293,10 +4327,14 @@ Returns a step-by-step guide for:
4293
4327
  },
4294
4328
  {
4295
4329
  name: TOOL_NAMES.GET_CVE_INFO,
4296
- description: `Get information about known CVEs. Use this to:
4297
- - Look up vulnerability details
4298
- - Check if a service version has known vulnerabilities
4299
- - Get exploit information
4330
+ description: `Get information about known CVEs from local bootstrap database.
4331
+ This is a STARTING POINT \u2014 use it first for quick lookups on major CVEs.
4332
+ If the CVE isn't found locally or you need more detail, then use web_search.
4333
+
4334
+ Use this to:
4335
+ - Quick-check if a well-known CVE exists locally
4336
+ - Get initial exploit hints for common vulnerabilities
4337
+ - If not found or insufficient, use web_search for full details and PoCs
4300
4338
 
4301
4339
  Includes critical CVEs like Log4Shell, Spring4Shell, EternalBlue, XZ Backdoor, etc.
4302
4340
  If CVE not found locally, use web_search to find it online.`,
@@ -4664,7 +4702,7 @@ Common wordlists are automatically searched in /usr/share/wordlists (rockyou.txt
4664
4702
  const cmd = `hashcat -m ${format || HASHCAT_MODES.MD5} "${hashes}" "${wordlistPath}" --force`;
4665
4703
  if (background) {
4666
4704
  const proc = startBackgroundProcess(cmd, {
4667
- description: `Cracking hashes: ${hashes.slice(0, 20)}...`,
4705
+ description: `Cracking hashes: ${hashes.slice(0, DISPLAY_LIMITS.HASH_PREVIEW_LENGTH)}...`,
4668
4706
  purpose: `Attempting to crack ${format || "unknown"} hashes using ${wordlist}`
4669
4707
  });
4670
4708
  return {
@@ -4771,72 +4809,61 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
4771
4809
  "/usr/share/dirb/",
4772
4810
  "/opt/wordlists/"
4773
4811
  ];
4774
- const results = [];
4775
- results.push("# Available Wordlists on System\\n");
4812
+ const CATEGORY_KEYWORDS = {
4813
+ passwords: ["password", "rockyou"],
4814
+ usernames: ["user"],
4815
+ web: ["web", "content", "raft"],
4816
+ dns: ["dns", "subdomain"],
4817
+ fuzzing: ["fuzz", "injection"],
4818
+ api: ["api"]
4819
+ };
4820
+ const WORDLIST_EXTENSIONS = /* @__PURE__ */ new Set(["txt", "lst", "dict", "list", "wlist"]);
4821
+ const SKIP_DIRS = /* @__PURE__ */ new Set([".", "doc"]);
4822
+ const matchesCategory = (filePath) => {
4823
+ if (!category) return true;
4824
+ const pathLower = filePath.toLowerCase();
4825
+ const keywords = CATEGORY_KEYWORDS[category.toLowerCase()] || [category.toLowerCase()];
4826
+ return keywords.some((kw) => pathLower.includes(kw));
4827
+ };
4828
+ const matchesSearch = (filePath, fileName) => {
4829
+ if (!search) return true;
4830
+ const q = search.toLowerCase();
4831
+ return fileName.toLowerCase().includes(q) || filePath.toLowerCase().includes(q);
4832
+ };
4833
+ const results = ["# Available Wordlists on System\\n"];
4776
4834
  let totalCount = 0;
4777
- const scanDir = (dirPath, maxDepth = 3, currentDepth = 0) => {
4778
- if (currentDepth > maxDepth) return;
4779
- if (!existsSync7(dirPath)) return;
4835
+ const processFile = (fullPath, fileName) => {
4836
+ const ext = fileName.split(".").pop()?.toLowerCase();
4837
+ if (!WORDLIST_EXTENSIONS.has(ext || "")) return;
4838
+ const stats = statSync2(fullPath);
4839
+ if (stats.size < minSize) return;
4840
+ if (!matchesCategory(fullPath)) return;
4841
+ if (!matchesSearch(fullPath, fileName)) return;
4842
+ totalCount++;
4843
+ results.push(`### ${fullPath}`);
4844
+ results.push(`- Size: ${Math.round(stats.size / 1024)} KB (${stats.size.toLocaleString()} bytes)`);
4845
+ results.push("");
4846
+ };
4847
+ const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
4848
+ if (depth > maxDepth || !existsSync7(dirPath)) return;
4849
+ let entries;
4780
4850
  try {
4781
- const entries = readdirSync3(dirPath, { withFileTypes: true });
4782
- for (const entry of entries) {
4783
- const fullPath = join10(dirPath, entry.name);
4784
- if (entry.isDirectory()) {
4785
- if (entry.name.startsWith(".")) continue;
4786
- if (entry.name === "doc") continue;
4787
- scanDir(fullPath, maxDepth, currentDepth + 1);
4788
- } else if (entry.isFile()) {
4789
- const ext = entry.name.split(".").pop()?.toLowerCase();
4790
- if (!["txt", "lst", "dict", "list", "wlist"].includes(ext || "")) {
4791
- continue;
4792
- }
4793
- try {
4794
- const stats = statSync2(fullPath);
4795
- const sizeBytes = stats.size;
4796
- if (sizeBytes < minSize) continue;
4797
- const relPath = fullPath;
4798
- const filename = entry.name;
4799
- const sizeKB = Math.round(sizeBytes / 1024);
4800
- if (category) {
4801
- const pathLower = relPath.toLowerCase();
4802
- let matchesCategory = false;
4803
- switch (category.toLowerCase()) {
4804
- case "passwords":
4805
- matchesCategory = pathLower.includes("password") || pathLower.includes("rockyou");
4806
- break;
4807
- case "usernames":
4808
- matchesCategory = pathLower.includes("user");
4809
- break;
4810
- case "web":
4811
- matchesCategory = pathLower.includes("web") || pathLower.includes("content") || pathLower.includes("raft");
4812
- break;
4813
- case "dns":
4814
- matchesCategory = pathLower.includes("dns") || pathLower.includes("subdomain");
4815
- break;
4816
- case "fuzzing":
4817
- matchesCategory = pathLower.includes("fuzz") || pathLower.includes("injection");
4818
- break;
4819
- case "api":
4820
- matchesCategory = pathLower.includes("api");
4821
- break;
4822
- default:
4823
- matchesCategory = pathLower.includes(category.toLowerCase());
4824
- }
4825
- if (!matchesCategory) continue;
4826
- }
4827
- if (search && !filename.toLowerCase().includes(search.toLowerCase()) && !relPath.toLowerCase().includes(search.toLowerCase())) {
4828
- continue;
4829
- }
4830
- totalCount++;
4831
- results.push(`### ${relPath}`);
4832
- results.push(`- Size: ${sizeKB} KB (${sizeBytes.toLocaleString()} bytes)`);
4833
- results.push("");
4834
- } catch {
4835
- continue;
4836
- }
4837
- }
4838
- }
4851
+ entries = readdirSync3(dirPath, { withFileTypes: true });
4839
4852
  } catch {
4853
+ return;
4854
+ }
4855
+ for (const entry of entries) {
4856
+ if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
4857
+ const fullPath = join10(dirPath, entry.name);
4858
+ if (entry.isDirectory()) {
4859
+ scanDir(fullPath, maxDepth, depth + 1);
4860
+ continue;
4861
+ }
4862
+ if (!entry.isFile()) continue;
4863
+ try {
4864
+ processFile(fullPath, entry.name);
4865
+ } catch {
4866
+ }
4840
4867
  }
4841
4868
  };
4842
4869
  for (const basePath of scanPaths) {
@@ -5848,15 +5875,6 @@ var CLOUD_KEYWORDS = [
5848
5875
  "heroku",
5849
5876
  "vercel"
5850
5877
  ];
5851
- var PASSIVE_CATEGORIES = [
5852
- SERVICE_CATEGORIES.NETWORK
5853
- ];
5854
- var ACTIVE_CATEGORIES = [
5855
- SERVICE_CATEGORIES.WEB,
5856
- SERVICE_CATEGORIES.API,
5857
- SERVICE_CATEGORIES.EMAIL,
5858
- SERVICE_CATEGORIES.FILE_SHARING
5859
- ];
5860
5878
  var DANGER_LEVEL_MAP = {
5861
5879
  [SERVICE_CATEGORIES.NETWORK]: DANGER_LEVELS.PASSIVE,
5862
5880
  [SERVICE_CATEGORIES.WEB]: DANGER_LEVELS.ACTIVE,
@@ -5905,80 +5923,80 @@ var ServiceParser = class {
5905
5923
 
5906
5924
  // src/domains/registry.ts
5907
5925
  import { join as join6, dirname as dirname3 } from "path";
5908
- import { fileURLToPath as fileURLToPath3 } from "url";
5909
- var __dirname3 = dirname3(fileURLToPath3(import.meta.url));
5926
+ import { fileURLToPath as fileURLToPath2 } from "url";
5927
+ var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
5910
5928
  var DOMAINS = {
5911
5929
  [SERVICE_CATEGORIES.NETWORK]: {
5912
5930
  id: SERVICE_CATEGORIES.NETWORK,
5913
5931
  name: "Network Infrastructure",
5914
5932
  description: "Vulnerability scanning, port mapping, and network service exploitation.",
5915
- promptPath: join6(__dirname3, "network/prompt.md")
5933
+ promptPath: join6(__dirname2, "network/prompt.md")
5916
5934
  },
5917
5935
  [SERVICE_CATEGORIES.WEB]: {
5918
5936
  id: SERVICE_CATEGORIES.WEB,
5919
5937
  name: "Web Application",
5920
5938
  description: "Web app security testing, injection attacks, and auth bypass.",
5921
- promptPath: join6(__dirname3, "web/prompt.md")
5939
+ promptPath: join6(__dirname2, "web/prompt.md")
5922
5940
  },
5923
5941
  [SERVICE_CATEGORIES.DATABASE]: {
5924
5942
  id: SERVICE_CATEGORIES.DATABASE,
5925
5943
  name: "Database Security",
5926
5944
  description: "SQL injection, database enumeration, and data extraction.",
5927
- promptPath: join6(__dirname3, "database/prompt.md")
5945
+ promptPath: join6(__dirname2, "database/prompt.md")
5928
5946
  },
5929
5947
  [SERVICE_CATEGORIES.AD]: {
5930
5948
  id: SERVICE_CATEGORIES.AD,
5931
5949
  name: "Active Directory",
5932
5950
  description: "Kerberos, LDAP, and Windows domain privilege escalation.",
5933
- promptPath: join6(__dirname3, "ad/prompt.md")
5951
+ promptPath: join6(__dirname2, "ad/prompt.md")
5934
5952
  },
5935
5953
  [SERVICE_CATEGORIES.EMAIL]: {
5936
5954
  id: SERVICE_CATEGORIES.EMAIL,
5937
5955
  name: "Email Services",
5938
5956
  description: "SMTP, IMAP, POP3 security and user enumeration.",
5939
- promptPath: join6(__dirname3, "email/prompt.md")
5957
+ promptPath: join6(__dirname2, "email/prompt.md")
5940
5958
  },
5941
5959
  [SERVICE_CATEGORIES.REMOTE_ACCESS]: {
5942
5960
  id: SERVICE_CATEGORIES.REMOTE_ACCESS,
5943
5961
  name: "Remote Access",
5944
5962
  description: "SSH, RDP, VNC and other remote control protocols.",
5945
- promptPath: join6(__dirname3, "remote-access/prompt.md")
5963
+ promptPath: join6(__dirname2, "remote-access/prompt.md")
5946
5964
  },
5947
5965
  [SERVICE_CATEGORIES.FILE_SHARING]: {
5948
5966
  id: SERVICE_CATEGORIES.FILE_SHARING,
5949
5967
  name: "File Sharing",
5950
5968
  description: "SMB, NFS, FTP and shared resource security.",
5951
- promptPath: join6(__dirname3, "file-sharing/prompt.md")
5969
+ promptPath: join6(__dirname2, "file-sharing/prompt.md")
5952
5970
  },
5953
5971
  [SERVICE_CATEGORIES.CLOUD]: {
5954
5972
  id: SERVICE_CATEGORIES.CLOUD,
5955
5973
  name: "Cloud Infrastructure",
5956
5974
  description: "AWS, Azure, and GCP security and misconfiguration.",
5957
- promptPath: join6(__dirname3, "cloud/prompt.md")
5975
+ promptPath: join6(__dirname2, "cloud/prompt.md")
5958
5976
  },
5959
5977
  [SERVICE_CATEGORIES.CONTAINER]: {
5960
5978
  id: SERVICE_CATEGORIES.CONTAINER,
5961
5979
  name: "Container Systems",
5962
5980
  description: "Docker and Kubernetes security testing.",
5963
- promptPath: join6(__dirname3, "container/prompt.md")
5981
+ promptPath: join6(__dirname2, "container/prompt.md")
5964
5982
  },
5965
5983
  [SERVICE_CATEGORIES.API]: {
5966
5984
  id: SERVICE_CATEGORIES.API,
5967
5985
  name: "API Security",
5968
5986
  description: "REST, GraphQL, and SOAP API security testing.",
5969
- promptPath: join6(__dirname3, "api/prompt.md")
5987
+ promptPath: join6(__dirname2, "api/prompt.md")
5970
5988
  },
5971
5989
  [SERVICE_CATEGORIES.WIRELESS]: {
5972
5990
  id: SERVICE_CATEGORIES.WIRELESS,
5973
5991
  name: "Wireless Networks",
5974
5992
  description: "WiFi and Bluetooth security testing.",
5975
- promptPath: join6(__dirname3, "wireless/prompt.md")
5993
+ promptPath: join6(__dirname2, "wireless/prompt.md")
5976
5994
  },
5977
5995
  [SERVICE_CATEGORIES.ICS]: {
5978
5996
  id: SERVICE_CATEGORIES.ICS,
5979
5997
  name: "Industrial Systems",
5980
5998
  description: "Critical infrastructure - Modbus, DNP3, ENIP.",
5981
- promptPath: join6(__dirname3, "ics/prompt.md")
5999
+ promptPath: join6(__dirname2, "ics/prompt.md")
5982
6000
  }
5983
6001
  };
5984
6002
 
@@ -6092,237 +6110,6 @@ var CategorizedToolRegistry = class extends ToolRegistry {
6092
6110
  }
6093
6111
  };
6094
6112
 
6095
- // src/shared/constants/service-ports.ts
6096
- var SERVICE_PORTS = {
6097
- SSH: 22,
6098
- FTP: 21,
6099
- TELNET: 23,
6100
- SMTP: 25,
6101
- DNS: 53,
6102
- HTTP: 80,
6103
- POP3: 110,
6104
- IMAP: 143,
6105
- SMB_NETBIOS: 139,
6106
- SMB: 445,
6107
- HTTPS: 443,
6108
- MSSQL: 1433,
6109
- MYSQL: 3306,
6110
- RDP: 3389,
6111
- POSTGRESQL: 5432,
6112
- REDIS: 6379,
6113
- HTTP_ALT: 8080,
6114
- HTTPS_ALT: 8443,
6115
- MONGODB: 27017,
6116
- ELASTICSEARCH: 9200,
6117
- MEMCACHED: 11211,
6118
- NODE_DEFAULT: 3e3,
6119
- FLASK_DEFAULT: 5e3,
6120
- DJANGO_DEFAULT: 8e3
6121
- };
6122
- var CRITICAL_SERVICE_PORTS = [
6123
- SERVICE_PORTS.SSH,
6124
- SERVICE_PORTS.RDP,
6125
- SERVICE_PORTS.MYSQL,
6126
- SERVICE_PORTS.POSTGRESQL,
6127
- SERVICE_PORTS.REDIS,
6128
- SERVICE_PORTS.MONGODB
6129
- ];
6130
- var NO_AUTH_CRITICAL_PORTS = [
6131
- SERVICE_PORTS.REDIS,
6132
- SERVICE_PORTS.MONGODB,
6133
- SERVICE_PORTS.ELASTICSEARCH,
6134
- SERVICE_PORTS.MEMCACHED
6135
- ];
6136
- var WEB_SERVICE_PORTS = [
6137
- SERVICE_PORTS.HTTP,
6138
- SERVICE_PORTS.HTTPS,
6139
- SERVICE_PORTS.HTTP_ALT,
6140
- SERVICE_PORTS.HTTPS_ALT,
6141
- SERVICE_PORTS.NODE_DEFAULT,
6142
- SERVICE_PORTS.FLASK_DEFAULT,
6143
- SERVICE_PORTS.DJANGO_DEFAULT
6144
- ];
6145
- var PLAINTEXT_HTTP_PORTS = [
6146
- SERVICE_PORTS.HTTP,
6147
- SERVICE_PORTS.HTTP_ALT,
6148
- SERVICE_PORTS.NODE_DEFAULT
6149
- ];
6150
- var DATABASE_PORTS = [
6151
- SERVICE_PORTS.MYSQL,
6152
- SERVICE_PORTS.POSTGRESQL,
6153
- SERVICE_PORTS.MSSQL,
6154
- SERVICE_PORTS.MONGODB,
6155
- SERVICE_PORTS.REDIS
6156
- ];
6157
- var SMB_PORTS = [
6158
- SERVICE_PORTS.SMB,
6159
- SERVICE_PORTS.SMB_NETBIOS
6160
- ];
6161
-
6162
- // src/shared/utils/logger.ts
6163
- var COLORS = {
6164
- reset: "\x1B[0m",
6165
- dim: "\x1B[2m",
6166
- red: "\x1B[31m",
6167
- yellow: "\x1B[33m",
6168
- green: "\x1B[32m",
6169
- blue: "\x1B[34m",
6170
- magenta: "\x1B[35m",
6171
- cyan: "\x1B[36m"
6172
- };
6173
- var levelColors = {
6174
- [0 /* DEBUG */]: COLORS.dim,
6175
- [1 /* INFO */]: COLORS.green,
6176
- [2 /* WARN */]: COLORS.yellow,
6177
- [3 /* ERROR */]: COLORS.red,
6178
- [999 /* SILENT */]: COLORS.reset
6179
- };
6180
- var levelNames = {
6181
- [0 /* DEBUG */]: "DEBUG",
6182
- [1 /* INFO */]: "INFO",
6183
- [2 /* WARN */]: "WARN",
6184
- [3 /* ERROR */]: "ERROR",
6185
- [999 /* SILENT */]: "SILENT"
6186
- };
6187
- var Logger = class {
6188
- constructor(component, config = {}) {
6189
- this.component = component;
6190
- this.config = {
6191
- minLevel: config.minLevel ?? 1 /* INFO */,
6192
- includeTimestamp: config.includeTimestamp ?? true,
6193
- includeComponent: config.includeComponent ?? true,
6194
- colorOutput: config.colorOutput ?? true,
6195
- outputToFile: config.outputToFile ?? false,
6196
- logFilePath: config.logFilePath
6197
- };
6198
- }
6199
- config;
6200
- logBuffer = [];
6201
- maxBufferSize = 1e3;
6202
- /**
6203
- * Set minimum log level
6204
- */
6205
- setMinLevel(level) {
6206
- this.config.minLevel = level;
6207
- }
6208
- /**
6209
- * Log at a specific level
6210
- */
6211
- log(level, message, data) {
6212
- if (level < this.config.minLevel) {
6213
- return;
6214
- }
6215
- const entry = {
6216
- timestamp: Date.now(),
6217
- level,
6218
- component: this.component,
6219
- message,
6220
- data
6221
- };
6222
- this.logBuffer.push(entry);
6223
- if (this.logBuffer.length > this.maxBufferSize) {
6224
- this.logBuffer.shift();
6225
- }
6226
- const formatted = this.formatEntry(entry);
6227
- console.log(formatted);
6228
- }
6229
- /**
6230
- * Format a log entry for output
6231
- */
6232
- formatEntry(entry) {
6233
- const parts = [];
6234
- if (this.config.includeTimestamp) {
6235
- const date = new Date(entry.timestamp);
6236
- const ts = date.toISOString().split("T")[1].slice(0, -1);
6237
- parts.push(this.config.colorOutput ? COLORS.dim + ts + COLORS.reset : ts);
6238
- }
6239
- const levelName = levelNames[entry.level];
6240
- const levelColor = this.config.colorOutput ? levelColors[entry.level] : "";
6241
- parts.push(levelColor + `[${levelName}]` + (this.config.colorOutput ? COLORS.reset : ""));
6242
- if (this.config.includeComponent) {
6243
- const comp = this.config.colorOutput ? COLORS.cyan + entry.component + COLORS.reset : entry.component;
6244
- parts.push(`[${comp}]`);
6245
- }
6246
- parts.push(entry.message);
6247
- if (entry.data) {
6248
- const dataStr = Object.entries(entry.data).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
6249
- parts.push(this.config.colorOutput ? COLORS.dim + dataStr + COLORS.reset : dataStr);
6250
- }
6251
- return parts.join(" ");
6252
- }
6253
- /**
6254
- * Debug level logging
6255
- */
6256
- debug(message, data) {
6257
- this.log(0 /* DEBUG */, message, data);
6258
- }
6259
- /**
6260
- * Info level logging
6261
- */
6262
- info(message, data) {
6263
- this.log(1 /* INFO */, message, data);
6264
- }
6265
- /**
6266
- * Warning level logging
6267
- */
6268
- warn(message, data) {
6269
- this.log(2 /* WARN */, message, data);
6270
- }
6271
- /**
6272
- * Error level logging
6273
- */
6274
- error(message, data) {
6275
- this.log(3 /* ERROR */, message, data);
6276
- }
6277
- /**
6278
- * Get all log entries
6279
- */
6280
- getLogs() {
6281
- return [...this.logBuffer];
6282
- }
6283
- /**
6284
- * Get logs by level
6285
- */
6286
- getLogsByLevel(minLevel) {
6287
- return this.logBuffer.filter((entry) => entry.level >= minLevel);
6288
- }
6289
- /**
6290
- * Clear log buffer
6291
- */
6292
- clearLogs() {
6293
- this.logBuffer = [];
6294
- }
6295
- /**
6296
- * Export logs to string
6297
- */
6298
- exportLogs() {
6299
- return this.logBuffer.map((entry) => this.formatEntry(entry)).join("\n");
6300
- }
6301
- };
6302
- var agentLogger = new Logger("Agent", {
6303
- minLevel: 1 /* INFO */,
6304
- colorOutput: true
6305
- });
6306
-
6307
- // src/shared/constants/_shared/http.const.ts
6308
- var HTTP_STATUS = {
6309
- // 2xx Success
6310
- OK: 200,
6311
- CREATED: 201,
6312
- NO_CONTENT: 204,
6313
- // 4xx Client Errors
6314
- BAD_REQUEST: 400,
6315
- UNAUTHORIZED: 401,
6316
- FORBIDDEN: 403,
6317
- NOT_FOUND: 404,
6318
- RATE_LIMIT: 429,
6319
- // 5xx Server Errors
6320
- INTERNAL_ERROR: 500,
6321
- BAD_GATEWAY: 502,
6322
- SERVICE_UNAVAILABLE: 503,
6323
- GATEWAY_TIMEOUT: 504
6324
- };
6325
-
6326
6113
  // src/shared/constants/llm/api.ts
6327
6114
  var LLM_API = {
6328
6115
  /** Default Anthropic API base URL */
@@ -6380,8 +6167,8 @@ var RETRY_CONFIG = {
6380
6167
  // Initial delay for rate limit retry (exponential backoff)
6381
6168
  };
6382
6169
  var LLM_LIMITS = {
6383
- nonStreamMaxTokens: 8192,
6384
- streamMaxTokens: 16384,
6170
+ nonStreamMaxTokens: 16384,
6171
+ streamMaxTokens: 32768,
6385
6172
  /** WHY: ~3.5 chars/token is a reasonable average for mixed English/CJK content */
6386
6173
  charsPerTokenEstimate: 3.5
6387
6174
  };
@@ -6394,7 +6181,8 @@ var LLM_ERROR_TYPES = {
6394
6181
  UNKNOWN: "unknown"
6395
6182
  };
6396
6183
 
6397
- // src/engine/llm.ts
6184
+ // src/engine/llm-types.ts
6185
+ var HTTP_STATUS = { BAD_REQUEST: 400, UNAUTHORIZED: 401, FORBIDDEN: 403, RATE_LIMIT: 429 };
6398
6186
  var LLMError = class extends Error {
6399
6187
  /** Structured error information */
6400
6188
  errorInfo;
@@ -6425,6 +6213,8 @@ function classifyError(error) {
6425
6213
  }
6426
6214
  return { type: LLM_ERROR_TYPES.UNKNOWN, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Analyze error" };
6427
6215
  }
6216
+
6217
+ // src/engine/llm.ts
6428
6218
  var LLMClient = class {
6429
6219
  apiKey;
6430
6220
  baseUrl;
@@ -6434,7 +6224,7 @@ var LLMClient = class {
6434
6224
  this.apiKey = getApiKey();
6435
6225
  this.baseUrl = getBaseUrl() || LLM_API.DEFAULT_BASE_URL;
6436
6226
  this.model = getModel();
6437
- debugLog("llm", "LLMClient constructed", { model: this.model, baseUrl: this.baseUrl, apiKeyPrefix: this.apiKey.slice(0, 8) + "..." });
6227
+ debugLog("llm", "LLMClient constructed", { model: this.model, baseUrl: this.baseUrl, apiKeyPrefix: this.apiKey.slice(0, DISPLAY_LIMITS.API_KEY_PREFIX) + "..." });
6438
6228
  }
6439
6229
  /**
6440
6230
  * Generate a non-streaming response
@@ -6614,7 +6404,7 @@ var LLMClient = class {
6614
6404
  toolCall.input = parseResult.data;
6615
6405
  debugLog("llm", `[${requestId}] Tool parsed OK`, { id, name: toolCall.name, input: toolCall.input });
6616
6406
  } else {
6617
- toolCall.input = { _parse_error: parseResult.error, _raw_json: toolCall._pendingJson.slice(0, 500) };
6407
+ toolCall.input = { _parse_error: parseResult.error, _raw_json: toolCall._pendingJson.slice(0, DISPLAY_LIMITS.RAW_JSON_ERROR_PREVIEW) };
6618
6408
  debugLog("llm", `[${requestId}] Tool parse FAILED`, { id, name: toolCall.name, error: parseResult.error, raw: toolCall._pendingJson });
6619
6409
  }
6620
6410
  delete toolCall._pendingJson;
@@ -6758,14 +6548,14 @@ function logLLM(message, data) {
6758
6548
  }
6759
6549
 
6760
6550
  // src/engine/orchestrator/orchestrator.ts
6761
- import { fileURLToPath as fileURLToPath4 } from "url";
6551
+ import { fileURLToPath as fileURLToPath3 } from "url";
6762
6552
  import { dirname as dirname4, join as join7 } from "path";
6763
- var __filename3 = fileURLToPath4(import.meta.url);
6764
- var __dirname4 = dirname4(__filename3);
6553
+ var __filename2 = fileURLToPath3(import.meta.url);
6554
+ var __dirname3 = dirname4(__filename2);
6765
6555
 
6766
6556
  // src/engine/state-persistence.ts
6767
- import { writeFileSync as writeFileSync5, readFileSync as readFileSync3, existsSync as existsSync5, readdirSync, statSync } from "fs";
6768
- import { join as join8, basename } from "path";
6557
+ import { writeFileSync as writeFileSync5, readFileSync as readFileSync3, existsSync as existsSync5, readdirSync, statSync, unlinkSync as unlinkSync3 } from "fs";
6558
+ import { join as join8 } from "path";
6769
6559
  function saveState(state) {
6770
6560
  const sessionsDir = WORKSPACE.SESSIONS;
6771
6561
  ensureDirExists(sessionsDir);
@@ -6777,7 +6567,7 @@ function saveState(state) {
6777
6567
  findings: state.getFindings(),
6778
6568
  loot: state.getLoot(),
6779
6569
  todo: state.getTodo(),
6780
- actionLog: state.getRecentActions(9999),
6570
+ actionLog: state.getRecentActions(AGENT_LIMITS.MAX_ACTION_LOG),
6781
6571
  currentPhase: state.getPhase(),
6782
6572
  missionSummary: state.getMissionSummary(),
6783
6573
  missionChecklist: state.getMissionChecklist()
@@ -6787,8 +6577,23 @@ function saveState(state) {
6787
6577
  writeFileSync5(sessionFile, JSON.stringify(snapshot, null, 2), "utf-8");
6788
6578
  const latestFile = join8(sessionsDir, "latest.json");
6789
6579
  writeFileSync5(latestFile, JSON.stringify(snapshot, null, 2), "utf-8");
6580
+ pruneOldSessions(sessionsDir);
6790
6581
  return sessionFile;
6791
6582
  }
6583
+ function pruneOldSessions(sessionsDir) {
6584
+ try {
6585
+ const sessionFiles = readdirSync(sessionsDir).filter((f) => f.endsWith(".json") && f !== "latest.json").map((f) => ({
6586
+ name: f,
6587
+ path: join8(sessionsDir, f),
6588
+ mtime: statSync(join8(sessionsDir, f)).mtimeMs
6589
+ })).sort((a, b) => b.mtime - a.mtime);
6590
+ const toDelete = sessionFiles.slice(AGENT_LIMITS.MAX_SESSION_FILES);
6591
+ for (const file of toDelete) {
6592
+ unlinkSync3(file.path);
6593
+ }
6594
+ } catch {
6595
+ }
6596
+ }
6792
6597
  function loadState(state) {
6793
6598
  const latestFile = join8(WORKSPACE.SESSIONS, "latest.json");
6794
6599
  if (!existsSync5(latestFile)) {
@@ -6798,7 +6603,7 @@ function loadState(state) {
6798
6603
  const raw = readFileSync3(latestFile, "utf-8");
6799
6604
  const snapshot = JSON.parse(raw);
6800
6605
  if (snapshot.version !== 1) {
6801
- console.warn(`[StatePersistence] Unknown snapshot version: ${snapshot.version}`);
6606
+ debugLog("general", `Unknown snapshot version: ${snapshot.version}`);
6802
6607
  return false;
6803
6608
  }
6804
6609
  if (snapshot.engagement) {
@@ -6816,7 +6621,9 @@ function loadState(state) {
6816
6621
  for (const item of snapshot.todo) {
6817
6622
  state.addTodo(item.content, item.priority);
6818
6623
  }
6819
- state.setPhase(snapshot.currentPhase);
6624
+ const validPhases = new Set(Object.values(PHASES));
6625
+ const restoredPhase = validPhases.has(snapshot.currentPhase) ? snapshot.currentPhase : PHASES.RECON;
6626
+ state.setPhase(restoredPhase);
6820
6627
  if (snapshot.missionSummary) {
6821
6628
  state.setMissionSummary(snapshot.missionSummary);
6822
6629
  }
@@ -6825,7 +6632,7 @@ function loadState(state) {
6825
6632
  }
6826
6633
  return true;
6827
6634
  } catch (err) {
6828
- console.warn(`[StatePersistence] Failed to load state: ${err}`);
6635
+ debugLog("general", `Failed to load state: ${err}`);
6829
6636
  return false;
6830
6637
  }
6831
6638
  }
@@ -6835,11 +6642,24 @@ var FLAG_PATTERNS = {
6835
6642
  // Generic CTF flag formats
6836
6643
  generic: /flag\{[^}]+\}/gi,
6837
6644
  curly_upper: /FLAG\{[^}]+\}/g,
6838
- // Platform-specific
6645
+ // Platform-specific (major competitions)
6839
6646
  htb: /HTB\{[^}]+\}/g,
6840
6647
  thm: /THM\{[^}]+\}/g,
6841
6648
  picoCTF: /picoCTF\{[^}]+\}/g,
6649
+ defcon_ooo: /OOO\{[^}]+\}/g,
6650
+ // DEF CON CTF (Order of the Overflow)
6651
+ csaw: /CSAW\{[^}]+\}/g,
6652
+ // CSAW CTF
6653
+ google: /CTF\{[^}]+\}/g,
6654
+ // Google CTF
6655
+ dragon: /DrgnS\{[^}]+\}/g,
6656
+ // Dragon Sector
6657
+ hxp: /hxp\{[^}]+\}/g,
6658
+ // hxp CTF
6659
+ zer0pts: /zer0pts\{[^}]+\}/g,
6660
+ // zer0pts CTF
6842
6661
  ctfd_generic: /[A-Z]{2,10}\{[^}]+\}/g,
6662
+ // Generic CTFd format
6843
6663
  // Hash-style flags (HTB user.txt / root.txt — 32-char hex)
6844
6664
  hash_flag: /\b[a-f0-9]{32}\b/g,
6845
6665
  // Base64-encoded flag detection (common in steganography/forensics)
@@ -6859,6 +6679,83 @@ function detectFlags(output) {
6859
6679
  return Array.from(found);
6860
6680
  }
6861
6681
 
6682
+ // src/agents/tool-error-enrichment.ts
6683
+ function enrichToolErrorContext(ctx) {
6684
+ const { toolName, input, error, originalOutput, progress } = ctx;
6685
+ const lines = [];
6686
+ if (originalOutput) lines.push(originalOutput);
6687
+ lines.push("");
6688
+ lines.push(`[TOOL ERROR ANALYSIS]`);
6689
+ lines.push(`Tool: ${toolName}`);
6690
+ lines.push(`Error: ${error}`);
6691
+ const errorLower = error.toLowerCase();
6692
+ if (isBlockedCommandError(errorLower)) {
6693
+ trackBlockedPattern(progress, toolName, errorLower);
6694
+ appendBlockedCommandHints(lines, errorLower);
6695
+ } else if (isMissingParamError(errorLower)) {
6696
+ const providedParams = Object.keys(input).join(", ") || "none";
6697
+ lines.push(`Provided parameters: ${providedParams}`);
6698
+ lines.push(`Fix: Check the tool's required parameters and provide ALL required values.`);
6699
+ lines.push(`Action: Retry this tool call with the missing parameter(s) added.`);
6700
+ } else if (isNotFoundError(errorLower)) {
6701
+ lines.push(`Fix: The tool or command may not be installed.`);
6702
+ lines.push(`Actions: (1) Try an alternative tool, (2) Use web_search to find installation instructions, or (3) Use a different approach entirely.`);
6703
+ } else if (isPermissionError(errorLower)) {
6704
+ lines.push(`Fix: Insufficient permissions for this operation.`);
6705
+ lines.push(`Actions: (1) Try with sudo if appropriate, (2) Use a different approach, or (3) Check if the target path/resource is correct.`);
6706
+ } else if (isConnectionError(errorLower)) {
6707
+ lines.push(`Fix: Cannot reach the target service.`);
6708
+ lines.push(`Actions: (1) Verify the target host/port is correct, (2) Check if the service is running, (3) Try a different port or protocol.`);
6709
+ } else if (isInvalidInputError(errorLower)) {
6710
+ lines.push(`Fix: The input format is incorrect.`);
6711
+ lines.push(`Actions: (1) Check the input format and fix it, (2) Use web_search to find the correct syntax, or (3) Try a simpler input.`);
6712
+ } else {
6713
+ lines.push(`Actions: (1) Analyze the error and modify your approach, (2) Use web_search("${toolName} ${errorLower.slice(0, AGENT_LIMITS.ERROR_SEARCH_PREVIEW_SLICE)}") to research the error, (3) Try an alternative tool or method.`);
6714
+ }
6715
+ return lines.join("\n");
6716
+ }
6717
+ function isBlockedCommandError(e) {
6718
+ return e.includes("command blocked") || e.includes("injection pattern") || e.includes("not in the allowed") || e.includes("not in allowed paths");
6719
+ }
6720
+ function isMissingParamError(e) {
6721
+ return e.includes("missing") && e.includes("parameter") || e.includes("required") && e.includes("missing") || e.includes("is required");
6722
+ }
6723
+ function isNotFoundError(e) {
6724
+ return e.includes("not found") || e.includes("command not found") || e.includes("no such file");
6725
+ }
6726
+ function isPermissionError(e) {
6727
+ return e.includes("permission denied") || e.includes("access denied") || e.includes("eacces");
6728
+ }
6729
+ function isConnectionError(e) {
6730
+ return e.includes("connection refused") || e.includes("econnrefused") || e.includes("connection reset") || e.includes("timeout");
6731
+ }
6732
+ function isInvalidInputError(e) {
6733
+ return e.includes("invalid") || e.includes("malformed") || e.includes("syntax error");
6734
+ }
6735
+ function trackBlockedPattern(progress, toolName, errorLower) {
6736
+ if (!progress) return;
6737
+ const patternKey = `${toolName}:${errorLower.slice(0, AGENT_LIMITS.BLOCKED_PATTERN_KEY_SLICE)}`;
6738
+ const count = (progress.blockedCommandPatterns.get(patternKey) || 0) + 1;
6739
+ progress.blockedCommandPatterns.set(patternKey, count);
6740
+ progress.totalBlockedCommands++;
6741
+ }
6742
+ function appendBlockedCommandHints(lines, errorLower) {
6743
+ if (errorLower.includes("pipe target") || errorLower.includes("injection")) {
6744
+ lines.push(`Fix: Use the tool's built-in output options instead of shell pipes.`);
6745
+ lines.push(`Alternative approaches:`);
6746
+ lines.push(` 1. Save output to file: command > /tmp/output.txt, then use read_file("/tmp/output.txt")`);
6747
+ lines.push(` 2. Use tool-specific flags: nmap -oN /tmp/scan.txt, curl -o /tmp/page.html`);
6748
+ lines.push(` 3. Use browse_url for web content instead of curl | grep`);
6749
+ lines.push(` 4. If filtering output: run the command first, then read_file + parse results`);
6750
+ } else if (errorLower.includes("redirect")) {
6751
+ lines.push(`Fix: Redirect to /tmp/ or /root/ paths only.`);
6752
+ lines.push(`Example: command > /tmp/output.txt or command 2>&1 > /tmp/errors.txt`);
6753
+ } else {
6754
+ lines.push(`Fix: Simplify the command. Avoid chaining complex operations.`);
6755
+ lines.push(`Break into multiple steps: run_cmd \u2192 read_file \u2192 run_cmd.`);
6756
+ }
6757
+ }
6758
+
6862
6759
  // src/agents/core-agent.ts
6863
6760
  var CoreAgent = class _CoreAgent {
6864
6761
  llm;
@@ -6929,44 +6826,28 @@ var CoreAgent = class _CoreAgent {
6929
6826
  const phase = this.state.getPhase();
6930
6827
  const targets = this.state.getTargets().size;
6931
6828
  const findings = this.state.getFindings().length;
6932
- const phaseActions = {
6933
- [PHASES.RECON]: `RECON PHASE: Start scanning NOW.
6934
- \u2192 run_cmd({ command: "nmap -sV -sC -p- --min-rate=1000 <target>" })
6935
- \u2192 run_cmd({ command: "ffuf -u http://<target>/FUZZ -w /usr/share/wordlists/dirb/common.txt -mc all -fc 404" })`,
6936
- [PHASES.VULN_ANALYSIS]: `VULN ANALYSIS: You have ${targets} target(s). Search CVEs NOW.
6937
- \u2192 For every discovered service+version: web_search("{service} {version} exploit hacktricks")
6938
- \u2192 web_search("{service} {version} CVE PoC github")`,
6939
- [PHASES.EXPLOIT]: `EXPLOIT PHASE: You have ${findings} finding(s). ATTACK NOW.
6940
- \u2192 Pick the highest-severity finding and write an exploit script.
6941
- \u2192 web_search("{CVE} PoC github") \u2192 browse_url \u2192 write_file \u2192 run_cmd`,
6942
- [PHASES.POST_EXPLOIT]: `POST-EXPLOIT: Escalate privileges and move laterally.
6943
- \u2192 bg_process interact: "cat /etc/passwd && cat /etc/shadow && sudo -l"
6944
- \u2192 web_search("{OS} privilege escalation hacktricks")`,
6945
- [PHASES.PRIV_ESC]: `PRIVESC: Escalate NOW.
6946
- \u2192 run_cmd({ command: "sudo -l && find / -perm -4000 -type f 2>/dev/null" })
6947
- \u2192 web_search("{kernel_version} privilege escalation exploit")`,
6948
- [PHASES.LATERAL]: `LATERAL MOVEMENT: Spread to other hosts.
6949
- \u2192 Reuse discovered credentials on all known hosts
6950
- \u2192 run_cmd({ command: "for ip in $(cat /tmp/hosts); do sshpass -p '<cred>' ssh user@$ip id; done" })`,
6951
- [PHASES.WEB]: `WEB PHASE: Test injection points.
6952
- \u2192 get_web_attack_surface with target URL
6953
- \u2192 Test every parameter for SQLi, SSTI, XSS, command injection`
6829
+ const phaseDirection = {
6830
+ [PHASES.RECON]: `RECON: Scan targets. Enumerate services and versions.`,
6831
+ [PHASES.VULN_ANALYSIS]: `VULN ANALYSIS: ${targets} target(s) discovered. Search for CVEs and known exploits.`,
6832
+ [PHASES.EXPLOIT]: `EXPLOIT: ${findings} finding(s) available. Attack the highest-severity one.`,
6833
+ [PHASES.POST_EXPLOIT]: `POST-EXPLOIT: Escalate privileges. Search for escalation paths.`,
6834
+ [PHASES.PRIV_ESC]: `PRIVESC: Find and exploit privilege escalation vectors.`,
6835
+ [PHASES.LATERAL]: `LATERAL: Reuse discovered credentials on other hosts.`,
6836
+ [PHASES.WEB]: `WEB: Enumerate the attack surface. Test every input for injection.`
6954
6837
  };
6955
- const phaseHint = phaseActions[phase] || phaseActions[PHASES.RECON];
6838
+ const direction = phaseDirection[phase] || phaseDirection[PHASES.RECON];
6956
6839
  messages.push({
6957
6840
  role: LLM_ROLES.USER,
6958
- content: `[CTF ALERT] DEADLOCK: ${AGENT_LIMITS.MAX_CONSECUTIVE_IDLE} turns with ZERO tool calls. You are WASTING CTF TIME!
6841
+ content: `\u26A1 DEADLOCK: ${AGENT_LIMITS.MAX_CONSECUTIVE_IDLE} turns with ZERO tool calls.
6959
6842
  Phase: ${phase} | Targets: ${targets} | Findings: ${findings} | Tools executed: ${progress.totalToolsExecuted} (${progress.toolSuccesses}\u2713 ${progress.toolErrors}\u2717)
6960
6843
 
6961
- ${phaseHint}
6844
+ ${direction}
6962
6845
 
6963
- CTF SPEED RULES:
6964
- - \u26A1 EVERY turn MUST have tool calls \u2014 NO exceptions
6965
- - \u26A1 PARALLELIZE: Run 3-5 tools simultaneously when possible
6966
- - \u26A1 NO planning: "Let me..." is forbidden \u2014 JUST ACT
6967
- - \u26A1 web_search is your weapon: Use it immediately when stuck
6968
- - \u26A1 1 fail \u2260 stop: Try DIFFERENT approach immediately
6969
- - \u26A1 NEVER output text without a tool call`
6846
+ RULES:
6847
+ - Every turn MUST have tool calls
6848
+ - If stuck: search for techniques (web_search)
6849
+ - If failed: try a DIFFERENT approach
6850
+ - ACT NOW \u2014 do not plan or explain`
6970
6851
  });
6971
6852
  }
6972
6853
  } catch (error) {
@@ -7162,7 +7043,7 @@ Please decide how to handle this error and continue.`;
7162
7043
  toolName,
7163
7044
  success,
7164
7045
  output,
7165
- outputSummary: output.slice(0, 200),
7046
+ outputSummary: output.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
7166
7047
  error,
7167
7048
  duration
7168
7049
  }
@@ -7273,7 +7154,7 @@ Please decide how to handle this error and continue.`;
7273
7154
  \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.`;
7274
7155
  }
7275
7156
  if (result2.error) {
7276
- outputText = this.enrichToolErrorContext({ toolName: call.name, input: call.input, error: result2.error, originalOutput: outputText, progress });
7157
+ outputText = this.enrichToolError({ toolName: call.name, input: call.input, error: result2.error, originalOutput: outputText, progress });
7277
7158
  if (progress) progress.toolErrors++;
7278
7159
  } else {
7279
7160
  if (progress) {
@@ -7286,75 +7167,17 @@ Please decide how to handle this error and continue.`;
7286
7167
  return { toolCallId: call.id, output: outputText, error: result2.error };
7287
7168
  } catch (error) {
7288
7169
  const errorMsg = String(error);
7289
- const enrichedError = this.enrichToolErrorContext({ toolName: call.name, input: call.input, error: errorMsg, originalOutput: "", progress });
7170
+ const enrichedError = this.enrichToolError({ toolName: call.name, input: call.input, error: errorMsg, originalOutput: "", progress });
7290
7171
  if (progress) progress.toolErrors++;
7291
7172
  this.emitToolResult(call.name, false, enrichedError, errorMsg, Date.now() - toolStartTime);
7292
7173
  return { toolCallId: call.id, output: enrichedError, error: errorMsg };
7293
7174
  }
7294
7175
  }
7295
7176
  /**
7296
- * Enrich tool error messages with actionable context so the LLM can self-correct.
7297
- * Instead of just showing the raw error, we add hints about what went wrong
7298
- * and what the agent can do to fix it.
7177
+ * Enrich tool error delegates to extracted module (§3-1)
7299
7178
  */
7300
- enrichToolErrorContext(ctx) {
7301
- const { toolName, input, error, originalOutput, progress } = ctx;
7302
- const lines = [];
7303
- if (originalOutput) {
7304
- lines.push(originalOutput);
7305
- }
7306
- lines.push("");
7307
- lines.push(`[TOOL ERROR ANALYSIS]`);
7308
- lines.push(`Tool: ${toolName}`);
7309
- lines.push(`Error: ${error}`);
7310
- const errorLower = error.toLowerCase();
7311
- if (errorLower.includes("command blocked") || errorLower.includes("injection pattern") || errorLower.includes("not in the allowed") || errorLower.includes("not in allowed paths")) {
7312
- if (progress) {
7313
- const patternKey = `${toolName}:${errorLower.slice(0, AGENT_LIMITS.BLOCKED_PATTERN_KEY_SLICE)}`;
7314
- const count = (progress.blockedCommandPatterns.get(patternKey) || 0) + 1;
7315
- progress.blockedCommandPatterns.set(patternKey, count);
7316
- progress.totalBlockedCommands++;
7317
- if (count >= AGENT_LIMITS.MAX_BLOCKED_BEFORE_WARN) {
7318
- lines.push(`
7319
- \u26A0\uFE0F REPEAT BLOCK DETECTED (${count}x same pattern). STOP retrying this approach.`);
7320
- lines.push(`You have been blocked ${progress.totalBlockedCommands} times total this session.`);
7321
- }
7322
- }
7323
- if (errorLower.includes("pipe target") || errorLower.includes("injection")) {
7324
- lines.push(`Fix: Use the tool's built-in output options instead of shell pipes.`);
7325
- lines.push(`Alternative approaches:`);
7326
- lines.push(` 1. Save output to file: command > /tmp/output.txt, then use read_file("/tmp/output.txt")`);
7327
- lines.push(` 2. Use tool-specific flags: nmap -oN /tmp/scan.txt, curl -o /tmp/page.html`);
7328
- lines.push(` 3. Use browse_url for web content instead of curl | grep`);
7329
- lines.push(` 4. If filtering output: run the command first, then read_file + parse results`);
7330
- } else if (errorLower.includes("redirect")) {
7331
- lines.push(`Fix: Redirect to /tmp/ or /root/ paths only.`);
7332
- lines.push(`Example: command > /tmp/output.txt or command 2>&1 > /tmp/errors.txt`);
7333
- } else {
7334
- lines.push(`Fix: Simplify the command. Avoid chaining complex operations.`);
7335
- lines.push(`Break into multiple steps: run_cmd \u2192 read_file \u2192 run_cmd.`);
7336
- }
7337
- } else if (errorLower.includes("missing") && errorLower.includes("parameter") || errorLower.includes("required") && errorLower.includes("missing") || errorLower.includes("is required")) {
7338
- const providedParams = Object.keys(input).join(", ") || "none";
7339
- lines.push(`Provided parameters: ${providedParams}`);
7340
- lines.push(`Fix: Check the tool's required parameters and provide ALL required values.`);
7341
- lines.push(`Action: Retry this tool call with the missing parameter(s) added.`);
7342
- } else if (errorLower.includes("not found") || errorLower.includes("command not found") || errorLower.includes("no such file")) {
7343
- lines.push(`Fix: The tool or command may not be installed.`);
7344
- lines.push(`Actions: (1) Try an alternative tool, (2) Use web_search to find installation instructions, or (3) Use a different approach entirely.`);
7345
- } else if (errorLower.includes("permission denied") || errorLower.includes("access denied") || errorLower.includes("eacces")) {
7346
- lines.push(`Fix: Insufficient permissions for this operation.`);
7347
- lines.push(`Actions: (1) Try with sudo if appropriate, (2) Use a different approach, or (3) Check if the target path/resource is correct.`);
7348
- } else if (errorLower.includes("connection refused") || errorLower.includes("econnrefused") || errorLower.includes("connection reset") || errorLower.includes("timeout")) {
7349
- lines.push(`Fix: Cannot reach the target service.`);
7350
- lines.push(`Actions: (1) Verify the target host/port is correct, (2) Check if the service is running, (3) Try a different port or protocol.`);
7351
- } else if (errorLower.includes("invalid") || errorLower.includes("malformed") || errorLower.includes("syntax error")) {
7352
- lines.push(`Fix: The input format is incorrect.`);
7353
- lines.push(`Actions: (1) Check the input format and fix it, (2) Use web_search to find the correct syntax, or (3) Try a simpler input.`);
7354
- } else {
7355
- lines.push(`Actions: (1) Analyze the error and modify your approach, (2) Use web_search("${toolName} ${errorLower.slice(0, AGENT_LIMITS.ERROR_SEARCH_PREVIEW_SLICE)}") to research the error, (3) Try an alternative tool or method.`);
7356
- }
7357
- return lines.join("\n");
7179
+ enrichToolError(ctx) {
7180
+ return enrichToolErrorContext(ctx);
7358
7181
  }
7359
7182
  getToolSchemas() {
7360
7183
  if (!this.toolRegistry) {
@@ -7416,9 +7239,9 @@ Please decide how to handle this error and continue.`;
7416
7239
  };
7417
7240
 
7418
7241
  // src/agents/prompt-builder.ts
7419
- import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
7242
+ import { readFileSync as readFileSync4, existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
7420
7243
  import { join as join9, dirname as dirname5 } from "path";
7421
- import { fileURLToPath as fileURLToPath5 } from "url";
7244
+ import { fileURLToPath as fileURLToPath4 } from "url";
7422
7245
 
7423
7246
  // src/shared/constants/prompts.ts
7424
7247
  var PROMPT_PATHS = {
@@ -7472,8 +7295,8 @@ var INITIAL_TASKS = {
7472
7295
  };
7473
7296
 
7474
7297
  // src/agents/prompt-builder.ts
7475
- var __dirname5 = dirname5(fileURLToPath5(import.meta.url));
7476
- var PROMPTS_DIR = join9(__dirname5, "prompts");
7298
+ var __dirname4 = dirname5(fileURLToPath4(import.meta.url));
7299
+ var PROMPTS_DIR = join9(__dirname4, "prompts");
7477
7300
  var TECHNIQUES_DIR = join9(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
7478
7301
  var { AGENT_FILES } = PROMPT_PATHS;
7479
7302
  var PHASE_PROMPT_MAP = {
@@ -7504,13 +7327,13 @@ var CORE_KNOWLEDGE_FILES = [
7504
7327
  ];
7505
7328
  var PHASE_TECHNIQUE_MAP = {
7506
7329
  [PHASES.RECON]: ["network-svc", "shells", "crypto"],
7507
- [PHASES.VULN_ANALYSIS]: ["injection", "network-svc", "file-attacks", "crypto"],
7508
- [PHASES.EXPLOIT]: ["injection", "shells", "file-attacks", "network-svc", "pwn", "container-escape"],
7509
- [PHASES.POST_EXPLOIT]: ["privesc", "lateral", "auth-access", "shells", "container-escape"],
7330
+ [PHASES.VULN_ANALYSIS]: ["injection", "network-svc", "file-attacks", "crypto", "reversing"],
7331
+ [PHASES.EXPLOIT]: ["injection", "shells", "file-attacks", "network-svc", "pwn", "container-escape", "reversing"],
7332
+ [PHASES.POST_EXPLOIT]: ["privesc", "lateral", "auth-access", "shells", "container-escape", "forensics"],
7510
7333
  [PHASES.PRIV_ESC]: ["privesc", "auth-access", "shells", "pwn", "container-escape"],
7511
7334
  [PHASES.LATERAL]: ["lateral", "ad-attack", "auth-access", "container-escape"],
7512
7335
  [PHASES.PERSISTENCE]: ["shells", "privesc"],
7513
- [PHASES.EXFIL]: ["lateral", "network-svc"],
7336
+ [PHASES.EXFIL]: ["lateral", "network-svc", "forensics"],
7514
7337
  [PHASES.WEB]: ["injection", "file-attacks", "auth-access", "crypto"],
7515
7338
  [PHASES.REPORT]: []
7516
7339
  // Report phase needs no attack techniques
@@ -7562,8 +7385,8 @@ ${content}
7562
7385
  * Load a prompt file from src/agents/prompts/
7563
7386
  */
7564
7387
  loadPromptFile(filename) {
7565
- const path3 = join9(PROMPTS_DIR, filename);
7566
- return existsSync6(path3) ? readFileSync4(path3, PROMPT_CONFIG.ENCODING) : "";
7388
+ const path2 = join9(PROMPTS_DIR, filename);
7389
+ return existsSync6(path2) ? readFileSync4(path2, PROMPT_CONFIG.ENCODING) : "";
7567
7390
  }
7568
7391
  /**
7569
7392
  * Load phase-specific prompt.
@@ -7598,19 +7421,22 @@ ${content}
7598
7421
  return fragments.join("\n\n");
7599
7422
  }
7600
7423
  /**
7601
- * Load only technique files relevant to the current phase.
7602
- * Phase→technique mapping defined in PHASE_TECHNIQUE_MAP.
7424
+ * Load technique files relevant to the current phase.
7425
+ *
7426
+ * Loading strategy (Philosophy §11 — zero-code extension):
7427
+ * 1. PHASE_TECHNIQUE_MAP defines priority techniques per phase (loaded first)
7428
+ * 2. Any .md file in techniques/ NOT in the map is auto-discovered and loaded
7429
+ * as general reference — NO code change needed to add new techniques.
7603
7430
  *
7604
- * Saves ~15-20K tokens vs loading all 8 technique files.
7605
- * If a technique is needed but not mapped, the agent can always
7606
- * use web_search to look up specific attack techniques on demand.
7431
+ * The map is an optimization (priority ordering), not a gate.
7432
+ * "마크다운 파일 하나를 폴더에 넣으면, PromptBuilder가 자동으로 발견하고 로드한다."
7607
7433
  */
7608
7434
  loadPhaseRelevantTechniques(phase) {
7609
7435
  if (!existsSync6(TECHNIQUES_DIR)) return "";
7610
- const relevantTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
7611
- if (relevantTechniques.length === 0) return "";
7436
+ const priorityTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
7437
+ const loadedSet = /* @__PURE__ */ new Set();
7612
7438
  const fragments = [];
7613
- for (const technique of relevantTechniques) {
7439
+ for (const technique of priorityTechniques) {
7614
7440
  const filePath = join9(TECHNIQUES_DIR, `${technique}.md`);
7615
7441
  try {
7616
7442
  if (!existsSync6(filePath)) continue;
@@ -7619,10 +7445,25 @@ ${content}
7619
7445
  fragments.push(`<technique-reference category="${technique}">
7620
7446
  ${content}
7621
7447
  </technique-reference>`);
7448
+ loadedSet.add(`${technique}.md`);
7622
7449
  }
7623
7450
  } catch {
7624
7451
  }
7625
7452
  }
7453
+ try {
7454
+ const allFiles = readdirSync2(TECHNIQUES_DIR).filter((f) => f.endsWith(".md") && f !== "README.md" && !loadedSet.has(f));
7455
+ for (const file of allFiles) {
7456
+ const filePath = join9(TECHNIQUES_DIR, file);
7457
+ const content = readFileSync4(filePath, PROMPT_CONFIG.ENCODING);
7458
+ if (content) {
7459
+ const category = file.replace(".md", "");
7460
+ fragments.push(`<technique-reference category="${category}">
7461
+ ${content}
7462
+ </technique-reference>`);
7463
+ }
7464
+ }
7465
+ } catch {
7466
+ }
7626
7467
  return fragments.join("\n\n");
7627
7468
  }
7628
7469
  getScopeFragment() {
@@ -7851,30 +7692,6 @@ var formatInlineStatus = () => {
7851
7692
  // src/platform/tui/hooks/useAgentState.ts
7852
7693
  import { useState, useRef, useCallback } from "react";
7853
7694
 
7854
- // src/shared/constants/thought.ts
7855
- var THOUGHT_TYPE = {
7856
- THINKING: "thinking",
7857
- // LLM text streaming
7858
- REASONING: "reasoning",
7859
- // LLM extended thinking
7860
- PLANNING: "planning",
7861
- // Strategic planning
7862
- OBSERVATION: "observation",
7863
- // Observing results
7864
- HYPOTHESIS: "hypothesis",
7865
- // Forming hypothesis
7866
- REFLECTION: "reflection",
7867
- // Self-reflection
7868
- ACTION: "action",
7869
- // Taking action
7870
- RESULT: "result",
7871
- // Action result
7872
- STUCK: "stuck",
7873
- // Detected stuck state
7874
- BREAKTHROUGH: "breakthrough"
7875
- // Found breakthrough
7876
- };
7877
-
7878
7695
  // src/shared/constants/theme.ts
7879
7696
  var THEME = {
7880
7697
  // Backgrounds (deep dark with blue tint)
@@ -8022,18 +7839,6 @@ var ICONS = {
8022
7839
  pwned: "\u25C8"
8023
7840
  // Compromised
8024
7841
  };
8025
- var THOUGHT_LABELS = {
8026
- [THOUGHT_TYPE.THINKING]: "[think]",
8027
- [THOUGHT_TYPE.REASONING]: "[reason]",
8028
- [THOUGHT_TYPE.PLANNING]: "[plan]",
8029
- [THOUGHT_TYPE.OBSERVATION]: "[observe]",
8030
- [THOUGHT_TYPE.HYPOTHESIS]: "[hypothesis]",
8031
- [THOUGHT_TYPE.REFLECTION]: "[reflect]",
8032
- [THOUGHT_TYPE.ACTION]: "[action]",
8033
- [THOUGHT_TYPE.RESULT]: "[result]",
8034
- [THOUGHT_TYPE.STUCK]: "[stuck]",
8035
- [THOUGHT_TYPE.BREAKTHROUGH]: "[!]"
8036
- };
8037
7842
 
8038
7843
  // src/platform/tui/constants/display.ts
8039
7844
  var TUI_DISPLAY_LIMITS = {
@@ -8062,7 +7867,19 @@ var TUI_DISPLAY_LIMITS = {
8062
7867
  /** Timer update interval in ms */
8063
7868
  timerInterval: 1e3,
8064
7869
  /** Exit delay in ms */
8065
- exitDelay: 100
7870
+ exitDelay: 100,
7871
+ /** Purpose text max length before truncation */
7872
+ purposeMaxLength: 30,
7873
+ /** Purpose text truncated length */
7874
+ purposeTruncated: 27,
7875
+ /** Tool detail preview length in flow display */
7876
+ toolDetailPreview: 100,
7877
+ /** Observe detail preview length in flow display */
7878
+ observeDetailPreview: 80,
7879
+ /** Max flow nodes to display */
7880
+ maxFlowNodes: 50,
7881
+ /** Max stopped processes to show */
7882
+ maxStoppedProcesses: 3
8066
7883
  };
8067
7884
  var MESSAGE_STYLES = {
8068
7885
  colors: {
@@ -8532,7 +8349,7 @@ function ProcessRow({ proc, compact }) {
8532
8349
  const duration = formatDuration2(proc.durationMs);
8533
8350
  const port = proc.listeningPort ? `:${proc.listeningPort}` : "";
8534
8351
  const purpose = proc.purpose || proc.description || "";
8535
- const truncatedPurpose = compact && purpose.length > 30 ? purpose.slice(0, 27) + "..." : purpose;
8352
+ const truncatedPurpose = compact && purpose.length > TUI_DISPLAY_LIMITS.purposeMaxLength ? purpose.slice(0, TUI_DISPLAY_LIMITS.purposeTruncated) + "..." : purpose;
8536
8353
  return /* @__PURE__ */ jsxs(Box, { children: [
8537
8354
  /* @__PURE__ */ jsx(StatusIndicator, { running: proc.running, exitCode: proc.exitCode }),
8538
8355
  /* @__PURE__ */ jsxs(Text, { color: THEME.text.muted, children: [
@@ -8589,10 +8406,10 @@ var InlineStatus = ({
8589
8406
  stopped.length,
8590
8407
  ")"
8591
8408
  ] }),
8592
- stopped.slice(0, 3).map((proc) => /* @__PURE__ */ jsx(ProcessRow, { proc, compact }, proc.id)),
8593
- stopped.length > 3 && /* @__PURE__ */ jsxs(Text, { color: THEME.text.muted, children: [
8409
+ stopped.slice(0, TUI_DISPLAY_LIMITS.maxStoppedProcesses).map((proc) => /* @__PURE__ */ jsx(ProcessRow, { proc, compact }, proc.id)),
8410
+ stopped.length > TUI_DISPLAY_LIMITS.maxStoppedProcesses && /* @__PURE__ */ jsxs(Text, { color: THEME.text.muted, children: [
8594
8411
  " ... and ",
8595
- stopped.length - 3,
8412
+ stopped.length - TUI_DISPLAY_LIMITS.maxStoppedProcesses,
8596
8413
  " more"
8597
8414
  ] })
8598
8415
  ] }),
@@ -9029,19 +8846,6 @@ ${procData.stdout || "(no output)"}
9029
8846
  };
9030
8847
  var app_default = App;
9031
8848
 
9032
- // src/shared/constants/_shared/signal.const.ts
9033
- var EXIT_CODE = {
9034
- SUCCESS: 0,
9035
- ERROR: 1,
9036
- // Unix convention: 128 + signal number
9037
- SIGINT: 130,
9038
- // 128 + 2
9039
- SIGTERM: 143,
9040
- // 128 + 15
9041
- SIGKILL: 137
9042
- // 128 + 9
9043
- };
9044
-
9045
8849
  // src/shared/constants/cli-defaults.const.ts
9046
8850
  var CLI_DEFAULT = {
9047
8851
  /** Default maximum steps for run command */
@@ -9112,8 +8916,8 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
9112
8916
  const shutdown = async (exitCode = 0) => {
9113
8917
  process.exit(exitCode);
9114
8918
  };
9115
- process.on("SIGINT", () => shutdown(EXIT_CODE.SIGINT));
9116
- process.on("SIGTERM", () => shutdown(EXIT_CODE.SIGTERM));
8919
+ process.on("SIGINT", () => shutdown(EXIT_CODES.SIGINT));
8920
+ process.on("SIGTERM", () => shutdown(EXIT_CODES.SIGTERM));
9117
8921
  try {
9118
8922
  const result2 = await agent.execute(objective);
9119
8923
  console.log(chalk.hex(THEME.status.success)("\n[+] Assessment complete!\n"));
@@ -9145,8 +8949,8 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
9145
8949
  const shutdown = async (exitCode = 0) => {
9146
8950
  process.exit(exitCode);
9147
8951
  };
9148
- process.on("SIGINT", () => shutdown(EXIT_CODE.SIGINT));
9149
- process.on("SIGTERM", () => shutdown(EXIT_CODE.SIGTERM));
8952
+ process.on("SIGINT", () => shutdown(EXIT_CODES.SIGINT));
8953
+ process.on("SIGTERM", () => shutdown(EXIT_CODES.SIGTERM));
9150
8954
  try {
9151
8955
  await agent.execute(`Perform a ${options.scanType} scan on ${target}${options.ports ? ` focusing on ports ${options.ports}` : ""}. Analyze the results and identify potential vulnerabilities.`);
9152
8956
  console.log(chalk.hex(THEME.status.success)("[+] Scan complete!"));