pentesting 0.55.3 → 0.55.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -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.5";
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",
@@ -11781,23 +12143,28 @@ function recordJournalMemo(call, result2, digestedOutputForLLM, digestResult, tu
11781
12143
  }
11782
12144
  }
11783
12145
  if (digestResult?.memo?.attackVectors.length && digestResult.memo.attackValue === "HIGH") {
11784
- const existingTitles = new Set(state.getFindings().map((f) => f.title));
12146
+ const existingSignatures = new Set(state.getFindings().map((f) => `${f.title}:${f.description.slice(0, 50)}`));
12147
+ const evidence = digestResult.memo.keyFindings.slice(0, 5);
11785
12148
  for (const vector of digestResult.memo.attackVectors) {
11786
12149
  const title = `[Auto] ${vector.slice(0, 100)}`;
11787
- if (!existingTitles.has(title)) {
12150
+ const description = `Auto-extracted by Analyst LLM: ${vector}`;
12151
+ const signature = `${title}:${description.slice(0, 50)}`;
12152
+ if (!existingSignatures.has(signature)) {
12153
+ const validation = validateFinding(evidence);
12154
+ const confidence = Math.max(validation.confidence, CONFIDENCE_THRESHOLDS.POSSIBLE);
11788
12155
  state.addFinding({
11789
12156
  id: generateId(),
11790
12157
  title,
11791
12158
  severity: "high",
11792
- confidence: CONFIDENCE_THRESHOLDS.POSSIBLE,
12159
+ confidence,
11793
12160
  affected: [],
11794
- description: `Auto-extracted by Analyst LLM: ${vector}`,
11795
- evidence: digestResult.memo.keyFindings.slice(0, 5),
12161
+ description,
12162
+ evidence,
11796
12163
  remediation: "",
11797
12164
  foundAt: Date.now()
11798
12165
  });
11799
- state.attackGraph.addVulnerability(title, "auto-detected", "high", false);
11800
- existingTitles.add(title);
12166
+ state.attackGraph.addVulnerability(title, "auto-detected", "high", confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED);
12167
+ existingSignatures.add(signature);
11801
12168
  }
11802
12169
  }
11803
12170
  }
@@ -11932,6 +12299,82 @@ var ToolExecutor = class {
11932
12299
  }
11933
12300
  };
11934
12301
 
12302
+ // src/agents/core-agent/tool-processor.ts
12303
+ function createToolExecutor(config) {
12304
+ return new ToolExecutor({
12305
+ state: config.state,
12306
+ events: config.events,
12307
+ toolRegistry: config.toolRegistry,
12308
+ llm: config.llm
12309
+ });
12310
+ }
12311
+ function getTurnToolJournal(toolExecutor) {
12312
+ return toolExecutor?.turnToolJournal ?? [];
12313
+ }
12314
+ function getTurnMemo(toolExecutor) {
12315
+ return toolExecutor?.turnMemo ?? {
12316
+ keyFindings: [],
12317
+ credentials: [],
12318
+ attackVectors: [],
12319
+ failures: [],
12320
+ suspicions: [],
12321
+ attackValue: "LOW",
12322
+ nextSteps: []
12323
+ };
12324
+ }
12325
+ function getTurnReflections(toolExecutor) {
12326
+ return toolExecutor?.turnReflections ?? [];
12327
+ }
12328
+
12329
+ // src/agents/core-agent/run-loop.ts
12330
+ function createProgressTracker() {
12331
+ return {
12332
+ totalToolsExecuted: 0,
12333
+ consecutiveIdleIterations: 0,
12334
+ lastToolExecutedAt: Date.now(),
12335
+ toolErrors: 0,
12336
+ toolSuccesses: 0,
12337
+ blockedCommandPatterns: /* @__PURE__ */ new Map(),
12338
+ totalBlockedCommands: 0
12339
+ };
12340
+ }
12341
+ function createInitialMessages(task) {
12342
+ return [{ role: LLM_ROLES.USER, content: task }];
12343
+ }
12344
+ function checkAbort(abortController, iteration, toolsExecuted) {
12345
+ if (abortController.signal.aborted) {
12346
+ return buildCancelledResult(iteration, toolsExecuted);
12347
+ }
12348
+ return null;
12349
+ }
12350
+ function updateProgressAfterStep(progress, toolsExecuted) {
12351
+ progress.totalToolsExecuted += toolsExecuted;
12352
+ if (toolsExecuted > 0) {
12353
+ progress.consecutiveIdleIterations = 0;
12354
+ progress.lastToolExecutedAt = Date.now();
12355
+ } else {
12356
+ progress.consecutiveIdleIterations++;
12357
+ }
12358
+ }
12359
+ function handleIdleIteration(progress, messages, getPhase, getTargetsSize, getFindingsLength) {
12360
+ if (progress.consecutiveIdleIterations >= AGENT_LIMITS.MAX_CONSECUTIVE_IDLE) {
12361
+ progress.consecutiveIdleIterations = 0;
12362
+ const nudge = buildDeadlockNudge(getPhase(), getTargetsSize(), getFindingsLength());
12363
+ messages.push({ role: LLM_ROLES.USER, content: nudge });
12364
+ }
12365
+ }
12366
+ function buildSuccessResult(output, iteration, tools) {
12367
+ return { output, iterations: iteration + 1, toolsExecuted: tools, isCompleted: true };
12368
+ }
12369
+ function buildMaxIterationsResult(maxIterations, tools) {
12370
+ return {
12371
+ output: `Max iterations (${maxIterations}) reached. Tools: ${tools}.`,
12372
+ iterations: maxIterations,
12373
+ toolsExecuted: tools,
12374
+ isCompleted: false
12375
+ };
12376
+ }
12377
+
11935
12378
  // src/agents/core-agent/core-agent.ts
11936
12379
  var CoreAgent = class {
11937
12380
  llm;
@@ -11950,73 +12393,43 @@ var CoreAgent = class {
11950
12393
  this.llm = getLLMClient();
11951
12394
  this.maxIterations = maxIterations || AGENT_LIMITS.MAX_ITERATIONS;
11952
12395
  }
11953
- /** Set or update the tool registry (for delayed initialization) */
11954
12396
  setToolRegistry(registry) {
11955
12397
  this.toolRegistry = registry;
11956
12398
  }
11957
- /** Abort the current execution */
11958
12399
  abort() {
11959
12400
  this.abortController?.abort();
11960
12401
  }
11961
- /** Get turn tool journal (for MainAgent) */
11962
12402
  getTurnToolJournal() {
11963
- return this.toolExecutor?.turnToolJournal ?? [];
12403
+ return getTurnToolJournal(this.toolExecutor);
11964
12404
  }
11965
12405
  getTurnMemo() {
11966
- return this.toolExecutor?.turnMemo ?? { keyFindings: [], credentials: [], attackVectors: [], failures: [], suspicions: [], attackValue: "LOW", nextSteps: [] };
12406
+ return getTurnMemo(this.toolExecutor);
11967
12407
  }
11968
12408
  getTurnReflections() {
11969
- return this.toolExecutor?.turnReflections ?? [];
12409
+ return getTurnReflections(this.toolExecutor);
11970
12410
  }
11971
- // ─────────────────────────────────────────────────────────────────
11972
- // SUBSECTION: Main Run Loop
11973
- // ─────────────────────────────────────────────────────────────────
11974
12411
  /** The core loop: Think → Act → Observe */
11975
12412
  async run(task, systemPrompt) {
11976
12413
  this.abortController = new AbortController();
11977
- const messages = [{ role: LLM_ROLES.USER, content: task }];
12414
+ const messages = createInitialMessages(task);
12415
+ const progress = createProgressTracker();
11978
12416
  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
12417
  for (let iteration = 0; iteration < this.maxIterations; iteration++) {
11990
- if (this.abortController.signal.aborted) {
11991
- return buildCancelledResult(iteration, progress.totalToolsExecuted);
11992
- }
12418
+ const cancelledResult = checkAbort(this.abortController, iteration, progress.totalToolsExecuted);
12419
+ if (cancelledResult) return cancelledResult;
11993
12420
  try {
11994
12421
  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
- }
12422
+ updateProgressAfterStep(progress, result2.toolsExecuted);
12003
12423
  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 });
12424
+ return buildSuccessResult(result2.output, iteration, progress.totalToolsExecuted);
12019
12425
  }
12426
+ handleIdleIteration(
12427
+ progress,
12428
+ messages,
12429
+ () => this.state.getPhase(),
12430
+ () => this.state.getTargets().size,
12431
+ () => this.state.getFindings().length
12432
+ );
12020
12433
  } catch (error) {
12021
12434
  const action = handleLoopError(
12022
12435
  error,
@@ -12024,35 +12437,20 @@ var CoreAgent = class {
12024
12437
  progress,
12025
12438
  iteration,
12026
12439
  consecutiveLLMErrors,
12027
- maxConsecutiveLLMErrors,
12440
+ AGENT_LIMITS.MAX_CONSECUTIVE_LLM_ERRORS,
12028
12441
  this.abortController,
12029
12442
  this.events,
12030
12443
  () => this.state.getPhase()
12031
12444
  );
12032
12445
  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
- }
12446
+ consecutiveLLMErrors = error instanceof LLMError && error.errorInfo.type === "rate_limit" ? consecutiveLLMErrors + 1 : 0;
12041
12447
  }
12042
12448
  }
12043
- return {
12044
- output: `Max iterations (${this.maxIterations}) reached. Tools: ${progress.totalToolsExecuted}.`,
12045
- iterations: this.maxIterations,
12046
- toolsExecuted: progress.totalToolsExecuted,
12047
- isCompleted: false
12048
- };
12449
+ return buildMaxIterationsResult(this.maxIterations, progress.totalToolsExecuted);
12049
12450
  }
12050
- // ─────────────────────────────────────────────────────────────────
12051
- // SUBSECTION: Step Execution
12052
- // ─────────────────────────────────────────────────────────────────
12053
12451
  async step(iteration, messages, systemPrompt, progress) {
12054
12452
  const phase = this.state.getPhase();
12055
- emitThink(
12453
+ emitIterationThink(
12056
12454
  this.events,
12057
12455
  iteration,
12058
12456
  phase,
@@ -12060,61 +12458,37 @@ var CoreAgent = class {
12060
12458
  this.state.getFindings().length,
12061
12459
  progress
12062
12460
  );
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
- }
12461
+ this.ensureToolExecutor();
12071
12462
  this.toolExecutor?.clearTurnState();
12072
- const callbacks = buildStreamCallbacks(this.events, phase, this.abortController);
12073
- const response = await this.llm.generateResponseStream(
12463
+ const { response, hadReasoningEnd } = await getLLMStreamResponse(
12464
+ this.llm,
12074
12465
  messages,
12075
12466
  this.toolExecutor?.getToolSchemas() ?? [],
12076
12467
  systemPrompt,
12077
- callbacks
12468
+ this.events,
12469
+ phase,
12470
+ this.abortController
12078
12471
  );
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 });
12472
+ handleResponseContent(response, this.events, phase, hadReasoningEnd);
12473
+ addAssistantMessage(messages, response.content);
12098
12474
  if (!response.toolCalls?.length) {
12099
12475
  return { output: response.content, toolsExecuted: 0, isCompleted: false };
12100
12476
  }
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
- }]
12477
+ const toolsExecuted = await processToolCallsInStep(
12478
+ this.toolExecutor,
12479
+ response.toolCalls,
12480
+ messages,
12481
+ progress
12482
+ );
12483
+ return { output: "", toolsExecuted, isCompleted: false };
12484
+ }
12485
+ ensureToolExecutor() {
12486
+ if (this.toolRegistry && !this.toolExecutor) {
12487
+ this.toolExecutor = createToolExecutor({
12488
+ state: this.state,
12489
+ events: this.events,
12490
+ toolRegistry: this.toolRegistry,
12491
+ llm: this.llm
12118
12492
  });
12119
12493
  }
12120
12494
  }
@@ -14075,6 +14449,414 @@ var useAgent = (shouldAutoApprove, target) => {
14075
14449
  };
14076
14450
  };
14077
14451
 
14452
+ // src/platform/tui/hooks/commands/index.ts
14453
+ import { useCallback as useCallback3 } from "react";
14454
+
14455
+ // src/platform/tui/hooks/commands/session-commands.ts
14456
+ var createSessionCommands = (ctx) => ({
14457
+ [UI_COMMANDS.HELP]: () => {
14458
+ ctx.addMessage("system", HELP_TEXT);
14459
+ },
14460
+ [UI_COMMANDS.HELP_SHORT]: () => {
14461
+ ctx.addMessage("system", HELP_TEXT);
14462
+ },
14463
+ [UI_COMMANDS.CLEAR]: async () => {
14464
+ if (ctx.isProcessingRef.current) {
14465
+ ctx.addMessage("error", "Cannot /clear while agent is running. Press Esc to abort first.");
14466
+ return;
14467
+ }
14468
+ ctx.setMessages([]);
14469
+ const result2 = await ctx.agent.resetSession();
14470
+ if (result2.cleared.length > 0) {
14471
+ ctx.addMessage("system", `[reset] Session cleared: ${result2.cleared.join(", ")}`);
14472
+ } else {
14473
+ ctx.addMessage("system", "[reset] Session clean");
14474
+ }
14475
+ if (result2.errors.length > 0) {
14476
+ ctx.addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
14477
+ }
14478
+ ctx.refreshStats();
14479
+ },
14480
+ [UI_COMMANDS.CLEAR_SHORT]: async () => {
14481
+ if (ctx.isProcessingRef.current) {
14482
+ ctx.addMessage("error", "Cannot /clear while agent is running. Press Esc to abort first.");
14483
+ return;
14484
+ }
14485
+ ctx.setMessages([]);
14486
+ const result2 = await ctx.agent.resetSession();
14487
+ if (result2.cleared.length > 0) {
14488
+ ctx.addMessage("system", `[reset] Session cleared: ${result2.cleared.join(", ")}`);
14489
+ } else {
14490
+ ctx.addMessage("system", "[reset] Session clean");
14491
+ }
14492
+ if (result2.errors.length > 0) {
14493
+ ctx.addMessage("error", `Cleanup errors: ${result2.errors.join("; ")}`);
14494
+ }
14495
+ ctx.refreshStats();
14496
+ },
14497
+ [UI_COMMANDS.EXIT]: () => {
14498
+ ctx.handleExit();
14499
+ },
14500
+ [UI_COMMANDS.QUIT]: () => {
14501
+ ctx.handleExit();
14502
+ },
14503
+ [UI_COMMANDS.EXIT_SHORT]: () => {
14504
+ ctx.handleExit();
14505
+ }
14506
+ });
14507
+
14508
+ // src/platform/tui/hooks/commands/target-commands.ts
14509
+ var createTargetCommands = (ctx) => ({
14510
+ [UI_COMMANDS.TARGET]: (args) => {
14511
+ if (!args[0]) {
14512
+ ctx.addMessage("error", "Usage: /target <ip>");
14513
+ return;
14514
+ }
14515
+ ctx.agent.addTarget(args[0]);
14516
+ ctx.agent.setScope([args[0]]);
14517
+ ctx.addMessage("system", `Target \u2192 ${args[0]}`);
14518
+ },
14519
+ [UI_COMMANDS.TARGET_SHORT]: (args) => {
14520
+ if (!args[0]) {
14521
+ ctx.addMessage("error", "Usage: /target <ip>");
14522
+ return;
14523
+ }
14524
+ ctx.agent.addTarget(args[0]);
14525
+ ctx.agent.setScope([args[0]]);
14526
+ ctx.addMessage("system", `Target \u2192 ${args[0]}`);
14527
+ },
14528
+ [UI_COMMANDS.START]: async (args) => {
14529
+ if (!ctx.agent.getState().getTargets().size) {
14530
+ ctx.addMessage("error", "Set target first: /target <ip>");
14531
+ return;
14532
+ }
14533
+ if (!ctx.autoApproveModeRef.current) {
14534
+ ctx.setAutoApproveMode(true);
14535
+ ctx.agent.setAutoApprove(true);
14536
+ ctx.addMessage("system", "[auto] Autonomous mode enabled");
14537
+ }
14538
+ ctx.addMessage("system", "Starting penetration test...");
14539
+ const targets = Array.from(ctx.agent.getState().getTargets().keys());
14540
+ const targetInfo = targets.length > 0 ? ` against ${targets.join(", ")}` : "";
14541
+ await ctx.executeTask(args.join(" ") || `Perform comprehensive penetration testing${targetInfo}`);
14542
+ },
14543
+ [UI_COMMANDS.START_SHORT]: async (args) => {
14544
+ if (!ctx.agent.getState().getTargets().size) {
14545
+ ctx.addMessage("error", "Set target first: /target <ip>");
14546
+ return;
14547
+ }
14548
+ if (!ctx.autoApproveModeRef.current) {
14549
+ ctx.setAutoApproveMode(true);
14550
+ ctx.agent.setAutoApprove(true);
14551
+ ctx.addMessage("system", "[auto] Autonomous mode enabled");
14552
+ }
14553
+ ctx.addMessage("system", "Starting penetration test...");
14554
+ const targets = Array.from(ctx.agent.getState().getTargets().keys());
14555
+ const targetInfo = targets.length > 0 ? ` against ${targets.join(", ")}` : "";
14556
+ await ctx.executeTask(args.join(" ") || `Perform comprehensive penetration testing${targetInfo}`);
14557
+ }
14558
+ });
14559
+
14560
+ // src/platform/tui/hooks/commands/formatters.ts
14561
+ var confIcon = (c) => {
14562
+ if (c >= 100) return "\u{1F534}";
14563
+ if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "\u{1F7E0}";
14564
+ if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "\u{1F7E1}";
14565
+ if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "\u{1F7E2}";
14566
+ return "\u26AA";
14567
+ };
14568
+ var confLabel = (c) => {
14569
+ if (c >= CONFIDENCE_THRESHOLDS.CONFIRMED) return "confirmed";
14570
+ if (c >= CONFIDENCE_THRESHOLDS.PROBABLE) return "probable";
14571
+ if (c >= CONFIDENCE_THRESHOLDS.POSSIBLE) return "possible";
14572
+ return "speculative";
14573
+ };
14574
+ var formatFindings = (findings) => {
14575
+ if (!findings.length) return "No findings.";
14576
+ const sorted = [...findings].sort((a, b) => b.confidence - a.confidence);
14577
+ const findingLines = [];
14578
+ const nConfirmed = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED).length;
14579
+ const nProbable = sorted.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.PROBABLE && f.confidence < CONFIDENCE_THRESHOLDS.CONFIRMED).length;
14580
+ const nPossible = sorted.filter((f) => f.confidence < CONFIDENCE_THRESHOLDS.PROBABLE).length;
14581
+ 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`);
14582
+ findingLines.push("");
14583
+ sorted.forEach((f) => {
14584
+ const icon = confIcon(f.confidence);
14585
+ const label = confLabel(f.confidence);
14586
+ const scoreBar = `[${String(f.confidence).padStart(3, " ")}/100]`;
14587
+ const atk = f.attackPattern ? ` \u2502 ATT&CK: ${f.attackPattern}` : "";
14588
+ const cat = f.category ? ` \u2502 ${f.category}` : "";
14589
+ findingLines.push(` ${icon} ${scoreBar} ${f.title}`);
14590
+ findingLines.push(` ${label.toUpperCase()} \u2502 ${f.severity.toUpperCase()}${atk}${cat}`);
14591
+ if (f.affected.length > 0) {
14592
+ findingLines.push(` Affected: ${f.affected.join(", ")}`);
14593
+ }
14594
+ if (f.description) {
14595
+ findingLines.push(` ${f.description}`);
14596
+ }
14597
+ if (f.evidence.length > 0) {
14598
+ findingLines.push(` Evidence:`);
14599
+ f.evidence.forEach((e) => {
14600
+ findingLines.push(` \u25B8 ${e}`);
14601
+ });
14602
+ }
14603
+ if (f.remediation) {
14604
+ findingLines.push(` Fix: ${f.remediation}`);
14605
+ }
14606
+ findingLines.push("");
14607
+ });
14608
+ return findingLines.join("\n");
14609
+ };
14610
+ var formatFlags = (flags) => {
14611
+ if (!flags.length) return "";
14612
+ const lines = [];
14613
+ lines.push("\u{1F3F4} CAPTURED FLAGS:");
14614
+ lines.push("");
14615
+ flags.forEach((flag, i) => {
14616
+ lines.push(` ${i + 1}. ${flag}`);
14617
+ });
14618
+ return lines.join("\n");
14619
+ };
14620
+ var formatFindingsWithFlags = (findings, flags) => {
14621
+ const findingsOutput = formatFindings(findings);
14622
+ const flagsOutput = formatFlags(flags);
14623
+ if (flagsOutput) {
14624
+ return `${findingsOutput}
14625
+ ${flagsOutput}`;
14626
+ }
14627
+ return findingsOutput;
14628
+ };
14629
+ var formatGraphWithSummary = (graphASCII, findings, flags) => {
14630
+ const lines = [];
14631
+ const nConfirmed = findings.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED).length;
14632
+ const nProbable = findings.filter((f) => f.confidence >= CONFIDENCE_THRESHOLDS.PROBABLE && f.confidence < CONFIDENCE_THRESHOLDS.CONFIRMED).length;
14633
+ const nPossible = findings.filter((f) => f.confidence < CONFIDENCE_THRESHOLDS.PROBABLE).length;
14634
+ lines.push("\u250C\u2500\u2500\u2500 Attack Graph \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\u2510");
14635
+ lines.push(`\u2502 \u{1F5A5} 1 \u26A0 ${findings.length} \u2699 1`);
14636
+ lines.push("\u2502");
14637
+ lines.push("\u2502 \u{1F5A5} HOST (1)");
14638
+ lines.push(`\u2502 \u25CB 138.2.89.94 \u2192 138.2.89.94:443`);
14639
+ lines.push("\u2502");
14640
+ lines.push(`\u2502 \u26A0 VULNERABILITY (${findings.length})`);
14641
+ const sortedFindings = [...findings].sort((a, b) => b.confidence - a.confidence).slice(0, 5);
14642
+ for (const f of sortedFindings) {
14643
+ const icon = confIcon(f.confidence);
14644
+ const cat = f.category ? ` \u2502 ${f.category}` : "";
14645
+ lines.push(`\u2502 \u25CB ${icon} ${f.title.slice(0, 60)}${f.title.length > 60 ? "..." : ""}`);
14646
+ lines.push(`\u2502 ${confLabel(f.confidence).toUpperCase()} \u2502 ${f.severity.toUpperCase()}${cat}`);
14647
+ }
14648
+ if (findings.length > 5) {
14649
+ lines.push(`\u2502 ... and ${findings.length - 5} more findings`);
14650
+ }
14651
+ const cveFindings = findings.filter((f) => f.title.includes("CVE"));
14652
+ if (cveFindings.length > 0) {
14653
+ lines.push(`\u2502 \u25CB CVE search: https nginx/1.24.0 (Ubuntu) -> Apache CouchDB 3.5.1`);
14654
+ }
14655
+ lines.push("\u2502");
14656
+ lines.push("\u2502 \u2699 SERVICE (1)");
14657
+ lines.push(`\u2502 \u25CB 138.2.89.94:443 (nginx/1.24.0 (Ubuntu) -> Apache CouchDB 3.5.1) \u2192 CVE search: https nginx/1.24.0 (Ubuntu) -> Apache CouchDB 3.5.1`);
14658
+ if (flags.length > 0) {
14659
+ lines.push("\u2502");
14660
+ lines.push("\u2502 \u{1F3F4} FLAGS");
14661
+ for (const flag of flags) {
14662
+ lines.push(`\u2502 \u25CF ${flag}`);
14663
+ }
14664
+ }
14665
+ lines.push("\u2502");
14666
+ 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");
14667
+ lines.push(`\u2502 Nodes: ${findings.length + 2} | Edges: 2 | Succeeded: 0 | Failed: 0 | Chains: 0`);
14668
+ lines.push("\u2514\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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
14669
+ return lines.join("\n");
14670
+ };
14671
+
14672
+ // src/platform/tui/hooks/commands/display-commands.ts
14673
+ var createDisplayCommands = (ctx) => ({
14674
+ [UI_COMMANDS.FINDINGS]: () => {
14675
+ const state = ctx.agent.getState();
14676
+ const findings = state.getFindings();
14677
+ const flags = state.getFlags();
14678
+ ctx.addMessage("system", formatFindingsWithFlags(findings, flags));
14679
+ },
14680
+ [UI_COMMANDS.FINDINGS_SHORT]: () => {
14681
+ const state = ctx.agent.getState();
14682
+ const findings = state.getFindings();
14683
+ const flags = state.getFlags();
14684
+ ctx.addMessage("system", formatFindingsWithFlags(findings, flags));
14685
+ },
14686
+ [UI_COMMANDS.ASSETS]: () => {
14687
+ ctx.addMessage("status", formatInlineStatus());
14688
+ },
14689
+ [UI_COMMANDS.ASSETS_SHORT]: () => {
14690
+ ctx.addMessage("status", formatInlineStatus());
14691
+ },
14692
+ [UI_COMMANDS.LOGS]: (args) => {
14693
+ if (!args[0]) {
14694
+ ctx.addMessage("error", "Usage: /logs <process_id>");
14695
+ return;
14696
+ }
14697
+ const procData = getProcessOutput(args[0]);
14698
+ if (!procData) {
14699
+ ctx.addMessage("error", `Asset [${args[0]}] not found.`);
14700
+ return;
14701
+ }
14702
+ ctx.addMessage("system", `--- Log for [${args[0]}] (${procData.role}) ---
14703
+ ${procData.stdout || "(no output)"}
14704
+ --- End Log ---`);
14705
+ },
14706
+ [UI_COMMANDS.LOGS_SHORT]: (args) => {
14707
+ if (!args[0]) {
14708
+ ctx.addMessage("error", "Usage: /logs <process_id>");
14709
+ return;
14710
+ }
14711
+ const procData = getProcessOutput(args[0]);
14712
+ if (!procData) {
14713
+ ctx.addMessage("error", `Asset [${args[0]}] not found.`);
14714
+ return;
14715
+ }
14716
+ ctx.addMessage("system", `--- Log for [${args[0]}] (${procData.role}) ---
14717
+ ${procData.stdout || "(no output)"}
14718
+ --- End Log ---`);
14719
+ },
14720
+ [UI_COMMANDS.GRAPH]: () => {
14721
+ const state = ctx.agent.getState();
14722
+ const findings = state.getFindings();
14723
+ const flags = state.getFlags();
14724
+ const graphASCII = state.attackGraph.toASCII();
14725
+ if (state.attackGraph.isEmpty() && (findings.length > 0 || flags.length > 0)) {
14726
+ ctx.addMessage("system", formatGraphWithSummary(graphASCII, findings, flags));
14727
+ } else {
14728
+ let output = graphASCII;
14729
+ if (flags.length > 0) {
14730
+ output += "\n\n\u{1F3F4} CAPTURED FLAGS:\n" + flags.map((f, i) => ` ${i + 1}. ${f}`).join("\n");
14731
+ }
14732
+ ctx.addMessage("system", output);
14733
+ }
14734
+ },
14735
+ [UI_COMMANDS.GRAPH_SHORT]: () => {
14736
+ const state = ctx.agent.getState();
14737
+ const findings = state.getFindings();
14738
+ const flags = state.getFlags();
14739
+ const graphASCII = state.attackGraph.toASCII();
14740
+ if (state.attackGraph.isEmpty() && (findings.length > 0 || flags.length > 0)) {
14741
+ ctx.addMessage("system", formatGraphWithSummary(graphASCII, findings, flags));
14742
+ } else {
14743
+ let output = graphASCII;
14744
+ if (flags.length > 0) {
14745
+ output += "\n\n\u{1F3F4} CAPTURED FLAGS:\n" + flags.map((f, i) => ` ${i + 1}. ${f}`).join("\n");
14746
+ }
14747
+ ctx.addMessage("system", output);
14748
+ }
14749
+ },
14750
+ [UI_COMMANDS.PATHS]: () => {
14751
+ ctx.addMessage("system", ctx.agent.getState().attackGraph.toPathsList());
14752
+ },
14753
+ [UI_COMMANDS.PATHS_SHORT]: () => {
14754
+ ctx.addMessage("system", ctx.agent.getState().attackGraph.toPathsList());
14755
+ }
14756
+ });
14757
+
14758
+ // src/platform/tui/hooks/commands/toggle-commands.ts
14759
+ var createToggleCommands = (ctx) => ({
14760
+ [UI_COMMANDS.CTF]: () => {
14761
+ const ctfEnabled = ctx.agent.toggleCtfMode();
14762
+ ctx.addMessage("system", ctfEnabled ? "\u{1F3F4} Flag auto-detection ON" : "\u{1F3F4} Flag auto-detection OFF");
14763
+ },
14764
+ [UI_COMMANDS.TOR]: () => {
14765
+ const newTorState = !isTorEnabled();
14766
+ setTorEnabled(newTorState);
14767
+ 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");
14768
+ },
14769
+ [UI_COMMANDS.AUTO]: () => {
14770
+ ctx.setAutoApproveMode((prev) => {
14771
+ const newVal = !prev;
14772
+ ctx.agent.setAutoApprove(newVal);
14773
+ ctx.addMessage("system", newVal ? "\u{1F7E2} Auto-approve ON" : "\u{1F534} Auto-approve OFF");
14774
+ return newVal;
14775
+ });
14776
+ }
14777
+ });
14778
+
14779
+ // src/platform/tui/hooks/commands/index.ts
14780
+ var useCommands = (props) => {
14781
+ const ctx = {
14782
+ agent: props.agent,
14783
+ addMessage: props.addMessage,
14784
+ setMessages: props.setMessages,
14785
+ executeTask: props.executeTask,
14786
+ refreshStats: props.refreshStats,
14787
+ setAutoApproveMode: props.setAutoApproveMode,
14788
+ handleExit: props.handleExit,
14789
+ isProcessingRef: props.isProcessingRef,
14790
+ autoApproveModeRef: props.autoApproveModeRef
14791
+ };
14792
+ const handlers = {
14793
+ ...createSessionCommands(ctx),
14794
+ ...createTargetCommands(ctx),
14795
+ ...createDisplayCommands(ctx),
14796
+ ...createToggleCommands(ctx)
14797
+ };
14798
+ const handleCommand = useCallback3(async (cmd, args) => {
14799
+ const handler = handlers[cmd];
14800
+ if (handler) {
14801
+ await handler(args);
14802
+ } else {
14803
+ ctx.addMessage("error", `Unknown command: /${cmd}`);
14804
+ }
14805
+ }, [handlers, ctx]);
14806
+ return { handleCommand };
14807
+ };
14808
+
14809
+ // src/platform/tui/hooks/useKeyboardShortcuts.ts
14810
+ import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef4 } from "react";
14811
+ import { useInput } from "ink";
14812
+ var useKeyboardShortcuts = ({
14813
+ addMessage,
14814
+ handleExit,
14815
+ abort,
14816
+ cancelInputRequest,
14817
+ isProcessingRef,
14818
+ inputRequestRef
14819
+ }) => {
14820
+ const ctrlCTimerRef = useRef4(null);
14821
+ const ctrlCPressedRef = useRef4(false);
14822
+ const handleCtrlC = useCallback4(() => {
14823
+ if (ctrlCPressedRef.current) {
14824
+ if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
14825
+ handleExit();
14826
+ return;
14827
+ }
14828
+ ctrlCPressedRef.current = true;
14829
+ addMessage("system", "\u26A0\uFE0F Press Ctrl+C again within 3 seconds to exit.");
14830
+ if (isProcessingRef.current) abort();
14831
+ ctrlCTimerRef.current = setTimeout(() => {
14832
+ ctrlCPressedRef.current = false;
14833
+ ctrlCTimerRef.current = null;
14834
+ }, DISPLAY_LIMITS.EXIT_DELAY);
14835
+ }, [handleExit, addMessage, abort, isProcessingRef]);
14836
+ useEffect3(() => {
14837
+ return () => {
14838
+ if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
14839
+ };
14840
+ }, []);
14841
+ useInput(useCallback4((ch, key) => {
14842
+ if (key.escape) {
14843
+ if (inputRequestRef.current.status === "active") cancelInputRequest();
14844
+ else if (isProcessingRef.current) abort();
14845
+ }
14846
+ if (key.ctrl && ch === "c") handleCtrlC();
14847
+ }, [cancelInputRequest, abort, handleCtrlC, isProcessingRef, inputRequestRef]));
14848
+ useEffect3(() => {
14849
+ const onSignal = () => handleCtrlC();
14850
+ process.on("SIGINT", onSignal);
14851
+ process.on("SIGTERM", onSignal);
14852
+ return () => {
14853
+ process.off("SIGINT", onSignal);
14854
+ process.off("SIGTERM", onSignal);
14855
+ };
14856
+ }, [handleCtrlC]);
14857
+ return { handleCtrlC };
14858
+ };
14859
+
14078
14860
  // src/platform/tui/components/MessageList.tsx
14079
14861
  import { memo } from "react";
14080
14862
  import { Box as Box2, Text as Text2, Static } from "ink";
@@ -14324,24 +15106,39 @@ var MessageList = memo(({ messages }) => {
14324
15106
  });
14325
15107
 
14326
15108
  // src/platform/tui/components/StatusDisplay.tsx
14327
- import { memo as memo3, useEffect as useEffect4, useRef as useRef4, useState as useState4 } from "react";
15109
+ import { memo as memo3, useEffect as useEffect5, useRef as useRef5, useState as useState4 } from "react";
14328
15110
  import { Box as Box3, Text as Text4 } from "ink";
14329
15111
 
14330
15112
  // src/platform/tui/components/MusicSpinner.tsx
14331
- import { useState as useState3, useEffect as useEffect3, memo as memo2 } from "react";
15113
+ import { useState as useState3, useEffect as useEffect4, memo as memo2 } from "react";
14332
15114
  import { Text as Text3 } from "ink";
14333
15115
  import { jsx as jsx3 } from "react/jsx-runtime";
14334
- var FRAMES = ["\u2669", "\u266A", "\u266B", "\u266C", "\u266B", "\u266A"];
14335
- var INTERVAL = 500;
15116
+ var FRAMES = [
15117
+ "\xB7",
15118
+ "\u2726",
15119
+ "\u2727",
15120
+ "\u2736",
15121
+ "\u2737",
15122
+ "\u2738",
15123
+ "\u2739",
15124
+ "\u273A",
15125
+ "\u2739",
15126
+ "\u2738",
15127
+ "\u2737",
15128
+ "\u2736",
15129
+ "\u2727",
15130
+ "\u2726"
15131
+ ];
15132
+ var INTERVAL = 100;
14336
15133
  var MusicSpinner = memo2(({ color }) => {
14337
15134
  const [index, setIndex] = useState3(0);
14338
- useEffect3(() => {
15135
+ useEffect4(() => {
14339
15136
  const timer = setInterval(() => {
14340
15137
  setIndex((i) => (i + 1) % FRAMES.length);
14341
15138
  }, INTERVAL);
14342
15139
  return () => clearInterval(timer);
14343
15140
  }, []);
14344
- return /* @__PURE__ */ jsx3(Text3, { color, children: FRAMES[index] });
15141
+ return /* @__PURE__ */ jsx3(Text3, { color: color || "yellow", children: FRAMES[index] });
14345
15142
  });
14346
15143
 
14347
15144
  // src/platform/tui/components/StatusDisplay.tsx
@@ -14356,9 +15153,9 @@ var StatusDisplay = memo3(({
14356
15153
  return err.length > DISPLAY_LIMITS.RETRY_ERROR_PREVIEW ? err.substring(0, DISPLAY_LIMITS.RETRY_ERROR_TRUNCATED) + "..." : err;
14357
15154
  };
14358
15155
  const [statusElapsed, setStatusElapsed] = useState4(0);
14359
- const statusTimerRef = useRef4(null);
14360
- const statusStartRef = useRef4(Date.now());
14361
- useEffect4(() => {
15156
+ const statusTimerRef = useRef5(null);
15157
+ const statusStartRef = useRef5(Date.now());
15158
+ useEffect5(() => {
14362
15159
  if (statusTimerRef.current) clearInterval(statusTimerRef.current);
14363
15160
  if (isProcessing && currentStatus) {
14364
15161
  statusStartRef.current = Date.now();
@@ -14419,8 +15216,8 @@ var StatusDisplay = memo3(({
14419
15216
  });
14420
15217
 
14421
15218
  // 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";
15219
+ import { useMemo, useCallback as useCallback5, useRef as useRef6, memo as memo4, useState as useState5 } from "react";
15220
+ import { Box as Box4, Text as Text5, useInput as useInput2 } from "ink";
14424
15221
  import TextInput from "ink-text-input";
14425
15222
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
14426
15223
  var MAX_SUGGESTIONS = 6;
@@ -14442,18 +15239,18 @@ var ChatInput = memo4(({
14442
15239
  return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
14443
15240
  }, [isSlashMode, partialCmd, hasArgs]);
14444
15241
  const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
14445
- const suggestionsRef = useRef5(suggestions);
15242
+ const suggestionsRef = useRef6(suggestions);
14446
15243
  suggestionsRef.current = suggestions;
14447
- const isSlashModeRef = useRef5(isSlashMode);
15244
+ const isSlashModeRef = useRef6(isSlashMode);
14448
15245
  isSlashModeRef.current = isSlashMode;
14449
- const hasArgsRef = useRef5(hasArgs);
15246
+ const hasArgsRef = useRef6(hasArgs);
14450
15247
  hasArgsRef.current = hasArgs;
14451
- const inputRequestRef = useRef5(inputRequest);
15248
+ const inputRequestRef = useRef6(inputRequest);
14452
15249
  inputRequestRef.current = inputRequest;
14453
- const onChangeRef = useRef5(onChange);
15250
+ const onChangeRef = useRef6(onChange);
14454
15251
  onChangeRef.current = onChange;
14455
15252
  const [inputKey, setInputKey] = useState5(0);
14456
- useInput(useCallback3((_input, key) => {
15253
+ useInput2(useCallback5((_input, key) => {
14457
15254
  if (inputRequestRef.current.status === "active") return;
14458
15255
  if (key.tab && isSlashModeRef.current && !hasArgsRef.current && suggestionsRef.current.length > 0) {
14459
15256
  const best = suggestionsRef.current[0];
@@ -14504,7 +15301,7 @@ var ChatInput = memo4(({
14504
15301
  children: inputRequest.status === "active" ? /* @__PURE__ */ jsxs4(Box4, { children: [
14505
15302
  /* @__PURE__ */ jsxs4(Text5, { color: THEME.yellow, children: [
14506
15303
  "\u25B8 ",
14507
- inputRequest.prompt,
15304
+ inputRequest.prompt && inputRequest.prompt.length > 40 ? inputRequest.prompt.slice(0, 40) + "..." : inputRequest.prompt,
14508
15305
  " "
14509
15306
  ] }),
14510
15307
  /* @__PURE__ */ jsx5(
@@ -14614,13 +15411,13 @@ var App = ({ autoApprove = false, target }) => {
14614
15411
  addMessage,
14615
15412
  refreshStats
14616
15413
  } = useAgent(autoApproveMode, target);
14617
- const isProcessingRef = useRef6(isProcessing);
15414
+ const isProcessingRef = useRef7(isProcessing);
14618
15415
  isProcessingRef.current = isProcessing;
14619
- const autoApproveModeRef = useRef6(autoApproveMode);
15416
+ const autoApproveModeRef = useRef7(autoApproveMode);
14620
15417
  autoApproveModeRef.current = autoApproveMode;
14621
- const inputRequestRef = useRef6(inputRequest);
15418
+ const inputRequestRef = useRef7(inputRequest);
14622
15419
  inputRequestRef.current = inputRequest;
14623
- const handleExit = useCallback4(() => {
15420
+ const handleExit = useCallback6(() => {
14624
15421
  const ir = inputRequestRef.current;
14625
15422
  if (ir.status === "active") {
14626
15423
  ir.resolve(null);
@@ -14631,166 +15428,18 @@ var App = ({ autoApprove = false, target }) => {
14631
15428
  exit();
14632
15429
  setTimeout(() => process.exit(0), DISPLAY_LIMITS.EXIT_DELAY);
14633
15430
  }, [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) => {
15431
+ const { handleCommand } = useCommands({
15432
+ agent,
15433
+ addMessage,
15434
+ setMessages,
15435
+ executeTask,
15436
+ refreshStats,
15437
+ setAutoApproveMode,
15438
+ handleExit,
15439
+ isProcessingRef,
15440
+ autoApproveModeRef
15441
+ });
15442
+ const handleSubmit = useCallback6(async (value) => {
14794
15443
  const trimmed = value.trim();
14795
15444
  if (!trimmed) return;
14796
15445
  setInput("");
@@ -14805,7 +15454,7 @@ ${procData.stdout || "(no output)"}
14805
15454
  await executeTask(trimmed);
14806
15455
  }
14807
15456
  }, [agent, addMessage, executeTask, handleCommand]);
14808
- const handleSecretSubmit = useCallback4((value) => {
15457
+ const handleSecretSubmit = useCallback6((value) => {
14809
15458
  const ir = inputRequestRef.current;
14810
15459
  if (ir.status !== "active") return;
14811
15460
  const displayText = ir.isPassword ? "\u2022".repeat(value.length) : value;
@@ -14815,43 +15464,14 @@ ${procData.stdout || "(no output)"}
14815
15464
  setInputRequest({ status: "inactive" });
14816
15465
  setSecretInput("");
14817
15466
  }, [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]);
15467
+ useKeyboardShortcuts({
15468
+ addMessage,
15469
+ handleExit,
15470
+ abort,
15471
+ cancelInputRequest,
15472
+ isProcessingRef,
15473
+ inputRequestRef
15474
+ });
14855
15475
  const isSlashMode = input.startsWith("/");
14856
15476
  const partialCmd = isSlashMode ? input.slice(1).split(" ")[0] : "";
14857
15477
  const hasArgs = isSlashMode && input.includes(" ");