pentesting 0.55.3 → 0.55.4

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.
Files changed (2) hide show
  1. package/dist/main.js +1009 -498
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -27,8 +27,8 @@ import { Command } from "commander";
27
27
  import chalk from "chalk";
28
28
 
29
29
  // src/platform/tui/app.tsx
30
- import { useState as useState6, useCallback as useCallback4, useEffect as useEffect5, useRef as useRef6 } from "react";
31
- import { Box as Box6, useInput as useInput2, useApp, useStdout } from "ink";
30
+ import { useState as useState6, useCallback as useCallback6, useRef as useRef7 } from "react";
31
+ import { Box as Box6, useApp, useStdout } from "ink";
32
32
 
33
33
  // src/platform/tui/hooks/useAgent.ts
34
34
  import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef3 } from "react";
@@ -1240,7 +1240,7 @@ var INPUT_PROMPT_PATTERNS = [
1240
1240
 
1241
1241
  // src/shared/constants/agent.ts
1242
1242
  var APP_NAME = "Pentest AI";
1243
- var APP_VERSION = "0.55.3";
1243
+ var APP_VERSION = "0.55.4";
1244
1244
  var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
1245
1245
  var LLM_ROLES = {
1246
1246
  SYSTEM: "system",
@@ -1516,6 +1516,181 @@ var IMPACT_WEIGHT = {
1516
1516
  low: 1
1517
1517
  };
1518
1518
 
1519
+ // src/shared/utils/attack-graph/node.ts
1520
+ function generateNodeId(type, label) {
1521
+ return `${type}:${label}`.toLowerCase().replace(/\s+/g, "_");
1522
+ }
1523
+ function createNode(type, label, data = {}, status = NODE_STATUS.DISCOVERED) {
1524
+ const id = generateNodeId(type, label);
1525
+ return {
1526
+ id,
1527
+ type,
1528
+ label,
1529
+ data,
1530
+ status,
1531
+ discoveredAt: Date.now()
1532
+ };
1533
+ }
1534
+ function canMarkAttempted(node) {
1535
+ return node.status === NODE_STATUS.DISCOVERED;
1536
+ }
1537
+
1538
+ // src/shared/utils/attack-graph/edge.ts
1539
+ function createEdge(from, to, relation, confidence = 0.5, status = EDGE_STATUS.UNTESTED) {
1540
+ return {
1541
+ from,
1542
+ to,
1543
+ relation,
1544
+ confidence,
1545
+ status
1546
+ };
1547
+ }
1548
+ function edgeExists(edges, fromId, toId, relation) {
1549
+ return edges.some(
1550
+ (e) => e.from === fromId && e.to === toId && e.relation === relation
1551
+ );
1552
+ }
1553
+ function getIncomingEdges(edges, nodeId) {
1554
+ return edges.filter((e) => e.to === nodeId);
1555
+ }
1556
+ function markEdgeSucceeded(edge) {
1557
+ return {
1558
+ ...edge,
1559
+ status: EDGE_STATUS.SUCCEEDED
1560
+ };
1561
+ }
1562
+ function markEdgeFailed(edge, reason) {
1563
+ return {
1564
+ ...edge,
1565
+ status: EDGE_STATUS.FAILED,
1566
+ failReason: reason
1567
+ };
1568
+ }
1569
+ function isTestableEdge(edge) {
1570
+ return edge.status === EDGE_STATUS.UNTESTED || edge.status === EDGE_STATUS.TESTING;
1571
+ }
1572
+
1573
+ // src/shared/utils/attack-graph/utils.ts
1574
+ var AUTH_SERVICES = [
1575
+ "ssh",
1576
+ "ftp",
1577
+ "rdp",
1578
+ "smb",
1579
+ "http",
1580
+ "mysql",
1581
+ "postgresql",
1582
+ "mssql",
1583
+ "winrm",
1584
+ "vnc",
1585
+ "telnet"
1586
+ ];
1587
+ var HIGH_PRIVILEGE_LEVELS = ["root", "admin", "SYSTEM", "Administrator"];
1588
+ function createHostNode(ip, hostname) {
1589
+ return createNode(NODE_TYPE.HOST, ip, { ip, hostname });
1590
+ }
1591
+ function createServiceNode(host, port, service, version) {
1592
+ const hostId = generateNodeId(NODE_TYPE.HOST, host);
1593
+ const node = createNode(
1594
+ NODE_TYPE.SERVICE,
1595
+ `${host}:${port}`,
1596
+ { host, port, service, version }
1597
+ );
1598
+ const hostEdge = createEdge(hostId, node.id, "has_service", 0.95);
1599
+ return { node, hostEdge };
1600
+ }
1601
+ function createVersionSearchNode(service, version) {
1602
+ return createNode(
1603
+ NODE_TYPE.VULNERABILITY,
1604
+ `CVE search: ${service} ${version}`,
1605
+ { service, version, status: GRAPH_STATUS.NEEDS_SEARCH }
1606
+ );
1607
+ }
1608
+ function createCredentialNode(username, password, source) {
1609
+ return createNode(
1610
+ NODE_TYPE.CREDENTIAL,
1611
+ `${username}:***`,
1612
+ { username, password, source }
1613
+ );
1614
+ }
1615
+ function createCredentialSprayEdges(credId, nodes) {
1616
+ const edges = [];
1617
+ for (const [id, node] of nodes) {
1618
+ if (node.type === NODE_TYPE.SERVICE) {
1619
+ const svc = String(node.data.service || "");
1620
+ if (AUTH_SERVICES.some((s) => svc.includes(s))) {
1621
+ edges.push(createEdge(credId, id, "can_try_on", 0.6));
1622
+ }
1623
+ }
1624
+ }
1625
+ return edges;
1626
+ }
1627
+ function createVulnerabilityNode(title, target, severity, hasExploit = false) {
1628
+ return createNode(
1629
+ NODE_TYPE.VULNERABILITY,
1630
+ title,
1631
+ { target, severity, hasExploit }
1632
+ );
1633
+ }
1634
+ function findTargetServices(nodes, target) {
1635
+ const results = [];
1636
+ for (const node of nodes.values()) {
1637
+ if (node.type === NODE_TYPE.SERVICE && node.label.includes(target)) {
1638
+ results.push(node);
1639
+ }
1640
+ }
1641
+ return results;
1642
+ }
1643
+ function createAccessNode(host, level, via) {
1644
+ return createNode(
1645
+ NODE_TYPE.ACCESS,
1646
+ `${level}@${host}`,
1647
+ { host, level, via }
1648
+ );
1649
+ }
1650
+ function createPotentialAccessNode(exploitTitle) {
1651
+ return createNode(
1652
+ NODE_TYPE.ACCESS,
1653
+ `shell via ${exploitTitle}`,
1654
+ { via: exploitTitle, status: GRAPH_STATUS.POTENTIAL }
1655
+ );
1656
+ }
1657
+ function createLootNode(host) {
1658
+ return createNode(
1659
+ NODE_TYPE.LOOT,
1660
+ `flags on ${host}`,
1661
+ { host, status: GRAPH_STATUS.NEEDS_SEARCH }
1662
+ );
1663
+ }
1664
+ function createOSINTNode(category, detail, data = {}) {
1665
+ return createNode(
1666
+ NODE_TYPE.OSINT,
1667
+ `${category}: ${detail}`,
1668
+ { category, detail, ...data }
1669
+ );
1670
+ }
1671
+ function findOSINTRelatedNodes(nodes, detail) {
1672
+ const results = [];
1673
+ for (const [id, node] of nodes) {
1674
+ if (node.type === NODE_TYPE.HOST || node.type === NODE_TYPE.SERVICE) {
1675
+ const hostIp = String(node.data.ip || node.data.host || "");
1676
+ const hostname = String(node.data.hostname || "");
1677
+ if (hostIp && detail.includes(hostIp) || hostname && detail.includes(hostname)) {
1678
+ results.push({ id, node });
1679
+ }
1680
+ }
1681
+ }
1682
+ return results;
1683
+ }
1684
+ function isHighPrivilege(level) {
1685
+ return HIGH_PRIVILEGE_LEVELS.includes(level);
1686
+ }
1687
+ function formatFailedPath(from, to, reason) {
1688
+ return `${from} \u2192 ${to}${reason ? ` (${reason})` : ""}`;
1689
+ }
1690
+ function formatFailedNode(label, reason) {
1691
+ return `${label}${reason ? ` (${reason})` : ""}`;
1692
+ }
1693
+
1519
1694
  // src/shared/utils/attack-graph/chain-discovery.ts
1520
1695
  function recommendChains(nodes, edges) {
1521
1696
  const chains = [];
@@ -1770,7 +1945,152 @@ function getGraphStats(nodes, edges) {
1770
1945
  };
1771
1946
  }
1772
1947
 
1773
- // src/shared/utils/attack-graph/attack-graph.ts
1948
+ // src/shared/utils/attack-graph/domain-operations.ts
1949
+ function registerHost(graph, ip, hostname) {
1950
+ const node = createHostNode(ip, hostname);
1951
+ if (!graph.nodes.has(node.id)) {
1952
+ graph.nodes.set(node.id, node);
1953
+ }
1954
+ return node.id;
1955
+ }
1956
+ function registerService(graph, host, port, service, version) {
1957
+ const hostId = registerHost(graph, host);
1958
+ const { node, hostEdge } = createServiceNode(host, port, service, version);
1959
+ if (!graph.nodes.has(node.id)) {
1960
+ graph.nodes.set(node.id, node);
1961
+ }
1962
+ if (hostEdge && !edgeExists(graph.edges, hostEdge.from, hostEdge.to, hostEdge.relation)) {
1963
+ graph.edges.push(hostEdge);
1964
+ }
1965
+ if (version) {
1966
+ const vulnNode = createVersionSearchNode(service, version);
1967
+ if (!graph.nodes.has(vulnNode.id)) {
1968
+ graph.nodes.set(vulnNode.id, vulnNode);
1969
+ }
1970
+ graph.addEdge(node.id, vulnNode.id, "may_have", 0.3);
1971
+ }
1972
+ return node.id;
1973
+ }
1974
+ function registerCredential(graph, username, password, source) {
1975
+ const node = createCredentialNode(username, password, source);
1976
+ if (!graph.nodes.has(node.id)) {
1977
+ graph.nodes.set(node.id, node);
1978
+ }
1979
+ const sprayEdges = createCredentialSprayEdges(node.id, graph.nodes);
1980
+ for (const edge of sprayEdges) {
1981
+ if (!edgeExists(graph.edges, edge.from, edge.to, edge.relation)) {
1982
+ graph.edges.push(edge);
1983
+ }
1984
+ }
1985
+ return node.id;
1986
+ }
1987
+ function registerVulnerability(graph, title, target, severity, hasExploit = false) {
1988
+ const vulnNode = createVulnerabilityNode(title, target, severity, hasExploit);
1989
+ if (!graph.nodes.has(vulnNode.id)) {
1990
+ graph.nodes.set(vulnNode.id, vulnNode);
1991
+ }
1992
+ const targetServices = findTargetServices(graph.nodes, target);
1993
+ for (const svc of targetServices) {
1994
+ graph.addEdge(svc.id, vulnNode.id, "has_vulnerability", 0.8);
1995
+ }
1996
+ if (hasExploit) {
1997
+ const accessNode = createPotentialAccessNode(title);
1998
+ if (!graph.nodes.has(accessNode.id)) {
1999
+ graph.nodes.set(accessNode.id, accessNode);
2000
+ }
2001
+ graph.addEdge(vulnNode.id, accessNode.id, "leads_to", 0.7);
2002
+ }
2003
+ return vulnNode.id;
2004
+ }
2005
+ function registerAccess(graph, host, level, via) {
2006
+ const accessNode = createAccessNode(host, level, via);
2007
+ if (!graph.nodes.has(accessNode.id)) {
2008
+ graph.nodes.set(accessNode.id, accessNode);
2009
+ }
2010
+ const node = graph.nodes.get(accessNode.id);
2011
+ if (node) {
2012
+ node.status = NODE_STATUS.SUCCEEDED;
2013
+ node.resolvedAt = Date.now();
2014
+ const incomingEdges = getIncomingEdges(graph.edges, accessNode.id);
2015
+ for (const edge of incomingEdges) {
2016
+ Object.assign(edge, markEdgeSucceeded(edge));
2017
+ }
2018
+ }
2019
+ if (isHighPrivilege(level)) {
2020
+ const lootNode = createLootNode(host);
2021
+ if (!graph.nodes.has(lootNode.id)) {
2022
+ graph.nodes.set(lootNode.id, lootNode);
2023
+ }
2024
+ graph.addEdge(accessNode.id, lootNode.id, "can_access", 0.9);
2025
+ }
2026
+ return accessNode.id;
2027
+ }
2028
+ function registerOSINT(graph, category, detail, data = {}) {
2029
+ const osintNode = createOSINTNode(category, detail, data);
2030
+ if (!graph.nodes.has(osintNode.id)) {
2031
+ graph.nodes.set(osintNode.id, osintNode);
2032
+ }
2033
+ const relatedNodes = findOSINTRelatedNodes(graph.nodes, detail);
2034
+ for (const { id } of relatedNodes) {
2035
+ graph.addEdge(osintNode.id, id, "relates_to", 0.5);
2036
+ }
2037
+ return osintNode.id;
2038
+ }
2039
+
2040
+ // src/shared/utils/attack-graph/status-management.ts
2041
+ function markNodeAttemptedInGraph(nodes, nodeId) {
2042
+ const node = nodes.get(nodeId);
2043
+ if (node && canMarkAttempted(node)) {
2044
+ node.status = NODE_STATUS.ATTEMPTED;
2045
+ node.attemptedAt = Date.now();
2046
+ }
2047
+ }
2048
+ function markNodeSucceededInGraph(nodes, edges, nodeId) {
2049
+ const node = nodes.get(nodeId);
2050
+ if (node) {
2051
+ node.status = NODE_STATUS.SUCCEEDED;
2052
+ node.resolvedAt = Date.now();
2053
+ const incomingEdges = getIncomingEdges(edges, nodeId);
2054
+ for (const edge of incomingEdges) {
2055
+ if (edge.status === EDGE_STATUS.TESTING) {
2056
+ Object.assign(edge, markEdgeSucceeded(edge));
2057
+ }
2058
+ }
2059
+ }
2060
+ }
2061
+ function markNodeFailedInGraph(nodes, edges, nodeId, reason, callbacks) {
2062
+ const node = nodes.get(nodeId);
2063
+ if (node) {
2064
+ node.status = NODE_STATUS.FAILED;
2065
+ node.resolvedAt = Date.now();
2066
+ node.failReason = reason;
2067
+ callbacks.onFailedPath(formatFailedNode(node.label, reason));
2068
+ const incomingEdges = getIncomingEdges(edges, nodeId);
2069
+ for (const edge of incomingEdges) {
2070
+ if (isTestableEdge(edge)) {
2071
+ Object.assign(edge, markEdgeFailed(edge, reason));
2072
+ }
2073
+ }
2074
+ }
2075
+ }
2076
+ function markEdgeTestingInList(edges, fromId, toId) {
2077
+ for (const edge of edges) {
2078
+ if (edge.from === fromId && edge.to === toId) {
2079
+ edge.status = EDGE_STATUS.TESTING;
2080
+ }
2081
+ }
2082
+ }
2083
+ function markEdgeFailedInList(edges, fromId, toId, reason, callbacks) {
2084
+ for (const edge of edges) {
2085
+ if (edge.from === fromId && edge.to === toId) {
2086
+ edge.status = EDGE_STATUS.FAILED;
2087
+ edge.failReason = reason;
2088
+ }
2089
+ }
2090
+ callbacks.onFailedPath(formatFailedPath(fromId, toId, reason));
2091
+ }
2092
+
2093
+ // src/shared/utils/attack-graph/graph.ts
1774
2094
  var AttackGraph = class {
1775
2095
  nodes = /* @__PURE__ */ new Map();
1776
2096
  edges = [];
@@ -1786,26 +2106,18 @@ var AttackGraph = class {
1786
2106
  * Add a node to the attack graph.
1787
2107
  */
1788
2108
  addNode(type, label, data = {}) {
1789
- const id = `${type}:${label}`.toLowerCase().replace(/\s+/g, "_");
1790
- if (!this.nodes.has(id)) {
1791
- this.nodes.set(id, {
1792
- id,
1793
- type,
1794
- label,
1795
- data,
1796
- status: NODE_STATUS.DISCOVERED,
1797
- discoveredAt: Date.now()
1798
- });
2109
+ const node = createNode(type, label, data);
2110
+ if (!this.nodes.has(node.id)) {
2111
+ this.nodes.set(node.id, node);
1799
2112
  }
1800
- return id;
2113
+ return node.id;
1801
2114
  }
1802
2115
  /**
1803
2116
  * Add an edge (relationship) between two nodes.
1804
2117
  */
1805
2118
  addEdge(fromId, toId, relation, confidence = 0.5) {
1806
- const exists = this.edges.some((e) => e.from === fromId && e.to === toId && e.relation === relation);
1807
- if (!exists) {
1808
- this.edges.push({ from: fromId, to: toId, relation, confidence, status: EDGE_STATUS.UNTESTED });
2119
+ if (!edgeExists(this.edges, fromId, toId, relation)) {
2120
+ this.edges.push(createEdge(fromId, toId, relation, confidence));
1809
2121
  }
1810
2122
  }
1811
2123
  /**
@@ -1819,176 +2131,81 @@ var AttackGraph = class {
1819
2131
  * Mark a node as being attempted (in-progress attack).
1820
2132
  */
1821
2133
  markAttempted(nodeId) {
1822
- const node = this.nodes.get(nodeId);
1823
- if (node && node.status === NODE_STATUS.DISCOVERED) {
1824
- node.status = NODE_STATUS.ATTEMPTED;
1825
- node.attemptedAt = Date.now();
1826
- }
2134
+ markNodeAttemptedInGraph(this.nodes, nodeId);
1827
2135
  }
1828
2136
  /**
1829
2137
  * Mark a node as successfully exploited/achieved.
1830
2138
  */
1831
2139
  markSucceeded(nodeId) {
1832
- const node = this.nodes.get(nodeId);
1833
- if (node) {
1834
- node.status = NODE_STATUS.SUCCEEDED;
1835
- node.resolvedAt = Date.now();
1836
- }
1837
- for (const edge of this.edges) {
1838
- if (edge.to === nodeId && edge.status === EDGE_STATUS.TESTING) {
1839
- edge.status = EDGE_STATUS.SUCCEEDED;
1840
- }
1841
- }
2140
+ markNodeSucceededInGraph(this.nodes, this.edges, nodeId);
1842
2141
  }
1843
2142
  /**
1844
2143
  * Mark a node as failed (attack didn't work).
1845
2144
  */
1846
2145
  markFailed(nodeId, reason) {
1847
- const node = this.nodes.get(nodeId);
1848
- if (node) {
1849
- node.status = NODE_STATUS.FAILED;
1850
- node.resolvedAt = Date.now();
1851
- node.failReason = reason;
1852
- this.failedPaths.push(`${node.label}${reason ? ` (${reason})` : ""}`);
1853
- }
1854
- for (const edge of this.edges) {
1855
- if (edge.to === nodeId && (edge.status === EDGE_STATUS.TESTING || edge.status === EDGE_STATUS.UNTESTED)) {
1856
- edge.status = EDGE_STATUS.FAILED;
1857
- edge.failReason = reason;
1858
- }
1859
- }
2146
+ markNodeFailedInGraph(this.nodes, this.edges, nodeId, reason, {
2147
+ onFailedPath: (path2) => this.failedPaths.push(path2)
2148
+ });
1860
2149
  }
1861
2150
  /**
1862
2151
  * Mark an edge as being tested.
1863
2152
  */
1864
2153
  markEdgeTesting(fromId, toId) {
1865
- for (const edge of this.edges) {
1866
- if (edge.from === fromId && edge.to === toId) {
1867
- edge.status = EDGE_STATUS.TESTING;
1868
- }
1869
- }
2154
+ markEdgeTestingInList(this.edges, fromId, toId);
1870
2155
  }
1871
2156
  /**
1872
2157
  * Mark an edge as failed.
1873
2158
  */
1874
2159
  markEdgeFailed(fromId, toId, reason) {
1875
- for (const edge of this.edges) {
1876
- if (edge.from === fromId && edge.to === toId) {
1877
- edge.status = EDGE_STATUS.FAILED;
1878
- edge.failReason = reason;
1879
- }
1880
- }
1881
- this.failedPaths.push(`${fromId} \u2192 ${toId}${reason ? ` (${reason})` : ""}`);
2160
+ markEdgeFailedInList(this.edges, fromId, toId, reason, {
2161
+ onFailedPath: (path2) => this.failedPaths.push(path2)
2162
+ });
1882
2163
  }
1883
2164
  // ─── Domain-Specific Registration ───────────────────────────
2165
+ /** Internal graph container for domain operations */
2166
+ get graphContainer() {
2167
+ return {
2168
+ nodes: this.nodes,
2169
+ edges: this.edges,
2170
+ addEdge: this.addEdge.bind(this),
2171
+ markSucceeded: this.markSucceeded.bind(this)
2172
+ };
2173
+ }
1884
2174
  /**
1885
2175
  * Record a host discovery.
1886
2176
  */
1887
2177
  addHost(ip, hostname) {
1888
- return this.addNode(NODE_TYPE.HOST, ip, { ip, hostname });
2178
+ return registerHost(this.graphContainer, ip, hostname);
1889
2179
  }
1890
2180
  /**
1891
2181
  * Record a service discovery and auto-create edges.
1892
2182
  */
1893
2183
  addService(host, port, service, version) {
1894
- const hostId = this.addHost(host);
1895
- const serviceId = this.addNode(NODE_TYPE.SERVICE, `${host}:${port}`, {
1896
- host,
1897
- port,
1898
- service,
1899
- version
1900
- });
1901
- this.addEdge(hostId, serviceId, "has_service", 0.95);
1902
- if (version) {
1903
- const vulnId = this.addNode(NODE_TYPE.VULNERABILITY, `CVE search: ${service} ${version}`, {
1904
- service,
1905
- version,
1906
- status: GRAPH_STATUS.NEEDS_SEARCH
1907
- });
1908
- this.addEdge(serviceId, vulnId, "may_have", 0.3);
1909
- }
1910
- return serviceId;
2184
+ return registerService(this.graphContainer, host, port, service, version);
1911
2185
  }
1912
2186
  /**
1913
2187
  * Record a credential discovery and create spray edges.
1914
2188
  */
1915
2189
  addCredential(username, password, source) {
1916
- const credId = this.addNode(NODE_TYPE.CREDENTIAL, `${username}:***`, {
1917
- username,
1918
- password,
1919
- source
1920
- });
1921
- for (const [id, node] of this.nodes) {
1922
- if (node.type === NODE_TYPE.SERVICE) {
1923
- const svc = String(node.data.service || "");
1924
- if (["ssh", "ftp", "rdp", "smb", "http", "mysql", "postgresql", "mssql", "winrm", "vnc", "telnet"].some((s) => svc.includes(s))) {
1925
- this.addEdge(credId, id, "can_try_on", 0.6);
1926
- }
1927
- }
1928
- }
1929
- return credId;
2190
+ return registerCredential(this.graphContainer, username, password, source);
1930
2191
  }
1931
2192
  /**
1932
2193
  * Record a vulnerability finding.
1933
2194
  */
1934
2195
  addVulnerability(title, target, severity, hasExploit = false) {
1935
- const vulnId = this.addNode(NODE_TYPE.VULNERABILITY, title, {
1936
- target,
1937
- severity,
1938
- hasExploit
1939
- });
1940
- for (const [id, node] of this.nodes) {
1941
- if (node.type === NODE_TYPE.SERVICE && node.label.includes(target)) {
1942
- this.addEdge(id, vulnId, "has_vulnerability", 0.8);
1943
- }
1944
- }
1945
- if (hasExploit) {
1946
- const accessId = this.addNode(NODE_TYPE.ACCESS, `shell via ${title}`, {
1947
- via: title,
1948
- status: GRAPH_STATUS.POTENTIAL
1949
- });
1950
- this.addEdge(vulnId, accessId, "leads_to", 0.7);
1951
- }
1952
- return vulnId;
2196
+ return registerVulnerability(this.graphContainer, title, target, severity, hasExploit);
1953
2197
  }
1954
2198
  /**
1955
2199
  * Record gained access.
1956
2200
  */
1957
2201
  addAccess(host, level, via) {
1958
- const accessId = this.addNode(NODE_TYPE.ACCESS, `${level}@${host}`, {
1959
- host,
1960
- level,
1961
- via
1962
- });
1963
- this.markSucceeded(accessId);
1964
- if (["root", "admin", "SYSTEM", "Administrator"].includes(level)) {
1965
- const lootId = this.addNode(NODE_TYPE.LOOT, `flags on ${host}`, {
1966
- host,
1967
- status: GRAPH_STATUS.NEEDS_SEARCH
1968
- });
1969
- this.addEdge(accessId, lootId, "can_access", 0.9);
1970
- }
1971
- return accessId;
2202
+ return registerAccess(this.graphContainer, host, level, via);
1972
2203
  }
1973
2204
  /**
1974
2205
  * Record OSINT discovery (Docker image, GitHub repo, company info, etc.)
1975
2206
  */
1976
2207
  addOSINT(category, detail, data = {}) {
1977
- const osintId = this.addNode(NODE_TYPE.OSINT, `${category}: ${detail}`, {
1978
- category,
1979
- detail,
1980
- ...data
1981
- });
1982
- for (const [id, node] of this.nodes) {
1983
- if (node.type === NODE_TYPE.HOST || node.type === NODE_TYPE.SERVICE) {
1984
- const hostIp = String(node.data.ip || node.data.host || "");
1985
- const hostname = String(node.data.hostname || "");
1986
- if (hostIp && detail.includes(hostIp) || hostname && detail.includes(hostname)) {
1987
- this.addEdge(osintId, id, "relates_to", 0.5);
1988
- }
1989
- }
1990
- }
1991
- return osintId;
2208
+ return registerOSINT(this.graphContainer, category, detail, data);
1992
2209
  }
1993
2210
  // ─── Chain Discovery (DFS) ──────────────────────────────────
1994
2211
  /**
@@ -2610,11 +2827,86 @@ var DynamicTechniqueLibrary = class {
2610
2827
  }
2611
2828
  };
2612
2829
 
2613
- // src/engine/state/state.ts
2830
+ // src/engine/state/target-state.ts
2831
+ var TargetState = class {
2832
+ targets = /* @__PURE__ */ new Map();
2833
+ attackGraph;
2834
+ constructor(attackGraph) {
2835
+ this.attackGraph = attackGraph;
2836
+ }
2837
+ /**
2838
+ * Add a target and register it in the attack graph.
2839
+ */
2840
+ add(target) {
2841
+ this.targets.set(target.ip, target);
2842
+ this.attackGraph.addHost(target.ip, target.hostname);
2843
+ for (const port of target.ports) {
2844
+ this.attackGraph.addService(target.ip, port.port, port.service, port.version);
2845
+ }
2846
+ }
2847
+ get(ip) {
2848
+ return this.targets.get(ip);
2849
+ }
2850
+ getAll() {
2851
+ return Array.from(this.targets.values());
2852
+ }
2853
+ getMap() {
2854
+ return this.targets;
2855
+ }
2856
+ /**
2857
+ * Clear all targets (for reset).
2858
+ */
2859
+ clear() {
2860
+ this.targets.clear();
2861
+ }
2862
+ };
2863
+
2864
+ // src/engine/state/finding-state.ts
2865
+ var FindingState = class {
2866
+ findings = [];
2867
+ add(finding) {
2868
+ this.findings.push(finding);
2869
+ }
2870
+ getAll() {
2871
+ return this.findings;
2872
+ }
2873
+ getBySeverity(severity) {
2874
+ return this.findings.filter((f) => f.severity === severity);
2875
+ }
2876
+ /**
2877
+ * Clear all findings (for reset).
2878
+ */
2879
+ clear() {
2880
+ this.findings = [];
2881
+ }
2882
+ };
2883
+
2884
+ // src/engine/state/loot-state.ts
2885
+ var LootState = class {
2886
+ loot = [];
2887
+ add(item) {
2888
+ this.loot.push(item);
2889
+ }
2890
+ getAll() {
2891
+ return this.loot;
2892
+ }
2893
+ /**
2894
+ * Clear all loot (for reset).
2895
+ */
2896
+ clear() {
2897
+ this.loot = [];
2898
+ }
2899
+ };
2900
+
2901
+ // src/engine/state/shared-state.ts
2614
2902
  var SharedState = class {
2615
2903
  data;
2616
- // ─── Improvement #6: Attack Graph ──────────────────────────────
2904
+ // ─── Enhancement #6: Attack Graph ──────────────────────────────
2617
2905
  attackGraph = new AttackGraph();
2906
+ // ─── Focused State Modules ─────────────────────────────────────
2907
+ targetState;
2908
+ findingState;
2909
+ lootState;
2618
2910
  // ─── Improvement #12: Multi-Tier Memory ───────────────────────
2619
2911
  workingMemory = new WorkingMemory();
2620
2912
  episodicMemory = new EpisodicMemory();
@@ -2623,11 +2915,11 @@ var SharedState = class {
2623
2915
  dynamicTechniques = new DynamicTechniqueLibrary();
2624
2916
  constructor() {
2625
2917
  const now = Date.now();
2918
+ this.targetState = new TargetState(this.attackGraph);
2919
+ this.findingState = new FindingState();
2920
+ this.lootState = new LootState();
2626
2921
  this.data = {
2627
2922
  engagement: null,
2628
- targets: /* @__PURE__ */ new Map(),
2629
- findings: [],
2630
- loot: [],
2631
2923
  todo: [],
2632
2924
  actionLog: [],
2633
2925
  currentPhase: PHASES.RECON,
@@ -2651,9 +2943,6 @@ var SharedState = class {
2651
2943
  const now = Date.now();
2652
2944
  this.data = {
2653
2945
  engagement: null,
2654
- targets: /* @__PURE__ */ new Map(),
2655
- findings: [],
2656
- loot: [],
2657
2946
  todo: [],
2658
2947
  actionLog: [],
2659
2948
  currentPhase: PHASES.RECON,
@@ -2665,12 +2954,17 @@ var SharedState = class {
2665
2954
  deadlineAt: computeDeadline(now),
2666
2955
  challengeAnalysis: null
2667
2956
  };
2957
+ this.targetState.clear();
2958
+ this.findingState.clear();
2959
+ this.lootState.clear();
2668
2960
  this.attackGraph.reset();
2669
2961
  this.workingMemory.clear();
2670
2962
  this.episodicMemory.clear();
2671
2963
  this.dynamicTechniques.clear();
2672
2964
  }
2673
- // --- Mission & Persistent Context ---
2965
+ // ═══════════════════════════════════════════════════════════════
2966
+ // SECTION: Mission & Persistent Context
2967
+ // ═══════════════════════════════════════════════════════════════
2674
2968
  setMissionSummary(summary) {
2675
2969
  this.data.missionSummary = summary;
2676
2970
  }
@@ -2715,7 +3009,9 @@ var SharedState = class {
2715
3009
  restoreTodoItem(item) {
2716
3010
  this.data.todo.push({ ...item });
2717
3011
  }
2718
- // --- Engagement & Scope ---
3012
+ // ═══════════════════════════════════════════════════════════════
3013
+ // SECTION: Engagement & Scope
3014
+ // ═══════════════════════════════════════════════════════════════
2719
3015
  setEngagement(engagement) {
2720
3016
  this.data.engagement = engagement;
2721
3017
  this.data.currentPhase = engagement.phase;
@@ -2740,40 +3036,42 @@ var SharedState = class {
2740
3036
  };
2741
3037
  }
2742
3038
  }
2743
- // --- Targets ---
3039
+ // ═══════════════════════════════════════════════════════════════
3040
+ // SECTION: Targets (delegated to TargetState)
3041
+ // ═══════════════════════════════════════════════════════════════
2744
3042
  addTarget(target) {
2745
- this.data.targets.set(target.ip, target);
2746
- this.attackGraph.addHost(target.ip, target.hostname);
2747
- for (const port of target.ports) {
2748
- this.attackGraph.addService(target.ip, port.port, port.service, port.version);
2749
- }
3043
+ this.targetState.add(target);
2750
3044
  }
2751
3045
  getTarget(ip) {
2752
- return this.data.targets.get(ip);
3046
+ return this.targetState.get(ip);
2753
3047
  }
2754
3048
  getAllTargets() {
2755
- return Array.from(this.data.targets.values());
3049
+ return this.targetState.getAll();
2756
3050
  }
2757
3051
  getTargets() {
2758
- return this.data.targets;
3052
+ return this.targetState.getMap();
2759
3053
  }
2760
- // --- Findings & Loot ---
3054
+ // ═══════════════════════════════════════════════════════════════
3055
+ // SECTION: Findings & Loot (delegated to focused modules)
3056
+ // ═══════════════════════════════════════════════════════════════
2761
3057
  addFinding(finding) {
2762
- this.data.findings.push(finding);
3058
+ this.findingState.add(finding);
2763
3059
  }
2764
3060
  getFindings() {
2765
- return this.data.findings;
3061
+ return this.findingState.getAll();
2766
3062
  }
2767
3063
  getFindingsBySeverity(severity) {
2768
- return this.data.findings.filter((f) => f.severity === severity);
3064
+ return this.findingState.getBySeverity(severity);
2769
3065
  }
2770
3066
  addLoot(loot) {
2771
- this.data.loot.push(loot);
3067
+ this.lootState.add(loot);
2772
3068
  }
2773
3069
  getLoot() {
2774
- return this.data.loot;
3070
+ return this.lootState.getAll();
2775
3071
  }
2776
- // --- TODO Management ---
3072
+ // ═══════════════════════════════════════════════════════════════
3073
+ // SECTION: TODO Management
3074
+ // ═══════════════════════════════════════════════════════════════
2777
3075
  addTodo(content, priority = PRIORITIES.MEDIUM) {
2778
3076
  const id = generateId(AGENT_LIMITS.ID_RADIX, AGENT_LIMITS.ID_LENGTH);
2779
3077
  const item = { id, content, status: TODO_STATUSES.PENDING, priority };
@@ -2790,7 +3088,9 @@ var SharedState = class {
2790
3088
  completeTodo(id) {
2791
3089
  this.updateTodo(id, { status: TODO_STATUSES.DONE });
2792
3090
  }
2793
- // --- Logs & Phase ---
3091
+ // ═══════════════════════════════════════════════════════════════
3092
+ // SECTION: Logs & Phase
3093
+ // ═══════════════════════════════════════════════════════════════
2794
3094
  logAction(action) {
2795
3095
  this.data.actionLog.push({
2796
3096
  id: generateId(AGENT_LIMITS.ID_RADIX, AGENT_LIMITS.ID_LENGTH),
@@ -2810,7 +3110,9 @@ var SharedState = class {
2810
3110
  getPhase() {
2811
3111
  return this.data.currentPhase;
2812
3112
  }
2813
- // --- Flag Detection ---
3113
+ // ═══════════════════════════════════════════════════════════════
3114
+ // SECTION: Flag Detection
3115
+ // ═══════════════════════════════════════════════════════════════
2814
3116
  setCtfMode(shouldEnable) {
2815
3117
  this.data.ctfMode = shouldEnable;
2816
3118
  }
@@ -2826,14 +3128,18 @@ var SharedState = class {
2826
3128
  getFlags() {
2827
3129
  return this.data.flags;
2828
3130
  }
2829
- // --- Challenge Analysis (#2: Auto-Prompter) ---
3131
+ // ═══════════════════════════════════════════════════════════════
3132
+ // SECTION: Challenge Analysis (#2: Auto-Prompter)
3133
+ // ═══════════════════════════════════════════════════════════════
2830
3134
  setChallengeAnalysis(analysis) {
2831
3135
  this.data.challengeAnalysis = analysis;
2832
3136
  }
2833
3137
  getChallengeAnalysis() {
2834
3138
  return this.data.challengeAnalysis;
2835
3139
  }
2836
- // --- Time Management ---
3140
+ // ═══════════════════════════════════════════════════════════════
3141
+ // SECTION: Time Management
3142
+ // ═══════════════════════════════════════════════════════════════
2837
3143
  /** Set a deadline for time-aware strategy (e.g., CTF competition end time) */
2838
3144
  setDeadline(deadlineMs) {
2839
3145
  this.data.deadlineAt = deadlineMs;
@@ -11215,6 +11521,62 @@ ${firstLine}`, phase }
11215
11521
  };
11216
11522
  }
11217
11523
 
11524
+ // src/agents/core-agent/step-handler.ts
11525
+ function emitIterationThink(events, iteration, phase, targetsSize, findingsLength, progress) {
11526
+ emitThink(events, iteration, phase, targetsSize, findingsLength, progress);
11527
+ }
11528
+ function handleResponseContent(response, events, phase, hadReasoningEnd) {
11529
+ if (response.reasoning && !hadReasoningEnd) {
11530
+ emitReasoningStart(events, phase);
11531
+ emitReasoningDelta(events, response.reasoning, phase);
11532
+ emitReasoningEnd(events, phase);
11533
+ }
11534
+ if (response.content?.trim()) {
11535
+ if (!response.reasoning && !hadReasoningEnd) {
11536
+ emitReasoningStart(events, phase);
11537
+ emitReasoningDelta(events, response.content.trim(), phase);
11538
+ emitReasoningEnd(events, phase);
11539
+ } else {
11540
+ events.emit({
11541
+ type: EVENT_TYPES.AI_RESPONSE,
11542
+ timestamp: Date.now(),
11543
+ data: { content: response.content.trim(), phase }
11544
+ });
11545
+ }
11546
+ }
11547
+ }
11548
+ function addAssistantMessage(messages, content) {
11549
+ messages.push({ role: LLM_ROLES.ASSISTANT, content });
11550
+ }
11551
+ function addToolResultsToMessages(messages, results) {
11552
+ for (const res of results) {
11553
+ messages.push({
11554
+ role: LLM_ROLES.USER,
11555
+ content: [{
11556
+ type: LLM_BLOCK_TYPE.TOOL_RESULT,
11557
+ tool_use_id: res.toolCallId,
11558
+ content: res.output,
11559
+ is_error: !!res.error
11560
+ }]
11561
+ });
11562
+ }
11563
+ }
11564
+ async function processToolCallsInStep(toolExecutor, toolCalls, messages, progress) {
11565
+ const results = await toolExecutor.processToolCalls(toolCalls, progress);
11566
+ addToolResultsToMessages(messages, results);
11567
+ return results.length;
11568
+ }
11569
+ async function getLLMStreamResponse(llm, messages, toolSchemas, systemPrompt, events, phase, abortController) {
11570
+ const callbacks = buildStreamCallbacks(events, phase, abortController);
11571
+ const response = await llm.generateResponseStream(
11572
+ messages,
11573
+ toolSchemas,
11574
+ systemPrompt,
11575
+ callbacks
11576
+ );
11577
+ return { response, hadReasoningEnd: callbacks.hadReasoningEnd() };
11578
+ }
11579
+
11218
11580
  // src/agents/tool-executor/types.ts
11219
11581
  var PARALLEL_SAFE_TOOLS = /* @__PURE__ */ new Set([
11220
11582
  "get_state",
@@ -11932,6 +12294,82 @@ var ToolExecutor = class {
11932
12294
  }
11933
12295
  };
11934
12296
 
12297
+ // src/agents/core-agent/tool-processor.ts
12298
+ function createToolExecutor(config) {
12299
+ return new ToolExecutor({
12300
+ state: config.state,
12301
+ events: config.events,
12302
+ toolRegistry: config.toolRegistry,
12303
+ llm: config.llm
12304
+ });
12305
+ }
12306
+ function getTurnToolJournal(toolExecutor) {
12307
+ return toolExecutor?.turnToolJournal ?? [];
12308
+ }
12309
+ function getTurnMemo(toolExecutor) {
12310
+ return toolExecutor?.turnMemo ?? {
12311
+ keyFindings: [],
12312
+ credentials: [],
12313
+ attackVectors: [],
12314
+ failures: [],
12315
+ suspicions: [],
12316
+ attackValue: "LOW",
12317
+ nextSteps: []
12318
+ };
12319
+ }
12320
+ function getTurnReflections(toolExecutor) {
12321
+ return toolExecutor?.turnReflections ?? [];
12322
+ }
12323
+
12324
+ // src/agents/core-agent/run-loop.ts
12325
+ function createProgressTracker() {
12326
+ return {
12327
+ totalToolsExecuted: 0,
12328
+ consecutiveIdleIterations: 0,
12329
+ lastToolExecutedAt: Date.now(),
12330
+ toolErrors: 0,
12331
+ toolSuccesses: 0,
12332
+ blockedCommandPatterns: /* @__PURE__ */ new Map(),
12333
+ totalBlockedCommands: 0
12334
+ };
12335
+ }
12336
+ function createInitialMessages(task) {
12337
+ return [{ role: LLM_ROLES.USER, content: task }];
12338
+ }
12339
+ function checkAbort(abortController, iteration, toolsExecuted) {
12340
+ if (abortController.signal.aborted) {
12341
+ return buildCancelledResult(iteration, toolsExecuted);
12342
+ }
12343
+ return null;
12344
+ }
12345
+ function updateProgressAfterStep(progress, toolsExecuted) {
12346
+ progress.totalToolsExecuted += toolsExecuted;
12347
+ if (toolsExecuted > 0) {
12348
+ progress.consecutiveIdleIterations = 0;
12349
+ progress.lastToolExecutedAt = Date.now();
12350
+ } else {
12351
+ progress.consecutiveIdleIterations++;
12352
+ }
12353
+ }
12354
+ function handleIdleIteration(progress, messages, getPhase, getTargetsSize, getFindingsLength) {
12355
+ if (progress.consecutiveIdleIterations >= AGENT_LIMITS.MAX_CONSECUTIVE_IDLE) {
12356
+ progress.consecutiveIdleIterations = 0;
12357
+ const nudge = buildDeadlockNudge(getPhase(), getTargetsSize(), getFindingsLength());
12358
+ messages.push({ role: LLM_ROLES.USER, content: nudge });
12359
+ }
12360
+ }
12361
+ function buildSuccessResult(output, iteration, tools) {
12362
+ return { output, iterations: iteration + 1, toolsExecuted: tools, isCompleted: true };
12363
+ }
12364
+ function buildMaxIterationsResult(maxIterations, tools) {
12365
+ return {
12366
+ output: `Max iterations (${maxIterations}) reached. Tools: ${tools}.`,
12367
+ iterations: maxIterations,
12368
+ toolsExecuted: tools,
12369
+ isCompleted: false
12370
+ };
12371
+ }
12372
+
11935
12373
  // src/agents/core-agent/core-agent.ts
11936
12374
  var CoreAgent = class {
11937
12375
  llm;
@@ -11950,73 +12388,43 @@ var CoreAgent = class {
11950
12388
  this.llm = getLLMClient();
11951
12389
  this.maxIterations = maxIterations || AGENT_LIMITS.MAX_ITERATIONS;
11952
12390
  }
11953
- /** Set or update the tool registry (for delayed initialization) */
11954
12391
  setToolRegistry(registry) {
11955
12392
  this.toolRegistry = registry;
11956
12393
  }
11957
- /** Abort the current execution */
11958
12394
  abort() {
11959
12395
  this.abortController?.abort();
11960
12396
  }
11961
- /** Get turn tool journal (for MainAgent) */
11962
12397
  getTurnToolJournal() {
11963
- return this.toolExecutor?.turnToolJournal ?? [];
12398
+ return getTurnToolJournal(this.toolExecutor);
11964
12399
  }
11965
12400
  getTurnMemo() {
11966
- return this.toolExecutor?.turnMemo ?? { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
12401
+ return getTurnMemo(this.toolExecutor);
11967
12402
  }
11968
12403
  getTurnReflections() {
11969
- return this.toolExecutor?.turnReflections ?? [];
12404
+ return getTurnReflections(this.toolExecutor);
11970
12405
  }
11971
- // ─────────────────────────────────────────────────────────────────
11972
- // SUBSECTION: Main Run Loop
11973
- // ─────────────────────────────────────────────────────────────────
11974
12406
  /** The core loop: Think → Act → Observe */
11975
12407
  async run(task, systemPrompt) {
11976
12408
  this.abortController = new AbortController();
11977
- const messages = [{ role: LLM_ROLES.USER, content: task }];
12409
+ const messages = createInitialMessages(task);
12410
+ const progress = createProgressTracker();
11978
12411
  let consecutiveLLMErrors = 0;
11979
- const maxConsecutiveLLMErrors = AGENT_LIMITS.MAX_CONSECUTIVE_LLM_ERRORS;
11980
- const progress = {
11981
- totalToolsExecuted: 0,
11982
- consecutiveIdleIterations: 0,
11983
- lastToolExecutedAt: Date.now(),
11984
- toolErrors: 0,
11985
- toolSuccesses: 0,
11986
- blockedCommandPatterns: /* @__PURE__ */ new Map(),
11987
- totalBlockedCommands: 0
11988
- };
11989
12412
  for (let iteration = 0; iteration < this.maxIterations; iteration++) {
11990
- if (this.abortController.signal.aborted) {
11991
- return buildCancelledResult(iteration, progress.totalToolsExecuted);
11992
- }
12413
+ const cancelledResult = checkAbort(this.abortController, iteration, progress.totalToolsExecuted);
12414
+ if (cancelledResult) return cancelledResult;
11993
12415
  try {
11994
12416
  const result2 = await this.step(iteration, messages, systemPrompt, progress);
11995
- progress.totalToolsExecuted += result2.toolsExecuted;
11996
- consecutiveLLMErrors = 0;
11997
- if (result2.toolsExecuted > 0) {
11998
- progress.consecutiveIdleIterations = 0;
11999
- progress.lastToolExecutedAt = Date.now();
12000
- } else if (!result2.isCompleted) {
12001
- progress.consecutiveIdleIterations++;
12002
- }
12417
+ updateProgressAfterStep(progress, result2.toolsExecuted);
12003
12418
  if (result2.isCompleted) {
12004
- return {
12005
- output: result2.output,
12006
- iterations: iteration + 1,
12007
- toolsExecuted: progress.totalToolsExecuted,
12008
- isCompleted: true
12009
- };
12010
- }
12011
- if (progress.consecutiveIdleIterations >= AGENT_LIMITS.MAX_CONSECUTIVE_IDLE) {
12012
- progress.consecutiveIdleIterations = 0;
12013
- const nudge = buildDeadlockNudge(
12014
- this.state.getPhase(),
12015
- this.state.getTargets().size,
12016
- this.state.getFindings().length
12017
- );
12018
- messages.push({ role: LLM_ROLES.USER, content: nudge });
12419
+ return buildSuccessResult(result2.output, iteration, progress.totalToolsExecuted);
12019
12420
  }
12421
+ handleIdleIteration(
12422
+ progress,
12423
+ messages,
12424
+ () => this.state.getPhase(),
12425
+ () => this.state.getTargets().size,
12426
+ () => this.state.getFindings().length
12427
+ );
12020
12428
  } catch (error) {
12021
12429
  const action = handleLoopError(
12022
12430
  error,
@@ -12024,35 +12432,20 @@ var CoreAgent = class {
12024
12432
  progress,
12025
12433
  iteration,
12026
12434
  consecutiveLLMErrors,
12027
- maxConsecutiveLLMErrors,
12435
+ AGENT_LIMITS.MAX_CONSECUTIVE_LLM_ERRORS,
12028
12436
  this.abortController,
12029
12437
  this.events,
12030
12438
  () => this.state.getPhase()
12031
12439
  );
12032
12440
  if (action.action === "return") return action.result;
12033
- if (action.action === "continue") {
12034
- if (error instanceof LLMError && error.errorInfo.type === "rate_limit") {
12035
- consecutiveLLMErrors++;
12036
- } else {
12037
- consecutiveLLMErrors = 0;
12038
- }
12039
- continue;
12040
- }
12441
+ consecutiveLLMErrors = error instanceof LLMError && error.errorInfo.type === "rate_limit" ? consecutiveLLMErrors + 1 : 0;
12041
12442
  }
12042
12443
  }
12043
- return {
12044
- output: `Max iterations (${this.maxIterations}) reached. Tools: ${progress.totalToolsExecuted}.`,
12045
- iterations: this.maxIterations,
12046
- toolsExecuted: progress.totalToolsExecuted,
12047
- isCompleted: false
12048
- };
12444
+ return buildMaxIterationsResult(this.maxIterations, progress.totalToolsExecuted);
12049
12445
  }
12050
- // ─────────────────────────────────────────────────────────────────
12051
- // SUBSECTION: Step Execution
12052
- // ─────────────────────────────────────────────────────────────────
12053
12446
  async step(iteration, messages, systemPrompt, progress) {
12054
12447
  const phase = this.state.getPhase();
12055
- emitThink(
12448
+ emitIterationThink(
12056
12449
  this.events,
12057
12450
  iteration,
12058
12451
  phase,
@@ -12060,61 +12453,37 @@ var CoreAgent = class {
12060
12453
  this.state.getFindings().length,
12061
12454
  progress
12062
12455
  );
12063
- if (this.toolRegistry && !this.toolExecutor) {
12064
- this.toolExecutor = new ToolExecutor({
12065
- state: this.state,
12066
- events: this.events,
12067
- toolRegistry: this.toolRegistry,
12068
- llm: this.llm
12069
- });
12070
- }
12456
+ this.ensureToolExecutor();
12071
12457
  this.toolExecutor?.clearTurnState();
12072
- const callbacks = buildStreamCallbacks(this.events, phase, this.abortController);
12073
- const response = await this.llm.generateResponseStream(
12458
+ const { response, hadReasoningEnd } = await getLLMStreamResponse(
12459
+ this.llm,
12074
12460
  messages,
12075
12461
  this.toolExecutor?.getToolSchemas() ?? [],
12076
12462
  systemPrompt,
12077
- callbacks
12463
+ this.events,
12464
+ phase,
12465
+ this.abortController
12078
12466
  );
12079
- if (response.reasoning && !callbacks.hadReasoningEnd()) {
12080
- emitReasoningStart(this.events, phase);
12081
- emitReasoningDelta(this.events, response.reasoning, phase);
12082
- emitReasoningEnd(this.events, phase);
12083
- }
12084
- if (response.content?.trim()) {
12085
- if (!response.reasoning && !callbacks.hadReasoningEnd()) {
12086
- emitReasoningStart(this.events, phase);
12087
- emitReasoningDelta(this.events, response.content.trim(), phase);
12088
- emitReasoningEnd(this.events, phase);
12089
- } else {
12090
- this.events.emit({
12091
- type: EVENT_TYPES.AI_RESPONSE,
12092
- timestamp: Date.now(),
12093
- data: { content: response.content.trim(), phase }
12094
- });
12095
- }
12096
- }
12097
- messages.push({ role: LLM_ROLES.ASSISTANT, content: response.content });
12467
+ handleResponseContent(response, this.events, phase, hadReasoningEnd);
12468
+ addAssistantMessage(messages, response.content);
12098
12469
  if (!response.toolCalls?.length) {
12099
12470
  return { output: response.content, toolsExecuted: 0, isCompleted: false };
12100
12471
  }
12101
- const results = await this.toolExecutor.processToolCalls(response.toolCalls, progress);
12102
- this.addToolResultsToMessages(messages, results);
12103
- return { output: "", toolsExecuted: results.length, isCompleted: false };
12104
- }
12105
- // ─────────────────────────────────────────────────────────────────
12106
- // SUBSECTION: Message Helpers
12107
- // ─────────────────────────────────────────────────────────────────
12108
- addToolResultsToMessages(messages, results) {
12109
- for (const res of results) {
12110
- messages.push({
12111
- role: LLM_ROLES.USER,
12112
- content: [{
12113
- type: LLM_BLOCK_TYPE.TOOL_RESULT,
12114
- tool_use_id: res.toolCallId,
12115
- content: res.output,
12116
- is_error: !!res.error
12117
- }]
12472
+ const toolsExecuted = await processToolCallsInStep(
12473
+ this.toolExecutor,
12474
+ response.toolCalls,
12475
+ messages,
12476
+ progress
12477
+ );
12478
+ return { output: "", toolsExecuted, isCompleted: false };
12479
+ }
12480
+ ensureToolExecutor() {
12481
+ if (this.toolRegistry && !this.toolExecutor) {
12482
+ this.toolExecutor = createToolExecutor({
12483
+ state: this.state,
12484
+ events: this.events,
12485
+ toolRegistry: this.toolRegistry,
12486
+ llm: this.llm
12118
12487
  });
12119
12488
  }
12120
12489
  }
@@ -14075,6 +14444,325 @@ var useAgent = (shouldAutoApprove, target) => {
14075
14444
  };
14076
14445
  };
14077
14446
 
14447
+ // src/platform/tui/hooks/commands/index.ts
14448
+ import { useCallback as useCallback3 } from "react";
14449
+
14450
+ // src/platform/tui/hooks/commands/session-commands.ts
14451
+ var createSessionCommands = (ctx) => ({
14452
+ [UI_COMMANDS.HELP]: () => {
14453
+ ctx.addMessage("system", HELP_TEXT);
14454
+ },
14455
+ [UI_COMMANDS.HELP_SHORT]: () => {
14456
+ ctx.addMessage("system", HELP_TEXT);
14457
+ },
14458
+ [UI_COMMANDS.CLEAR]: async () => {
14459
+ if (ctx.isProcessingRef.current) {
14460
+ ctx.addMessage("error", "Cannot /clear while agent is running. Press Esc to abort first.");
14461
+ return;
14462
+ }
14463
+ ctx.setMessages([]);
14464
+ const result2 = await ctx.agent.resetSession();
14465
+ if (result2.cleared.length > 0) {
14466
+ ctx.addMessage("system", `[reset] Session cleared: ${result2.cleared.join(", ")}`);
14467
+ } else {
14468
+ ctx.addMessage("system", "[reset] Session clean");
14469
+ }
14470
+ if (result2.errors.length > 0) {
14471
+ ctx.addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
14472
+ }
14473
+ ctx.refreshStats();
14474
+ },
14475
+ [UI_COMMANDS.CLEAR_SHORT]: async () => {
14476
+ if (ctx.isProcessingRef.current) {
14477
+ ctx.addMessage("error", "Cannot /clear while agent is running. Press Esc to abort first.");
14478
+ return;
14479
+ }
14480
+ ctx.setMessages([]);
14481
+ const result2 = await ctx.agent.resetSession();
14482
+ if (result2.cleared.length > 0) {
14483
+ ctx.addMessage("system", `[reset] Session cleared: ${result2.cleared.join(", ")}`);
14484
+ } else {
14485
+ ctx.addMessage("system", "[reset] Session clean");
14486
+ }
14487
+ if (result2.errors.length > 0) {
14488
+ ctx.addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
14489
+ }
14490
+ ctx.refreshStats();
14491
+ },
14492
+ [UI_COMMANDS.EXIT]: () => {
14493
+ ctx.handleExit();
14494
+ },
14495
+ [UI_COMMANDS.QUIT]: () => {
14496
+ ctx.handleExit();
14497
+ },
14498
+ [UI_COMMANDS.EXIT_SHORT]: () => {
14499
+ ctx.handleExit();
14500
+ }
14501
+ });
14502
+
14503
+ // src/platform/tui/hooks/commands/target-commands.ts
14504
+ var createTargetCommands = (ctx) => ({
14505
+ [UI_COMMANDS.TARGET]: (args) => {
14506
+ if (!args[0]) {
14507
+ ctx.addMessage("error", "Usage: /target <ip>");
14508
+ return;
14509
+ }
14510
+ ctx.agent.addTarget(args[0]);
14511
+ ctx.agent.setScope([args[0]]);
14512
+ ctx.addMessage("system", `Target \u2192 ${args[0]}`);
14513
+ },
14514
+ [UI_COMMANDS.TARGET_SHORT]: (args) => {
14515
+ if (!args[0]) {
14516
+ ctx.addMessage("error", "Usage: /target <ip>");
14517
+ return;
14518
+ }
14519
+ ctx.agent.addTarget(args[0]);
14520
+ ctx.agent.setScope([args[0]]);
14521
+ ctx.addMessage("system", `Target \u2192 ${args[0]}`);
14522
+ },
14523
+ [UI_COMMANDS.START]: async (args) => {
14524
+ if (!ctx.agent.getState().getTargets().size) {
14525
+ ctx.addMessage("error", "Set target first: /target <ip>");
14526
+ return;
14527
+ }
14528
+ if (!ctx.autoApproveModeRef.current) {
14529
+ ctx.setAutoApproveMode(true);
14530
+ ctx.agent.setAutoApprove(true);
14531
+ ctx.addMessage("system", "[auto] Autonomous mode enabled");
14532
+ }
14533
+ ctx.addMessage("system", "Starting penetration test...");
14534
+ const targets = Array.from(ctx.agent.getState().getTargets().keys());
14535
+ const targetInfo = targets.length > 0 ? ` against ${targets.join(", ")}` : "";
14536
+ await ctx.executeTask(args.join(" ") || `Perform comprehensive penetration testing${targetInfo}`);
14537
+ },
14538
+ [UI_COMMANDS.START_SHORT]: async (args) => {
14539
+ if (!ctx.agent.getState().getTargets().size) {
14540
+ ctx.addMessage("error", "Set target first: /target <ip>");
14541
+ return;
14542
+ }
14543
+ if (!ctx.autoApproveModeRef.current) {
14544
+ ctx.setAutoApproveMode(true);
14545
+ ctx.agent.setAutoApprove(true);
14546
+ ctx.addMessage("system", "[auto] Autonomous mode enabled");
14547
+ }
14548
+ ctx.addMessage("system", "Starting penetration test...");
14549
+ const targets = Array.from(ctx.agent.getState().getTargets().keys());
14550
+ const targetInfo = targets.length > 0 ? ` against ${targets.join(", ")}` : "";
14551
+ await ctx.executeTask(args.join(" ") || `Perform comprehensive penetration testing${targetInfo}`);
14552
+ }
14553
+ });
14554
+
14555
+ // src/platform/tui/hooks/commands/formatters.ts
14556
+ var confIcon = (c) => {
14557
+ if (c >= 100) return "\u{1F534}";
14558
+ if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "\u{1F7E0}";
14559
+ if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "\u{1F7E1}";
14560
+ if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "\u{1F7E2}";
14561
+ return "\u26AA";
14562
+ };
14563
+ var confLabel = (c) => {
14564
+ if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "confirmed";
14565
+ if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "probable";
14566
+ if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "possible";
14567
+ return "speculative";
14568
+ };
14569
+ var formatFindings = (findings) => {
14570
+ if (!findings.length) return "No findings.";
14571
+ const sorted = [...findings].sort((a, b) => b.confidence - a.confidence);
14572
+ const findingLines = [];
14573
+ const nConfirmed = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED).length;
14574
+ const nProbable = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.PROBABLE && f.confidence < CONFIDENCE_THRESHOLDS.CONFIRMED).length;
14575
+ const nPossible = sorted.filter((f) => f.confidence < CONFIDENCE_THRESHOLDS.PROBABLE).length;
14576
+ findingLines.push(`\u2500\u2500\u2500 ${findings.length} Findings \u2500\u2500 \u{1F534}\u{1F7E0} confirmed:${nConfirmed} \u{1F7E1} probable:${nProbable} \u{1F7E2}\u26AA possible:${nPossible} \u2500\u2500\u2500`);
14577
+ findingLines.push("");
14578
+ sorted.forEach((f) => {
14579
+ const icon = confIcon(f.confidence);
14580
+ const label = confLabel(f.confidence);
14581
+ const scoreBar = `[${String(f.confidence).padStart(3, " ")}/100]`;
14582
+ const atk = f.attackPattern ? ` \u2502 ATT&CK: ${f.attackPattern}` : "";
14583
+ const cat = f.category ? ` \u2502 ${f.category}` : "";
14584
+ findingLines.push(` ${icon} ${scoreBar} ${f.title}`);
14585
+ findingLines.push(` ${label.toUpperCase()} \u2502 ${f.severity.toUpperCase()}${atk}${cat}`);
14586
+ if (f.affected.length > 0) {
14587
+ findingLines.push(` Affected: ${f.affected.join(", ")}`);
14588
+ }
14589
+ if (f.description) {
14590
+ findingLines.push(` ${f.description}`);
14591
+ }
14592
+ if (f.evidence.length > 0) {
14593
+ findingLines.push(` Evidence:`);
14594
+ f.evidence.forEach((e) => {
14595
+ findingLines.push(` \u25B8 ${e}`);
14596
+ });
14597
+ }
14598
+ if (f.remediation) {
14599
+ findingLines.push(` Fix: ${f.remediation}`);
14600
+ }
14601
+ findingLines.push("");
14602
+ });
14603
+ return findingLines.join("\n");
14604
+ };
14605
+
14606
+ // src/platform/tui/hooks/commands/display-commands.ts
14607
+ var createDisplayCommands = (ctx) => ({
14608
+ [UI_COMMANDS.FINDINGS]: () => {
14609
+ const findings = ctx.agent.getState().getFindings();
14610
+ ctx.addMessage("system", formatFindings(findings));
14611
+ },
14612
+ [UI_COMMANDS.FINDINGS_SHORT]: () => {
14613
+ const findings = ctx.agent.getState().getFindings();
14614
+ ctx.addMessage("system", formatFindings(findings));
14615
+ },
14616
+ [UI_COMMANDS.ASSETS]: () => {
14617
+ ctx.addMessage("status", formatInlineStatus());
14618
+ },
14619
+ [UI_COMMANDS.ASSETS_SHORT]: () => {
14620
+ ctx.addMessage("status", formatInlineStatus());
14621
+ },
14622
+ [UI_COMMANDS.LOGS]: (args) => {
14623
+ if (!args[0]) {
14624
+ ctx.addMessage("error", "Usage: /logs <process_id>");
14625
+ return;
14626
+ }
14627
+ const procData = getProcessOutput(args[0]);
14628
+ if (!procData) {
14629
+ ctx.addMessage("error", `Asset [${args[0]}] not found.`);
14630
+ return;
14631
+ }
14632
+ ctx.addMessage("system", `--- Log for [${args[0]}] (${procData.role}) ---
14633
+ ${procData.stdout || "(no output)"}
14634
+ --- End Log ---`);
14635
+ },
14636
+ [UI_COMMANDS.LOGS_SHORT]: (args) => {
14637
+ if (!args[0]) {
14638
+ ctx.addMessage("error", "Usage: /logs <process_id>");
14639
+ return;
14640
+ }
14641
+ const procData = getProcessOutput(args[0]);
14642
+ if (!procData) {
14643
+ ctx.addMessage("error", `Asset [${args[0]}] not found.`);
14644
+ return;
14645
+ }
14646
+ ctx.addMessage("system", `--- Log for [${args[0]}] (${procData.role}) ---
14647
+ ${procData.stdout || "(no output)"}
14648
+ --- End Log ---`);
14649
+ },
14650
+ [UI_COMMANDS.GRAPH]: () => {
14651
+ ctx.addMessage("system", ctx.agent.getState().attackGraph.toASCII());
14652
+ },
14653
+ [UI_COMMANDS.GRAPH_SHORT]: () => {
14654
+ ctx.addMessage("system", ctx.agent.getState().attackGraph.toASCII());
14655
+ },
14656
+ [UI_COMMANDS.PATHS]: () => {
14657
+ ctx.addMessage("system", ctx.agent.getState().attackGraph.toPathsList());
14658
+ },
14659
+ [UI_COMMANDS.PATHS_SHORT]: () => {
14660
+ ctx.addMessage("system", ctx.agent.getState().attackGraph.toPathsList());
14661
+ }
14662
+ });
14663
+
14664
+ // src/platform/tui/hooks/commands/toggle-commands.ts
14665
+ var createToggleCommands = (ctx) => ({
14666
+ [UI_COMMANDS.CTF]: () => {
14667
+ const ctfEnabled = ctx.agent.toggleCtfMode();
14668
+ ctx.addMessage("system", ctfEnabled ? "\u{1F3F4} Flag auto-detection ON" : "\u{1F3F4} Flag auto-detection OFF");
14669
+ },
14670
+ [UI_COMMANDS.TOR]: () => {
14671
+ const newTorState = !isTorEnabled();
14672
+ setTorEnabled(newTorState);
14673
+ ctx.addMessage("system", newTorState ? "\u{1F9C5} Tor proxy ON \u2014 target traffic routed through SOCKS5 (proxychains4 / native flags)" : "\u{1F9C5} Tor proxy OFF \u2014 direct connections");
14674
+ },
14675
+ [UI_COMMANDS.AUTO]: () => {
14676
+ ctx.setAutoApproveMode((prev) => {
14677
+ const newVal = !prev;
14678
+ ctx.agent.setAutoApprove(newVal);
14679
+ ctx.addMessage("system", newVal ? "\u{1F7E2} Auto-approve ON" : "\u{1F534} Auto-approve OFF");
14680
+ return newVal;
14681
+ });
14682
+ }
14683
+ });
14684
+
14685
+ // src/platform/tui/hooks/commands/index.ts
14686
+ var useCommands = (props) => {
14687
+ const ctx = {
14688
+ agent: props.agent,
14689
+ addMessage: props.addMessage,
14690
+ setMessages: props.setMessages,
14691
+ executeTask: props.executeTask,
14692
+ refreshStats: props.refreshStats,
14693
+ setAutoApproveMode: props.setAutoApproveMode,
14694
+ handleExit: props.handleExit,
14695
+ isProcessingRef: props.isProcessingRef,
14696
+ autoApproveModeRef: props.autoApproveModeRef
14697
+ };
14698
+ const handlers = {
14699
+ ...createSessionCommands(ctx),
14700
+ ...createTargetCommands(ctx),
14701
+ ...createDisplayCommands(ctx),
14702
+ ...createToggleCommands(ctx)
14703
+ };
14704
+ const handleCommand = useCallback3(async (cmd, args) => {
14705
+ const handler = handlers[cmd];
14706
+ if (handler) {
14707
+ await handler(args);
14708
+ } else {
14709
+ ctx.addMessage("error", `Unknown command: /${cmd}`);
14710
+ }
14711
+ }, [handlers, ctx]);
14712
+ return { handleCommand };
14713
+ };
14714
+
14715
+ // src/platform/tui/hooks/useKeyboardShortcuts.ts
14716
+ import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef4 } from "react";
14717
+ import { useInput } from "ink";
14718
+ var useKeyboardShortcuts = ({
14719
+ addMessage,
14720
+ handleExit,
14721
+ abort,
14722
+ cancelInputRequest,
14723
+ isProcessingRef,
14724
+ inputRequestRef
14725
+ }) => {
14726
+ const ctrlCTimerRef = useRef4(null);
14727
+ const ctrlCPressedRef = useRef4(false);
14728
+ const handleCtrlC = useCallback4(() => {
14729
+ if (ctrlCPressedRef.current) {
14730
+ if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
14731
+ handleExit();
14732
+ return;
14733
+ }
14734
+ ctrlCPressedRef.current = true;
14735
+ addMessage("system", "\u26A0\uFE0F Press Ctrl+C again within 3 seconds to exit.");
14736
+ if (isProcessingRef.current) abort();
14737
+ ctrlCTimerRef.current = setTimeout(() => {
14738
+ ctrlCPressedRef.current = false;
14739
+ ctrlCTimerRef.current = null;
14740
+ }, DISPLAY_LIMITS.EXIT_DELAY);
14741
+ }, [handleExit, addMessage, abort, isProcessingRef]);
14742
+ useEffect3(() => {
14743
+ return () => {
14744
+ if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
14745
+ };
14746
+ }, []);
14747
+ useInput(useCallback4((ch, key) => {
14748
+ if (key.escape) {
14749
+ if (inputRequestRef.current.status === "active") cancelInputRequest();
14750
+ else if (isProcessingRef.current) abort();
14751
+ }
14752
+ if (key.ctrl && ch === "c") handleCtrlC();
14753
+ }, [cancelInputRequest, abort, handleCtrlC, isProcessingRef, inputRequestRef]));
14754
+ useEffect3(() => {
14755
+ const onSignal = () => handleCtrlC();
14756
+ process.on("SIGINT", onSignal);
14757
+ process.on("SIGTERM", onSignal);
14758
+ return () => {
14759
+ process.off("SIGINT", onSignal);
14760
+ process.off("SIGTERM", onSignal);
14761
+ };
14762
+ }, [handleCtrlC]);
14763
+ return { handleCtrlC };
14764
+ };
14765
+
14078
14766
  // src/platform/tui/components/MessageList.tsx
14079
14767
  import { memo } from "react";
14080
14768
  import { Box as Box2, Text as Text2, Static } from "ink";
@@ -14324,24 +15012,24 @@ var MessageList = memo(({ messages }) => {
14324
15012
  });
14325
15013
 
14326
15014
  // src/platform/tui/components/StatusDisplay.tsx
14327
- import { memo as memo3, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
15015
+ import { memo as memo3, useEffect as useEffect5, useRef as useRef5, useState as useState4 } from "react";
14328
15016
  import { Box as Box3, Text as Text4 } from "ink";
14329
15017
 
14330
15018
  // src/platform/tui/components/MusicSpinner.tsx
14331
- import { useState as useState3, useEffect as useEffect3, memo as memo2 } from "react";
15019
+ import { useState as useState3, useEffect as useEffect4, memo as memo2 } from "react";
14332
15020
  import { Text as Text3 } from "ink";
14333
15021
  import { jsx as jsx3 } from "react/jsx-runtime";
14334
- var FRAMES = ["\u2669", "\u266A", "\u266B", "\u266C", "\u266B", "\u266A"];
14335
- var INTERVAL = 500;
15022
+ var FRAMES = ["\xB7", "\u2736", "\u2737", "\u2738", "\u2739", "\u273A", "\u2739", "\u2738", "\u2737", "\u2736"];
15023
+ var INTERVAL = 100;
14336
15024
  var MusicSpinner = memo2(({ color }) => {
14337
15025
  const [index, setIndex] = useState3(0);
14338
- useEffect3(() => {
15026
+ useEffect4(() => {
14339
15027
  const timer = setInterval(() => {
14340
15028
  setIndex((i) => (i + 1) % FRAMES.length);
14341
15029
  }, INTERVAL);
14342
15030
  return () => clearInterval(timer);
14343
15031
  }, []);
14344
- return /* @__PURE__ */ jsx3(Text3, { color, children: FRAMES[index] });
15032
+ return /* @__PURE__ */ jsx3(Text3, { color: color || "yellow", children: FRAMES[index] });
14345
15033
  });
14346
15034
 
14347
15035
  // src/platform/tui/components/StatusDisplay.tsx
@@ -14356,9 +15044,9 @@ var StatusDisplay = memo3(({
14356
15044
  return err.length > DISPLAY_LIMITS.RETRY_ERROR_PREVIEW ? err.substring(0, DISPLAY_LIMITS.RETRY_ERROR_TRUNCATED) + "..." : err;
14357
15045
  };
14358
15046
  const [statusElapsed, setStatusElapsed] = useState4(0);
14359
- const statusTimerRef = useRef4(null);
14360
- const statusStartRef = useRef4(Date.now());
14361
- useEffect4(() => {
15047
+ const statusTimerRef = useRef5(null);
15048
+ const statusStartRef = useRef5(Date.now());
15049
+ useEffect5(() => {
14362
15050
  if (statusTimerRef.current) clearInterval(statusTimerRef.current);
14363
15051
  if (isProcessing && currentStatus) {
14364
15052
  statusStartRef.current = Date.now();
@@ -14419,8 +15107,8 @@ var StatusDisplay = memo3(({
14419
15107
  });
14420
15108
 
14421
15109
  // src/platform/tui/components/ChatInput.tsx
14422
- import { useMemo, useCallback as useCallback3, useRef as useRef5, memo as memo4, useState as useState5 } from "react";
14423
- import { Box as Box4, Text as Text5, useInput } from "ink";
15110
+ import { useMemo, useCallback as useCallback5, useRef as useRef6, memo as memo4, useState as useState5 } from "react";
15111
+ import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
14424
15112
  import TextInput from "ink-text-input";
14425
15113
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
14426
15114
  var MAX_SUGGESTIONS = 6;
@@ -14442,18 +15130,18 @@ var ChatInput = memo4(({
14442
15130
  return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
14443
15131
  }, [isSlashMode, partialCmd, hasArgs]);
14444
15132
  const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
14445
- const suggestionsRef = useRef5(suggestions);
15133
+ const suggestionsRef = useRef6(suggestions);
14446
15134
  suggestionsRef.current = suggestions;
14447
- const isSlashModeRef = useRef5(isSlashMode);
15135
+ const isSlashModeRef = useRef6(isSlashMode);
14448
15136
  isSlashModeRef.current = isSlashMode;
14449
- const hasArgsRef = useRef5(hasArgs);
15137
+ const hasArgsRef = useRef6(hasArgs);
14450
15138
  hasArgsRef.current = hasArgs;
14451
- const inputRequestRef = useRef5(inputRequest);
15139
+ const inputRequestRef = useRef6(inputRequest);
14452
15140
  inputRequestRef.current = inputRequest;
14453
- const onChangeRef = useRef5(onChange);
15141
+ const onChangeRef = useRef6(onChange);
14454
15142
  onChangeRef.current = onChange;
14455
15143
  const [inputKey, setInputKey] = useState5(0);
14456
- useInput(useCallback3((_input, key) => {
15144
+ useInput2(useCallback5((_input, key) => {
14457
15145
  if (inputRequestRef.current.status === "active") return;
14458
15146
  if (key.tab && isSlashModeRef.current && !hasArgsRef.current && suggestionsRef.current.length > 0) {
14459
15147
  const best = suggestionsRef.current[0];
@@ -14614,13 +15302,13 @@ var App = ({ autoApprove = false, target }) => {
14614
15302
  addMessage,
14615
15303
  refreshStats
14616
15304
  } = useAgent(autoApproveMode, target);
14617
- const isProcessingRef = useRef6(isProcessing);
15305
+ const isProcessingRef = useRef7(isProcessing);
14618
15306
  isProcessingRef.current = isProcessing;
14619
- const autoApproveModeRef = useRef6(autoApproveMode);
15307
+ const autoApproveModeRef = useRef7(autoApproveMode);
14620
15308
  autoApproveModeRef.current = autoApproveMode;
14621
- const inputRequestRef = useRef6(inputRequest);
15309
+ const inputRequestRef = useRef7(inputRequest);
14622
15310
  inputRequestRef.current = inputRequest;
14623
- const handleExit = useCallback4(() => {
15311
+ const handleExit = useCallback6(() => {
14624
15312
  const ir = inputRequestRef.current;
14625
15313
  if (ir.status === "active") {
14626
15314
  ir.resolve(null);
@@ -14631,166 +15319,18 @@ var App = ({ autoApprove = false, target }) => {
14631
15319
  exit();
14632
15320
  setTimeout(() => process.exit(0), DISPLAY_LIMITS.EXIT_DELAY);
14633
15321
  }, [exit, setInputRequest]);
14634
- const handleCommand = useCallback4(async (cmd, args) => {
14635
- switch (cmd) {
14636
- case UI_COMMANDS.HELP:
14637
- case UI_COMMANDS.HELP_SHORT:
14638
- addMessage("system", HELP_TEXT);
14639
- break;
14640
- case UI_COMMANDS.CLEAR:
14641
- case UI_COMMANDS.CLEAR_SHORT: {
14642
- if (isProcessingRef.current) {
14643
- addMessage("error", "Cannot /clear while agent is running. Press Esc to abort first.");
14644
- break;
14645
- }
14646
- setMessages([]);
14647
- const result2 = await agent.resetSession();
14648
- if (result2.cleared.length > 0) {
14649
- addMessage("system", `[reset] Session cleared: ${result2.cleared.join(", ")}`);
14650
- } else {
14651
- addMessage("system", "[reset] Session clean");
14652
- }
14653
- if (result2.errors.length > 0) {
14654
- addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
14655
- }
14656
- refreshStats();
14657
- break;
14658
- }
14659
- case UI_COMMANDS.TARGET:
14660
- case UI_COMMANDS.TARGET_SHORT:
14661
- if (!args[0]) {
14662
- addMessage("error", "Usage: /target <ip>");
14663
- break;
14664
- }
14665
- agent.addTarget(args[0]);
14666
- agent.setScope([args[0]]);
14667
- addMessage("system", `Target \u2192 ${args[0]}`);
14668
- break;
14669
- case UI_COMMANDS.START:
14670
- case UI_COMMANDS.START_SHORT:
14671
- if (!agent.getState().getTargets().size) {
14672
- addMessage("error", "Set target first: /target <ip>");
14673
- break;
14674
- }
14675
- if (!autoApproveModeRef.current) {
14676
- setAutoApproveMode(true);
14677
- agent.setAutoApprove(true);
14678
- addMessage("system", "[auto] Autonomous mode enabled");
14679
- }
14680
- addMessage("system", "Starting penetration test...");
14681
- const targets = Array.from(agent.getState().getTargets().keys());
14682
- const targetInfo = targets.length > 0 ? ` against ${targets.join(", ")}` : "";
14683
- executeTask(args.join(" ") || `Perform comprehensive penetration testing${targetInfo}`);
14684
- break;
14685
- case UI_COMMANDS.FINDINGS:
14686
- case UI_COMMANDS.FINDINGS_SHORT: {
14687
- const findings = agent.getState().getFindings();
14688
- if (!findings.length) {
14689
- addMessage("system", "No findings.");
14690
- break;
14691
- }
14692
- const sorted = [...findings].sort((a, b) => b.confidence - a.confidence);
14693
- const confIcon = (c) => {
14694
- if (c >= 100) return "\u{1F534}";
14695
- if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "\u{1F7E0}";
14696
- if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "\u{1F7E1}";
14697
- if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "\u{1F7E2}";
14698
- return "\u26AA";
14699
- };
14700
- const confLabel = (c) => {
14701
- if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "confirmed";
14702
- if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "probable";
14703
- if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "possible";
14704
- return "speculative";
14705
- };
14706
- const findingLines = [];
14707
- const nConfirmed = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED).length;
14708
- const nProbable = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.PROBABLE && f.confidence < CONFIDENCE_THRESHOLDS.CONFIRMED).length;
14709
- const nPossible = sorted.filter((f) => f.confidence < CONFIDENCE_THRESHOLDS.PROBABLE).length;
14710
- findingLines.push(`\u2500\u2500\u2500 ${findings.length} Findings \u2500\u2500 \u{1F534}\u{1F7E0} confirmed:${nConfirmed} \u{1F7E1} probable:${nProbable} \u{1F7E2}\u26AA possible:${nPossible} \u2500\u2500\u2500`);
14711
- findingLines.push("");
14712
- sorted.forEach((f, i) => {
14713
- const icon = confIcon(f.confidence);
14714
- const label = confLabel(f.confidence);
14715
- const scoreBar = `[${String(f.confidence).padStart(3, " ")}/100]`;
14716
- const atk = f.attackPattern ? ` \u2502 ATT&CK: ${f.attackPattern}` : "";
14717
- const cat = f.category ? ` \u2502 ${f.category}` : "";
14718
- findingLines.push(` ${icon} ${scoreBar} ${f.title}`);
14719
- findingLines.push(` ${label.toUpperCase()} \u2502 ${f.severity.toUpperCase()}${atk}${cat}`);
14720
- if (f.affected.length > 0) {
14721
- findingLines.push(` Affected: ${f.affected.join(", ")}`);
14722
- }
14723
- if (f.description) {
14724
- findingLines.push(` ${f.description}`);
14725
- }
14726
- if (f.evidence.length > 0) {
14727
- findingLines.push(` Evidence:`);
14728
- f.evidence.forEach((e) => {
14729
- findingLines.push(` \u25B8 ${e}`);
14730
- });
14731
- }
14732
- if (f.remediation) {
14733
- findingLines.push(` Fix: ${f.remediation}`);
14734
- }
14735
- findingLines.push("");
14736
- });
14737
- addMessage("system", findingLines.join("\n"));
14738
- break;
14739
- }
14740
- case UI_COMMANDS.ASSETS:
14741
- case UI_COMMANDS.ASSETS_SHORT:
14742
- addMessage("status", formatInlineStatus());
14743
- break;
14744
- case UI_COMMANDS.LOGS:
14745
- case UI_COMMANDS.LOGS_SHORT:
14746
- if (!args[0]) {
14747
- addMessage("error", "Usage: /logs <process_id>");
14748
- break;
14749
- }
14750
- const procData = getProcessOutput(args[0]);
14751
- if (!procData) {
14752
- addMessage("error", `Asset [${args[0]}] not found.`);
14753
- break;
14754
- }
14755
- addMessage("system", `--- Log for [${args[0]}] (${procData.role}) ---
14756
- ${procData.stdout || "(no output)"}
14757
- --- End Log ---`);
14758
- break;
14759
- case UI_COMMANDS.CTF:
14760
- const ctfEnabled = agent.toggleCtfMode();
14761
- addMessage("system", ctfEnabled ? "\u{1F3F4} Flag auto-detection ON" : "\u{1F3F4} Flag auto-detection OFF");
14762
- break;
14763
- case UI_COMMANDS.TOR:
14764
- const newTorState = !isTorEnabled();
14765
- setTorEnabled(newTorState);
14766
- addMessage("system", newTorState ? "\u{1F9C5} Tor proxy ON \u2014 target traffic routed through SOCKS5 (proxychains4 / native flags)" : "\u{1F9C5} Tor proxy OFF \u2014 direct connections");
14767
- break;
14768
- case UI_COMMANDS.GRAPH:
14769
- case UI_COMMANDS.GRAPH_SHORT:
14770
- addMessage("system", agent.getState().attackGraph.toASCII());
14771
- break;
14772
- case UI_COMMANDS.PATHS:
14773
- case UI_COMMANDS.PATHS_SHORT:
14774
- addMessage("system", agent.getState().attackGraph.toPathsList());
14775
- break;
14776
- case UI_COMMANDS.AUTO:
14777
- setAutoApproveMode((prev) => {
14778
- const newVal = !prev;
14779
- agent.setAutoApprove(newVal);
14780
- addMessage("system", newVal ? "\u{1F7E2} Auto-approve ON" : "\u{1F534} Auto-approve OFF");
14781
- return newVal;
14782
- });
14783
- break;
14784
- case UI_COMMANDS.EXIT:
14785
- case UI_COMMANDS.QUIT:
14786
- case UI_COMMANDS.EXIT_SHORT:
14787
- handleExit();
14788
- break;
14789
- default:
14790
- addMessage("error", `Unknown command: /${cmd}`);
14791
- }
14792
- }, [agent, addMessage, executeTask, setMessages, handleExit, refreshStats]);
14793
- const handleSubmit = useCallback4(async (value) => {
15322
+ const { handleCommand } = useCommands({
15323
+ agent,
15324
+ addMessage,
15325
+ setMessages,
15326
+ executeTask,
15327
+ refreshStats,
15328
+ setAutoApproveMode,
15329
+ handleExit,
15330
+ isProcessingRef,
15331
+ autoApproveModeRef
15332
+ });
15333
+ const handleSubmit = useCallback6(async (value) => {
14794
15334
  const trimmed = value.trim();
14795
15335
  if (!trimmed) return;
14796
15336
  setInput("");
@@ -14805,7 +15345,7 @@ ${procData.stdout || "(no output)"}
14805
15345
  await executeTask(trimmed);
14806
15346
  }
14807
15347
  }, [agent, addMessage, executeTask, handleCommand]);
14808
- const handleSecretSubmit = useCallback4((value) => {
15348
+ const handleSecretSubmit = useCallback6((value) => {
14809
15349
  const ir = inputRequestRef.current;
14810
15350
  if (ir.status !== "active") return;
14811
15351
  const displayText = ir.isPassword ? "\u2022".repeat(value.length) : value;
@@ -14815,43 +15355,14 @@ ${procData.stdout || "(no output)"}
14815
15355
  setInputRequest({ status: "inactive" });
14816
15356
  setSecretInput("");
14817
15357
  }, [addMessage, setInputRequest]);
14818
- const ctrlCTimerRef = useRef6(null);
14819
- const ctrlCPressedRef = useRef6(false);
14820
- const handleCtrlC = useCallback4(() => {
14821
- if (ctrlCPressedRef.current) {
14822
- if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
14823
- handleExit();
14824
- return;
14825
- }
14826
- ctrlCPressedRef.current = true;
14827
- addMessage("system", "\u26A0\uFE0F Press Ctrl+C again within 3 seconds to exit.");
14828
- if (isProcessingRef.current) abort();
14829
- ctrlCTimerRef.current = setTimeout(() => {
14830
- ctrlCPressedRef.current = false;
14831
- ctrlCTimerRef.current = null;
14832
- }, 3e3);
14833
- }, [handleExit, addMessage, abort]);
14834
- useEffect5(() => {
14835
- return () => {
14836
- if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
14837
- };
14838
- }, []);
14839
- useInput2(useCallback4((ch, key) => {
14840
- if (key.escape) {
14841
- if (inputRequestRef.current.status === "active") cancelInputRequest();
14842
- else if (isProcessingRef.current) abort();
14843
- }
14844
- if (key.ctrl && ch === "c") handleCtrlC();
14845
- }, [cancelInputRequest, abort, handleCtrlC]));
14846
- useEffect5(() => {
14847
- const onSignal = () => handleCtrlC();
14848
- process.on("SIGINT", onSignal);
14849
- process.on("SIGTERM", onSignal);
14850
- return () => {
14851
- process.off("SIGINT", onSignal);
14852
- process.off("SIGTERM", onSignal);
14853
- };
14854
- }, [handleCtrlC]);
15358
+ useKeyboardShortcuts({
15359
+ addMessage,
15360
+ handleExit,
15361
+ abort,
15362
+ cancelInputRequest,
15363
+ isProcessingRef,
15364
+ inputRequestRef
15365
+ });
14855
15366
  const isSlashMode = input.startsWith("/");
14856
15367
  const partialCmd = isSlashMode ? input.slice(1).split(" ")[0] : "";
14857
15368
  const hasArgs = isSlashMode && input.includes(" ");