pentesting 0.22.0 → 0.23.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.
@@ -123,7 +137,17 @@ var AGENT_LIMITS = {
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.23.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,131 @@ 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
+ var PATHS = {
871
+ ROOT: PROJECT_ROOT,
872
+ SRC: path.join(PROJECT_ROOT, "src"),
873
+ DIST: path.join(PROJECT_ROOT, "dist")
874
+ };
875
+
876
+ // src/shared/utils/debug-logger.ts
877
+ var DebugLogger = class _DebugLogger {
878
+ static instance;
879
+ logPath;
880
+ initialized = false;
881
+ constructor(clearOnInit = false) {
882
+ const debugDir = WORKSPACE.DEBUG;
883
+ try {
884
+ ensureDirExists(debugDir);
885
+ this.logPath = join(debugDir, "debug.log");
886
+ if (clearOnInit) {
887
+ this.clear();
888
+ }
889
+ this.initialized = true;
890
+ this.log("general", "=== DEBUG LOGGER INITIALIZED ===");
891
+ this.log("general", `Log file: ${this.logPath}`);
892
+ } catch (e) {
893
+ console.error("[DebugLogger] Failed to initialize:", e);
894
+ this.logPath = "";
895
+ }
896
+ }
897
+ static getInstance(clearOnInit = false) {
898
+ if (!_DebugLogger.instance) {
899
+ _DebugLogger.instance = new _DebugLogger(clearOnInit);
900
+ }
901
+ return _DebugLogger.instance;
902
+ }
903
+ /** Reset the singleton instance (used by initDebugLogger) */
904
+ static resetInstance(clearOnInit = false) {
905
+ _DebugLogger.instance = new _DebugLogger(clearOnInit);
906
+ return _DebugLogger.instance;
907
+ }
908
+ log(category, message, data) {
909
+ if (!this.initialized || !this.logPath) return;
910
+ try {
911
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
912
+ let logLine = `[${timestamp}] [${category.toUpperCase()}] ${message}`;
913
+ if (data !== void 0) {
914
+ logLine += ` | ${JSON.stringify(data)}`;
915
+ }
916
+ logLine += "\n";
917
+ appendFileSync(this.logPath, logLine);
918
+ } catch (e) {
919
+ console.error("[DebugLogger] Write error:", e);
920
+ }
921
+ }
922
+ logRaw(category, label, raw) {
923
+ if (!this.initialized || !this.logPath) return;
924
+ try {
925
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
926
+ const logLine = `[${timestamp}] [${category.toUpperCase()}] ${label}:
927
+ ${raw}
928
+ ---
929
+ `;
930
+ appendFileSync(this.logPath, logLine);
931
+ } catch (e) {
932
+ console.error("[DebugLogger] Write error:", e);
933
+ }
934
+ }
935
+ clear() {
936
+ if (!this.logPath) return;
937
+ try {
938
+ writeFileSync(this.logPath, "");
939
+ } catch (e) {
940
+ console.error("[DebugLogger] Clear error:", e);
941
+ }
942
+ }
943
+ };
944
+ var logger = DebugLogger.getInstance(false);
945
+ function initDebugLogger() {
946
+ DebugLogger.resetInstance(true);
947
+ }
948
+ function debugLog(category, message, data) {
949
+ logger.log(category, message, data);
950
+ }
951
+
952
+ // src/shared/utils/command-validator.ts
802
953
  function validateCommand(command) {
803
954
  if (!command || typeof command !== "string") {
804
955
  return { safe: false, error: "Empty or invalid command" };
@@ -893,7 +1044,7 @@ function validateSingleCommand(command) {
893
1044
  };
894
1045
  }
895
1046
  if (!ALLOWED_BINARIES.has(binary)) {
896
- console.warn(`[Security] Unknown binary '${binary}' - use with caution`);
1047
+ debugLog("security", `Unknown binary '${binary}' - use with caution`);
897
1048
  }
898
1049
  }
899
1050
  const redirectResult = validateRedirects(command);
@@ -992,7 +1143,8 @@ function extractBinary(command) {
992
1143
  return binary.toLowerCase();
993
1144
  }
994
1145
 
995
- // src/engine/tools-base.ts
1146
+ // src/engine/tool-auto-installer.ts
1147
+ import { spawn } from "child_process";
996
1148
  var TOOL_PACKAGE_MAP = {
997
1149
  "nmap": { apt: "nmap", brew: "nmap" },
998
1150
  "masscan": { apt: "masscan", brew: "masscan" },
@@ -1046,15 +1198,7 @@ var TOOL_PACKAGE_MAP = {
1046
1198
  "evil-winrm": { apt: "evil-winrm", brew: "evil-winrm" },
1047
1199
  "netexec": { apt: "netexec", brew: "netexec" }
1048
1200
  };
1049
- var globalEventEmitter = null;
1050
- function setCommandEventEmitter(emitter) {
1051
- globalEventEmitter = emitter;
1052
- }
1053
1201
  var installedTools = /* @__PURE__ */ new Set();
1054
- var globalInputHandler = null;
1055
- function setInputHandler(handler) {
1056
- globalInputHandler = handler;
1057
- }
1058
1202
  async function detectPackageManager() {
1059
1203
  const managers = [
1060
1204
  { name: "apt", check: "which apt-get" },
@@ -1091,7 +1235,7 @@ function isCommandNotFound(stderr, stdout) {
1091
1235
  const toolName = toolMatch ? toolMatch[1] : null;
1092
1236
  return { missing: true, toolName };
1093
1237
  }
1094
- async function installTool(toolName) {
1238
+ async function installTool(toolName, eventEmitter, inputHandler) {
1095
1239
  const pkgInfo = TOOL_PACKAGE_MAP[toolName];
1096
1240
  if (!pkgInfo) {
1097
1241
  return {
@@ -1114,7 +1258,7 @@ async function installTool(toolName) {
1114
1258
  };
1115
1259
  }
1116
1260
  const packageName = pkgInfo[pkgManager] || pkgInfo.apt;
1117
- globalEventEmitter?.({
1261
+ eventEmitter?.({
1118
1262
  type: COMMAND_EVENT_TYPES.TOOL_INSTALL,
1119
1263
  message: `Installing missing tool: ${toolName}`,
1120
1264
  detail: `Using ${pkgManager} to install ${packageName}`
@@ -1134,10 +1278,10 @@ async function installTool(toolName) {
1134
1278
  let stdout = "";
1135
1279
  let stderr = "";
1136
1280
  const checkForSudoPrompt = async (data) => {
1137
- if (!globalInputHandler) return;
1281
+ if (!inputHandler) return;
1138
1282
  for (const pattern of INPUT_PROMPT_PATTERNS) {
1139
1283
  if (pattern.test(data)) {
1140
- const userInput = await globalInputHandler(data.trim());
1284
+ const userInput = await inputHandler(data.trim());
1141
1285
  if (userInput !== null && child.stdin.writable) {
1142
1286
  child.stdin.write(userInput + "\n");
1143
1287
  }
@@ -1157,14 +1301,14 @@ async function installTool(toolName) {
1157
1301
  });
1158
1302
  child.on("close", (code) => {
1159
1303
  if (code === 0) {
1160
- globalEventEmitter?.({
1304
+ eventEmitter?.({
1161
1305
  type: COMMAND_EVENT_TYPES.TOOL_INSTALLED,
1162
1306
  message: `Successfully installed: ${toolName}`,
1163
1307
  detail: `Package: ${packageName}`
1164
1308
  });
1165
1309
  resolve({ success: true, output: `Successfully installed ${toolName}` });
1166
1310
  } else {
1167
- globalEventEmitter?.({
1311
+ eventEmitter?.({
1168
1312
  type: COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED,
1169
1313
  message: `Failed to install: ${toolName}`,
1170
1314
  detail: stderr.slice(0, SYSTEM_LIMITS.MAX_ERROR_DETAIL_SLICE)
@@ -1180,6 +1324,16 @@ async function installTool(toolName) {
1180
1324
  });
1181
1325
  });
1182
1326
  }
1327
+
1328
+ // src/engine/tools-base.ts
1329
+ var globalEventEmitter = null;
1330
+ function setCommandEventEmitter(emitter) {
1331
+ globalEventEmitter = emitter;
1332
+ }
1333
+ var globalInputHandler = null;
1334
+ function setInputHandler(handler) {
1335
+ globalInputHandler = handler;
1336
+ }
1183
1337
  async function runCommand(command, args = [], options = {}) {
1184
1338
  const fullCommand = args.length > 0 ? `${command} ${args.join(" ")}` : command;
1185
1339
  const validation = validateCommand(fullCommand);
@@ -1208,7 +1362,7 @@ async function runCommand(command, args = [], options = {}) {
1208
1362
  message: `${STATUS_MARKERS.WARNING} Tool not found: ${toolName}`,
1209
1363
  detail: `Attempting to install...`
1210
1364
  });
1211
- const installResult = await installTool(toolName);
1365
+ const installResult = await installTool(toolName, globalEventEmitter, globalInputHandler);
1212
1366
  if (!installResult.success) {
1213
1367
  return {
1214
1368
  success: false,
@@ -1233,7 +1387,7 @@ async function executeCommandOnce(command, args = [], options = {}) {
1233
1387
  type: COMMAND_EVENT_TYPES.COMMAND_START,
1234
1388
  message: `Executing: ${command.slice(0, DISPLAY_LIMITS.COMMAND_PREVIEW)}${command.length > DISPLAY_LIMITS.COMMAND_PREVIEW ? "..." : ""}`
1235
1389
  });
1236
- const child = spawn("sh", ["-c", command], {
1390
+ const child = spawn2("sh", ["-c", command], {
1237
1391
  timeout,
1238
1392
  env: { ...process.env, ...options.env },
1239
1393
  cwd: options.cwd
@@ -1339,7 +1493,7 @@ async function writeFileContent(filePath, content) {
1339
1493
  try {
1340
1494
  const dir = dirname(filePath);
1341
1495
  ensureDirExists(dir);
1342
- writeFileSync(filePath, content, "utf-8");
1496
+ writeFileSync2(filePath, content, "utf-8");
1343
1497
  return {
1344
1498
  success: true,
1345
1499
  output: `Written to ${filePath}`
@@ -1354,7 +1508,7 @@ async function writeFileContent(filePath, content) {
1354
1508
  }
1355
1509
  }
1356
1510
  function createTempFile(suffix = "") {
1357
- return join(tmpdir(), generateTempFilename(suffix));
1511
+ return join2(tmpdir(), generateTempFilename(suffix));
1358
1512
  }
1359
1513
 
1360
1514
  // src/engine/process-detector.ts
@@ -1512,12 +1666,12 @@ function startBackgroundProcess(command, options = {}) {
1512
1666
  const { tags, port, role, isInteractive } = detectProcessRole(command);
1513
1667
  let wrappedCmd;
1514
1668
  if (isInteractive) {
1515
- writeFileSync2(stdinFile, "", "utf-8");
1669
+ writeFileSync3(stdinFile, "", "utf-8");
1516
1670
  wrappedCmd = `tail -f ${stdinFile} | ${command} > ${stdoutFile} 2> ${stderrFile}`;
1517
1671
  } else {
1518
1672
  wrappedCmd = `${command} > ${stdoutFile} 2> ${stderrFile}`;
1519
1673
  }
1520
- const child = spawn2("sh", ["-c", wrappedCmd], {
1674
+ const child = spawn3("sh", ["-c", wrappedCmd], {
1521
1675
  detached: true,
1522
1676
  stdio: "ignore",
1523
1677
  cwd: options.cwd,
@@ -1571,7 +1725,7 @@ async function sendToProcess(processId, input, waitMs = SYSTEM_LIMITS.DEFAULT_WA
1571
1725
  } catch {
1572
1726
  }
1573
1727
  try {
1574
- appendFileSync(proc.stdinFile, input + "\n", "utf-8");
1728
+ appendFileSync2(proc.stdinFile, input + "\n", "utf-8");
1575
1729
  } catch (error) {
1576
1730
  return { success: false, output: `Failed to send input: ${error}`, newOutput: "" };
1577
1731
  }
@@ -2140,6 +2294,9 @@ var SharedState = class {
2140
2294
  timestamp: Date.now(),
2141
2295
  ...action
2142
2296
  });
2297
+ if (this.data.actionLog.length > AGENT_LIMITS.MAX_ACTION_LOG) {
2298
+ this.data.actionLog.splice(0, this.data.actionLog.length - AGENT_LIMITS.MAX_ACTION_LOG);
2299
+ }
2143
2300
  }
2144
2301
  getRecentActions(count = DISPLAY_LIMITS.COMPACT_LIST_ITEMS) {
2145
2302
  return this.data.actionLog.slice(-count);
@@ -2564,7 +2721,7 @@ Used ports: ${usedPorts.join(", ")}
2564
2721
  }
2565
2722
  try {
2566
2723
  const proc = startBackgroundProcess(command, {
2567
- description: command.slice(0, 80),
2724
+ description: command.slice(0, DISPLAY_LIMITS.COMMAND_DESCRIPTION_PREVIEW),
2568
2725
  purpose: params.purpose
2569
2726
  });
2570
2727
  const portInfo = proc.listeningPort ? `
@@ -2937,7 +3094,7 @@ Types: credential, hash, token, ssh_key, api_key, file, session, ticket, certifi
2937
3094
  success: true,
2938
3095
  output: `Loot recorded: [${lootType}] from ${p.host}
2939
3096
  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.`)
3097
+ ` + (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
3098
  };
2942
3099
  }
2943
3100
  },
@@ -2971,10 +3128,10 @@ Detail: ${p.detail}
2971
3128
  import { execFileSync } from "child_process";
2972
3129
 
2973
3130
  // 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);
3131
+ import path2 from "path";
3132
+ import { fileURLToPath as fileURLToPath2 } from "url";
3133
+ var __filename2 = fileURLToPath2(import.meta.url);
3134
+ var __dirname2 = path2.dirname(__filename2);
2978
3135
  var ENV_KEYS = {
2979
3136
  API_KEY: "PENTEST_API_KEY",
2980
3137
  BASE_URL: "PENTEST_BASE_URL",
@@ -3022,129 +3179,6 @@ var SEARCH_LIMIT = {
3022
3179
  TIMEOUT_MS: 1e4
3023
3180
  };
3024
3181
 
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
3182
  // src/engine/tools/web-browser.ts
3149
3183
  import { join as join5 } from "path";
3150
3184
  import { tmpdir as tmpdir3 } from "os";
@@ -3196,7 +3230,7 @@ var PLAYWRIGHT_SCRIPT = {
3196
3230
  };
3197
3231
 
3198
3232
  // src/engine/tools/web-browser-setup.ts
3199
- import { spawn as spawn3 } from "child_process";
3233
+ import { spawn as spawn4 } from "child_process";
3200
3234
  import { existsSync as existsSync4 } from "fs";
3201
3235
  import { join as join3, dirname as dirname2 } from "path";
3202
3236
  function getPlaywrightPath() {
@@ -3217,12 +3251,12 @@ function getPlaywrightPath() {
3217
3251
  async function checkPlaywright() {
3218
3252
  try {
3219
3253
  const result2 = await new Promise((resolve) => {
3220
- const child = spawn3(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_ACTION.VERSION], { shell: true });
3254
+ const child = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_ACTION.VERSION], { shell: true });
3221
3255
  let stdout = "";
3222
3256
  child.stdout.on("data", (data) => stdout += data);
3223
3257
  child.on("close", (code) => {
3224
3258
  if (code === 0) {
3225
- const browserCheck = spawn3(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_BROWSER.CHROMIUM, PLAYWRIGHT_ACTION.HELP], { shell: true });
3259
+ const browserCheck = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_BROWSER.CHROMIUM, PLAYWRIGHT_ACTION.HELP], { shell: true });
3226
3260
  browserCheck.on("close", (browserCode) => {
3227
3261
  resolve({ installed: true, browserInstalled: browserCode === 0 });
3228
3262
  });
@@ -3240,7 +3274,7 @@ async function checkPlaywright() {
3240
3274
  }
3241
3275
  async function installPlaywright() {
3242
3276
  return new Promise((resolve) => {
3243
- const child = spawn3(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_ACTION.INSTALL, PLAYWRIGHT_BROWSER.CHROMIUM], {
3277
+ const child = spawn4(PLAYWRIGHT_CMD.NPM, [PLAYWRIGHT_CMD.PLAYWRIGHT, PLAYWRIGHT_ACTION.INSTALL, PLAYWRIGHT_BROWSER.CHROMIUM], {
3244
3278
  shell: true,
3245
3279
  stdio: ["ignore", "pipe", "pipe"]
3246
3280
  });
@@ -3262,7 +3296,7 @@ async function installPlaywright() {
3262
3296
  }
3263
3297
 
3264
3298
  // src/engine/tools/web-browser-script.ts
3265
- import { spawn as spawn4 } from "child_process";
3299
+ import { spawn as spawn5 } from "child_process";
3266
3300
  import { writeFileSync as writeFileSync4, unlinkSync as unlinkSync2 } from "fs";
3267
3301
  import { join as join4 } from "path";
3268
3302
  import { tmpdir as tmpdir2 } from "os";
@@ -3289,7 +3323,7 @@ function runPlaywrightScript(script, timeout, scriptPrefix) {
3289
3323
  join4(process.cwd(), "node_modules"),
3290
3324
  process.env.NODE_PATH || ""
3291
3325
  ].filter(Boolean).join(":");
3292
- const child = spawn4(PLAYWRIGHT_CMD.NODE, [scriptPath], {
3326
+ const child = spawn5(PLAYWRIGHT_CMD.NODE, [scriptPath], {
3293
3327
  timeout: timeout + PLAYWRIGHT_SCRIPT.SPAWN_TIMEOUT_BUFFER_MS,
3294
3328
  env: { ...process.env, NODE_PATH: nodePathDirs }
3295
3329
  });
@@ -3392,7 +3426,7 @@ const { chromium } = require(${safePlaywrightPath});
3392
3426
  result.links = await page.evaluate(() => {
3393
3427
  return Array.from(document.querySelectorAll('a[href]')).map(a => ({
3394
3428
  href: a.href,
3395
- text: a.textContent.trim().slice(0, 100)
3429
+ text: a.textContent.trim().slice(0, ${DISPLAY_LIMITS.LINK_TEXT_PREVIEW})
3396
3430
  })).slice(0, ${BROWSER_LIMITS.MAX_LINKS_EXTRACTION});
3397
3431
  });` : ""}
3398
3432
 
@@ -3441,11 +3475,11 @@ function formatBrowserOutput(data, options) {
3441
3475
  }
3442
3476
  if (data.links && options.extractLinks && data.links.length > 0) {
3443
3477
  lines.push(`## Links (${data.links.length} found)`);
3444
- data.links.slice(0, 20).forEach((link) => {
3478
+ data.links.slice(0, DISPLAY_LIMITS.LINKS_PREVIEW).forEach((link) => {
3445
3479
  lines.push(`- [${link.text || "no text"}](${link.href})`);
3446
3480
  });
3447
- if (data.links.length > 20) {
3448
- lines.push(`... and ${data.links.length - 20} more`);
3481
+ if (data.links.length > DISPLAY_LIMITS.LINKS_PREVIEW) {
3482
+ lines.push(`... and ${data.links.length - DISPLAY_LIMITS.LINKS_PREVIEW} more`);
3449
3483
  }
3450
3484
  lines.push("");
3451
3485
  }
@@ -3600,146 +3634,10 @@ async function webSearchWithBrowser(query, engine = "google") {
3600
3634
  });
3601
3635
  }
3602
3636
 
3603
- // src/engine/tools-mid.ts
3637
+ // src/engine/web-search-providers.ts
3604
3638
  function getErrorMessage(error) {
3605
3639
  return error instanceof Error ? error.message : String(error);
3606
3640
  }
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
3641
  async function searchWithGLM(query, apiKey, apiUrl) {
3744
3642
  debugLog("search", "GLM request START", { apiUrl, query });
3745
3643
  const requestBody = {
@@ -3860,30 +3758,171 @@ async function searchWithGenericApi(query, apiKey, apiUrl) {
3860
3758
  const errorText = await response.text();
3861
3759
  throw new Error(`Search API error: ${response.status} - ${errorText}`);
3862
3760
  }
3863
- const data = await response.json();
3864
- return { success: true, output: JSON.stringify(data, null, 2) };
3761
+ const data = await response.json();
3762
+ return { success: true, output: JSON.stringify(data, null, 2) };
3763
+ }
3764
+ async function searchWithPlaywright(query) {
3765
+ debugLog("search", "Playwright Google search START", { query });
3766
+ try {
3767
+ const result2 = await webSearchWithBrowser(query, "google");
3768
+ if (!result2.success) {
3769
+ return {
3770
+ success: false,
3771
+ output: "",
3772
+ error: `Playwright search failed: ${result2.error}. Set SEARCH_API_KEY for reliable search (Brave API by default).`
3773
+ };
3774
+ }
3775
+ debugLog("search", "Playwright Google search COMPLETE", { outputLength: result2.output.length });
3776
+ return {
3777
+ success: true,
3778
+ output: result2.output || `No results found for: ${query}`
3779
+ };
3780
+ } catch (error) {
3781
+ return {
3782
+ success: false,
3783
+ output: "",
3784
+ error: `Playwright search error: ${getErrorMessage(error)}. Set SEARCH_API_KEY for reliable search (Brave API by default).`
3785
+ };
3786
+ }
3787
+ }
3788
+
3789
+ // src/engine/tools-mid.ts
3790
+ function getErrorMessage2(error) {
3791
+ return error instanceof Error ? error.message : String(error);
3792
+ }
3793
+ async function parseNmap(xmlPath) {
3794
+ try {
3795
+ const fileResult = await readFileContent(xmlPath);
3796
+ if (!fileResult.success) {
3797
+ return fileResult;
3798
+ }
3799
+ const xmlContent = fileResult.output;
3800
+ const results = {
3801
+ targets: [],
3802
+ summary: { totalTargets: 0, openPorts: 0, servicesFound: 0 }
3803
+ };
3804
+ const hostRegex = /<host[^>]*>[\s\S]*?<\/host>/g;
3805
+ const hosts = xmlContent.match(hostRegex) || [];
3806
+ for (const hostBlock of hosts) {
3807
+ const ipMatch = hostBlock.match(/<address[^>]*addr="([^"]+)"/);
3808
+ const ip = ipMatch ? ipMatch[1] : "";
3809
+ const hostnameMatch = hostBlock.match(/<hostname[^>]*name="([^"]+)"/);
3810
+ const hostname = hostnameMatch ? hostnameMatch[1] : void 0;
3811
+ const ports = [];
3812
+ const portRegex = /<port[^>]*protocol="([^"]*)"[^>]*portid="(\d+)">[\s\S]*?<\/port>/g;
3813
+ let portMatch;
3814
+ while ((portMatch = portRegex.exec(hostBlock)) !== null) {
3815
+ const protocol = portMatch[1];
3816
+ const port = parseInt(portMatch[2]);
3817
+ const stateMatch = portMatch[0].match(/<state[^>]*state="([^"]+)"/);
3818
+ const state = stateMatch ? stateMatch[1] : "";
3819
+ const serviceMatch = portMatch[0].match(/<service[^>]*name="([^"]*)"(?:[^>]*version="([^"]*)")?/);
3820
+ const service = serviceMatch ? serviceMatch[1] : void 0;
3821
+ const version = serviceMatch && serviceMatch[2] ? serviceMatch[2] : void 0;
3822
+ if (state === "open") {
3823
+ ports.push({ port, protocol, state, service, version });
3824
+ results.summary.openPorts++;
3825
+ if (service) results.summary.servicesFound++;
3826
+ }
3827
+ }
3828
+ if (ip) {
3829
+ results.targets.push({ ip, hostname, ports });
3830
+ results.summary.totalTargets++;
3831
+ }
3832
+ }
3833
+ return {
3834
+ success: true,
3835
+ output: JSON.stringify(results, null, 2)
3836
+ };
3837
+ } catch (error) {
3838
+ return {
3839
+ success: false,
3840
+ output: "",
3841
+ error: getErrorMessage2(error)
3842
+ };
3843
+ }
3865
3844
  }
3866
- async function searchWithPlaywright(query) {
3867
- debugLog("search", "Playwright Google search START", { query });
3845
+ async function searchCVE(service, version) {
3868
3846
  try {
3869
- const result2 = await webSearchWithBrowser(query, "google");
3870
- if (!result2.success) {
3847
+ return searchExploitDB(service, version);
3848
+ } catch (error) {
3849
+ return {
3850
+ success: false,
3851
+ output: "",
3852
+ error: getErrorMessage2(error)
3853
+ };
3854
+ }
3855
+ }
3856
+ async function searchExploitDB(service, version) {
3857
+ try {
3858
+ const query = version ? `${service} ${version}` : service;
3859
+ try {
3860
+ const output = execFileSync("searchsploit", [query, "--color", "never"], {
3861
+ encoding: "utf-8",
3862
+ stdio: ["ignore", "pipe", "pipe"],
3863
+ timeout: SEARCH_LIMIT.TIMEOUT_MS
3864
+ });
3865
+ const lines = output.trim().split("\n").slice(0, SEARCH_LIMIT.MAX_OUTPUT_LINES);
3871
3866
  return {
3872
- success: false,
3873
- output: "",
3874
- error: `Playwright search failed: ${result2.error}. Set SEARCH_API_KEY for reliable search (Brave API by default).`
3867
+ success: true,
3868
+ output: lines.join("\n") || `No exploits found for ${query}`
3869
+ };
3870
+ } catch (e) {
3871
+ const execError = e;
3872
+ const stderr = String(execError.stderr || "");
3873
+ const stdout = String(execError.stdout || "");
3874
+ if (stderr.includes("No results")) {
3875
+ return {
3876
+ success: true,
3877
+ output: `No exploits found for ${query}`
3878
+ };
3879
+ }
3880
+ return {
3881
+ success: true,
3882
+ output: stdout || `No exploits found for ${query}`
3875
3883
  };
3876
3884
  }
3877
- debugLog("search", "Playwright Google search COMPLETE", { outputLength: result2.output.length });
3885
+ } catch (error) {
3878
3886
  return {
3879
- success: true,
3880
- output: result2.output || `No results found for: ${query}`
3887
+ success: false,
3888
+ output: "",
3889
+ error: getErrorMessage2(error)
3881
3890
  };
3891
+ }
3892
+ }
3893
+ async function webSearch(query, _engine) {
3894
+ debugLog("search", "webSearch START", { query });
3895
+ try {
3896
+ const apiKey = getSearchApiKey();
3897
+ const apiUrl = getSearchApiUrl();
3898
+ debugLog("search", "Search API config", {
3899
+ hasApiKey: !!apiKey,
3900
+ apiUrl,
3901
+ apiKeyPrefix: apiKey ? apiKey.slice(0, DISPLAY_LIMITS.API_KEY_PREFIX) + "..." : null
3902
+ });
3903
+ if (apiKey) {
3904
+ if (apiUrl.includes(SEARCH_URL_PATTERN.GLM) || apiUrl.includes(SEARCH_URL_PATTERN.ZHIPU)) {
3905
+ debugLog("search", "Using GLM search");
3906
+ return await searchWithGLM(query, apiKey, apiUrl);
3907
+ } else if (apiUrl.includes(SEARCH_URL_PATTERN.BRAVE)) {
3908
+ debugLog("search", "Using Brave search");
3909
+ return await searchWithBrave(query, apiKey, apiUrl);
3910
+ } else if (apiUrl.includes(SEARCH_URL_PATTERN.SERPER)) {
3911
+ debugLog("search", "Using Serper search");
3912
+ return await searchWithSerper(query, apiKey, apiUrl);
3913
+ } else {
3914
+ debugLog("search", "Using generic search API");
3915
+ return await searchWithGenericApi(query, apiKey, apiUrl);
3916
+ }
3917
+ }
3918
+ debugLog("search", "SEARCH_API_KEY not set \u2014 falling back to Playwright Google search");
3919
+ return await searchWithPlaywright(query);
3882
3920
  } catch (error) {
3921
+ debugLog("search", "webSearch ERROR", { error: getErrorMessage2(error) });
3883
3922
  return {
3884
3923
  success: false,
3885
3924
  output: "",
3886
- error: `Playwright search error: ${getErrorMessage(error)}. Set SEARCH_API_KEY for reliable search (Brave API by default).`
3925
+ error: getErrorMessage2(error)
3887
3926
  };
3888
3927
  }
3889
3928
  }
@@ -4215,6 +4254,10 @@ Can extract forms and inputs for security testing.`,
4215
4254
  {
4216
4255
  name: TOOL_NAMES.GET_OWASP_KNOWLEDGE,
4217
4256
  description: `Get OWASP Top 10 vulnerability knowledge (2017, 2021, 2025 editions).
4257
+ This is BOOTSTRAP knowledge for initial direction. Use it to quickly
4258
+ orient your attack approach, then try attacks based on what you learn.
4259
+ If built-in knowledge isn't enough, use web_search for version-specific details.
4260
+
4218
4261
  Returns structured information about:
4219
4262
  - OWASP Top 10 category details (detection methods, test payloads, tools)
4220
4263
  - Common vulnerability patterns
@@ -4293,10 +4336,14 @@ Returns a step-by-step guide for:
4293
4336
  },
4294
4337
  {
4295
4338
  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
4339
+ description: `Get information about known CVEs from local bootstrap database.
4340
+ This is a STARTING POINT \u2014 use it first for quick lookups on major CVEs.
4341
+ If the CVE isn't found locally or you need more detail, then use web_search.
4342
+
4343
+ Use this to:
4344
+ - Quick-check if a well-known CVE exists locally
4345
+ - Get initial exploit hints for common vulnerabilities
4346
+ - If not found or insufficient, use web_search for full details and PoCs
4300
4347
 
4301
4348
  Includes critical CVEs like Log4Shell, Spring4Shell, EternalBlue, XZ Backdoor, etc.
4302
4349
  If CVE not found locally, use web_search to find it online.`,
@@ -4664,7 +4711,7 @@ Common wordlists are automatically searched in /usr/share/wordlists (rockyou.txt
4664
4711
  const cmd = `hashcat -m ${format || HASHCAT_MODES.MD5} "${hashes}" "${wordlistPath}" --force`;
4665
4712
  if (background) {
4666
4713
  const proc = startBackgroundProcess(cmd, {
4667
- description: `Cracking hashes: ${hashes.slice(0, 20)}...`,
4714
+ description: `Cracking hashes: ${hashes.slice(0, DISPLAY_LIMITS.HASH_PREVIEW_LENGTH)}...`,
4668
4715
  purpose: `Attempting to crack ${format || "unknown"} hashes using ${wordlist}`
4669
4716
  });
4670
4717
  return {
@@ -4771,72 +4818,61 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
4771
4818
  "/usr/share/dirb/",
4772
4819
  "/opt/wordlists/"
4773
4820
  ];
4774
- const results = [];
4775
- results.push("# Available Wordlists on System\\n");
4821
+ const CATEGORY_KEYWORDS = {
4822
+ passwords: ["password", "rockyou"],
4823
+ usernames: ["user"],
4824
+ web: ["web", "content", "raft"],
4825
+ dns: ["dns", "subdomain"],
4826
+ fuzzing: ["fuzz", "injection"],
4827
+ api: ["api"]
4828
+ };
4829
+ const WORDLIST_EXTENSIONS = /* @__PURE__ */ new Set(["txt", "lst", "dict", "list", "wlist"]);
4830
+ const SKIP_DIRS = /* @__PURE__ */ new Set([".", "doc"]);
4831
+ const matchesCategory = (filePath) => {
4832
+ if (!category) return true;
4833
+ const pathLower = filePath.toLowerCase();
4834
+ const keywords = CATEGORY_KEYWORDS[category.toLowerCase()] || [category.toLowerCase()];
4835
+ return keywords.some((kw) => pathLower.includes(kw));
4836
+ };
4837
+ const matchesSearch = (filePath, fileName) => {
4838
+ if (!search) return true;
4839
+ const q = search.toLowerCase();
4840
+ return fileName.toLowerCase().includes(q) || filePath.toLowerCase().includes(q);
4841
+ };
4842
+ const results = ["# Available Wordlists on System\\n"];
4776
4843
  let totalCount = 0;
4777
- const scanDir = (dirPath, maxDepth = 3, currentDepth = 0) => {
4778
- if (currentDepth > maxDepth) return;
4779
- if (!existsSync7(dirPath)) return;
4844
+ const processFile = (fullPath, fileName) => {
4845
+ const ext = fileName.split(".").pop()?.toLowerCase();
4846
+ if (!WORDLIST_EXTENSIONS.has(ext || "")) return;
4847
+ const stats = statSync2(fullPath);
4848
+ if (stats.size < minSize) return;
4849
+ if (!matchesCategory(fullPath)) return;
4850
+ if (!matchesSearch(fullPath, fileName)) return;
4851
+ totalCount++;
4852
+ results.push(`### ${fullPath}`);
4853
+ results.push(`- Size: ${Math.round(stats.size / 1024)} KB (${stats.size.toLocaleString()} bytes)`);
4854
+ results.push("");
4855
+ };
4856
+ const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
4857
+ if (depth > maxDepth || !existsSync7(dirPath)) return;
4858
+ let entries;
4780
4859
  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
- }
4860
+ entries = readdirSync3(dirPath, { withFileTypes: true });
4839
4861
  } catch {
4862
+ return;
4863
+ }
4864
+ for (const entry of entries) {
4865
+ if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
4866
+ const fullPath = join10(dirPath, entry.name);
4867
+ if (entry.isDirectory()) {
4868
+ scanDir(fullPath, maxDepth, depth + 1);
4869
+ continue;
4870
+ }
4871
+ if (!entry.isFile()) continue;
4872
+ try {
4873
+ processFile(fullPath, entry.name);
4874
+ } catch {
4875
+ }
4840
4876
  }
4841
4877
  };
4842
4878
  for (const basePath of scanPaths) {
@@ -6092,237 +6128,6 @@ var CategorizedToolRegistry = class extends ToolRegistry {
6092
6128
  }
6093
6129
  };
6094
6130
 
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
6131
  // src/shared/constants/llm/api.ts
6327
6132
  var LLM_API = {
6328
6133
  /** Default Anthropic API base URL */
@@ -6394,7 +6199,26 @@ var LLM_ERROR_TYPES = {
6394
6199
  UNKNOWN: "unknown"
6395
6200
  };
6396
6201
 
6397
- // src/engine/llm.ts
6202
+ // src/shared/constants/_shared/http.const.ts
6203
+ var HTTP_STATUS = {
6204
+ // 2xx Success
6205
+ OK: 200,
6206
+ CREATED: 201,
6207
+ NO_CONTENT: 204,
6208
+ // 4xx Client Errors
6209
+ BAD_REQUEST: 400,
6210
+ UNAUTHORIZED: 401,
6211
+ FORBIDDEN: 403,
6212
+ NOT_FOUND: 404,
6213
+ RATE_LIMIT: 429,
6214
+ // 5xx Server Errors
6215
+ INTERNAL_ERROR: 500,
6216
+ BAD_GATEWAY: 502,
6217
+ SERVICE_UNAVAILABLE: 503,
6218
+ GATEWAY_TIMEOUT: 504
6219
+ };
6220
+
6221
+ // src/engine/llm-types.ts
6398
6222
  var LLMError = class extends Error {
6399
6223
  /** Structured error information */
6400
6224
  errorInfo;
@@ -6425,6 +6249,8 @@ function classifyError(error) {
6425
6249
  }
6426
6250
  return { type: LLM_ERROR_TYPES.UNKNOWN, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Analyze error" };
6427
6251
  }
6252
+
6253
+ // src/engine/llm.ts
6428
6254
  var LLMClient = class {
6429
6255
  apiKey;
6430
6256
  baseUrl;
@@ -6434,7 +6260,7 @@ var LLMClient = class {
6434
6260
  this.apiKey = getApiKey();
6435
6261
  this.baseUrl = getBaseUrl() || LLM_API.DEFAULT_BASE_URL;
6436
6262
  this.model = getModel();
6437
- debugLog("llm", "LLMClient constructed", { model: this.model, baseUrl: this.baseUrl, apiKeyPrefix: this.apiKey.slice(0, 8) + "..." });
6263
+ debugLog("llm", "LLMClient constructed", { model: this.model, baseUrl: this.baseUrl, apiKeyPrefix: this.apiKey.slice(0, DISPLAY_LIMITS.API_KEY_PREFIX) + "..." });
6438
6264
  }
6439
6265
  /**
6440
6266
  * Generate a non-streaming response
@@ -6614,7 +6440,7 @@ var LLMClient = class {
6614
6440
  toolCall.input = parseResult.data;
6615
6441
  debugLog("llm", `[${requestId}] Tool parsed OK`, { id, name: toolCall.name, input: toolCall.input });
6616
6442
  } else {
6617
- toolCall.input = { _parse_error: parseResult.error, _raw_json: toolCall._pendingJson.slice(0, 500) };
6443
+ toolCall.input = { _parse_error: parseResult.error, _raw_json: toolCall._pendingJson.slice(0, DISPLAY_LIMITS.RAW_JSON_ERROR_PREVIEW) };
6618
6444
  debugLog("llm", `[${requestId}] Tool parse FAILED`, { id, name: toolCall.name, error: parseResult.error, raw: toolCall._pendingJson });
6619
6445
  }
6620
6446
  delete toolCall._pendingJson;
@@ -6764,7 +6590,7 @@ var __filename3 = fileURLToPath4(import.meta.url);
6764
6590
  var __dirname4 = dirname4(__filename3);
6765
6591
 
6766
6592
  // src/engine/state-persistence.ts
6767
- import { writeFileSync as writeFileSync5, readFileSync as readFileSync3, existsSync as existsSync5, readdirSync, statSync } from "fs";
6593
+ import { writeFileSync as writeFileSync5, readFileSync as readFileSync3, existsSync as existsSync5, readdirSync, statSync, unlinkSync as unlinkSync3 } from "fs";
6768
6594
  import { join as join8, basename } from "path";
6769
6595
  function saveState(state) {
6770
6596
  const sessionsDir = WORKSPACE.SESSIONS;
@@ -6777,7 +6603,7 @@ function saveState(state) {
6777
6603
  findings: state.getFindings(),
6778
6604
  loot: state.getLoot(),
6779
6605
  todo: state.getTodo(),
6780
- actionLog: state.getRecentActions(9999),
6606
+ actionLog: state.getRecentActions(AGENT_LIMITS.MAX_ACTION_LOG),
6781
6607
  currentPhase: state.getPhase(),
6782
6608
  missionSummary: state.getMissionSummary(),
6783
6609
  missionChecklist: state.getMissionChecklist()
@@ -6787,8 +6613,23 @@ function saveState(state) {
6787
6613
  writeFileSync5(sessionFile, JSON.stringify(snapshot, null, 2), "utf-8");
6788
6614
  const latestFile = join8(sessionsDir, "latest.json");
6789
6615
  writeFileSync5(latestFile, JSON.stringify(snapshot, null, 2), "utf-8");
6616
+ pruneOldSessions(sessionsDir);
6790
6617
  return sessionFile;
6791
6618
  }
6619
+ function pruneOldSessions(sessionsDir) {
6620
+ try {
6621
+ const sessionFiles = readdirSync(sessionsDir).filter((f) => f.endsWith(".json") && f !== "latest.json").map((f) => ({
6622
+ name: f,
6623
+ path: join8(sessionsDir, f),
6624
+ mtime: statSync(join8(sessionsDir, f)).mtimeMs
6625
+ })).sort((a, b) => b.mtime - a.mtime);
6626
+ const toDelete = sessionFiles.slice(AGENT_LIMITS.MAX_SESSION_FILES);
6627
+ for (const file of toDelete) {
6628
+ unlinkSync3(file.path);
6629
+ }
6630
+ } catch {
6631
+ }
6632
+ }
6792
6633
  function loadState(state) {
6793
6634
  const latestFile = join8(WORKSPACE.SESSIONS, "latest.json");
6794
6635
  if (!existsSync5(latestFile)) {
@@ -6798,7 +6639,7 @@ function loadState(state) {
6798
6639
  const raw = readFileSync3(latestFile, "utf-8");
6799
6640
  const snapshot = JSON.parse(raw);
6800
6641
  if (snapshot.version !== 1) {
6801
- console.warn(`[StatePersistence] Unknown snapshot version: ${snapshot.version}`);
6642
+ debugLog("general", `Unknown snapshot version: ${snapshot.version}`);
6802
6643
  return false;
6803
6644
  }
6804
6645
  if (snapshot.engagement) {
@@ -6816,7 +6657,9 @@ function loadState(state) {
6816
6657
  for (const item of snapshot.todo) {
6817
6658
  state.addTodo(item.content, item.priority);
6818
6659
  }
6819
- state.setPhase(snapshot.currentPhase);
6660
+ const validPhases = new Set(Object.values(PHASES));
6661
+ const restoredPhase = validPhases.has(snapshot.currentPhase) ? snapshot.currentPhase : PHASES.RECON;
6662
+ state.setPhase(restoredPhase);
6820
6663
  if (snapshot.missionSummary) {
6821
6664
  state.setMissionSummary(snapshot.missionSummary);
6822
6665
  }
@@ -6825,7 +6668,7 @@ function loadState(state) {
6825
6668
  }
6826
6669
  return true;
6827
6670
  } catch (err) {
6828
- console.warn(`[StatePersistence] Failed to load state: ${err}`);
6671
+ debugLog("general", `Failed to load state: ${err}`);
6829
6672
  return false;
6830
6673
  }
6831
6674
  }
@@ -6835,11 +6678,24 @@ var FLAG_PATTERNS = {
6835
6678
  // Generic CTF flag formats
6836
6679
  generic: /flag\{[^}]+\}/gi,
6837
6680
  curly_upper: /FLAG\{[^}]+\}/g,
6838
- // Platform-specific
6681
+ // Platform-specific (major competitions)
6839
6682
  htb: /HTB\{[^}]+\}/g,
6840
6683
  thm: /THM\{[^}]+\}/g,
6841
6684
  picoCTF: /picoCTF\{[^}]+\}/g,
6685
+ defcon_ooo: /OOO\{[^}]+\}/g,
6686
+ // DEF CON CTF (Order of the Overflow)
6687
+ csaw: /CSAW\{[^}]+\}/g,
6688
+ // CSAW CTF
6689
+ google: /CTF\{[^}]+\}/g,
6690
+ // Google CTF
6691
+ dragon: /DrgnS\{[^}]+\}/g,
6692
+ // Dragon Sector
6693
+ hxp: /hxp\{[^}]+\}/g,
6694
+ // hxp CTF
6695
+ zer0pts: /zer0pts\{[^}]+\}/g,
6696
+ // zer0pts CTF
6842
6697
  ctfd_generic: /[A-Z]{2,10}\{[^}]+\}/g,
6698
+ // Generic CTFd format
6843
6699
  // Hash-style flags (HTB user.txt / root.txt — 32-char hex)
6844
6700
  hash_flag: /\b[a-f0-9]{32}\b/g,
6845
6701
  // Base64-encoded flag detection (common in steganography/forensics)
@@ -6859,6 +6715,83 @@ function detectFlags(output) {
6859
6715
  return Array.from(found);
6860
6716
  }
6861
6717
 
6718
+ // src/agents/tool-error-enrichment.ts
6719
+ function enrichToolErrorContext(ctx) {
6720
+ const { toolName, input, error, originalOutput, progress } = ctx;
6721
+ const lines = [];
6722
+ if (originalOutput) lines.push(originalOutput);
6723
+ lines.push("");
6724
+ lines.push(`[TOOL ERROR ANALYSIS]`);
6725
+ lines.push(`Tool: ${toolName}`);
6726
+ lines.push(`Error: ${error}`);
6727
+ const errorLower = error.toLowerCase();
6728
+ if (isBlockedCommandError(errorLower)) {
6729
+ trackBlockedPattern(progress, toolName, errorLower);
6730
+ appendBlockedCommandHints(lines, errorLower);
6731
+ } else if (isMissingParamError(errorLower)) {
6732
+ const providedParams = Object.keys(input).join(", ") || "none";
6733
+ lines.push(`Provided parameters: ${providedParams}`);
6734
+ lines.push(`Fix: Check the tool's required parameters and provide ALL required values.`);
6735
+ lines.push(`Action: Retry this tool call with the missing parameter(s) added.`);
6736
+ } else if (isNotFoundError(errorLower)) {
6737
+ lines.push(`Fix: The tool or command may not be installed.`);
6738
+ lines.push(`Actions: (1) Try an alternative tool, (2) Use web_search to find installation instructions, or (3) Use a different approach entirely.`);
6739
+ } else if (isPermissionError(errorLower)) {
6740
+ lines.push(`Fix: Insufficient permissions for this operation.`);
6741
+ lines.push(`Actions: (1) Try with sudo if appropriate, (2) Use a different approach, or (3) Check if the target path/resource is correct.`);
6742
+ } else if (isConnectionError(errorLower)) {
6743
+ lines.push(`Fix: Cannot reach the target service.`);
6744
+ 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.`);
6745
+ } else if (isInvalidInputError(errorLower)) {
6746
+ lines.push(`Fix: The input format is incorrect.`);
6747
+ 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.`);
6748
+ } else {
6749
+ 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.`);
6750
+ }
6751
+ return lines.join("\n");
6752
+ }
6753
+ function isBlockedCommandError(e) {
6754
+ return e.includes("command blocked") || e.includes("injection pattern") || e.includes("not in the allowed") || e.includes("not in allowed paths");
6755
+ }
6756
+ function isMissingParamError(e) {
6757
+ return e.includes("missing") && e.includes("parameter") || e.includes("required") && e.includes("missing") || e.includes("is required");
6758
+ }
6759
+ function isNotFoundError(e) {
6760
+ return e.includes("not found") || e.includes("command not found") || e.includes("no such file");
6761
+ }
6762
+ function isPermissionError(e) {
6763
+ return e.includes("permission denied") || e.includes("access denied") || e.includes("eacces");
6764
+ }
6765
+ function isConnectionError(e) {
6766
+ return e.includes("connection refused") || e.includes("econnrefused") || e.includes("connection reset") || e.includes("timeout");
6767
+ }
6768
+ function isInvalidInputError(e) {
6769
+ return e.includes("invalid") || e.includes("malformed") || e.includes("syntax error");
6770
+ }
6771
+ function trackBlockedPattern(progress, toolName, errorLower) {
6772
+ if (!progress) return;
6773
+ const patternKey = `${toolName}:${errorLower.slice(0, AGENT_LIMITS.BLOCKED_PATTERN_KEY_SLICE)}`;
6774
+ const count = (progress.blockedCommandPatterns.get(patternKey) || 0) + 1;
6775
+ progress.blockedCommandPatterns.set(patternKey, count);
6776
+ progress.totalBlockedCommands++;
6777
+ }
6778
+ function appendBlockedCommandHints(lines, errorLower) {
6779
+ if (errorLower.includes("pipe target") || errorLower.includes("injection")) {
6780
+ lines.push(`Fix: Use the tool's built-in output options instead of shell pipes.`);
6781
+ lines.push(`Alternative approaches:`);
6782
+ lines.push(` 1. Save output to file: command > /tmp/output.txt, then use read_file("/tmp/output.txt")`);
6783
+ lines.push(` 2. Use tool-specific flags: nmap -oN /tmp/scan.txt, curl -o /tmp/page.html`);
6784
+ lines.push(` 3. Use browse_url for web content instead of curl | grep`);
6785
+ lines.push(` 4. If filtering output: run the command first, then read_file + parse results`);
6786
+ } else if (errorLower.includes("redirect")) {
6787
+ lines.push(`Fix: Redirect to /tmp/ or /root/ paths only.`);
6788
+ lines.push(`Example: command > /tmp/output.txt or command 2>&1 > /tmp/errors.txt`);
6789
+ } else {
6790
+ lines.push(`Fix: Simplify the command. Avoid chaining complex operations.`);
6791
+ lines.push(`Break into multiple steps: run_cmd \u2192 read_file \u2192 run_cmd.`);
6792
+ }
6793
+ }
6794
+
6862
6795
  // src/agents/core-agent.ts
6863
6796
  var CoreAgent = class _CoreAgent {
6864
6797
  llm;
@@ -6929,44 +6862,28 @@ var CoreAgent = class _CoreAgent {
6929
6862
  const phase = this.state.getPhase();
6930
6863
  const targets = this.state.getTargets().size;
6931
6864
  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`
6865
+ const phaseDirection = {
6866
+ [PHASES.RECON]: `RECON: Scan targets. Enumerate services and versions.`,
6867
+ [PHASES.VULN_ANALYSIS]: `VULN ANALYSIS: ${targets} target(s) discovered. Search for CVEs and known exploits.`,
6868
+ [PHASES.EXPLOIT]: `EXPLOIT: ${findings} finding(s) available. Attack the highest-severity one.`,
6869
+ [PHASES.POST_EXPLOIT]: `POST-EXPLOIT: Escalate privileges. Search for escalation paths.`,
6870
+ [PHASES.PRIV_ESC]: `PRIVESC: Find and exploit privilege escalation vectors.`,
6871
+ [PHASES.LATERAL]: `LATERAL: Reuse discovered credentials on other hosts.`,
6872
+ [PHASES.WEB]: `WEB: Enumerate the attack surface. Test every input for injection.`
6954
6873
  };
6955
- const phaseHint = phaseActions[phase] || phaseActions[PHASES.RECON];
6874
+ const direction = phaseDirection[phase] || phaseDirection[PHASES.RECON];
6956
6875
  messages.push({
6957
6876
  role: LLM_ROLES.USER,
6958
- content: `[CTF ALERT] DEADLOCK: ${AGENT_LIMITS.MAX_CONSECUTIVE_IDLE} turns with ZERO tool calls. You are WASTING CTF TIME!
6877
+ content: `\u26A1 DEADLOCK: ${AGENT_LIMITS.MAX_CONSECUTIVE_IDLE} turns with ZERO tool calls.
6959
6878
  Phase: ${phase} | Targets: ${targets} | Findings: ${findings} | Tools executed: ${progress.totalToolsExecuted} (${progress.toolSuccesses}\u2713 ${progress.toolErrors}\u2717)
6960
6879
 
6961
- ${phaseHint}
6880
+ ${direction}
6962
6881
 
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`
6882
+ RULES:
6883
+ - Every turn MUST have tool calls
6884
+ - If stuck: search for techniques (web_search)
6885
+ - If failed: try a DIFFERENT approach
6886
+ - ACT NOW \u2014 do not plan or explain`
6970
6887
  });
6971
6888
  }
6972
6889
  } catch (error) {
@@ -7162,7 +7079,7 @@ Please decide how to handle this error and continue.`;
7162
7079
  toolName,
7163
7080
  success,
7164
7081
  output,
7165
- outputSummary: output.slice(0, 200),
7082
+ outputSummary: output.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
7166
7083
  error,
7167
7084
  duration
7168
7085
  }
@@ -7273,7 +7190,7 @@ Please decide how to handle this error and continue.`;
7273
7190
  \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
7191
  }
7275
7192
  if (result2.error) {
7276
- outputText = this.enrichToolErrorContext({ toolName: call.name, input: call.input, error: result2.error, originalOutput: outputText, progress });
7193
+ outputText = this.enrichToolError({ toolName: call.name, input: call.input, error: result2.error, originalOutput: outputText, progress });
7277
7194
  if (progress) progress.toolErrors++;
7278
7195
  } else {
7279
7196
  if (progress) {
@@ -7286,75 +7203,17 @@ Please decide how to handle this error and continue.`;
7286
7203
  return { toolCallId: call.id, output: outputText, error: result2.error };
7287
7204
  } catch (error) {
7288
7205
  const errorMsg = String(error);
7289
- const enrichedError = this.enrichToolErrorContext({ toolName: call.name, input: call.input, error: errorMsg, originalOutput: "", progress });
7206
+ const enrichedError = this.enrichToolError({ toolName: call.name, input: call.input, error: errorMsg, originalOutput: "", progress });
7290
7207
  if (progress) progress.toolErrors++;
7291
7208
  this.emitToolResult(call.name, false, enrichedError, errorMsg, Date.now() - toolStartTime);
7292
7209
  return { toolCallId: call.id, output: enrichedError, error: errorMsg };
7293
7210
  }
7294
7211
  }
7295
7212
  /**
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.
7213
+ * Enrich tool error delegates to extracted module (§3-1)
7299
7214
  */
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");
7215
+ enrichToolError(ctx) {
7216
+ return enrichToolErrorContext(ctx);
7358
7217
  }
7359
7218
  getToolSchemas() {
7360
7219
  if (!this.toolRegistry) {
@@ -7416,7 +7275,7 @@ Please decide how to handle this error and continue.`;
7416
7275
  };
7417
7276
 
7418
7277
  // src/agents/prompt-builder.ts
7419
- import { readFileSync as readFileSync4, existsSync as existsSync6 } from "fs";
7278
+ import { readFileSync as readFileSync4, existsSync as existsSync6, readdirSync as readdirSync2 } from "fs";
7420
7279
  import { join as join9, dirname as dirname5 } from "path";
7421
7280
  import { fileURLToPath as fileURLToPath5 } from "url";
7422
7281
 
@@ -7504,13 +7363,13 @@ var CORE_KNOWLEDGE_FILES = [
7504
7363
  ];
7505
7364
  var PHASE_TECHNIQUE_MAP = {
7506
7365
  [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"],
7366
+ [PHASES.VULN_ANALYSIS]: ["injection", "network-svc", "file-attacks", "crypto", "reversing"],
7367
+ [PHASES.EXPLOIT]: ["injection", "shells", "file-attacks", "network-svc", "pwn", "container-escape", "reversing"],
7368
+ [PHASES.POST_EXPLOIT]: ["privesc", "lateral", "auth-access", "shells", "container-escape", "forensics"],
7510
7369
  [PHASES.PRIV_ESC]: ["privesc", "auth-access", "shells", "pwn", "container-escape"],
7511
7370
  [PHASES.LATERAL]: ["lateral", "ad-attack", "auth-access", "container-escape"],
7512
7371
  [PHASES.PERSISTENCE]: ["shells", "privesc"],
7513
- [PHASES.EXFIL]: ["lateral", "network-svc"],
7372
+ [PHASES.EXFIL]: ["lateral", "network-svc", "forensics"],
7514
7373
  [PHASES.WEB]: ["injection", "file-attacks", "auth-access", "crypto"],
7515
7374
  [PHASES.REPORT]: []
7516
7375
  // Report phase needs no attack techniques
@@ -7598,19 +7457,22 @@ ${content}
7598
7457
  return fragments.join("\n\n");
7599
7458
  }
7600
7459
  /**
7601
- * Load only technique files relevant to the current phase.
7602
- * Phase→technique mapping defined in PHASE_TECHNIQUE_MAP.
7460
+ * Load technique files relevant to the current phase.
7461
+ *
7462
+ * Loading strategy (Philosophy §11 — zero-code extension):
7463
+ * 1. PHASE_TECHNIQUE_MAP defines priority techniques per phase (loaded first)
7464
+ * 2. Any .md file in techniques/ NOT in the map is auto-discovered and loaded
7465
+ * as general reference — NO code change needed to add new techniques.
7603
7466
  *
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.
7467
+ * The map is an optimization (priority ordering), not a gate.
7468
+ * "마크다운 파일 하나를 폴더에 넣으면, PromptBuilder가 자동으로 발견하고 로드한다."
7607
7469
  */
7608
7470
  loadPhaseRelevantTechniques(phase) {
7609
7471
  if (!existsSync6(TECHNIQUES_DIR)) return "";
7610
- const relevantTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
7611
- if (relevantTechniques.length === 0) return "";
7472
+ const priorityTechniques = PHASE_TECHNIQUE_MAP[phase] || [];
7473
+ const loadedSet = /* @__PURE__ */ new Set();
7612
7474
  const fragments = [];
7613
- for (const technique of relevantTechniques) {
7475
+ for (const technique of priorityTechniques) {
7614
7476
  const filePath = join9(TECHNIQUES_DIR, `${technique}.md`);
7615
7477
  try {
7616
7478
  if (!existsSync6(filePath)) continue;
@@ -7619,10 +7481,25 @@ ${content}
7619
7481
  fragments.push(`<technique-reference category="${technique}">
7620
7482
  ${content}
7621
7483
  </technique-reference>`);
7484
+ loadedSet.add(`${technique}.md`);
7622
7485
  }
7623
7486
  } catch {
7624
7487
  }
7625
7488
  }
7489
+ try {
7490
+ const allFiles = readdirSync2(TECHNIQUES_DIR).filter((f) => f.endsWith(".md") && f !== "README.md" && !loadedSet.has(f));
7491
+ for (const file of allFiles) {
7492
+ const filePath = join9(TECHNIQUES_DIR, file);
7493
+ const content = readFileSync4(filePath, PROMPT_CONFIG.ENCODING);
7494
+ if (content) {
7495
+ const category = file.replace(".md", "");
7496
+ fragments.push(`<technique-reference category="${category}">
7497
+ ${content}
7498
+ </technique-reference>`);
7499
+ }
7500
+ }
7501
+ } catch {
7502
+ }
7626
7503
  return fragments.join("\n\n");
7627
7504
  }
7628
7505
  getScopeFragment() {
@@ -7851,30 +7728,6 @@ var formatInlineStatus = () => {
7851
7728
  // src/platform/tui/hooks/useAgentState.ts
7852
7729
  import { useState, useRef, useCallback } from "react";
7853
7730
 
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
7731
  // src/shared/constants/theme.ts
7879
7732
  var THEME = {
7880
7733
  // Backgrounds (deep dark with blue tint)
@@ -8022,18 +7875,6 @@ var ICONS = {
8022
7875
  pwned: "\u25C8"
8023
7876
  // Compromised
8024
7877
  };
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
7878
 
8038
7879
  // src/platform/tui/constants/display.ts
8039
7880
  var TUI_DISPLAY_LIMITS = {
@@ -8062,7 +7903,19 @@ var TUI_DISPLAY_LIMITS = {
8062
7903
  /** Timer update interval in ms */
8063
7904
  timerInterval: 1e3,
8064
7905
  /** Exit delay in ms */
8065
- exitDelay: 100
7906
+ exitDelay: 100,
7907
+ /** Purpose text max length before truncation */
7908
+ purposeMaxLength: 30,
7909
+ /** Purpose text truncated length */
7910
+ purposeTruncated: 27,
7911
+ /** Tool detail preview length in flow display */
7912
+ toolDetailPreview: 100,
7913
+ /** Observe detail preview length in flow display */
7914
+ observeDetailPreview: 80,
7915
+ /** Max flow nodes to display */
7916
+ maxFlowNodes: 50,
7917
+ /** Max stopped processes to show */
7918
+ maxStoppedProcesses: 3
8066
7919
  };
8067
7920
  var MESSAGE_STYLES = {
8068
7921
  colors: {
@@ -8532,7 +8385,7 @@ function ProcessRow({ proc, compact }) {
8532
8385
  const duration = formatDuration2(proc.durationMs);
8533
8386
  const port = proc.listeningPort ? `:${proc.listeningPort}` : "";
8534
8387
  const purpose = proc.purpose || proc.description || "";
8535
- const truncatedPurpose = compact && purpose.length > 30 ? purpose.slice(0, 27) + "..." : purpose;
8388
+ const truncatedPurpose = compact && purpose.length > TUI_DISPLAY_LIMITS.purposeMaxLength ? purpose.slice(0, TUI_DISPLAY_LIMITS.purposeTruncated) + "..." : purpose;
8536
8389
  return /* @__PURE__ */ jsxs(Box, { children: [
8537
8390
  /* @__PURE__ */ jsx(StatusIndicator, { running: proc.running, exitCode: proc.exitCode }),
8538
8391
  /* @__PURE__ */ jsxs(Text, { color: THEME.text.muted, children: [
@@ -8589,10 +8442,10 @@ var InlineStatus = ({
8589
8442
  stopped.length,
8590
8443
  ")"
8591
8444
  ] }),
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: [
8445
+ stopped.slice(0, TUI_DISPLAY_LIMITS.maxStoppedProcesses).map((proc) => /* @__PURE__ */ jsx(ProcessRow, { proc, compact }, proc.id)),
8446
+ stopped.length > TUI_DISPLAY_LIMITS.maxStoppedProcesses && /* @__PURE__ */ jsxs(Text, { color: THEME.text.muted, children: [
8594
8447
  " ... and ",
8595
- stopped.length - 3,
8448
+ stopped.length - TUI_DISPLAY_LIMITS.maxStoppedProcesses,
8596
8449
  " more"
8597
8450
  ] })
8598
8451
  ] }),
@@ -9029,19 +8882,6 @@ ${procData.stdout || "(no output)"}
9029
8882
  };
9030
8883
  var app_default = App;
9031
8884
 
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
8885
  // src/shared/constants/cli-defaults.const.ts
9046
8886
  var CLI_DEFAULT = {
9047
8887
  /** Default maximum steps for run command */
@@ -9112,8 +8952,8 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
9112
8952
  const shutdown = async (exitCode = 0) => {
9113
8953
  process.exit(exitCode);
9114
8954
  };
9115
- process.on("SIGINT", () => shutdown(EXIT_CODE.SIGINT));
9116
- process.on("SIGTERM", () => shutdown(EXIT_CODE.SIGTERM));
8955
+ process.on("SIGINT", () => shutdown(EXIT_CODES.SIGINT));
8956
+ process.on("SIGTERM", () => shutdown(EXIT_CODES.SIGTERM));
9117
8957
  try {
9118
8958
  const result2 = await agent.execute(objective);
9119
8959
  console.log(chalk.hex(THEME.status.success)("\n[+] Assessment complete!\n"));
@@ -9145,8 +8985,8 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
9145
8985
  const shutdown = async (exitCode = 0) => {
9146
8986
  process.exit(exitCode);
9147
8987
  };
9148
- process.on("SIGINT", () => shutdown(EXIT_CODE.SIGINT));
9149
- process.on("SIGTERM", () => shutdown(EXIT_CODE.SIGTERM));
8988
+ process.on("SIGINT", () => shutdown(EXIT_CODES.SIGINT));
8989
+ process.on("SIGTERM", () => shutdown(EXIT_CODES.SIGTERM));
9150
8990
  try {
9151
8991
  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
8992
  console.log(chalk.hex(THEME.status.success)("[+] Scan complete!"));