pentesting 0.41.0 → 0.44.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.js +626 -373
- package/dist/prompts/ctf-mode.md +1 -1
- package/dist/prompts/recon.md +3 -3
- package/dist/prompts/strategist-system.md +271 -0
- package/dist/prompts/strategy.md +8 -2
- package/dist/prompts/techniques/lateral.md +1 -1
- package/dist/prompts/techniques/network-svc.md +3 -3
- package/dist/prompts/vuln.md +2 -2
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -10,7 +10,6 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
10
10
|
import { render } from "ink";
|
|
11
11
|
import { Command } from "commander";
|
|
12
12
|
import chalk from "chalk";
|
|
13
|
-
import gradient from "gradient-string";
|
|
14
13
|
|
|
15
14
|
// src/platform/tui/app.tsx
|
|
16
15
|
import { useState as useState4, useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
@@ -307,14 +306,12 @@ var ORPHAN_PROCESS_NAMES = [
|
|
|
307
306
|
"dnsspoof",
|
|
308
307
|
"tcpdump",
|
|
309
308
|
"tshark",
|
|
310
|
-
"socat"
|
|
311
|
-
"nc",
|
|
312
|
-
"python"
|
|
309
|
+
"socat"
|
|
313
310
|
];
|
|
314
311
|
|
|
315
312
|
// src/shared/constants/agent.ts
|
|
316
313
|
var APP_NAME = "Pentest AI";
|
|
317
|
-
var APP_VERSION = "0.
|
|
314
|
+
var APP_VERSION = "0.44.0";
|
|
318
315
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
319
316
|
var LLM_ROLES = {
|
|
320
317
|
SYSTEM: "system",
|
|
@@ -342,6 +339,146 @@ var SENSITIVE_INPUT_TYPES = [
|
|
|
342
339
|
INPUT_TYPES.CREDENTIAL
|
|
343
340
|
];
|
|
344
341
|
|
|
342
|
+
// src/shared/utils/time-strategy.ts
|
|
343
|
+
var PHASE_RATIOS = {
|
|
344
|
+
/** Sprint ends at 25% of total time */
|
|
345
|
+
SPRINT_END: 0.25,
|
|
346
|
+
/** Exploit phase ends at 50% of total time */
|
|
347
|
+
EXPLOIT_END: 0.5,
|
|
348
|
+
/** Creative phase ends at 75% of total time */
|
|
349
|
+
CREATIVE_END: 0.75
|
|
350
|
+
// Harvest: 75%-100%
|
|
351
|
+
};
|
|
352
|
+
var DEFAULT_DURATION_SEC = 86400;
|
|
353
|
+
var MIN_DURATION_SEC = 300;
|
|
354
|
+
var MAX_DURATION_SEC = 604800;
|
|
355
|
+
var MS_PER_SEC = 1e3;
|
|
356
|
+
function getSessionDurationMs() {
|
|
357
|
+
const envValue = process.env.PENTEST_DURATION;
|
|
358
|
+
if (envValue) {
|
|
359
|
+
const parsed = parseInt(envValue, 10);
|
|
360
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
361
|
+
const clamped = Math.max(MIN_DURATION_SEC, Math.min(MAX_DURATION_SEC, parsed));
|
|
362
|
+
return clamped * MS_PER_SEC;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return DEFAULT_DURATION_SEC * MS_PER_SEC;
|
|
366
|
+
}
|
|
367
|
+
function computeDeadline(startedAt) {
|
|
368
|
+
return startedAt + getSessionDurationMs();
|
|
369
|
+
}
|
|
370
|
+
function formatDuration(totalSec) {
|
|
371
|
+
if (totalSec <= 0) return "0s";
|
|
372
|
+
const h = Math.floor(totalSec / 3600);
|
|
373
|
+
const m = Math.floor(totalSec % 3600 / 60);
|
|
374
|
+
const s = Math.floor(totalSec % 60);
|
|
375
|
+
if (h > 0 && m > 0) return `${h}h${m}m`;
|
|
376
|
+
if (h > 0) return `${h}h`;
|
|
377
|
+
if (m > 0 && s > 0) return `${m}m${s}s`;
|
|
378
|
+
if (m > 0) return `${m}m`;
|
|
379
|
+
return `${s}s`;
|
|
380
|
+
}
|
|
381
|
+
function getTimeAdaptiveStrategy(elapsedMs, deadlineMs) {
|
|
382
|
+
let totalMs;
|
|
383
|
+
let remainingMs;
|
|
384
|
+
if (deadlineMs > 0) {
|
|
385
|
+
remainingMs = Math.max(0, deadlineMs - Date.now());
|
|
386
|
+
totalMs = elapsedMs + remainingMs;
|
|
387
|
+
} else {
|
|
388
|
+
totalMs = getSessionDurationMs();
|
|
389
|
+
remainingMs = Math.max(0, totalMs - elapsedMs);
|
|
390
|
+
}
|
|
391
|
+
if (totalMs <= 0) totalMs = 1;
|
|
392
|
+
const progress = Math.min(1, elapsedMs / totalMs);
|
|
393
|
+
const remainingSec = Math.floor(remainingMs / MS_PER_SEC);
|
|
394
|
+
const totalDurationSec = Math.floor(totalMs / MS_PER_SEC);
|
|
395
|
+
const elapsedStr = formatDuration(Math.floor(elapsedMs / MS_PER_SEC));
|
|
396
|
+
const remainStr = formatDuration(remainingSec);
|
|
397
|
+
const totalStr = formatDuration(totalDurationSec);
|
|
398
|
+
const pctStr = `${Math.floor(progress * 100)}%`;
|
|
399
|
+
if (progress >= PHASE_RATIOS.CREATIVE_END) {
|
|
400
|
+
return {
|
|
401
|
+
phase: "harvest",
|
|
402
|
+
label: "HARVEST",
|
|
403
|
+
urgency: "critical",
|
|
404
|
+
progress,
|
|
405
|
+
totalDurationSec,
|
|
406
|
+
remainingSec,
|
|
407
|
+
directive: [
|
|
408
|
+
`\u{1F3C1} HARVEST MODE [${pctStr} elapsed, ${remainStr} of ${totalStr} remaining]:`,
|
|
409
|
+
"- STOP exploring new attack vectors. Exploit what you HAVE.",
|
|
410
|
+
"- Submit ALL discovered flags immediately",
|
|
411
|
+
"- Check obvious flag locations: /root/flag.txt, user.txt, env vars, DB tables",
|
|
412
|
+
"- Re-check previously accessed systems for missed flags/proof",
|
|
413
|
+
"- Collect and record ALL evidence and proof files",
|
|
414
|
+
"- Write final report with all findings",
|
|
415
|
+
"- Credential spray ALL discovered creds on ALL remaining services",
|
|
416
|
+
remainingSec <= 300 ? "- \u26A0\uFE0F FINAL 5 MINUTES \u2014 submit everything NOW, stop all scans" : "- Focus on highest-confidence paths only"
|
|
417
|
+
].join("\n")
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
if (progress >= PHASE_RATIOS.EXPLOIT_END) {
|
|
421
|
+
return {
|
|
422
|
+
phase: "creative",
|
|
423
|
+
label: "CREATIVE",
|
|
424
|
+
urgency: "high",
|
|
425
|
+
progress,
|
|
426
|
+
totalDurationSec,
|
|
427
|
+
remainingSec,
|
|
428
|
+
directive: [
|
|
429
|
+
`\u{1F52C} CREATIVE MODE [${pctStr} elapsed, ${remainStr} of ${totalStr} remaining]:`,
|
|
430
|
+
"- Try advanced techniques: chained exploits, custom tools, race conditions",
|
|
431
|
+
"- Binary analysis for SUID/custom binaries, kernel exploits for privesc",
|
|
432
|
+
"- Protocol-level attacks: SMB relay, Kerberoasting, ADCS abuse",
|
|
433
|
+
'- Search for latest bypasses: web_search("{defense} bypass {year}")',
|
|
434
|
+
"- If stuck >5min on any vector, SWITCH immediately",
|
|
435
|
+
"- Start preparing evidence collection for final phase",
|
|
436
|
+
"- If all targets are compromised \u2192 skip to harvest immediately"
|
|
437
|
+
].join("\n")
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
if (progress >= PHASE_RATIOS.SPRINT_END) {
|
|
441
|
+
return {
|
|
442
|
+
phase: "exploit",
|
|
443
|
+
label: "EXPLOIT",
|
|
444
|
+
urgency: "medium",
|
|
445
|
+
progress,
|
|
446
|
+
totalDurationSec,
|
|
447
|
+
remainingSec,
|
|
448
|
+
directive: [
|
|
449
|
+
`\u{1F3AF} EXPLOIT MODE [${pctStr} elapsed, ${remainStr} of ${totalStr} remaining]:`,
|
|
450
|
+
"- Concentrate on discovered vectors with highest success probability",
|
|
451
|
+
"- Run parallel exploitation attempts on multiple targets",
|
|
452
|
+
"- Search for CVE PoCs for every identified service+version",
|
|
453
|
+
"- Chain: credentials \u2192 spray everywhere, LFI \u2192 config \u2192 DB creds",
|
|
454
|
+
"- Keep background scans running for new targets",
|
|
455
|
+
"- Time-box each vector: 10min max then switch",
|
|
456
|
+
"- If all vectors exhausted early \u2192 move to creative techniques immediately"
|
|
457
|
+
].join("\n")
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
phase: "sprint",
|
|
462
|
+
label: "SPRINT",
|
|
463
|
+
urgency: "low",
|
|
464
|
+
progress,
|
|
465
|
+
totalDurationSec,
|
|
466
|
+
remainingSec,
|
|
467
|
+
directive: [
|
|
468
|
+
`\u26A1 SPRINT MODE [${pctStr} elapsed, ${remainStr} of ${totalStr} remaining]:`,
|
|
469
|
+
"- RustScan first for fast port discovery \u2192 then nmap -Pn -sV -sC on found ports",
|
|
470
|
+
"- ALWAYS use nmap -Pn (no exceptions \u2014 firewalls block ICMP)",
|
|
471
|
+
"- Run ALL scans in parallel (rustscan, nmap UDP, web fuzzing, CVE search)",
|
|
472
|
+
"- Try default/weak credentials on every discovered service IMMEDIATELY",
|
|
473
|
+
"- Check for exposed files (.env, .git, backup.sql, phpinfo)",
|
|
474
|
+
"- Anonymous access: FTP, SMB null session, Redis no auth, MongoDB",
|
|
475
|
+
"- OSINT: web_search for target intel, shodan, github repos",
|
|
476
|
+
"- Goal: Maximum attack surface discovery + instant wins",
|
|
477
|
+
"- \u26A1 If recon completes early \u2192 ATTACK IMMEDIATELY, do not wait for this phase to end"
|
|
478
|
+
].join("\n")
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
345
482
|
// src/shared/utils/id.ts
|
|
346
483
|
var ID_DEFAULT_RADIX = 36;
|
|
347
484
|
var ID_DEFAULT_LENGTH = 6;
|
|
@@ -2027,7 +2164,7 @@ async function cleanupAllProcesses() {
|
|
|
2027
2164
|
backgroundProcesses.clear();
|
|
2028
2165
|
for (const name of ORPHAN_PROCESS_NAMES) {
|
|
2029
2166
|
try {
|
|
2030
|
-
execSync2(`pkill -
|
|
2167
|
+
execSync2(`pkill -x "${name}" 2>/dev/null || true`, { stdio: "ignore", timeout: SYSTEM_LIMITS.PROCESS_OP_TIMEOUT_MS });
|
|
2031
2168
|
} catch {
|
|
2032
2169
|
}
|
|
2033
2170
|
}
|
|
@@ -2652,6 +2789,22 @@ var AttackGraph = class {
|
|
|
2652
2789
|
if (nodeCount >= GRAPH_LIMITS.ASCII_MAX_NODES) break;
|
|
2653
2790
|
const sIcon = statusIcons[node.status] || "?";
|
|
2654
2791
|
const fail = node.status === NODE_STATUS.FAILED && node.failReason ? ` \u2014 ${node.failReason}` : "";
|
|
2792
|
+
let detail = "";
|
|
2793
|
+
if (type === NODE_TYPE.CREDENTIAL) {
|
|
2794
|
+
const source = node.data.source ? ` (from: ${node.data.source})` : "";
|
|
2795
|
+
detail = source;
|
|
2796
|
+
} else if (type === NODE_TYPE.VULNERABILITY) {
|
|
2797
|
+
const sev = node.data.severity ? ` [${String(node.data.severity).toUpperCase()}]` : "";
|
|
2798
|
+
const exploit = node.data.hasExploit ? " [EXPLOIT]" : "";
|
|
2799
|
+
const target = node.data.target ? ` \u2192 ${node.data.target}` : "";
|
|
2800
|
+
detail = `${sev}${exploit}${target}`;
|
|
2801
|
+
} else if (type === NODE_TYPE.ACCESS) {
|
|
2802
|
+
const via = node.data.via ? ` via ${node.data.via}` : "";
|
|
2803
|
+
detail = via;
|
|
2804
|
+
} else if (type === NODE_TYPE.SERVICE) {
|
|
2805
|
+
const ver = node.data.version ? ` (${node.data.version})` : "";
|
|
2806
|
+
detail = ver;
|
|
2807
|
+
}
|
|
2655
2808
|
const outEdges = this.edges.filter((e) => e.from === node.id);
|
|
2656
2809
|
const edgeStr = outEdges.length > 0 ? ` \u2192 ${outEdges.map((e) => {
|
|
2657
2810
|
const target = this.nodes.get(e.to);
|
|
@@ -2659,10 +2812,19 @@ var AttackGraph = class {
|
|
|
2659
2812
|
const eStatus = e.status === EDGE_STATUS.FAILED ? " \u2717" : e.status === EDGE_STATUS.SUCCEEDED ? " \u2713" : "";
|
|
2660
2813
|
return `${eName}${eStatus}`;
|
|
2661
2814
|
}).join(", ")}` : "";
|
|
2662
|
-
lines.push(`\u2502 ${sIcon} ${node.label}${fail}${edgeStr}`);
|
|
2815
|
+
lines.push(`\u2502 ${sIcon} ${node.label}${detail}${fail}${edgeStr}`);
|
|
2663
2816
|
nodeCount++;
|
|
2664
2817
|
}
|
|
2665
2818
|
}
|
|
2819
|
+
const succeededNodes = Array.from(this.nodes.values()).filter((n) => n.status === NODE_STATUS.SUCCEEDED);
|
|
2820
|
+
if (succeededNodes.length > 0) {
|
|
2821
|
+
lines.push(`\u2502`);
|
|
2822
|
+
lines.push(`\u251C\u2500\u2500\u2500 Exploited \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524`);
|
|
2823
|
+
for (const n of succeededNodes) {
|
|
2824
|
+
const via = n.data.via ? ` via ${n.data.via}` : "";
|
|
2825
|
+
lines.push(`\u2502 \u25CF ${n.label}${via}`);
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2666
2828
|
const stats = this.getStats();
|
|
2667
2829
|
lines.push(`\u2502`);
|
|
2668
2830
|
lines.push(`\u251C\u2500\u2500\u2500 Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524`);
|
|
@@ -3183,6 +3345,7 @@ var SharedState = class {
|
|
|
3183
3345
|
// ─── Improvement #7: Dynamic Technique Library ────────────────
|
|
3184
3346
|
dynamicTechniques = new DynamicTechniqueLibrary();
|
|
3185
3347
|
constructor() {
|
|
3348
|
+
const now = Date.now();
|
|
3186
3349
|
this.data = {
|
|
3187
3350
|
engagement: null,
|
|
3188
3351
|
targets: /* @__PURE__ */ new Map(),
|
|
@@ -3196,8 +3359,10 @@ var SharedState = class {
|
|
|
3196
3359
|
ctfMode: true,
|
|
3197
3360
|
// CTF mode ON by default
|
|
3198
3361
|
flags: [],
|
|
3199
|
-
startedAt:
|
|
3200
|
-
|
|
3362
|
+
startedAt: now,
|
|
3363
|
+
// Auto-configure from PENTEST_DURATION env (seconds).
|
|
3364
|
+
// Defaults to 24h if not set. Can be overridden via setDeadline().
|
|
3365
|
+
deadlineAt: computeDeadline(now),
|
|
3201
3366
|
challengeAnalysis: null
|
|
3202
3367
|
};
|
|
3203
3368
|
}
|
|
@@ -3206,6 +3371,7 @@ var SharedState = class {
|
|
|
3206
3371
|
* Used by /clear command to start a fresh session without recreating the agent.
|
|
3207
3372
|
*/
|
|
3208
3373
|
reset() {
|
|
3374
|
+
const now = Date.now();
|
|
3209
3375
|
this.data = {
|
|
3210
3376
|
engagement: null,
|
|
3211
3377
|
targets: /* @__PURE__ */ new Map(),
|
|
@@ -3218,8 +3384,8 @@ var SharedState = class {
|
|
|
3218
3384
|
missionChecklist: [],
|
|
3219
3385
|
ctfMode: true,
|
|
3220
3386
|
flags: [],
|
|
3221
|
-
startedAt:
|
|
3222
|
-
deadlineAt:
|
|
3387
|
+
startedAt: now,
|
|
3388
|
+
deadlineAt: computeDeadline(now),
|
|
3223
3389
|
challengeAnalysis: null
|
|
3224
3390
|
};
|
|
3225
3391
|
this.attackGraph.reset();
|
|
@@ -3404,24 +3570,28 @@ var SharedState = class {
|
|
|
3404
3570
|
if (this.data.deadlineAt === 0) return 0;
|
|
3405
3571
|
return Math.max(0, this.data.deadlineAt - Date.now());
|
|
3406
3572
|
}
|
|
3407
|
-
/** Get time status string for prompt injection */
|
|
3573
|
+
/** Get time status string for prompt injection (ratio-based, aligned with PHASE_RATIOS) */
|
|
3408
3574
|
getTimeStatus() {
|
|
3409
3575
|
const elapsed = this.getElapsedMs();
|
|
3410
3576
|
const mins = Math.floor(elapsed / 6e4);
|
|
3411
3577
|
let status = `\u23F1 Elapsed: ${mins}min`;
|
|
3412
3578
|
if (this.data.deadlineAt > 0) {
|
|
3413
3579
|
const remainingMs = this.getRemainingMs();
|
|
3580
|
+
const totalMs = elapsed + remainingMs;
|
|
3581
|
+
const progress = totalMs > 0 ? elapsed / totalMs : 1;
|
|
3414
3582
|
const remainingMins = Math.floor(remainingMs / 6e4);
|
|
3415
|
-
if (
|
|
3583
|
+
if (remainingMs <= 0) {
|
|
3416
3584
|
status += " | \u26A0\uFE0F TIME IS UP \u2014 submit any flags you have NOW";
|
|
3417
|
-
} else if (
|
|
3585
|
+
} else if (progress >= 0.95) {
|
|
3418
3586
|
status += ` | \u{1F534} ${remainingMins}min LEFT \u2014 FINAL PUSH: submit flags, check obvious locations, env vars, DBs`;
|
|
3419
|
-
} else if (
|
|
3420
|
-
status += ` | \u{1F7E1} ${remainingMins}min LEFT \u2014 wrap up
|
|
3421
|
-
} else if (
|
|
3422
|
-
status += ` | \u{1F7E0} ${remainingMins}min LEFT \u2014
|
|
3587
|
+
} else if (progress >= 0.75) {
|
|
3588
|
+
status += ` | \u{1F7E1} ${remainingMins}min LEFT \u2014 HARVEST: wrap up, collect evidence, submit flags`;
|
|
3589
|
+
} else if (progress >= 0.5) {
|
|
3590
|
+
status += ` | \u{1F7E0} ${remainingMins}min LEFT \u2014 CREATIVE: advanced techniques, switch if stuck`;
|
|
3591
|
+
} else if (progress >= 0.25) {
|
|
3592
|
+
status += ` | \u{1F535} ${remainingMins}min LEFT \u2014 EXPLOIT: focus on discovered vectors`;
|
|
3423
3593
|
} else {
|
|
3424
|
-
status += ` | \u{1F7E2} ${remainingMins}min remaining`;
|
|
3594
|
+
status += ` | \u{1F7E2} ${remainingMins}min remaining \u2014 SPRINT: recon + quick wins`;
|
|
3425
3595
|
}
|
|
3426
3596
|
}
|
|
3427
3597
|
return status;
|
|
@@ -4912,26 +5082,31 @@ Detail: ${detail}
|
|
|
4912
5082
|
},
|
|
4913
5083
|
{
|
|
4914
5084
|
name: TOOL_NAMES.ADD_FINDING,
|
|
4915
|
-
description:
|
|
5085
|
+
description: `Add a security finding with full details.
|
|
5086
|
+
ALWAYS provide: description (HOW you exploited it, step-by-step), evidence (actual command output proving success), and attackPattern (MITRE ATT&CK tactic).
|
|
5087
|
+
Findings without evidence are marked as UNVERIFIED and have low credibility.`,
|
|
4916
5088
|
parameters: {
|
|
4917
|
-
title: { type: "string", description:
|
|
5089
|
+
title: { type: "string", description: 'Concise finding title (e.g., "Path Traversal via /download endpoint")' },
|
|
4918
5090
|
severity: { type: "string", description: "Severity: critical, high, medium, low, info" },
|
|
4919
|
-
affected: { type: "array", items: { type: "string" }, description:
|
|
5091
|
+
affected: { type: "array", items: { type: "string" }, description: 'Affected host:port or URLs (e.g., ["ctf.example.com:443/download"])' },
|
|
5092
|
+
description: { type: "string", description: "Detailed description: what the vulnerability is, how you exploited it step-by-step, what access it gives, and the impact. Include credentials found, methods used, and exploitation chain." },
|
|
5093
|
+
evidence: { type: "array", items: { type: "string" }, description: 'Actual command outputs proving the finding (e.g., ["curl output showing /etc/passwd", "uid=0(root) gid=0(root)"]). Copy real output here.' },
|
|
4920
5094
|
attackPattern: { type: "string", description: "MITRE ATT&CK tactic: initial_access, execution, persistence, privilege_escalation, defense_evasion, credential_access, discovery, lateral_movement, collection, exfiltration, command_and_control, impact" }
|
|
4921
5095
|
},
|
|
4922
|
-
required: ["title", "severity"],
|
|
5096
|
+
required: ["title", "severity", "description", "evidence"],
|
|
4923
5097
|
execute: async (p) => {
|
|
4924
5098
|
const evidence = p.evidence || [];
|
|
4925
5099
|
const title = p.title;
|
|
4926
5100
|
const severity = p.severity;
|
|
4927
5101
|
const affected = p.affected || [];
|
|
5102
|
+
const description = p.description || "";
|
|
4928
5103
|
const validation = validateFinding(evidence, severity);
|
|
4929
5104
|
state.addFinding({
|
|
4930
5105
|
id: generateId(AGENT_LIMITS.ID_RADIX, AGENT_LIMITS.ID_LENGTH),
|
|
4931
5106
|
title,
|
|
4932
5107
|
severity,
|
|
4933
5108
|
affected,
|
|
4934
|
-
description
|
|
5109
|
+
description,
|
|
4935
5110
|
evidence,
|
|
4936
5111
|
isVerified: validation.isVerified,
|
|
4937
5112
|
remediation: "",
|
|
@@ -6686,8 +6861,8 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
6686
6861
|
}
|
|
6687
6862
|
},
|
|
6688
6863
|
execute: async (p) => {
|
|
6689
|
-
const { existsSync:
|
|
6690
|
-
const { join:
|
|
6864
|
+
const { existsSync: existsSync10, statSync: statSync2, readdirSync: readdirSync3 } = await import("fs");
|
|
6865
|
+
const { join: join12 } = await import("path");
|
|
6691
6866
|
const category = p.category || "";
|
|
6692
6867
|
const search = p.search || "";
|
|
6693
6868
|
const minSize = p.min_size || 0;
|
|
@@ -6733,7 +6908,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
6733
6908
|
results.push("");
|
|
6734
6909
|
};
|
|
6735
6910
|
const scanDir = (dirPath, maxDepth = 3, depth = 0) => {
|
|
6736
|
-
if (depth > maxDepth || !
|
|
6911
|
+
if (depth > maxDepth || !existsSync10(dirPath)) return;
|
|
6737
6912
|
let entries;
|
|
6738
6913
|
try {
|
|
6739
6914
|
entries = readdirSync3(dirPath, { withFileTypes: true });
|
|
@@ -6742,7 +6917,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
|
|
|
6742
6917
|
}
|
|
6743
6918
|
for (const entry of entries) {
|
|
6744
6919
|
if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name)) continue;
|
|
6745
|
-
const fullPath =
|
|
6920
|
+
const fullPath = join12(dirPath, entry.name);
|
|
6746
6921
|
if (entry.isDirectory()) {
|
|
6747
6922
|
scanDir(fullPath, maxDepth, depth + 1);
|
|
6748
6923
|
continue;
|
|
@@ -8686,6 +8861,11 @@ var NETWORK_ERROR_CODES = {
|
|
|
8686
8861
|
ENOTFOUND: "ENOTFOUND",
|
|
8687
8862
|
CONNECT_TIMEOUT: "UND_ERR_CONNECT_TIMEOUT"
|
|
8688
8863
|
};
|
|
8864
|
+
var TRANSIENT_NETWORK_ERRORS = [
|
|
8865
|
+
NETWORK_ERROR_CODES.ECONNRESET,
|
|
8866
|
+
NETWORK_ERROR_CODES.ETIMEDOUT,
|
|
8867
|
+
NETWORK_ERROR_CODES.ENOTFOUND
|
|
8868
|
+
];
|
|
8689
8869
|
var LLMError = class extends Error {
|
|
8690
8870
|
/** Structured error information */
|
|
8691
8871
|
errorInfo;
|
|
@@ -8696,8 +8876,11 @@ var LLMError = class extends Error {
|
|
|
8696
8876
|
}
|
|
8697
8877
|
};
|
|
8698
8878
|
function classifyError(error) {
|
|
8879
|
+
if (error === null || error === void 0) {
|
|
8880
|
+
return { type: LLM_ERROR_TYPES.UNKNOWN, message: String(error), isRetryable: false, suggestedAction: "Analyze error" };
|
|
8881
|
+
}
|
|
8699
8882
|
const e = error;
|
|
8700
|
-
const statusCode = e
|
|
8883
|
+
const statusCode = e?.status || e?.statusCode;
|
|
8701
8884
|
const errorMessage = e?.error?.error?.message || e?.error?.message || e?.message || String(error);
|
|
8702
8885
|
if (statusCode === HTTP_STATUS.RATE_LIMIT || errorMessage.toLowerCase().includes("rate limit")) {
|
|
8703
8886
|
return { type: LLM_ERROR_TYPES.RATE_LIMIT, message: errorMessage, statusCode, isRetryable: true, suggestedAction: "Wait and retry" };
|
|
@@ -8708,7 +8891,7 @@ function classifyError(error) {
|
|
|
8708
8891
|
if (statusCode === HTTP_STATUS.BAD_REQUEST) {
|
|
8709
8892
|
return { type: LLM_ERROR_TYPES.INVALID_REQUEST, message: errorMessage, statusCode, isRetryable: false, suggestedAction: "Modify request" };
|
|
8710
8893
|
}
|
|
8711
|
-
if (e.code
|
|
8894
|
+
if (e.code && TRANSIENT_NETWORK_ERRORS.includes(e.code)) {
|
|
8712
8895
|
return { type: LLM_ERROR_TYPES.NETWORK_ERROR, message: errorMessage, isRetryable: true, suggestedAction: "Check network" };
|
|
8713
8896
|
}
|
|
8714
8897
|
if (errorMessage.toLowerCase().includes("timeout") || e.code === NETWORK_ERROR_CODES.CONNECT_TIMEOUT) {
|
|
@@ -10317,100 +10500,6 @@ var INITIAL_TASKS = {
|
|
|
10317
10500
|
RECON: "Initial reconnaissance and target discovery"
|
|
10318
10501
|
};
|
|
10319
10502
|
|
|
10320
|
-
// src/shared/utils/time-strategy.ts
|
|
10321
|
-
var TIME_CONSTANTS = {
|
|
10322
|
-
/** Milliseconds per minute */
|
|
10323
|
-
MS_PER_MINUTE: 6e4,
|
|
10324
|
-
/** Minutes threshold for sprint-to-exploit switch (no deadline mode) */
|
|
10325
|
-
SPRINT_MINUTES: 10,
|
|
10326
|
-
/** Deadline percentage thresholds for phase transitions */
|
|
10327
|
-
PHASE_SPRINT: 0.25,
|
|
10328
|
-
PHASE_EXPLOIT: 0.5,
|
|
10329
|
-
PHASE_CREATIVE: 0.75
|
|
10330
|
-
};
|
|
10331
|
-
function getTimeAdaptiveStrategy(elapsedMs, deadlineMs) {
|
|
10332
|
-
if (deadlineMs === 0) {
|
|
10333
|
-
const mins = Math.floor(elapsedMs / TIME_CONSTANTS.MS_PER_MINUTE);
|
|
10334
|
-
if (mins < TIME_CONSTANTS.SPRINT_MINUTES) {
|
|
10335
|
-
return {
|
|
10336
|
-
phase: "sprint",
|
|
10337
|
-
label: "SPRINT",
|
|
10338
|
-
urgency: "low",
|
|
10339
|
-
directive: `\u26A1 SPRINT MODE (${mins}min elapsed): Parallel reconnaissance. Full port scan + service enumeration + default credentials. Attack immediately on any finding.`
|
|
10340
|
-
};
|
|
10341
|
-
}
|
|
10342
|
-
return {
|
|
10343
|
-
phase: "exploit",
|
|
10344
|
-
label: "EXPLOIT",
|
|
10345
|
-
urgency: "low",
|
|
10346
|
-
directive: `\u{1F3AF} EXPLOIT MODE (${mins}min elapsed): Focus on discovered vectors. Chain findings. Build custom tools if needed.`
|
|
10347
|
-
};
|
|
10348
|
-
}
|
|
10349
|
-
const totalMs = deadlineMs - (deadlineMs > Date.now() ? Date.now() - elapsedMs : 0);
|
|
10350
|
-
const remainingMs = Math.max(0, deadlineMs - Date.now());
|
|
10351
|
-
const pct = totalMs > 0 ? 1 - remainingMs / totalMs : 1;
|
|
10352
|
-
const remainingMins = Math.floor(remainingMs / TIME_CONSTANTS.MS_PER_MINUTE);
|
|
10353
|
-
if (pct < TIME_CONSTANTS.PHASE_SPRINT) {
|
|
10354
|
-
return {
|
|
10355
|
-
phase: "sprint",
|
|
10356
|
-
label: "SPRINT",
|
|
10357
|
-
urgency: "low",
|
|
10358
|
-
directive: [
|
|
10359
|
-
`\u26A1 SPRINT MODE (${remainingMins}min remaining):`,
|
|
10360
|
-
"- Run ALL scans in parallel (nmap TCP/UDP, web fuzzing, CVE search)",
|
|
10361
|
-
"- Try default/weak credentials on every discovered service IMMEDIATELY",
|
|
10362
|
-
"- Check for exposed files (.env, .git, backup.sql, phpinfo)",
|
|
10363
|
-
"- Anonymous access: FTP, SMB null session, Redis no auth, MongoDB",
|
|
10364
|
-
"- Goal: Maximum attack surface discovery + instant wins"
|
|
10365
|
-
].join("\n")
|
|
10366
|
-
};
|
|
10367
|
-
}
|
|
10368
|
-
if (pct < TIME_CONSTANTS.PHASE_EXPLOIT) {
|
|
10369
|
-
return {
|
|
10370
|
-
phase: "exploit",
|
|
10371
|
-
label: "EXPLOIT",
|
|
10372
|
-
urgency: "medium",
|
|
10373
|
-
directive: [
|
|
10374
|
-
`\u{1F3AF} EXPLOIT MODE (${remainingMins}min remaining):`,
|
|
10375
|
-
"- Concentrate on discovered vectors with highest success probability",
|
|
10376
|
-
"- Run parallel exploitation attempts on multiple targets",
|
|
10377
|
-
"- Search for CVE PoCs for every identified service+version",
|
|
10378
|
-
"- Chain: credentials \u2192 spray everywhere, LFI \u2192 config \u2192 DB creds",
|
|
10379
|
-
"- Keep background scans running for new targets"
|
|
10380
|
-
].join("\n")
|
|
10381
|
-
};
|
|
10382
|
-
}
|
|
10383
|
-
if (pct < TIME_CONSTANTS.PHASE_CREATIVE) {
|
|
10384
|
-
return {
|
|
10385
|
-
phase: "creative",
|
|
10386
|
-
label: "CREATIVE",
|
|
10387
|
-
urgency: "high",
|
|
10388
|
-
directive: [
|
|
10389
|
-
`\u{1F52C} CREATIVE MODE (${remainingMins}min remaining):`,
|
|
10390
|
-
"- Try advanced techniques: chained exploits, custom tools, race conditions",
|
|
10391
|
-
"- Binary analysis for SUID/custom binaries, kernel exploits for privesc",
|
|
10392
|
-
"- Protocol-level attacks: SMB relay, Kerberoasting, ADCS abuse",
|
|
10393
|
-
'- Search for latest bypasses: web_search("{defense} bypass 2025")',
|
|
10394
|
-
"- If stuck >5min on any vector, SWITCH immediately"
|
|
10395
|
-
].join("\n")
|
|
10396
|
-
};
|
|
10397
|
-
}
|
|
10398
|
-
return {
|
|
10399
|
-
phase: "harvest",
|
|
10400
|
-
label: "HARVEST",
|
|
10401
|
-
urgency: "critical",
|
|
10402
|
-
directive: [
|
|
10403
|
-
`\u{1F3C1} HARVEST MODE (${remainingMins}min remaining \u2014 FINAL PUSH):`,
|
|
10404
|
-
"- STOP exploring new vectors. Focus on what you HAVE.",
|
|
10405
|
-
"- Submit ALL discovered flags immediately",
|
|
10406
|
-
"- Check obvious flag locations: /root/flag.txt, env vars, DB tables",
|
|
10407
|
-
"- Re-check previously accessed systems for missed flags",
|
|
10408
|
-
"- Collect and record ALL evidence and proof files",
|
|
10409
|
-
"- Write final report with all findings"
|
|
10410
|
-
].join("\n")
|
|
10411
|
-
};
|
|
10412
|
-
}
|
|
10413
|
-
|
|
10414
10503
|
// src/shared/constants/service-ports.ts
|
|
10415
10504
|
var SERVICE_PORTS = {
|
|
10416
10505
|
SSH: 22,
|
|
@@ -10684,13 +10773,21 @@ var PHASE_TECHNIQUE_MAP = {
|
|
|
10684
10773
|
};
|
|
10685
10774
|
var PromptBuilder = class {
|
|
10686
10775
|
state;
|
|
10776
|
+
strategist = null;
|
|
10687
10777
|
constructor(state) {
|
|
10688
10778
|
this.state = state;
|
|
10689
10779
|
}
|
|
10690
10780
|
/**
|
|
10691
|
-
*
|
|
10781
|
+
* Attach a Strategist instance for meta-prompting.
|
|
10782
|
+
* Called by MainAgent after construction.
|
|
10783
|
+
*/
|
|
10784
|
+
setStrategist(strategist) {
|
|
10785
|
+
this.strategist = strategist;
|
|
10786
|
+
}
|
|
10787
|
+
/**
|
|
10788
|
+
* Build complete prompt for LLM iteration (async).
|
|
10692
10789
|
*
|
|
10693
|
-
* Layers (phase-aware, enhanced with
|
|
10790
|
+
* Layers (phase-aware, enhanced with D-CIPHER meta-prompting):
|
|
10694
10791
|
* 1. base.md — Core identity, rules, autonomy, shell management (~7.6K tok)
|
|
10695
10792
|
* 2. Phase-specific prompt — current phase's full specialist knowledge (~2K tok)
|
|
10696
10793
|
* 3. Core methodology — strategy, orchestrator, evasion (always loaded, ~12K tok)
|
|
@@ -10706,9 +10803,10 @@ var PromptBuilder = class {
|
|
|
10706
10803
|
* 12. Session timeline (#12: episodic memory)
|
|
10707
10804
|
* 13. Learned techniques (#7: dynamic technique library)
|
|
10708
10805
|
* 14. Persistent memory (#12: cross-session knowledge)
|
|
10709
|
-
* 15.
|
|
10806
|
+
* ★ 15. STRATEGIC DIRECTIVE — LLM-generated tactical instructions (D-CIPHER)
|
|
10807
|
+
* 16. User context
|
|
10710
10808
|
*/
|
|
10711
|
-
build(userInput, phase) {
|
|
10809
|
+
async build(userInput, phase) {
|
|
10712
10810
|
const fragments = [
|
|
10713
10811
|
this.loadPromptFile(PROMPT_PATHS.BASE),
|
|
10714
10812
|
this.loadCtfModePrompt(),
|
|
@@ -10731,10 +10829,14 @@ var PromptBuilder = class {
|
|
|
10731
10829
|
// #12
|
|
10732
10830
|
this.getDynamicTechniquesFragment(),
|
|
10733
10831
|
// #7
|
|
10734
|
-
this.getPersistentMemoryFragment()
|
|
10832
|
+
this.getPersistentMemoryFragment()
|
|
10735
10833
|
// #12
|
|
10736
|
-
PROMPT_DEFAULTS.USER_CONTEXT(userInput)
|
|
10737
10834
|
];
|
|
10835
|
+
const strategistDirective = await this.getStrategistFragment();
|
|
10836
|
+
if (strategistDirective) {
|
|
10837
|
+
fragments.push(strategistDirective);
|
|
10838
|
+
}
|
|
10839
|
+
fragments.push(PROMPT_DEFAULTS.USER_CONTEXT(userInput));
|
|
10738
10840
|
return fragments.filter((f) => !!f).join("\n\n");
|
|
10739
10841
|
}
|
|
10740
10842
|
/**
|
|
@@ -10924,11 +11026,217 @@ ${lines.join("\n")}
|
|
|
10924
11026
|
}
|
|
10925
11027
|
return this.state.persistentMemory.toPrompt(services);
|
|
10926
11028
|
}
|
|
11029
|
+
// --- D-CIPHER: Strategist Meta-Prompting ---
|
|
11030
|
+
/**
|
|
11031
|
+
* Generate strategic directive via Strategist LLM.
|
|
11032
|
+
* Called every turn. Returns '' if no strategist is attached or call fails.
|
|
11033
|
+
*/
|
|
11034
|
+
async getStrategistFragment() {
|
|
11035
|
+
if (!this.strategist) return "";
|
|
11036
|
+
return this.strategist.generateDirective();
|
|
11037
|
+
}
|
|
11038
|
+
};
|
|
11039
|
+
|
|
11040
|
+
// src/agents/strategist.ts
|
|
11041
|
+
import { readFileSync as readFileSync6, existsSync as existsSync9 } from "fs";
|
|
11042
|
+
import { join as join11, dirname as dirname6 } from "path";
|
|
11043
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
11044
|
+
|
|
11045
|
+
// src/shared/constants/strategist.ts
|
|
11046
|
+
var STRATEGIST_LIMITS = {
|
|
11047
|
+
/** Maximum characters of state context sent to Strategist LLM.
|
|
11048
|
+
* WHY: Keeps Strategist input focused and affordable (~3-5K tokens).
|
|
11049
|
+
* Full state can be 20K+; Strategist needs summary, not everything. */
|
|
11050
|
+
MAX_INPUT_CHARS: 15e3,
|
|
11051
|
+
/** Maximum characters for the Strategist's response.
|
|
11052
|
+
* WHY: Directives should be terse and actionable (~800-1500 tokens).
|
|
11053
|
+
* Enhanced format includes SITUATION, priorities, EXHAUSTED, and SEARCH ORDERS. */
|
|
11054
|
+
MAX_OUTPUT_CHARS: 5e3,
|
|
11055
|
+
/** Maximum lines in the directive output.
|
|
11056
|
+
* WHY: Forces concise, prioritized directives while allowing
|
|
11057
|
+
* structured format (priorities + exhausted + search orders). */
|
|
11058
|
+
MAX_DIRECTIVE_LINES: 60
|
|
11059
|
+
};
|
|
11060
|
+
|
|
11061
|
+
// src/agents/strategist.ts
|
|
11062
|
+
var __dirname5 = dirname6(fileURLToPath5(import.meta.url));
|
|
11063
|
+
var STRATEGIST_PROMPT_PATH = join11(__dirname5, "prompts", "strategist-system.md");
|
|
11064
|
+
var Strategist = class {
|
|
11065
|
+
llm;
|
|
11066
|
+
state;
|
|
11067
|
+
systemPrompt;
|
|
11068
|
+
// Metrics
|
|
11069
|
+
totalTokenCost = 0;
|
|
11070
|
+
totalCalls = 0;
|
|
11071
|
+
lastDirective = null;
|
|
11072
|
+
constructor(llm, state) {
|
|
11073
|
+
this.llm = llm;
|
|
11074
|
+
this.state = state;
|
|
11075
|
+
this.systemPrompt = this.loadSystemPrompt();
|
|
11076
|
+
}
|
|
11077
|
+
/**
|
|
11078
|
+
* Generate a fresh strategic directive for this turn.
|
|
11079
|
+
* Called every iteration by PromptBuilder.
|
|
11080
|
+
*
|
|
11081
|
+
* @returns Formatted directive string for prompt injection, or '' on failure
|
|
11082
|
+
*/
|
|
11083
|
+
async generateDirective() {
|
|
11084
|
+
try {
|
|
11085
|
+
const input = this.buildInput();
|
|
11086
|
+
const directive = await this.callLLM(input);
|
|
11087
|
+
this.lastDirective = directive;
|
|
11088
|
+
this.totalCalls++;
|
|
11089
|
+
debugLog("general", "Strategist directive generated", {
|
|
11090
|
+
tokens: directive.tokenCost,
|
|
11091
|
+
totalCalls: this.totalCalls,
|
|
11092
|
+
contentLength: directive.content.length
|
|
11093
|
+
});
|
|
11094
|
+
return this.formatForPrompt(directive);
|
|
11095
|
+
} catch (err) {
|
|
11096
|
+
debugLog("general", "Strategist failed \u2014 agent will proceed without directive", {
|
|
11097
|
+
error: String(err)
|
|
11098
|
+
});
|
|
11099
|
+
if (this.lastDirective?.content) {
|
|
11100
|
+
return this.formatForPrompt(this.lastDirective, true);
|
|
11101
|
+
}
|
|
11102
|
+
return "";
|
|
11103
|
+
}
|
|
11104
|
+
}
|
|
11105
|
+
// ─── Input Construction ─────────────────────────────────────
|
|
11106
|
+
/**
|
|
11107
|
+
* Build the user message for the Strategist LLM.
|
|
11108
|
+
* Assembles state, memory, attack graph, and timeline into a focused context.
|
|
11109
|
+
*/
|
|
11110
|
+
buildInput() {
|
|
11111
|
+
const sections = [];
|
|
11112
|
+
sections.push("## Engagement State");
|
|
11113
|
+
sections.push(this.state.toPrompt());
|
|
11114
|
+
const timeline = this.state.episodicMemory.toPrompt();
|
|
11115
|
+
if (timeline) {
|
|
11116
|
+
sections.push("");
|
|
11117
|
+
sections.push("## Recent Actions");
|
|
11118
|
+
sections.push(timeline);
|
|
11119
|
+
}
|
|
11120
|
+
const failures = this.state.workingMemory.toPrompt();
|
|
11121
|
+
if (failures) {
|
|
11122
|
+
sections.push("");
|
|
11123
|
+
sections.push("## Failed Attempts (DO NOT REPEAT THESE)");
|
|
11124
|
+
sections.push(failures);
|
|
11125
|
+
}
|
|
11126
|
+
const graph = this.state.attackGraph.toPrompt();
|
|
11127
|
+
if (graph) {
|
|
11128
|
+
sections.push("");
|
|
11129
|
+
sections.push("## Attack Graph");
|
|
11130
|
+
sections.push(graph);
|
|
11131
|
+
}
|
|
11132
|
+
const techniques = this.state.dynamicTechniques.toPrompt();
|
|
11133
|
+
if (techniques) {
|
|
11134
|
+
sections.push("");
|
|
11135
|
+
sections.push("## Learned Techniques");
|
|
11136
|
+
sections.push(techniques);
|
|
11137
|
+
}
|
|
11138
|
+
sections.push("");
|
|
11139
|
+
sections.push("## Time");
|
|
11140
|
+
sections.push(this.state.getTimeStatus());
|
|
11141
|
+
const analysis = this.state.getChallengeAnalysis();
|
|
11142
|
+
if (analysis && analysis.primaryType !== "unknown") {
|
|
11143
|
+
sections.push("");
|
|
11144
|
+
sections.push(`## Challenge Type: ${analysis.primaryType.toUpperCase()} (${(analysis.confidence * 100).toFixed(0)}%)`);
|
|
11145
|
+
sections.push(analysis.strategySuggestion);
|
|
11146
|
+
}
|
|
11147
|
+
let input = sections.join("\n");
|
|
11148
|
+
if (input.length > STRATEGIST_LIMITS.MAX_INPUT_CHARS) {
|
|
11149
|
+
input = input.slice(0, STRATEGIST_LIMITS.MAX_INPUT_CHARS) + "\n\n... [state truncated for Strategist context]";
|
|
11150
|
+
}
|
|
11151
|
+
return input;
|
|
11152
|
+
}
|
|
11153
|
+
// ─── LLM Call ───────────────────────────────────────────────
|
|
11154
|
+
async callLLM(input) {
|
|
11155
|
+
const messages = [{
|
|
11156
|
+
role: "user",
|
|
11157
|
+
content: `Analyze this penetration test situation and write a tactical directive for the attack agent.
|
|
11158
|
+
|
|
11159
|
+
${input}`
|
|
11160
|
+
}];
|
|
11161
|
+
const response = await this.llm.generateResponse(
|
|
11162
|
+
messages,
|
|
11163
|
+
void 0,
|
|
11164
|
+
// No tools — strategy generation only
|
|
11165
|
+
this.systemPrompt
|
|
11166
|
+
);
|
|
11167
|
+
let content = response.content || "";
|
|
11168
|
+
if (content.length > STRATEGIST_LIMITS.MAX_OUTPUT_CHARS) {
|
|
11169
|
+
content = content.slice(0, STRATEGIST_LIMITS.MAX_OUTPUT_CHARS) + "\n... [directive truncated]";
|
|
11170
|
+
}
|
|
11171
|
+
const cost = response.usage ? response.usage.input_tokens + response.usage.output_tokens : 0;
|
|
11172
|
+
this.totalTokenCost += cost;
|
|
11173
|
+
return {
|
|
11174
|
+
content,
|
|
11175
|
+
generatedAt: Date.now(),
|
|
11176
|
+
tokenCost: cost
|
|
11177
|
+
};
|
|
11178
|
+
}
|
|
11179
|
+
// ─── Formatting ─────────────────────────────────────────────
|
|
11180
|
+
/**
|
|
11181
|
+
* Format directive for injection into the attack agent's system prompt.
|
|
11182
|
+
*/
|
|
11183
|
+
formatForPrompt(directive, isStale = false) {
|
|
11184
|
+
if (!directive.content) return "";
|
|
11185
|
+
const age = Math.floor((Date.now() - directive.generatedAt) / 6e4);
|
|
11186
|
+
const staleWarning = isStale ? `
|
|
11187
|
+
NOTE: This directive is from ${age}min ago (Strategist call failed this turn). Verify assumptions are still valid.` : "";
|
|
11188
|
+
return [
|
|
11189
|
+
"<strategic-directive>",
|
|
11190
|
+
"TACTICAL DIRECTIVE (generated by Strategist LLM \u2014 follow these priorities):",
|
|
11191
|
+
"",
|
|
11192
|
+
directive.content,
|
|
11193
|
+
staleWarning,
|
|
11194
|
+
"</strategic-directive>"
|
|
11195
|
+
].filter(Boolean).join("\n");
|
|
11196
|
+
}
|
|
11197
|
+
// ─── System Prompt Loading ──────────────────────────────────
|
|
11198
|
+
loadSystemPrompt() {
|
|
11199
|
+
try {
|
|
11200
|
+
if (existsSync9(STRATEGIST_PROMPT_PATH)) {
|
|
11201
|
+
return readFileSync6(STRATEGIST_PROMPT_PATH, "utf-8");
|
|
11202
|
+
}
|
|
11203
|
+
} catch {
|
|
11204
|
+
}
|
|
11205
|
+
return FALLBACK_SYSTEM_PROMPT;
|
|
11206
|
+
}
|
|
11207
|
+
// ─── Public API ─────────────────────────────────────────────
|
|
11208
|
+
/** Get total token cost of all Strategist calls this session. */
|
|
11209
|
+
getTotalTokenCost() {
|
|
11210
|
+
return this.totalTokenCost;
|
|
11211
|
+
}
|
|
11212
|
+
/** Get number of Strategist calls this session. */
|
|
11213
|
+
getTotalCalls() {
|
|
11214
|
+
return this.totalCalls;
|
|
11215
|
+
}
|
|
11216
|
+
/** Get the most recent directive (for TUI display). */
|
|
11217
|
+
getLastDirective() {
|
|
11218
|
+
return this.lastDirective;
|
|
11219
|
+
}
|
|
11220
|
+
/** Reset strategist state (for /clear command). */
|
|
11221
|
+
reset() {
|
|
11222
|
+
this.lastDirective = null;
|
|
11223
|
+
this.totalTokenCost = 0;
|
|
11224
|
+
this.totalCalls = 0;
|
|
11225
|
+
}
|
|
10927
11226
|
};
|
|
11227
|
+
var FALLBACK_SYSTEM_PROMPT = `You are an elite autonomous penetration testing STRATEGIST \u2014 a red team tactical commander.
|
|
11228
|
+
Analyze the engagement state and issue precise attack orders for the execution agent.
|
|
11229
|
+
Format: SITUATION line, numbered PRIORITY items with ACTION/SEARCH/SUCCESS/FALLBACK/CHAIN fields, EXHAUSTED list, and SEARCH ORDERS.
|
|
11230
|
+
Be surgically specific: name exact tools, commands, parameters, and wordlists. The agent copy-pastes your commands.
|
|
11231
|
+
Include mandatory web_search directives for every unknown service/version.
|
|
11232
|
+
Detect stalls (repeated failures, no progress) and force completely different attack vectors.
|
|
11233
|
+
Chain every finding: "If X works \u2192 immediately do Y \u2192 which enables Z."
|
|
11234
|
+
Maximum 50 lines. Zero preamble. Direct imperatives only. Never repeat failed approaches.`;
|
|
10928
11235
|
|
|
10929
11236
|
// src/agents/main-agent.ts
|
|
10930
11237
|
var MainAgent = class extends CoreAgent {
|
|
10931
11238
|
promptBuilder;
|
|
11239
|
+
strategist;
|
|
10932
11240
|
approvalGate;
|
|
10933
11241
|
scopeGuard;
|
|
10934
11242
|
userInput = "";
|
|
@@ -10937,6 +11245,8 @@ var MainAgent = class extends CoreAgent {
|
|
|
10937
11245
|
this.approvalGate = approvalGate;
|
|
10938
11246
|
this.scopeGuard = scopeGuard;
|
|
10939
11247
|
this.promptBuilder = new PromptBuilder(state);
|
|
11248
|
+
this.strategist = new Strategist(this.llm, state);
|
|
11249
|
+
this.promptBuilder.setStrategist(this.strategist);
|
|
10940
11250
|
}
|
|
10941
11251
|
/**
|
|
10942
11252
|
* Public entry point for running the agent.
|
|
@@ -10947,7 +11257,8 @@ var MainAgent = class extends CoreAgent {
|
|
|
10947
11257
|
this.emitStart(userInput);
|
|
10948
11258
|
this.initializeTask();
|
|
10949
11259
|
try {
|
|
10950
|
-
const
|
|
11260
|
+
const initialPrompt = await this.getCurrentPrompt();
|
|
11261
|
+
const result2 = await this.run(userInput, initialPrompt);
|
|
10951
11262
|
return result2.output;
|
|
10952
11263
|
} finally {
|
|
10953
11264
|
try {
|
|
@@ -10963,9 +11274,10 @@ var MainAgent = class extends CoreAgent {
|
|
|
10963
11274
|
/**
|
|
10964
11275
|
* Override step to rebuild prompt dynamically each iteration.
|
|
10965
11276
|
* This ensures the agent always sees the latest state, phase, and active processes.
|
|
11277
|
+
* The Strategist LLM generates a fresh tactical directive every turn.
|
|
10966
11278
|
*/
|
|
10967
11279
|
async step(iteration, messages, _unusedPrompt, progress) {
|
|
10968
|
-
const dynamicPrompt = this.getCurrentPrompt();
|
|
11280
|
+
const dynamicPrompt = await this.getCurrentPrompt();
|
|
10969
11281
|
const result2 = await super.step(iteration, messages, dynamicPrompt, progress);
|
|
10970
11282
|
this.emitStateChange();
|
|
10971
11283
|
return result2;
|
|
@@ -10994,7 +11306,7 @@ var MainAgent = class extends CoreAgent {
|
|
|
10994
11306
|
saveCurrentState() {
|
|
10995
11307
|
return saveState(this.state);
|
|
10996
11308
|
}
|
|
10997
|
-
getCurrentPrompt() {
|
|
11309
|
+
async getCurrentPrompt() {
|
|
10998
11310
|
const phase = this.state.getPhase() || PHASES.RECON;
|
|
10999
11311
|
return this.promptBuilder.build(this.userInput, phase);
|
|
11000
11312
|
}
|
|
@@ -11048,6 +11360,7 @@ var MainAgent = class extends CoreAgent {
|
|
|
11048
11360
|
});
|
|
11049
11361
|
this.state.reset();
|
|
11050
11362
|
this.userInput = "";
|
|
11363
|
+
this.strategist.reset();
|
|
11051
11364
|
return clearWorkspace();
|
|
11052
11365
|
}
|
|
11053
11366
|
setScope(allowed, exclusions = []) {
|
|
@@ -11069,6 +11382,10 @@ var MainAgent = class extends CoreAgent {
|
|
|
11069
11382
|
});
|
|
11070
11383
|
this.emitStateChange();
|
|
11071
11384
|
}
|
|
11385
|
+
/** Get the Strategist instance (for TUI metrics display). */
|
|
11386
|
+
getStrategist() {
|
|
11387
|
+
return this.strategist;
|
|
11388
|
+
}
|
|
11072
11389
|
};
|
|
11073
11390
|
|
|
11074
11391
|
// src/agents/factory.ts
|
|
@@ -11095,7 +11412,7 @@ var AgentFactory = class {
|
|
|
11095
11412
|
};
|
|
11096
11413
|
|
|
11097
11414
|
// src/platform/tui/utils/format.ts
|
|
11098
|
-
var
|
|
11415
|
+
var formatDuration2 = (ms) => {
|
|
11099
11416
|
const totalSec = ms / 1e3;
|
|
11100
11417
|
if (totalSec < 60) return `${totalSec.toFixed(1)}s`;
|
|
11101
11418
|
const minutes = Math.floor(totalSec / 60);
|
|
@@ -11109,7 +11426,7 @@ var formatTokens = (count) => {
|
|
|
11109
11426
|
};
|
|
11110
11427
|
var formatMeta = (ms, tokens) => {
|
|
11111
11428
|
const parts = [];
|
|
11112
|
-
if (ms > 0) parts.push(
|
|
11429
|
+
if (ms > 0) parts.push(formatDuration2(ms));
|
|
11113
11430
|
if (tokens > 0) parts.push(`\u2191 ${formatTokens(tokens)} tokens`);
|
|
11114
11431
|
return parts.length > 0 ? `(${parts.join(" \xB7 ")})` : "";
|
|
11115
11432
|
};
|
|
@@ -11143,137 +11460,59 @@ var formatInlineStatus = () => {
|
|
|
11143
11460
|
import { useState, useRef, useCallback } from "react";
|
|
11144
11461
|
|
|
11145
11462
|
// src/shared/constants/theme.ts
|
|
11463
|
+
var COLORS = {
|
|
11464
|
+
primary: "#e11d48",
|
|
11465
|
+
// Rose 600 - red team primary
|
|
11466
|
+
accent: "#fb923c",
|
|
11467
|
+
// Orange 400 - fire accent
|
|
11468
|
+
secondary: "#94a3b8",
|
|
11469
|
+
// Slate - secondary/muted
|
|
11470
|
+
white: "#f8fafc",
|
|
11471
|
+
// Main text
|
|
11472
|
+
red: "#ff4500",
|
|
11473
|
+
// OrangeRed - Error/Critical (distinct from rose primary)
|
|
11474
|
+
yellow: "#facc15"
|
|
11475
|
+
// Yellow 400 - Warning/Pending (pure yellow)
|
|
11476
|
+
};
|
|
11146
11477
|
var THEME = {
|
|
11147
|
-
|
|
11478
|
+
...COLORS,
|
|
11148
11479
|
bg: {
|
|
11149
11480
|
primary: "#050505",
|
|
11150
|
-
// Deepest black
|
|
11151
|
-
secondary: "#0a0c10",
|
|
11152
|
-
// Dark void
|
|
11153
|
-
tertiary: "#0f172a",
|
|
11154
|
-
// Slate dark
|
|
11155
|
-
elevated: "#1e293b",
|
|
11156
|
-
// Bright slate
|
|
11157
11481
|
input: "#020617"
|
|
11158
|
-
// Midnight
|
|
11159
11482
|
},
|
|
11160
|
-
// Text colors
|
|
11161
11483
|
text: {
|
|
11162
|
-
primary:
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
// Lighter blue-gray (was too dark)
|
|
11168
|
-
accent: "#38bdf8",
|
|
11169
|
-
// Sky blue
|
|
11170
|
-
highlight: "#ffffff"
|
|
11171
|
-
// Pure white
|
|
11484
|
+
primary: COLORS.white,
|
|
11485
|
+
secondary: COLORS.secondary,
|
|
11486
|
+
muted: "#64748b",
|
|
11487
|
+
accent: COLORS.accent
|
|
11488
|
+
// Using Emerald for "accented" text
|
|
11172
11489
|
},
|
|
11173
|
-
// Status colors
|
|
11174
11490
|
status: {
|
|
11175
|
-
success:
|
|
11176
|
-
//
|
|
11177
|
-
warning:
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
//
|
|
11181
|
-
info: "#3b82f6",
|
|
11182
|
-
// Blue 500 (distinct from user sky)
|
|
11183
|
-
running: "#60a5fa",
|
|
11184
|
-
// Blue 400 (AI activity — distinct from user sky)
|
|
11185
|
-
pending: "#64748b"
|
|
11186
|
-
// Slate
|
|
11491
|
+
success: COLORS.accent,
|
|
11492
|
+
// Success feels good in emerald
|
|
11493
|
+
warning: COLORS.yellow,
|
|
11494
|
+
error: COLORS.red,
|
|
11495
|
+
running: COLORS.primary
|
|
11496
|
+
// System operations in blue
|
|
11187
11497
|
},
|
|
11188
|
-
// Severity colors
|
|
11189
|
-
semantic: {
|
|
11190
|
-
critical: "#7f1d1d",
|
|
11191
|
-
// Dark red
|
|
11192
|
-
high: "#ef4444",
|
|
11193
|
-
// Red
|
|
11194
|
-
medium: "#f97316",
|
|
11195
|
-
// Orange
|
|
11196
|
-
low: "#eab308",
|
|
11197
|
-
// Yellow
|
|
11198
|
-
info: "#3b82f6"
|
|
11199
|
-
// Blue
|
|
11200
|
-
},
|
|
11201
|
-
// Border colors
|
|
11202
11498
|
border: {
|
|
11203
11499
|
default: "#1e293b",
|
|
11204
|
-
focus:
|
|
11205
|
-
|
|
11206
|
-
error: "#ef4444",
|
|
11207
|
-
success: "#22c55e"
|
|
11208
|
-
},
|
|
11209
|
-
// Phase colors
|
|
11210
|
-
phase: {
|
|
11211
|
-
recon: "#94a3b8",
|
|
11212
|
-
enum: "#60a5fa",
|
|
11213
|
-
// Blue 400 (phase indicator)
|
|
11214
|
-
vuln: "#f59e0b",
|
|
11215
|
-
exploit: "#ef4444",
|
|
11216
|
-
privesc: "#8b5cf6",
|
|
11217
|
-
persist: "#22c55e",
|
|
11218
|
-
report: "#64748b"
|
|
11219
|
-
},
|
|
11220
|
-
// Accent colors
|
|
11221
|
-
accent: {
|
|
11222
|
-
pink: "#f472b6",
|
|
11223
|
-
rose: "#fb7185",
|
|
11224
|
-
fuchsia: "#e879f9",
|
|
11225
|
-
purple: "#a78bfa",
|
|
11226
|
-
violet: "#8b5cf6",
|
|
11227
|
-
indigo: "#818cf8",
|
|
11228
|
-
blue: "#60a5fa",
|
|
11229
|
-
cyan: "#22d3ee",
|
|
11230
|
-
teal: "#2dd4bf",
|
|
11231
|
-
emerald: "#34d399",
|
|
11232
|
-
green: "#4ade80",
|
|
11233
|
-
lime: "#a3e635",
|
|
11234
|
-
yellow: "#facc15",
|
|
11235
|
-
amber: "#fbbf24",
|
|
11236
|
-
orange: "#fb923c",
|
|
11237
|
-
red: "#f87171"
|
|
11500
|
+
focus: COLORS.primary,
|
|
11501
|
+
error: COLORS.red
|
|
11238
11502
|
},
|
|
11239
|
-
// Gradients
|
|
11240
11503
|
gradient: {
|
|
11241
|
-
cyber: [
|
|
11242
|
-
|
|
11243
|
-
// Sky 400
|
|
11244
|
-
"#34c6f4",
|
|
11245
|
-
"#30d0f0",
|
|
11246
|
-
"#2cd9ec",
|
|
11247
|
-
"#28e3e8",
|
|
11248
|
-
"#22d3ee",
|
|
11249
|
-
// Cyan 400
|
|
11250
|
-
"#25dbd6",
|
|
11251
|
-
"#28e4be",
|
|
11252
|
-
"#2dd4bf",
|
|
11253
|
-
// Teal 400
|
|
11254
|
-
"#31dbac",
|
|
11255
|
-
"#34d399"
|
|
11256
|
-
// Emerald 400
|
|
11257
|
-
],
|
|
11258
|
-
danger: ["#ef4444", "#7f1d1d"],
|
|
11259
|
-
success: ["#22c55e", "#14532d"],
|
|
11260
|
-
gold: ["#f59e0b", "#78350f"],
|
|
11261
|
-
royal: ["#818cf8", "#312e81"]
|
|
11504
|
+
cyber: [COLORS.primary, "#f43f5e", "#fb923c"]
|
|
11505
|
+
// Red team fire gradient: rose → pink → amber
|
|
11262
11506
|
},
|
|
11263
|
-
|
|
11264
|
-
spinner: "#7dd3fc",
|
|
11265
|
-
// Sky 300
|
|
11266
|
-
// Identity color (branded accent)
|
|
11267
|
-
identity: "#60a5fa"
|
|
11268
|
-
// Blue 400
|
|
11507
|
+
spinner: COLORS.primary
|
|
11269
11508
|
};
|
|
11270
11509
|
var ASCII_BANNER = `
|
|
11271
|
-
|
|
11272
|
-
|
|
11273
|
-
|
|
11274
|
-
|
|
11275
|
-
|
|
11276
|
-
|
|
11510
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557
|
|
11511
|
+
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D
|
|
11512
|
+
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2557
|
|
11513
|
+
\u2588\u2588\u2554\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551
|
|
11514
|
+
\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
|
|
11515
|
+
\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D
|
|
11277
11516
|
`;
|
|
11278
11517
|
var ICONS = {
|
|
11279
11518
|
// Status
|
|
@@ -11287,7 +11526,7 @@ var ICONS = {
|
|
|
11287
11526
|
target: "\u25C8",
|
|
11288
11527
|
// Diamond for target
|
|
11289
11528
|
scan: "\u25CE",
|
|
11290
|
-
exploit: "\
|
|
11529
|
+
exploit: "\u2607",
|
|
11291
11530
|
shell: "\u276F",
|
|
11292
11531
|
// Progress
|
|
11293
11532
|
pending: "\u25CB",
|
|
@@ -11299,9 +11538,9 @@ var ICONS = {
|
|
|
11299
11538
|
medium: "\u25C7",
|
|
11300
11539
|
low: "\u25E6",
|
|
11301
11540
|
// Security
|
|
11302
|
-
lock: "\
|
|
11303
|
-
unlock: "\
|
|
11304
|
-
key: "\
|
|
11541
|
+
lock: "\u22A1",
|
|
11542
|
+
unlock: "\u2B1A",
|
|
11543
|
+
key: "\u26B7",
|
|
11305
11544
|
flag: "\u2691",
|
|
11306
11545
|
// Simple flag
|
|
11307
11546
|
pwned: "\u25C8"
|
|
@@ -11311,39 +11550,39 @@ var ICONS = {
|
|
|
11311
11550
|
// src/platform/tui/constants/display.ts
|
|
11312
11551
|
var TUI_DISPLAY_LIMITS = {
|
|
11313
11552
|
/** Reasoning buffer size for display */
|
|
11314
|
-
reasoningBuffer:
|
|
11553
|
+
reasoningBuffer: 2e3,
|
|
11315
11554
|
/** Reasoning preview length */
|
|
11316
|
-
reasoningPreview:
|
|
11555
|
+
reasoningPreview: 500,
|
|
11317
11556
|
/** Reasoning history slice for display */
|
|
11318
|
-
reasoningHistorySlice:
|
|
11557
|
+
reasoningHistorySlice: 1e3,
|
|
11319
11558
|
/** Tool input preview length (for command display) */
|
|
11320
|
-
toolInputPreview:
|
|
11559
|
+
toolInputPreview: 200,
|
|
11321
11560
|
/** Tool input truncated length */
|
|
11322
|
-
toolInputTruncated:
|
|
11323
|
-
/** Tool output preview length */
|
|
11324
|
-
toolOutputPreview:
|
|
11561
|
+
toolInputTruncated: 197,
|
|
11562
|
+
/** Tool output preview length - increased to show full output */
|
|
11563
|
+
toolOutputPreview: 2e3,
|
|
11325
11564
|
/** Error message preview length */
|
|
11326
|
-
errorPreview:
|
|
11565
|
+
errorPreview: 500,
|
|
11327
11566
|
/** Status thought preview length */
|
|
11328
|
-
statusThoughtPreview:
|
|
11567
|
+
statusThoughtPreview: 150,
|
|
11329
11568
|
/** Status thought truncated length */
|
|
11330
|
-
statusThoughtTruncated:
|
|
11569
|
+
statusThoughtTruncated: 147,
|
|
11331
11570
|
/** Retry error preview length */
|
|
11332
|
-
retryErrorPreview:
|
|
11571
|
+
retryErrorPreview: 100,
|
|
11333
11572
|
/** Retry error truncated length */
|
|
11334
|
-
retryErrorTruncated:
|
|
11573
|
+
retryErrorTruncated: 97,
|
|
11335
11574
|
/** Timer update interval in ms */
|
|
11336
11575
|
timerInterval: 1e3,
|
|
11337
11576
|
/** Exit delay in ms */
|
|
11338
11577
|
exitDelay: 100,
|
|
11339
11578
|
/** Purpose text max length before truncation */
|
|
11340
|
-
purposeMaxLength:
|
|
11579
|
+
purposeMaxLength: 50,
|
|
11341
11580
|
/** Purpose text truncated length */
|
|
11342
|
-
purposeTruncated:
|
|
11581
|
+
purposeTruncated: 47,
|
|
11343
11582
|
/** Tool detail preview length in flow display */
|
|
11344
|
-
toolDetailPreview:
|
|
11583
|
+
toolDetailPreview: 200,
|
|
11345
11584
|
/** Observe detail preview length in flow display */
|
|
11346
|
-
observeDetailPreview:
|
|
11585
|
+
observeDetailPreview: 150,
|
|
11347
11586
|
/** Max flow nodes to display */
|
|
11348
11587
|
maxFlowNodes: 50,
|
|
11349
11588
|
/** Max stopped processes to show */
|
|
@@ -11358,7 +11597,7 @@ var MESSAGE_STYLES = {
|
|
|
11358
11597
|
error: THEME.status.error,
|
|
11359
11598
|
tool: THEME.status.running,
|
|
11360
11599
|
result: THEME.text.muted,
|
|
11361
|
-
status: THEME.
|
|
11600
|
+
status: THEME.primary
|
|
11362
11601
|
},
|
|
11363
11602
|
prefixes: {
|
|
11364
11603
|
user: "\u276F",
|
|
@@ -11419,21 +11658,9 @@ var useAgentState = () => {
|
|
|
11419
11658
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
11420
11659
|
const [currentStatus, setCurrentStatus] = useState("");
|
|
11421
11660
|
const [elapsedTime, setElapsedTime] = useState(0);
|
|
11422
|
-
const [retryState, setRetryState] = useState({
|
|
11423
|
-
isRetrying: false,
|
|
11424
|
-
attempt: 0,
|
|
11425
|
-
maxRetries: 0,
|
|
11426
|
-
delayMs: 0,
|
|
11427
|
-
error: "",
|
|
11428
|
-
countdown: 0
|
|
11429
|
-
});
|
|
11661
|
+
const [retryState, setRetryState] = useState({ status: "idle" });
|
|
11430
11662
|
const [currentTokens, setCurrentTokens] = useState(0);
|
|
11431
|
-
const [inputRequest, setInputRequest] = useState({
|
|
11432
|
-
isActive: false,
|
|
11433
|
-
prompt: "",
|
|
11434
|
-
isPassword: false,
|
|
11435
|
-
resolve: null
|
|
11436
|
-
});
|
|
11663
|
+
const [inputRequest, setInputRequest] = useState({ status: "inactive" });
|
|
11437
11664
|
const [stats, setStats] = useState({ phase: DEFAULTS.INIT_PHASE, targets: 0, findings: 0, todo: 0 });
|
|
11438
11665
|
const lastResponseMetaRef = useRef(null);
|
|
11439
11666
|
const startTimeRef = useRef(0);
|
|
@@ -11557,10 +11784,10 @@ var useAgentEvents = (agent, eventsRef, state) => {
|
|
|
11557
11784
|
setCurrentTokens(tokenAccumRef.current + stepTokens);
|
|
11558
11785
|
};
|
|
11559
11786
|
const onFlagFound = (e) => {
|
|
11560
|
-
addMessage("system",
|
|
11787
|
+
addMessage("system", `[FLAG] ${e.data.flag} (total: ${e.data.totalFlags})`);
|
|
11561
11788
|
};
|
|
11562
11789
|
const onPhaseChange = (e) => {
|
|
11563
|
-
addMessage("system",
|
|
11790
|
+
addMessage("system", `[Phase] ${e.data.fromPhase} -> ${e.data.toPhase} (${e.data.reason})`);
|
|
11564
11791
|
const s = agent.getState();
|
|
11565
11792
|
setStats({
|
|
11566
11793
|
phase: e.data.toPhase,
|
|
@@ -11574,7 +11801,7 @@ var useAgentEvents = (agent, eventsRef, state) => {
|
|
|
11574
11801
|
const isPassword = /password|passphrase/i.test(p);
|
|
11575
11802
|
const inputType = /sudo/i.test(p) ? "sudo_password" : isPassword ? "password" : "text";
|
|
11576
11803
|
setInputRequest({
|
|
11577
|
-
|
|
11804
|
+
status: "active",
|
|
11578
11805
|
prompt: p.trim(),
|
|
11579
11806
|
isPassword,
|
|
11580
11807
|
inputType,
|
|
@@ -11588,7 +11815,7 @@ var useAgentEvents = (agent, eventsRef, state) => {
|
|
|
11588
11815
|
const isPassword = hiddenTypes.includes(request.type);
|
|
11589
11816
|
const displayPrompt = buildCredentialPrompt(request);
|
|
11590
11817
|
setInputRequest({
|
|
11591
|
-
|
|
11818
|
+
status: "active",
|
|
11592
11819
|
prompt: displayPrompt,
|
|
11593
11820
|
isPassword,
|
|
11594
11821
|
inputType: request.type,
|
|
@@ -11669,7 +11896,7 @@ function handleRetry(e, addMessage, setRetryState, retryCountdownRef, retryCount
|
|
|
11669
11896
|
const retryNum = retryCountRef.current;
|
|
11670
11897
|
addMessage("system", `\u27F3 Retry #${retryNum} \xB7 ${e.data.error} \xB7 waiting ${delaySec}s...`);
|
|
11671
11898
|
setRetryState({
|
|
11672
|
-
|
|
11899
|
+
status: "retrying",
|
|
11673
11900
|
attempt: retryNum,
|
|
11674
11901
|
maxRetries: e.data.maxRetries,
|
|
11675
11902
|
delayMs: e.data.delayMs,
|
|
@@ -11681,10 +11908,10 @@ function handleRetry(e, addMessage, setRetryState, retryCountdownRef, retryCount
|
|
|
11681
11908
|
retryCountdownRef.current = setInterval(() => {
|
|
11682
11909
|
remaining -= 1;
|
|
11683
11910
|
if (remaining <= 0) {
|
|
11684
|
-
setRetryState(
|
|
11911
|
+
setRetryState({ status: "idle" });
|
|
11685
11912
|
if (retryCountdownRef.current) clearInterval(retryCountdownRef.current);
|
|
11686
11913
|
} else {
|
|
11687
|
-
setRetryState((prev) =>
|
|
11914
|
+
setRetryState((prev) => prev.status === "retrying" ? { ...prev, countdown: remaining } : prev);
|
|
11688
11915
|
}
|
|
11689
11916
|
}, 1e3);
|
|
11690
11917
|
}
|
|
@@ -11705,18 +11932,18 @@ Options: ${request.options.join(", ")}`;
|
|
|
11705
11932
|
}
|
|
11706
11933
|
function getCommandEventIcon(eventType) {
|
|
11707
11934
|
const icons = {
|
|
11708
|
-
[COMMAND_EVENT_TYPES.TOOL_MISSING]: "
|
|
11709
|
-
[COMMAND_EVENT_TYPES.TOOL_INSTALL]: "
|
|
11710
|
-
[COMMAND_EVENT_TYPES.TOOL_INSTALLED]: "
|
|
11711
|
-
[COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED]: "
|
|
11712
|
-
[COMMAND_EVENT_TYPES.TOOL_RETRY]: "
|
|
11713
|
-
[COMMAND_EVENT_TYPES.COMMAND_START]: "
|
|
11714
|
-
[COMMAND_EVENT_TYPES.COMMAND_SUCCESS]: "
|
|
11715
|
-
[COMMAND_EVENT_TYPES.COMMAND_FAILED]: "
|
|
11716
|
-
[COMMAND_EVENT_TYPES.COMMAND_ERROR]: "
|
|
11717
|
-
[COMMAND_EVENT_TYPES.INPUT_REQUIRED]: "
|
|
11935
|
+
[COMMAND_EVENT_TYPES.TOOL_MISSING]: "[!]",
|
|
11936
|
+
[COMMAND_EVENT_TYPES.TOOL_INSTALL]: "[install]",
|
|
11937
|
+
[COMMAND_EVENT_TYPES.TOOL_INSTALLED]: "[ok]",
|
|
11938
|
+
[COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED]: "[fail]",
|
|
11939
|
+
[COMMAND_EVENT_TYPES.TOOL_RETRY]: "[retry]",
|
|
11940
|
+
[COMMAND_EVENT_TYPES.COMMAND_START]: "[>]",
|
|
11941
|
+
[COMMAND_EVENT_TYPES.COMMAND_SUCCESS]: "[ok]",
|
|
11942
|
+
[COMMAND_EVENT_TYPES.COMMAND_FAILED]: "[x]",
|
|
11943
|
+
[COMMAND_EVENT_TYPES.COMMAND_ERROR]: "[err]",
|
|
11944
|
+
[COMMAND_EVENT_TYPES.INPUT_REQUIRED]: "[auth]"
|
|
11718
11945
|
};
|
|
11719
|
-
return icons[eventType] || "
|
|
11946
|
+
return icons[eventType] || "[-]";
|
|
11720
11947
|
}
|
|
11721
11948
|
|
|
11722
11949
|
// src/platform/tui/hooks/useAgent.ts
|
|
@@ -11779,9 +12006,9 @@ var useAgent = (shouldAutoApprove, target) => {
|
|
|
11779
12006
|
inputRequestRef.current = inputRequest;
|
|
11780
12007
|
const cancelInputRequest = useCallback2(() => {
|
|
11781
12008
|
const ir = inputRequestRef.current;
|
|
11782
|
-
if (ir.
|
|
12009
|
+
if (ir.status === "active") {
|
|
11783
12010
|
ir.resolve(null);
|
|
11784
|
-
setInputRequest({
|
|
12011
|
+
setInputRequest({ status: "inactive" });
|
|
11785
12012
|
addMessage("system", "Input cancelled");
|
|
11786
12013
|
}
|
|
11787
12014
|
}, [setInputRequest, addMessage]);
|
|
@@ -11817,12 +12044,13 @@ var useAgent = (shouldAutoApprove, target) => {
|
|
|
11817
12044
|
};
|
|
11818
12045
|
|
|
11819
12046
|
// src/platform/tui/components/MessageList.tsx
|
|
12047
|
+
import { memo } from "react";
|
|
11820
12048
|
import { Box as Box2, Text as Text2, Static } from "ink";
|
|
11821
12049
|
|
|
11822
12050
|
// src/platform/tui/components/inline-status.tsx
|
|
11823
12051
|
import { Box, Text } from "ink";
|
|
11824
12052
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11825
|
-
function
|
|
12053
|
+
function formatDuration3(ms) {
|
|
11826
12054
|
const seconds = Math.floor(ms / 1e3);
|
|
11827
12055
|
if (seconds < 60) return `${seconds}s`;
|
|
11828
12056
|
const minutes = Math.floor(seconds / 60);
|
|
@@ -11834,13 +12062,13 @@ function formatDuration2(ms) {
|
|
|
11834
12062
|
}
|
|
11835
12063
|
function getRoleColor(role) {
|
|
11836
12064
|
const roleColors = {
|
|
11837
|
-
listener: THEME.
|
|
11838
|
-
active_shell: THEME.
|
|
11839
|
-
server: THEME.
|
|
11840
|
-
sniffer: THEME.
|
|
11841
|
-
spoofer: THEME.
|
|
11842
|
-
proxy: THEME.
|
|
11843
|
-
callback: THEME.
|
|
12065
|
+
listener: THEME.primary,
|
|
12066
|
+
active_shell: THEME.primary,
|
|
12067
|
+
server: THEME.secondary,
|
|
12068
|
+
sniffer: THEME.yellow,
|
|
12069
|
+
spoofer: THEME.yellow,
|
|
12070
|
+
proxy: THEME.primary,
|
|
12071
|
+
callback: THEME.primary,
|
|
11844
12072
|
background: THEME.text.muted
|
|
11845
12073
|
};
|
|
11846
12074
|
return roleColors[role] || THEME.text.secondary;
|
|
@@ -11864,7 +12092,7 @@ function StatusIndicator({ running, exitCode }) {
|
|
|
11864
12092
|
] });
|
|
11865
12093
|
}
|
|
11866
12094
|
function ProcessRow({ proc, compact }) {
|
|
11867
|
-
const duration =
|
|
12095
|
+
const duration = formatDuration3(proc.durationMs);
|
|
11868
12096
|
const port = proc.listeningPort ? `:${proc.listeningPort}` : "";
|
|
11869
12097
|
const purpose = proc.purpose || proc.description || "";
|
|
11870
12098
|
const truncatedPurpose = compact && purpose.length > TUI_DISPLAY_LIMITS.purposeMaxLength ? purpose.slice(0, TUI_DISPLAY_LIMITS.purposeTruncated) + "..." : purpose;
|
|
@@ -11979,7 +12207,7 @@ function parseStatusContent(content) {
|
|
|
11979
12207
|
}
|
|
11980
12208
|
return null;
|
|
11981
12209
|
}
|
|
11982
|
-
var MessageList = ({ messages }) => {
|
|
12210
|
+
var MessageList = memo(({ messages }) => {
|
|
11983
12211
|
return /* @__PURE__ */ jsx2(Static, { items: messages, children: (msg) => {
|
|
11984
12212
|
if (msg.type === "status") {
|
|
11985
12213
|
const statusData = parseStatusContent(msg.content);
|
|
@@ -12000,18 +12228,19 @@ var MessageList = ({ messages }) => {
|
|
|
12000
12228
|
msg.content
|
|
12001
12229
|
] }) }, msg.id);
|
|
12002
12230
|
} });
|
|
12003
|
-
};
|
|
12231
|
+
});
|
|
12004
12232
|
|
|
12005
12233
|
// src/platform/tui/components/StatusDisplay.tsx
|
|
12234
|
+
import { memo as memo3 } from "react";
|
|
12006
12235
|
import { Box as Box3, Text as Text4 } from "ink";
|
|
12007
12236
|
|
|
12008
12237
|
// src/platform/tui/components/MusicSpinner.tsx
|
|
12009
|
-
import { useState as useState3, useEffect as useEffect3 } from "react";
|
|
12238
|
+
import { useState as useState3, useEffect as useEffect3, memo as memo2 } from "react";
|
|
12010
12239
|
import { Text as Text3 } from "ink";
|
|
12011
12240
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
12012
12241
|
var FRAMES = ["\u2669", "\u266A", "\u266B", "\u266C", "\u266B", "\u266A"];
|
|
12013
|
-
var INTERVAL =
|
|
12014
|
-
var MusicSpinner = ({ color }) => {
|
|
12242
|
+
var INTERVAL = 600;
|
|
12243
|
+
var MusicSpinner = memo2(({ color }) => {
|
|
12015
12244
|
const [index, setIndex] = useState3(0);
|
|
12016
12245
|
useEffect3(() => {
|
|
12017
12246
|
const timer = setInterval(() => {
|
|
@@ -12020,11 +12249,11 @@ var MusicSpinner = ({ color }) => {
|
|
|
12020
12249
|
return () => clearInterval(timer);
|
|
12021
12250
|
}, []);
|
|
12022
12251
|
return /* @__PURE__ */ jsx3(Text3, { color, children: FRAMES[index] });
|
|
12023
|
-
};
|
|
12252
|
+
});
|
|
12024
12253
|
|
|
12025
12254
|
// src/platform/tui/components/StatusDisplay.tsx
|
|
12026
12255
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
12027
|
-
var StatusDisplay = ({
|
|
12256
|
+
var StatusDisplay = memo3(({
|
|
12028
12257
|
retryState,
|
|
12029
12258
|
isProcessing,
|
|
12030
12259
|
currentStatus,
|
|
@@ -12036,7 +12265,7 @@ var StatusDisplay = ({
|
|
|
12036
12265
|
};
|
|
12037
12266
|
const meta = formatMeta(elapsedTime * 1e3, currentTokens);
|
|
12038
12267
|
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 0, children: [
|
|
12039
|
-
retryState.
|
|
12268
|
+
retryState.status === "retrying" && /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
|
|
12040
12269
|
/* @__PURE__ */ jsx4(Text4, { color: THEME.status.warning, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: THEME.status.warning }) }),
|
|
12041
12270
|
/* @__PURE__ */ jsxs3(Text4, { color: THEME.status.warning, children: [
|
|
12042
12271
|
" \u27F3 Retry #",
|
|
@@ -12046,13 +12275,13 @@ var StatusDisplay = ({
|
|
|
12046
12275
|
" \xB7 ",
|
|
12047
12276
|
truncateError(retryState.error)
|
|
12048
12277
|
] }),
|
|
12049
|
-
/* @__PURE__ */ jsxs3(Text4, { color: THEME.
|
|
12278
|
+
/* @__PURE__ */ jsxs3(Text4, { color: THEME.primary, bold: true, children: [
|
|
12050
12279
|
" \xB7 ",
|
|
12051
12280
|
retryState.countdown,
|
|
12052
12281
|
"s"
|
|
12053
12282
|
] })
|
|
12054
12283
|
] }),
|
|
12055
|
-
isProcessing &&
|
|
12284
|
+
isProcessing && retryState.status !== "retrying" && /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
|
|
12056
12285
|
/* @__PURE__ */ jsx4(Text4, { color: THEME.spinner, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: THEME.spinner }) }),
|
|
12057
12286
|
/* @__PURE__ */ jsxs3(Text4, { color: THEME.text.muted, children: [
|
|
12058
12287
|
" ",
|
|
@@ -12064,15 +12293,15 @@ var StatusDisplay = ({
|
|
|
12064
12293
|
] })
|
|
12065
12294
|
] })
|
|
12066
12295
|
] });
|
|
12067
|
-
};
|
|
12296
|
+
});
|
|
12068
12297
|
|
|
12069
12298
|
// src/platform/tui/components/ChatInput.tsx
|
|
12070
|
-
import { useMemo, useCallback as useCallback3, useRef as useRef3 } from "react";
|
|
12299
|
+
import { useMemo, useCallback as useCallback3, useRef as useRef3, memo as memo4 } from "react";
|
|
12071
12300
|
import { Box as Box4, Text as Text5, useInput } from "ink";
|
|
12072
12301
|
import TextInput from "ink-text-input";
|
|
12073
12302
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
12074
12303
|
var MAX_SUGGESTIONS = 6;
|
|
12075
|
-
var ChatInput = ({
|
|
12304
|
+
var ChatInput = memo4(({
|
|
12076
12305
|
value,
|
|
12077
12306
|
onChange,
|
|
12078
12307
|
onSubmit,
|
|
@@ -12101,11 +12330,13 @@ var ChatInput = ({
|
|
|
12101
12330
|
const onChangeRef = useRef3(onChange);
|
|
12102
12331
|
onChangeRef.current = onChange;
|
|
12103
12332
|
useInput(useCallback3((_input, key) => {
|
|
12104
|
-
if (inputRequestRef.current.
|
|
12333
|
+
if (inputRequestRef.current.status === "active") return;
|
|
12105
12334
|
if (key.tab && isSlashModeRef.current && !hasArgsRef.current && suggestionsRef.current.length > 0) {
|
|
12106
12335
|
const best = suggestionsRef.current[0];
|
|
12107
12336
|
const argsHint = best.args ? " " : "";
|
|
12108
|
-
|
|
12337
|
+
const completed = `/${best.name}${argsHint}`;
|
|
12338
|
+
onChangeRef.current("");
|
|
12339
|
+
setTimeout(() => onChangeRef.current(completed), 0);
|
|
12109
12340
|
}
|
|
12110
12341
|
}, []));
|
|
12111
12342
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
@@ -12119,7 +12350,7 @@ var ChatInput = ({
|
|
|
12119
12350
|
marginBottom: 0,
|
|
12120
12351
|
children: suggestions.map((cmd, i) => {
|
|
12121
12352
|
const isFirst = i === 0;
|
|
12122
|
-
const nameColor = isFirst ? THEME.
|
|
12353
|
+
const nameColor = isFirst ? THEME.primary : THEME.text.secondary;
|
|
12123
12354
|
const aliasText = cmd.alias ? ` /${cmd.alias}` : "";
|
|
12124
12355
|
const argsText = cmd.args ? ` ${cmd.args}` : "";
|
|
12125
12356
|
return /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
@@ -12133,7 +12364,7 @@ var ChatInput = ({
|
|
|
12133
12364
|
" \u2014 ",
|
|
12134
12365
|
cmd.description
|
|
12135
12366
|
] }),
|
|
12136
|
-
isFirst && /* @__PURE__ */ jsx5(Text5, { color: THEME.
|
|
12367
|
+
isFirst && /* @__PURE__ */ jsx5(Text5, { color: THEME.primary, children: " [Tab]" })
|
|
12137
12368
|
] }, cmd.name);
|
|
12138
12369
|
})
|
|
12139
12370
|
}
|
|
@@ -12142,10 +12373,10 @@ var ChatInput = ({
|
|
|
12142
12373
|
Box4,
|
|
12143
12374
|
{
|
|
12144
12375
|
borderStyle: "single",
|
|
12145
|
-
borderColor: inputRequest.
|
|
12376
|
+
borderColor: inputRequest.status === "active" ? THEME.status.warning : THEME.border.default,
|
|
12146
12377
|
paddingX: 1,
|
|
12147
|
-
children: inputRequest.
|
|
12148
|
-
/* @__PURE__ */ jsx5(Text5, { color: THEME.status.warning, children: "
|
|
12378
|
+
children: inputRequest.status === "active" ? /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
12379
|
+
/* @__PURE__ */ jsx5(Text5, { color: THEME.status.warning, children: "[auth]" }),
|
|
12149
12380
|
/* @__PURE__ */ jsxs4(Text5, { color: THEME.text.muted, children: [
|
|
12150
12381
|
" ",
|
|
12151
12382
|
inputRequest.prompt
|
|
@@ -12176,9 +12407,10 @@ var ChatInput = ({
|
|
|
12176
12407
|
}
|
|
12177
12408
|
)
|
|
12178
12409
|
] });
|
|
12179
|
-
};
|
|
12410
|
+
});
|
|
12180
12411
|
|
|
12181
12412
|
// src/platform/tui/components/footer.tsx
|
|
12413
|
+
import { memo as memo5 } from "react";
|
|
12182
12414
|
import { Box as Box5, Text as Text6 } from "ink";
|
|
12183
12415
|
import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
12184
12416
|
var formatElapsed = (totalSeconds) => {
|
|
@@ -12191,7 +12423,7 @@ var formatElapsed = (totalSeconds) => {
|
|
|
12191
12423
|
}
|
|
12192
12424
|
return `${minutes}:${pad(seconds)}`;
|
|
12193
12425
|
};
|
|
12194
|
-
var Footer = ({ phase, targets, findings, todo, elapsedTime, isProcessing }) => {
|
|
12426
|
+
var Footer = memo5(({ phase, targets, findings, todo, elapsedTime, isProcessing }) => {
|
|
12195
12427
|
return /* @__PURE__ */ jsxs5(
|
|
12196
12428
|
Box5,
|
|
12197
12429
|
{
|
|
@@ -12202,7 +12434,7 @@ var Footer = ({ phase, targets, findings, todo, elapsedTime, isProcessing }) =>
|
|
|
12202
12434
|
/* @__PURE__ */ jsxs5(Box5, { gap: 2, children: [
|
|
12203
12435
|
/* @__PURE__ */ jsxs5(Text6, { color: THEME.text.muted, children: [
|
|
12204
12436
|
"Phase: ",
|
|
12205
|
-
/* @__PURE__ */ jsx6(Text6, { color: THEME.
|
|
12437
|
+
/* @__PURE__ */ jsx6(Text6, { color: THEME.primary, children: phase })
|
|
12206
12438
|
] }),
|
|
12207
12439
|
/* @__PURE__ */ jsxs5(Text6, { color: THEME.text.muted, children: [
|
|
12208
12440
|
"Targets: ",
|
|
@@ -12224,7 +12456,7 @@ var Footer = ({ phase, targets, findings, todo, elapsedTime, isProcessing }) =>
|
|
|
12224
12456
|
]
|
|
12225
12457
|
}
|
|
12226
12458
|
);
|
|
12227
|
-
};
|
|
12459
|
+
});
|
|
12228
12460
|
var footer_default = Footer;
|
|
12229
12461
|
|
|
12230
12462
|
// src/platform/tui/app.tsx
|
|
@@ -12260,7 +12492,7 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
12260
12492
|
inputRequestRef.current = inputRequest;
|
|
12261
12493
|
const handleExit = useCallback4(() => {
|
|
12262
12494
|
const ir = inputRequestRef.current;
|
|
12263
|
-
if (ir.
|
|
12495
|
+
if (ir.status === "active") ir.resolve(null);
|
|
12264
12496
|
cleanupAllProcesses().catch(() => {
|
|
12265
12497
|
});
|
|
12266
12498
|
exit();
|
|
@@ -12281,9 +12513,9 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
12281
12513
|
setMessages([]);
|
|
12282
12514
|
const result2 = await agent.resetSession();
|
|
12283
12515
|
if (result2.cleared.length > 0) {
|
|
12284
|
-
addMessage("system",
|
|
12516
|
+
addMessage("system", `[reset] Session cleared: ${result2.cleared.join(", ")}`);
|
|
12285
12517
|
} else {
|
|
12286
|
-
addMessage("system", "
|
|
12518
|
+
addMessage("system", "[reset] Session clean");
|
|
12287
12519
|
}
|
|
12288
12520
|
if (result2.errors.length > 0) {
|
|
12289
12521
|
addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
|
|
@@ -12310,7 +12542,7 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
12310
12542
|
if (!autoApproveModeRef.current) {
|
|
12311
12543
|
setAutoApproveMode(true);
|
|
12312
12544
|
agent.setAutoApprove(true);
|
|
12313
|
-
addMessage("system", "
|
|
12545
|
+
addMessage("system", "[auto] Autonomous mode enabled");
|
|
12314
12546
|
}
|
|
12315
12547
|
addMessage("system", "Starting penetration test...");
|
|
12316
12548
|
const targets = Array.from(agent.getState().getTargets().keys());
|
|
@@ -12324,8 +12556,29 @@ var App = ({ autoApprove = false, target }) => {
|
|
|
12324
12556
|
addMessage("system", "No findings.");
|
|
12325
12557
|
break;
|
|
12326
12558
|
}
|
|
12327
|
-
|
|
12328
|
-
findings.forEach((f) =>
|
|
12559
|
+
const findingLines = [`\u2500\u2500\u2500 ${findings.length} Findings \u2500\u2500\u2500`, ""];
|
|
12560
|
+
findings.forEach((f, i) => {
|
|
12561
|
+
const verified = f.isVerified ? `[${ICONS.success}] Verified` : `[${ICONS.warning}] Unverified`;
|
|
12562
|
+
const atk = f.attackPattern ? ` | ATT&CK: ${f.attackPattern}` : "";
|
|
12563
|
+
findingLines.push(`[${i + 1}] [${f.severity.toUpperCase()}] ${f.title}`);
|
|
12564
|
+
findingLines.push(` ${verified}${atk}`);
|
|
12565
|
+
if (f.affected.length > 0) {
|
|
12566
|
+
findingLines.push(` Affected: ${f.affected.join(", ")}`);
|
|
12567
|
+
}
|
|
12568
|
+
if (f.description) {
|
|
12569
|
+
findingLines.push(` ${f.description}`);
|
|
12570
|
+
}
|
|
12571
|
+
if (f.evidence.length > 0) {
|
|
12572
|
+
findingLines.push(` Evidence:`);
|
|
12573
|
+
f.evidence.slice(0, 3).forEach((e) => {
|
|
12574
|
+
const preview = e.length > 120 ? e.slice(0, 120) + "..." : e;
|
|
12575
|
+
findingLines.push(` \u25B8 ${preview}`);
|
|
12576
|
+
});
|
|
12577
|
+
if (f.evidence.length > 3) findingLines.push(` ... +${f.evidence.length - 3} more`);
|
|
12578
|
+
}
|
|
12579
|
+
findingLines.push("");
|
|
12580
|
+
});
|
|
12581
|
+
addMessage("system", findingLines.join("\n"));
|
|
12329
12582
|
break;
|
|
12330
12583
|
case UI_COMMANDS.ASSETS:
|
|
12331
12584
|
case UI_COMMANDS.ASSETS_SHORT:
|
|
@@ -12348,7 +12601,7 @@ ${procData.stdout || "(no output)"}
|
|
|
12348
12601
|
break;
|
|
12349
12602
|
case UI_COMMANDS.CTF:
|
|
12350
12603
|
const ctfEnabled = agent.toggleCtfMode();
|
|
12351
|
-
addMessage("system", ctfEnabled ? "
|
|
12604
|
+
addMessage("system", ctfEnabled ? "[CTF] Mode ON - flag detection active" : "[CTF] Mode OFF - standard pentest");
|
|
12352
12605
|
break;
|
|
12353
12606
|
case UI_COMMANDS.GRAPH:
|
|
12354
12607
|
case UI_COMMANDS.GRAPH_SHORT:
|
|
@@ -12389,17 +12642,17 @@ ${procData.stdout || "(no output)"}
|
|
|
12389
12642
|
}, [addMessage, executeTask, handleCommand]);
|
|
12390
12643
|
const handleSecretSubmit = useCallback4((value) => {
|
|
12391
12644
|
const ir = inputRequestRef.current;
|
|
12392
|
-
if (
|
|
12645
|
+
if (ir.status !== "active") return;
|
|
12393
12646
|
const displayText = ir.isPassword ? "\u2022".repeat(value.length) : value;
|
|
12394
12647
|
const promptLabel = ir.prompt || "Input";
|
|
12395
12648
|
addMessage("system", `\u21B3 ${promptLabel} ${displayText}`);
|
|
12396
12649
|
ir.resolve(value);
|
|
12397
|
-
setInputRequest({
|
|
12650
|
+
setInputRequest({ status: "inactive" });
|
|
12398
12651
|
setSecretInput("");
|
|
12399
12652
|
}, [addMessage, setInputRequest]);
|
|
12400
12653
|
useInput2(useCallback4((ch, key) => {
|
|
12401
12654
|
if (key.escape) {
|
|
12402
|
-
if (inputRequestRef.current.
|
|
12655
|
+
if (inputRequestRef.current.status === "active") cancelInputRequest();
|
|
12403
12656
|
else if (isProcessingRef.current) abort();
|
|
12404
12657
|
}
|
|
12405
12658
|
if (key.ctrl && ch === "c") handleExit();
|
|
@@ -12486,9 +12739,9 @@ program.command("interactive", { isDefault: true }).alias("i").description("Star
|
|
|
12486
12739
|
const opts = program.opts();
|
|
12487
12740
|
const skipPermissions = opts.dangerouslySkipPermissions || false;
|
|
12488
12741
|
console.clear();
|
|
12489
|
-
console.log(
|
|
12742
|
+
console.log(chalk.hex(THEME.primary)(ASCII_BANNER));
|
|
12490
12743
|
console.log(
|
|
12491
|
-
" " + chalk.hex(THEME.text.secondary)(`v${APP_VERSION}`) + chalk.hex(THEME.text.muted)(" \u2502 ") + chalk.hex(THEME.
|
|
12744
|
+
" " + chalk.hex(THEME.text.secondary)(`v${APP_VERSION}`) + chalk.hex(THEME.text.muted)(" \u2502 ") + chalk.hex(THEME.primary)("Type /help for commands") + "\n"
|
|
12492
12745
|
);
|
|
12493
12746
|
if (skipPermissions) {
|
|
12494
12747
|
console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions"));
|
|
@@ -12508,11 +12761,11 @@ program.command("interactive", { isDefault: true }).alias("i").description("Star
|
|
|
12508
12761
|
program.command("run <objective>").alias("r").description("Run a single objective and exit").option("-o, --output <file>", "Output file for results").option("--max-steps <n>", "Maximum number of steps", String(CLI_DEFAULT.MAX_STEPS)).action(async (objective, options) => {
|
|
12509
12762
|
const opts = program.opts();
|
|
12510
12763
|
const skipPermissions = opts.dangerouslySkipPermissions || false;
|
|
12511
|
-
console.log(
|
|
12764
|
+
console.log(chalk.hex(THEME.primary)(ASCII_BANNER));
|
|
12512
12765
|
if (skipPermissions) {
|
|
12513
12766
|
console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions\n"));
|
|
12514
12767
|
}
|
|
12515
|
-
console.log(chalk.hex(THEME.
|
|
12768
|
+
console.log(chalk.hex(THEME.primary)(`[target] Objective: ${objective}
|
|
12516
12769
|
`));
|
|
12517
12770
|
const agent = AgentFactory.createMainAgent(skipPermissions);
|
|
12518
12771
|
if (skipPermissions) {
|
|
@@ -12534,7 +12787,7 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
|
|
|
12534
12787
|
if (options.output) {
|
|
12535
12788
|
const fs = await import("fs/promises");
|
|
12536
12789
|
await fs.writeFile(options.output, JSON.stringify({ result: result2 }, null, 2));
|
|
12537
|
-
console.log(chalk.hex(THEME.
|
|
12790
|
+
console.log(chalk.hex(THEME.primary)(`
|
|
12538
12791
|
[+] Report saved to: ${options.output}`));
|
|
12539
12792
|
}
|
|
12540
12793
|
await shutdown(0);
|
|
@@ -12548,8 +12801,8 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
|
|
|
12548
12801
|
program.command("scan <target>").description("Quick scan a target").option("-s, --scan-type <type>", `Scan type (${CLI_SCAN_TYPES.join("|")})`, CLI_DEFAULT.SCAN_TYPE).option("-p, --ports <ports>", "Specific ports to scan").action(async (target, options) => {
|
|
12549
12802
|
const opts = program.opts();
|
|
12550
12803
|
const skipPermissions = opts.dangerouslySkipPermissions || false;
|
|
12551
|
-
console.log(
|
|
12552
|
-
console.log(chalk.hex(THEME.
|
|
12804
|
+
console.log(chalk.hex(THEME.primary)(ASCII_BANNER));
|
|
12805
|
+
console.log(chalk.hex(THEME.primary)(`
|
|
12553
12806
|
[scan] Target: ${target} (${options.scanType})
|
|
12554
12807
|
`));
|
|
12555
12808
|
const agent = AgentFactory.createMainAgent(skipPermissions);
|
|
@@ -12571,11 +12824,11 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
|
|
|
12571
12824
|
}
|
|
12572
12825
|
});
|
|
12573
12826
|
program.command("help-extended").description("Show extended help with examples").action(() => {
|
|
12574
|
-
console.log(
|
|
12827
|
+
console.log(chalk.hex(THEME.primary)(ASCII_BANNER));
|
|
12575
12828
|
console.log(`
|
|
12576
|
-
${chalk.hex(THEME.
|
|
12829
|
+
${chalk.hex(THEME.primary)(APP_NAME + " - Autonomous Penetration Testing AI")}
|
|
12577
12830
|
|
|
12578
|
-
${chalk.hex(THEME.status.warning)("Usage:")}
|
|
12831
|
+
${chalk.hex(THEME.status.warning)("Usage:")}
|
|
12579
12832
|
|
|
12580
12833
|
${chalk.hex(THEME.status.success)("$ pentesting")} Start interactive mode
|
|
12581
12834
|
${chalk.hex(THEME.status.success)("$ pentesting -t 192.168.1.1")} Start with target
|
|
@@ -12583,24 +12836,24 @@ ${chalk.hex(THEME.status.warning)("Usage:")}
|
|
|
12583
12836
|
|
|
12584
12837
|
${chalk.hex(THEME.status.warning)("Commands:")}
|
|
12585
12838
|
|
|
12586
|
-
${chalk.hex(THEME.
|
|
12587
|
-
${chalk.hex(THEME.
|
|
12588
|
-
${chalk.hex(THEME.
|
|
12839
|
+
${chalk.hex(THEME.primary)("pentesting")} Interactive TUI mode
|
|
12840
|
+
${chalk.hex(THEME.primary)("pentesting run <objective>")} Run single objective
|
|
12841
|
+
${chalk.hex(THEME.primary)("pentesting scan <target>")} Quick scan target
|
|
12589
12842
|
|
|
12590
12843
|
${chalk.hex(THEME.status.warning)("Options:")}
|
|
12591
12844
|
|
|
12592
|
-
${chalk.hex(THEME.
|
|
12593
|
-
${chalk.hex(THEME.
|
|
12594
|
-
${chalk.hex(THEME.
|
|
12845
|
+
${chalk.hex(THEME.primary)("--dangerously-skip-permissions")} Skip all permission prompts
|
|
12846
|
+
${chalk.hex(THEME.primary)("-t, --target <ip>")} Set target
|
|
12847
|
+
${chalk.hex(THEME.primary)("-o, --output <file>")} Save results to file
|
|
12595
12848
|
|
|
12596
12849
|
${chalk.hex(THEME.status.warning)("Interactive Commands:")}
|
|
12597
12850
|
|
|
12598
|
-
${chalk.hex(THEME.
|
|
12599
|
-
${chalk.hex(THEME.
|
|
12600
|
-
${chalk.hex(THEME.
|
|
12601
|
-
${chalk.hex(THEME.
|
|
12602
|
-
${chalk.hex(THEME.
|
|
12603
|
-
${chalk.hex(THEME.
|
|
12851
|
+
${chalk.hex(THEME.primary)("/target <ip>")} Set target
|
|
12852
|
+
${chalk.hex(THEME.primary)("/start")} Start autonomous mode
|
|
12853
|
+
${chalk.hex(THEME.primary)("/config")} Manage configuration
|
|
12854
|
+
${chalk.hex(THEME.primary)("/hint <text>")} Provide hint
|
|
12855
|
+
${chalk.hex(THEME.primary)("/findings")} Show findings
|
|
12856
|
+
${chalk.hex(THEME.primary)("/reset")} Reset session
|
|
12604
12857
|
|
|
12605
12858
|
${chalk.hex(THEME.status.warning)("Examples:")}
|
|
12606
12859
|
|
|
@@ -12615,9 +12868,9 @@ ${chalk.hex(THEME.status.warning)("Examples:")}
|
|
|
12615
12868
|
|
|
12616
12869
|
${chalk.hex(THEME.status.warning)("Environment:")}
|
|
12617
12870
|
|
|
12618
|
-
${chalk.hex(THEME.
|
|
12619
|
-
${chalk.hex(THEME.
|
|
12620
|
-
${chalk.hex(THEME.
|
|
12871
|
+
${chalk.hex(THEME.primary)("PENTEST_API_KEY")} Required - LLM API key
|
|
12872
|
+
${chalk.hex(THEME.primary)("PENTEST_BASE_URL")} Optional - AI API base URL
|
|
12873
|
+
${chalk.hex(THEME.primary)("PENTEST_MODEL")} Optional - Model override
|
|
12621
12874
|
`);
|
|
12622
12875
|
});
|
|
12623
12876
|
program.parse();
|