pentesting 0.70.2 → 0.70.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +119 -93
  2. package/package.json +3 -3
package/dist/main.js CHANGED
@@ -120,7 +120,7 @@ var ICONS = {
120
120
  };
121
121
 
122
122
  // src/shared/utils/debug/debug-logger.ts
123
- import { appendFileSync, writeFileSync } from "fs";
123
+ import { appendFileSync, writeFileSync, statSync, readFileSync } from "fs";
124
124
  import { join } from "path";
125
125
 
126
126
  // src/shared/utils/file-ops/file-utils.ts
@@ -275,10 +275,14 @@ var FILE_PATTERNS = {
275
275
  };
276
276
 
277
277
  // src/shared/utils/debug/debug-logger.ts
278
+ var ROTATE_BYTES = 5 * 1024 * 1024;
279
+ var WIPE_BYTES = 20 * 1024 * 1024;
280
+ var ROTATE_CHECK_INTERVAL = 500;
278
281
  var DebugLogger = class _DebugLogger {
279
282
  static instance;
280
283
  logPath;
281
284
  initialized = false;
285
+ writeCount = 0;
282
286
  constructor(clearOnInit = false) {
283
287
  const debugDir = WORKSPACE.DEBUG;
284
288
  try {
@@ -306,6 +310,31 @@ var DebugLogger = class _DebugLogger {
306
310
  _DebugLogger.instance = new _DebugLogger(clearOnInit);
307
311
  return _DebugLogger.instance;
308
312
  }
313
+ /**
314
+ * Rotate or wipe debug.log if it exceeds size thresholds.
315
+ * Called every ROTATE_CHECK_INTERVAL writes to amortize statSync cost.
316
+ *
317
+ * Policy:
318
+ * > 20 MB → wipe entirely (too much data, disk pressure)
319
+ * > 5 MB → keep latest half (recent logs more useful than old ones)
320
+ */
321
+ rotateIfNeeded() {
322
+ try {
323
+ const size = statSync(this.logPath).size;
324
+ if (size > WIPE_BYTES) {
325
+ writeFileSync(this.logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] [GENERAL] === LOG WIPED (exceeded ${Math.round(WIPE_BYTES / 1024 / 1024)}MB) ===
326
+ `);
327
+ } else if (size > ROTATE_BYTES) {
328
+ const content = readFileSync(this.logPath, "utf-8");
329
+ const half = content.slice(Math.floor(content.length / 2));
330
+ const firstNewline = half.indexOf("\n");
331
+ const trimmed = firstNewline >= 0 ? half.slice(firstNewline + 1) : half;
332
+ writeFileSync(this.logPath, `[${(/* @__PURE__ */ new Date()).toISOString()}] [GENERAL] === LOG ROTATED (exceeded ${Math.round(ROTATE_BYTES / 1024 / 1024)}MB, kept latest half) ===
333
+ ` + trimmed);
334
+ }
335
+ } catch {
336
+ }
337
+ }
309
338
  log(category, message, data) {
310
339
  if (!this.initialized || !this.logPath) return;
311
340
  try {
@@ -316,6 +345,9 @@ var DebugLogger = class _DebugLogger {
316
345
  }
317
346
  logLine += "\n";
318
347
  appendFileSync(this.logPath, logLine);
348
+ if (++this.writeCount % ROTATE_CHECK_INTERVAL === 0) {
349
+ this.rotateIfNeeded();
350
+ }
319
351
  } catch (e) {
320
352
  console.error("[DebugLogger] Write error:", e);
321
353
  }
@@ -329,6 +361,9 @@ ${raw}
329
361
  ---
330
362
  `;
331
363
  appendFileSync(this.logPath, logLine);
364
+ if (++this.writeCount % ROTATE_CHECK_INTERVAL === 0) {
365
+ this.rotateIfNeeded();
366
+ }
332
367
  } catch (e) {
333
368
  console.error("[DebugLogger] Write error:", e);
334
369
  }
@@ -727,7 +762,7 @@ var INPUT_PROMPT_PATTERNS = [
727
762
 
728
763
  // src/shared/constants/agent.ts
729
764
  var APP_NAME = "Pentest AI";
730
- var APP_VERSION = "0.70.2";
765
+ var APP_VERSION = "0.70.4";
731
766
  var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
732
767
  var LLM_ROLES = {
733
768
  SYSTEM: "system",
@@ -782,7 +817,7 @@ import { render } from "ink";
782
817
  import chalk from "chalk";
783
818
 
784
819
  // src/platform/tui/app.tsx
785
- import { useState as useState9, useCallback as useCallback11, useRef as useRef10 } from "react";
820
+ import { useState as useState8, useCallback as useCallback11, useRef as useRef10 } from "react";
786
821
  import { Box as Box19, useApp, useStdout as useStdout4 } from "ink";
787
822
 
788
823
  // src/platform/tui/hooks/useAgent.ts
@@ -2458,7 +2493,7 @@ var EpisodicMemory = class {
2458
2493
  };
2459
2494
 
2460
2495
  // src/shared/utils/agent-memory/persistent-memory.ts
2461
- import { existsSync as existsSync2, readFileSync, writeFileSync as writeFileSync2, unlinkSync } from "fs";
2496
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync } from "fs";
2462
2497
  import { join as join2 } from "path";
2463
2498
 
2464
2499
  // src/shared/utils/agent-memory/similarity.ts
@@ -2634,7 +2669,7 @@ var PersistentMemory = class {
2634
2669
  loadSessionSnapshot() {
2635
2670
  try {
2636
2671
  if (existsSync2(SNAPSHOT_FILE)) {
2637
- return JSON.parse(readFileSync(SNAPSHOT_FILE, "utf-8"));
2672
+ return JSON.parse(readFileSync2(SNAPSHOT_FILE, "utf-8"));
2638
2673
  }
2639
2674
  } catch {
2640
2675
  }
@@ -2670,7 +2705,7 @@ var PersistentMemory = class {
2670
2705
  load() {
2671
2706
  try {
2672
2707
  if (existsSync2(MEMORY_FILE)) {
2673
- const data = JSON.parse(readFileSync(MEMORY_FILE, "utf-8"));
2708
+ const data = JSON.parse(readFileSync2(MEMORY_FILE, "utf-8"));
2674
2709
  return {
2675
2710
  ...data,
2676
2711
  exploitChains: data.exploitChains ?? []
@@ -4555,7 +4590,7 @@ Suggestion: ${torLeak.suggestion}`
4555
4590
  }
4556
4591
 
4557
4592
  // src/engine/tools-base/file-operations.ts
4558
- import { readFileSync as readFileSync2, existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
4593
+ import { readFileSync as readFileSync3, existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
4559
4594
  import { dirname } from "path";
4560
4595
  import { join as join3 } from "path";
4561
4596
  import { tmpdir } from "os";
@@ -4575,7 +4610,7 @@ async function readFileContent(filePath) {
4575
4610
  error: `File not found: ${filePath}`
4576
4611
  };
4577
4612
  }
4578
- const content = readFileSync2(filePath, "utf-8");
4613
+ const content = readFileSync3(filePath, "utf-8");
4579
4614
  return {
4580
4615
  success: true,
4581
4616
  output: content
@@ -4679,7 +4714,7 @@ function startBackgroundProcess(command, options = {}) {
4679
4714
  }
4680
4715
 
4681
4716
  // src/engine/process/process-interaction.ts
4682
- import { existsSync as existsSync4, readFileSync as readFileSync3, appendFileSync as appendFileSync2 } from "fs";
4717
+ import { existsSync as existsSync4, readFileSync as readFileSync4, appendFileSync as appendFileSync2 } from "fs";
4683
4718
  async function sendToProcess(processId, input, waitMs = SYSTEM_LIMITS.DEFAULT_WAIT_MS_INTERACT) {
4684
4719
  const proc = getProcess(processId);
4685
4720
  if (!proc) return { success: false, output: `Process ${processId} not found`, newOutput: "" };
@@ -4688,7 +4723,7 @@ async function sendToProcess(processId, input, waitMs = SYSTEM_LIMITS.DEFAULT_WA
4688
4723
  let currentLen = 0;
4689
4724
  try {
4690
4725
  if (existsSync4(proc.stdoutFile)) {
4691
- currentLen = readFileSync3(proc.stdoutFile, "utf-8").length;
4726
+ currentLen = readFileSync4(proc.stdoutFile, "utf-8").length;
4692
4727
  }
4693
4728
  } catch {
4694
4729
  }
@@ -4702,7 +4737,7 @@ async function sendToProcess(processId, input, waitMs = SYSTEM_LIMITS.DEFAULT_WA
4702
4737
  let fullStdout = "";
4703
4738
  try {
4704
4739
  if (existsSync4(proc.stdoutFile)) {
4705
- fullStdout = readFileSync3(proc.stdoutFile, "utf-8");
4740
+ fullStdout = readFileSync4(proc.stdoutFile, "utf-8");
4706
4741
  }
4707
4742
  } catch {
4708
4743
  }
@@ -4724,7 +4759,7 @@ function promoteToShell(processId, description) {
4724
4759
  }
4725
4760
 
4726
4761
  // src/engine/process/process-monitor.ts
4727
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
4762
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
4728
4763
  function isProcessRunning(processId) {
4729
4764
  const proc = getProcess(processId);
4730
4765
  if (!proc) return false;
@@ -4738,7 +4773,7 @@ function isProcessRunning(processId) {
4738
4773
  if (proc.role === PROCESS_ROLES.LISTENER) {
4739
4774
  try {
4740
4775
  if (existsSync5(proc.stdoutFile)) {
4741
- const stdout = readFileSync4(proc.stdoutFile, "utf-8");
4776
+ const stdout = readFileSync5(proc.stdoutFile, "utf-8");
4742
4777
  if (detectConnection(stdout)) {
4743
4778
  promoteToShell(processId, "Reverse shell connected (auto-detected)");
4744
4779
  logEvent(processId, PROCESS_EVENTS.CONNECTION_DETECTED, `Connection detected on port ${proc.listeningPort}`);
@@ -4757,7 +4792,7 @@ function getProcessOutput(processId) {
4757
4792
  let stderr = "";
4758
4793
  try {
4759
4794
  if (existsSync5(proc.stdoutFile)) {
4760
- const content = readFileSync4(proc.stdoutFile, "utf-8");
4795
+ const content = readFileSync5(proc.stdoutFile, "utf-8");
4761
4796
  stdout = content.length > SYSTEM_LIMITS.MAX_STDOUT_SLICE ? `... [truncated ${content.length - SYSTEM_LIMITS.MAX_STDOUT_SLICE} chars] ...
4762
4797
  ` + content.slice(-SYSTEM_LIMITS.MAX_STDOUT_SLICE) : content;
4763
4798
  }
@@ -4765,7 +4800,7 @@ function getProcessOutput(processId) {
4765
4800
  }
4766
4801
  try {
4767
4802
  if (existsSync5(proc.stderrFile)) {
4768
- const content = readFileSync4(proc.stderrFile, "utf-8");
4803
+ const content = readFileSync5(proc.stderrFile, "utf-8");
4769
4804
  stderr = content.length > SYSTEM_LIMITS.MAX_STDERR_SLICE ? `... [truncated ${content.length - SYSTEM_LIMITS.MAX_STDERR_SLICE} chars] ...
4770
4805
  ` + content.slice(-SYSTEM_LIMITS.MAX_STDERR_SLICE) : content;
4771
4806
  }
@@ -4938,7 +4973,7 @@ async function cleanupAllProcesses() {
4938
4973
  }
4939
4974
 
4940
4975
  // src/engine/process/resource-summary.ts
4941
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
4976
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
4942
4977
  function getResourceSummary() {
4943
4978
  const procs = listBackgroundProcesses();
4944
4979
  const running = procs.filter((p) => p.isRunning);
@@ -4958,7 +4993,7 @@ function getResourceSummary() {
4958
4993
  let lastOutput = "";
4959
4994
  try {
4960
4995
  if (p.stdoutFile && existsSync6(p.stdoutFile)) {
4961
- const content = readFileSync5(p.stdoutFile, "utf-8");
4996
+ const content = readFileSync6(p.stdoutFile, "utf-8");
4962
4997
  const outputLines = content.trim().split("\n");
4963
4998
  lastOutput = outputLines.slice(-SYSTEM_LIMITS.RECENT_OUTPUT_LINES).join(" | ").replace(/\n/g, " ");
4964
4999
  }
@@ -5203,7 +5238,7 @@ BLOCKED (leak real IP): ping, traceroute, dig, nslookup, nmap -sU`
5203
5238
  };
5204
5239
 
5205
5240
  // src/engine/state/persistence/saver.ts
5206
- import { writeFileSync as writeFileSync5, readdirSync, statSync, unlinkSync as unlinkSync5 } from "fs";
5241
+ import { writeFileSync as writeFileSync5, readdirSync, statSync as statSync2, unlinkSync as unlinkSync5 } from "fs";
5207
5242
  import { join as join4 } from "path";
5208
5243
  function saveState(state) {
5209
5244
  const sessionsDir = WORKSPACE.SESSIONS;
@@ -5236,7 +5271,7 @@ function pruneOldSessions(sessionsDir) {
5236
5271
  return {
5237
5272
  name: f,
5238
5273
  path: filePath,
5239
- mtime: statSync(filePath).mtimeMs
5274
+ mtime: statSync2(filePath).mtimeMs
5240
5275
  };
5241
5276
  }).sort((a, b) => b.mtime - a.mtime);
5242
5277
  const toDelete = sessionFiles.slice(AGENT_LIMITS.MAX_SESSION_FILES);
@@ -5248,7 +5283,7 @@ function pruneOldSessions(sessionsDir) {
5248
5283
  }
5249
5284
 
5250
5285
  // src/engine/state/persistence/loader.ts
5251
- import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
5286
+ import { readFileSync as readFileSync7, existsSync as existsSync7 } from "fs";
5252
5287
  import { join as join5 } from "path";
5253
5288
  function loadState(state) {
5254
5289
  const latestFile = join5(WORKSPACE.SESSIONS, "latest.json");
@@ -5256,7 +5291,7 @@ function loadState(state) {
5256
5291
  return false;
5257
5292
  }
5258
5293
  try {
5259
- const raw = readFileSync6(latestFile, "utf-8");
5294
+ const raw = readFileSync7(latestFile, "utf-8");
5260
5295
  const snapshot = JSON.parse(raw);
5261
5296
  if (snapshot.version !== 1) {
5262
5297
  debugLog("general", `Unknown snapshot version: ${snapshot.version}`);
@@ -10357,7 +10392,7 @@ function mutatePayload(request) {
10357
10392
  }
10358
10393
 
10359
10394
  // src/domains/exploit/tools.ts
10360
- import { existsSync as existsSync10, statSync as statSync2, readdirSync as readdirSync2 } from "fs";
10395
+ import { existsSync as existsSync10, statSync as statSync3, readdirSync as readdirSync2 } from "fs";
10361
10396
  import { join as join10 } from "path";
10362
10397
  var hashCrackTool = {
10363
10398
  name: TOOL_NAMES.HASH_CRACK,
@@ -10516,7 +10551,7 @@ Returns: All available wordlists with their paths, sizes, and categories.`,
10516
10551
  const processFile = (fullPath, fileName) => {
10517
10552
  const ext = fileName.split(".").pop()?.toLowerCase();
10518
10553
  if (!WORDLIST_EXTENSIONS.has(ext || "")) return;
10519
- const stats = statSync2(fullPath);
10554
+ const stats = statSync3(fullPath);
10520
10555
  if (stats.size < minSize) return;
10521
10556
  if (!matchesCategory(fullPath)) return;
10522
10557
  if (!matchesSearch(fullPath, fileName)) return;
@@ -11748,19 +11783,19 @@ var LLMClient = class {
11748
11783
  debugLog("llm", `[${requestId}] Stream request START`, { model: this.model, toolCount: tools?.length, toolNames: tools?.map((t) => t.name), thinking: !!thinking });
11749
11784
  const response = await makeRequest(this.baseUrl, this.apiKey, requestBody, callbacks?.abortSignal);
11750
11785
  const { context } = createStreamContext(callbacks);
11751
- let fullContent = "";
11752
- let fullReasoning = "";
11786
+ const contentChunks = [];
11787
+ const reasoningChunks = [];
11753
11788
  let usage = { input_tokens: 0, output_tokens: 0 };
11754
11789
  const currentBlockRef = { value: null };
11755
11790
  const toolCallsMap = /* @__PURE__ */ new Map();
11756
11791
  let totalChars = 0;
11757
11792
  let wasAborted = false;
11758
11793
  context.onContent = (text) => {
11759
- fullContent += text;
11794
+ contentChunks.push(text);
11760
11795
  totalChars += text.length;
11761
11796
  };
11762
11797
  context.onReasoning = (text) => {
11763
- fullReasoning += text;
11798
+ reasoningChunks.push(text);
11764
11799
  totalChars += text.length;
11765
11800
  };
11766
11801
  context.onUsage = (u) => {
@@ -11771,7 +11806,7 @@ var LLMClient = class {
11771
11806
  context.toolCallsMap = toolCallsMap;
11772
11807
  wasAborted = await readSSEStream(response, requestId, context, callbacks?.abortSignal);
11773
11808
  const toolCalls = resolveToolCalls(toolCallsMap);
11774
- const stripped = stripThinkTags(fullContent, fullReasoning);
11809
+ const stripped = stripThinkTags(contentChunks.join(""), reasoningChunks.join(""));
11775
11810
  return {
11776
11811
  content: stripped.cleanText,
11777
11812
  toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
@@ -12261,7 +12296,7 @@ function handleToolResult(result, call, outputText, progress) {
12261
12296
  }
12262
12297
 
12263
12298
  // src/shared/utils/context-digest/constants.ts
12264
- var PASSTHROUGH_THRESHOLD = 500;
12299
+ var PASSTHROUGH_THRESHOLD = 2e3;
12265
12300
  var PREPROCESS_THRESHOLD = 3e3;
12266
12301
  var MAX_PREPROCESSED_LINES = 800;
12267
12302
  var MAX_DUPLICATE_DISPLAY = 3;
@@ -13135,7 +13170,7 @@ var PHASE_TECHNIQUE_MAP = {
13135
13170
  };
13136
13171
 
13137
13172
  // src/agents/prompt-builder/prompt-loader.ts
13138
- import { readFileSync as readFileSync7, existsSync as existsSync11 } from "fs";
13173
+ import { readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
13139
13174
  import { join as join12, dirname as dirname4 } from "path";
13140
13175
  import { fileURLToPath as fileURLToPath2 } from "url";
13141
13176
  var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
@@ -13143,13 +13178,13 @@ var PROMPTS_DIR = join12(__dirname2, "../prompts");
13143
13178
  var TECHNIQUES_DIR = join12(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
13144
13179
  function loadPromptFile(filename) {
13145
13180
  const path2 = join12(PROMPTS_DIR, filename);
13146
- return existsSync11(path2) ? readFileSync7(path2, PROMPT_CONFIG.ENCODING) : "";
13181
+ return existsSync11(path2) ? readFileSync8(path2, PROMPT_CONFIG.ENCODING) : "";
13147
13182
  }
13148
13183
  function loadTechniqueFile(techniqueName) {
13149
13184
  const filePath = join12(TECHNIQUES_DIR, `${techniqueName}.md`);
13150
13185
  try {
13151
13186
  if (!existsSync11(filePath)) return "";
13152
- return readFileSync7(filePath, PROMPT_CONFIG.ENCODING);
13187
+ return readFileSync8(filePath, PROMPT_CONFIG.ENCODING);
13153
13188
  } catch {
13154
13189
  return "";
13155
13190
  }
@@ -13294,11 +13329,11 @@ ${lines.join("\n")}
13294
13329
  }
13295
13330
 
13296
13331
  // src/shared/utils/journal/reader.ts
13297
- import { readFileSync as readFileSync8, existsSync as existsSync13 } from "fs";
13332
+ import { readFileSync as readFileSync9, existsSync as existsSync13 } from "fs";
13298
13333
  import { join as join14 } from "path";
13299
13334
 
13300
13335
  // src/shared/utils/journal/rotation.ts
13301
- import { existsSync as existsSync12, readdirSync as readdirSync3, statSync as statSync3, rmSync as rmSync2 } from "fs";
13336
+ import { existsSync as existsSync12, readdirSync as readdirSync3, statSync as statSync4, rmSync as rmSync2 } from "fs";
13302
13337
  import { join as join13 } from "path";
13303
13338
  function parseTurnNumbers(turnsDir) {
13304
13339
  if (!existsSync12(turnsDir)) return [];
@@ -13308,7 +13343,7 @@ function rotateTurnRecords() {
13308
13343
  try {
13309
13344
  const turnsDir = WORKSPACE.TURNS;
13310
13345
  if (!existsSync12(turnsDir)) return;
13311
- const turnDirs = parseTurnNumbers(turnsDir).map((n) => `${TURN_FOLDER_PREFIX}${n}`).filter((e) => statSync3(join13(turnsDir, e)).isDirectory()).sort((a, b) => Number(a.slice(TURN_FOLDER_PREFIX.length)) - Number(b.slice(TURN_FOLDER_PREFIX.length)));
13346
+ const turnDirs = parseTurnNumbers(turnsDir).map((n) => `${TURN_FOLDER_PREFIX}${n}`).filter((e) => statSync4(join13(turnsDir, e)).isDirectory()).sort((a, b) => Number(a.slice(TURN_FOLDER_PREFIX.length)) - Number(b.slice(TURN_FOLDER_PREFIX.length)));
13312
13347
  if (turnDirs.length > MEMORY_LIMITS.MAX_TURN_ENTRIES) {
13313
13348
  const dirsToDel = turnDirs.slice(0, turnDirs.length - MEMORY_LIMITS.MAX_TURN_ENTRIES);
13314
13349
  for (const dir of dirsToDel) {
@@ -13334,7 +13369,7 @@ function readJournalSummary() {
13334
13369
  for (const turn of turnDirs) {
13335
13370
  const summaryPath = join14(WORKSPACE.turnPath(turn), TURN_FILES.SUMMARY);
13336
13371
  if (existsSync13(summaryPath)) {
13337
- return readFileSync8(summaryPath, "utf-8");
13372
+ return readFileSync9(summaryPath, "utf-8");
13338
13373
  }
13339
13374
  }
13340
13375
  return "";
@@ -13351,7 +13386,7 @@ function getRecentEntries(count = MEMORY_LIMITS.MAX_TURN_ENTRIES) {
13351
13386
  try {
13352
13387
  const filePath = join14(WORKSPACE.turnPath(turn), TURN_FILES.STRUCTURED);
13353
13388
  if (existsSync13(filePath)) {
13354
- const raw = readFileSync8(filePath, "utf-8");
13389
+ const raw = readFileSync9(filePath, "utf-8");
13355
13390
  entries.push(JSON.parse(raw));
13356
13391
  }
13357
13392
  } catch {
@@ -14073,7 +14108,7 @@ function formatForPrompt(directive, isStale = false) {
14073
14108
  }
14074
14109
 
14075
14110
  // src/agents/strategist/prompt-loader.ts
14076
- import { readFileSync as readFileSync9, existsSync as existsSync14 } from "fs";
14111
+ import { readFileSync as readFileSync10, existsSync as existsSync14 } from "fs";
14077
14112
  import { join as join16, dirname as dirname5 } from "path";
14078
14113
  import { fileURLToPath as fileURLToPath3 } from "url";
14079
14114
  var __dirname3 = dirname5(fileURLToPath3(import.meta.url));
@@ -14081,7 +14116,7 @@ var STRATEGIST_PROMPT_PATH = join16(__dirname3, "../prompts", "strategist-system
14081
14116
  function loadSystemPrompt() {
14082
14117
  try {
14083
14118
  if (existsSync14(STRATEGIST_PROMPT_PATH)) {
14084
- return readFileSync9(STRATEGIST_PROMPT_PATH, "utf-8");
14119
+ return readFileSync10(STRATEGIST_PROMPT_PATH, "utf-8");
14085
14120
  }
14086
14121
  } catch {
14087
14122
  }
@@ -14477,7 +14512,7 @@ async function processReflection(toolJournal, memo14, phase, reflector) {
14477
14512
  }
14478
14513
 
14479
14514
  // src/agents/main-agent/turn-recorder.ts
14480
- import { writeFileSync as writeFileSync10, existsSync as existsSync15, readFileSync as readFileSync10 } from "fs";
14515
+ import { writeFileSync as writeFileSync10, existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
14481
14516
  import { join as join17 } from "path";
14482
14517
  async function recordTurn(context) {
14483
14518
  const { turnCounter, phase, toolJournal, memo: memo14, reflections, summaryRegenerator } = context;
@@ -14550,7 +14585,7 @@ async function regenerateSummary(turnCounter, summaryRegenerator, ctx) {
14550
14585
  if (prevTurn >= 1) {
14551
14586
  const prevSummaryPath = join17(WORKSPACE.turnPath(prevTurn), TURN_FILES.SUMMARY);
14552
14587
  if (existsSync15(prevSummaryPath)) {
14553
- existingSummary = readFileSync10(prevSummaryPath, "utf-8");
14588
+ existingSummary = readFileSync11(prevSummaryPath, "utf-8");
14554
14589
  }
14555
14590
  }
14556
14591
  const turnData = formatTurnRecord({
@@ -14888,7 +14923,14 @@ var TUI_DISPLAY_LIMITS = {
14888
14923
  /** Max chars for thinking block first-line summary */
14889
14924
  thinkingSummaryChars: 72,
14890
14925
  /** Delay before exit to allow Ink to cleanup */
14891
- EXIT_DELAY: 100
14926
+ EXIT_DELAY: 100,
14927
+ /**
14928
+ * Maximum number of messages to keep in React state (TUI message list).
14929
+ * WHY: addMessage() uses [...prev, newMsg] spreading — without a cap, long
14930
+ * sessions accumulate thousands of messages and RAM grows without bound.
14931
+ * Oldest messages are pruned first; all content is preserved in the disk archive.
14932
+ */
14933
+ MAX_MESSAGES: 500
14892
14934
  };
14893
14935
 
14894
14936
  // src/platform/tui/hooks/useAgentState.ts
@@ -14912,7 +14954,13 @@ var useAgentState = () => {
14912
14954
  const toolStartedAtRef = useRef(0);
14913
14955
  const addMessage = useCallback((type, content) => {
14914
14956
  const id = generateId();
14915
- setMessages((prev) => [...prev, { id, type, content, timestamp: /* @__PURE__ */ new Date() }]);
14957
+ setMessages((prev) => {
14958
+ const next = [...prev, { id, type, content, timestamp: /* @__PURE__ */ new Date() }];
14959
+ if (next.length > TUI_DISPLAY_LIMITS.MAX_MESSAGES) {
14960
+ return next.slice(next.length - TUI_DISPLAY_LIMITS.MAX_MESSAGES);
14961
+ }
14962
+ return next;
14963
+ });
14916
14964
  }, []);
14917
14965
  const resetCumulativeCounters = useCallback(() => {
14918
14966
  setCurrentTokens(0);
@@ -16110,7 +16158,7 @@ var useKeyboardShortcuts = ({
16110
16158
  };
16111
16159
 
16112
16160
  // src/platform/tui/components/MessageList.tsx
16113
- import { memo as memo7, useState as useState4, useCallback as useCallback8, useRef as useRef6 } from "react";
16161
+ import { memo as memo7, useState as useState3, useCallback as useCallback8, useRef as useRef6 } from "react";
16114
16162
  import { Box as Box8, Text as Text8, useInput as useInput2 } from "ink";
16115
16163
 
16116
16164
  // src/platform/tui/components/messages/ThinkingBlock.tsx
@@ -16646,34 +16694,12 @@ import { memo as memo6 } from "react";
16646
16694
  import { Box as Box7, Text as Text7 } from "ink";
16647
16695
 
16648
16696
  // src/platform/tui/components/ShimmerBanner.tsx
16649
- import { useState as useState3, useEffect as useEffect5, memo as memo5 } from "react";
16697
+ import { memo as memo5 } from "react";
16650
16698
  import { Box as Box6, Text as Text6 } from "ink";
16651
16699
  import { jsx as jsx6 } from "react/jsx-runtime";
16652
- var FRAME_INTERVAL = 60;
16653
- var WAVE_SPEED = 0.22;
16654
- var CHAR_GAP = 0.18;
16655
- var ROW_GAP = 1.6;
16656
- function waveColor(sin) {
16657
- const t = (sin + 1) / 2;
16658
- const r = Math.round(36 + t * (255 - 36));
16659
- const g = Math.round(150 + t * (255 - 150));
16660
- const b = Math.round(237 + t * (255 - 237));
16661
- return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
16662
- }
16663
16700
  var ShimmerBanner = memo5(({ banner }) => {
16664
- const [tick, setTick] = useState3(0);
16665
- useEffect5(() => {
16666
- const timer = setInterval(() => setTick((t) => t + 1), FRAME_INTERVAL);
16667
- return () => clearInterval(timer);
16668
- }, []);
16669
- const globalPhase = tick * WAVE_SPEED;
16670
16701
  const lines = banner.split("\n").filter((l) => l.length > 0);
16671
- return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: lines.map((line, row) => /* @__PURE__ */ jsx6(Text6, { children: Array.from(line).map((char, col) => {
16672
- const phase = globalPhase - col * CHAR_GAP - row * ROW_GAP;
16673
- const sin = Math.sin(phase);
16674
- const color = char.trim() === "" ? HEX.primary : waveColor(sin);
16675
- return /* @__PURE__ */ jsx6(Text6, { color, children: char }, col);
16676
- }) }, row)) });
16702
+ return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: lines.map((line, row) => /* @__PURE__ */ jsx6(Text6, { color: HEX.primary, children: line }, row)) });
16677
16703
  });
16678
16704
 
16679
16705
  // src/platform/tui/components/messages/EmptyState.tsx
@@ -16739,7 +16765,7 @@ function computeSlidingWindow(messages, scrollOffset) {
16739
16765
  // src/platform/tui/components/MessageList.tsx
16740
16766
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
16741
16767
  var MessageList = memo7(({ messages, isModalOpen, modelName, autoApproveMode, version, scrollOffset = 0 }) => {
16742
- const [expandedIds, setExpandedIds] = useState4(/* @__PURE__ */ new Set());
16768
+ const [expandedIds, setExpandedIds] = useState3(/* @__PURE__ */ new Set());
16743
16769
  const messagesRef = useRef6(messages);
16744
16770
  messagesRef.current = messages;
16745
16771
  const isModalOpenRef = useRef6(isModalOpen);
@@ -16821,12 +16847,12 @@ import { memo as memo10 } from "react";
16821
16847
  import { Box as Box11, Text as Text13 } from "ink";
16822
16848
 
16823
16849
  // src/platform/tui/hooks/useStatusTimer.ts
16824
- import { useState as useState5, useEffect as useEffect6, useRef as useRef7 } from "react";
16850
+ import { useState as useState4, useEffect as useEffect5, useRef as useRef7 } from "react";
16825
16851
  var useStatusTimer = (currentStatus, isProcessing) => {
16826
- const [statusElapsed, setStatusElapsed] = useState5(0);
16852
+ const [statusElapsed, setStatusElapsed] = useState4(0);
16827
16853
  const statusTimerRef = useRef7(null);
16828
16854
  const statusStartRef = useRef7(Date.now());
16829
- useEffect6(() => {
16855
+ useEffect5(() => {
16830
16856
  if (statusTimerRef.current) clearInterval(statusTimerRef.current);
16831
16857
  if (isProcessing && currentStatus) {
16832
16858
  statusStartRef.current = Date.now();
@@ -16849,7 +16875,7 @@ var useStatusTimer = (currentStatus, isProcessing) => {
16849
16875
  import { Box as Box9, Text as Text10 } from "ink";
16850
16876
 
16851
16877
  // src/platform/tui/components/MusicSpinner.tsx
16852
- import { useState as useState6, useEffect as useEffect7, memo as memo8 } from "react";
16878
+ import { useState as useState5, useEffect as useEffect6, memo as memo8 } from "react";
16853
16879
  import { Text as Text9 } from "ink";
16854
16880
  import { jsx as jsx9 } from "react/jsx-runtime";
16855
16881
  var FRAMES = [
@@ -16868,10 +16894,10 @@ var FRAMES = [
16868
16894
  "\u2727",
16869
16895
  "\u2726"
16870
16896
  ];
16871
- var INTERVAL = 100;
16897
+ var INTERVAL = 150;
16872
16898
  var MusicSpinner = memo8(({ color }) => {
16873
- const [index, setIndex] = useState6(0);
16874
- useEffect7(() => {
16899
+ const [index, setIndex] = useState5(0);
16900
+ useEffect6(() => {
16875
16901
  const timer = setInterval(() => {
16876
16902
  setIndex((i) => (i + 1) % FRAMES.length);
16877
16903
  }, INTERVAL);
@@ -16912,11 +16938,11 @@ var RetryView = ({ retryState }) => {
16912
16938
  import { Box as Box10, Text as Text12 } from "ink";
16913
16939
 
16914
16940
  // src/platform/tui/components/ShimmerText.tsx
16915
- import { useState as useState7, useEffect as useEffect8, memo as memo9 } from "react";
16941
+ import { useState as useState6, useEffect as useEffect7, memo as memo9 } from "react";
16916
16942
  import { Text as Text11 } from "ink";
16917
16943
  import { jsx as jsx11 } from "react/jsx-runtime";
16918
- var FRAME_INTERVAL2 = 60;
16919
- var WAVE_SPEED2 = 0.25;
16944
+ var FRAME_INTERVAL = 120;
16945
+ var WAVE_SPEED = 0.25;
16920
16946
  var CHAR_PHASE_GAP = 0.55;
16921
16947
  function sinToColor(sin) {
16922
16948
  const t = (sin + 1) / 2;
@@ -16925,14 +16951,14 @@ function sinToColor(sin) {
16925
16951
  return `#${hex}${hex}${hex}`;
16926
16952
  }
16927
16953
  var ShimmerText = memo9(({ children, bold, phase = 0 }) => {
16928
- const [tick, setTick] = useState7(0);
16929
- useEffect8(() => {
16954
+ const [tick, setTick] = useState6(0);
16955
+ useEffect7(() => {
16930
16956
  const timer = setInterval(() => {
16931
16957
  setTick((t) => t + 1);
16932
- }, FRAME_INTERVAL2);
16958
+ }, FRAME_INTERVAL);
16933
16959
  return () => clearInterval(timer);
16934
16960
  }, []);
16935
- const globalPhase = tick * WAVE_SPEED2 + phase;
16961
+ const globalPhase = tick * WAVE_SPEED + phase;
16936
16962
  return /* @__PURE__ */ jsx11(Text11, { bold, children: Array.from(children).map((char, i) => {
16937
16963
  const charPhase = globalPhase - i * CHAR_PHASE_GAP;
16938
16964
  const sin = Math.sin(charPhase);
@@ -17010,7 +17036,7 @@ var StatusDisplay = memo10(({
17010
17036
  });
17011
17037
 
17012
17038
  // src/platform/tui/components/ChatInput.tsx
17013
- import { useMemo, useCallback as useCallback9, useRef as useRef8, memo as memo11, useState as useState8, useEffect as useEffect9 } from "react";
17039
+ import { useMemo, useCallback as useCallback9, useRef as useRef8, memo as memo11, useState as useState7, useEffect as useEffect8 } from "react";
17014
17040
  import { Box as Box15, Text as Text17, useInput as useInput3 } from "ink";
17015
17041
 
17016
17042
  // src/platform/tui/components/input/AutocompletePreview.tsx
@@ -17133,7 +17159,7 @@ var ChatInput = memo11(({
17133
17159
  return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
17134
17160
  }, [isSlashMode, partialCmd, hasArgs]);
17135
17161
  const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
17136
- const [selectedIdx, setSelectedIdx] = useState8(0);
17162
+ const [selectedIdx, setSelectedIdx] = useState7(0);
17137
17163
  const clampedIdx = Math.min(selectedIdx, Math.max(0, suggestions.length - 1));
17138
17164
  const selectedIdxRef = useRef8(clampedIdx);
17139
17165
  selectedIdxRef.current = clampedIdx;
@@ -17149,10 +17175,10 @@ var ChatInput = memo11(({
17149
17175
  inputRequestRef.current = inputRequest;
17150
17176
  const onChangeRef = useRef8(onChange);
17151
17177
  onChangeRef.current = onChange;
17152
- const [pastedHint, setPastedHint] = useState8(null);
17178
+ const [pastedHint, setPastedHint] = useState7(null);
17153
17179
  const prevValueRef = useRef8(value);
17154
17180
  const pasteTimerRef = useRef8(null);
17155
- useEffect9(() => {
17181
+ useEffect8(() => {
17156
17182
  const diff = value.length - prevValueRef.current.length;
17157
17183
  if (diff > 20) {
17158
17184
  if (pasteTimerRef.current) clearTimeout(pasteTimerRef.current);
@@ -17164,7 +17190,7 @@ var ChatInput = memo11(({
17164
17190
  if (pasteTimerRef.current) clearTimeout(pasteTimerRef.current);
17165
17191
  };
17166
17192
  }, [value]);
17167
- const [inputKey, setInputKey] = useState8(0);
17193
+ const [inputKey, setInputKey] = useState7(0);
17168
17194
  const completeCommand = useCallback9((idx) => {
17169
17195
  const sug = suggestionsRef.current;
17170
17196
  if (!sug.length) return;
@@ -17485,10 +17511,10 @@ var App = ({ autoApprove = false, target }) => {
17485
17511
  const { exit } = useApp();
17486
17512
  const { stdout } = useStdout4();
17487
17513
  const terminalWidth = stdout?.columns ?? 80;
17488
- const [input, setInput] = useState9("");
17489
- const [secretInput, setSecretInput] = useState9("");
17490
- const [autoApproveMode, setAutoApproveMode] = useState9(autoApprove);
17491
- const [modal, setModal] = useState9({ type: null, content: "", scrollOffset: 0 });
17514
+ const [input, setInput] = useState8("");
17515
+ const [secretInput, setSecretInput] = useState8("");
17516
+ const [autoApproveMode, setAutoApproveMode] = useState8(autoApprove);
17517
+ const [modal, setModal] = useState8({ type: null, content: "", scrollOffset: 0 });
17492
17518
  const {
17493
17519
  agent,
17494
17520
  messages,
@@ -17521,7 +17547,7 @@ var App = ({ autoApprove = false, target }) => {
17521
17547
  const clearInput = useCallback11(() => {
17522
17548
  setInput("");
17523
17549
  }, []);
17524
- const [historyScrollOffset, setHistoryScrollOffset] = useState9(0);
17550
+ const [historyScrollOffset, setHistoryScrollOffset] = useState8(0);
17525
17551
  const handleScroll = useCallback11((delta) => {
17526
17552
  setHistoryScrollOffset((prev) => Math.max(0, prev - delta));
17527
17553
  }, []);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pentesting",
3
- "version": "0.70.2",
3
+ "version": "0.70.4",
4
4
  "description": "Autonomous Penetration Testing AI Agent",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",
@@ -35,9 +35,9 @@
35
35
  "type": "git",
36
36
  "url": "git+https://github.com/agnusdei1207"
37
37
  },
38
- "homepage": "https://agnusdei1207.github.io/brainscience/",
38
+ "homepage": "https://agnusdei1207.github.io/brainscience/pentesting",
39
39
  "bugs": {
40
- "url": "https://agnusdei1207.github.io/brainscience/"
40
+ "url": "https://agnusdei1207.github.io/brainscience/pentesting"
41
41
  },
42
42
  "keywords": [
43
43
  "penetration-testing",