pentesting 0.72.12 → 0.73.1
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/README.md +31 -4
- package/dist/agent-tool-COMG67ES.js +256 -0
- package/dist/chunk-EUWDAGHZ.js +11710 -0
- package/dist/{chunk-GHJPYI4S.js → chunk-YFDJI3GO.js} +11 -1
- package/dist/{chunk-OUS2TZXI.js → chunk-ZQAVMACI.js} +882 -139
- package/dist/main.js +1206 -13248
- package/dist/{persistence-UTTTBCYW.js → persistence-SNUMO4WG.js} +2 -2
- package/dist/{process-registry-CCAQVJ4Y.js → process-registry-GSHEX2LT.js} +3 -1
- package/dist/prompts/base.md +1 -0
- package/dist/prompts/llm/analyst-system.md +7 -0
- package/dist/prompts/{orchestrator.md → main-agent.md} +31 -1
- package/dist/prompts/strategist-system.md +27 -0
- package/package.json +7 -3
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
getProcessEventLog,
|
|
19
19
|
logEvent,
|
|
20
20
|
setProcess
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-YFDJI3GO.js";
|
|
22
22
|
|
|
23
23
|
// src/shared/constants/time/conversions.ts
|
|
24
24
|
var MS_PER_MINUTE = 6e4;
|
|
@@ -235,7 +235,7 @@ var INPUT_PROMPT_PATTERNS = [
|
|
|
235
235
|
|
|
236
236
|
// src/shared/constants/agent.ts
|
|
237
237
|
var APP_NAME = "Pentest AI";
|
|
238
|
-
var APP_VERSION = "0.
|
|
238
|
+
var APP_VERSION = "0.73.1";
|
|
239
239
|
var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
|
|
240
240
|
var LLM_ROLES = {
|
|
241
241
|
SYSTEM: "system",
|
|
@@ -304,7 +304,8 @@ var PHASE_TRANSITIONS = {
|
|
|
304
304
|
[PHASES.FORENSICS]: [PHASES.EXPLOIT, PHASES.REPORT, PHASES.PWN, PHASES.CRYPTO]
|
|
305
305
|
};
|
|
306
306
|
var AGENT_ROLES = {
|
|
307
|
-
ORCHESTRATOR: "orchestrator"
|
|
307
|
+
ORCHESTRATOR: "orchestrator",
|
|
308
|
+
AGENT_TOOL: "agent_tool"
|
|
308
309
|
};
|
|
309
310
|
|
|
310
311
|
// src/shared/constants/protocol/services.ts
|
|
@@ -619,7 +620,10 @@ var TOOL_NAMES = {
|
|
|
619
620
|
HARVESTER: "harvester",
|
|
620
621
|
SHODAN_QUERY: "shodan_query",
|
|
621
622
|
CERT_TRANSPARENCY: "cert_transparency",
|
|
622
|
-
GOOGLE_DORK: "google_dork"
|
|
623
|
+
GOOGLE_DORK: "google_dork",
|
|
624
|
+
// Task Delegation (v1)
|
|
625
|
+
RUN_TASK: "run_task",
|
|
626
|
+
TASK_COMPLETE: "task_complete"
|
|
623
627
|
};
|
|
624
628
|
|
|
625
629
|
// src/shared/constants/attack.ts
|
|
@@ -717,7 +721,6 @@ var UI_COMMANDS = {
|
|
|
717
721
|
LOGS: "logs",
|
|
718
722
|
LOGS_SHORT: "l",
|
|
719
723
|
CTF: "ctf",
|
|
720
|
-
TOR: "tor",
|
|
721
724
|
AUTO: "auto",
|
|
722
725
|
LAYOUT: "layout",
|
|
723
726
|
GRAPH: "graph",
|
|
@@ -1919,57 +1922,6 @@ async function installTool(toolName, eventEmitter, inputHandler) {
|
|
|
1919
1922
|
});
|
|
1920
1923
|
}
|
|
1921
1924
|
|
|
1922
|
-
// src/shared/utils/config/env.ts
|
|
1923
|
-
var ENV_KEYS = {
|
|
1924
|
-
API_KEY: "PENTEST_API_KEY",
|
|
1925
|
-
BASE_URL: "PENTEST_BASE_URL",
|
|
1926
|
-
MODEL: "PENTEST_MODEL",
|
|
1927
|
-
SEARCH_API_KEY: "SEARCH_API_KEY",
|
|
1928
|
-
SEARCH_API_URL: "SEARCH_API_URL",
|
|
1929
|
-
THINKING: "PENTEST_THINKING",
|
|
1930
|
-
THINKING_BUDGET: "PENTEST_THINKING_BUDGET",
|
|
1931
|
-
SCOPE_MODE: "PENTEST_SCOPE_MODE",
|
|
1932
|
-
APPROVAL_MODE: "PENTEST_APPROVAL_MODE",
|
|
1933
|
-
APP_VERSION: "PENTESTING_VERSION"
|
|
1934
|
-
};
|
|
1935
|
-
var DEFAULT_SEARCH_API_URL = "https://api.search.brave.com/res/v1/web/search";
|
|
1936
|
-
var DEFAULT_MODEL = "glm-4.7";
|
|
1937
|
-
function getApiKey() {
|
|
1938
|
-
return process.env[ENV_KEYS.API_KEY] || "";
|
|
1939
|
-
}
|
|
1940
|
-
function getBaseUrl() {
|
|
1941
|
-
return process.env[ENV_KEYS.BASE_URL] || void 0;
|
|
1942
|
-
}
|
|
1943
|
-
function getModel() {
|
|
1944
|
-
return process.env[ENV_KEYS.MODEL] || "";
|
|
1945
|
-
}
|
|
1946
|
-
function getSearchApiKey() {
|
|
1947
|
-
if (process.env[ENV_KEYS.SEARCH_API_KEY]) {
|
|
1948
|
-
return process.env[ENV_KEYS.SEARCH_API_KEY];
|
|
1949
|
-
}
|
|
1950
|
-
return process.env[ENV_KEYS.API_KEY];
|
|
1951
|
-
}
|
|
1952
|
-
function getSearchApiUrl() {
|
|
1953
|
-
return process.env[ENV_KEYS.SEARCH_API_URL] || DEFAULT_SEARCH_API_URL;
|
|
1954
|
-
}
|
|
1955
|
-
function isZaiProvider() {
|
|
1956
|
-
const baseUrl = getBaseUrl() || "";
|
|
1957
|
-
return baseUrl.includes("z.ai");
|
|
1958
|
-
}
|
|
1959
|
-
function isThinkingEnabled() {
|
|
1960
|
-
return process.env[ENV_KEYS.THINKING] !== "false";
|
|
1961
|
-
}
|
|
1962
|
-
function getThinkingBudget() {
|
|
1963
|
-
const val = parseInt(process.env[ENV_KEYS.THINKING_BUDGET] || "", 10);
|
|
1964
|
-
return isNaN(val) ? 8e3 : Math.max(1024, val);
|
|
1965
|
-
}
|
|
1966
|
-
function getScopeMode() {
|
|
1967
|
-
return process.env[ENV_KEYS.SCOPE_MODE] === "enforce" ? "enforce" : "advisory";
|
|
1968
|
-
}
|
|
1969
|
-
function getApprovalMode() {
|
|
1970
|
-
return process.env[ENV_KEYS.APPROVAL_MODE] === "require_auto_approve" ? "require_auto_approve" : "advisory";
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
1925
|
// src/shared/utils/config/tor/core.ts
|
|
1974
1926
|
var TOR_PROXY = {
|
|
1975
1927
|
/** SOCKS5 proxy host (Tor daemon listens here) */
|
|
@@ -2068,31 +2020,10 @@ function checkTorLeakRisk(command) {
|
|
|
2068
2020
|
return { safe: true };
|
|
2069
2021
|
}
|
|
2070
2022
|
|
|
2071
|
-
// src/shared/utils/config/validation.ts
|
|
2072
|
-
function validateRequiredConfig() {
|
|
2073
|
-
const errors = [];
|
|
2074
|
-
if (!getApiKey()) {
|
|
2075
|
-
errors.push(
|
|
2076
|
-
`[config] PENTEST_API_KEY is required.
|
|
2077
|
-
Export it before running:
|
|
2078
|
-
export PENTEST_API_KEY=your_api_key
|
|
2079
|
-
For z.ai: get your key at https://api.z.ai`
|
|
2080
|
-
);
|
|
2081
|
-
}
|
|
2082
|
-
const budgetRaw = process.env[ENV_KEYS.THINKING_BUDGET];
|
|
2083
|
-
if (budgetRaw !== void 0 && budgetRaw !== "") {
|
|
2084
|
-
const parsed = parseInt(budgetRaw, 10);
|
|
2085
|
-
if (isNaN(parsed) || parsed < 1024) {
|
|
2086
|
-
errors.push(
|
|
2087
|
-
`[config] PENTEST_THINKING_BUDGET must be an integer \u2265 1024 (got: "${budgetRaw}")`
|
|
2088
|
-
);
|
|
2089
|
-
}
|
|
2090
|
-
}
|
|
2091
|
-
return errors;
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
2023
|
// src/engine/tools-base/command-executor/internal.ts
|
|
2095
2024
|
import { spawn as spawn3 } from "child_process";
|
|
2025
|
+
|
|
2026
|
+
// src/engine/tools-base/command-executor/preparer.ts
|
|
2096
2027
|
function injectCurlMaxTime(command, timeoutSec) {
|
|
2097
2028
|
if (!/\bcurl\b/.test(command)) return command;
|
|
2098
2029
|
if (/--max-time\b|-m\s+\d/.test(command)) return command;
|
|
@@ -2110,45 +2041,38 @@ Reason: ${torLeak.reason}
|
|
|
2110
2041
|
Suggestion: ${torLeak.suggestion}`
|
|
2111
2042
|
};
|
|
2112
2043
|
}
|
|
2113
|
-
return {
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
onTimeout();
|
|
2118
|
-
try {
|
|
2119
|
-
process.kill(-child.pid, "SIGKILL");
|
|
2120
|
-
} catch {
|
|
2121
|
-
try {
|
|
2122
|
-
child.kill("SIGKILL");
|
|
2123
|
-
} catch {
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
}, timeoutMs);
|
|
2044
|
+
return {
|
|
2045
|
+
safeCommand,
|
|
2046
|
+
execCommand: wrapCommandForTor(safeCommand)
|
|
2047
|
+
};
|
|
2127
2048
|
}
|
|
2049
|
+
|
|
2050
|
+
// src/engine/tools-base/command-executor/io-handler.ts
|
|
2128
2051
|
var ProcessIOHandler = class {
|
|
2129
|
-
constructor(
|
|
2130
|
-
this.
|
|
2131
|
-
this.processState = processState;
|
|
2132
|
-
this.eventEmitter = eventEmitter;
|
|
2133
|
-
this.inputHandler = inputHandler;
|
|
2052
|
+
constructor(ctx) {
|
|
2053
|
+
this.ctx = ctx;
|
|
2134
2054
|
}
|
|
2135
2055
|
stdout = "";
|
|
2136
2056
|
stderr = "";
|
|
2137
2057
|
inputHandled = false;
|
|
2058
|
+
/**
|
|
2059
|
+
* Checks if the data contains a known input prompt pattern.
|
|
2060
|
+
* If found, it invokes the inputHandler and writes to stdin.
|
|
2061
|
+
*/
|
|
2138
2062
|
async checkForInputPrompt(data) {
|
|
2139
|
-
if (this.inputHandled || !this.inputHandler || this.processState.terminated) return;
|
|
2063
|
+
if (this.inputHandled || !this.ctx.inputHandler || this.ctx.processState.terminated) return;
|
|
2140
2064
|
for (const pattern of INPUT_PROMPT_PATTERNS) {
|
|
2141
2065
|
if (pattern.test(data)) {
|
|
2142
2066
|
this.inputHandled = true;
|
|
2143
|
-
this.eventEmitter?.({
|
|
2067
|
+
this.ctx.eventEmitter?.({
|
|
2144
2068
|
type: COMMAND_EVENT_TYPES.INPUT_REQUIRED,
|
|
2145
2069
|
message: `Input required: ${data.trim().slice(0, SYSTEM_LIMITS.MAX_PROMPT_PREVIEW)}`,
|
|
2146
2070
|
detail: "Waiting for user input..."
|
|
2147
2071
|
});
|
|
2148
2072
|
try {
|
|
2149
|
-
const userInput = await this.inputHandler(data.trim());
|
|
2150
|
-
if (userInput !== null && !this.processState.terminated && this.child.stdin?.writable) {
|
|
2151
|
-
this.child.stdin.write(userInput + "\n");
|
|
2073
|
+
const userInput = await this.ctx.inputHandler(data.trim());
|
|
2074
|
+
if (userInput !== null && !this.ctx.processState.terminated && this.ctx.child.stdin?.writable) {
|
|
2075
|
+
this.ctx.child.stdin.write(userInput + "\n");
|
|
2152
2076
|
}
|
|
2153
2077
|
} catch {
|
|
2154
2078
|
}
|
|
@@ -2156,6 +2080,9 @@ var ProcessIOHandler = class {
|
|
|
2156
2080
|
}
|
|
2157
2081
|
}
|
|
2158
2082
|
}
|
|
2083
|
+
/**
|
|
2084
|
+
* Handles a chunk of stdout data.
|
|
2085
|
+
*/
|
|
2159
2086
|
handleStdout(data) {
|
|
2160
2087
|
const text = data.toString();
|
|
2161
2088
|
this.stdout += text;
|
|
@@ -2164,13 +2091,16 @@ var ProcessIOHandler = class {
|
|
|
2164
2091
|
}
|
|
2165
2092
|
const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2166
2093
|
if (lines.length > 0) {
|
|
2167
|
-
this.eventEmitter?.({
|
|
2094
|
+
this.ctx.eventEmitter?.({
|
|
2168
2095
|
type: COMMAND_EVENT_TYPES.COMMAND_STDOUT,
|
|
2169
2096
|
message: lines[lines.length - 1]
|
|
2170
2097
|
});
|
|
2171
2098
|
}
|
|
2172
2099
|
this.checkForInputPrompt(text);
|
|
2173
2100
|
}
|
|
2101
|
+
/**
|
|
2102
|
+
* Handles a chunk of stderr data.
|
|
2103
|
+
*/
|
|
2174
2104
|
handleStderr(data) {
|
|
2175
2105
|
const text = data.toString();
|
|
2176
2106
|
this.stderr += text;
|
|
@@ -2180,6 +2110,23 @@ var ProcessIOHandler = class {
|
|
|
2180
2110
|
this.checkForInputPrompt(text);
|
|
2181
2111
|
}
|
|
2182
2112
|
};
|
|
2113
|
+
|
|
2114
|
+
// src/engine/tools-base/command-executor/internal.ts
|
|
2115
|
+
function setupTimeout(child, timeoutMs, onTimeout) {
|
|
2116
|
+
return setTimeout(() => {
|
|
2117
|
+
onTimeout();
|
|
2118
|
+
try {
|
|
2119
|
+
if (child.pid) {
|
|
2120
|
+
process.kill(-child.pid, "SIGKILL");
|
|
2121
|
+
}
|
|
2122
|
+
} catch {
|
|
2123
|
+
try {
|
|
2124
|
+
child.kill("SIGKILL");
|
|
2125
|
+
} catch {
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
}, timeoutMs);
|
|
2129
|
+
}
|
|
2183
2130
|
async function executeCommandOnce(command, options = {}) {
|
|
2184
2131
|
return new Promise((resolve) => {
|
|
2185
2132
|
const eventEmitter = getEventEmitter();
|
|
@@ -2202,7 +2149,12 @@ async function executeCommandOnce(command, options = {}) {
|
|
|
2202
2149
|
processState.timedOut = true;
|
|
2203
2150
|
processState.terminated = true;
|
|
2204
2151
|
});
|
|
2205
|
-
const ioHandler = new ProcessIOHandler(
|
|
2152
|
+
const ioHandler = new ProcessIOHandler({
|
|
2153
|
+
child,
|
|
2154
|
+
processState,
|
|
2155
|
+
eventEmitter,
|
|
2156
|
+
inputHandler: getInputHandler()
|
|
2157
|
+
});
|
|
2206
2158
|
child.stdout.on("data", (d) => ioHandler.handleStdout(d));
|
|
2207
2159
|
child.stderr.on("data", (d) => ioHandler.handleStderr(d));
|
|
2208
2160
|
child.on("close", (code) => {
|
|
@@ -2211,7 +2163,10 @@ async function executeCommandOnce(command, options = {}) {
|
|
|
2211
2163
|
const outTrimmed = ioHandler.stdout.trim();
|
|
2212
2164
|
const errTrimmed = ioHandler.stderr.trim();
|
|
2213
2165
|
if (processState.timedOut) {
|
|
2214
|
-
eventEmitter?.({
|
|
2166
|
+
eventEmitter?.({
|
|
2167
|
+
type: COMMAND_EVENT_TYPES.COMMAND_FAILED,
|
|
2168
|
+
message: `Command timed out after ${Math.round(timeoutMs / 1e3)}s`
|
|
2169
|
+
});
|
|
2215
2170
|
return resolve({
|
|
2216
2171
|
success: false,
|
|
2217
2172
|
output: outTrimmed,
|
|
@@ -2223,7 +2178,11 @@ ${(ioHandler.stdout + ioHandler.stderr).trim().slice(-500)}`
|
|
|
2223
2178
|
eventEmitter?.({ type: COMMAND_EVENT_TYPES.COMMAND_SUCCESS, message: `Command completed` });
|
|
2224
2179
|
return resolve({ success: true, output: outTrimmed || errTrimmed });
|
|
2225
2180
|
}
|
|
2226
|
-
eventEmitter?.({
|
|
2181
|
+
eventEmitter?.({
|
|
2182
|
+
type: COMMAND_EVENT_TYPES.COMMAND_FAILED,
|
|
2183
|
+
message: `Command failed (exit ${code})`,
|
|
2184
|
+
detail: errTrimmed.slice(0, SYSTEM_LIMITS.MAX_INPUT_SLICE)
|
|
2185
|
+
});
|
|
2227
2186
|
return resolve({ success: false, output: outTrimmed, error: errTrimmed || `Process exited with code ${code}` });
|
|
2228
2187
|
});
|
|
2229
2188
|
child.on("error", (err) => {
|
|
@@ -2647,7 +2606,7 @@ async function cleanupAllProcesses() {
|
|
|
2647
2606
|
cleanupDone = false;
|
|
2648
2607
|
return;
|
|
2649
2608
|
}
|
|
2650
|
-
const { getBackgroundProcessesMap: getBackgroundProcessesMap2 } = await import("./process-registry-
|
|
2609
|
+
const { getBackgroundProcessesMap: getBackgroundProcessesMap2 } = await import("./process-registry-GSHEX2LT.js");
|
|
2651
2610
|
const backgroundProcesses = getBackgroundProcessesMap2();
|
|
2652
2611
|
terminateAllNatively(backgroundProcesses, "SIGTERM");
|
|
2653
2612
|
await new Promise((r) => setTimeout(r, SYSTEM_LIMITS.CLEANUP_BATCH_WAIT_MS));
|
|
@@ -2830,6 +2789,799 @@ function registerExitHandlers(processMap) {
|
|
|
2830
2789
|
// src/engine/process/process-manager.ts
|
|
2831
2790
|
registerExitHandlers(getBackgroundProcessesMap());
|
|
2832
2791
|
|
|
2792
|
+
// src/shared/utils/agent-memory/types.ts
|
|
2793
|
+
var MEMORY_ENTRY_CATEGORIES = {
|
|
2794
|
+
SUCCESS: "success",
|
|
2795
|
+
FAILURE: "failure",
|
|
2796
|
+
DISCOVERY: "discovery",
|
|
2797
|
+
TECHNIQUE: "technique",
|
|
2798
|
+
INSIGHT: "insight"
|
|
2799
|
+
};
|
|
2800
|
+
var EPISODIC_EVENT_TYPES = {
|
|
2801
|
+
TOOL_SUCCESS: "tool_success",
|
|
2802
|
+
TOOL_FAILURE: "tool_failure",
|
|
2803
|
+
PHASE_CHANGE: "phase_change",
|
|
2804
|
+
FLAG_FOUND: "flag_found",
|
|
2805
|
+
ACCESS_GAINED: "access_gained",
|
|
2806
|
+
VECTOR_SWITCH: "vector_switch"
|
|
2807
|
+
};
|
|
2808
|
+
|
|
2809
|
+
// src/shared/utils/agent-memory/fingerprint.ts
|
|
2810
|
+
var WORDLIST_FLAGS = /* @__PURE__ */ new Set(["-w", "-P", "-U", "-L", "--wordlist", "--password", "--passwords", "--username", "--usernames"]);
|
|
2811
|
+
var PORT_FLAGS = /* @__PURE__ */ new Set(["-p", "--port"]);
|
|
2812
|
+
var SCRIPT_RUNNERS = /* @__PURE__ */ new Set(["python", "python3", "bash", "sh", "zsh", "node", "ruby", "perl"]);
|
|
2813
|
+
function stripWrappingQuotes(value) {
|
|
2814
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
2815
|
+
return value.slice(1, -1);
|
|
2816
|
+
}
|
|
2817
|
+
return value;
|
|
2818
|
+
}
|
|
2819
|
+
function tokenizeCommand(command) {
|
|
2820
|
+
const matches = command.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
|
|
2821
|
+
return matches.map(stripWrappingQuotes);
|
|
2822
|
+
}
|
|
2823
|
+
function parseOptionTokens(tokens) {
|
|
2824
|
+
const options = [];
|
|
2825
|
+
const positionals = [];
|
|
2826
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
2827
|
+
const token = tokens[i];
|
|
2828
|
+
if (token.startsWith("--")) {
|
|
2829
|
+
const eqIndex = token.indexOf("=");
|
|
2830
|
+
if (eqIndex !== -1) {
|
|
2831
|
+
options.push({ flag: token.slice(0, eqIndex), value: token.slice(eqIndex + 1) });
|
|
2832
|
+
continue;
|
|
2833
|
+
}
|
|
2834
|
+
const next = tokens[i + 1];
|
|
2835
|
+
if (next && !next.startsWith("-")) {
|
|
2836
|
+
options.push({ flag: token, value: next });
|
|
2837
|
+
i++;
|
|
2838
|
+
} else {
|
|
2839
|
+
options.push({ flag: token, value: "" });
|
|
2840
|
+
}
|
|
2841
|
+
continue;
|
|
2842
|
+
}
|
|
2843
|
+
if (token.startsWith("-") && token.length > 1) {
|
|
2844
|
+
const next = tokens[i + 1];
|
|
2845
|
+
const expectsValue = /^-[A-Za-z]$/.test(token) || /^-[a-z]{2,}$/.test(token);
|
|
2846
|
+
const canConsumeNext = expectsValue && next && !next.startsWith("-");
|
|
2847
|
+
if (canConsumeNext) {
|
|
2848
|
+
options.push({ flag: token, value: next });
|
|
2849
|
+
i++;
|
|
2850
|
+
} else {
|
|
2851
|
+
options.push({ flag: token, value: "" });
|
|
2852
|
+
}
|
|
2853
|
+
continue;
|
|
2854
|
+
}
|
|
2855
|
+
positionals.push(token);
|
|
2856
|
+
}
|
|
2857
|
+
return { options, positionals };
|
|
2858
|
+
}
|
|
2859
|
+
function findOptionValue(options, flags) {
|
|
2860
|
+
return options.find((option) => flags.has(option.flag))?.value || "";
|
|
2861
|
+
}
|
|
2862
|
+
function buildOptionSignature(options, positionals, effectiveTool) {
|
|
2863
|
+
const entries = options.filter((option) => !WORDLIST_FLAGS.has(option.flag) && !PORT_FLAGS.has(option.flag)).map((option) => option.value ? `${option.flag}=${option.value}` : option.flag);
|
|
2864
|
+
if (SCRIPT_RUNNERS.has(effectiveTool) && positionals.length > 0) {
|
|
2865
|
+
entries.push(`script=${positionals[0]}`);
|
|
2866
|
+
}
|
|
2867
|
+
return Array.from(new Set(entries)).sort().join(" ");
|
|
2868
|
+
}
|
|
2869
|
+
function extractFingerprint(tool, command) {
|
|
2870
|
+
const cmd = command || "";
|
|
2871
|
+
const tokens = tokenizeCommand(cmd);
|
|
2872
|
+
let effectiveTool = tool.toLowerCase();
|
|
2873
|
+
if (effectiveTool === "run_cmd" || effectiveTool === "run_background") {
|
|
2874
|
+
const firstWord = tokens[0];
|
|
2875
|
+
if (firstWord && !firstWord.startsWith("-")) {
|
|
2876
|
+
effectiveTool = firstWord.toLowerCase();
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
const { options, positionals } = parseOptionTokens(tokens);
|
|
2880
|
+
const targetMatch = cmd.match(
|
|
2881
|
+
/(?::\/\/|@)([\w.\-]+(?::\d+)?)|\b(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?::\d+)?\b/
|
|
2882
|
+
);
|
|
2883
|
+
const target = targetMatch?.[1] || targetMatch?.[2] || "";
|
|
2884
|
+
const wordlist = findOptionValue(options, WORDLIST_FLAGS);
|
|
2885
|
+
const port = findOptionValue(options, PORT_FLAGS);
|
|
2886
|
+
const flags = buildOptionSignature(options, positionals, effectiveTool);
|
|
2887
|
+
return { tool: effectiveTool, target, wordlist, flags, port };
|
|
2888
|
+
}
|
|
2889
|
+
function fingerprintsMatch(a, b) {
|
|
2890
|
+
return a.tool === b.tool && a.target === b.target && a.wordlist === b.wordlist && a.flags === b.flags && a.port === b.port;
|
|
2891
|
+
}
|
|
2892
|
+
function formatFingerprint(fp) {
|
|
2893
|
+
const parts = [fp.tool];
|
|
2894
|
+
if (fp.target) parts.push(`\u2192${fp.target}`);
|
|
2895
|
+
if (fp.wordlist) parts.push(`[wordlist:${fp.wordlist.split("/").pop()}]`);
|
|
2896
|
+
if (fp.port) parts.push(`[port:${fp.port}]`);
|
|
2897
|
+
if (fp.flags) parts.push(`[${fp.flags}]`);
|
|
2898
|
+
return parts.join(" ");
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2901
|
+
// src/shared/utils/agent-memory/working-memory.ts
|
|
2902
|
+
var PRUNE_IMPORTANCE_WEIGHT = 0.7;
|
|
2903
|
+
var PRUNE_RECENCY_WEIGHT = 0.3;
|
|
2904
|
+
var PRUNE_WINDOW_MS = 6e5;
|
|
2905
|
+
var WorkingMemory = class {
|
|
2906
|
+
entries = [];
|
|
2907
|
+
maxEntries = MEMORY_LIMITS.WORKING_MEMORY_MAX_ENTRIES;
|
|
2908
|
+
add(category, content, importance = 0.5, context = {}) {
|
|
2909
|
+
this.entries.push({
|
|
2910
|
+
id: generatePrefixedId("wm"),
|
|
2911
|
+
timestamp: Date.now(),
|
|
2912
|
+
category,
|
|
2913
|
+
content,
|
|
2914
|
+
context,
|
|
2915
|
+
importance
|
|
2916
|
+
});
|
|
2917
|
+
this.pruneIfNeeded();
|
|
2918
|
+
}
|
|
2919
|
+
/**
|
|
2920
|
+
* Record a failed attempt with structured fingerprint.
|
|
2921
|
+
*
|
|
2922
|
+
* WHY: Fingerprint-based recording ensures that "hydra with rockyou.txt"
|
|
2923
|
+
* and "hydra with darkweb2017.txt" are stored as distinct attempts.
|
|
2924
|
+
* The LLM can see what parameter combinations have been tried and pick new ones.
|
|
2925
|
+
*/
|
|
2926
|
+
recordFailure(tool, command, error, hypothesizedReason) {
|
|
2927
|
+
const fp = extractFingerprint(tool, command);
|
|
2928
|
+
fp.hypothesizedReason = hypothesizedReason;
|
|
2929
|
+
const fpLabel = formatFingerprint(fp);
|
|
2930
|
+
const entry = {
|
|
2931
|
+
id: generatePrefixedId("wm"),
|
|
2932
|
+
timestamp: Date.now(),
|
|
2933
|
+
category: MEMORY_ENTRY_CATEGORIES.FAILURE,
|
|
2934
|
+
content: `FAILED: ${fpLabel} \u2192 ${error.slice(0, DISPLAY_LIMITS.ERROR_PREVIEW)}`,
|
|
2935
|
+
context: { tool, command },
|
|
2936
|
+
importance: 0.8,
|
|
2937
|
+
fingerprint: fp
|
|
2938
|
+
};
|
|
2939
|
+
this.entries.push(entry);
|
|
2940
|
+
this.pruneIfNeeded();
|
|
2941
|
+
}
|
|
2942
|
+
/**
|
|
2943
|
+
* Record a successful action for reference.
|
|
2944
|
+
*/
|
|
2945
|
+
recordSuccess(tool, command, result) {
|
|
2946
|
+
this.add(MEMORY_ENTRY_CATEGORIES.SUCCESS, `SUCCESS: ${tool} \u2192 ${command.slice(0, DISPLAY_LIMITS.COMMAND_PREVIEW)}`, 0.6, { tool, result: result.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY) });
|
|
2947
|
+
}
|
|
2948
|
+
/**
|
|
2949
|
+
* Check if the EXACT same attack attempt (all parameters identical) has failed.
|
|
2950
|
+
*
|
|
2951
|
+
* WHY: Only blocks truly duplicate attempts. Different wordlists, flags, or
|
|
2952
|
+
* ports are treated as new attacks and allowed through.
|
|
2953
|
+
*/
|
|
2954
|
+
hasFailedBefore(command, tool) {
|
|
2955
|
+
const effectiveTool = tool || command.split(/\s+/)[0] || "";
|
|
2956
|
+
const fp = extractFingerprint(effectiveTool, command);
|
|
2957
|
+
return this.entries.find(
|
|
2958
|
+
(e) => e.category === MEMORY_ENTRY_CATEGORIES.FAILURE && e.fingerprint != null && fingerprintsMatch(e.fingerprint, fp)
|
|
2959
|
+
);
|
|
2960
|
+
}
|
|
2961
|
+
/**
|
|
2962
|
+
* 사후 보완: Analyst가 분류한 hypothesizedReason을 기존 실패 항목에 추가한다.
|
|
2963
|
+
*
|
|
2964
|
+
* WHY: pipeline.ts에서 recordFailure()가 먼저 호출되고, Analyst 분석은
|
|
2965
|
+
* 비동기로 나중에 완료된다. command 매칭으로 최근 실패 fingerprint를 찾아
|
|
2966
|
+
* hypothesizedReason을 보완한다. 기존 reason이 있으면 덮어쓰지 않는다.
|
|
2967
|
+
*/
|
|
2968
|
+
updateLastFailureReason(command, hypothesizedReason) {
|
|
2969
|
+
const effectiveTool = command.split(/\s+/)[0] || "";
|
|
2970
|
+
const fp = extractFingerprint(effectiveTool, command);
|
|
2971
|
+
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
2972
|
+
const entry = this.entries[i];
|
|
2973
|
+
if (entry.category === MEMORY_ENTRY_CATEGORIES.FAILURE && entry.fingerprint && fingerprintsMatch(entry.fingerprint, fp)) {
|
|
2974
|
+
if (!entry.fingerprint.hypothesizedReason) {
|
|
2975
|
+
entry.fingerprint.hypothesizedReason = hypothesizedReason;
|
|
2976
|
+
}
|
|
2977
|
+
break;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
/** Internal prune helper (used by both add() and recordFailure()) */
|
|
2982
|
+
pruneIfNeeded() {
|
|
2983
|
+
if (this.entries.length > this.maxEntries) {
|
|
2984
|
+
this.entries.sort((a, b) => {
|
|
2985
|
+
const aScore = a.importance * PRUNE_IMPORTANCE_WEIGHT + (1 - (Date.now() - a.timestamp) / PRUNE_WINDOW_MS) * PRUNE_RECENCY_WEIGHT;
|
|
2986
|
+
const bScore = b.importance * PRUNE_IMPORTANCE_WEIGHT + (1 - (Date.now() - b.timestamp) / PRUNE_WINDOW_MS) * PRUNE_RECENCY_WEIGHT;
|
|
2987
|
+
return bScore - aScore;
|
|
2988
|
+
});
|
|
2989
|
+
this.entries = this.entries.slice(0, this.maxEntries);
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
/**
|
|
2993
|
+
* Get count of consecutive failures on the SAME attack vector.
|
|
2994
|
+
*
|
|
2995
|
+
* WHY: Switching from "hydra with wordlist A" to "hydra with wordlist B"
|
|
2996
|
+
* is a legitimate new approach. The consecutive-failure counter should
|
|
2997
|
+
* only trigger when every recent attempt is against the same tool+target
|
|
2998
|
+
* with no variation producing success. This prevents premature vector abandonment.
|
|
2999
|
+
*/
|
|
3000
|
+
getConsecutiveFailures() {
|
|
3001
|
+
let count = 0;
|
|
3002
|
+
for (let i = this.entries.length - 1; i >= 0; i--) {
|
|
3003
|
+
if (this.entries[i].category === MEMORY_ENTRY_CATEGORIES.FAILURE) count++;
|
|
3004
|
+
else break;
|
|
3005
|
+
}
|
|
3006
|
+
return count;
|
|
3007
|
+
}
|
|
3008
|
+
/**
|
|
3009
|
+
* Format for prompt injection.
|
|
3010
|
+
*/
|
|
3011
|
+
toPrompt() {
|
|
3012
|
+
if (this.entries.length === 0) return "";
|
|
3013
|
+
const failures = this.entries.filter((e) => e.category === MEMORY_ENTRY_CATEGORIES.FAILURE);
|
|
3014
|
+
const successes = this.entries.filter((e) => e.category === MEMORY_ENTRY_CATEGORIES.SUCCESS);
|
|
3015
|
+
const insights = this.entries.filter((e) => e.category === MEMORY_ENTRY_CATEGORIES.INSIGHT || e.category === MEMORY_ENTRY_CATEGORIES.DISCOVERY);
|
|
3016
|
+
const lines = ["<working-memory>"];
|
|
3017
|
+
lines.push(...this.buildFailuresSection(failures));
|
|
3018
|
+
lines.push(...this.buildSuccessesSection(successes));
|
|
3019
|
+
lines.push(...this.buildInsightsSection(insights));
|
|
3020
|
+
lines.push("</working-memory>");
|
|
3021
|
+
return lines.join("\n");
|
|
3022
|
+
}
|
|
3023
|
+
buildFailuresSection(failures) {
|
|
3024
|
+
if (failures.length === 0) return [];
|
|
3025
|
+
const lines = [];
|
|
3026
|
+
lines.push(`\u26A0\uFE0F FAILED ATTEMPTS (${failures.length} \u2014 DO NOT REPEAT EXACT SAME PARAMS):`);
|
|
3027
|
+
for (const f of failures.slice(-DISPLAY_LIMITS.RECENT_FAILURES)) {
|
|
3028
|
+
const fp = f.fingerprint;
|
|
3029
|
+
if (fp) {
|
|
3030
|
+
const reason = fp.hypothesizedReason ? ` [${fp.hypothesizedReason}]` : "";
|
|
3031
|
+
lines.push(` \u2717 ${formatFingerprint(fp)}${reason} \u2192 ${f.content.split("\u2192").pop()?.trim() || ""}`);
|
|
3032
|
+
} else {
|
|
3033
|
+
lines.push(` \u2717 ${f.content}`);
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
lines.push(...this.buildAttackCoverageLines(failures));
|
|
3037
|
+
const consecutiveFails = this.getConsecutiveFailures();
|
|
3038
|
+
if (consecutiveFails >= MEMORY_LIMITS.CONSECUTIVE_FAIL_THRESHOLD) {
|
|
3039
|
+
lines.push(`\u{1F534} ${consecutiveFails} CONSECUTIVE FAILURES \u2014 consider changing approach or parameters`);
|
|
3040
|
+
}
|
|
3041
|
+
return lines;
|
|
3042
|
+
}
|
|
3043
|
+
buildSuccessesSection(successes) {
|
|
3044
|
+
if (successes.length === 0) return [];
|
|
3045
|
+
const lines = [`\u2705 RECENT SUCCESSES (${successes.length}):`];
|
|
3046
|
+
for (const s of successes.slice(-DISPLAY_LIMITS.RECENT_SUCCESSES)) {
|
|
3047
|
+
lines.push(` \u2713 ${s.content}`);
|
|
3048
|
+
}
|
|
3049
|
+
return lines;
|
|
3050
|
+
}
|
|
3051
|
+
buildInsightsSection(insights) {
|
|
3052
|
+
if (insights.length === 0) return [];
|
|
3053
|
+
const lines = ["\u{1F4A1} INSIGHTS:"];
|
|
3054
|
+
for (const i of insights.slice(-DISPLAY_LIMITS.RECENT_INSIGHTS)) {
|
|
3055
|
+
lines.push(` \u2192 ${i.content}`);
|
|
3056
|
+
}
|
|
3057
|
+
return lines;
|
|
3058
|
+
}
|
|
3059
|
+
/**
|
|
3060
|
+
* Build ATTACK COVERAGE lines grouped by tool+target.
|
|
3061
|
+
* WHY: Shows the LLM which parameter combinations have been tried per vector,
|
|
3062
|
+
* so it can pick genuinely untried variations instead of repeating.
|
|
3063
|
+
*/
|
|
3064
|
+
buildAttackCoverageLines(failures) {
|
|
3065
|
+
const vectorMap = /* @__PURE__ */ new Map();
|
|
3066
|
+
for (const f of failures) {
|
|
3067
|
+
if (f.fingerprint) {
|
|
3068
|
+
const key = `${f.fingerprint.tool}\u2192${f.fingerprint.target}`;
|
|
3069
|
+
const detail = f.fingerprint.wordlist ? f.fingerprint.wordlist.split("/").pop() || "" : f.fingerprint.flags || "default";
|
|
3070
|
+
const existing = vectorMap.get(key) || [];
|
|
3071
|
+
if (!existing.includes(detail)) existing.push(detail);
|
|
3072
|
+
vectorMap.set(key, existing);
|
|
3073
|
+
}
|
|
3074
|
+
}
|
|
3075
|
+
if (vectorMap.size === 0) return [];
|
|
3076
|
+
const lines = [` \u{1F4CA} ATTACK COVERAGE (tried variations):`];
|
|
3077
|
+
for (const [vector, variations] of vectorMap) {
|
|
3078
|
+
lines.push(` ${vector}: tried [${variations.join(", ")}] \u2014 try DIFFERENT params`);
|
|
3079
|
+
}
|
|
3080
|
+
return lines;
|
|
3081
|
+
}
|
|
3082
|
+
getEntries() {
|
|
3083
|
+
return [...this.entries];
|
|
3084
|
+
}
|
|
3085
|
+
/** Clear all working memory entries. */
|
|
3086
|
+
clear() {
|
|
3087
|
+
this.entries = [];
|
|
3088
|
+
}
|
|
3089
|
+
};
|
|
3090
|
+
|
|
3091
|
+
// src/shared/utils/agent-memory/episodic-memory.ts
|
|
3092
|
+
var EpisodicMemory = class {
|
|
3093
|
+
events = [];
|
|
3094
|
+
maxEvents = MEMORY_LIMITS.EPISODIC_MEMORY_MAX_EVENTS;
|
|
3095
|
+
record(type, summary, details = {}) {
|
|
3096
|
+
this.events.push({
|
|
3097
|
+
timestamp: Date.now(),
|
|
3098
|
+
type,
|
|
3099
|
+
summary,
|
|
3100
|
+
details
|
|
3101
|
+
});
|
|
3102
|
+
if (this.events.length > this.maxEvents) {
|
|
3103
|
+
this.events = this.events.slice(-this.maxEvents);
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
/**
|
|
3107
|
+
* Get timeline of key events (for strategic overview).
|
|
3108
|
+
*/
|
|
3109
|
+
getTimeline() {
|
|
3110
|
+
return [...this.events];
|
|
3111
|
+
}
|
|
3112
|
+
/**
|
|
3113
|
+
* Get events of a specific type.
|
|
3114
|
+
*/
|
|
3115
|
+
getByType(type) {
|
|
3116
|
+
return this.events.filter((e) => e.type === type);
|
|
3117
|
+
}
|
|
3118
|
+
/**
|
|
3119
|
+
* Count how many times a vector has been switched (indicator of difficulty).
|
|
3120
|
+
*/
|
|
3121
|
+
getVectorSwitchCount() {
|
|
3122
|
+
return this.events.filter((e) => e.type === EPISODIC_EVENT_TYPES.VECTOR_SWITCH).length;
|
|
3123
|
+
}
|
|
3124
|
+
/**
|
|
3125
|
+
* Format for prompt (condensed timeline).
|
|
3126
|
+
*/
|
|
3127
|
+
toPrompt() {
|
|
3128
|
+
if (this.events.length === 0) return "";
|
|
3129
|
+
const lines = ["<session-timeline>"];
|
|
3130
|
+
const recent = this.events.slice(-DISPLAY_LIMITS.RECENT_MEMORY_EVENTS);
|
|
3131
|
+
for (const e of recent) {
|
|
3132
|
+
const mins = Math.floor((Date.now() - e.timestamp) / MS_PER_MINUTE);
|
|
3133
|
+
const icon = {
|
|
3134
|
+
tool_success: "\u2705",
|
|
3135
|
+
tool_failure: "\u274C",
|
|
3136
|
+
phase_change: "\u{1F504}",
|
|
3137
|
+
flag_found: "\u{1F3C1}",
|
|
3138
|
+
access_gained: "\u{1F513}",
|
|
3139
|
+
vector_switch: "\u21AA\uFE0F"
|
|
3140
|
+
}[e.type] || "\u2022";
|
|
3141
|
+
lines.push(` ${icon} [${mins}min ago] ${e.summary}`);
|
|
3142
|
+
}
|
|
3143
|
+
lines.push("</session-timeline>");
|
|
3144
|
+
return lines.join("\n");
|
|
3145
|
+
}
|
|
3146
|
+
/** Clear all episodic events. */
|
|
3147
|
+
clear() {
|
|
3148
|
+
this.events = [];
|
|
3149
|
+
}
|
|
3150
|
+
};
|
|
3151
|
+
|
|
3152
|
+
// src/shared/utils/agent-memory/persistent-memory.ts
|
|
3153
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
3154
|
+
import { join as join4 } from "path";
|
|
3155
|
+
|
|
3156
|
+
// src/shared/utils/agent-memory/similarity.ts
|
|
3157
|
+
var JACCARD_MATCH_THRESHOLD = 0.15;
|
|
3158
|
+
var MIN_TOKEN_LENGTH = 2;
|
|
3159
|
+
function tokenize(s) {
|
|
3160
|
+
return new Set(
|
|
3161
|
+
s.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= MIN_TOKEN_LENGTH)
|
|
3162
|
+
);
|
|
3163
|
+
}
|
|
3164
|
+
function jaccardSimilarity(a, b) {
|
|
3165
|
+
const setA = tokenize(a);
|
|
3166
|
+
const setB = tokenize(b);
|
|
3167
|
+
if (setA.size === 0 || setB.size === 0) return 0;
|
|
3168
|
+
let intersection = 0;
|
|
3169
|
+
for (const token of setA) {
|
|
3170
|
+
if (setB.has(token)) intersection++;
|
|
3171
|
+
}
|
|
3172
|
+
const union = setA.size + setB.size - intersection;
|
|
3173
|
+
return union === 0 ? 0 : intersection / union;
|
|
3174
|
+
}
|
|
3175
|
+
function matchesServiceProfile(serviceProfile, services) {
|
|
3176
|
+
if (services.length === 0) return false;
|
|
3177
|
+
const query = services.join(" ");
|
|
3178
|
+
return jaccardSimilarity(serviceProfile, query) >= JACCARD_MATCH_THRESHOLD;
|
|
3179
|
+
}
|
|
3180
|
+
|
|
3181
|
+
// src/shared/utils/agent-memory/session-snapshot.ts
|
|
3182
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4, unlinkSync as unlinkSync4 } from "fs";
|
|
3183
|
+
import { join as join3 } from "path";
|
|
3184
|
+
var SNAPSHOT_FILE = join3(WORKSPACE.MEMORY, SPECIAL_FILES.SESSION_SNAPSHOT);
|
|
3185
|
+
function saveSessionSnapshot(snapshot) {
|
|
3186
|
+
try {
|
|
3187
|
+
ensureDirExists(WORKSPACE.MEMORY);
|
|
3188
|
+
writeFileSync4(SNAPSHOT_FILE, JSON.stringify(snapshot, null, 2));
|
|
3189
|
+
} catch {
|
|
3190
|
+
}
|
|
3191
|
+
}
|
|
3192
|
+
function loadSessionSnapshot() {
|
|
3193
|
+
try {
|
|
3194
|
+
if (existsSync6(SNAPSHOT_FILE)) {
|
|
3195
|
+
return JSON.parse(readFileSync6(SNAPSHOT_FILE, "utf-8"));
|
|
3196
|
+
}
|
|
3197
|
+
} catch {
|
|
3198
|
+
}
|
|
3199
|
+
return null;
|
|
3200
|
+
}
|
|
3201
|
+
function snapshotToPrompt() {
|
|
3202
|
+
const snap = loadSessionSnapshot();
|
|
3203
|
+
if (!snap) return "";
|
|
3204
|
+
const lines = [
|
|
3205
|
+
"<session-snapshot>",
|
|
3206
|
+
`TARGET: ${snap.target} PHASE: ${snap.phase} SAVED: ${new Date(snap.savedAt).toISOString()}`,
|
|
3207
|
+
"",
|
|
3208
|
+
"ACHIEVED:",
|
|
3209
|
+
...snap.achieved.map((a) => ` \u2705 ${a}`),
|
|
3210
|
+
"",
|
|
3211
|
+
"NEXT PRIORITIES (resume here):",
|
|
3212
|
+
...snap.next.map((n, i) => ` ${i + 1}. ${n}`)
|
|
3213
|
+
];
|
|
3214
|
+
if (snap.credentials.length > 0) {
|
|
3215
|
+
lines.push("", "USABLE CREDENTIALS:");
|
|
3216
|
+
snap.credentials.forEach((c) => lines.push(` \u{1F511} ${c}`));
|
|
3217
|
+
}
|
|
3218
|
+
if (snap.notes) {
|
|
3219
|
+
lines.push("", `NOTES: ${snap.notes}`);
|
|
3220
|
+
}
|
|
3221
|
+
lines.push("</session-snapshot>");
|
|
3222
|
+
return lines.join("\n");
|
|
3223
|
+
}
|
|
3224
|
+
function clearSessionSnapshot() {
|
|
3225
|
+
try {
|
|
3226
|
+
if (existsSync6(SNAPSHOT_FILE)) {
|
|
3227
|
+
unlinkSync4(SNAPSHOT_FILE);
|
|
3228
|
+
}
|
|
3229
|
+
} catch {
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
|
|
3233
|
+
// src/shared/utils/agent-memory/persistent-memory.ts
|
|
3234
|
+
var MEMORY_FILE = join4(WORKSPACE.MEMORY, SPECIAL_FILES.PERSISTENT_KNOWLEDGE);
|
|
3235
|
+
var MAX_PLAYBOOK_DISPLAY = 3;
|
|
3236
|
+
var MAX_SUCCESSFUL_TECHNIQUES = 100;
|
|
3237
|
+
var MAX_FAILURE_PATTERNS = 100;
|
|
3238
|
+
var MAX_TECH_FACTS = 200;
|
|
3239
|
+
var MAX_EXPLOIT_CHAINS = 50;
|
|
3240
|
+
var PersistentMemory = class {
|
|
3241
|
+
knowledge;
|
|
3242
|
+
constructor() {
|
|
3243
|
+
this.knowledge = this.load();
|
|
3244
|
+
}
|
|
3245
|
+
/**
|
|
3246
|
+
* Record a successful technique.
|
|
3247
|
+
*/
|
|
3248
|
+
recordSuccess(service, version, technique) {
|
|
3249
|
+
const existing = this.knowledge.successfulTechniques.find(
|
|
3250
|
+
(t) => t.service === service && t.technique === technique
|
|
3251
|
+
);
|
|
3252
|
+
if (existing) {
|
|
3253
|
+
existing.successCount++;
|
|
3254
|
+
existing.lastUsed = Date.now();
|
|
3255
|
+
} else {
|
|
3256
|
+
this.knowledge.successfulTechniques.push({
|
|
3257
|
+
service,
|
|
3258
|
+
version,
|
|
3259
|
+
technique,
|
|
3260
|
+
successCount: 1,
|
|
3261
|
+
lastUsed: Date.now()
|
|
3262
|
+
});
|
|
3263
|
+
if (this.knowledge.successfulTechniques.length > MAX_SUCCESSFUL_TECHNIQUES) {
|
|
3264
|
+
this.knowledge.successfulTechniques.sort((a, b) => b.lastUsed - a.lastUsed);
|
|
3265
|
+
this.knowledge.successfulTechniques = this.knowledge.successfulTechniques.slice(0, MAX_SUCCESSFUL_TECHNIQUES);
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
this.save();
|
|
3269
|
+
}
|
|
3270
|
+
/**
|
|
3271
|
+
* Record a failure pattern to avoid.
|
|
3272
|
+
*/
|
|
3273
|
+
recordFailurePattern(pattern, reason) {
|
|
3274
|
+
const existing = this.knowledge.failurePatterns.find((f) => f.pattern === pattern);
|
|
3275
|
+
if (existing) {
|
|
3276
|
+
existing.avoidCount++;
|
|
3277
|
+
} else {
|
|
3278
|
+
this.knowledge.failurePatterns.push({ pattern, reason, avoidCount: 1 });
|
|
3279
|
+
if (this.knowledge.failurePatterns.length > MAX_FAILURE_PATTERNS) {
|
|
3280
|
+
this.knowledge.failurePatterns.sort((a, b) => b.avoidCount - a.avoidCount);
|
|
3281
|
+
this.knowledge.failurePatterns = this.knowledge.failurePatterns.slice(0, MAX_FAILURE_PATTERNS);
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
this.save();
|
|
3285
|
+
}
|
|
3286
|
+
/**
|
|
3287
|
+
* Learn a fact about a technology.
|
|
3288
|
+
*/
|
|
3289
|
+
learnFact(technology, fact) {
|
|
3290
|
+
if (!this.knowledge.techFacts.some((f) => f.technology === technology && f.fact === fact)) {
|
|
3291
|
+
this.knowledge.techFacts.push({ technology, fact, learnedAt: Date.now() });
|
|
3292
|
+
if (this.knowledge.techFacts.length > MAX_TECH_FACTS) {
|
|
3293
|
+
this.knowledge.techFacts.sort((a, b) => b.learnedAt - a.learnedAt);
|
|
3294
|
+
this.knowledge.techFacts = this.knowledge.techFacts.slice(0, MAX_TECH_FACTS);
|
|
3295
|
+
}
|
|
3296
|
+
this.save();
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
/**
|
|
3300
|
+
* Get known successful techniques for a service.
|
|
3301
|
+
*/
|
|
3302
|
+
getSuccessfulTechniques(service) {
|
|
3303
|
+
return this.knowledge.successfulTechniques.filter((t) => t.service.toLowerCase().includes(service.toLowerCase())).sort((a, b) => b.successCount - a.successCount);
|
|
3304
|
+
}
|
|
3305
|
+
/**
|
|
3306
|
+
* Clear all knowledge (for testing isolation).
|
|
3307
|
+
*/
|
|
3308
|
+
clear() {
|
|
3309
|
+
this.knowledge = { successfulTechniques: [], failurePatterns: [], techFacts: [], exploitChains: [] };
|
|
3310
|
+
this.save();
|
|
3311
|
+
clearSessionSnapshot();
|
|
3312
|
+
}
|
|
3313
|
+
/**
|
|
3314
|
+
* Record a successful exploit chain for session-level reuse (CurriculumPT §).
|
|
3315
|
+
*
|
|
3316
|
+
* WHY: 단일 기법 저장(recordSuccess)과 달리, 체인 전체를 저장한다.
|
|
3317
|
+
* 다음 세션에서 동일 서비스 스택 조우 시 체인 첫 단계부터 시도할 수 있다.
|
|
3318
|
+
*/
|
|
3319
|
+
recordExploitChain(serviceProfile, chainSummary, toolsUsed) {
|
|
3320
|
+
const existing = this.knowledge.exploitChains.find(
|
|
3321
|
+
(c) => c.serviceProfile === serviceProfile && c.chainSummary === chainSummary
|
|
3322
|
+
);
|
|
3323
|
+
if (existing) {
|
|
3324
|
+
existing.successCount++;
|
|
3325
|
+
existing.lastSucceededAt = Date.now();
|
|
3326
|
+
} else {
|
|
3327
|
+
this.knowledge.exploitChains.push({
|
|
3328
|
+
serviceProfile,
|
|
3329
|
+
chainSummary,
|
|
3330
|
+
toolsUsed,
|
|
3331
|
+
successCount: 1,
|
|
3332
|
+
lastSucceededAt: Date.now()
|
|
3333
|
+
});
|
|
3334
|
+
if (this.knowledge.exploitChains.length > MAX_EXPLOIT_CHAINS) {
|
|
3335
|
+
this.knowledge.exploitChains.sort((a, b) => b.successCount - a.successCount);
|
|
3336
|
+
this.knowledge.exploitChains = this.knowledge.exploitChains.slice(0, MAX_EXPLOIT_CHAINS);
|
|
3337
|
+
}
|
|
3338
|
+
}
|
|
3339
|
+
this.save();
|
|
3340
|
+
}
|
|
3341
|
+
/**
|
|
3342
|
+
* Format for prompt injection (most relevant persistent knowledge).
|
|
3343
|
+
*/
|
|
3344
|
+
toPrompt(services) {
|
|
3345
|
+
const relevant = [];
|
|
3346
|
+
for (const svc of services) {
|
|
3347
|
+
const techniques = this.getSuccessfulTechniques(svc);
|
|
3348
|
+
if (techniques.length > 0) {
|
|
3349
|
+
relevant.push(`${svc}: ${techniques.map((t) => `${t.technique} (${t.successCount}x)`).join(", ")}`);
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3352
|
+
const matchedChains = this.knowledge.exploitChains.filter(
|
|
3353
|
+
(chain) => matchesServiceProfile(chain.serviceProfile, services)
|
|
3354
|
+
).sort((a, b) => b.successCount - a.successCount).slice(0, MAX_PLAYBOOK_DISPLAY);
|
|
3355
|
+
if (relevant.length === 0 && matchedChains.length === 0) return "";
|
|
3356
|
+
const sections = ["<persistent-memory>"];
|
|
3357
|
+
if (relevant.length > 0) {
|
|
3358
|
+
sections.push(
|
|
3359
|
+
"PREVIOUSLY SUCCESSFUL TECHNIQUES:",
|
|
3360
|
+
...relevant.map((r) => ` \u{1F4DA} ${r}`)
|
|
3361
|
+
);
|
|
3362
|
+
}
|
|
3363
|
+
if (matchedChains.length > 0) {
|
|
3364
|
+
sections.push(
|
|
3365
|
+
"EXPLOIT CHAIN PLAYBOOKS (matched to current target):",
|
|
3366
|
+
...matchedChains.map(
|
|
3367
|
+
(c) => ` \u{1F517} [${c.serviceProfile}] ${c.chainSummary} (${c.successCount}x) tools:[${c.toolsUsed.join(",")}]`
|
|
3368
|
+
)
|
|
3369
|
+
);
|
|
3370
|
+
}
|
|
3371
|
+
sections.push("</persistent-memory>");
|
|
3372
|
+
return sections.join("\n");
|
|
3373
|
+
}
|
|
3374
|
+
load() {
|
|
3375
|
+
try {
|
|
3376
|
+
if (existsSync7(MEMORY_FILE)) {
|
|
3377
|
+
const data = JSON.parse(readFileSync7(MEMORY_FILE, "utf-8"));
|
|
3378
|
+
return {
|
|
3379
|
+
...data,
|
|
3380
|
+
exploitChains: data.exploitChains ?? []
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
3383
|
+
} catch {
|
|
3384
|
+
}
|
|
3385
|
+
return { successfulTechniques: [], failurePatterns: [], techFacts: [], exploitChains: [] };
|
|
3386
|
+
}
|
|
3387
|
+
save() {
|
|
3388
|
+
try {
|
|
3389
|
+
ensureDirExists(WORKSPACE.MEMORY);
|
|
3390
|
+
writeFileSync5(MEMORY_FILE, JSON.stringify(this.knowledge, null, 2));
|
|
3391
|
+
} catch {
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
};
|
|
3395
|
+
|
|
3396
|
+
// src/shared/utils/agent-memory/dynamic/extraction.ts
|
|
3397
|
+
var KNOWN_TECHS = [
|
|
3398
|
+
"apache",
|
|
3399
|
+
"nginx",
|
|
3400
|
+
"iis",
|
|
3401
|
+
"tomcat",
|
|
3402
|
+
"nodejs",
|
|
3403
|
+
"express",
|
|
3404
|
+
"flask",
|
|
3405
|
+
"django",
|
|
3406
|
+
"rails",
|
|
3407
|
+
"php",
|
|
3408
|
+
"wordpress",
|
|
3409
|
+
"joomla",
|
|
3410
|
+
"drupal",
|
|
3411
|
+
"mysql",
|
|
3412
|
+
"postgresql",
|
|
3413
|
+
"mssql",
|
|
3414
|
+
"mongodb",
|
|
3415
|
+
"redis",
|
|
3416
|
+
"memcached",
|
|
3417
|
+
"ssh",
|
|
3418
|
+
"ftp",
|
|
3419
|
+
"smb",
|
|
3420
|
+
"rdp",
|
|
3421
|
+
"ldap",
|
|
3422
|
+
"kerberos",
|
|
3423
|
+
"dns",
|
|
3424
|
+
"docker",
|
|
3425
|
+
"kubernetes",
|
|
3426
|
+
"jenkins",
|
|
3427
|
+
"gitlab",
|
|
3428
|
+
"grafana",
|
|
3429
|
+
"spring",
|
|
3430
|
+
"struts",
|
|
3431
|
+
"log4j",
|
|
3432
|
+
"jackson",
|
|
3433
|
+
"fastjson"
|
|
3434
|
+
];
|
|
3435
|
+
var TECHNIQUE_PATTERNS = [
|
|
3436
|
+
// "Step 1: ...", "1. Run ..."
|
|
3437
|
+
/(?:step\s*\d+|^\d+\.\s)[:.]?\s*(.{20,150})/gim,
|
|
3438
|
+
// "Exploit: ...", "Payload: ...", "Command: ..."
|
|
3439
|
+
/(?:exploit|payload|command|technique|bypass|trick):\s*(.{10,150})/gi,
|
|
3440
|
+
// Code blocks with commands
|
|
3441
|
+
/(?:```|`)((?:curl|wget|python|nmap|sqlmap|nikto|gobuster|ffuf)\s+.{10,100})(?:```|`)/g
|
|
3442
|
+
];
|
|
3443
|
+
function extractTechnologies(query) {
|
|
3444
|
+
const techs = [];
|
|
3445
|
+
const lower = query.toLowerCase();
|
|
3446
|
+
for (const tech of KNOWN_TECHS) {
|
|
3447
|
+
if (lower.includes(tech)) techs.push(tech);
|
|
3448
|
+
}
|
|
3449
|
+
const versionMatch = query.match(/(\d+\.\d+(?:\.\d+)?)/);
|
|
3450
|
+
if (versionMatch && techs.length > 0) {
|
|
3451
|
+
techs[0] = `${techs[0]}/${versionMatch[1]}`;
|
|
3452
|
+
}
|
|
3453
|
+
return techs.length > 0 ? techs : ["general"];
|
|
3454
|
+
}
|
|
3455
|
+
function extractTechniquesFromText(resultText) {
|
|
3456
|
+
if (!resultText || resultText.length < 50) return [];
|
|
3457
|
+
const extracted = [];
|
|
3458
|
+
for (const pattern of TECHNIQUE_PATTERNS) {
|
|
3459
|
+
pattern.lastIndex = 0;
|
|
3460
|
+
let match;
|
|
3461
|
+
while ((match = pattern.exec(resultText)) !== null && extracted.length < 5) {
|
|
3462
|
+
const technique = (match[1] || match[0]).trim();
|
|
3463
|
+
if (technique.length > 10 && technique.length < 200) {
|
|
3464
|
+
extracted.push(technique);
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
return extracted;
|
|
3469
|
+
}
|
|
3470
|
+
|
|
3471
|
+
// src/shared/utils/agent-memory/dynamic/formatter.ts
|
|
3472
|
+
function formatTechniquesForPrompt(techniques) {
|
|
3473
|
+
if (techniques.length === 0) return "";
|
|
3474
|
+
const confirmed = techniques.filter((t) => t.confidence >= CONFIDENCE_THRESHOLDS.CONFIRMED);
|
|
3475
|
+
const discovered = techniques.filter((t) => t.confidence < CONFIDENCE_THRESHOLDS.CONFIRMED);
|
|
3476
|
+
const lines = ["<learned-techniques>"];
|
|
3477
|
+
if (confirmed.length > 0) {
|
|
3478
|
+
lines.push("CONFIRMED (worked in this session):");
|
|
3479
|
+
for (const t of confirmed) {
|
|
3480
|
+
lines.push(` \u2705 [${t.applicableTo.join(",")}] ${t.technique}`);
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
if (discovered.length > 0) {
|
|
3484
|
+
lines.push(`DISCOVERED (${discovered.length} unverified):`);
|
|
3485
|
+
for (const t of discovered.slice(0, MEMORY_LIMITS.PROMPT_UNVERIFIED_TECHNIQUES)) {
|
|
3486
|
+
lines.push(` \u{1F4A1} [${t.applicableTo.join(",")}] ${t.technique} (from: ${t.source})`);
|
|
3487
|
+
}
|
|
3488
|
+
}
|
|
3489
|
+
lines.push("</learned-techniques>");
|
|
3490
|
+
return lines.join("\n");
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
// src/shared/utils/agent-memory/dynamic/engine.ts
|
|
3494
|
+
var DynamicTechniqueLibrary = class {
|
|
3495
|
+
techniques = [];
|
|
3496
|
+
maxTechniques = MEMORY_LIMITS.DYNAMIC_TECHNIQUES_MAX;
|
|
3497
|
+
/**
|
|
3498
|
+
* Learn a new technique from a search result or successful exploit.
|
|
3499
|
+
*/
|
|
3500
|
+
learn(technique) {
|
|
3501
|
+
const exists = this.techniques.some(
|
|
3502
|
+
(t) => t.technique.toLowerCase() === technique.technique.toLowerCase()
|
|
3503
|
+
);
|
|
3504
|
+
if (exists) return;
|
|
3505
|
+
this.techniques.push({
|
|
3506
|
+
...technique,
|
|
3507
|
+
learnedAt: Date.now()
|
|
3508
|
+
});
|
|
3509
|
+
if (this.techniques.length > this.maxTechniques) {
|
|
3510
|
+
this.techniques.sort((a, b) => {
|
|
3511
|
+
if (a.confidence !== b.confidence) return b.confidence - a.confidence;
|
|
3512
|
+
return b.learnedAt - a.learnedAt;
|
|
3513
|
+
});
|
|
3514
|
+
this.techniques = this.techniques.slice(0, this.maxTechniques);
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
/**
|
|
3518
|
+
* Extract and learn techniques from a web search result.
|
|
3519
|
+
*/
|
|
3520
|
+
learnFromSearchResult(query, resultText) {
|
|
3521
|
+
const extracted = extractTechniquesFromText(resultText);
|
|
3522
|
+
if (extracted.length === 0) return;
|
|
3523
|
+
const applicableTo = extractTechnologies(query);
|
|
3524
|
+
for (const tech of extracted) {
|
|
3525
|
+
this.learn({
|
|
3526
|
+
source: `Web search: "${query}"`,
|
|
3527
|
+
technique: tech,
|
|
3528
|
+
applicableTo,
|
|
3529
|
+
confidence: CONFIDENCE_THRESHOLDS.POSSIBLE,
|
|
3530
|
+
fromQuery: query
|
|
3531
|
+
});
|
|
3532
|
+
}
|
|
3533
|
+
}
|
|
3534
|
+
/**
|
|
3535
|
+
* Mark a technique as verified (it worked in practice).
|
|
3536
|
+
*/
|
|
3537
|
+
verify(techniqueSubstring) {
|
|
3538
|
+
for (const t of this.techniques) {
|
|
3539
|
+
if (t.technique.toLowerCase().includes(techniqueSubstring.toLowerCase())) {
|
|
3540
|
+
t.confidence = CONFIDENCE_THRESHOLDS.CONFIRMED;
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
/**
|
|
3545
|
+
* Record that a technique failed in practice.
|
|
3546
|
+
*/
|
|
3547
|
+
recordFailure(techniqueSubstring) {
|
|
3548
|
+
if (!techniqueSubstring || techniqueSubstring.length < 3) return;
|
|
3549
|
+
const lower = techniqueSubstring.toLowerCase();
|
|
3550
|
+
this.techniques = this.techniques.filter((t) => {
|
|
3551
|
+
if (t.technique.toLowerCase().includes(lower)) {
|
|
3552
|
+
t.confidence -= MEMORY_LIMITS.TECHNIQUE_FAILURE_DECAY;
|
|
3553
|
+
return t.confidence > MEMORY_LIMITS.TECHNIQUE_PRUNE_THRESHOLD;
|
|
3554
|
+
}
|
|
3555
|
+
return true;
|
|
3556
|
+
});
|
|
3557
|
+
}
|
|
3558
|
+
/**
|
|
3559
|
+
* Get techniques relevant to a specific service/technology.
|
|
3560
|
+
*/
|
|
3561
|
+
getRelevant(service) {
|
|
3562
|
+
const lower = service.toLowerCase();
|
|
3563
|
+
return this.techniques.filter(
|
|
3564
|
+
(t) => t.applicableTo.some((a) => a.toLowerCase().includes(lower) || lower.includes(a.toLowerCase()))
|
|
3565
|
+
);
|
|
3566
|
+
}
|
|
3567
|
+
/**
|
|
3568
|
+
* Get all learned techniques.
|
|
3569
|
+
*/
|
|
3570
|
+
getAll() {
|
|
3571
|
+
return [...this.techniques];
|
|
3572
|
+
}
|
|
3573
|
+
/** Clear all learned techniques. */
|
|
3574
|
+
clear() {
|
|
3575
|
+
this.techniques = [];
|
|
3576
|
+
}
|
|
3577
|
+
/**
|
|
3578
|
+
* Format for prompt injection.
|
|
3579
|
+
*/
|
|
3580
|
+
toPrompt() {
|
|
3581
|
+
return formatTechniquesForPrompt(this.techniques);
|
|
3582
|
+
}
|
|
3583
|
+
};
|
|
3584
|
+
|
|
2833
3585
|
// src/engine/state/persistence/serializer.ts
|
|
2834
3586
|
var UNVERIFIED_CHAIN_DEPTH_THRESHOLD = 2;
|
|
2835
3587
|
var ACTIONABLE_LOOT_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -2857,7 +3609,7 @@ var StateSerializer = class {
|
|
|
2857
3609
|
this.formatLoot(state3, lines);
|
|
2858
3610
|
this.formatTodos(state3, lines);
|
|
2859
3611
|
this.formatEnvironment(state3, lines);
|
|
2860
|
-
const snapshot =
|
|
3612
|
+
const snapshot = snapshotToPrompt();
|
|
2861
3613
|
if (snapshot) lines.push(snapshot);
|
|
2862
3614
|
lines.push(`Phase: ${state3.getPhase()}`);
|
|
2863
3615
|
return lines.join("\n");
|
|
@@ -2993,8 +3745,8 @@ BLOCKED (leak real IP): ping, traceroute, dig, nslookup, nmap -sU`
|
|
|
2993
3745
|
};
|
|
2994
3746
|
|
|
2995
3747
|
// src/engine/state/persistence/saver.ts
|
|
2996
|
-
import { writeFileSync as
|
|
2997
|
-
import { join as
|
|
3748
|
+
import { writeFileSync as writeFileSync6, readdirSync, statSync as statSync2, unlinkSync as unlinkSync5 } from "fs";
|
|
3749
|
+
import { join as join5 } from "path";
|
|
2998
3750
|
function saveState(state3) {
|
|
2999
3751
|
const sessionsDir = WORKSPACE.SESSIONS;
|
|
3000
3752
|
ensureDirExists(sessionsDir);
|
|
@@ -3011,18 +3763,18 @@ function saveState(state3) {
|
|
|
3011
3763
|
missionSummary: state3.getMissionSummary(),
|
|
3012
3764
|
missionChecklist: state3.getMissionChecklist()
|
|
3013
3765
|
};
|
|
3014
|
-
const sessionFile =
|
|
3766
|
+
const sessionFile = join5(sessionsDir, FILE_PATTERNS.session());
|
|
3015
3767
|
const json = JSON.stringify(snapshot, null, 2);
|
|
3016
|
-
|
|
3017
|
-
const latestFile =
|
|
3018
|
-
|
|
3768
|
+
writeFileSync6(sessionFile, json, "utf-8");
|
|
3769
|
+
const latestFile = join5(sessionsDir, "latest.json");
|
|
3770
|
+
writeFileSync6(latestFile, json, "utf-8");
|
|
3019
3771
|
pruneOldSessions(sessionsDir);
|
|
3020
3772
|
return sessionFile;
|
|
3021
3773
|
}
|
|
3022
3774
|
function pruneOldSessions(sessionsDir) {
|
|
3023
3775
|
try {
|
|
3024
3776
|
const sessionFiles = readdirSync(sessionsDir).filter((f) => f.endsWith(FILE_EXTENSIONS.JSON) && f !== SPECIAL_FILES.LATEST_STATE).map((f) => {
|
|
3025
|
-
const filePath =
|
|
3777
|
+
const filePath = join5(sessionsDir, f);
|
|
3026
3778
|
return {
|
|
3027
3779
|
name: f,
|
|
3028
3780
|
path: filePath,
|
|
@@ -3031,22 +3783,22 @@ function pruneOldSessions(sessionsDir) {
|
|
|
3031
3783
|
}).sort((a, b) => b.mtime - a.mtime);
|
|
3032
3784
|
const toDelete = sessionFiles.slice(AGENT_LIMITS.MAX_SESSION_FILES);
|
|
3033
3785
|
for (const file of toDelete) {
|
|
3034
|
-
|
|
3786
|
+
unlinkSync5(file.path);
|
|
3035
3787
|
}
|
|
3036
3788
|
} catch {
|
|
3037
3789
|
}
|
|
3038
3790
|
}
|
|
3039
3791
|
|
|
3040
3792
|
// src/engine/state/persistence/loader.ts
|
|
3041
|
-
import { readFileSync as
|
|
3042
|
-
import { join as
|
|
3793
|
+
import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
|
|
3794
|
+
import { join as join6 } from "path";
|
|
3043
3795
|
function loadState(state3) {
|
|
3044
|
-
const latestFile =
|
|
3045
|
-
if (!
|
|
3796
|
+
const latestFile = join6(WORKSPACE.SESSIONS, "latest.json");
|
|
3797
|
+
if (!existsSync8(latestFile)) {
|
|
3046
3798
|
return false;
|
|
3047
3799
|
}
|
|
3048
3800
|
try {
|
|
3049
|
-
const raw =
|
|
3801
|
+
const raw = readFileSync8(latestFile, "utf-8");
|
|
3050
3802
|
const snapshot = JSON.parse(raw);
|
|
3051
3803
|
if (snapshot.version !== 1) {
|
|
3052
3804
|
debugLog("general", `Unknown snapshot version: ${snapshot.version}`);
|
|
@@ -3088,7 +3840,7 @@ function loadState(state3) {
|
|
|
3088
3840
|
}
|
|
3089
3841
|
|
|
3090
3842
|
// src/engine/state/persistence/janitor.ts
|
|
3091
|
-
import { existsSync as
|
|
3843
|
+
import { existsSync as existsSync9, rmSync } from "fs";
|
|
3092
3844
|
import path2 from "path";
|
|
3093
3845
|
function clearWorkspace() {
|
|
3094
3846
|
const cleared = [];
|
|
@@ -3106,7 +3858,7 @@ function clearWorkspace() {
|
|
|
3106
3858
|
];
|
|
3107
3859
|
for (const dir2 of dirsToClean) {
|
|
3108
3860
|
try {
|
|
3109
|
-
if (
|
|
3861
|
+
if (existsSync9(dir2.path)) {
|
|
3110
3862
|
rmSync(dir2.path, { recursive: true, force: true });
|
|
3111
3863
|
ensureDirExists(dir2.path);
|
|
3112
3864
|
cleared.push(dir2.label);
|
|
@@ -3121,28 +3873,14 @@ function clearWorkspace() {
|
|
|
3121
3873
|
export {
|
|
3122
3874
|
ensureDirExists,
|
|
3123
3875
|
FILE_EXTENSIONS,
|
|
3124
|
-
SPECIAL_FILES,
|
|
3125
3876
|
FILE_PATTERNS,
|
|
3126
3877
|
WORK_DIR,
|
|
3127
3878
|
WORKSPACE,
|
|
3128
3879
|
initDebugLogger,
|
|
3129
3880
|
debugLog,
|
|
3130
3881
|
flowLog,
|
|
3131
|
-
DEFAULT_MODEL,
|
|
3132
|
-
getApiKey,
|
|
3133
|
-
getBaseUrl,
|
|
3134
|
-
getModel,
|
|
3135
|
-
getSearchApiKey,
|
|
3136
|
-
getSearchApiUrl,
|
|
3137
|
-
isZaiProvider,
|
|
3138
|
-
isThinkingEnabled,
|
|
3139
|
-
getThinkingBudget,
|
|
3140
|
-
getScopeMode,
|
|
3141
|
-
getApprovalMode,
|
|
3142
|
-
isTorEnabled,
|
|
3143
3882
|
setTorEnabled,
|
|
3144
3883
|
getTorBrowserArgs,
|
|
3145
|
-
validateRequiredConfig,
|
|
3146
3884
|
MS_PER_MINUTE,
|
|
3147
3885
|
DISPLAY_LIMITS,
|
|
3148
3886
|
RETRY_CONFIG,
|
|
@@ -3184,7 +3922,12 @@ export {
|
|
|
3184
3922
|
COMMAND_EVENT_TYPES,
|
|
3185
3923
|
UI_COMMANDS,
|
|
3186
3924
|
generateId,
|
|
3187
|
-
|
|
3925
|
+
WorkingMemory,
|
|
3926
|
+
EpisodicMemory,
|
|
3927
|
+
saveSessionSnapshot,
|
|
3928
|
+
snapshotToPrompt,
|
|
3929
|
+
PersistentMemory,
|
|
3930
|
+
DynamicTechniqueLibrary,
|
|
3188
3931
|
createSessionRuntime,
|
|
3189
3932
|
setActiveSessionRuntime,
|
|
3190
3933
|
clearActiveSessionRuntime,
|