pentesting 0.12.7 → 0.12.13

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/index.js CHANGED
@@ -1,4 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ ctfResearch,
4
+ searchADWriteups,
5
+ searchByScenario,
6
+ searchLinuxPrivesc,
7
+ searchMachineWriteup,
8
+ searchWindowsPrivesc,
9
+ searchWriteups,
10
+ securityResearch
11
+ } from "./chunk-AOJBE232.js";
2
12
  import {
3
13
  AGENT_CONFIG,
4
14
  AGENT_EVENT,
@@ -15,8 +25,9 @@ import {
15
25
  PHASE_ID,
16
26
  PHASE_STATUS,
17
27
  THOUGHT_TYPE,
18
- TOOL_NAME
19
- } from "./chunk-JXR7HH4V.js";
28
+ TOOL_NAME,
29
+ TOOL_TO_APT
30
+ } from "./chunk-6IXHQS2A.js";
20
31
  import {
21
32
  __require
22
33
  } from "./chunk-3RG5ZIWI.js";
@@ -33,7 +44,7 @@ import Spinner from "ink-spinner";
33
44
 
34
45
  // src/core/agent/autonomous-agent.ts
35
46
  import Anthropic from "@anthropic-ai/sdk";
36
- import { EventEmitter as EventEmitter18 } from "events";
47
+ import { EventEmitter as EventEmitter17 } from "events";
37
48
 
38
49
  // src/core/prompts/autonomous-prompt.ts
39
50
  var AUTONOMOUS_HACKING_PROMPT = `You are Pentesting, an elite autonomous penetration testing AI designed for CTF competitions and professional security assessments. You operate with minimal human intervention, making intelligent decisions, adapting to obstacles, and persistently pursuing objectives until complete system compromise.
@@ -205,7 +216,16 @@ RIGHT: Use ffuf tool with url=https://domain.com/FUZZ, mode=directory
205
216
 
206
217
  NEVER write manual bash loops when MCP tools exist!
207
218
  ALWAYS prefer MCP tools over bash commands!
208
- </mandatory_autonomous_execution>
219
+
220
+ <explore_and_verify_strategy>
221
+ When performing research or vulnerability analysis:
222
+ 1. **Search**: Start with broad searches (search_writeups, security_research).
223
+ 2. **Explore**: Use fetchUrlContent to read the actual content of promising links.
224
+ 3. **Recursive Search**: If you find specific keywords, CVEs, or tool names in the content, perform NEW searches for those specific items.
225
+ 4. **Verify**: Use browser_automation to interact with target sites and verify if the techniques found in research actually apply to the current target.
226
+ 5. **Trace**: Document the path from initial search to final verification.
227
+ </explore_and_verify_strategy>
228
+ </mandatory_autonomous_execution>
209
229
 
210
230
  <output_format>
211
231
  Always structure your thinking clearly:
@@ -2776,12 +2796,6 @@ var ALL_TOOLS = [
2776
2796
  ...RESEARCH_TOOLS
2777
2797
  ];
2778
2798
 
2779
- // src/core/tools/tool-executor.ts
2780
- import { exec, spawn as spawn2 } from "child_process";
2781
- import { promisify } from "util";
2782
- import * as fs from "fs/promises";
2783
- import * as path from "path";
2784
-
2785
2799
  // src/core/resource/resource-manager.ts
2786
2800
  import { EventEmitter } from "events";
2787
2801
  var RESOURCE_EVENT = {
@@ -3085,343 +3099,234 @@ function getResourceManager(config) {
3085
3099
  return resourceManagerInstance;
3086
3100
  }
3087
3101
 
3088
- // src/core/tools/web-search.ts
3089
- import { spawn } from "child_process";
3090
- var WRITEUP_SOURCES = {
3091
- general: [
3092
- "site:0xdf.gitlab.io",
3093
- // 0xdf's blog
3094
- "site:ippsec.rocks",
3095
- // IppSec video index
3096
- "site:medium.com",
3097
- // Medium writeups
3098
- "site:infosecwriteups.com",
3099
- // InfoSec writeups
3100
- "site:hackingarticles.in"
3101
- // Raj Chandel's blog
3102
- ],
3103
- htb: [
3104
- "site:0xdf.gitlab.io",
3105
- "site:ippsec.rocks",
3106
- "site:app.hackthebox.com",
3107
- '"HackTheBox" OR "HTB"'
3108
- ],
3109
- thm: [
3110
- "site:tryhackme.com",
3111
- '"TryHackMe" OR "THM"'
3112
- ],
3113
- ctf: [
3114
- "site:ctftime.org",
3115
- "site:ctf.zeyu2001.com",
3116
- "site:github.com CTF writeup"
3117
- ],
3118
- vulnhub: [
3119
- "site:vulnhub.com",
3120
- '"VulnHub" writeup'
3121
- ]
3122
- };
3123
- var SCENARIO_KEYWORDS = {
3124
- ad: "Active Directory Kerberos LDAP BloodHound",
3125
- windows: "Windows privilege escalation PowerShell",
3126
- linux: "Linux privilege escalation GTFOBins SUID",
3127
- web: "web exploitation XSS SQLi SSTI RCE",
3128
- crypto: "cryptography RSA AES cipher",
3129
- forensics: "forensics memory dump volatility",
3130
- pwn: "binary exploitation buffer overflow ROP",
3131
- reversing: "reverse engineering IDA Ghidra",
3132
- misc: "miscellaneous steganography OSINT"
3133
- };
3134
- async function searchDuckDuckGo(query, options = {}) {
3135
- const { maxResults = 10, timeout = 3e4 } = options;
3136
- return new Promise((resolve) => {
3137
- const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
3138
- const proc = spawn("curl", [
3139
- "-s",
3140
- "-A",
3141
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
3142
- url
3143
- ], { timeout });
3144
- let stdout = "";
3145
- proc.stdout.on("data", (data) => {
3146
- stdout += data.toString();
3147
- });
3148
- proc.on("close", () => {
3149
- const results = [];
3150
- const resultRegex = /<a[^>]+class="result__a"[^>]+href="([^"]+)"[^>]*>([^<]+)<\/a>/g;
3151
- const snippetRegex = /<a[^>]+class="result__snippet"[^>]*>([^<]+)<\/a>/g;
3152
- let match;
3153
- const urls = [];
3154
- const titles = [];
3155
- const snippets = [];
3156
- while ((match = resultRegex.exec(stdout)) !== null) {
3157
- urls.push(match[1]);
3158
- titles.push(match[2]);
3159
- }
3160
- while ((match = snippetRegex.exec(stdout)) !== null) {
3161
- snippets.push(match[1]);
3162
- }
3163
- for (let i = 0; i < Math.min(urls.length, maxResults); i++) {
3164
- results.push({
3165
- title: titles[i] || "",
3166
- url: urls[i] || "",
3167
- snippet: snippets[i] || ""
3168
- });
3169
- }
3170
- resolve(results);
3171
- });
3172
- proc.on("error", () => {
3173
- resolve([]);
3174
- });
3175
- });
3176
- }
3177
- async function searchCVE(query) {
3178
- return searchDuckDuckGo(`${query} site:cve.mitre.org OR site:nvd.nist.gov`);
3179
- }
3180
- async function searchExploits(query) {
3181
- return searchDuckDuckGo(`${query} site:exploit-db.com OR site:github.com exploit POC`);
3182
- }
3183
- async function searchWriteups(query, platform3 = "all") {
3184
- const sources = platform3 === "all" ? WRITEUP_SOURCES.general : WRITEUP_SOURCES[platform3] || WRITEUP_SOURCES.general;
3185
- const siteQuery = sources.join(" OR ");
3186
- return searchDuckDuckGo(`${query} writeup walkthrough ${siteQuery}`);
3187
- }
3188
- async function searchMachineWriteup(machineName) {
3189
- const [writeups, videos] = await Promise.all([
3190
- searchDuckDuckGo(`"${machineName}" HackTheBox OR TryHackMe writeup walkthrough site:0xdf.gitlab.io OR site:medium.com`),
3191
- searchDuckDuckGo(`"${machineName}" ippsec site:youtube.com OR site:ippsec.rocks`)
3192
- ]);
3193
- return { writeups, videos };
3102
+ // src/core/tools/tool-utils.ts
3103
+ import { exec, spawn } from "child_process";
3104
+ import { promisify } from "util";
3105
+ import * as fs from "fs/promises";
3106
+ import * as path from "path";
3107
+ var execAsync = promisify(exec);
3108
+ var SEARCHED_PACKAGES = {};
3109
+ var USE_DOCKER = process.env.PENTESTING_DOCKER === "1";
3110
+ var DOCKER_CONTAINER = process.env.PENTESTING_CONTAINER || "pentesting-tools";
3111
+ var autoInstallEnabled = true;
3112
+ async function commandExists(cmd) {
3113
+ try {
3114
+ await execAsync(`which ${cmd}`);
3115
+ return true;
3116
+ } catch {
3117
+ return false;
3118
+ }
3194
3119
  }
3195
- async function searchByScenario(query, scenario) {
3196
- const keywords = SCENARIO_KEYWORDS[scenario] || "";
3197
- return searchDuckDuckGo(`${query} ${keywords} writeup walkthrough exploit`);
3120
+ async function searchForAptPackage(toolName) {
3121
+ if (SEARCHED_PACKAGES[toolName]) return SEARCHED_PACKAGES[toolName];
3122
+ console.log(`
3123
+ \u{1F50D} Searching for apt package name for '${toolName}' on Kali Linux...`);
3124
+ try {
3125
+ const { searchGoogle, deepSearch } = await import("./web-search-XQYEM24B.js");
3126
+ const query = `apt install package name for ${toolName} on Kali Linux`;
3127
+ const searchResults = await deepSearch(query, { maxResults: 3 });
3128
+ for (const res of searchResults) {
3129
+ const content = res.content || res.snippet;
3130
+ const match = content.match(/apt(?:-get)?\s+install\s+(?:-y\s+)?([a-z0-9._-]+)/i);
3131
+ if (match && match[1]) {
3132
+ const pkg = match[1].toLowerCase();
3133
+ console.log(`\u2728 Found potential package: ${pkg}`);
3134
+ SEARCHED_PACKAGES[toolName] = pkg;
3135
+ return pkg;
3136
+ }
3137
+ }
3138
+ } catch (error) {
3139
+ console.error("Failed to search for package:", error);
3140
+ }
3141
+ return null;
3198
3142
  }
3199
- async function searchADWriteups(query) {
3200
- return searchDuckDuckGo(
3201
- `${query} Active Directory Kerberos LDAP BloodHound GetNPUsers AS-REP Roasting Kerberoasting DCSync Pass-the-Hash writeup`
3202
- );
3143
+ async function installTool(toolName, customPackage) {
3144
+ const packageName = customPackage || TOOL_TO_APT[toolName] || toolName;
3145
+ try {
3146
+ console.log(`
3147
+ \u{1F4E6} Installing ${toolName} (${packageName})...`);
3148
+ const { stdout, stderr } = await execAsync(
3149
+ `sudo apt update -qq && sudo apt install -y ${packageName}`,
3150
+ { timeout: 3e5 }
3151
+ // 5 minute timeout for installation
3152
+ );
3153
+ console.log(`\u2705 ${toolName} installed successfully
3154
+ `);
3155
+ return { success: true, output: stdout + stderr, duration: 0 };
3156
+ } catch (error) {
3157
+ const errorMessage = error instanceof Error ? error.message : String(error);
3158
+ console.log(`\u274C Failed to install ${toolName}: ${errorMessage}
3159
+ `);
3160
+ return { success: false, output: "", error: errorMessage, duration: 0 };
3161
+ }
3203
3162
  }
3204
- async function searchLinuxPrivesc(query) {
3205
- return searchDuckDuckGo(
3206
- `${query} Linux privilege escalation SUID GTFOBins sudo capabilities cronjob kernel exploit writeup`
3207
- );
3163
+ async function ensureToolAvailable(toolName) {
3164
+ if (await commandExists(toolName)) {
3165
+ return true;
3166
+ }
3167
+ if (!autoInstallEnabled) {
3168
+ console.log(`\u26A0\uFE0F Tool '${toolName}' is missing but auto-install is disabled.`);
3169
+ return false;
3170
+ }
3171
+ let packageName = TOOL_TO_APT[toolName];
3172
+ if (!packageName) {
3173
+ packageName = await searchForAptPackage(toolName);
3174
+ }
3175
+ const result = await installTool(toolName, packageName);
3176
+ return result.success;
3208
3177
  }
3209
- async function searchWindowsPrivesc(query) {
3210
- return searchDuckDuckGo(
3211
- `${query} Windows privilege escalation SeImpersonate JuicyPotato PrintSpoofer token impersonation UAC bypass writeup`
3212
- );
3178
+ async function isDockerAvailable() {
3179
+ if (!USE_DOCKER) return false;
3180
+ try {
3181
+ const { stdout } = await execAsync(`docker inspect ${DOCKER_CONTAINER} --format='{{.State.Running}}'`);
3182
+ return stdout.trim() === "true";
3183
+ } catch {
3184
+ return false;
3185
+ }
3213
3186
  }
3214
- async function securityResearch(query) {
3215
- const [general, cves, exploits, writeups] = await Promise.all([
3216
- searchDuckDuckGo(query),
3217
- searchCVE(query),
3218
- searchExploits(query),
3219
- searchWriteups(query)
3220
- ]);
3221
- return { general, cves, exploits, writeups };
3187
+ function wrapForDocker(command) {
3188
+ const cleanCmd = command.replace(/^sudo\s+/, "");
3189
+ return `docker exec ${DOCKER_CONTAINER} sh -c "${cleanCmd.replace(/"/g, '\\"')}"`;
3222
3190
  }
3223
- async function ctfResearch(boxName, scenario) {
3224
- const [machine, scenarioResults, exploits] = await Promise.all([
3225
- searchMachineWriteup(boxName),
3226
- scenario ? searchByScenario(boxName, scenario) : Promise.resolve([]),
3227
- searchExploits(boxName)
3228
- ]);
3229
- return { machine, scenario: scenarioResults, exploits };
3191
+ async function shouldUseDocker(command) {
3192
+ if (!USE_DOCKER) return false;
3193
+ return await isDockerAvailable();
3230
3194
  }
3231
-
3232
- // src/core/tools/tool-executor.ts
3233
- var execAsync = promisify(exec);
3234
- var DOCKER_IMAGE = "agnusdei1207/pentesting:latest";
3235
- var DOCKER_CONTAINER = process.env.PENTESTING_CONTAINER || "pentesting-tools";
3236
- var FORCE_DOCKER = process.env.PENTESTING_DOCKER === "1";
3237
- var dockerStarted = false;
3238
- var DOCKER_TOOLS = [
3239
- // Network scanning
3240
- TOOL_NAME.RUSTSCAN,
3241
- TOOL_NAME.NMAP_SCAN,
3242
- TOOL_NAME.MASSCAN,
3243
- TOOL_NAME.TCPDUMP_CAPTURE,
3244
- TOOL_NAME.PING,
3245
- TOOL_NAME.TRACEROUTE,
3246
- TOOL_NAME.MTR,
3247
- TOOL_NAME.NETCAT,
3248
- TOOL_NAME.TSHARK,
3249
- TOOL_NAME.NGREP,
3250
- TOOL_NAME.ARP_SCAN,
3251
- TOOL_NAME.SOCAT,
3252
- // DNS & Subdomain
3253
- TOOL_NAME.DIG,
3254
- TOOL_NAME.HOST,
3255
- TOOL_NAME.NSLOOKUP,
3256
- TOOL_NAME.WHOIS,
3257
- TOOL_NAME.SUBFINDER,
3258
- TOOL_NAME.AMASS,
3259
- TOOL_NAME.DNSENUM,
3260
- TOOL_NAME.DNSRECON,
3261
- TOOL_NAME.DNSMAP,
3262
- TOOL_NAME.ZONE_TRANSFER,
3263
- // Service Enumeration
3264
- TOOL_NAME.SNMP_WALK,
3265
- TOOL_NAME.SNMP_CHECK,
3266
- TOOL_NAME.ONESIXTYONE,
3267
- TOOL_NAME.FTP_ENUM,
3268
- TOOL_NAME.FTP_ANON,
3269
- TOOL_NAME.NBTSCAN,
3270
- TOOL_NAME.RPC_INFO,
3271
- TOOL_NAME.SHOWMOUNT,
3272
- TOOL_NAME.TELNET,
3273
- // Web tools
3274
- TOOL_NAME.FFUF,
3275
- TOOL_NAME.GOBUSTER,
3276
- TOOL_NAME.DIRB,
3277
- TOOL_NAME.FEROXBUSTER,
3278
- TOOL_NAME.WHATWEB,
3279
- TOOL_NAME.HTTPX,
3280
- TOOL_NAME.NUCLEI,
3281
- TOOL_NAME.NIKTO,
3282
- TOOL_NAME.DIRECTORY_BRUTEFORCE,
3283
- TOOL_NAME.SQL_INJECTION,
3284
- TOOL_NAME.WAYBACKURLS,
3285
- TOOL_NAME.WAFW00F,
3286
- TOOL_NAME.GOWITNESS,
3287
- // Windows/SMB/AD
3288
- TOOL_NAME.SMB_ENUM,
3289
- TOOL_NAME.SMBMAP,
3290
- TOOL_NAME.ENUM4LINUX,
3291
- TOOL_NAME.CRACKMAPEXEC,
3292
- TOOL_NAME.SMBCLIENT,
3293
- TOOL_NAME.RPCCLIENT,
3294
- TOOL_NAME.WINRM,
3295
- TOOL_NAME.RDP_CHECK,
3296
- TOOL_NAME.LDAP_SEARCH,
3297
- TOOL_NAME.KERBRUTE,
3298
- TOOL_NAME.BLOODHOUND,
3299
- // Database
3300
- TOOL_NAME.MSSQL_CLIENT,
3301
- TOOL_NAME.MYSQL_CLIENT,
3302
- TOOL_NAME.PSQL_CLIENT,
3303
- TOOL_NAME.REDIS_CLI,
3304
- TOOL_NAME.MONGO_CLIENT,
3305
- // Bruteforce & Credentials
3306
- TOOL_NAME.HYDRA,
3307
- TOOL_NAME.MEDUSA,
3308
- TOOL_NAME.BRUTEFORCE_LOGIN,
3309
- TOOL_NAME.CRACK_HASH,
3310
- TOOL_NAME.JOHN,
3311
- TOOL_NAME.HASHCAT,
3312
- TOOL_NAME.HASHID,
3313
- // Exploitation
3314
- TOOL_NAME.SEARCHSPLOIT,
3315
- TOOL_NAME.METASPLOIT,
3316
- TOOL_NAME.GENERATE_PAYLOAD,
3317
- TOOL_NAME.MSFVENOM,
3318
- // SSH & Tunneling
3319
- TOOL_NAME.SSH,
3320
- TOOL_NAME.CHISEL,
3321
- TOOL_NAME.PROXYCHAINS,
3322
- TOOL_NAME.SETUP_TUNNEL,
3323
- TOOL_NAME.LATERAL_MOVEMENT,
3324
- TOOL_NAME.REVERSE_SHELL,
3325
- TOOL_NAME.DUMP_CREDENTIALS,
3326
- // Listener & Payload
3327
- TOOL_NAME.NC_LISTENER,
3328
- TOOL_NAME.PYTHON_HTTP_SERVER,
3329
- TOOL_NAME.RLWRAP,
3330
- TOOL_NAME.PWNCAT,
3331
- // Impacket
3332
- TOOL_NAME.IMPACKET_SECRETSDUMP,
3333
- TOOL_NAME.IMPACKET_PSEXEC,
3334
- TOOL_NAME.IMPACKET_WMIEXEC,
3335
- TOOL_NAME.IMPACKET_SMBEXEC,
3336
- TOOL_NAME.IMPACKET_ATEXEC,
3337
- TOOL_NAME.IMPACKET_DCOMEXEC,
3338
- TOOL_NAME.IMPACKET_GETNPUSERS,
3339
- TOOL_NAME.IMPACKET_GETUSERSPNS,
3340
- // Forensics
3341
- TOOL_NAME.BINWALK,
3342
- TOOL_NAME.FOREMOST,
3343
- TOOL_NAME.STEGHIDE,
3344
- TOOL_NAME.EXIFTOOL,
3345
- // Reversing
3346
- TOOL_NAME.GDB,
3347
- TOOL_NAME.RADARE2,
3348
- // Privesc
3349
- TOOL_NAME.RUN_PRIVESC_ENUM,
3350
- TOOL_NAME.CHECK_SUDO,
3351
- TOOL_NAME.FIND_SUID
3352
- ];
3353
- async function ensureDockerContainer() {
3354
- if (dockerStarted) return true;
3195
+ async function executeBash(command, options = {}) {
3196
+ const startTime = Date.now();
3197
+ const timeout = options.timeout || 6e4;
3198
+ const resourceManager = getResourceManager();
3355
3199
  try {
3356
- const { stdout } = await execAsync(`docker inspect ${DOCKER_CONTAINER} --format='{{.State.Running}}'`);
3357
- if (stdout.trim() === "true") {
3358
- dockerStarted = true;
3359
- return true;
3360
- }
3361
- await execAsync(`docker start ${DOCKER_CONTAINER}`);
3362
- dockerStarted = true;
3363
- console.log(`[Docker] Started container: ${DOCKER_CONTAINER}`);
3364
- return true;
3365
- } catch (err) {
3366
- const errorMsg = err.message || String(err);
3367
- if (errorMsg.includes("Cannot connect to the Docker daemon") || errorMsg.includes("docker.sock")) {
3368
- console.log("\n\u26A0\uFE0F Docker is not running!\n");
3369
- console.log("To use advanced security tools (nmap, rustscan, etc.), please:");
3370
- console.log(" 1. Install Docker: https://docs.docker.com/get-docker/");
3371
- console.log(" 2. Start Docker Desktop (macOS): open -a Docker");
3372
- console.log(" 3. Wait for Docker to fully start, then retry\n");
3373
- console.log("Falling back to local tools (curl, dig, etc.)...\n");
3374
- return false;
3200
+ let finalCommand = command;
3201
+ const useDocker = options.useDocker ?? await shouldUseDocker(command);
3202
+ if (useDocker) {
3203
+ finalCommand = wrapForDocker(command);
3204
+ } else {
3205
+ const baseCmd = command.trim().split(/\s+/)[0].replace(/^sudo\s+/, "");
3206
+ const aptPackage = TOOL_TO_APT[baseCmd];
3207
+ if (aptPackage && !await commandExists(baseCmd)) {
3208
+ const installed = await ensureToolAvailable(baseCmd);
3209
+ if (!installed) {
3210
+ return {
3211
+ success: false,
3212
+ output: "",
3213
+ error: `Tool '${baseCmd}' is not installed and auto-install failed. Run: sudo apt install ${aptPackage}`,
3214
+ duration: 0
3215
+ };
3216
+ }
3217
+ }
3375
3218
  }
3376
- try {
3377
- console.log(`[Docker] Pulling and starting ${DOCKER_IMAGE}...`);
3378
- await execAsync(`docker run -d --name ${DOCKER_CONTAINER} --network host ${DOCKER_IMAGE}`, {
3379
- timeout: 12e4
3380
- // 2 min timeout for pull
3219
+ if (options.background) {
3220
+ const child = spawn("/bin/bash", ["-c", finalCommand], {
3221
+ detached: true,
3222
+ stdio: "ignore",
3223
+ env: { ...process.env, LANG: "en_US.UTF-8", LC_ALL: "en_US.UTF-8" }
3381
3224
  });
3382
- dockerStarted = true;
3383
- console.log(`[Docker] Container ready: ${DOCKER_CONTAINER}`);
3384
- return true;
3385
- } catch (createErr) {
3386
- const createMsg = createErr.message || "";
3387
- if (createMsg.includes("Cannot connect to the Docker daemon")) {
3388
- console.log("\n\u26A0\uFE0F Docker is not running! Start it first:\n");
3389
- console.log(" macOS: open -a Docker");
3390
- console.log(" Linux: sudo systemctl start docker");
3391
- console.log(" Windows: Start Docker Desktop\n");
3392
- } else {
3393
- console.error(`[Docker] Failed to start container: ${createErr}`);
3225
+ if (child.pid) {
3226
+ resourceManager.trackProcess(child.pid);
3227
+ child.on("exit", () => {
3228
+ if (child.pid) resourceManager.untrackProcess(child.pid);
3229
+ });
3230
+ child.on("error", () => {
3231
+ if (child.pid) resourceManager.untrackProcess(child.pid);
3232
+ });
3394
3233
  }
3395
- return false;
3234
+ child.unref();
3235
+ return { success: true, output: `[Background] Command started with PID ${child.pid}`, duration: 0 };
3396
3236
  }
3237
+ const promise = new Promise((resolve, reject) => {
3238
+ const child = exec(finalCommand, {
3239
+ timeout,
3240
+ maxBuffer: 50 * 1024 * 1024,
3241
+ shell: "/bin/bash",
3242
+ encoding: "utf8",
3243
+ env: { ...process.env, LANG: "en_US.UTF-8", LC_ALL: "en_US.UTF-8" }
3244
+ }, (error, stdout2, stderr2) => {
3245
+ if (child.pid) resourceManager.untrackProcess(child.pid);
3246
+ if (error) reject({ ...error, stdout: stdout2, stderr: stderr2 });
3247
+ else resolve({ stdout: stdout2, stderr: stderr2 });
3248
+ });
3249
+ if (child.pid) resourceManager.trackProcess(child.pid);
3250
+ });
3251
+ const { stdout, stderr } = await promise;
3252
+ return {
3253
+ success: true,
3254
+ output: (useDocker ? "[Docker] " : "") + stdout + (stderr ? `
3255
+ [STDERR]
3256
+ ${stderr}` : ""),
3257
+ duration: Math.round((Date.now() - startTime) / 1e3)
3258
+ };
3259
+ } catch (error) {
3260
+ const err = error;
3261
+ if (err.message?.includes("No such container")) {
3262
+ return { success: false, output: "", error: `Docker container '${DOCKER_CONTAINER}' not running.`, duration: 0 };
3263
+ }
3264
+ return { success: false, output: err.stdout || "", error: err.stderr || err.message, exitCode: err.code, duration: 0 };
3397
3265
  }
3398
3266
  }
3399
- async function isDockerAvailable() {
3400
- return await ensureDockerContainer();
3267
+ var _currentTarget = null;
3268
+ var _targetListeners = [];
3269
+ function onTargetChange(listener) {
3270
+ _targetListeners.push(listener);
3401
3271
  }
3402
- async function commandExists(cmd) {
3272
+ async function setTarget(target) {
3403
3273
  try {
3404
- await execAsync(`which ${cmd}`);
3405
- return true;
3406
- } catch {
3407
- return false;
3274
+ _currentTarget = target;
3275
+ _targetListeners.forEach((listener) => {
3276
+ try {
3277
+ listener(target);
3278
+ } catch (e) {
3279
+ }
3280
+ });
3281
+ return { success: true, output: `\u{1F3AF} Target set: ${target}
3282
+
3283
+ Now beginning reconnaissance...`, duration: 0 };
3284
+ } catch (error) {
3285
+ const errorMessage = error instanceof Error ? error.message : String(error);
3286
+ return { success: false, output: "", error: errorMessage, duration: 0 };
3408
3287
  }
3409
3288
  }
3410
- async function shouldUseDocker(command) {
3411
- if (FORCE_DOCKER) return true;
3412
- const baseCmd = command.trim().split(/\s+/)[0].replace(/^sudo\s+/, "");
3413
- if (!DOCKER_TOOLS.some((tool) => baseCmd.includes(tool))) {
3414
- return false;
3289
+ async function readFile2(filePath, startLine, endLine) {
3290
+ try {
3291
+ const content = await fs.readFile(filePath, "utf-8");
3292
+ let output = content;
3293
+ if (startLine || endLine) {
3294
+ const lines = content.split("\n");
3295
+ output = lines.slice((startLine || 1) - 1, endLine || lines.length).join("\n");
3296
+ }
3297
+ return { success: true, output, duration: 0 };
3298
+ } catch (error) {
3299
+ const errorMessage = error instanceof Error ? error.message : String(error);
3300
+ return { success: false, output: "", error: errorMessage, duration: 0 };
3415
3301
  }
3416
- if (await commandExists(baseCmd)) {
3417
- return false;
3302
+ }
3303
+ async function writeFile2(filePath, content, overwrite = false) {
3304
+ try {
3305
+ const exists = await fs.access(filePath).then(() => true).catch(() => false);
3306
+ if (exists && !overwrite) {
3307
+ return { success: false, output: "", error: "File exists. Set overwrite: true to replace.", duration: 0 };
3308
+ }
3309
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
3310
+ await fs.writeFile(filePath, content, "utf-8");
3311
+ return { success: true, output: `Written to ${filePath}`, duration: 0 };
3312
+ } catch (error) {
3313
+ const errorMessage = error instanceof Error ? error.message : String(error);
3314
+ return { success: false, output: "", error: errorMessage, duration: 0 };
3418
3315
  }
3419
- return await isDockerAvailable();
3420
3316
  }
3421
- function wrapForDocker(command) {
3422
- const cleanCmd = command.replace(/^sudo\s+/, "");
3423
- return `docker exec ${DOCKER_CONTAINER} sh -c "${cleanCmd.replace(/"/g, '\\"')}"`;
3317
+ async function listDirectory(dirPath, recursive = false, hidden = false) {
3318
+ try {
3319
+ const flags = `-l${hidden ? "a" : ""}${recursive ? "R" : ""}`;
3320
+ const { stdout } = await execAsync(`ls ${flags} "${dirPath}"`);
3321
+ return { success: true, output: stdout, duration: 0 };
3322
+ } catch (error) {
3323
+ const errorMessage = error instanceof Error ? error.message : String(error);
3324
+ return { success: false, output: "", error: errorMessage, duration: 0 };
3325
+ }
3424
3326
  }
3327
+
3328
+ // src/core/tools/tool-executor.ts
3329
+ import * as fs2 from "fs/promises";
3425
3330
  async function executeToolCall(toolName, input) {
3426
3331
  const startTime = Date.now();
3427
3332
  const resourceManager = getResourceManager();
@@ -3796,206 +3701,50 @@ async function executeToolCall(toolName, input) {
3796
3701
  result = await takeScreenshot(input);
3797
3702
  break;
3798
3703
  // Research & Writeup Tools
3799
- case "search_writeups":
3704
+ case TOOL_NAME.SEARCH_WRITEUPS:
3800
3705
  result = await executeSearchWriteups(input);
3801
3706
  break;
3802
- case "search_machine":
3707
+ case TOOL_NAME.SEARCH_MACHINE:
3803
3708
  result = await executeSearchMachine(input);
3804
3709
  break;
3805
- case "search_by_scenario":
3710
+ case TOOL_NAME.SEARCH_BY_SCENARIO:
3806
3711
  result = await executeSearchByScenario(input);
3807
3712
  break;
3808
- case "search_ad_writeups":
3713
+ case TOOL_NAME.SEARCH_AD_WRITEUPS:
3809
3714
  result = await executeSearchADWriteups(input);
3810
3715
  break;
3811
- case "search_linux_privesc":
3716
+ case TOOL_NAME.SEARCH_LINUX_PRIVESC:
3812
3717
  result = await executeSearchLinuxPrivesc(input);
3813
3718
  break;
3814
- case "search_windows_privesc":
3815
- result = await executeSearchWindowsPrivesc(input);
3816
- break;
3817
- case "ctf_research":
3818
- result = await executeCtfResearch(input);
3819
- break;
3820
- case "security_research":
3821
- result = await executeSecurityResearch(input);
3822
- break;
3823
- default:
3824
- result = {
3825
- success: false,
3826
- output: "",
3827
- error: `Unknown tool: ${toolName}`,
3828
- duration: Date.now() - startTime
3829
- };
3830
- }
3831
- result.duration = Date.now() - startTime;
3832
- return result;
3833
- } catch (error) {
3834
- return {
3835
- success: false,
3836
- output: "",
3837
- error: error.message || String(error),
3838
- duration: Date.now() - startTime
3839
- };
3840
- } finally {
3841
- resourceManager.trackJobEnd();
3842
- }
3843
- }
3844
- async function executeBash(command, options = {}) {
3845
- const startTime = Date.now();
3846
- const timeout = options.timeout || 6e4;
3847
- const resourceManager = getResourceManager();
3848
- try {
3849
- let finalCommand = command;
3850
- const useDocker = options.useDocker ?? await shouldUseDocker(command);
3851
- if (useDocker) {
3852
- finalCommand = wrapForDocker(command);
3853
- console.log(`[Docker] Executing: ${finalCommand}`);
3854
- }
3855
- if (options.background) {
3856
- const child = spawn2("/bin/bash", ["-c", finalCommand], {
3857
- detached: true,
3858
- stdio: "ignore",
3859
- env: {
3860
- ...process.env,
3861
- LANG: "en_US.UTF-8",
3862
- LC_ALL: "en_US.UTF-8"
3863
- }
3864
- });
3865
- if (child.pid) {
3866
- resourceManager.trackProcess(child.pid);
3867
- child.on("exit", () => {
3868
- if (child.pid) resourceManager.untrackProcess(child.pid);
3869
- });
3870
- child.on("error", () => {
3871
- if (child.pid) resourceManager.untrackProcess(child.pid);
3872
- });
3873
- }
3874
- child.unref();
3875
- return {
3876
- success: true,
3877
- output: `[Background] Command started with PID ${child.pid}`,
3878
- duration: 0
3879
- };
3880
- }
3881
- const promise = new Promise((resolve, reject) => {
3882
- const child = exec(finalCommand, {
3883
- timeout,
3884
- maxBuffer: 50 * 1024 * 1024,
3885
- // 50MB
3886
- shell: "/bin/bash",
3887
- encoding: "utf8",
3888
- env: {
3889
- ...process.env,
3890
- LANG: "en_US.UTF-8",
3891
- LC_ALL: "en_US.UTF-8"
3892
- }
3893
- }, (error, stdout2, stderr2) => {
3894
- if (child.pid) resourceManager.untrackProcess(child.pid);
3895
- if (error) reject({ ...error, stdout: stdout2, stderr: stderr2 });
3896
- else resolve({ stdout: stdout2, stderr: stderr2 });
3897
- });
3898
- if (child.pid) {
3899
- resourceManager.trackProcess(child.pid);
3900
- }
3901
- });
3902
- const { stdout, stderr } = await promise;
3903
- return {
3904
- success: true,
3905
- output: (useDocker ? "[Docker] " : "") + stdout + (stderr ? `
3906
- [STDERR]
3907
- ${stderr}` : ""),
3908
- duration: Math.round((Date.now() - startTime) / 1e3)
3909
- };
3910
- } catch (error) {
3911
- if (error.message?.includes("No such container")) {
3912
- return {
3913
- success: false,
3914
- output: "",
3915
- error: `Docker container '${DOCKER_CONTAINER}' not running. Start it with: cd docker && docker-compose up -d`,
3916
- duration: 0
3917
- };
3918
- }
3919
- return {
3920
- success: false,
3921
- output: error.stdout || "",
3922
- error: error.stderr || error.message,
3923
- exitCode: error.code,
3924
- duration: 0
3925
- };
3926
- }
3927
- }
3928
- var _currentTarget = null;
3929
- var _targetListeners = [];
3930
- function onTargetChange(listener) {
3931
- _targetListeners.push(listener);
3932
- }
3933
- async function setTarget(target) {
3934
- try {
3935
- _currentTarget = target;
3936
- _targetListeners.forEach((listener) => {
3937
- try {
3938
- listener(target);
3939
- } catch (e) {
3940
- console.error("Target listener error:", e);
3941
- }
3942
- });
3943
- return {
3944
- success: true,
3945
- output: `\u{1F3AF} Target set: ${target}
3946
-
3947
- Now beginning reconnaissance...`,
3948
- duration: 0
3949
- };
3950
- } catch (error) {
3951
- return {
3952
- success: false,
3953
- output: "",
3954
- error: error.message || String(error),
3955
- duration: 0
3956
- };
3957
- }
3958
- }
3959
- async function readFile2(filePath, startLine, endLine) {
3960
- try {
3961
- const content = await fs.readFile(filePath, "utf-8");
3962
- let output = content;
3963
- if (startLine || endLine) {
3964
- const lines = content.split("\n");
3965
- const start = (startLine || 1) - 1;
3966
- const end = endLine || lines.length;
3967
- output = lines.slice(start, end).join("\n");
3968
- }
3969
- return { success: true, output, duration: 0 };
3970
- } catch (error) {
3971
- return { success: false, output: "", error: error.message, duration: 0 };
3972
- }
3973
- }
3974
- async function writeFile2(filePath, content, overwrite = false) {
3975
- try {
3976
- const exists = await fs.access(filePath).then(() => true).catch(() => false);
3977
- if (exists && !overwrite) {
3978
- return {
3979
- success: false,
3980
- output: "",
3981
- error: "File exists. Set overwrite: true to replace.",
3982
- duration: 0
3983
- };
3719
+ case TOOL_NAME.SEARCH_WINDOWS_PRIVESC:
3720
+ result = await executeSearchWindowsPrivesc(input);
3721
+ break;
3722
+ case TOOL_NAME.CTF_RESEARCH:
3723
+ result = await executeCtfResearch(input);
3724
+ break;
3725
+ case TOOL_NAME.SECURITY_RESEARCH:
3726
+ result = await executeSecurityResearch(input);
3727
+ break;
3728
+ default:
3729
+ result = {
3730
+ success: false,
3731
+ output: "",
3732
+ error: `Unknown tool: ${toolName}`,
3733
+ duration: Date.now() - startTime
3734
+ };
3984
3735
  }
3985
- await fs.mkdir(path.dirname(filePath), { recursive: true });
3986
- await fs.writeFile(filePath, content, "utf-8");
3987
- return { success: true, output: `Written to ${filePath}`, duration: 0 };
3988
- } catch (error) {
3989
- return { success: false, output: "", error: error.message, duration: 0 };
3990
- }
3991
- }
3992
- async function listDirectory(dirPath, recursive = false, hidden = false) {
3993
- try {
3994
- const flags = `-l${hidden ? "a" : ""}${recursive ? "R" : ""}`;
3995
- const { stdout } = await execAsync(`ls ${flags} "${dirPath}"`);
3996
- return { success: true, output: stdout, duration: 0 };
3736
+ result.duration = Date.now() - startTime;
3737
+ return result;
3997
3738
  } catch (error) {
3998
- return { success: false, output: "", error: error.message, duration: 0 };
3739
+ const errMsg = error instanceof Error ? error.message : String(error);
3740
+ return {
3741
+ success: false,
3742
+ output: "",
3743
+ error: errMsg,
3744
+ duration: Date.now() - startTime
3745
+ };
3746
+ } finally {
3747
+ resourceManager.trackJobEnd();
3999
3748
  }
4000
3749
  }
4001
3750
  async function executeRustscan(input) {
@@ -4134,7 +3883,7 @@ const { chromium } = require('playwright');
4134
3883
  })();
4135
3884
  `;
4136
3885
  const scriptPath = "/tmp/playwright_script.js";
4137
- await fs.writeFile(scriptPath, playwrightScript);
3886
+ await fs2.writeFile(scriptPath, playwrightScript);
4138
3887
  return executeBash(`node ${scriptPath}`, { timeout: 6e4 });
4139
3888
  }
4140
3889
  async function executeSearchsploit(input) {
@@ -4306,12 +4055,12 @@ async function reportFinding(input) {
4306
4055
  const findingsPath = "/tmp/hacker-code-findings.json";
4307
4056
  let findings = [];
4308
4057
  try {
4309
- const existing = await fs.readFile(findingsPath, "utf-8");
4058
+ const existing = await fs2.readFile(findingsPath, "utf-8");
4310
4059
  findings = JSON.parse(existing);
4311
4060
  } catch {
4312
4061
  }
4313
4062
  findings.push(finding);
4314
- await fs.writeFile(findingsPath, JSON.stringify(findings, null, 2));
4063
+ await fs2.writeFile(findingsPath, JSON.stringify(findings, null, 2));
4315
4064
  return {
4316
4065
  success: true,
4317
4066
  output: `Finding recorded: [${severity?.toString().toUpperCase()}] ${title}`,
@@ -4332,7 +4081,7 @@ const { chromium } = require('playwright');
4332
4081
  console.log('Screenshot saved');
4333
4082
  })();
4334
4083
  `;
4335
- await fs.writeFile("/tmp/screenshot.js", script);
4084
+ await fs2.writeFile("/tmp/screenshot.js", script);
4336
4085
  return executeBash("node /tmp/screenshot.js");
4337
4086
  }
4338
4087
  return executeBash(`script -q /dev/null -c "cat" | tee "${filename || "terminal.txt"}"`);
@@ -4930,14 +4679,14 @@ async function executeSearchWriteups(input) {
4930
4679
  const startTime = Date.now();
4931
4680
  try {
4932
4681
  const query = String(input.query || "");
4933
- const platform3 = input.platform || "all";
4682
+ const platform3 = input.platform || "general";
4934
4683
  const results = await searchWriteups(query, platform3);
4935
4684
  const output = results.length > 0 ? results.map((r, i) => `[${i + 1}] ${r.title}
4936
4685
  URL: ${r.url}
4937
4686
  ${r.snippet}`).join("\n\n") : "No writeups found.";
4938
4687
  return { success: true, output, duration: Date.now() - startTime };
4939
4688
  } catch (error) {
4940
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4689
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
4941
4690
  }
4942
4691
  }
4943
4692
  async function executeSearchMachine(input) {
@@ -4957,7 +4706,7 @@ async function executeSearchMachine(input) {
4957
4706
  ${r.url}`).join("\n") : "No videos found.";
4958
4707
  return { success: true, output, duration: Date.now() - startTime };
4959
4708
  } catch (error) {
4960
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4709
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
4961
4710
  }
4962
4711
  }
4963
4712
  async function executeSearchByScenario(input) {
@@ -4971,7 +4720,7 @@ async function executeSearchByScenario(input) {
4971
4720
  ${r.snippet}`).join("\n\n") : `No ${scenario} writeups found.`;
4972
4721
  return { success: true, output, duration: Date.now() - startTime };
4973
4722
  } catch (error) {
4974
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4723
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
4975
4724
  }
4976
4725
  }
4977
4726
  async function executeSearchADWriteups(input) {
@@ -4984,7 +4733,7 @@ async function executeSearchADWriteups(input) {
4984
4733
  ${r.snippet}`).join("\n\n") : "No AD writeups found.";
4985
4734
  return { success: true, output, duration: Date.now() - startTime };
4986
4735
  } catch (error) {
4987
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4736
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
4988
4737
  }
4989
4738
  }
4990
4739
  async function executeSearchLinuxPrivesc(input) {
@@ -4997,7 +4746,7 @@ async function executeSearchLinuxPrivesc(input) {
4997
4746
  ${r.snippet}`).join("\n\n") : "No Linux privesc writeups found.";
4998
4747
  return { success: true, output, duration: Date.now() - startTime };
4999
4748
  } catch (error) {
5000
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4749
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
5001
4750
  }
5002
4751
  }
5003
4752
  async function executeSearchWindowsPrivesc(input) {
@@ -5010,7 +4759,7 @@ async function executeSearchWindowsPrivesc(input) {
5010
4759
  ${r.snippet}`).join("\n\n") : "No Windows privesc writeups found.";
5011
4760
  return { success: true, output, duration: Date.now() - startTime };
5012
4761
  } catch (error) {
5013
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4762
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
5014
4763
  }
5015
4764
  }
5016
4765
  async function executeCtfResearch(input) {
@@ -5041,7 +4790,7 @@ async function executeCtfResearch(input) {
5041
4790
  ${r.url}`).join("\n") : "No exploits found.";
5042
4791
  return { success: true, output, duration: Date.now() - startTime };
5043
4792
  } catch (error) {
5044
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4793
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
5045
4794
  }
5046
4795
  }
5047
4796
  async function executeSecurityResearch(input) {
@@ -5066,7 +4815,7 @@ async function executeSecurityResearch(input) {
5066
4815
  ${r.url}`).join("\n") : "No writeups found.";
5067
4816
  return { success: true, output, duration: Date.now() - startTime };
5068
4817
  } catch (error) {
5069
- return { success: false, output: "", error: error.message, duration: Date.now() - startTime };
4818
+ return { success: false, output: "", error: error instanceof Error ? error.message : String(error), duration: Date.now() - startTime };
5070
4819
  }
5071
4820
  }
5072
4821
 
@@ -6939,12 +6688,14 @@ EFFORT: [low/medium/high]`;
6939
6688
  for (const section of sections.slice(1)) {
6940
6689
  const match = section.match(/(\w+)\s+DESCRIPTION:\s*(.+)\s+REASONING:\s*(.+)\s+IMPACT:\s*([\d.]+)\s+EFFORT:\s*(\w+)/is);
6941
6690
  if (match) {
6691
+ const suggestionType = match[1].toLowerCase().trim();
6692
+ const suggestionEffort = match[5].toLowerCase().trim();
6942
6693
  suggestions.push({
6943
- type: match[1],
6694
+ type: suggestionType,
6944
6695
  description: match[2].trim(),
6945
6696
  reasoning: match[3].trim(),
6946
6697
  estimatedImpact: parseFloat(match[4]) || 0.5,
6947
- effort: match[5].trim()
6698
+ effort: suggestionEffort
6948
6699
  });
6949
6700
  }
6950
6701
  }
@@ -7483,7 +7234,7 @@ import { v4 as uuidv46 } from "uuid";
7483
7234
  import { EventEmitter as EventEmitter8 } from "events";
7484
7235
  import { v4 as uuidv45 } from "uuid";
7485
7236
  import { createHash } from "crypto";
7486
- import { promises as fs2 } from "fs";
7237
+ import { promises as fs3 } from "fs";
7487
7238
  import { join } from "path";
7488
7239
  import { homedir } from "os";
7489
7240
  var AUDIT_EVENT = {
@@ -7509,7 +7260,7 @@ var AuditLogger = class extends EventEmitter8 {
7509
7260
  async initialize() {
7510
7261
  if (this.initialized) return;
7511
7262
  try {
7512
- await fs2.mkdir(this.logDir, { recursive: true });
7263
+ await fs3.mkdir(this.logDir, { recursive: true });
7513
7264
  this.initialized = true;
7514
7265
  } catch (error) {
7515
7266
  console.error("[AuditLogger] Failed to initialize:", error);
@@ -7606,19 +7357,19 @@ var AuditLogger = class extends EventEmitter8 {
7606
7357
  async pruneDiskLogs(maxFiles = 1e3) {
7607
7358
  if (!this.initialized) await this.initialize();
7608
7359
  try {
7609
- const files = await fs2.readdir(this.logDir);
7360
+ const files = await fs3.readdir(this.logDir);
7610
7361
  if (files.length <= maxFiles) return 0;
7611
7362
  const fileStats = await Promise.all(
7612
7363
  files.map(async (file) => {
7613
7364
  const filePath = join(this.logDir, file);
7614
- const stats = await fs2.stat(filePath);
7365
+ const stats = await fs3.stat(filePath);
7615
7366
  return { file, time: stats.mtimeMs, path: filePath };
7616
7367
  })
7617
7368
  );
7618
7369
  fileStats.sort((a, b) => a.time - b.time);
7619
7370
  const toDelete = fileStats.slice(0, files.length - maxFiles);
7620
7371
  for (const item of toDelete) {
7621
- await fs2.unlink(item.path);
7372
+ await fs3.unlink(item.path);
7622
7373
  }
7623
7374
  return toDelete.length;
7624
7375
  } catch (error) {
@@ -7689,7 +7440,7 @@ var AuditLogger = class extends EventEmitter8 {
7689
7440
  this.logDir,
7690
7441
  `audit_${this.sessionId}_${Date.now()}.json`
7691
7442
  );
7692
- await fs2.writeFile(
7443
+ await fs3.writeFile(
7693
7444
  exportPath,
7694
7445
  JSON.stringify({
7695
7446
  sessionId: this.sessionId,
@@ -7750,7 +7501,7 @@ var AuditLogger = class extends EventEmitter8 {
7750
7501
  try {
7751
7502
  const filename = `${entry.timestamp.replace(/[:.]/g, "-")}_${entry.id}.json`;
7752
7503
  const filepath = join(this.logDir, filename);
7753
- await fs2.writeFile(filepath, JSON.stringify(entry, null, 2));
7504
+ await fs3.writeFile(filepath, JSON.stringify(entry, null, 2));
7754
7505
  } catch (error) {
7755
7506
  console.error("[AuditLogger] Failed to persist entry:", error);
7756
7507
  }
@@ -7771,7 +7522,7 @@ var AuditLogger = class extends EventEmitter8 {
7771
7522
  try {
7772
7523
  const filename = `system_${entry.timestamp.replace(/[:.]/g, "-")}_${entry.id}.json`;
7773
7524
  const filepath = join(this.logDir, filename);
7774
- await fs2.writeFile(filepath, JSON.stringify(entry, null, 2));
7525
+ await fs3.writeFile(filepath, JSON.stringify(entry, null, 2));
7775
7526
  } catch (error) {
7776
7527
  console.error("[AuditLogger] Failed to persist system event:", error);
7777
7528
  }
@@ -10139,7 +9890,7 @@ Constraints: ${context.constraints.join(", ")}`;
10139
9890
  // src/core/agent/learning/self-reflection.ts
10140
9891
  import { EventEmitter as EventEmitter12 } from "events";
10141
9892
  import { v4 as uuidv410 } from "uuid";
10142
- import * as fs3 from "fs/promises";
9893
+ import * as fs4 from "fs/promises";
10143
9894
  import * as path2 from "path";
10144
9895
  var SelfReflectionSystem = class extends EventEmitter12 {
10145
9896
  llm;
@@ -10393,10 +10144,10 @@ CONFIDENCE: [0.0-1.0]`;
10393
10144
  */
10394
10145
  async persistExperience(experience) {
10395
10146
  try {
10396
- await fs3.mkdir(this.dataDir, { recursive: true });
10147
+ await fs4.mkdir(this.dataDir, { recursive: true });
10397
10148
  const filename = `${experience.id}.json`;
10398
10149
  const filepath = path2.join(this.dataDir, filename);
10399
- await fs3.writeFile(filepath, JSON.stringify(experience, null, 2));
10150
+ await fs4.writeFile(filepath, JSON.stringify(experience, null, 2));
10400
10151
  } catch (error) {
10401
10152
  this.emit("persist_error", error);
10402
10153
  }
@@ -10406,13 +10157,13 @@ CONFIDENCE: [0.0-1.0]`;
10406
10157
  */
10407
10158
  async loadExperiences() {
10408
10159
  try {
10409
- await fs3.mkdir(this.dataDir, { recursive: true });
10410
- const files = await fs3.readdir(this.dataDir);
10160
+ await fs4.mkdir(this.dataDir, { recursive: true });
10161
+ const files = await fs4.readdir(this.dataDir);
10411
10162
  for (const file of files) {
10412
10163
  if (file.endsWith(".json")) {
10413
10164
  const filepath = path2.join(this.dataDir, file);
10414
10165
  try {
10415
- const content = await fs3.readFile(filepath, "utf-8");
10166
+ const content = await fs4.readFile(filepath, "utf-8");
10416
10167
  const experience = JSON.parse(content);
10417
10168
  this.experiences.push(experience);
10418
10169
  for (const lesson of experience.lessons) {
@@ -10469,7 +10220,7 @@ CONFIDENCE: [0.0-1.0]`;
10469
10220
 
10470
10221
  // src/core/agent/background/job-manager.ts
10471
10222
  import { EventEmitter as EventEmitter13 } from "events";
10472
- import { spawn as spawn3 } from "child_process";
10223
+ import { spawn as spawn2 } from "child_process";
10473
10224
  import { v4 as uuidv411 } from "uuid";
10474
10225
  var BackgroundJobManager = class extends EventEmitter13 {
10475
10226
  jobs = /* @__PURE__ */ new Map();
@@ -10512,7 +10263,7 @@ var BackgroundJobManager = class extends EventEmitter13 {
10512
10263
  this.jobs.set(job.id, job);
10513
10264
  this.incrementResource(config.type);
10514
10265
  try {
10515
- const process2 = spawn3(config.command, config.args || [], {
10266
+ const process2 = spawn2(config.command, config.args || [], {
10516
10267
  cwd: config.cwd,
10517
10268
  shell: true,
10518
10269
  detached: false,
@@ -10900,7 +10651,7 @@ var backgroundJobManager = new BackgroundJobManager();
10900
10651
  // src/core/agent/session/checkpoint-manager.ts
10901
10652
  import { EventEmitter as EventEmitter14 } from "events";
10902
10653
  import { v4 as uuidv412 } from "uuid";
10903
- import * as fs4 from "fs/promises";
10654
+ import * as fs5 from "fs/promises";
10904
10655
  import * as path3 from "path";
10905
10656
  var SessionManager = class extends EventEmitter14 {
10906
10657
  checkpoints = /* @__PURE__ */ new Map();
@@ -11089,10 +10840,10 @@ var SessionManager = class extends EventEmitter14 {
11089
10840
  async persistCheckpoint(checkpoint) {
11090
10841
  try {
11091
10842
  const sessionDir = path3.join(this.checkpointDir, checkpoint.sessionId);
11092
- await fs4.mkdir(sessionDir, { recursive: true });
10843
+ await fs5.mkdir(sessionDir, { recursive: true });
11093
10844
  const filename = `${checkpoint.id}.json`;
11094
10845
  const filepath = path3.join(sessionDir, filename);
11095
- await fs4.writeFile(filepath, JSON.stringify(checkpoint, null, 2));
10846
+ await fs5.writeFile(filepath, JSON.stringify(checkpoint, null, 2));
11096
10847
  } catch (error) {
11097
10848
  this.emit("persist_error", error);
11098
10849
  }
@@ -11103,17 +10854,17 @@ var SessionManager = class extends EventEmitter14 {
11103
10854
  async loadCheckpoints(sessionId) {
11104
10855
  const checkpoints = [];
11105
10856
  try {
11106
- await fs4.mkdir(this.checkpointDir, { recursive: true });
11107
- const sessions = await fs4.readdir(this.checkpointDir);
10857
+ await fs5.mkdir(this.checkpointDir, { recursive: true });
10858
+ const sessions = await fs5.readdir(this.checkpointDir);
11108
10859
  const targetSessions = sessionId ? [sessionId] : sessions;
11109
10860
  for (const session of targetSessions) {
11110
10861
  const sessionDir = path3.join(this.checkpointDir, session);
11111
10862
  try {
11112
- const files = await fs4.readdir(sessionDir);
10863
+ const files = await fs5.readdir(sessionDir);
11113
10864
  for (const file of files) {
11114
10865
  if (file.endsWith(".json")) {
11115
10866
  const filepath = path3.join(sessionDir, file);
11116
- const content = await fs4.readFile(filepath, "utf-8");
10867
+ const content = await fs5.readFile(filepath, "utf-8");
11117
10868
  try {
11118
10869
  const checkpoint = JSON.parse(content);
11119
10870
  checkpoints.push(checkpoint);
@@ -11152,7 +10903,7 @@ var SessionManager = class extends EventEmitter14 {
11152
10903
  checkpoint.sessionId,
11153
10904
  `${checkpointId}.json`
11154
10905
  );
11155
- await fs4.unlink(filepath);
10906
+ await fs5.unlink(filepath);
11156
10907
  this.checkpoints.delete(checkpointId);
11157
10908
  return true;
11158
10909
  } catch {
@@ -11394,8 +11145,8 @@ var ContextManager = class {
11394
11145
  };
11395
11146
 
11396
11147
  // src/core/hooks/hook-executor.ts
11397
- import { spawn as spawn4 } from "child_process";
11398
- import * as fs5 from "fs/promises";
11148
+ import { spawn as spawn3 } from "child_process";
11149
+ import * as fs6 from "fs/promises";
11399
11150
  import * as path4 from "path";
11400
11151
  import { EventEmitter as EventEmitter15 } from "events";
11401
11152
  var HookExecutor = class extends EventEmitter15 {
@@ -11410,7 +11161,7 @@ var HookExecutor = class extends EventEmitter15 {
11410
11161
  async initialize() {
11411
11162
  try {
11412
11163
  const configPath = path4.join(this.hooksDir, "hooks.json");
11413
- const configContent = await fs5.readFile(configPath, "utf-8");
11164
+ const configContent = await fs6.readFile(configPath, "utf-8");
11414
11165
  this.hooksConfig = JSON.parse(configContent);
11415
11166
  this.initialized = true;
11416
11167
  this.emit("initialized", { hooks: Object.keys(this.hooksConfig?.hooks || {}) });
@@ -11452,7 +11203,7 @@ var HookExecutor = class extends EventEmitter15 {
11452
11203
  HOOK_PHASE: context.phase || "",
11453
11204
  HOOK_TARGET: context.target || ""
11454
11205
  };
11455
- const proc = spawn4("bash", ["-c", command], {
11206
+ const proc = spawn3("bash", ["-c", command], {
11456
11207
  env,
11457
11208
  cwd: this.hooksDir,
11458
11209
  timeout
@@ -11524,205 +11275,8 @@ function getHookExecutor() {
11524
11275
  return hookExecutor;
11525
11276
  }
11526
11277
 
11527
- // src/mcp/mcp-client.ts
11528
- import { spawn as spawn5 } from "child_process";
11529
- import { EventEmitter as EventEmitter16 } from "events";
11530
- import * as readline from "readline";
11531
- var MCPClient = class extends EventEmitter16 {
11532
- process = null;
11533
- requestId = 0;
11534
- pendingRequests = /* @__PURE__ */ new Map();
11535
- serverName;
11536
- config;
11537
- connected = false;
11538
- constructor(serverName, config) {
11539
- super();
11540
- this.serverName = serverName;
11541
- this.config = config;
11542
- }
11543
- // Start MCP server
11544
- async connect() {
11545
- return new Promise((resolve, reject) => {
11546
- const { command, args, env } = this.config;
11547
- this.process = spawn5(command, args || [], {
11548
- env: { ...process.env, ...env },
11549
- stdio: ["pipe", "pipe", "pipe"]
11550
- });
11551
- if (this.process.stdout) {
11552
- const rl = readline.createInterface({
11553
- input: this.process.stdout,
11554
- crlfDelay: Infinity
11555
- });
11556
- rl.on("line", (line) => {
11557
- try {
11558
- const response = JSON.parse(line);
11559
- this.handleResponse(response);
11560
- } catch {
11561
- }
11562
- });
11563
- }
11564
- if (this.process.stderr) {
11565
- this.process.stderr.on("data", (data) => {
11566
- this.emit("stderr", data.toString());
11567
- });
11568
- }
11569
- this.process.on("error", (error) => {
11570
- this.emit("error", error);
11571
- reject(error);
11572
- });
11573
- this.process.on("spawn", () => {
11574
- this.connected = true;
11575
- this.emit("connected", { server: this.serverName });
11576
- resolve();
11577
- });
11578
- this.process.on("close", (code) => {
11579
- this.connected = false;
11580
- this.emit("disconnected", { server: this.serverName, code });
11581
- });
11582
- });
11583
- }
11584
- // Send JSON-RPC request
11585
- async sendRequest(method, params) {
11586
- if (!this.process?.stdin || !this.connected) {
11587
- throw new Error("MCP server not connected");
11588
- }
11589
- const id = ++this.requestId;
11590
- const request = {
11591
- jsonrpc: "2.0",
11592
- id,
11593
- method,
11594
- params
11595
- };
11596
- return new Promise((resolve, reject) => {
11597
- this.pendingRequests.set(id, { resolve, reject });
11598
- const line = JSON.stringify(request) + "\n";
11599
- this.process.stdin.write(line, (error) => {
11600
- if (error) {
11601
- this.pendingRequests.delete(id);
11602
- reject(error);
11603
- }
11604
- });
11605
- setTimeout(() => {
11606
- if (this.pendingRequests.has(id)) {
11607
- this.pendingRequests.delete(id);
11608
- reject(new Error("MCP request timeout"));
11609
- }
11610
- }, 3e4);
11611
- });
11612
- }
11613
- // Handle JSON-RPC response
11614
- handleResponse(response) {
11615
- const pending = this.pendingRequests.get(response.id);
11616
- if (!pending) {
11617
- return;
11618
- }
11619
- this.pendingRequests.delete(response.id);
11620
- if (response.error) {
11621
- pending.reject(new Error(response.error.message));
11622
- } else {
11623
- pending.resolve(response.result);
11624
- }
11625
- }
11626
- // Initialize MCP server
11627
- async initialize() {
11628
- return this.sendRequest("initialize", {
11629
- protocolVersion: "2024-11-05",
11630
- capabilities: {},
11631
- clientInfo: {
11632
- name: "pentesting",
11633
- version: "1.0.0"
11634
- }
11635
- });
11636
- }
11637
- // List available tools
11638
- async listTools() {
11639
- const result = await this.sendRequest("tools/list");
11640
- return result.tools || [];
11641
- }
11642
- // Call a tool
11643
- async callTool(name, arguments_) {
11644
- return this.sendRequest("tools/call", { name, arguments: arguments_ });
11645
- }
11646
- // List resources
11647
- async listResources() {
11648
- const result = await this.sendRequest("resources/list");
11649
- return result.resources || [];
11650
- }
11651
- // Read a resource
11652
- async readResource(uri) {
11653
- return this.sendRequest("resources/read", { uri });
11654
- }
11655
- // Disconnect
11656
- async disconnect() {
11657
- if (this.process) {
11658
- this.process.kill();
11659
- this.process = null;
11660
- this.connected = false;
11661
- }
11662
- }
11663
- // Check if connected
11664
- isConnected() {
11665
- return this.connected;
11666
- }
11667
- };
11668
- var MCPManager = class extends EventEmitter16 {
11669
- clients = /* @__PURE__ */ new Map();
11670
- tools = /* @__PURE__ */ new Map();
11671
- // Add and connect to a server
11672
- async addServer(name, config) {
11673
- const client = new MCPClient(name, config);
11674
- client.on("error", (error) => this.emit("error", { server: name, error }));
11675
- client.on("connected", () => this.emit("server_connected", { server: name }));
11676
- client.on("disconnected", (info) => this.emit("server_disconnected", info));
11677
- try {
11678
- await client.connect();
11679
- await client.initialize();
11680
- const tools = await client.listTools();
11681
- for (const tool of tools) {
11682
- this.tools.set(tool.name, { server: name, tool });
11683
- }
11684
- this.clients.set(name, client);
11685
- this.emit("tools_loaded", { server: name, count: tools.length });
11686
- } catch (error) {
11687
- this.emit("error", { server: name, error });
11688
- throw error;
11689
- }
11690
- }
11691
- // Call a tool by name
11692
- async callTool(toolName, args) {
11693
- const entry = this.tools.get(toolName);
11694
- if (!entry) {
11695
- throw new Error(`Unknown MCP tool: ${toolName}`);
11696
- }
11697
- const client = this.clients.get(entry.server);
11698
- if (!client) {
11699
- throw new Error(`MCP server not connected: ${entry.server}`);
11700
- }
11701
- return client.callTool(toolName, args);
11702
- }
11703
- // Get all available tools
11704
- getAvailableTools() {
11705
- return Array.from(this.tools.values()).map((e) => e.tool);
11706
- }
11707
- // Disconnect all
11708
- async disconnectAll() {
11709
- for (const client of this.clients.values()) {
11710
- await client.disconnect();
11711
- }
11712
- this.clients.clear();
11713
- this.tools.clear();
11714
- }
11715
- };
11716
- var mcpManager = null;
11717
- function getMCPManager() {
11718
- if (!mcpManager) {
11719
- mcpManager = new MCPManager();
11720
- }
11721
- return mcpManager;
11722
- }
11723
-
11724
11278
  // src/core/approval/approval-manager.ts
11725
- import { EventEmitter as EventEmitter17 } from "events";
11279
+ import { EventEmitter as EventEmitter16 } from "events";
11726
11280
  var APPROVAL_EVENT = {
11727
11281
  REQUEST: "approval_request",
11728
11282
  RESPONSE: "approval_response",
@@ -11893,7 +11447,7 @@ function assessRisk(toolName, toolInput) {
11893
11447
  function generateRequestId() {
11894
11448
  return `approval_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
11895
11449
  }
11896
- var ApprovalManager = class extends EventEmitter17 {
11450
+ var ApprovalManager = class extends EventEmitter16 {
11897
11451
  pendingRequests = /* @__PURE__ */ new Map();
11898
11452
  autoApprovedTools = /* @__PURE__ */ new Set();
11899
11453
  autoDeniedTools = /* @__PURE__ */ new Set();
@@ -12017,93 +11571,672 @@ var ApprovalManager = class extends EventEmitter17 {
12017
11571
  });
12018
11572
  }
12019
11573
  /**
12020
- * Respond to an approval request (called by UI)
12021
- * Now handles timed YOLO modes
11574
+ * Respond to an approval request (called by UI)
11575
+ * Now handles timed YOLO modes
11576
+ */
11577
+ respond(requestId, decision) {
11578
+ if (decision === "yolo_5min") {
11579
+ this.setYoloMode(true, 5 * 60 * 1e3);
11580
+ decision = "approve";
11581
+ } else if (decision === "yolo_15min") {
11582
+ this.setYoloMode(true, 15 * 60 * 1e3);
11583
+ decision = "approve";
11584
+ } else if (decision === "yolo_30min") {
11585
+ this.setYoloMode(true, 30 * 60 * 1e3);
11586
+ decision = "approve";
11587
+ }
11588
+ if (decision === "approve" || decision === "approve_always") {
11589
+ this.stats.approved++;
11590
+ } else if (decision === "deny" || decision === "deny_always") {
11591
+ this.stats.denied++;
11592
+ }
11593
+ const response = {
11594
+ requestId,
11595
+ decision,
11596
+ respondedAt: (/* @__PURE__ */ new Date()).toISOString()
11597
+ };
11598
+ this.emit(APPROVAL_EVENT.RESPONSE, response);
11599
+ }
11600
+ /**
11601
+ * Get pending requests
11602
+ */
11603
+ getPendingRequests() {
11604
+ return Array.from(this.pendingRequests.values());
11605
+ }
11606
+ /**
11607
+ * Get auto-approved tools
11608
+ */
11609
+ getAutoApprovedTools() {
11610
+ return Array.from(this.autoApprovedTools);
11611
+ }
11612
+ /**
11613
+ * Add a tool to the auto-approved list
11614
+ */
11615
+ addAutoApprovedTool(toolName) {
11616
+ this.autoApprovedTools.add(toolName);
11617
+ }
11618
+ /**
11619
+ * Reset all auto decisions
11620
+ */
11621
+ resetAutoDecisions() {
11622
+ this.autoApprovedTools.clear();
11623
+ this.autoDeniedTools.clear();
11624
+ }
11625
+ /**
11626
+ * Get YOLO mode status
11627
+ */
11628
+ getYoloStatus() {
11629
+ const remainingMs = this.yoloExpiresAt ? Math.max(0, this.yoloExpiresAt - Date.now()) : null;
11630
+ return {
11631
+ enabled: this.yoloMode,
11632
+ expiresAt: this.yoloExpiresAt,
11633
+ remainingMs
11634
+ };
11635
+ }
11636
+ /**
11637
+ * Get approval statistics
11638
+ */
11639
+ getStats() {
11640
+ return { ...this.stats };
11641
+ }
11642
+ /**
11643
+ * Cancel YOLO mode
11644
+ */
11645
+ cancelYoloMode() {
11646
+ this.setYoloMode(false);
11647
+ }
11648
+ /**
11649
+ * Dispose of the approval manager
11650
+ */
11651
+ dispose() {
11652
+ if (this.yoloTimer) {
11653
+ clearTimeout(this.yoloTimer);
11654
+ this.yoloTimer = null;
11655
+ }
11656
+ this.pendingRequests.clear();
11657
+ this.removeAllListeners();
11658
+ }
11659
+ };
11660
+ var approvalManager = null;
11661
+ function getApprovalManager(options) {
11662
+ if (!approvalManager) {
11663
+ approvalManager = new ApprovalManager(options);
11664
+ }
11665
+ return approvalManager;
11666
+ }
11667
+
11668
+ // src/core/agent/strategy/attack-planner.ts
11669
+ var SERVICE_TOOL_MAP = {
11670
+ http: {
11671
+ enumTools: [
11672
+ { tool: TOOL_NAME.WHATWEB, description: "Identify web technologies", successIndicators: ["WordPress", "Apache", "nginx", "PHP"], timeout: 3e4, priority: 90 },
11673
+ { tool: TOOL_NAME.NIKTO, description: "Web vulnerability scan", successIndicators: ["OSVDB", "vulnerability", "found"], timeout: 3e5, priority: 80 },
11674
+ { tool: TOOL_NAME.FFUF, description: "Directory/file fuzzing", successIndicators: ["200", "301", "302", "Status"], timeout: 3e5, priority: 85 },
11675
+ { tool: TOOL_NAME.GOBUSTER, description: "Directory bruteforce", successIndicators: ["Status: 200", "Found:"], timeout: 3e5, priority: 70 },
11676
+ { tool: TOOL_NAME.NUCLEI, description: "Vulnerability scanning with templates", successIndicators: ["[critical]", "[high]", "[medium]", "found"], timeout: 6e5, priority: 75 }
11677
+ ],
11678
+ exploitTools: [
11679
+ { tool: TOOL_NAME.SQL_INJECTION, description: "SQL injection testing", successIndicators: ["injectable", "sql", "dump"], timeout: 6e5, priority: 85 },
11680
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Search for known exploits", successIndicators: ["Exploit", "exploit/"], timeout: 3e4, priority: 90 }
11681
+ ],
11682
+ vulnChecks: ["sql_injection", "xss", "file_inclusion", "command_injection", "path_traversal"]
11683
+ },
11684
+ https: {
11685
+ enumTools: [
11686
+ { tool: TOOL_NAME.WHATWEB, description: "Identify web technologies", successIndicators: ["WordPress", "Apache", "nginx"], timeout: 3e4, priority: 90 },
11687
+ { tool: TOOL_NAME.NIKTO, description: "Web vulnerability scan", successIndicators: ["OSVDB", "vulnerability"], timeout: 3e5, priority: 80 },
11688
+ { tool: TOOL_NAME.FFUF, description: "Directory fuzzing", successIndicators: ["200", "301", "302"], timeout: 3e5, priority: 85 },
11689
+ { tool: TOOL_NAME.NUCLEI, description: "Template-based vuln scanning", successIndicators: ["[critical]", "[high]"], timeout: 6e5, priority: 75 }
11690
+ ],
11691
+ exploitTools: [
11692
+ { tool: TOOL_NAME.SQL_INJECTION, description: "SQL injection", successIndicators: ["injectable", "dump"], timeout: 6e5, priority: 85 },
11693
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Search exploits", successIndicators: ["Exploit"], timeout: 3e4, priority: 90 }
11694
+ ],
11695
+ vulnChecks: ["ssl_vulnerability", "sql_injection", "xss"]
11696
+ },
11697
+ ssh: {
11698
+ enumTools: [
11699
+ { tool: TOOL_NAME.BASH, description: "SSH banner grab", successIndicators: ["SSH", "OpenSSH"], timeout: 1e4, priority: 90 }
11700
+ ],
11701
+ exploitTools: [
11702
+ { tool: TOOL_NAME.HYDRA, description: "SSH bruteforce", successIndicators: ["login:", "password:"], timeout: 6e5, priority: 70 },
11703
+ { tool: TOOL_NAME.SSH, description: "SSH with found credentials", successIndicators: ["$", "#", "Last login"], timeout: 3e4, priority: 95 },
11704
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Search SSH exploits", successIndicators: ["Exploit"], timeout: 3e4, priority: 80 }
11705
+ ],
11706
+ vulnChecks: ["weak_credentials", "old_version"]
11707
+ },
11708
+ smb: {
11709
+ enumTools: [
11710
+ { tool: TOOL_NAME.SMB_ENUM, description: "SMB enumeration", successIndicators: ["share", "IPC$", "users"], timeout: 12e4, priority: 90 },
11711
+ { tool: TOOL_NAME.ENUM4LINUX, description: "Full SMB enum", successIndicators: ["user", "share", "password"], timeout: 3e5, priority: 85 },
11712
+ { tool: TOOL_NAME.SMBMAP, description: "SMB share permissions", successIndicators: ["READ", "WRITE", "Disk"], timeout: 12e4, priority: 88 },
11713
+ { tool: TOOL_NAME.SMBCLIENT, description: "SMB client access", successIndicators: ["smb:", "NT_STATUS"], timeout: 6e4, priority: 80 }
11714
+ ],
11715
+ exploitTools: [
11716
+ { tool: TOOL_NAME.CRACKMAPEXEC, description: "Credential spray SMB", successIndicators: ["Pwn3d!", "+)"], timeout: 3e5, priority: 85 },
11717
+ { tool: TOOL_NAME.IMPACKET_PSEXEC, description: "PsExec remote execution", successIndicators: ["C:\\>", "$"], timeout: 12e4, priority: 90 },
11718
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Search SMB exploits", successIndicators: ["Exploit", "EternalBlue"], timeout: 3e4, priority: 95 }
11719
+ ],
11720
+ vulnChecks: ["null_session", "anonymous_access", "eternal_blue", "weak_credentials"]
11721
+ },
11722
+ ftp: {
11723
+ enumTools: [
11724
+ { tool: TOOL_NAME.FTP_ANON, description: "Check anonymous FTP", successIndicators: ["Anonymous", "230", "Login successful"], timeout: 3e4, priority: 95 },
11725
+ { tool: TOOL_NAME.FTP_ENUM, description: "FTP enumeration", successIndicators: ["user", "directory"], timeout: 6e4, priority: 85 }
11726
+ ],
11727
+ exploitTools: [
11728
+ { tool: TOOL_NAME.HYDRA, description: "FTP bruteforce", successIndicators: ["login:", "password"], timeout: 6e5, priority: 70 },
11729
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Search FTP exploits", successIndicators: ["Exploit"], timeout: 3e4, priority: 90 }
11730
+ ],
11731
+ vulnChecks: ["anonymous_access", "weak_credentials", "known_vulnerability"]
11732
+ },
11733
+ mysql: {
11734
+ enumTools: [
11735
+ { tool: TOOL_NAME.MYSQL_CLIENT, description: "MySQL client access", successIndicators: ["mysql>", "Server version"], timeout: 3e4, priority: 90 }
11736
+ ],
11737
+ exploitTools: [
11738
+ { tool: TOOL_NAME.HYDRA, description: "MySQL bruteforce", successIndicators: ["login:", "password"], timeout: 6e5, priority: 70 },
11739
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "MySQL exploits", successIndicators: ["Exploit"], timeout: 3e4, priority: 85 }
11740
+ ],
11741
+ vulnChecks: ["default_credentials", "udf_exploit"]
11742
+ },
11743
+ snmp: {
11744
+ enumTools: [
11745
+ { tool: TOOL_NAME.SNMP_WALK, description: "SNMP walk", successIndicators: ["STRING:", "INTEGER:", "SNMPv2"], timeout: 12e4, priority: 90 },
11746
+ { tool: TOOL_NAME.SNMP_CHECK, description: "SNMP check", successIndicators: ["system", "user", "process"], timeout: 6e4, priority: 85 },
11747
+ { tool: TOOL_NAME.ONESIXTYONE, description: "SNMP community string bruteforce", successIndicators: ["public", "private"], timeout: 6e4, priority: 88 }
11748
+ ],
11749
+ exploitTools: [],
11750
+ vulnChecks: ["default_community_string", "snmpv1"]
11751
+ },
11752
+ ldap: {
11753
+ enumTools: [
11754
+ { tool: TOOL_NAME.LDAP_SEARCH, description: "LDAP enumeration", successIndicators: ["dn:", "cn=", "dc="], timeout: 12e4, priority: 90 }
11755
+ ],
11756
+ exploitTools: [
11757
+ { tool: TOOL_NAME.KERBRUTE, description: "Kerberos user enum", successIndicators: ["VALID", "user"], timeout: 3e5, priority: 80 },
11758
+ { tool: TOOL_NAME.IMPACKET_GETNPUSERS, description: "AS-REP roasting", successIndicators: ["$krb5asrep$"], timeout: 12e4, priority: 85 }
11759
+ ],
11760
+ vulnChecks: ["anonymous_bind", "kerberoasting", "as-rep_roasting"]
11761
+ },
11762
+ rdp: {
11763
+ enumTools: [
11764
+ { tool: TOOL_NAME.RDP_CHECK, description: "RDP security check", successIndicators: ["RDP", "PROTOCOL_RDP"], timeout: 3e4, priority: 90 }
11765
+ ],
11766
+ exploitTools: [
11767
+ { tool: TOOL_NAME.HYDRA, description: "RDP bruteforce", successIndicators: ["login:", "password"], timeout: 6e5, priority: 60 },
11768
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "RDP exploits", successIndicators: ["BlueKeep", "CVE-2019"], timeout: 3e4, priority: 90 }
11769
+ ],
11770
+ vulnChecks: ["bluekeep", "weak_credentials"]
11771
+ },
11772
+ redis: {
11773
+ enumTools: [
11774
+ { tool: TOOL_NAME.REDIS_CLI, description: "Redis client access", successIndicators: ["redis", "PONG", "keys"], timeout: 3e4, priority: 90 }
11775
+ ],
11776
+ exploitTools: [
11777
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Redis exploits", successIndicators: ["Exploit"], timeout: 3e4, priority: 85 }
11778
+ ],
11779
+ vulnChecks: ["no_auth", "default_config"]
11780
+ }
11781
+ };
11782
+ var AttackPlanner = class {
11783
+ /**
11784
+ * Generate an initial attack plan based on target and discovered services
11785
+ */
11786
+ generatePlan(target, services) {
11787
+ const plan = {
11788
+ target,
11789
+ phases: [],
11790
+ currentPhaseIndex: 0,
11791
+ adaptations: []
11792
+ };
11793
+ plan.phases.push({
11794
+ name: "initial_recon",
11795
+ steps: [
11796
+ { tool: TOOL_NAME.NMAP_SCAN, description: "Full port scan", successIndicators: ["open", "tcp", "udp"], timeout: 3e5, priority: 100 },
11797
+ { tool: TOOL_NAME.RUSTSCAN, description: "Fast port scan", successIndicators: ["Open"], timeout: 6e4, priority: 95 }
11798
+ ],
11799
+ successCriteria: "At least 1 service discovered",
11800
+ maxAttempts: 5
11801
+ });
11802
+ const enumSteps = [];
11803
+ for (const svc of services) {
11804
+ const normalizedService = this.normalizeServiceName(svc.service);
11805
+ const mapping = SERVICE_TOOL_MAP[normalizedService];
11806
+ if (mapping) {
11807
+ enumSteps.push(...mapping.enumTools.map((step) => ({
11808
+ ...step,
11809
+ description: `[${svc.port}/${svc.service}] ${step.description}`
11810
+ })));
11811
+ }
11812
+ }
11813
+ if (enumSteps.length > 0) {
11814
+ enumSteps.sort((a, b) => b.priority - a.priority);
11815
+ plan.phases.push({
11816
+ name: "service_enumeration",
11817
+ steps: enumSteps,
11818
+ successCriteria: "Vulnerabilities or access vectors identified",
11819
+ maxAttempts: enumSteps.length + 5
11820
+ });
11821
+ }
11822
+ const vulnSteps = [
11823
+ { tool: TOOL_NAME.SEARCHSPLOIT, description: "Search for known exploits", successIndicators: ["Exploit", "CVE"], timeout: 3e4, priority: 90 }
11824
+ ];
11825
+ for (const svc of services) {
11826
+ if (svc.version) {
11827
+ vulnSteps.push({
11828
+ tool: TOOL_NAME.SEARCHSPLOIT,
11829
+ description: `Search exploits for ${svc.service} ${svc.version}`,
11830
+ successIndicators: ["Exploit"],
11831
+ timeout: 3e4,
11832
+ priority: 88
11833
+ });
11834
+ }
11835
+ }
11836
+ plan.phases.push({
11837
+ name: "vulnerability_analysis",
11838
+ steps: vulnSteps,
11839
+ successCriteria: "Exploitable vulnerability found",
11840
+ maxAttempts: 10
11841
+ });
11842
+ const exploitSteps = [];
11843
+ for (const svc of services) {
11844
+ const normalizedService = this.normalizeServiceName(svc.service);
11845
+ const mapping = SERVICE_TOOL_MAP[normalizedService];
11846
+ if (mapping) {
11847
+ exploitSteps.push(...mapping.exploitTools.map((step) => ({
11848
+ ...step,
11849
+ description: `[${svc.port}/${svc.service}] ${step.description}`
11850
+ })));
11851
+ }
11852
+ }
11853
+ if (exploitSteps.length > 0) {
11854
+ exploitSteps.sort((a, b) => b.priority - a.priority);
11855
+ plan.phases.push({
11856
+ name: "exploitation",
11857
+ steps: exploitSteps,
11858
+ successCriteria: "Shell or admin access obtained",
11859
+ maxAttempts: exploitSteps.length + 10
11860
+ });
11861
+ }
11862
+ plan.phases.push({
11863
+ name: "post_exploitation",
11864
+ steps: [
11865
+ { tool: TOOL_NAME.RUN_PRIVESC_ENUM, description: "Privilege escalation enumeration", successIndicators: ["SUID", "cron", "writable"], timeout: 12e4, priority: 90 },
11866
+ { tool: TOOL_NAME.CHECK_SUDO, description: "Check sudo rights", successIndicators: ["(ALL)", "NOPASSWD", "may run"], timeout: 3e4, priority: 95 },
11867
+ { tool: TOOL_NAME.FIND_SUID, description: "Find SUID binaries", successIndicators: ["/usr/", "/bin/"], timeout: 3e4, priority: 85 },
11868
+ { tool: TOOL_NAME.DUMP_CREDENTIALS, description: "Dump credentials", successIndicators: ["password", "hash", "credential"], timeout: 12e4, priority: 80 }
11869
+ ],
11870
+ successCriteria: "Root/admin access obtained",
11871
+ maxAttempts: 15
11872
+ });
11873
+ return plan;
11874
+ }
11875
+ /**
11876
+ * Adapt the plan based on new discoveries
11877
+ */
11878
+ adaptPlan(plan, newServices, findings) {
11879
+ const adaptation = `Adapting plan: ${newServices.length} new services, ${findings.length} findings`;
11880
+ plan.adaptations.push(adaptation);
11881
+ for (const svc of newServices) {
11882
+ const normalizedService = this.normalizeServiceName(svc.service);
11883
+ const mapping = SERVICE_TOOL_MAP[normalizedService];
11884
+ if (mapping) {
11885
+ let enumPhase = plan.phases.find((p) => p.name === "service_enumeration");
11886
+ if (!enumPhase) {
11887
+ enumPhase = {
11888
+ name: "service_enumeration",
11889
+ steps: [],
11890
+ successCriteria: "Vulnerabilities identified",
11891
+ maxAttempts: 10
11892
+ };
11893
+ const reconIdx = plan.phases.findIndex((p) => p.name === "initial_recon");
11894
+ plan.phases.splice(reconIdx + 1, 0, enumPhase);
11895
+ }
11896
+ for (const tool of mapping.enumTools) {
11897
+ const exists = enumPhase.steps.some(
11898
+ (s) => s.tool === tool.tool && s.description.includes(`${svc.port}`)
11899
+ );
11900
+ if (!exists) {
11901
+ enumPhase.steps.push({
11902
+ ...tool,
11903
+ description: `[${svc.port}/${svc.service}] ${tool.description}`
11904
+ });
11905
+ }
11906
+ }
11907
+ }
11908
+ }
11909
+ return plan;
11910
+ }
11911
+ /**
11912
+ * Get suggested next steps based on current state
11913
+ */
11914
+ suggestNextSteps(services, completedTools, hasCredentials, hasShell) {
11915
+ const suggestions = [];
11916
+ if (hasShell) {
11917
+ suggestions.push(
11918
+ { tool: TOOL_NAME.RUN_PRIVESC_ENUM, description: "Run privilege escalation enumeration", successIndicators: ["SUID", "cron"], timeout: 12e4, priority: 95 },
11919
+ { tool: TOOL_NAME.CHECK_SUDO, description: "Check sudo permissions", successIndicators: ["(ALL)", "NOPASSWD"], timeout: 3e4, priority: 90 },
11920
+ { tool: TOOL_NAME.FIND_SUID, description: "Find SUID binaries", successIndicators: ["/usr/", "/bin/"], timeout: 3e4, priority: 85 }
11921
+ );
11922
+ } else if (hasCredentials) {
11923
+ for (const svc of services) {
11924
+ if (["ssh", "smb", "ftp", "rdp", "winrm"].includes(this.normalizeServiceName(svc.service))) {
11925
+ suggestions.push({
11926
+ tool: svc.service === "ssh" ? TOOL_NAME.SSH : TOOL_NAME.CRACKMAPEXEC,
11927
+ description: `Try credentials on ${svc.service}:${svc.port}`,
11928
+ successIndicators: ["$", "#", "Pwn3d!", "Login successful"],
11929
+ timeout: 3e4,
11930
+ priority: 95
11931
+ });
11932
+ }
11933
+ }
11934
+ } else {
11935
+ for (const svc of services) {
11936
+ const normalizedService = this.normalizeServiceName(svc.service);
11937
+ const mapping = SERVICE_TOOL_MAP[normalizedService];
11938
+ if (mapping) {
11939
+ for (const tool of mapping.enumTools) {
11940
+ if (!completedTools.includes(tool.tool)) {
11941
+ suggestions.push({
11942
+ ...tool,
11943
+ description: `[${svc.port}/${svc.service}] ${tool.description}`
11944
+ });
11945
+ }
11946
+ }
11947
+ }
11948
+ }
11949
+ }
11950
+ suggestions.sort((a, b) => b.priority - a.priority);
11951
+ return suggestions.slice(0, 10);
11952
+ }
11953
+ /**
11954
+ * Get vulnerability checks for discovered services
12022
11955
  */
12023
- respond(requestId, decision) {
12024
- if (decision === "yolo_5min") {
12025
- this.setYoloMode(true, 5 * 60 * 1e3);
12026
- decision = "approve";
12027
- } else if (decision === "yolo_15min") {
12028
- this.setYoloMode(true, 15 * 60 * 1e3);
12029
- decision = "approve";
12030
- } else if (decision === "yolo_30min") {
12031
- this.setYoloMode(true, 30 * 60 * 1e3);
12032
- decision = "approve";
11956
+ getVulnChecks(services) {
11957
+ const checks = [];
11958
+ for (const svc of services) {
11959
+ const normalizedService = this.normalizeServiceName(svc.service);
11960
+ const mapping = SERVICE_TOOL_MAP[normalizedService];
11961
+ if (mapping) {
11962
+ checks.push(...mapping.vulnChecks);
11963
+ }
12033
11964
  }
12034
- if (decision === "approve" || decision === "approve_always") {
12035
- this.stats.approved++;
12036
- } else if (decision === "deny" || decision === "deny_always") {
12037
- this.stats.denied++;
11965
+ return [...new Set(checks)];
11966
+ }
11967
+ /**
11968
+ * Normalize service names from nmap output to our mapping keys
11969
+ */
11970
+ normalizeServiceName(service) {
11971
+ const lower = service.toLowerCase();
11972
+ if (lower.includes("http") && !lower.includes("https")) return "http";
11973
+ if (lower.includes("https") || lower.includes("ssl/http")) return "https";
11974
+ if (lower.includes("ssh")) return "ssh";
11975
+ if (lower.includes("smb") || lower.includes("microsoft-ds") || lower.includes("netbios")) return "smb";
11976
+ if (lower.includes("ftp")) return "ftp";
11977
+ if (lower.includes("mysql") || lower.includes("mariadb")) return "mysql";
11978
+ if (lower.includes("mssql") || lower.includes("ms-sql")) return "mssql";
11979
+ if (lower.includes("postgresql") || lower.includes("postgres")) return "postgresql";
11980
+ if (lower.includes("redis")) return "redis";
11981
+ if (lower.includes("mongo")) return "mongodb";
11982
+ if (lower.includes("snmp")) return "snmp";
11983
+ if (lower.includes("ldap")) return "ldap";
11984
+ if (lower.includes("rdp") || lower.includes("ms-wbt-server")) return "rdp";
11985
+ return lower;
11986
+ }
11987
+ };
11988
+ var attackPlannerInstance = null;
11989
+ function getAttackPlanner() {
11990
+ if (!attackPlannerInstance) {
11991
+ attackPlannerInstance = new AttackPlanner();
11992
+ }
11993
+ return attackPlannerInstance;
11994
+ }
11995
+
11996
+ // src/core/agent/strategy/stuck-detector.ts
11997
+ var StuckDetector = class {
11998
+ actionHistory = [];
11999
+ progressEvents = [];
12000
+ maxHistorySize = 100;
12001
+ // Thresholds
12002
+ SAME_TOOL_THRESHOLD = 3;
12003
+ // Same tool+target 3x = stuck
12004
+ CYCLE_MIN_LENGTH = 2;
12005
+ // Minimum cycle length
12006
+ CYCLE_REPETITIONS = 2;
12007
+ // Cycle seen 2x = stuck
12008
+ NO_PROGRESS_TIMEOUT = 3e5;
12009
+ // 5 minutes without progress
12010
+ NO_NEW_INFO_THRESHOLD = 8;
12011
+ // 8 actions without new info
12012
+ /**
12013
+ * Record a tool execution
12014
+ */
12015
+ recordAction(tool, target, success, hasNewInfo) {
12016
+ this.actionHistory.push({
12017
+ tool,
12018
+ target,
12019
+ timestamp: Date.now(),
12020
+ success,
12021
+ hasNewInfo
12022
+ });
12023
+ if (this.actionHistory.length > this.maxHistorySize) {
12024
+ this.actionHistory = this.actionHistory.slice(-this.maxHistorySize);
12038
12025
  }
12039
- const response = {
12040
- requestId,
12041
- decision,
12042
- respondedAt: (/* @__PURE__ */ new Date()).toISOString()
12043
- };
12044
- this.emit(APPROVAL_EVENT.RESPONSE, response);
12045
12026
  }
12046
12027
  /**
12047
- * Get pending requests
12028
+ * Record a progress event
12048
12029
  */
12049
- getPendingRequests() {
12050
- return Array.from(this.pendingRequests.values());
12030
+ recordProgress(type, details) {
12031
+ this.progressEvents.push({
12032
+ type,
12033
+ timestamp: Date.now(),
12034
+ details
12035
+ });
12051
12036
  }
12052
12037
  /**
12053
- * Get auto-approved tools
12038
+ * Analyze whether the agent is stuck
12054
12039
  */
12055
- getAutoApprovedTools() {
12056
- return Array.from(this.autoApprovedTools);
12040
+ analyze() {
12041
+ const checks = [
12042
+ this.checkSameToolRepetition(),
12043
+ this.checkCycleDetection(),
12044
+ this.checkProgressTimeout(),
12045
+ this.checkNoNewInfoStreak(),
12046
+ this.checkAllFailures()
12047
+ ];
12048
+ const stuckChecks = checks.filter((c) => c.isStuck);
12049
+ if (stuckChecks.length === 0) {
12050
+ return {
12051
+ isStuck: false,
12052
+ reason: "",
12053
+ suggestion: "",
12054
+ confidence: 0
12055
+ };
12056
+ }
12057
+ stuckChecks.sort((a, b) => b.confidence - a.confidence);
12058
+ return stuckChecks[0];
12057
12059
  }
12058
12060
  /**
12059
- * Reset all auto decisions
12061
+ * Check 1: Same tool+target repeated too many times
12060
12062
  */
12061
- resetAutoDecisions() {
12062
- this.autoApprovedTools.clear();
12063
- this.autoDeniedTools.clear();
12063
+ checkSameToolRepetition() {
12064
+ if (this.actionHistory.length < this.SAME_TOOL_THRESHOLD) {
12065
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12066
+ }
12067
+ const recent = this.actionHistory.slice(-10);
12068
+ const comboCounts = /* @__PURE__ */ new Map();
12069
+ for (const action of recent) {
12070
+ const key = `${action.tool}:${action.target}`;
12071
+ comboCounts.set(key, (comboCounts.get(key) || 0) + 1);
12072
+ }
12073
+ for (const [combo, count] of comboCounts) {
12074
+ if (count >= this.SAME_TOOL_THRESHOLD) {
12075
+ const [tool] = combo.split(":");
12076
+ return {
12077
+ isStuck: true,
12078
+ reason: `Same tool "${tool}" executed ${count} times with same target`,
12079
+ suggestion: `Try a different tool or target. Consider switching approach entirely.`,
12080
+ confidence: Math.min(0.9, 0.5 + count * 0.1)
12081
+ };
12082
+ }
12083
+ }
12084
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12064
12085
  }
12065
12086
  /**
12066
- * Get YOLO mode status
12087
+ * Check 2: Cycle detection (A→B→C→A→B→C pattern)
12067
12088
  */
12068
- getYoloStatus() {
12069
- const remainingMs = this.yoloExpiresAt ? Math.max(0, this.yoloExpiresAt - Date.now()) : null;
12070
- return {
12071
- enabled: this.yoloMode,
12072
- expiresAt: this.yoloExpiresAt,
12073
- remainingMs
12074
- };
12089
+ checkCycleDetection() {
12090
+ if (this.actionHistory.length < this.CYCLE_MIN_LENGTH * this.CYCLE_REPETITIONS) {
12091
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12092
+ }
12093
+ const recent = this.actionHistory.slice(-20).map((a) => a.tool);
12094
+ for (let cycleLen = this.CYCLE_MIN_LENGTH; cycleLen <= 5; cycleLen++) {
12095
+ if (recent.length < cycleLen * this.CYCLE_REPETITIONS) continue;
12096
+ const lastCycle = recent.slice(-cycleLen);
12097
+ let repetitions = 0;
12098
+ for (let i = recent.length - cycleLen; i >= 0; i -= cycleLen) {
12099
+ const segment = recent.slice(i, i + cycleLen);
12100
+ if (segment.length === cycleLen && segment.every((s, idx) => s === lastCycle[idx])) {
12101
+ repetitions++;
12102
+ } else {
12103
+ break;
12104
+ }
12105
+ }
12106
+ if (repetitions >= this.CYCLE_REPETITIONS) {
12107
+ return {
12108
+ isStuck: true,
12109
+ reason: `Cycle detected: [${lastCycle.join(" \u2192 ")}] repeated ${repetitions + 1} times`,
12110
+ suggestion: `Break the cycle. Try a completely different approach or tool category.`,
12111
+ confidence: Math.min(0.95, 0.6 + repetitions * 0.1)
12112
+ };
12113
+ }
12114
+ }
12115
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12075
12116
  }
12076
12117
  /**
12077
- * Get approval statistics
12118
+ * Check 3: No progress for too long
12078
12119
  */
12079
- getStats() {
12080
- return { ...this.stats };
12120
+ checkProgressTimeout() {
12121
+ if (this.progressEvents.length === 0 && this.actionHistory.length > 5) {
12122
+ const oldestAction = this.actionHistory[0];
12123
+ const elapsed = Date.now() - oldestAction.timestamp;
12124
+ if (elapsed > this.NO_PROGRESS_TIMEOUT) {
12125
+ return {
12126
+ isStuck: true,
12127
+ reason: `No progress events recorded in ${Math.round(elapsed / 6e4)} minutes`,
12128
+ suggestion: `Consider scanning more broadly or trying a different attack vector.`,
12129
+ confidence: 0.7
12130
+ };
12131
+ }
12132
+ }
12133
+ if (this.progressEvents.length > 0) {
12134
+ const lastProgress = this.progressEvents[this.progressEvents.length - 1];
12135
+ const elapsed = Date.now() - lastProgress.timestamp;
12136
+ if (elapsed > this.NO_PROGRESS_TIMEOUT) {
12137
+ return {
12138
+ isStuck: true,
12139
+ reason: `No progress for ${Math.round(elapsed / 6e4)} minutes (last: ${lastProgress.details})`,
12140
+ suggestion: `Last progress was "${lastProgress.type}". Try a different approach.`,
12141
+ confidence: Math.min(0.85, 0.5 + elapsed / this.NO_PROGRESS_TIMEOUT * 0.3)
12142
+ };
12143
+ }
12144
+ }
12145
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12081
12146
  }
12082
12147
  /**
12083
- * Cancel YOLO mode
12148
+ * Check 4: Streak of actions with no new information
12084
12149
  */
12085
- cancelYoloMode() {
12086
- this.setYoloMode(false);
12150
+ checkNoNewInfoStreak() {
12151
+ if (this.actionHistory.length < this.NO_NEW_INFO_THRESHOLD) {
12152
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12153
+ }
12154
+ const recent = this.actionHistory.slice(-this.NO_NEW_INFO_THRESHOLD);
12155
+ const noNewInfoCount = recent.filter((a) => !a.hasNewInfo).length;
12156
+ if (noNewInfoCount >= this.NO_NEW_INFO_THRESHOLD) {
12157
+ return {
12158
+ isStuck: true,
12159
+ reason: `${noNewInfoCount} consecutive actions produced no new information`,
12160
+ suggestion: `All recent actions returned known information. Try deeper enumeration or a different service.`,
12161
+ confidence: 0.75
12162
+ };
12163
+ }
12164
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12087
12165
  }
12088
12166
  /**
12089
- * Dispose of the approval manager
12167
+ * Check 5: All recent actions failed
12090
12168
  */
12091
- dispose() {
12092
- if (this.yoloTimer) {
12093
- clearTimeout(this.yoloTimer);
12094
- this.yoloTimer = null;
12169
+ checkAllFailures() {
12170
+ if (this.actionHistory.length < 5) {
12171
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12095
12172
  }
12096
- this.pendingRequests.clear();
12097
- this.removeAllListeners();
12173
+ const recent = this.actionHistory.slice(-5);
12174
+ const failureCount = recent.filter((a) => !a.success).length;
12175
+ if (failureCount >= 5) {
12176
+ return {
12177
+ isStuck: true,
12178
+ reason: `Last ${failureCount} actions all failed`,
12179
+ suggestion: `Multiple failures detected. Check if the target is reachable, or try different tools.`,
12180
+ confidence: 0.8
12181
+ };
12182
+ }
12183
+ return { isStuck: false, reason: "", suggestion: "", confidence: 0 };
12098
12184
  }
12099
- };
12100
- var approvalManager = null;
12101
- function getApprovalManager(options) {
12102
- if (!approvalManager) {
12103
- approvalManager = new ApprovalManager(options);
12185
+ /**
12186
+ * Get suggested alternative approaches
12187
+ */
12188
+ getSuggestedPivots(completedTools, availableServices) {
12189
+ const suggestions = [];
12190
+ const toolCategories = {
12191
+ "web_scanning": ["ffuf", "gobuster", "nikto", "nuclei", "whatweb"],
12192
+ "credential_attack": ["hydra", "medusa", "john", "hashcat"],
12193
+ "smb_attack": ["smbclient", "smbmap", "enum4linux", "crackmapexec"],
12194
+ "ssh_attack": ["ssh", "hydra"],
12195
+ "exploit_search": ["searchsploit", "metasploit"],
12196
+ "dns_recon": ["dig", "dnsenum", "dnsrecon", "subfinder"],
12197
+ "privesc": ["run_privesc_enum", "check_sudo", "find_suid"]
12198
+ };
12199
+ for (const [category, tools] of Object.entries(toolCategories)) {
12200
+ const usedCount = tools.filter((t) => completedTools.includes(t)).length;
12201
+ const totalCount = tools.length;
12202
+ if (usedCount < totalCount / 2) {
12203
+ suggestions.push(`Try ${category} tools (${totalCount - usedCount} unused)`);
12204
+ }
12205
+ }
12206
+ for (const service of availableServices) {
12207
+ const normalizedService = service.toLowerCase();
12208
+ if (normalizedService.includes("http") && !completedTools.includes("ffuf")) {
12209
+ suggestions.push("Try directory fuzzing on web services");
12210
+ }
12211
+ if (normalizedService.includes("smb") && !completedTools.includes("enum4linux")) {
12212
+ suggestions.push("Try full SMB enumeration");
12213
+ }
12214
+ }
12215
+ return suggestions.slice(0, 5);
12104
12216
  }
12105
- return approvalManager;
12106
- }
12217
+ /**
12218
+ * Reset detector state
12219
+ */
12220
+ reset() {
12221
+ this.actionHistory = [];
12222
+ this.progressEvents = [];
12223
+ }
12224
+ /**
12225
+ * Get statistics about current state
12226
+ */
12227
+ getStats() {
12228
+ const total = this.actionHistory.length;
12229
+ const successes = this.actionHistory.filter((a) => a.success).length;
12230
+ const newInfo = this.actionHistory.filter((a) => a.hasNewInfo).length;
12231
+ const lastProgress = this.progressEvents.length > 0 ? Date.now() - this.progressEvents[this.progressEvents.length - 1].timestamp : null;
12232
+ return {
12233
+ totalActions: total,
12234
+ successRate: total > 0 ? successes / total : 0,
12235
+ newInfoRate: total > 0 ? newInfo / total : 0,
12236
+ lastProgressAge: lastProgress
12237
+ };
12238
+ }
12239
+ };
12107
12240
 
12108
12241
  // src/core/agent/autonomous-agent.ts
12109
12242
  function toContentBlockParam(block) {
@@ -12147,7 +12280,7 @@ var DEFAULT_PHASES = [
12147
12280
  { id: PHASE_ID.EXFIL, name: "Data Exfiltration", shortName: "Exfil", status: PHASE_STATUS.PENDING, attempts: 0 },
12148
12281
  { id: PHASE_ID.REPORT, name: "Reporting", shortName: "Report", status: PHASE_STATUS.PENDING, attempts: 0 }
12149
12282
  ];
12150
- var AutonomousHackingAgent = class extends EventEmitter18 {
12283
+ var AutonomousHackingAgent = class extends EventEmitter17 {
12151
12284
  // LLM Client
12152
12285
  client;
12153
12286
  // Core State
@@ -12163,12 +12296,15 @@ var AutonomousHackingAgent = class extends EventEmitter18 {
12163
12296
  sessionManager;
12164
12297
  // Utility Systems
12165
12298
  hookExecutor;
12166
- mcpManager;
12167
12299
  contextManager;
12168
12300
  approvalManager;
12169
- // v0.12.0 New Systems
12301
+ // v0.12.7 New Systems
12170
12302
  resourceManager;
12171
12303
  auditLogger;
12304
+ // Strategy modules
12305
+ attackPlanner;
12306
+ stuckDetector;
12307
+ currentAttackPlan;
12172
12308
  // Token usage tracking
12173
12309
  tokenUsage = { input: 0, output: 0, total: 0 };
12174
12310
  // Execution control flags
@@ -12181,11 +12317,12 @@ var AutonomousHackingAgent = class extends EventEmitter18 {
12181
12317
  enableAutoCheckpoint = true;
12182
12318
  checkpointIntervalMs = 6e4;
12183
12319
  enableAutonomousOrchestrator = true;
12184
- // New: Use enhanced autonomous orchestrator
12185
12320
  // Rabbit hole detection settings
12186
12321
  STUCK_THRESHOLD = 5;
12187
12322
  STUCK_TIME_THRESHOLD = 3e5;
12188
12323
  MAX_PHASE_ATTEMPTS = 20;
12324
+ // Stream control
12325
+ currentStream = null;
12189
12326
  constructor(apiKey, config) {
12190
12327
  super();
12191
12328
  this.client = new Anthropic({
@@ -12194,6 +12331,8 @@ var AutonomousHackingAgent = class extends EventEmitter18 {
12194
12331
  });
12195
12332
  this.config = { ...AGENT_CONFIG, ...config };
12196
12333
  this.tools = ALL_TOOLS;
12334
+ this.attackPlanner = getAttackPlanner();
12335
+ this.stuckDetector = new StuckDetector();
12197
12336
  this.state = this.createInitialState();
12198
12337
  this.sharedMemory = new SharedMemory();
12199
12338
  this.sharedMemory.on("finding_added", (finding) => {
@@ -12248,7 +12387,7 @@ var AutonomousHackingAgent = class extends EventEmitter18 {
12248
12387
  this.think(THOUGHT_TYPE.PLANNING, "[coordinator] Replanning attack strategy...");
12249
12388
  });
12250
12389
  this.coordinator.on("thought", (data) => {
12251
- this.think(THOUGHT_TYPE.PLANNING, data.message);
12390
+ this.think(THOUGHT_TYPE.PLANNING, data.content);
12252
12391
  });
12253
12392
  if (this.enableAutonomousOrchestrator) {
12254
12393
  const autonomousOrch = this.coordinator;
@@ -12264,7 +12403,6 @@ var AutonomousHackingAgent = class extends EventEmitter18 {
12264
12403
  }
12265
12404
  this.sessionManager = new SessionManager(".pentesting/sessions");
12266
12405
  this.hookExecutor = getHookExecutor();
12267
- this.mcpManager = getMCPManager();
12268
12406
  this.contextManager = new ContextManager(this.client);
12269
12407
  this.approvalManager = getApprovalManager({ yoloMode: config?.autoApprove });
12270
12408
  this.resourceManager = getResourceManager();
@@ -12394,6 +12532,16 @@ var AutonomousHackingAgent = class extends EventEmitter18 {
12394
12532
  this.emit(AGENT_EVENT.TARGET_SET, { target, action: "added" });
12395
12533
  return true;
12396
12534
  }
12535
+ /**
12536
+ * Add an MCP server for extended tool capabilities
12537
+ */
12538
+ async addMCPServer(name, command, args) {
12539
+ this.think(THOUGHT_TYPE.PLANNING, `[mcp] Registering MCP server: ${name} (${command})`);
12540
+ this.emit(AGENT_EVENT.THOUGHT, {
12541
+ type: THOUGHT_TYPE.PLANNING,
12542
+ content: `Connected to MCP server: ${name}`
12543
+ });
12544
+ }
12397
12545
  // ===== MAIN AUTONOMOUS EXECUTION =====
12398
12546
  async runAutonomous(objective) {
12399
12547
  if (!this.state.target.primary) {
@@ -12411,6 +12559,17 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
12411
12559
  role: "user",
12412
12560
  content: mainObjective
12413
12561
  });
12562
+ this.currentAttackPlan = this.attackPlanner.generatePlan(
12563
+ this.state.target.primary,
12564
+ this.state.target.services.map((s) => ({
12565
+ host: s.host,
12566
+ port: s.port,
12567
+ protocol: s.protocol,
12568
+ service: s.service,
12569
+ version: s.version
12570
+ }))
12571
+ );
12572
+ this.think(THOUGHT_TYPE.PLANNING, `[planner] Generated attack plan with ${this.currentAttackPlan.phases.length} phases`);
12414
12573
  if (this.enableAutoCheckpoint) {
12415
12574
  this.sessionManager.enableAutoCheckpoint(this.checkpointIntervalMs, async () => {
12416
12575
  return await this.getCurrentState();
@@ -12425,8 +12584,14 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
12425
12584
  this.state.fullAttempts++;
12426
12585
  this.emit(AGENT_EVENT.ITERATION, { current: iteration, max: maxIterations, phase: this.state.currentPhase });
12427
12586
  try {
12428
- if (this.checkIfStuck()) {
12587
+ const stuckState = this.stuckDetector.analyze();
12588
+ const legacyStuck = this.checkIfStuck();
12589
+ if (stuckState.isStuck || legacyStuck) {
12429
12590
  this.state.status = AGENT_STATUS.STUCK;
12591
+ if (stuckState.isStuck) {
12592
+ this.think(THOUGHT_TYPE.STUCK, `[stuck] ${stuckState.reason} (confidence: ${(stuckState.confidence * 100).toFixed(0)}%)`);
12593
+ this.think(THOUGHT_TYPE.PLANNING, `[stuck] Suggestion: ${stuckState.suggestion}`);
12594
+ }
12430
12595
  if (this.learningSystem) {
12431
12596
  const reflection = await this.analyzeFailureWithLearning();
12432
12597
  this.think(THOUGHT_TYPE.REFLECTION, `[learning] ${reflection}`);
@@ -12435,6 +12600,37 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
12435
12600
  this.think(THOUGHT_TYPE.PLANNING, `[learning] Trying alternative: ${alternative}`);
12436
12601
  }
12437
12602
  }
12603
+ const completedTools = [...this.state.repeatedActions.keys()].map((k) => k.split(":")[0]);
12604
+ const serviceNames = this.state.target.services.map((s) => s.service);
12605
+ const pivots = this.stuckDetector.getSuggestedPivots(completedTools, serviceNames);
12606
+ if (pivots.length > 0) {
12607
+ this.think(THOUGHT_TYPE.PLANNING, `[planner] Pivot suggestions: ${pivots.join(", ")}`);
12608
+ }
12609
+ if (this.currentAttackPlan) {
12610
+ const serviceInfos = this.state.target.services.map((s) => ({
12611
+ host: s.host,
12612
+ port: s.port,
12613
+ protocol: s.protocol,
12614
+ service: s.service,
12615
+ version: s.version
12616
+ }));
12617
+ const hasCredentials = this.state.target.credentials.length > 0;
12618
+ const hasShell = this.state.target.compromised.length > 0;
12619
+ const nextSteps = this.attackPlanner.suggestNextSteps(
12620
+ serviceInfos,
12621
+ completedTools,
12622
+ hasCredentials,
12623
+ hasShell
12624
+ );
12625
+ if (nextSteps.length > 0) {
12626
+ const stepDescriptions = nextSteps.slice(0, 3).map((s) => `${s.tool}: ${s.description}`).join("; ");
12627
+ this.think(THOUGHT_TYPE.PLANNING, `[planner] Suggested: ${stepDescriptions}`);
12628
+ this.state.history.push({
12629
+ role: "user",
12630
+ content: `[System] You are stuck. Try these alternative approaches: ${stepDescriptions}. Avoid repeating the same tools/commands.`
12631
+ });
12632
+ }
12633
+ }
12438
12634
  const shouldSkip = await this.decideNextPhase();
12439
12635
  if (shouldSkip) {
12440
12636
  this.setPhaseStatus(this.state.currentPhase, PHASE_STATUS.SKIPPED);
@@ -12497,7 +12693,7 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
12497
12693
  try {
12498
12694
  await this.coordinator.initializeAttack(target, attackGoal);
12499
12695
  let findings = [];
12500
- if (this.enableAutonomousOrchestrator && this.coordinator.runAutonomous) {
12696
+ if (this.enableAutonomousOrchestrator && "runAutonomous" in this.coordinator) {
12501
12697
  findings = await this.coordinator.runAutonomous(attackGoal);
12502
12698
  } else {
12503
12699
  findings = await this.coordinator.run();
@@ -12622,7 +12818,7 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
12622
12818
  this.emit(AGENT_EVENT.LLM_START, { model: LLM_MODEL });
12623
12819
  let response;
12624
12820
  try {
12625
- const stream = this.client.messages.stream({
12821
+ this.currentStream = this.client.messages.stream({
12626
12822
  model: LLM_MODEL,
12627
12823
  max_tokens: LLM_MAX_TOKENS,
12628
12824
  system: systemPrompt,
@@ -12631,7 +12827,7 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
12631
12827
  thinking: { type: "enabled", budget_tokens: 16e3 }
12632
12828
  });
12633
12829
  let thinkingBuffer = "";
12634
- stream.on("contentBlock", (block) => {
12830
+ this.currentStream.on("contentBlock", (block) => {
12635
12831
  if (block.type === "thinking" && block.thinking) {
12636
12832
  thinkingBuffer += block.thinking;
12637
12833
  this.emit(AGENT_EVENT.THOUGHT, {
@@ -12645,7 +12841,8 @@ Goal: Deep penetration to obtain root/system privileges, extract internal data,
12645
12841
  });
12646
12842
  }
12647
12843
  });
12648
- response = await stream.finalMessage();
12844
+ response = await this.currentStream.finalMessage();
12845
+ this.currentStream = null;
12649
12846
  } catch (error) {
12650
12847
  response = await withRetry(
12651
12848
  () => this.client.messages.create({
@@ -12702,32 +12899,46 @@ Leverage shared memory findings from other agents.
12702
12899
  }
12703
12900
  async processResponse(response) {
12704
12901
  const contentBlocks = [];
12705
- const toolResults = [] = [];
12902
+ const toolUseBlocks = [];
12903
+ const toolResults = [];
12706
12904
  let textResponse = "";
12707
- let hasToolUse = false;
12708
- const processTool = async (block) => {
12709
- hasToolUse = true;
12905
+ for (const block of response.content) {
12906
+ if (block.type === "text") {
12907
+ textResponse += block.text;
12908
+ contentBlocks.push({ type: "text", text: block.text });
12909
+ this.think(THOUGHT_TYPE.OBSERVATION, block.text.slice(0, 500));
12910
+ this.emit(AGENT_EVENT.RESPONSE, block.text);
12911
+ } else if (block.type === "tool_use") {
12912
+ contentBlocks.push({
12913
+ type: "tool_use",
12914
+ id: block.id,
12915
+ name: block.name,
12916
+ input: block.input
12917
+ });
12918
+ toolUseBlocks.push(block);
12919
+ }
12920
+ }
12921
+ if (contentBlocks.length > 0) {
12922
+ this.state.history.push({
12923
+ role: "assistant",
12924
+ content: contentBlocks
12925
+ });
12926
+ }
12927
+ for (const block of toolUseBlocks) {
12710
12928
  const toolName = block.name;
12711
12929
  const toolInput = block.input;
12712
- contentBlocks.push({
12713
- type: "tool_use",
12714
- id: block.id,
12715
- name: toolName,
12716
- input: toolInput
12717
- });
12718
12930
  const actionKey = `${toolName}:${JSON.stringify(toolInput).slice(0, 100)}`;
12719
- this.trackAction(actionKey);
12720
12931
  this.think(THOUGHT_TYPE.ACTION, `[tool] ${toolName}`);
12721
12932
  this.emit(AGENT_EVENT.TOOL_CALL, { id: block.id, name: toolName, input: toolInput });
12722
12933
  const hookCheck = await this.hookExecutor.checkPreToolUse(toolName, toolInput);
12723
12934
  if (hookCheck.decision === "block") {
12724
12935
  this.think(THOUGHT_TYPE.STUCK, `Tool blocked: ${hookCheck.output}`);
12725
12936
  toolResults.push({ id: block.id, content: `Blocked: ${hookCheck.output}`, is_error: true });
12726
- return;
12937
+ continue;
12727
12938
  }
12728
12939
  if (this.shouldStopLoop()) {
12729
12940
  this.think(THOUGHT_TYPE.OBSERVATION, "Execution paused");
12730
- return;
12941
+ break;
12731
12942
  }
12732
12943
  if (this.approvalManager.requiresApproval(toolName, toolInput)) {
12733
12944
  const risk = assessRisk(toolName, toolInput);
@@ -12738,14 +12949,14 @@ Leverage shared memory findings from other agents.
12738
12949
  riskLevel: risk
12739
12950
  });
12740
12951
  const decision = await new Promise((resolve) => {
12741
- const handler = (response2) => {
12742
- if (response2.requestId === block.id) {
12952
+ const handler = (resp) => {
12953
+ if (resp.requestId === block.id) {
12743
12954
  this.approvalManager.removeListener("approval_response", handler);
12744
- if (response2.decision === "approve_always") {
12745
- this.approvalManager.autoApprovedTools?.add(toolName);
12955
+ if (resp.decision === "approve_always") {
12956
+ this.approvalManager.addAutoApprovedTool(toolName);
12746
12957
  resolve("approve");
12747
12958
  } else {
12748
- resolve(response2.decision);
12959
+ resolve(resp.decision);
12749
12960
  }
12750
12961
  }
12751
12962
  };
@@ -12754,7 +12965,7 @@ Leverage shared memory findings from other agents.
12754
12965
  if (decision === "deny") {
12755
12966
  this.think(THOUGHT_TYPE.STUCK, `Tool denied: ${toolName}`);
12756
12967
  toolResults.push({ id: block.id, content: "Denied", is_error: true });
12757
- return;
12968
+ continue;
12758
12969
  }
12759
12970
  }
12760
12971
  const result = await executeToolCall(toolName, toolInput);
@@ -12765,26 +12976,12 @@ Leverage shared memory findings from other agents.
12765
12976
  );
12766
12977
  this.emit(AGENT_EVENT.TOOL_RESULT, { id: block.id, name: toolName, result });
12767
12978
  this.extractIntelligence(toolName, result);
12979
+ this.trackAction(actionKey, result);
12768
12980
  toolResults.push({
12769
12981
  id: block.id,
12770
12982
  content: result.output || result.error || "No output",
12771
12983
  is_error: !result.success
12772
12984
  });
12773
- };
12774
- const toolsToProcess = response.content.filter((b) => b.type === "tool_use").map(processTool);
12775
- for (const block of response.content) {
12776
- if (block.type === "text") {
12777
- textResponse += block.text;
12778
- contentBlocks.push({ type: "text", text: block.text });
12779
- this.think(THOUGHT_TYPE.OBSERVATION, block.text.slice(0, 500));
12780
- this.emit(AGENT_EVENT.RESPONSE, block.text);
12781
- }
12782
- }
12783
- if (contentBlocks.length > 0) {
12784
- this.state.history.push({
12785
- role: "assistant",
12786
- content: contentBlocks
12787
- });
12788
12985
  }
12789
12986
  if (toolResults.length > 0) {
12790
12987
  this.state.history.push({
@@ -12797,11 +12994,8 @@ Leverage shared memory findings from other agents.
12797
12994
  }))
12798
12995
  });
12799
12996
  }
12800
- await Promise.all(toolsToProcess);
12801
- if (response.stop_reason === "tool_use" && hasToolUse && !this.shouldStopLoop()) {
12802
- setImmediate(() => this.executeStep().catch((err) => {
12803
- this.think(THOUGHT_TYPE.STUCK, `Error in next step: ${err}`);
12804
- }));
12997
+ if (response.stop_reason === "tool_use" && toolUseBlocks.length > 0 && !this.shouldStopLoop()) {
12998
+ return await this.executeStep();
12805
12999
  }
12806
13000
  return textResponse;
12807
13001
  }
@@ -12809,7 +13003,7 @@ Leverage shared memory findings from other agents.
12809
13003
  extractIntelligence(toolName, result) {
12810
13004
  if (!result.success) return;
12811
13005
  const output = result.output;
12812
- if (toolName === "nmap_scan" || toolName === "bash") {
13006
+ if (toolName === TOOL_NAME.NMAP_SCAN || toolName === TOOL_NAME.BASH) {
12813
13007
  const portMatches = output.match(/(\d+)\/(tcp|udp)\s+open\s+(\S+)/gi);
12814
13008
  if (portMatches) {
12815
13009
  portMatches.forEach((match) => {
@@ -12838,10 +13032,41 @@ Leverage shared memory findings from other agents.
12838
13032
  }
12839
13033
  });
12840
13034
  }
13035
+ if (portMatches && this.currentAttackPlan) {
13036
+ const currentServices = this.state.target.services.map((s) => ({
13037
+ host: s.host,
13038
+ port: s.port,
13039
+ protocol: s.protocol,
13040
+ service: s.service,
13041
+ version: s.version
13042
+ }));
13043
+ const findingTitles = this.state.findings.map((f) => f.title);
13044
+ this.currentAttackPlan = this.attackPlanner.adaptPlan(
13045
+ this.currentAttackPlan,
13046
+ currentServices,
13047
+ findingTitles
13048
+ );
13049
+ if (this.currentAttackPlan.adaptations.length > 0) {
13050
+ const latest = this.currentAttackPlan.adaptations[this.currentAttackPlan.adaptations.length - 1];
13051
+ this.think(THOUGHT_TYPE.PLANNING, `[planner] Plan adapted: ${latest}`);
13052
+ }
13053
+ }
12841
13054
  }
12842
- if (output.includes("meterpreter") || output.includes("shell") || output.includes("www-data") || output.includes("uid=")) {
13055
+ if (output.includes("meterpreter") || output.includes("www-data") || output.includes("uid=")) {
12843
13056
  this.addCompromisedHost(this.state.target.primary);
12844
13057
  }
13058
+ const credPatterns = [
13059
+ /(?:user|login|username)[:\s=]+([\w.@-]+)/gi,
13060
+ /(?:pass|password|pwd)[:\s=]+([\S]+)/gi
13061
+ ];
13062
+ for (const pattern of credPatterns) {
13063
+ const matches = output.matchAll(pattern);
13064
+ for (const match of matches) {
13065
+ if (match[1] && match[1].length > 2 && match[1].length < 100) {
13066
+ this.stuckDetector.recordProgress("credential_found", `Potential cred: ${match[1].substring(0, 20)}`);
13067
+ }
13068
+ }
13069
+ }
12845
13070
  }
12846
13071
  // ===== LEARNING SYSTEM INTEGRATION =====
12847
13072
  async analyzeFailureWithLearning() {
@@ -13021,13 +13246,19 @@ Alternatives: ${reflection.alternatives.map((a) => a.description).join(", ")}`;
13021
13246
  this.state.stuckCounter = 0;
13022
13247
  this.state.lastProgressTime = /* @__PURE__ */ new Date();
13023
13248
  this.state.repeatedActions.clear();
13249
+ this.stuckDetector.recordProgress("phase_advanced", `Reset at phase ${this.state.currentPhase}`);
13024
13250
  }
13025
- trackAction(action) {
13251
+ trackAction(action, result) {
13026
13252
  const count = this.state.repeatedActions.get(action) || 0;
13027
13253
  this.state.repeatedActions.set(action, count + 1);
13028
13254
  if (count + 1 >= this.STUCK_THRESHOLD) {
13029
13255
  this.state.stuckCounter++;
13030
13256
  }
13257
+ const [tool, ...targetParts] = action.split(":");
13258
+ const target = targetParts.join(":") || this.state.target.primary;
13259
+ const success = result ? result.success : true;
13260
+ const hasNewInfo = result ? result.output.length > 50 && success : false;
13261
+ this.stuckDetector.recordAction(tool, target, success, hasNewInfo);
13031
13262
  }
13032
13263
  // ===== DECISION HELPERS =====
13033
13264
  shouldAdvancePhase() {
@@ -13063,6 +13294,11 @@ Alternatives: ${reflection.alternatives.map((a) => a.description).join(", ")}`;
13063
13294
  const hasSuccess = successKeywords.some((kw) => response.toLowerCase().includes(kw));
13064
13295
  if (hasSuccess) {
13065
13296
  this.resetStuckCounter();
13297
+ if (response.toLowerCase().includes("shell") || response.toLowerCase().includes("root")) {
13298
+ this.stuckDetector.recordProgress("shell_obtained", "Shell access detected in response");
13299
+ } else if (response.toLowerCase().includes("discovered") || response.toLowerCase().includes("found")) {
13300
+ this.stuckDetector.recordProgress("service_discovered", "Discovery detected in response");
13301
+ }
13066
13302
  }
13067
13303
  }
13068
13304
  async attemptRecovery(error) {
@@ -13122,6 +13358,13 @@ Alternatives: ${reflection.alternatives.map((a) => a.description).join(", ")}`;
13122
13358
  recordProgress(type) {
13123
13359
  this.resetStuckCounter();
13124
13360
  this.state.lastProgressTime = /* @__PURE__ */ new Date();
13361
+ const progressTypeMap = {
13362
+ discovery: "service_discovered",
13363
+ credential: "credential_found",
13364
+ access: "shell_obtained",
13365
+ exploit: "vulnerability_found"
13366
+ };
13367
+ this.stuckDetector.recordProgress(progressTypeMap[type] || "service_discovered", type);
13125
13368
  let message = "";
13126
13369
  let importance = 60;
13127
13370
  switch (type) {
@@ -13220,6 +13463,13 @@ ${this.state.target.compromised.map((h) => `- ${h}`).join("\n") || "None"}
13220
13463
  pause() {
13221
13464
  this.isPaused = true;
13222
13465
  this.state.status = AGENT_STATUS.PAUSED;
13466
+ if (this.currentStream) {
13467
+ try {
13468
+ this.currentStream.abort();
13469
+ } catch (e) {
13470
+ }
13471
+ this.currentStream = null;
13472
+ }
13223
13473
  this.emit(AGENT_EVENT.PAUSED);
13224
13474
  }
13225
13475
  resume() {
@@ -13234,6 +13484,13 @@ ${this.state.target.compromised.map((h) => `- ${h}`).join("\n") || "None"}
13234
13484
  this.isPaused = true;
13235
13485
  this.isAborted = true;
13236
13486
  this.state.status = AGENT_STATUS.IDLE;
13487
+ if (this.currentStream) {
13488
+ try {
13489
+ this.currentStream.abort();
13490
+ } catch (e) {
13491
+ }
13492
+ this.currentStream = null;
13493
+ }
13237
13494
  this.emit(AGENT_EVENT.PAUSED);
13238
13495
  }
13239
13496
  reset() {
@@ -13243,25 +13500,6 @@ ${this.state.target.compromised.map((h) => `- ${h}`).join("\n") || "None"}
13243
13500
  this.sharedMemory.clear();
13244
13501
  this.emit(AGENT_EVENT.RESET);
13245
13502
  }
13246
- // ===== MCP INTEGRATION =====
13247
- async addMCPServer(name, command, args) {
13248
- await this.mcpManager.addServer(name, { command, args });
13249
- const mcpTools = this.mcpManager.getAvailableTools();
13250
- for (const mcpTool of mcpTools) {
13251
- const anthropicTool = {
13252
- name: mcpTool.name,
13253
- description: mcpTool.description,
13254
- input_schema: mcpTool.inputSchema
13255
- };
13256
- if (!this.tools.find((t) => t.name === mcpTool.name)) {
13257
- this.tools.push(anthropicTool);
13258
- }
13259
- }
13260
- this.emit(AGENT_EVENT.MCP_SERVER_ADDED, { name, toolCount: mcpTools.length });
13261
- }
13262
- getMCPTools() {
13263
- return this.mcpManager.getAvailableTools().map((t) => t.name);
13264
- }
13265
13503
  // ===== PUBLIC ACCESSORS =====
13266
13504
  getSystemPrompt() {
13267
13505
  return AUTONOMOUS_HACKING_PROMPT;
@@ -13333,7 +13571,42 @@ ${this.state.target.compromised.map((h) => `- ${h}`).join("\n") || "None"}
13333
13571
  } else if (block.type === "tool_use") {
13334
13572
  hasToolCalls = true;
13335
13573
  this.emit(AGENT_EVENT.TOOL_CALL, { name: block.name, input: block.input });
13336
- const result = await executeToolCall(block.name, block.input);
13574
+ const toolInput = block.input;
13575
+ if (this.approvalManager.requiresApproval(block.name, toolInput)) {
13576
+ const risk = assessRisk(block.name, toolInput);
13577
+ this.emit(AGENT_EVENT.APPROVAL_NEEDED, {
13578
+ id: block.id,
13579
+ toolName: block.name,
13580
+ toolInput,
13581
+ riskLevel: risk
13582
+ });
13583
+ const decision = await new Promise((resolve) => {
13584
+ const handler = (resp) => {
13585
+ if (resp.requestId === block.id) {
13586
+ this.approvalManager.removeListener("approval_response", handler);
13587
+ resolve(resp.decision);
13588
+ }
13589
+ };
13590
+ this.approvalManager.on("approval_response", handler);
13591
+ });
13592
+ if (decision === "deny") {
13593
+ this.state.history.push({
13594
+ role: "assistant",
13595
+ content: response.content
13596
+ });
13597
+ this.state.history.push({
13598
+ role: "user",
13599
+ content: [{
13600
+ type: "tool_result",
13601
+ tool_use_id: block.id,
13602
+ content: "Tool execution denied by user",
13603
+ is_error: true
13604
+ }]
13605
+ });
13606
+ continue;
13607
+ }
13608
+ }
13609
+ const result = await executeToolCall(block.name, toolInput);
13337
13610
  this.emit(AGENT_EVENT.TOOL_RESULT, { name: block.name, result });
13338
13611
  this.state.history.push({
13339
13612
  role: "assistant",
@@ -13567,8 +13840,8 @@ Respond helpfully. If asked to perform security testing, use appropriate tools.`
13567
13840
  this.resourceManager.stopMonitoring();
13568
13841
  await this.resourceManager.performCleanup();
13569
13842
  this.resourceManager.dispose();
13570
- this.removeAllListeners();
13571
13843
  this.emit("shutdown");
13844
+ this.removeAllListeners();
13572
13845
  }
13573
13846
  // ===== UTILITY: Execute Tool with Audit =====
13574
13847
  async executeToolWithAudit(toolName, toolInput) {
@@ -13610,16 +13883,16 @@ Respond helpfully. If asked to perform security testing, use appropriate tools.`
13610
13883
  };
13611
13884
 
13612
13885
  // src/core/session/session-manager.ts
13613
- import * as fs6 from "fs/promises";
13886
+ import * as fs7 from "fs/promises";
13614
13887
  import * as path5 from "path";
13615
- import { EventEmitter as EventEmitter19 } from "events";
13888
+ import { EventEmitter as EventEmitter18 } from "events";
13616
13889
  var SESSIONS_DIR = ".pentesting/sessions";
13617
13890
  function generateSessionId() {
13618
13891
  const timestamp = Date.now().toString(36);
13619
13892
  const random = Math.random().toString(36).substring(2, 8);
13620
13893
  return `session_${timestamp}_${random}`;
13621
13894
  }
13622
- var SessionManager2 = class extends EventEmitter19 {
13895
+ var SessionManager2 = class extends EventEmitter18 {
13623
13896
  sessionsDir;
13624
13897
  currentSession = null;
13625
13898
  constructor(baseDir) {
@@ -13631,7 +13904,7 @@ var SessionManager2 = class extends EventEmitter19 {
13631
13904
  */
13632
13905
  async initialize() {
13633
13906
  try {
13634
- await fs6.mkdir(this.sessionsDir, { recursive: true });
13907
+ await fs7.mkdir(this.sessionsDir, { recursive: true });
13635
13908
  } catch {
13636
13909
  }
13637
13910
  }
@@ -13652,7 +13925,7 @@ var SessionManager2 = class extends EventEmitter19 {
13652
13925
  };
13653
13926
  this.currentSession = metadata;
13654
13927
  const sessionDir = path5.join(this.sessionsDir, metadata.id);
13655
- await fs6.mkdir(sessionDir, { recursive: true });
13928
+ await fs7.mkdir(sessionDir, { recursive: true });
13656
13929
  await this.saveMetadata(metadata);
13657
13930
  this.emit("session_created", metadata);
13658
13931
  return metadata;
@@ -13662,7 +13935,7 @@ var SessionManager2 = class extends EventEmitter19 {
13662
13935
  */
13663
13936
  async saveMetadata(metadata) {
13664
13937
  const metadataPath = path5.join(this.sessionsDir, metadata.id, "metadata.json");
13665
- await fs6.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
13938
+ await fs7.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
13666
13939
  }
13667
13940
  /**
13668
13941
  * Save full session snapshot
@@ -13676,16 +13949,16 @@ var SessionManager2 = class extends EventEmitter19 {
13676
13949
  this.currentSession.currentPhase = snapshot.state.currentPhase;
13677
13950
  await this.saveMetadata(this.currentSession);
13678
13951
  const statePath = path5.join(sessionDir, "state.json");
13679
- await fs6.writeFile(statePath, JSON.stringify(snapshot.state, null, 2));
13952
+ await fs7.writeFile(statePath, JSON.stringify(snapshot.state, null, 2));
13680
13953
  const configPath = path5.join(sessionDir, "config.json");
13681
- await fs6.writeFile(configPath, JSON.stringify(snapshot.config, null, 2));
13954
+ await fs7.writeFile(configPath, JSON.stringify(snapshot.config, null, 2));
13682
13955
  const historyPath = path5.join(sessionDir, "history.jsonl");
13683
13956
  const historyLine = JSON.stringify({
13684
13957
  timestamp: this.currentSession.updatedAt,
13685
13958
  iteration: snapshot.state.iteration,
13686
13959
  phase: snapshot.state.currentPhase
13687
13960
  }) + "\n";
13688
- await fs6.appendFile(historyPath, historyLine);
13961
+ await fs7.appendFile(historyPath, historyLine);
13689
13962
  this.emit("snapshot_saved", this.currentSession.id);
13690
13963
  }
13691
13964
  /**
@@ -13695,15 +13968,15 @@ var SessionManager2 = class extends EventEmitter19 {
13695
13968
  try {
13696
13969
  const sessionDir = path5.join(this.sessionsDir, sessionId);
13697
13970
  const metadataPath = path5.join(sessionDir, "metadata.json");
13698
- const metadataContent = await fs6.readFile(metadataPath, "utf-8");
13971
+ const metadataContent = await fs7.readFile(metadataPath, "utf-8");
13699
13972
  const metadata = JSON.parse(metadataContent);
13700
13973
  const statePath = path5.join(sessionDir, "state.json");
13701
- const stateContent = await fs6.readFile(statePath, "utf-8");
13974
+ const stateContent = await fs7.readFile(statePath, "utf-8");
13702
13975
  const state = JSON.parse(stateContent);
13703
13976
  const configPath = path5.join(sessionDir, "config.json");
13704
13977
  let config = {};
13705
13978
  try {
13706
- const configContent = await fs6.readFile(configPath, "utf-8");
13979
+ const configContent = await fs7.readFile(configPath, "utf-8");
13707
13980
  config = JSON.parse(configContent);
13708
13981
  } catch {
13709
13982
  }
@@ -13720,13 +13993,13 @@ var SessionManager2 = class extends EventEmitter19 {
13720
13993
  async listSessions() {
13721
13994
  await this.initialize();
13722
13995
  try {
13723
- const entries = await fs6.readdir(this.sessionsDir, { withFileTypes: true });
13996
+ const entries = await fs7.readdir(this.sessionsDir, { withFileTypes: true });
13724
13997
  const sessions = [];
13725
13998
  for (const entry of entries) {
13726
13999
  if (entry.isDirectory()) {
13727
14000
  try {
13728
14001
  const metadataPath = path5.join(this.sessionsDir, entry.name, "metadata.json");
13729
- const content = await fs6.readFile(metadataPath, "utf-8");
14002
+ const content = await fs7.readFile(metadataPath, "utf-8");
13730
14003
  const metadata = JSON.parse(content);
13731
14004
  sessions.push({
13732
14005
  id: metadata.id,
@@ -13776,14 +14049,14 @@ var SessionManager2 = class extends EventEmitter19 {
13776
14049
  await this.initialize();
13777
14050
  let deletedCount = 0;
13778
14051
  try {
13779
- const entries = await fs6.readdir(this.sessionsDir, { withFileTypes: true });
14052
+ const entries = await fs7.readdir(this.sessionsDir, { withFileTypes: true });
13780
14053
  for (const entry of entries) {
13781
14054
  if (entry.isDirectory()) {
13782
14055
  const metadataPath = path5.join(this.sessionsDir, entry.name, "metadata.json");
13783
14056
  try {
13784
- await fs6.access(metadataPath);
14057
+ await fs7.access(metadataPath);
13785
14058
  } catch {
13786
- await fs6.rm(path5.join(this.sessionsDir, entry.name), { recursive: true });
14059
+ await fs7.rm(path5.join(this.sessionsDir, entry.name), { recursive: true });
13787
14060
  deletedCount++;
13788
14061
  }
13789
14062
  }
@@ -13806,7 +14079,7 @@ var SessionManager2 = class extends EventEmitter19 {
13806
14079
  async deleteSession(sessionId) {
13807
14080
  try {
13808
14081
  const sessionDir = path5.join(this.sessionsDir, sessionId);
13809
- await fs6.rm(sessionDir, { recursive: true });
14082
+ await fs7.rm(sessionDir, { recursive: true });
13810
14083
  this.emit("session_deleted", sessionId);
13811
14084
  return true;
13812
14085
  } catch {
@@ -13938,7 +14211,7 @@ function getSlashCommandRegistry() {
13938
14211
  // src/core/context/context-manager.ts
13939
14212
  import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "fs";
13940
14213
  import { join as join6, dirname as dirname2 } from "path";
13941
- import { EventEmitter as EventEmitter20 } from "events";
14214
+ import { EventEmitter as EventEmitter19 } from "events";
13942
14215
  var CONTEXT_EVENT = {
13943
14216
  MESSAGE_ADDED: "message_added",
13944
14217
  CHECKPOINT_CREATED: "checkpoint_created",
@@ -13946,7 +14219,7 @@ var CONTEXT_EVENT = {
13946
14219
  CLEARED: "cleared",
13947
14220
  COMPACTED: "compacted"
13948
14221
  };
13949
- var ContextManager2 = class extends EventEmitter20 {
14222
+ var ContextManager2 = class extends EventEmitter19 {
13950
14223
  filePath;
13951
14224
  state;
13952
14225
  maxMessages;
@@ -14728,7 +15001,7 @@ import { homedir as homedir2 } from "os";
14728
15001
  import { join as join9 } from "path";
14729
15002
 
14730
15003
  // src/utils/input-queue.ts
14731
- import { EventEmitter as EventEmitter21 } from "events";
15004
+ import { EventEmitter as EventEmitter20 } from "events";
14732
15005
  var INPUT_QUEUE_EVENT = {
14733
15006
  QUEUED: "queued",
14734
15007
  // Message added to queue
@@ -14741,7 +15014,7 @@ var INPUT_QUEUE_EVENT = {
14741
15014
  SHUTDOWN: "shutdown"
14742
15015
  // Queue shutdown
14743
15016
  };
14744
- var InputQueue = class extends EventEmitter21 {
15017
+ var InputQueue = class extends EventEmitter20 {
14745
15018
  queue = [];
14746
15019
  isShutdown = false;
14747
15020
  isPaused = false;
@@ -14888,9 +15161,10 @@ var App = ({ autoApprove = false, target }) => {
14888
15161
  const [mode, setMode] = useState("agent");
14889
15162
  const [checkpointCount, setCheckpointCount] = useState(0);
14890
15163
  const [pendingTargetConfirmation, setPendingTargetConfirmation] = useState(null);
14891
- const [spinnerHue, setSpinnerHue] = useState(0);
14892
15164
  const [queuedCount, setQueuedCount] = useState(0);
14893
15165
  const [, forceUpdate] = useState(0);
15166
+ const [ctrlCCount, setCtrlCCount] = useState(0);
15167
+ const ctrlCTimerRef = useRef(null);
14894
15168
  const updateProcessing = useCallback((val) => {
14895
15169
  isProcessingRef.current = val;
14896
15170
  setIsProcessing(val);
@@ -14932,7 +15206,7 @@ var App = ({ autoApprove = false, target }) => {
14932
15206
  setCheckpointCount(contextManagerRef.current?.getCheckpoints().length || 0);
14933
15207
  }
14934
15208
  });
14935
- import("./auto-update-GKO64QMO.js").then(({ checkForUpdateAsync, formatUpdateNotification }) => {
15209
+ import("./auto-update-6CLBRLE3.js").then(({ checkForUpdateAsync, formatUpdateNotification }) => {
14936
15210
  checkForUpdateAsync().then((result) => {
14937
15211
  if (result.hasUpdate) {
14938
15212
  const notification = formatUpdateNotification(result);
@@ -14958,6 +15232,20 @@ var App = ({ autoApprove = false, target }) => {
14958
15232
  }
14959
15233
  });
14960
15234
  }, [sessionManager2]);
15235
+ useEffect(() => {
15236
+ const queue = getInputQueue();
15237
+ const syncQueueState = () => {
15238
+ setQueuedCount(queue.length);
15239
+ };
15240
+ queue.on(INPUT_QUEUE_EVENT.QUEUED, syncQueueState);
15241
+ queue.on(INPUT_QUEUE_EVENT.DEQUEUED, syncQueueState);
15242
+ queue.on(INPUT_QUEUE_EVENT.CLEARED, syncQueueState);
15243
+ return () => {
15244
+ queue.off(INPUT_QUEUE_EVENT.QUEUED, syncQueueState);
15245
+ queue.off(INPUT_QUEUE_EVENT.DEQUEUED, syncQueueState);
15246
+ queue.off(INPUT_QUEUE_EVENT.CLEARED, syncQueueState);
15247
+ };
15248
+ }, []);
14961
15249
  const startTimeRef = useRef(0);
14962
15250
  const timerRef = useRef(null);
14963
15251
  const addMessage = useCallback((type, content, extraOrDuration) => {
@@ -14980,9 +15268,8 @@ var App = ({ autoApprove = false, target }) => {
14980
15268
  const startTimer = useCallback(() => {
14981
15269
  startTimeRef.current = Date.now();
14982
15270
  timerRef.current = setInterval(() => {
14983
- setElapsedTime(Math.floor((Date.now() - startTimeRef.current) / 100) / 10);
14984
- setSpinnerHue((h) => (h + 15) % 360);
14985
- }, 100);
15271
+ setElapsedTime(Math.floor((Date.now() - startTimeRef.current) / 1e3));
15272
+ }, 1e3);
14986
15273
  }, []);
14987
15274
  const stopTimer = useCallback(() => {
14988
15275
  if (timerRef.current) {
@@ -15159,10 +15446,24 @@ var App = ({ autoApprove = false, target }) => {
15159
15446
  }, [agent, target, addMessage, stopTimer, autoApprove, approvalManager2]);
15160
15447
  const handleExit = useCallback(async () => {
15161
15448
  setCurrentStatus("Exiting...");
15162
- await agent.shutdown();
15163
- setTimeout(() => {
15449
+ try {
15450
+ if (timerRef.current) {
15451
+ clearInterval(timerRef.current);
15452
+ timerRef.current = null;
15453
+ }
15454
+ const queue = getInputQueue();
15455
+ queue.shutdown();
15456
+ await Promise.race([
15457
+ agent.shutdown(),
15458
+ new Promise((resolve) => setTimeout(resolve, 3e3))
15459
+ ]);
15460
+ const rm2 = getResourceManager();
15461
+ await rm2.performCleanup();
15462
+ exit();
15463
+ } catch (err) {
15464
+ console.error("Shutdown error:", err);
15164
15465
  exit();
15165
- }, 150);
15466
+ }
15166
15467
  }, [agent, exit]);
15167
15468
  useEffect(() => {
15168
15469
  const onExit = () => {
@@ -15228,10 +15529,7 @@ var App = ({ autoApprove = false, target }) => {
15228
15529
  }
15229
15530
  }
15230
15531
  if (trimmed === "/exit" || trimmed === "/quit" || trimmed === "/q") {
15231
- if (isProcessing && agent) {
15232
- agent.pause();
15233
- }
15234
- exit();
15532
+ await handleExit();
15235
15533
  return;
15236
15534
  }
15237
15535
  if (!isProcessing && !trimmed.startsWith("/") && !pendingTargetConfirmation && !pendingApproval) {
@@ -15526,7 +15824,7 @@ ${list}`);
15526
15824
  case CLI_COMMAND.EXIT:
15527
15825
  case "quit":
15528
15826
  case "q":
15529
- exit();
15827
+ await handleExit();
15530
15828
  return;
15531
15829
  case "approve":
15532
15830
  case "y":
@@ -15703,7 +16001,7 @@ ${list}`);
15703
16001
  return;
15704
16002
  case "update":
15705
16003
  try {
15706
- const { checkForUpdate, formatUpdateNotification, doUpdate } = await import("./update-UMRID565.js");
16004
+ const { checkForUpdate, formatUpdateNotification, doUpdate } = await import("./update-34NDFWS3.js");
15707
16005
  const result = checkForUpdate(true);
15708
16006
  if (result.hasUpdate) {
15709
16007
  const notification = formatUpdateNotification(result);
@@ -15761,7 +16059,7 @@ ${list}`);
15761
16059
  }, (error, stdout2, stderr2) => {
15762
16060
  if (child.pid) resourceManager.untrackProcess(child.pid);
15763
16061
  if (error) reject({ ...error, stdout: stdout2, stderr: stderr2 });
15764
- else resolve({ stdout: stdout2.toString(), stderr: stderr2.toString() });
16062
+ else resolve({ stdout: String(stdout2), stderr: String(stderr2) });
15765
16063
  });
15766
16064
  if (child.pid) resourceManager.trackProcess(child.pid);
15767
16065
  });
@@ -15821,29 +16119,44 @@ ${list}`);
15821
16119
  }
15822
16120
  if (key.ctrl && input2 === "c") {
15823
16121
  if (isProcessing) {
15824
- agent.pause();
15825
- stopTimer();
15826
- updateProcessing(false);
15827
- setCurrentStatus("");
15828
- addMessage(MESSAGE_TYPE.SYSTEM, "Interrupted.");
16122
+ if (ctrlCCount === 0) {
16123
+ setCtrlCCount(1);
16124
+ addMessage(MESSAGE_TYPE.SYSTEM, "\u26A0\uFE0F Really stop? Press Ctrl+C again within 10 seconds to confirm.");
16125
+ if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
16126
+ ctrlCTimerRef.current = setTimeout(() => {
16127
+ setCtrlCCount(0);
16128
+ addMessage(MESSAGE_TYPE.SYSTEM, "Continuing operation...");
16129
+ }, 1e4);
16130
+ } else {
16131
+ if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
16132
+ setCtrlCCount(0);
16133
+ agent.pause();
16134
+ stopTimer();
16135
+ updateProcessing(false);
16136
+ setCurrentStatus("");
16137
+ addMessage(MESSAGE_TYPE.SYSTEM, "\u23F9 Interrupted by user.");
16138
+ }
15829
16139
  } else {
15830
16140
  exit();
15831
16141
  }
16142
+ return;
15832
16143
  }
15833
- if (key.escape) {
16144
+ if (key.escape || input2 === "\x1B") {
15834
16145
  if (isProcessing) {
15835
16146
  agent.pause();
15836
16147
  stopTimer();
15837
16148
  updateProcessing(false);
15838
16149
  setCurrentStatus("");
15839
16150
  updatePendingApproval(null);
15840
- addMessage(MESSAGE_TYPE.SYSTEM, "Interrupted. You can continue chatting.");
16151
+ addMessage(MESSAGE_TYPE.SYSTEM, "\u23F9 Process halted via ESC. Ready for next command.");
15841
16152
  }
16153
+ return;
15842
16154
  }
15843
16155
  if (key.tab) {
15844
16156
  const newMode = mode === "agent" ? "shell" : "agent";
15845
16157
  setMode(newMode);
15846
16158
  addMessage(MESSAGE_TYPE.SYSTEM, `Mode: ${newMode === "agent" ? "Agent" : "Shell"}`);
16159
+ return;
15847
16160
  }
15848
16161
  });
15849
16162
  const getStyle = (type) => {
@@ -15931,10 +16244,25 @@ ${list}`);
15931
16244
  }
15932
16245
  )
15933
16246
  ] }),
15934
- isProcessing && /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
15935
- "ESC to interrupt \u2502 ",
15936
- queuedCount > 0 ? `\u{1F4E5} ${queuedCount} messages queued` : "Chatting enabled during execution"
15937
- ] }) })
16247
+ isProcessing && /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
16248
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
16249
+ "ESC to interrupt \u2502 ",
16250
+ queuedCount > 0 ? `\u{1F4E5} ${queuedCount} messages queued` : "Chatting enabled during execution"
16251
+ ] }),
16252
+ queuedCount > 0 && (() => {
16253
+ try {
16254
+ const pending = getInputQueue().getPending().slice(0, 3);
16255
+ return pending.map((item, i) => /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
16256
+ " ",
16257
+ i + 1,
16258
+ ". ",
16259
+ String(item.content || item).slice(0, 60)
16260
+ ] }, i));
16261
+ } catch {
16262
+ return null;
16263
+ }
16264
+ })()
16265
+ ] })
15938
16266
  ] }),
15939
16267
  /* @__PURE__ */ jsx3(
15940
16268
  status_bar_default,
@@ -16022,8 +16350,8 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
16022
16350
  const summary = agent.getSummary();
16023
16351
  console.log(JSON.stringify(summary, null, 2));
16024
16352
  if (options.output) {
16025
- const fs7 = await import("fs/promises");
16026
- await fs7.writeFile(options.output, JSON.stringify(summary, null, 2));
16353
+ const fs8 = await import("fs/promises");
16354
+ await fs8.writeFile(options.output, JSON.stringify(summary, null, 2));
16027
16355
  console.log(chalk.hex(THEME.text.accent)(`
16028
16356
  [+] Report saved to: ${options.output}`));
16029
16357
  }