pentesting 0.70.5 → 0.70.6

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 +485 -512
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -762,7 +762,7 @@ var INPUT_PROMPT_PATTERNS = [
762
762
 
763
763
  // src/shared/constants/agent.ts
764
764
  var APP_NAME = "Pentest AI";
765
- var APP_VERSION = "0.70.5";
765
+ var APP_VERSION = "0.70.6";
766
766
  var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
767
767
  var LLM_ROLES = {
768
768
  SYSTEM: "system",
@@ -817,7 +817,7 @@ import { render } from "ink";
817
817
  import chalk from "chalk";
818
818
 
819
819
  // src/platform/tui/app.tsx
820
- import { useState as useState7, useCallback as useCallback11, useRef as useRef10 } from "react";
820
+ import { useState as useState6, useCallback as useCallback9, useRef as useRef8 } from "react";
821
821
  import { Box as Box19, useApp, useStdout as useStdout4 } from "ink";
822
822
 
823
823
  // src/platform/tui/hooks/useAgent.ts
@@ -1115,7 +1115,8 @@ var TOOL_NAMES = {
1115
1115
  MITM_PROXY: "mitm_proxy",
1116
1116
  PACKET_SNIFF: "packet_sniff",
1117
1117
  DNS_SPOOF: "dns_spoof",
1118
- TRAFFIC_INTERCEPT: "traffic_intercept"
1118
+ TRAFFIC_INTERCEPT: "traffic_intercept",
1119
+ ANALYZE_PCAP: "analyze_pcap"
1119
1120
  };
1120
1121
 
1121
1122
  // src/shared/constants/attack.ts
@@ -1174,7 +1175,8 @@ var EVENT_TYPES = {
1174
1175
  RETRY: "retry",
1175
1176
  USAGE_UPDATE: "usage_update",
1176
1177
  INPUT_REQUEST: "input_request",
1177
- FLAG_FOUND: "flag_found"
1178
+ FLAG_FOUND: "flag_found",
1179
+ NOTIFICATION: "notification"
1178
1180
  };
1179
1181
  var COMMAND_EVENT_TYPES = {
1180
1182
  TOOL_MISSING: "tool_missing",
@@ -1186,7 +1188,9 @@ var COMMAND_EVENT_TYPES = {
1186
1188
  COMMAND_SUCCESS: "command_success",
1187
1189
  COMMAND_FAILED: "command_failed",
1188
1190
  COMMAND_ERROR: "command_error",
1189
- INPUT_REQUIRED: "input_required"
1191
+ INPUT_REQUIRED: "input_required",
1192
+ COMMAND_STDOUT: "command_stdout",
1193
+ PROCESS_NOTIFICATION: "process_notification"
1190
1194
  };
1191
1195
 
1192
1196
  // src/shared/constants/ui-commands.ts
@@ -1268,13 +1272,6 @@ var EDGE_STATUS = {
1268
1272
  SUCCEEDED: "succeeded",
1269
1273
  FAILED: "failed"
1270
1274
  };
1271
- var SEVERITY = {
1272
- CRITICAL: "critical",
1273
- HIGH: "high",
1274
- MEDIUM: "medium",
1275
- LOW: "low",
1276
- INFO: "info"
1277
- };
1278
1275
  var IMPACT_WEIGHT = {
1279
1276
  critical: 4,
1280
1277
  high: 3,
@@ -1523,21 +1520,21 @@ function dfsChains(nodeId, path2, pathEdges, visited, ctx) {
1523
1520
  visited.delete(nodeId);
1524
1521
  }
1525
1522
  function estimateImpact(node) {
1526
- if (node.type === NODE_TYPE.LOOT) return SEVERITY.CRITICAL;
1523
+ if (node.type === NODE_TYPE.LOOT) return SEVERITIES.CRITICAL;
1527
1524
  if (node.type === NODE_TYPE.ACCESS) {
1528
1525
  const level = String(node.data.level || "");
1529
- if (["root", "SYSTEM", "Administrator", "admin"].includes(level)) return SEVERITY.CRITICAL;
1530
- return SEVERITY.HIGH;
1526
+ if (["root", "SYSTEM", "Administrator", "admin"].includes(level)) return SEVERITIES.CRITICAL;
1527
+ return SEVERITIES.HIGH;
1531
1528
  }
1532
1529
  if (node.type === NODE_TYPE.VULNERABILITY) {
1533
1530
  const sev = String(node.data.severity || "");
1534
- if (sev === SEVERITY.CRITICAL) return SEVERITY.CRITICAL;
1535
- if (sev === SEVERITY.HIGH) return SEVERITY.HIGH;
1536
- if (sev === SEVERITY.MEDIUM) return SEVERITY.MEDIUM;
1531
+ if (sev === SEVERITIES.CRITICAL) return SEVERITIES.CRITICAL;
1532
+ if (sev === SEVERITIES.HIGH) return SEVERITIES.HIGH;
1533
+ if (sev === SEVERITIES.MEDIUM) return SEVERITIES.MEDIUM;
1537
1534
  }
1538
- if (node.type === NODE_TYPE.DOMAIN) return SEVERITY.CRITICAL;
1539
- if (node.type === NODE_TYPE.CERTIFICATE_TEMPLATE) return SEVERITY.HIGH;
1540
- return SEVERITY.LOW;
1535
+ if (node.type === NODE_TYPE.DOMAIN) return SEVERITIES.CRITICAL;
1536
+ if (node.type === NODE_TYPE.CERTIFICATE_TEMPLATE) return SEVERITIES.HIGH;
1537
+ return SEVERITIES.LOW;
1541
1538
  }
1542
1539
  function extractSuccessfulChain(nodes, edges) {
1543
1540
  const MIN_CHAIN_LENGTH = 2;
@@ -1753,10 +1750,10 @@ function formatPathsList(nodes, edges, failedPaths) {
1753
1750
  const c = chains[i];
1754
1751
  const prob = (c.probability * 100).toFixed(0);
1755
1752
  const impactColors = {
1756
- [SEVERITY.CRITICAL]: "\u{1F534}",
1757
- [SEVERITY.HIGH]: "\u{1F7E0}",
1758
- [SEVERITY.MEDIUM]: "\u{1F7E1}",
1759
- [SEVERITY.LOW]: "\u{1F7E2}"
1753
+ [SEVERITIES.CRITICAL]: "\u{1F534}",
1754
+ [SEVERITIES.HIGH]: "\u{1F7E0}",
1755
+ [SEVERITIES.MEDIUM]: "\u{1F7E1}",
1756
+ [SEVERITIES.LOW]: "\u{1F7E2}"
1760
1757
  };
1761
1758
  const impactIcon = impactColors[c.impact] || "\u26AA";
1762
1759
  lines.push(`${impactIcon} PATH ${i + 1} [${c.impact.toUpperCase()} | prob: ${prob}% | steps: ${c.length}]`);
@@ -4397,133 +4394,133 @@ function injectCurlMaxTime(command, timeoutSec) {
4397
4394
  if (/--max-time\b|-m\s+\d/.test(command)) return command;
4398
4395
  return command.replace(/\bcurl\b/, `curl --max-time ${timeoutSec}`);
4399
4396
  }
4400
- async function executeCommandOnce(command, options = {}) {
4401
- return new Promise((resolve) => {
4402
- const eventEmitter = getEventEmitter();
4403
- const inputHandler = getInputHandler();
4404
- const timeout = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
4405
- const safeCommand = injectCurlMaxTime(command, CURL_MAX_TIME_SEC);
4406
- const torLeak = checkTorLeakRisk(safeCommand);
4407
- if (!torLeak.safe) {
4408
- resolve({
4409
- success: false,
4410
- output: "",
4411
- error: `\u{1F6D1} TOR IP LEAK BLOCKED
4397
+ function prepareCommand(command) {
4398
+ const safeCommand = injectCurlMaxTime(command, CURL_MAX_TIME_SEC);
4399
+ const torLeak = checkTorLeakRisk(safeCommand);
4400
+ if (!torLeak.safe) {
4401
+ return {
4402
+ safeCommand,
4403
+ execCommand: "",
4404
+ error: `\u{1F6D1} TOR IP LEAK BLOCKED
4412
4405
  Reason: ${torLeak.reason}
4413
4406
  Suggestion: ${torLeak.suggestion}`
4407
+ };
4408
+ }
4409
+ return { safeCommand, execCommand: wrapCommandForTor(safeCommand) };
4410
+ }
4411
+ function setupTimeout(child, timeoutMs, onTimeout) {
4412
+ return setTimeout(() => {
4413
+ onTimeout();
4414
+ try {
4415
+ process.kill(-child.pid, "SIGKILL");
4416
+ } catch {
4417
+ try {
4418
+ child.kill("SIGKILL");
4419
+ } catch {
4420
+ }
4421
+ }
4422
+ }, timeoutMs);
4423
+ }
4424
+ var ProcessIOHandler = class {
4425
+ constructor(child, processState, eventEmitter, inputHandler) {
4426
+ this.child = child;
4427
+ this.processState = processState;
4428
+ this.eventEmitter = eventEmitter;
4429
+ this.inputHandler = inputHandler;
4430
+ }
4431
+ stdout = "";
4432
+ stderr = "";
4433
+ inputHandled = false;
4434
+ async checkForInputPrompt(data) {
4435
+ if (this.inputHandled || !this.inputHandler || this.processState.terminated) return;
4436
+ for (const pattern of INPUT_PROMPT_PATTERNS) {
4437
+ if (pattern.test(data)) {
4438
+ this.inputHandled = true;
4439
+ this.eventEmitter?.({
4440
+ type: COMMAND_EVENT_TYPES.INPUT_REQUIRED,
4441
+ message: `Input required: ${data.trim().slice(0, SYSTEM_LIMITS.MAX_PROMPT_PREVIEW)}`,
4442
+ detail: "Waiting for user input..."
4443
+ });
4444
+ try {
4445
+ const userInput = await this.inputHandler(data.trim());
4446
+ if (userInput !== null && !this.processState.terminated && this.child.stdin?.writable) {
4447
+ this.child.stdin.write(userInput + "\n");
4448
+ }
4449
+ } catch {
4450
+ }
4451
+ return;
4452
+ }
4453
+ }
4454
+ }
4455
+ handleStdout(data) {
4456
+ const text = data.toString();
4457
+ this.stdout += text;
4458
+ const lines = text.split("\n").map((l) => l.trim()).filter(Boolean);
4459
+ if (lines.length > 0) {
4460
+ this.eventEmitter?.({
4461
+ type: COMMAND_EVENT_TYPES.COMMAND_STDOUT,
4462
+ message: lines[lines.length - 1]
4414
4463
  });
4415
- return;
4464
+ }
4465
+ this.checkForInputPrompt(text);
4466
+ }
4467
+ handleStderr(data) {
4468
+ const text = data.toString();
4469
+ this.stderr += text;
4470
+ this.checkForInputPrompt(text);
4471
+ }
4472
+ };
4473
+ async function executeCommandOnce(command, options = {}) {
4474
+ return new Promise((resolve) => {
4475
+ const eventEmitter = getEventEmitter();
4476
+ const { safeCommand, execCommand, error } = prepareCommand(command);
4477
+ if (error) {
4478
+ return resolve({ success: false, output: "", error });
4416
4479
  }
4417
4480
  eventEmitter?.({
4418
4481
  type: COMMAND_EVENT_TYPES.COMMAND_START,
4419
4482
  message: `Executing: ${safeCommand.slice(0, DISPLAY_LIMITS.COMMAND_PREVIEW)}${safeCommand.length > DISPLAY_LIMITS.COMMAND_PREVIEW ? "..." : ""}`
4420
4483
  });
4421
- const execCommand = wrapCommandForTor(safeCommand);
4422
4484
  const child = spawn3("sh", ["-c", execCommand], {
4423
4485
  detached: true,
4424
4486
  env: { ...process.env, ...options.env },
4425
4487
  cwd: options.cwd
4426
4488
  });
4427
- let stdout = "";
4428
- let stderr = "";
4429
- let inputHandled = false;
4430
- let processTerminated = false;
4431
- let timedOut = false;
4432
- const killTimer = setTimeout(() => {
4433
- if (processTerminated) return;
4434
- timedOut = true;
4435
- processTerminated = true;
4436
- try {
4437
- process.kill(-child.pid, "SIGKILL");
4438
- } catch {
4439
- try {
4440
- child.kill("SIGKILL");
4441
- } catch {
4442
- }
4443
- }
4444
- }, timeout);
4445
- const checkForInputPrompt = async (data) => {
4446
- if (inputHandled || !inputHandler || processTerminated) return;
4447
- for (const pattern of INPUT_PROMPT_PATTERNS) {
4448
- if (pattern.test(data)) {
4449
- inputHandled = true;
4450
- eventEmitter?.({
4451
- type: COMMAND_EVENT_TYPES.INPUT_REQUIRED,
4452
- message: `Input required: ${data.trim().slice(0, SYSTEM_LIMITS.MAX_PROMPT_PREVIEW)}`,
4453
- detail: "Waiting for user input..."
4454
- });
4455
- const promptText = data.trim();
4456
- try {
4457
- const userInput = await inputHandler(promptText);
4458
- if (userInput !== null && !processTerminated && child.stdin?.writable) {
4459
- child.stdin.write(userInput + "\n");
4460
- }
4461
- } catch {
4462
- }
4463
- return;
4464
- }
4465
- }
4466
- };
4467
- child.stdout.on("data", (data) => {
4468
- const text = data.toString();
4469
- stdout += text;
4470
- checkForInputPrompt(text);
4471
- });
4472
- child.stderr.on("data", (data) => {
4473
- const text = data.toString();
4474
- stderr += text;
4475
- checkForInputPrompt(text);
4489
+ const processState = { terminated: false, timedOut: false };
4490
+ const timeoutMs = options.timeout ?? TOOL_TIMEOUTS.DEFAULT_COMMAND;
4491
+ const killTimer = setupTimeout(child, timeoutMs, () => {
4492
+ processState.timedOut = true;
4493
+ processState.terminated = true;
4476
4494
  });
4495
+ const ioHandler = new ProcessIOHandler(child, processState, eventEmitter, getInputHandler());
4496
+ child.stdout.on("data", (d) => ioHandler.handleStdout(d));
4497
+ child.stderr.on("data", (d) => ioHandler.handleStderr(d));
4477
4498
  child.on("close", (code) => {
4478
4499
  clearTimeout(killTimer);
4479
- processTerminated = true;
4480
- if (timedOut) {
4481
- eventEmitter?.({
4482
- type: COMMAND_EVENT_TYPES.COMMAND_FAILED,
4483
- message: `Command timed out after ${Math.round(timeout / 1e3)}s`
4484
- });
4485
- resolve({
4500
+ processState.terminated = true;
4501
+ const outTrimmed = ioHandler.stdout.trim();
4502
+ const errTrimmed = ioHandler.stderr.trim();
4503
+ if (processState.timedOut) {
4504
+ eventEmitter?.({ type: COMMAND_EVENT_TYPES.COMMAND_FAILED, message: `Command timed out after ${Math.round(timeoutMs / 1e3)}s` });
4505
+ return resolve({
4486
4506
  success: false,
4487
- output: stdout.trim(),
4488
- error: `Command timed out after ${Math.round(timeout / 1e3)}s. Output so far:
4489
- ${(stdout + stderr).trim().slice(-500)}`
4507
+ output: outTrimmed,
4508
+ error: `Command timed out after ${Math.round(timeoutMs / 1e3)}s. Output so far:
4509
+ ${(ioHandler.stdout + ioHandler.stderr).trim().slice(-500)}`
4490
4510
  });
4491
- return;
4492
4511
  }
4493
4512
  if (code === 0) {
4494
- eventEmitter?.({
4495
- type: COMMAND_EVENT_TYPES.COMMAND_SUCCESS,
4496
- message: `Command completed`
4497
- });
4498
- resolve({
4499
- success: true,
4500
- output: stdout.trim() || stderr.trim()
4501
- });
4502
- } else {
4503
- eventEmitter?.({
4504
- type: COMMAND_EVENT_TYPES.COMMAND_FAILED,
4505
- message: `Command failed (exit ${code})`,
4506
- detail: stderr.slice(0, SYSTEM_LIMITS.MAX_INPUT_SLICE)
4507
- });
4508
- resolve({
4509
- success: false,
4510
- output: stdout.trim(),
4511
- error: stderr.trim() || `Process exited with code ${code}`
4512
- });
4513
+ eventEmitter?.({ type: COMMAND_EVENT_TYPES.COMMAND_SUCCESS, message: `Command completed` });
4514
+ return resolve({ success: true, output: outTrimmed || errTrimmed });
4513
4515
  }
4516
+ eventEmitter?.({ type: COMMAND_EVENT_TYPES.COMMAND_FAILED, message: `Command failed (exit ${code})`, detail: errTrimmed.slice(0, SYSTEM_LIMITS.MAX_INPUT_SLICE) });
4517
+ return resolve({ success: false, output: outTrimmed, error: errTrimmed || `Process exited with code ${code}` });
4514
4518
  });
4515
4519
  child.on("error", (err) => {
4516
4520
  clearTimeout(killTimer);
4517
- processTerminated = true;
4518
- eventEmitter?.({
4519
- type: COMMAND_EVENT_TYPES.COMMAND_ERROR,
4520
- message: `Command error: ${err.message}`
4521
- });
4522
- resolve({
4523
- success: false,
4524
- output: "",
4525
- error: err.message || String(err)
4526
- });
4521
+ processState.terminated = true;
4522
+ eventEmitter?.({ type: COMMAND_EVENT_TYPES.COMMAND_ERROR, message: `Command error: ${err.message}` });
4523
+ resolve({ success: false, output: "", error: err.message || String(err) });
4527
4524
  });
4528
4525
  });
4529
4526
  }
@@ -4702,6 +4699,13 @@ function startBackgroundProcess(command, options = {}) {
4702
4699
  bgProcess.hasExited = true;
4703
4700
  bgProcess.exitCode = code;
4704
4701
  logEvent(processId, PROCESS_EVENTS.DIED, `Process exited with code ${code}`);
4702
+ const emitter = getEventEmitter();
4703
+ if (emitter) {
4704
+ emitter({
4705
+ type: COMMAND_EVENT_TYPES.PROCESS_NOTIFICATION,
4706
+ message: `Process finished: ${bgProcess.description} (exit ${code}). Ready for analysis.`
4707
+ });
4708
+ }
4705
4709
  });
4706
4710
  setProcess(processId, bgProcess);
4707
4711
  logEvent(processId, PROCESS_EVENTS.STARTED, `${role} started: ${command.slice(0, SYSTEM_LIMITS.MAX_DESCRIPTION_LENGTH)} (PID:${child.pid})`);
@@ -6363,10 +6367,6 @@ Returns recommendations on process status, port conflicts, long-running tasks, e
6363
6367
  }
6364
6368
  ];
6365
6369
 
6366
- // src/domains/registry.ts
6367
- import { join as join11, dirname as dirname3 } from "path";
6368
- import { fileURLToPath } from "url";
6369
-
6370
6370
  // src/shared/constants/service-ports.ts
6371
6371
  var SERVICE_PORTS = {
6372
6372
  SSH: 22,
@@ -6779,7 +6779,8 @@ Requires root/sudo privileges.`,
6779
6779
  duration: { type: "number", description: "Capture duration in seconds. Default: 30" },
6780
6780
  output_file: { type: "string", description: "Path to save pcap file. Auto-generated if not specified." },
6781
6781
  count: { type: "number", description: "Number of packets to capture. Default: 1000" },
6782
- extract_creds: { type: "boolean", description: "Try to extract credentials from captured traffic. Default: true" }
6782
+ extract_creds: { type: "boolean", description: "Try to extract credentials from captured traffic. Default: true" },
6783
+ background: { type: "boolean", description: "Run the capture in the background and return immediately. Default: false" }
6783
6784
  },
6784
6785
  execute: async (p) => {
6785
6786
  const iface = p.interface || "any";
@@ -6788,11 +6789,23 @@ Requires root/sudo privileges.`,
6788
6789
  const outputFile = p.output_file || createTempFile(FILE_EXTENSIONS.PCAP);
6789
6790
  const count = p.count || NETWORK_CONFIG.DEFAULT_PACKET_COUNT;
6790
6791
  const extractCreds = p.extract_creds !== false;
6792
+ const isBackground = p.background === true;
6791
6793
  const filterFlag = filter ? `"${filter}"` : "";
6792
6794
  const captureCmd = NETWORK_COMMANDS.TCPDUMP_CAPTURE.replace("${duration}", duration.toString()).replace("${iface}", iface).replace("${count}", count.toString()).replace("${outputFile}", outputFile).replace("${filterFlag}", filterFlag);
6793
6795
  const proc = startBackgroundProcess(captureCmd, {
6794
6796
  description: `Packet capture on ${iface}`
6795
6797
  });
6798
+ if (isBackground) {
6799
+ return {
6800
+ success: true,
6801
+ output: `Packet capture started in background.
6802
+ Process ID: ${proc.id}
6803
+ Duration: ${duration}s
6804
+ Output File: ${outputFile}
6805
+
6806
+ You can continue with other tasks. To analyze the capture later, use run_cmd with tshark or tcpdump against the output file.`
6807
+ };
6808
+ }
6796
6809
  await new Promise((r) => setTimeout(r, (duration + NETWORK_CONFIG.WAIT_BUFFER_SECONDS) * 1e3));
6797
6810
  const captureOutput = getProcessOutput(proc.id);
6798
6811
  await stopBackgroundProcess(proc.id);
@@ -6958,6 +6971,56 @@ ${dnsQueries.output}
6958
6971
  }
6959
6972
  });
6960
6973
 
6974
+ // src/domains/network/analyze-pcap.ts
6975
+ var createAnalyzePcapTool = () => ({
6976
+ name: TOOL_NAMES.ANALYZE_PCAP,
6977
+ description: `Perform deep microscopic analysis of a PCAP file using tshark filters.
6978
+ Use this to:
6979
+ - Extract credentials from specific protocols
6980
+ - Reconstruct TCP streams
6981
+ - Analyze specific communication patterns (e.g. HTTP, SMB, DNS)
6982
+ - Identify potential attack vectors from intercepted traffic`,
6983
+ parameters: {
6984
+ file_path: { type: "string", description: "Path to the PCAP file to analyze." },
6985
+ filter: { type: "string", description: 'tshark display filter (e.g., "http.request.method == POST", "tcp.port == 445")' },
6986
+ fields: { type: "string", description: 'Comma-separated list of fields to extract (e.g., "http.host,http.user_agent,http.auth_basic")' },
6987
+ limit: { type: "number", description: "Maximum number of packets to process. Default: 100" }
6988
+ },
6989
+ required: ["file_path"],
6990
+ execute: async (p) => {
6991
+ const filePath = p.file_path;
6992
+ const filter = p.filter || "";
6993
+ const fields = p.fields || "";
6994
+ const limit = p.limit || 100;
6995
+ let cmd = `tshark -r ${filePath}`;
6996
+ if (filter) {
6997
+ cmd += ` -Y "${filter}"`;
6998
+ }
6999
+ if (fields) {
7000
+ const fieldArgs = fields.split(",").map((f) => `-e ${f.trim()}`).join(" ");
7001
+ cmd += ` -T fields ${fieldArgs} -E header=y -E separator=/t`;
7002
+ } else {
7003
+ cmd += ` -c ${limit}`;
7004
+ }
7005
+ const result = await runCommand(cmd);
7006
+ if (!result.success) {
7007
+ return {
7008
+ success: false,
7009
+ output: `Failed to analyze PCAP: ${result.error}`
7010
+ };
7011
+ }
7012
+ let output = `PCAP Analysis Results for ${filePath}:
7013
+ `;
7014
+ if (filter) output += `Filter: ${filter}
7015
+ `;
7016
+ if (fields) output += `Fields: ${fields}
7017
+ `;
7018
+ output += `
7019
+ ${result.output}`;
7020
+ return { success: true, output };
7021
+ }
7022
+ });
7023
+
6961
7024
  // src/engine/tools/intel-utils/types.ts
6962
7025
  var PORT_STATE = {
6963
7026
  OPEN: "open",
@@ -8057,13 +8120,12 @@ var RETRY_CONFIG = {
8057
8120
  baseDelayMs: 2e3,
8058
8121
  maxDelayMs: 6e4,
8059
8122
  jitterMs: 1e3,
8060
- // Auto-retry only for transient errors (rate limits)
8061
- // Other errors are returned to agent for autonomous decision
8062
- // Set high to handle extended rate limiting during peak usage
8063
- autoRetryMaxAttempts: 10,
8064
- // Auto-retry for rate limits
8065
- autoRetryDelayMs: 5e3
8066
- // Initial delay for rate limit retry (exponential backoff)
8123
+ /** WHY Infinity: Rate limit (429) errors are transient infrastructure delays.
8124
+ * Infinite fixed-interval retries allow the agent to eventually recover
8125
+ * without failing the entire engagement. */
8126
+ autoRetryMaxAttempts: Infinity,
8127
+ autoRetryDelayMs: 1e4
8128
+ // 10s fixed interval for 429/network errors
8067
8129
  };
8068
8130
  var LLM_LIMITS = {
8069
8131
  /** WHY 64K: non-streaming calls (orchestrator, summaries) benefit from
@@ -8320,7 +8382,8 @@ var NETWORK_TOOLS = [
8320
8382
  createDnsSpoofTool(),
8321
8383
  createMitmProxyTool(),
8322
8384
  createPacketSniffTool(),
8323
- createTrafficInterceptTool()
8385
+ createTrafficInterceptTool(),
8386
+ createAnalyzePcapTool()
8324
8387
  ];
8325
8388
 
8326
8389
  // src/domains/web/intel.ts
@@ -10739,98 +10802,82 @@ var INTEL_TOOLS = [
10739
10802
  ];
10740
10803
 
10741
10804
  // src/domains/registry.ts
10742
- var __dirname = dirname3(fileURLToPath(import.meta.url));
10743
10805
  var DOMAINS = {
10744
10806
  [SERVICE_CATEGORIES.NETWORK]: {
10745
10807
  id: SERVICE_CATEGORIES.NETWORK,
10746
10808
  name: "Network Infrastructure",
10747
- description: "Vulnerability scanning, port mapping, and network service exploitation.",
10748
- promptPath: join11(__dirname, "network/prompt.md")
10809
+ description: "Vulnerability scanning, port mapping, and network service exploitation."
10749
10810
  },
10750
10811
  [SERVICE_CATEGORIES.WEB]: {
10751
10812
  id: SERVICE_CATEGORIES.WEB,
10752
10813
  name: "Web Application",
10753
- description: "Web app security testing, injection attacks, and auth bypass.",
10754
- promptPath: join11(__dirname, "web/prompt.md")
10814
+ description: "Web app security testing, injection attacks, and auth bypass."
10755
10815
  },
10756
10816
  // Phase 2 domains (using internal categories for now)
10757
10817
  "engagement": {
10758
10818
  id: "engagement",
10759
10819
  name: "Engagement Management",
10760
- description: "Mission planning, state tracking, and finding management.",
10761
- promptPath: join11(__dirname, "engagement/prompt.md")
10820
+ description: "Mission planning, state tracking, and finding management."
10762
10821
  },
10763
10822
  "exploit": {
10764
10823
  id: "exploit",
10765
10824
  name: "Exploitation Utilities",
10766
- description: "Payload mutation, hash cracking, and wordlists.",
10767
- promptPath: join11(__dirname, "exploit/prompt.md")
10825
+ description: "Payload mutation, hash cracking, and wordlists."
10768
10826
  },
10769
10827
  "intel": {
10770
10828
  id: "intel",
10771
10829
  name: "Vulnerability Intelligence",
10772
- description: "OWASP knowledge, CVE research, and web attack surface.",
10773
- promptPath: join11(__dirname, "intel/prompt.md")
10830
+ description: "OWASP knowledge, CVE research, and web attack surface."
10774
10831
  },
10775
10832
  [SERVICE_CATEGORIES.DATABASE]: {
10776
10833
  id: SERVICE_CATEGORIES.DATABASE,
10777
10834
  name: "Database Security",
10778
- description: "SQL injection, database enumeration, and data extraction.",
10779
- promptPath: join11(__dirname, "database/prompt.md")
10835
+ description: "SQL injection, database enumeration, and data extraction."
10780
10836
  },
10781
10837
  [SERVICE_CATEGORIES.AD]: {
10782
10838
  id: SERVICE_CATEGORIES.AD,
10783
10839
  name: "Active Directory",
10784
- description: "Kerberos, LDAP, and Windows domain privilege escalation.",
10785
- promptPath: join11(__dirname, "ad/prompt.md")
10840
+ description: "Kerberos, LDAP, and Windows domain privilege escalation."
10786
10841
  },
10787
10842
  [SERVICE_CATEGORIES.EMAIL]: {
10788
10843
  id: SERVICE_CATEGORIES.EMAIL,
10789
10844
  name: "Email Services",
10790
- description: "SMTP, IMAP, POP3 security and user enumeration.",
10791
- promptPath: join11(__dirname, "email/prompt.md")
10845
+ description: "SMTP, IMAP, POP3 security and user enumeration."
10792
10846
  },
10793
10847
  [SERVICE_CATEGORIES.REMOTE_ACCESS]: {
10794
10848
  id: SERVICE_CATEGORIES.REMOTE_ACCESS,
10795
10849
  name: "Remote Access",
10796
- description: "SSH, RDP, VNC and other remote control protocols.",
10797
- promptPath: join11(__dirname, "remote-access/prompt.md")
10850
+ description: "SSH, RDP, VNC and other remote control protocols."
10798
10851
  },
10799
10852
  [SERVICE_CATEGORIES.FILE_SHARING]: {
10800
10853
  id: SERVICE_CATEGORIES.FILE_SHARING,
10801
10854
  name: "File Sharing",
10802
- description: "SMB, NFS, FTP and shared resource security.",
10803
- promptPath: join11(__dirname, "file-sharing/prompt.md")
10855
+ description: "SMB, NFS, FTP and shared resource security."
10804
10856
  },
10805
10857
  [SERVICE_CATEGORIES.CLOUD]: {
10806
10858
  id: SERVICE_CATEGORIES.CLOUD,
10807
10859
  name: "Cloud Infrastructure",
10808
- description: "AWS, Azure, and GCP security and misconfiguration.",
10809
- promptPath: join11(__dirname, "cloud/prompt.md")
10860
+ description: "AWS, Azure, and GCP security and misconfiguration."
10810
10861
  },
10811
10862
  [SERVICE_CATEGORIES.CONTAINER]: {
10812
10863
  id: SERVICE_CATEGORIES.CONTAINER,
10813
10864
  name: "Container Systems",
10814
- description: "Docker and Kubernetes security testing.",
10815
- promptPath: join11(__dirname, "container/prompt.md")
10865
+ description: "Docker and Kubernetes security testing."
10816
10866
  },
10817
10867
  [SERVICE_CATEGORIES.API]: {
10818
10868
  id: SERVICE_CATEGORIES.API,
10819
10869
  name: "API Security",
10820
- description: "REST, GraphQL, and SOAP API security testing.",
10821
- promptPath: join11(__dirname, "api/prompt.md")
10870
+ description: "REST, GraphQL, and SOAP API security testing."
10822
10871
  },
10823
10872
  [SERVICE_CATEGORIES.WIRELESS]: {
10824
10873
  id: SERVICE_CATEGORIES.WIRELESS,
10825
10874
  name: "Wireless Networks",
10826
- description: "WiFi and Bluetooth security testing.",
10827
- promptPath: join11(__dirname, "wireless/prompt.md")
10875
+ description: "WiFi and Bluetooth security testing."
10828
10876
  },
10829
10877
  [SERVICE_CATEGORIES.ICS]: {
10830
10878
  id: SERVICE_CATEGORIES.ICS,
10831
10879
  name: "Industrial Systems",
10832
- description: "Critical infrastructure - Modbus, DNP3, ENIP.",
10833
- promptPath: join11(__dirname, "ics/prompt.md")
10880
+ description: "Critical infrastructure - Modbus, DNP3, ENIP."
10834
10881
  }
10835
10882
  };
10836
10883
  function loadAllDomainTools(state, events) {
@@ -11503,20 +11550,38 @@ function classifyError(error) {
11503
11550
  }
11504
11551
 
11505
11552
  // src/engine/llm-client/retry-handler.ts
11553
+ var retryUtils = {
11554
+ sleep: (ms, signal) => {
11555
+ return new Promise((resolve, reject) => {
11556
+ if (signal?.aborted) {
11557
+ reject(new DOMException("Aborted", "AbortError"));
11558
+ return;
11559
+ }
11560
+ const timer = setTimeout(resolve, ms);
11561
+ signal?.addEventListener("abort", () => {
11562
+ clearTimeout(timer);
11563
+ reject(new DOMException("Aborted", "AbortError"));
11564
+ }, { once: true });
11565
+ });
11566
+ }
11567
+ };
11506
11568
  async function executeWithRetry(operation, callbacks) {
11507
11569
  const signal = callbacks?.abortSignal;
11508
11570
  const maxAttempts = RETRY_CONFIG.autoRetryMaxAttempts;
11509
- for (let attempt = 0; attempt < maxAttempts; attempt++) {
11571
+ const delayMs = RETRY_CONFIG.autoRetryDelayMs;
11572
+ let attempt = 0;
11573
+ while (attempt < maxAttempts) {
11510
11574
  if (signal?.aborted) throwAbort();
11511
11575
  try {
11512
11576
  return await operation();
11513
11577
  } catch (error) {
11514
11578
  if (isAbortError(error, signal)) throwAbort();
11515
11579
  const errorInfo = classifyError(error);
11516
- if (errorInfo.type === LLM_ERROR_TYPES.RATE_LIMIT && attempt < maxAttempts - 1) {
11517
- const delayMs = RETRY_CONFIG.autoRetryDelayMs * Math.pow(2, attempt);
11518
- callbacks?.onRetry?.(attempt + 1, maxAttempts, delayMs, errorInfo.message);
11519
- await sleep(delayMs, signal);
11580
+ const isRetryableType = errorInfo.type === LLM_ERROR_TYPES.RATE_LIMIT || errorInfo.type === LLM_ERROR_TYPES.NETWORK_ERROR || errorInfo.type === LLM_ERROR_TYPES.TIMEOUT;
11581
+ if (isRetryableType) {
11582
+ attempt++;
11583
+ callbacks?.onRetry?.(attempt, maxAttempts, delayMs, errorInfo.message);
11584
+ await retryUtils.sleep(delayMs, signal);
11520
11585
  continue;
11521
11586
  }
11522
11587
  throw new LLMError(errorInfo);
@@ -11524,19 +11589,6 @@ async function executeWithRetry(operation, callbacks) {
11524
11589
  }
11525
11590
  throw new LLMError({ type: LLM_ERROR_TYPES.UNKNOWN, message: "Max retry exceeded", isRetryable: false });
11526
11591
  }
11527
- function sleep(ms, signal) {
11528
- return new Promise((resolve, reject) => {
11529
- if (signal?.aborted) {
11530
- reject(new DOMException("Aborted", "AbortError"));
11531
- return;
11532
- }
11533
- const timer = setTimeout(resolve, ms);
11534
- signal?.addEventListener("abort", () => {
11535
- clearTimeout(timer);
11536
- reject(new DOMException("Aborted", "AbortError"));
11537
- }, { once: true });
11538
- });
11539
- }
11540
11592
  function isAbortError(error, signal) {
11541
11593
  return error?.name === "AbortError" || !!signal?.aborted;
11542
11594
  }
@@ -12518,6 +12570,18 @@ function extractHypothesizedReason(failureLine) {
12518
12570
  }
12519
12571
 
12520
12572
  // src/shared/utils/context-digest/formatters.ts
12573
+ function formatContextForLLM(memo14) {
12574
+ const compact = {};
12575
+ if (memo14.keyFindings.length > 0) compact.findings = memo14.keyFindings;
12576
+ if (memo14.credentials.length > 0) compact.credentials = memo14.credentials;
12577
+ if (memo14.attackVectors.length > 0) compact.attackVectors = memo14.attackVectors;
12578
+ if (memo14.failures.length > 0) compact.failures = memo14.failures;
12579
+ if (memo14.suspicions.length > 0) compact.suspicions = memo14.suspicions;
12580
+ if (memo14.nextSteps.length > 0) compact.nextSteps = memo14.nextSteps;
12581
+ compact.attackValue = memo14.attackValue;
12582
+ if (memo14.reflection) compact.reflection = memo14.reflection;
12583
+ return JSON.stringify(compact);
12584
+ }
12521
12585
  function formatAnalystDigest(digest, filePath, originalChars) {
12522
12586
  return [
12523
12587
  digest,
@@ -12555,6 +12619,7 @@ async function digestToolOutput(output, toolName, toolInput, analystFn) {
12555
12619
  if (originalLength < PASSTHROUGH_THRESHOLD) {
12556
12620
  return {
12557
12621
  digestedOutput: output,
12622
+ contextForLLM: output,
12558
12623
  fullOutputPath: null,
12559
12624
  analystUsed: false,
12560
12625
  memo: null,
@@ -12574,8 +12639,10 @@ async function digestToolOutput(output, toolName, toolInput, analystFn) {
12574
12639
  const rawAnalystResponse = await analystFn(preprocessed, context);
12575
12640
  const memo14 = parseAnalystMemo(rawAnalystResponse);
12576
12641
  const formatted = formatAnalystDigest(rawAnalystResponse, savedOutputPath, originalLength);
12642
+ const contextForLLM = formatContextForLLM(memo14);
12577
12643
  return {
12578
12644
  digestedOutput: formatted,
12645
+ contextForLLM,
12579
12646
  fullOutputPath: savedOutputPath,
12580
12647
  analystUsed: true,
12581
12648
  memo: memo14,
@@ -12593,6 +12660,7 @@ async function digestToolOutput(output, toolName, toolInput, analystFn) {
12593
12660
  const fallback = formatFallbackDigest(preprocessed, savedOutputPath, originalLength);
12594
12661
  return {
12595
12662
  digestedOutput: fallback,
12663
+ contextForLLM: fallback,
12596
12664
  fullOutputPath: savedOutputPath,
12597
12665
  analystUsed: false,
12598
12666
  memo: null,
@@ -12696,7 +12764,7 @@ ${text}
12696
12764
 
12697
12765
  // src/agents/tool-executor/result-handler/digest.ts
12698
12766
  async function digestAndEmit(call, outputText, result, toolStartTime, llm, events) {
12699
- let digestedOutputForLLM = outputText;
12767
+ let contextOutputForLLM = outputText;
12700
12768
  let digestResult = null;
12701
12769
  try {
12702
12770
  const llmDigestFn = createLLMDigestFn(llm);
@@ -12706,12 +12774,12 @@ async function digestAndEmit(call, outputText, result, toolStartTime, llm, event
12706
12774
  JSON.stringify(call.input).slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY),
12707
12775
  llmDigestFn
12708
12776
  );
12709
- digestedOutputForLLM = digestResult.digestedOutput;
12777
+ contextOutputForLLM = digestResult.contextForLLM;
12710
12778
  } catch {
12711
- if (digestedOutputForLLM.length > AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH) {
12712
- const truncated = digestedOutputForLLM.slice(0, AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH);
12713
- const remaining = digestedOutputForLLM.length - AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH;
12714
- digestedOutputForLLM = `${truncated}
12779
+ if (contextOutputForLLM.length > AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH) {
12780
+ const truncated = contextOutputForLLM.slice(0, AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH);
12781
+ const remaining = contextOutputForLLM.length - AGENT_LIMITS.MAX_TOOL_OUTPUT_LENGTH;
12782
+ contextOutputForLLM = `${truncated}
12715
12783
 
12716
12784
  ... [TRUNCATED ${remaining} chars] ...`;
12717
12785
  }
@@ -12720,16 +12788,16 @@ async function digestAndEmit(call, outputText, result, toolStartTime, llm, event
12720
12788
  const tuiOutput = digestResult?.digestedOutput ? `${digestResult.digestedOutput}${outputFilePath ? `
12721
12789
  \u{1F4C4} Full output: ${outputFilePath}` : ""}` : outputText.slice(0, DISPLAY_LIMITS.OUTPUT_SUMMARY);
12722
12790
  emitToolResult(events, call.name, result.success, tuiOutput, result.error, Date.now() - toolStartTime);
12723
- return { digestedOutputForLLM, digestResult };
12791
+ return { contextOutputForLLM, digestResult };
12724
12792
  }
12725
12793
 
12726
12794
  // src/agents/tool-executor/result-handler/journal.ts
12727
- function recordJournalMemo(call, result, digestedOutputForLLM, digestResult, turnState, state) {
12795
+ function recordJournalMemo(call, result, contextOutputForLLM, digestResult, turnState, state) {
12728
12796
  turnState.toolJournal.push({
12729
12797
  name: call.name,
12730
12798
  inputSummary: JSON.stringify(call.input),
12731
12799
  success: result.success,
12732
- analystSummary: digestResult?.memo ? digestResult.memo.keyFindings.join("; ") || "No key findings" : digestedOutputForLLM,
12800
+ analystSummary: digestResult?.memo ? digestResult.memo.keyFindings.join("; ") || "No key findings" : contextOutputForLLM,
12733
12801
  outputFile: digestResult?.fullOutputPath ?? null
12734
12802
  });
12735
12803
  if (digestResult?.memo) {
@@ -12843,7 +12911,7 @@ async function executeSingle(call, deps, progress) {
12843
12911
  let outputText = result.output ?? "";
12844
12912
  scanForFlags(outputText, deps.state, deps.events);
12845
12913
  outputText = handleToolResult(result, call, outputText, progress);
12846
- const { digestedOutputForLLM, digestResult } = await digestAndEmit(
12914
+ const { contextOutputForLLM, digestResult } = await digestAndEmit(
12847
12915
  call,
12848
12916
  outputText,
12849
12917
  result,
@@ -12851,8 +12919,8 @@ async function executeSingle(call, deps, progress) {
12851
12919
  deps.llm,
12852
12920
  deps.events
12853
12921
  );
12854
- recordJournalMemo(call, result, digestedOutputForLLM, digestResult, deps.turnState, deps.state);
12855
- return { toolCallId: call.id, output: digestedOutputForLLM, error: result.error };
12922
+ recordJournalMemo(call, result, contextOutputForLLM, digestResult, deps.turnState, deps.state);
12923
+ return { toolCallId: call.id, output: contextOutputForLLM, error: result.error };
12856
12924
  } catch (error) {
12857
12925
  const errorMsg = String(error);
12858
12926
  const enrichedError = enrichToolErrorContext({
@@ -13171,17 +13239,17 @@ var PHASE_TECHNIQUE_MAP = {
13171
13239
 
13172
13240
  // src/agents/prompt-builder/prompt-loader.ts
13173
13241
  import { readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
13174
- import { join as join12, dirname as dirname4 } from "path";
13175
- import { fileURLToPath as fileURLToPath2 } from "url";
13176
- var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
13177
- var PROMPTS_DIR = join12(__dirname2, "../prompts");
13178
- var TECHNIQUES_DIR = join12(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
13242
+ import { join as join11, dirname as dirname3 } from "path";
13243
+ import { fileURLToPath } from "url";
13244
+ var __dirname = dirname3(fileURLToPath(import.meta.url));
13245
+ var PROMPTS_DIR = join11(__dirname, "../prompts");
13246
+ var TECHNIQUES_DIR = join11(PROMPTS_DIR, PROMPT_PATHS.TECHNIQUES_DIR);
13179
13247
  function loadPromptFile(filename) {
13180
- const path2 = join12(PROMPTS_DIR, filename);
13248
+ const path2 = join11(PROMPTS_DIR, filename);
13181
13249
  return existsSync11(path2) ? readFileSync8(path2, PROMPT_CONFIG.ENCODING) : "";
13182
13250
  }
13183
13251
  function loadTechniqueFile(techniqueName) {
13184
- const filePath = join12(TECHNIQUES_DIR, `${techniqueName}.md`);
13252
+ const filePath = join11(TECHNIQUES_DIR, `${techniqueName}.md`);
13185
13253
  try {
13186
13254
  if (!existsSync11(filePath)) return "";
13187
13255
  return readFileSync8(filePath, PROMPT_CONFIG.ENCODING);
@@ -13330,11 +13398,11 @@ ${lines.join("\n")}
13330
13398
 
13331
13399
  // src/shared/utils/journal/reader.ts
13332
13400
  import { readFileSync as readFileSync9, existsSync as existsSync13 } from "fs";
13333
- import { join as join14 } from "path";
13401
+ import { join as join13 } from "path";
13334
13402
 
13335
13403
  // src/shared/utils/journal/rotation.ts
13336
13404
  import { existsSync as existsSync12, readdirSync as readdirSync3, statSync as statSync4, rmSync as rmSync2 } from "fs";
13337
- import { join as join13 } from "path";
13405
+ import { join as join12 } from "path";
13338
13406
  function parseTurnNumbers(turnsDir) {
13339
13407
  if (!existsSync12(turnsDir)) return [];
13340
13408
  return readdirSync3(turnsDir).filter((e) => e.startsWith(TURN_FOLDER_PREFIX) && /^\d+$/.test(e.slice(TURN_FOLDER_PREFIX.length))).map((e) => Number(e.slice(TURN_FOLDER_PREFIX.length)));
@@ -13343,12 +13411,12 @@ function rotateTurnRecords() {
13343
13411
  try {
13344
13412
  const turnsDir = WORKSPACE.TURNS;
13345
13413
  if (!existsSync12(turnsDir)) return;
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)));
13414
+ const turnDirs = parseTurnNumbers(turnsDir).map((n) => `${TURN_FOLDER_PREFIX}${n}`).filter((e) => statSync4(join12(turnsDir, e)).isDirectory()).sort((a, b) => Number(a.slice(TURN_FOLDER_PREFIX.length)) - Number(b.slice(TURN_FOLDER_PREFIX.length)));
13347
13415
  if (turnDirs.length > MEMORY_LIMITS.MAX_TURN_ENTRIES) {
13348
13416
  const dirsToDel = turnDirs.slice(0, turnDirs.length - MEMORY_LIMITS.MAX_TURN_ENTRIES);
13349
13417
  for (const dir of dirsToDel) {
13350
13418
  try {
13351
- rmSync2(join13(turnsDir, dir), { recursive: true, force: true });
13419
+ rmSync2(join12(turnsDir, dir), { recursive: true, force: true });
13352
13420
  } catch {
13353
13421
  }
13354
13422
  }
@@ -13367,7 +13435,7 @@ function readJournalSummary() {
13367
13435
  const turnsDir = WORKSPACE.TURNS;
13368
13436
  const turnDirs = parseTurnNumbers(turnsDir).sort((a, b) => b - a);
13369
13437
  for (const turn of turnDirs) {
13370
- const summaryPath = join14(WORKSPACE.turnPath(turn), TURN_FILES.SUMMARY);
13438
+ const summaryPath = join13(WORKSPACE.turnPath(turn), TURN_FILES.SUMMARY);
13371
13439
  if (existsSync13(summaryPath)) {
13372
13440
  return readFileSync9(summaryPath, "utf-8");
13373
13441
  }
@@ -13384,7 +13452,7 @@ function getRecentEntries(count = MEMORY_LIMITS.MAX_TURN_ENTRIES) {
13384
13452
  const entries = [];
13385
13453
  for (const turn of turnDirs) {
13386
13454
  try {
13387
- const filePath = join14(WORKSPACE.turnPath(turn), TURN_FILES.STRUCTURED);
13455
+ const filePath = join13(WORKSPACE.turnPath(turn), TURN_FILES.STRUCTURED);
13388
13456
  if (existsSync13(filePath)) {
13389
13457
  const raw = readFileSync9(filePath, "utf-8");
13390
13458
  entries.push(JSON.parse(raw));
@@ -13410,7 +13478,7 @@ function getNextTurnNumber() {
13410
13478
 
13411
13479
  // src/shared/utils/journal/summary.ts
13412
13480
  import { writeFileSync as writeFileSync9 } from "fs";
13413
- import { join as join15 } from "path";
13481
+ import { join as join14 } from "path";
13414
13482
 
13415
13483
  // src/shared/utils/journal/summary-collector.ts
13416
13484
  function collectSummaryBuckets(entries) {
@@ -13515,7 +13583,7 @@ function regenerateJournalSummary() {
13515
13583
  const turnDir = WORKSPACE.turnPath(latestTurn);
13516
13584
  ensureDirExists(turnDir);
13517
13585
  const summary = buildSummaryFromEntries(entries);
13518
- const summaryPath = join15(turnDir, TURN_FILES.SUMMARY);
13586
+ const summaryPath = join14(turnDir, TURN_FILES.SUMMARY);
13519
13587
  writeFileSync9(summaryPath, summary, "utf-8");
13520
13588
  debugLog("general", "Journal summary regenerated", {
13521
13589
  entries: entries.length,
@@ -14109,10 +14177,10 @@ function formatForPrompt(directive, isStale = false) {
14109
14177
 
14110
14178
  // src/agents/strategist/prompt-loader.ts
14111
14179
  import { readFileSync as readFileSync10, existsSync as existsSync14 } from "fs";
14112
- import { join as join16, dirname as dirname5 } from "path";
14113
- import { fileURLToPath as fileURLToPath3 } from "url";
14114
- var __dirname3 = dirname5(fileURLToPath3(import.meta.url));
14115
- var STRATEGIST_PROMPT_PATH = join16(__dirname3, "../prompts", "strategist-system.md");
14180
+ import { join as join15, dirname as dirname4 } from "path";
14181
+ import { fileURLToPath as fileURLToPath2 } from "url";
14182
+ var __dirname2 = dirname4(fileURLToPath2(import.meta.url));
14183
+ var STRATEGIST_PROMPT_PATH = join15(__dirname2, "../prompts", "strategist-system.md");
14116
14184
  function loadSystemPrompt() {
14117
14185
  try {
14118
14186
  if (existsSync14(STRATEGIST_PROMPT_PATH)) {
@@ -14513,7 +14581,7 @@ async function processReflection(toolJournal, memo14, phase, reflector) {
14513
14581
 
14514
14582
  // src/agents/main-agent/turn-recorder.ts
14515
14583
  import { writeFileSync as writeFileSync10, existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
14516
- import { join as join17 } from "path";
14584
+ import { join as join16 } from "path";
14517
14585
  async function recordTurn(context) {
14518
14586
  const { turnCounter, phase, toolJournal, memo: memo14, reflections, summaryRegenerator } = context;
14519
14587
  if (toolJournal.length === 0) {
@@ -14551,8 +14619,8 @@ async function createTurnArchive(turnCounter, ctx) {
14551
14619
  memo: memo14,
14552
14620
  reflection: entry.reflection
14553
14621
  });
14554
- writeFileSync10(join17(turnDir, TURN_FILES.RECORD), turnContent, "utf-8");
14555
- writeFileSync10(join17(turnDir, TURN_FILES.STRUCTURED), JSON.stringify(entry, null, 2), "utf-8");
14622
+ writeFileSync10(join16(turnDir, TURN_FILES.RECORD), turnContent, "utf-8");
14623
+ writeFileSync10(join16(turnDir, TURN_FILES.STRUCTURED), JSON.stringify(entry, null, 2), "utf-8");
14556
14624
  writeAnalystFile(turnDir, memo14);
14557
14625
  } catch {
14558
14626
  }
@@ -14566,7 +14634,7 @@ function writeAnalystFile(turnDir, memo14) {
14566
14634
  if (memo14.suspicions.length > 0) memoLines.push("## Suspicious", ...memo14.suspicions.map((s) => `- ${s}`), "");
14567
14635
  if (memo14.nextSteps.length > 0) memoLines.push("## Next Steps", ...memo14.nextSteps.map((n) => `- ${n}`), "");
14568
14636
  if (memoLines.length > 0) {
14569
- writeFileSync10(join17(turnDir, TURN_FILES.ANALYST), memoLines.join("\n"), "utf-8");
14637
+ writeFileSync10(join16(turnDir, TURN_FILES.ANALYST), memoLines.join("\n"), "utf-8");
14570
14638
  flowLog(
14571
14639
  "\u2462Analyst",
14572
14640
  "\u2192",
@@ -14579,11 +14647,11 @@ async function regenerateSummary(turnCounter, summaryRegenerator, ctx) {
14579
14647
  const { entry, journalTools, memo: memo14, phase } = ctx;
14580
14648
  try {
14581
14649
  const turnDir = WORKSPACE.turnPath(turnCounter);
14582
- const summaryPath = join17(turnDir, TURN_FILES.SUMMARY);
14650
+ const summaryPath = join16(turnDir, TURN_FILES.SUMMARY);
14583
14651
  const prevTurn = turnCounter - 1;
14584
14652
  let existingSummary = null;
14585
14653
  if (prevTurn >= 1) {
14586
- const prevSummaryPath = join17(WORKSPACE.turnPath(prevTurn), TURN_FILES.SUMMARY);
14654
+ const prevSummaryPath = join16(WORKSPACE.turnPath(prevTurn), TURN_FILES.SUMMARY);
14587
14655
  if (existsSync15(prevSummaryPath)) {
14588
14656
  existingSummary = readFileSync11(prevSummaryPath, "utf-8");
14589
14657
  }
@@ -15198,6 +15266,10 @@ function createLifecycleHandlers(agent, state, _reasoningBufferRef) {
15198
15266
  todo: s.getTodo().length
15199
15267
  });
15200
15268
  };
15269
+ const onNotification = (e) => {
15270
+ const prefix = e.data.level === "error" ? "\u274C" : e.data.level === "warning" ? "\u26A0\uFE0F" : e.data.level === "success" ? "\u2705" : "\u{1F514}";
15271
+ addMessage("system", `${prefix} [${e.data.title}] ${e.data.message}`);
15272
+ };
15201
15273
  return {
15202
15274
  onComplete,
15203
15275
  onRetry,
@@ -15206,7 +15278,8 @@ function createLifecycleHandlers(agent, state, _reasoningBufferRef) {
15206
15278
  onAIResponse,
15207
15279
  onUsageUpdate,
15208
15280
  onFlagFound,
15209
- onPhaseChange
15281
+ onPhaseChange,
15282
+ onNotification
15210
15283
  };
15211
15284
  }
15212
15285
 
@@ -15276,9 +15349,26 @@ function setupInputHandlers(setInputRequest, addMessage) {
15276
15349
  }
15277
15350
 
15278
15351
  // src/platform/tui/hooks/useAgentEvents/handlers/command.ts
15279
- function setupCommandHandlers(addMessage) {
15352
+ function setupCommandHandlers(addMessage, setCurrentStatus) {
15353
+ let lastStatusBase = "";
15280
15354
  setCommandEventEmitter((event) => {
15281
- if (event.type === COMMAND_EVENT_TYPES.COMMAND_START || event.type === COMMAND_EVENT_TYPES.COMMAND_SUCCESS) {
15355
+ if (event.type === COMMAND_EVENT_TYPES.COMMAND_START) {
15356
+ lastStatusBase = event.message;
15357
+ return;
15358
+ }
15359
+ if (event.type === COMMAND_EVENT_TYPES.COMMAND_SUCCESS) {
15360
+ return;
15361
+ }
15362
+ if (event.type === COMMAND_EVENT_TYPES.COMMAND_STDOUT) {
15363
+ if (lastStatusBase) {
15364
+ const cleanLine = event.message.replace(/\s+/g, " ").slice(0, 100);
15365
+ setCurrentStatus(`${lastStatusBase}
15366
+ [live] ${cleanLine}`);
15367
+ }
15368
+ return;
15369
+ }
15370
+ if (event.type === COMMAND_EVENT_TYPES.PROCESS_NOTIFICATION) {
15371
+ addMessage("system", `\u{1F514} [Background Alert] ${event.message}`);
15282
15372
  return;
15283
15373
  }
15284
15374
  const icon = getCommandEventIcon(event.type);
@@ -15325,7 +15415,7 @@ var useAgentEvents = (agent, eventsRef, state) => {
15325
15415
  }, reasoningBufferRef);
15326
15416
  const reasoningHandlers = createReasoningHandlers({ addMessage, setCurrentStatus }, reasoningBufferRef);
15327
15417
  const cleanupInput = setupInputHandlers(setInputRequest, addMessage);
15328
- const cleanupCommand = setupCommandHandlers(addMessage);
15418
+ const cleanupCommand = setupCommandHandlers(addMessage, setCurrentStatus);
15329
15419
  const updateStats = () => {
15330
15420
  const s = agent.getState();
15331
15421
  setStats({
@@ -15344,6 +15434,7 @@ var useAgentEvents = (agent, eventsRef, state) => {
15344
15434
  events.on(EVENT_TYPES.USAGE_UPDATE, lifecycleHandlers.onUsageUpdate);
15345
15435
  events.on(EVENT_TYPES.FLAG_FOUND, lifecycleHandlers.onFlagFound);
15346
15436
  events.on(EVENT_TYPES.PHASE_CHANGE, lifecycleHandlers.onPhaseChange);
15437
+ events.on(EVENT_TYPES.NOTIFICATION, lifecycleHandlers.onNotification);
15347
15438
  events.on(EVENT_TYPES.STATE_CHANGE, updateStats);
15348
15439
  events.on(EVENT_TYPES.START, updateStats);
15349
15440
  events.on(EVENT_TYPES.REASONING_START, reasoningHandlers.onStart);
@@ -16003,7 +16094,7 @@ var useCommands = (props) => {
16003
16094
  };
16004
16095
 
16005
16096
  // src/platform/tui/hooks/useKeyboardShortcuts.ts
16006
- import { useCallback as useCallback7, useEffect as useEffect4 } from "react";
16097
+ import { useCallback as useCallback6, useEffect as useEffect4 } from "react";
16007
16098
  import { useInput } from "ink";
16008
16099
 
16009
16100
  // src/platform/tui/hooks/keyboard/useDoubleTap.ts
@@ -16093,17 +16184,6 @@ var useEscHandler = ({
16093
16184
  return { handleEsc };
16094
16185
  };
16095
16186
 
16096
- // src/platform/tui/hooks/keyboard/useScrollHandler.ts
16097
- import { useRef as useRef5, useCallback as useCallback6 } from "react";
16098
- var useScrollHandler = ({ onScroll }) => {
16099
- const onScrollRef = useRef5(onScroll);
16100
- onScrollRef.current = onScroll;
16101
- const handleScroll = useCallback6((delta) => {
16102
- onScrollRef.current?.(delta);
16103
- }, []);
16104
- return { handleScroll };
16105
- };
16106
-
16107
16187
  // src/platform/tui/hooks/useKeyboardShortcuts.ts
16108
16188
  var DOUBLE_TAP_WINDOW = 3e3;
16109
16189
  var useKeyboardShortcuts = ({
@@ -16115,8 +16195,7 @@ var useKeyboardShortcuts = ({
16115
16195
  inputRequestRef,
16116
16196
  isModalOpenRef,
16117
16197
  inputRef,
16118
- clearInput,
16119
- onScroll
16198
+ clearInput
16120
16199
  }) => {
16121
16200
  const { handleCtrlC } = useExitHandler({
16122
16201
  handleExit,
@@ -16135,18 +16214,11 @@ var useKeyboardShortcuts = ({
16135
16214
  inputRef,
16136
16215
  windowMs: DOUBLE_TAP_WINDOW
16137
16216
  });
16138
- const { handleScroll } = useScrollHandler({ onScroll });
16139
- useInput(useCallback7((ch, key) => {
16217
+ useInput(useCallback6((ch, key) => {
16140
16218
  if (isModalOpenRef.current) return;
16141
16219
  if (key.escape) handleEsc();
16142
16220
  if (key.ctrl && ch === "c") handleCtrlC();
16143
- if (key.pageUp) {
16144
- handleScroll(-10);
16145
- }
16146
- if (key.pageDown) {
16147
- handleScroll(10);
16148
- }
16149
- }, [handleEsc, handleCtrlC, handleScroll, isModalOpenRef]));
16221
+ }, [handleEsc, handleCtrlC, isModalOpenRef]));
16150
16222
  useEffect4(() => {
16151
16223
  const onSignal = () => handleCtrlC();
16152
16224
  process.on("SIGINT", onSignal);
@@ -16177,33 +16249,32 @@ var AnimationProvider = ({ children }) => {
16177
16249
  var useAnimationTick = () => useContext(AnimationContext);
16178
16250
 
16179
16251
  // src/platform/tui/components/MessageList.tsx
16180
- import { memo as memo7, useState as useState4, useCallback as useCallback8, useRef as useRef6 } from "react";
16181
- import { Box as Box8, Text as Text8, useInput as useInput2 } from "ink";
16252
+ import { memo as memo7 } from "react";
16253
+ import { Box as Box8, Static } from "ink";
16182
16254
 
16183
16255
  // src/platform/tui/components/messages/ThinkingBlock.tsx
16184
16256
  import { memo } from "react";
16185
16257
  import { Box, Text } from "ink";
16186
16258
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
16187
16259
  var THINKING_PREVIEW_LINES = 3;
16188
- var ThinkingBlock = memo(({ msg, isExpanded }) => {
16260
+ var ThinkingBlock = memo(({ msg }) => {
16189
16261
  const lines = msg.content.split("\n");
16190
16262
  const charCount = msg.content.length;
16191
16263
  const estTokens = Math.round(charCount / LLM_LIMITS.charsPerTokenEstimate);
16192
16264
  const hiddenCount = lines.length - THINKING_PREVIEW_LINES;
16193
- const visibleLines = isExpanded ? lines : lines.slice(0, THINKING_PREVIEW_LINES);
16265
+ const visibleLines = lines.slice(0, THINKING_PREVIEW_LINES);
16194
16266
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [
16195
16267
  /* @__PURE__ */ jsxs(Box, { children: [
16196
16268
  /* @__PURE__ */ jsx2(Text, { color: THEME.dimGray, children: " \u25C6 " }),
16197
16269
  /* @__PURE__ */ jsx2(Text, { color: THEME.gray, children: "Reasoning" }),
16198
16270
  /* @__PURE__ */ jsx2(Text, { color: THEME.dimGray, children: ` (~${estTokens} tokens)` }),
16199
- !isExpanded && hiddenCount > 0 && /* @__PURE__ */ jsx2(Text, { color: THEME.dimGray, children: ` [+${hiddenCount} lines \xB7 R]` }),
16200
- isExpanded && /* @__PURE__ */ jsx2(Text, { color: THEME.dimGray, children: " [R to collapse]" })
16271
+ hiddenCount > 0 && /* @__PURE__ */ jsx2(Text, { color: THEME.dimGray, children: ` [+${hiddenCount} lines]` })
16201
16272
  ] }),
16202
16273
  visibleLines.map((line, i) => /* @__PURE__ */ jsxs(Box, { children: [
16203
16274
  /* @__PURE__ */ jsx2(Text, { dimColor: true, children: " \u2502 " }),
16204
16275
  /* @__PURE__ */ jsx2(Text, { dimColor: true, children: line })
16205
16276
  ] }, i)),
16206
- !isExpanded && hiddenCount > 0 && /* @__PURE__ */ jsxs(Box, { children: [
16277
+ hiddenCount > 0 && /* @__PURE__ */ jsxs(Box, { children: [
16207
16278
  /* @__PURE__ */ jsx2(Text, { dimColor: true, children: " \u2502 " }),
16208
16279
  /* @__PURE__ */ jsx2(Text, { dimColor: true, children: "..." })
16209
16280
  ] })
@@ -16611,23 +16682,22 @@ function classifyResult(content) {
16611
16682
  if (content.startsWith("\u26A0") || content.startsWith("!")) return RESULT_KINDS.WARNING;
16612
16683
  return RESULT_KINDS.NEUTRAL;
16613
16684
  }
16614
- function computeResultDisplay(content, isExpanded) {
16685
+ function computeResultDisplay(content) {
16615
16686
  const kind = classifyResult(content);
16616
16687
  const isFailure = kind === RESULT_KINDS.FAILURE;
16617
16688
  const lines = content.split("\n");
16618
16689
  const firstLine = lines[0] || "";
16619
16690
  const restLines = lines.slice(1);
16620
16691
  const hiddenCount = restLines.length;
16621
- const effectiveExpanded = isFailure ? true : isExpanded;
16622
- const showCount = effectiveExpanded ? hiddenCount : Math.min(hiddenCount, RESULT_PREVIEW_LINES);
16623
- const visibleRest = restLines.slice(0, showCount);
16624
- const hasMore = !effectiveExpanded && hiddenCount > RESULT_PREVIEW_LINES;
16625
- return { firstLine, visibleRest, hasMore, effectiveExpanded };
16692
+ const previewLines = isFailure ? RESULT_PREVIEW_LINES * 2 : RESULT_PREVIEW_LINES;
16693
+ const visibleRest = restLines.slice(0, previewLines);
16694
+ const hasMore = hiddenCount > previewLines;
16695
+ return { firstLine, visibleRest, hasMore };
16626
16696
  }
16627
16697
 
16628
16698
  // src/platform/tui/components/messages/MessageRow.tsx
16629
16699
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
16630
- var MessageRow = memo4(({ msg, isExpanded }) => {
16700
+ var MessageRow = memo4(({ msg }) => {
16631
16701
  if (msg.type === "status") {
16632
16702
  const statusData = parseStatusContent(msg.content);
16633
16703
  if (statusData) {
@@ -16646,7 +16716,7 @@ var MessageRow = memo4(({ msg, isExpanded }) => {
16646
16716
  ] }, msg.id);
16647
16717
  }
16648
16718
  if (msg.type === "thinking") {
16649
- return /* @__PURE__ */ jsx6(ThinkingBlock, { msg, isExpanded });
16719
+ return /* @__PURE__ */ jsx6(ThinkingBlock, { msg });
16650
16720
  }
16651
16721
  if (msg.type === "tool") {
16652
16722
  return /* @__PURE__ */ jsx6(Box5, { children: /* @__PURE__ */ jsx6(ToolCard, { content: msg.content }) }, msg.id);
@@ -16654,15 +16724,13 @@ var MessageRow = memo4(({ msg, isExpanded }) => {
16654
16724
  if (msg.type === "result") {
16655
16725
  const kind = classifyResult(msg.content);
16656
16726
  const color = kind === "success" ? THEME.primary : kind === "failure" ? THEME.red : kind === "warning" ? THEME.yellow : THEME.gray;
16657
- const { firstLine, visibleRest, hasMore, effectiveExpanded } = computeResultDisplay(msg.content, isExpanded);
16727
+ const { firstLine, visibleRest, hasMore } = computeResultDisplay(msg.content);
16658
16728
  const hiddenCount = msg.content.split("\n").length - 1;
16659
- const isFailure = kind === "failure";
16660
16729
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
16661
16730
  /* @__PURE__ */ jsxs5(Box5, { children: [
16662
16731
  /* @__PURE__ */ jsx6(Text5, { color: THEME.dimGray, children: " \u2514 " }),
16663
16732
  /* @__PURE__ */ jsx6(Text5, { color, children: firstLine }),
16664
- hasMore && /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.dimGray, children: ` [+${hiddenCount - RESULT_PREVIEW_LINES} \xB7 T]` }),
16665
- effectiveExpanded && hiddenCount > RESULT_PREVIEW_LINES && !isFailure && /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.dimGray, children: " [T \u2193]" })
16733
+ hasMore && /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.dimGray, children: ` [+${hiddenCount - RESULT_PREVIEW_LINES}]` })
16666
16734
  ] }),
16667
16735
  visibleRest.map((line, i) => /* @__PURE__ */ jsxs5(Box5, { children: [
16668
16736
  /* @__PURE__ */ jsx6(Text5, { color: THEME.dimGray, children: " " }),
@@ -16677,18 +16745,11 @@ var MessageRow = memo4(({ msg, isExpanded }) => {
16677
16745
  const eLines = msg.content.split("\n");
16678
16746
  const eFirst = eLines[0] || msg.content;
16679
16747
  const eHidden = eLines.length - 1;
16680
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
16681
- /* @__PURE__ */ jsxs5(Box5, { children: [
16682
- /* @__PURE__ */ jsx6(Text5, { color: THEME.red, children: " \u2717 " }),
16683
- /* @__PURE__ */ jsx6(Text5, { color: THEME.red, children: eFirst }),
16684
- !isExpanded && eHidden > 0 && /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.dimGray, children: ` [+${eHidden} \xB7 E]` }),
16685
- isExpanded && eHidden > 0 && /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.dimGray, children: " [E \u2191]" })
16686
- ] }),
16687
- isExpanded && eLines.slice(1).map((line, i) => /* @__PURE__ */ jsxs5(Box5, { children: [
16688
- /* @__PURE__ */ jsx6(Text5, { color: THEME.dimGray, children: " " }),
16689
- /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.red, children: line })
16690
- ] }, i))
16691
- ] }, msg.id);
16748
+ return /* @__PURE__ */ jsx6(Box5, { flexDirection: "column", children: /* @__PURE__ */ jsxs5(Box5, { children: [
16749
+ /* @__PURE__ */ jsx6(Text5, { color: THEME.red, children: " \u2717 " }),
16750
+ /* @__PURE__ */ jsx6(Text5, { color: THEME.red, children: eFirst }),
16751
+ eHidden > 0 && /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.dimGray, children: ` [+${eHidden}]` })
16752
+ ] }) }, msg.id);
16692
16753
  }
16693
16754
  if (msg.type === "user") {
16694
16755
  return /* @__PURE__ */ jsxs5(Box5, { children: [
@@ -16771,72 +16832,9 @@ var EmptyState = memo6(({ modelName, autoApproveMode, version }) => {
16771
16832
  ] });
16772
16833
  });
16773
16834
 
16774
- // src/platform/tui/components/messages/sliding-window.ts
16775
- var MAX_VISIBLE_MESSAGES = 200;
16776
- function computeSlidingWindow(messages, scrollOffset) {
16777
- const allVisible = messages.length > MAX_VISIBLE_MESSAGES ? messages.slice(-MAX_VISIBLE_MESSAGES) : messages;
16778
- const clampedOffset = Math.min(scrollOffset, Math.max(0, allVisible.length - 1));
16779
- const visibleMessages = clampedOffset === 0 ? allVisible : allVisible.slice(0, allVisible.length - clampedOffset);
16780
- const hiddenAbove = allVisible.length - visibleMessages.length;
16781
- return { visibleMessages, hiddenAbove, clampedOffset };
16782
- }
16783
-
16784
16835
  // src/platform/tui/components/MessageList.tsx
16785
- import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
16786
- var MessageList = memo7(({ messages, isModalOpen, modelName, autoApproveMode, version, scrollOffset = 0 }) => {
16787
- const [expandedIds, setExpandedIds] = useState4(/* @__PURE__ */ new Set());
16788
- const messagesRef = useRef6(messages);
16789
- messagesRef.current = messages;
16790
- const isModalOpenRef = useRef6(isModalOpen);
16791
- isModalOpenRef.current = isModalOpen;
16792
- useInput2(useCallback8((_ch, key) => {
16793
- if (isModalOpenRef.current || key.ctrl) return;
16794
- if (_ch === "r") {
16795
- const msgs = messagesRef.current;
16796
- for (let i = msgs.length - 1; i >= 0; i--) {
16797
- if (msgs[i].type === "thinking") {
16798
- const id = msgs[i].id;
16799
- setExpandedIds((prev) => {
16800
- const next = new Set(prev);
16801
- if (next.has(id)) next.delete(id);
16802
- else next.add(id);
16803
- return next;
16804
- });
16805
- break;
16806
- }
16807
- }
16808
- }
16809
- if (_ch === "e") {
16810
- const msgs = messagesRef.current;
16811
- for (let i = msgs.length - 1; i >= 0; i--) {
16812
- if (msgs[i].type === "error") {
16813
- const id = msgs[i].id;
16814
- setExpandedIds((prev) => {
16815
- const next = new Set(prev);
16816
- if (next.has(id)) next.delete(id);
16817
- else next.add(id);
16818
- return next;
16819
- });
16820
- break;
16821
- }
16822
- }
16823
- }
16824
- if (_ch === "t") {
16825
- const msgs = messagesRef.current;
16826
- for (let i = msgs.length - 1; i >= 0; i--) {
16827
- if (msgs[i].type === "result") {
16828
- const id = msgs[i].id;
16829
- setExpandedIds((prev) => {
16830
- const next = new Set(prev);
16831
- if (next.has(id)) next.delete(id);
16832
- else next.add(id);
16833
- return next;
16834
- });
16835
- break;
16836
- }
16837
- }
16838
- }
16839
- }, []));
16836
+ import { jsx as jsx9 } from "react/jsx-runtime";
16837
+ var MessageList = memo7(({ messages, modelName, autoApproveMode, version }) => {
16840
16838
  if (messages.length === 0) {
16841
16839
  return /* @__PURE__ */ jsx9(
16842
16840
  EmptyState,
@@ -16847,30 +16845,19 @@ var MessageList = memo7(({ messages, isModalOpen, modelName, autoApproveMode, ve
16847
16845
  }
16848
16846
  );
16849
16847
  }
16850
- const { visibleMessages, hiddenAbove, clampedOffset } = computeSlidingWindow(messages, scrollOffset);
16851
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
16852
- clampedOffset > 0 && /* @__PURE__ */ jsx9(Box8, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text8, { dimColor: true, color: THEME.dimGray, children: `\u2191 ${hiddenAbove} message${hiddenAbove !== 1 ? "s" : ""} above \xB7 PgDn to scroll down` }) }),
16853
- visibleMessages.map((msg) => /* @__PURE__ */ jsx9(
16854
- MessageRow,
16855
- {
16856
- msg,
16857
- isExpanded: expandedIds.has(msg.id)
16858
- },
16859
- msg.id
16860
- ))
16861
- ] });
16848
+ return /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsx9(Static, { items: messages, children: (msg) => /* @__PURE__ */ jsx9(MessageRow, { msg }, msg.id) }) });
16862
16849
  });
16863
16850
 
16864
16851
  // src/platform/tui/components/StatusDisplay.tsx
16865
16852
  import { memo as memo10 } from "react";
16866
- import { Box as Box11, Text as Text13 } from "ink";
16853
+ import { Box as Box11, Text as Text12 } from "ink";
16867
16854
 
16868
16855
  // src/platform/tui/hooks/useStatusTimer.ts
16869
- import { useState as useState5, useEffect as useEffect6, useRef as useRef7 } from "react";
16856
+ import { useState as useState4, useEffect as useEffect6, useRef as useRef5 } from "react";
16870
16857
  var useStatusTimer = (currentStatus, isProcessing) => {
16871
- const [statusElapsed, setStatusElapsed] = useState5(0);
16872
- const statusTimerRef = useRef7(null);
16873
- const statusStartRef = useRef7(Date.now());
16858
+ const [statusElapsed, setStatusElapsed] = useState4(0);
16859
+ const statusTimerRef = useRef5(null);
16860
+ const statusStartRef = useRef5(Date.now());
16874
16861
  useEffect6(() => {
16875
16862
  if (statusTimerRef.current) clearInterval(statusTimerRef.current);
16876
16863
  if (isProcessing && currentStatus) {
@@ -16891,11 +16878,11 @@ var useStatusTimer = (currentStatus, isProcessing) => {
16891
16878
  };
16892
16879
 
16893
16880
  // src/platform/tui/components/status/RetryView.tsx
16894
- import { Box as Box9, Text as Text10 } from "ink";
16881
+ import { Box as Box9, Text as Text9 } from "ink";
16895
16882
 
16896
16883
  // src/platform/tui/components/StarSpinner.tsx
16897
16884
  import { memo as memo8 } from "react";
16898
- import { Text as Text9 } from "ink";
16885
+ import { Text as Text8 } from "ink";
16899
16886
  import { jsx as jsx10 } from "react/jsx-runtime";
16900
16887
  var FRAMES = [
16901
16888
  "\xB7",
@@ -16916,31 +16903,31 @@ var FRAMES = [
16916
16903
  var StarSpinner = memo8(({ color }) => {
16917
16904
  const tick = useAnimationTick();
16918
16905
  const index = tick % FRAMES.length;
16919
- return /* @__PURE__ */ jsx10(Text9, { color: color || "yellow", children: FRAMES[index] });
16906
+ return /* @__PURE__ */ jsx10(Text8, { color: color || "yellow", children: FRAMES[index] });
16920
16907
  });
16921
16908
 
16922
16909
  // src/platform/tui/components/status/RetryView.tsx
16923
- import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
16910
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
16924
16911
  var RetryView = ({ retryState }) => {
16925
16912
  const truncateError = (err) => {
16926
16913
  return err.length > DISPLAY_LIMITS.RETRY_ERROR_PREVIEW ? err.substring(0, DISPLAY_LIMITS.RETRY_ERROR_TRUNCATED) + "..." : err;
16927
16914
  };
16928
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", height: 2, children: [
16929
- /* @__PURE__ */ jsxs8(Box9, { children: [
16930
- /* @__PURE__ */ jsx11(Text10, { color: THEME.yellow, wrap: "truncate", children: /* @__PURE__ */ jsx11(StarSpinner, { color: THEME.yellow }) }),
16931
- /* @__PURE__ */ jsxs8(Text10, { color: THEME.yellow, bold: true, wrap: "truncate", children: [
16915
+ return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", height: 2, children: [
16916
+ /* @__PURE__ */ jsxs7(Box9, { children: [
16917
+ /* @__PURE__ */ jsx11(Text9, { color: THEME.yellow, wrap: "truncate", children: /* @__PURE__ */ jsx11(StarSpinner, { color: THEME.yellow }) }),
16918
+ /* @__PURE__ */ jsxs7(Text9, { color: THEME.yellow, bold: true, wrap: "truncate", children: [
16932
16919
  " \u29F3 Retry #",
16933
16920
  retryState.attempt,
16934
16921
  "/",
16935
16922
  retryState.maxRetries
16936
16923
  ] }),
16937
- /* @__PURE__ */ jsxs8(Text10, { color: THEME.gray, wrap: "truncate", children: [
16924
+ /* @__PURE__ */ jsxs7(Text9, { color: THEME.gray, wrap: "truncate", children: [
16938
16925
  " \u2014 ",
16939
16926
  retryState.countdown,
16940
16927
  "s"
16941
16928
  ] })
16942
16929
  ] }),
16943
- /* @__PURE__ */ jsx11(Box9, { children: /* @__PURE__ */ jsxs8(Text10, { color: THEME.gray, wrap: "truncate", children: [
16930
+ /* @__PURE__ */ jsx11(Box9, { children: /* @__PURE__ */ jsxs7(Text9, { color: THEME.gray, wrap: "truncate", children: [
16944
16931
  " ",
16945
16932
  truncateError(retryState.error)
16946
16933
  ] }) })
@@ -16948,11 +16935,11 @@ var RetryView = ({ retryState }) => {
16948
16935
  };
16949
16936
 
16950
16937
  // src/platform/tui/components/status/ProcessingView.tsx
16951
- import { Box as Box10, Text as Text12 } from "ink";
16938
+ import { Box as Box10, Text as Text11 } from "ink";
16952
16939
 
16953
16940
  // src/platform/tui/components/ShimmerText.tsx
16954
16941
  import { memo as memo9 } from "react";
16955
- import { Text as Text11 } from "ink";
16942
+ import { Text as Text10 } from "ink";
16956
16943
  import { jsx as jsx12 } from "react/jsx-runtime";
16957
16944
  var WAVE_SPEED = 0.25 * (120 / ANIM_TICK_MS);
16958
16945
  var CHAR_PHASE_GAP = 0.55;
@@ -16965,16 +16952,16 @@ function sinToColor(sin) {
16965
16952
  var ShimmerText = memo9(({ children, bold, phase = 0 }) => {
16966
16953
  const tick = useAnimationTick();
16967
16954
  const globalPhase = tick * WAVE_SPEED + phase;
16968
- return /* @__PURE__ */ jsx12(Text11, { bold, children: Array.from(children).map((char, i) => {
16955
+ return /* @__PURE__ */ jsx12(Text10, { bold, children: Array.from(children).map((char, i) => {
16969
16956
  const charPhase = globalPhase - i * CHAR_PHASE_GAP;
16970
16957
  const sin = Math.sin(charPhase);
16971
16958
  const color = sinToColor(sin);
16972
- return /* @__PURE__ */ jsx12(Text11, { color, children: char }, i);
16959
+ return /* @__PURE__ */ jsx12(Text10, { color, children: char }, i);
16973
16960
  }) });
16974
16961
  });
16975
16962
 
16976
16963
  // src/platform/tui/components/status/ProcessingView.tsx
16977
- import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
16964
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
16978
16965
  var ProcessingView = ({
16979
16966
  statusMain,
16980
16967
  detailText,
@@ -16993,26 +16980,26 @@ var ProcessingView = ({
16993
16980
  const parenIdx = statusMain.indexOf("(");
16994
16981
  const shimmerPart = parenIdx > -1 ? statusMain.slice(0, parenIdx).trimEnd() : statusMain;
16995
16982
  const staticSuffix = parenIdx > -1 ? " " + statusMain.slice(parenIdx) : "";
16996
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", height: 2, children: [
16997
- /* @__PURE__ */ jsxs9(Box10, { children: [
16998
- /* @__PURE__ */ jsx13(Text12, { color, wrap: "truncate", children: /* @__PURE__ */ jsx13(StarSpinner, { color }) }),
16999
- /* @__PURE__ */ jsx13(Text12, { children: " " }),
16983
+ return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", height: 2, children: [
16984
+ /* @__PURE__ */ jsxs8(Box10, { children: [
16985
+ /* @__PURE__ */ jsx13(Text11, { color, wrap: "truncate", children: /* @__PURE__ */ jsx13(StarSpinner, { color }) }),
16986
+ /* @__PURE__ */ jsx13(Text11, { children: " " }),
17000
16987
  /* @__PURE__ */ jsx13(ShimmerText, { bold: true, phase: 0, children: shimmerPart }),
17001
- staticSuffix ? /* @__PURE__ */ jsx13(Text12, { color: THEME.dimGray, wrap: "truncate", children: staticSuffix }) : null,
17002
- /* @__PURE__ */ jsxs9(Text12, { color: THEME.dimGray, wrap: "truncate", children: [
16988
+ staticSuffix ? /* @__PURE__ */ jsx13(Text11, { color: THEME.dimGray, wrap: "truncate", children: staticSuffix }) : null,
16989
+ /* @__PURE__ */ jsxs8(Text11, { color: THEME.dimGray, wrap: "truncate", children: [
17003
16990
  " ",
17004
16991
  meta
17005
16992
  ] })
17006
16993
  ] }),
17007
- /* @__PURE__ */ jsx13(Box10, { children: detailText ? /* @__PURE__ */ jsxs9(Text12, { color: THEME.dimGray, wrap: "truncate", children: [
16994
+ /* @__PURE__ */ jsx13(Box10, { children: detailText ? /* @__PURE__ */ jsxs8(Text11, { color: THEME.dimGray, wrap: "truncate", children: [
17008
16995
  " ",
17009
16996
  detailText
17010
- ] }) : /* @__PURE__ */ jsx13(Text12, { children: " " }) })
16997
+ ] }) : /* @__PURE__ */ jsx13(Text11, { children: " " }) })
17011
16998
  ] });
17012
16999
  };
17013
17000
 
17014
17001
  // src/platform/tui/components/StatusDisplay.tsx
17015
- import { jsx as jsx14, jsxs as jsxs10 } from "react/jsx-runtime";
17002
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
17016
17003
  var StatusDisplay = memo10(({
17017
17004
  retryState,
17018
17005
  isProcessing,
@@ -17026,9 +17013,9 @@ var StatusDisplay = memo10(({
17026
17013
  return /* @__PURE__ */ jsx14(RetryView, { retryState });
17027
17014
  }
17028
17015
  if (isProcessing && isWaitingForInput) {
17029
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", height: 2, children: [
17030
- /* @__PURE__ */ jsx14(Text13, { children: " " }),
17031
- /* @__PURE__ */ jsx14(Text13, { children: " " })
17016
+ return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", height: 2, children: [
17017
+ /* @__PURE__ */ jsx14(Text12, { children: " " }),
17018
+ /* @__PURE__ */ jsx14(Text12, { children: " " })
17032
17019
  ] });
17033
17020
  }
17034
17021
  if (isProcessing) {
@@ -17047,19 +17034,19 @@ var StatusDisplay = memo10(({
17047
17034
  }
17048
17035
  );
17049
17036
  }
17050
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", height: 2, children: [
17051
- /* @__PURE__ */ jsx14(Text13, { children: " " }),
17052
- /* @__PURE__ */ jsx14(Text13, { children: " " })
17037
+ return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", height: 2, children: [
17038
+ /* @__PURE__ */ jsx14(Text12, { children: " " }),
17039
+ /* @__PURE__ */ jsx14(Text12, { children: " " })
17053
17040
  ] });
17054
17041
  });
17055
17042
 
17056
17043
  // src/platform/tui/components/ChatInput.tsx
17057
- import { useMemo, useCallback as useCallback9, useRef as useRef8, memo as memo11, useState as useState6, useEffect as useEffect7 } from "react";
17058
- import { Box as Box15, Text as Text17, useInput as useInput3 } from "ink";
17044
+ import { useMemo, useCallback as useCallback7, useRef as useRef6, memo as memo11, useState as useState5, useEffect as useEffect7 } from "react";
17045
+ import { Box as Box15, Text as Text16, useInput as useInput2 } from "ink";
17059
17046
 
17060
17047
  // src/platform/tui/components/input/AutocompletePreview.tsx
17061
- import { Box as Box12, Text as Text14 } from "ink";
17062
- import { jsx as jsx15, jsxs as jsxs11 } from "react/jsx-runtime";
17048
+ import { Box as Box12, Text as Text13 } from "ink";
17049
+ import { jsx as jsx15, jsxs as jsxs10 } from "react/jsx-runtime";
17063
17050
  var AutocompletePreview = ({
17064
17051
  suggestions,
17065
17052
  clampedIdx
@@ -17067,26 +17054,26 @@ var AutocompletePreview = ({
17067
17054
  return /* @__PURE__ */ jsx15(Box12, { flexDirection: "column", paddingX: 1, children: suggestions.map((cmd, i) => {
17068
17055
  const isSelected = i === clampedIdx;
17069
17056
  const argsText = cmd.args ? ` ${cmd.args}` : "";
17070
- return /* @__PURE__ */ jsxs11(Box12, { children: [
17071
- /* @__PURE__ */ jsx15(Text14, { color: isSelected ? THEME.primary : THEME.dimGray, children: isSelected ? " \u276F " : " " }),
17072
- /* @__PURE__ */ jsxs11(Text14, { color: isSelected ? THEME.white : THEME.gray, bold: isSelected, children: [
17057
+ return /* @__PURE__ */ jsxs10(Box12, { children: [
17058
+ /* @__PURE__ */ jsx15(Text13, { color: isSelected ? THEME.primary : THEME.dimGray, children: isSelected ? " \u276F " : " " }),
17059
+ /* @__PURE__ */ jsxs10(Text13, { color: isSelected ? THEME.white : THEME.gray, bold: isSelected, children: [
17073
17060
  "/",
17074
17061
  cmd.name
17075
17062
  ] }),
17076
- /* @__PURE__ */ jsx15(Text14, { dimColor: true, color: THEME.gray, children: argsText }),
17077
- /* @__PURE__ */ jsxs11(Text14, { dimColor: true, color: THEME.dimGray, children: [
17063
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, color: THEME.gray, children: argsText }),
17064
+ /* @__PURE__ */ jsxs10(Text13, { dimColor: true, color: THEME.dimGray, children: [
17078
17065
  " \u2014 ",
17079
17066
  cmd.description
17080
17067
  ] }),
17081
- isSelected && cmd.alias && /* @__PURE__ */ jsx15(Text14, { dimColor: true, color: THEME.dimGray, children: ` (/${cmd.alias})` })
17068
+ isSelected && cmd.alias && /* @__PURE__ */ jsx15(Text13, { dimColor: true, color: THEME.dimGray, children: ` (/${cmd.alias})` })
17082
17069
  ] }, cmd.name);
17083
17070
  }) });
17084
17071
  };
17085
17072
 
17086
17073
  // src/platform/tui/components/input/SecretInputArea.tsx
17087
- import { Box as Box13, Text as Text15, useStdout } from "ink";
17074
+ import { Box as Box13, Text as Text14, useStdout } from "ink";
17088
17075
  import TextInput from "ink-text-input";
17089
- import { jsx as jsx16, jsxs as jsxs12 } from "react/jsx-runtime";
17076
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
17090
17077
  var OUTER_PADDING = 2;
17091
17078
  var SecretInputArea = ({
17092
17079
  inputRequest,
@@ -17097,10 +17084,10 @@ var SecretInputArea = ({
17097
17084
  const { stdout } = useStdout();
17098
17085
  const borderWidth = Math.max(10, (stdout?.columns ?? 80) - OUTER_PADDING);
17099
17086
  const borderLine = "\u2501".repeat(borderWidth);
17100
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
17101
- /* @__PURE__ */ jsx16(Box13, { children: /* @__PURE__ */ jsx16(Text15, { color: THEME.yellow, children: borderLine }) }),
17102
- /* @__PURE__ */ jsxs12(Box13, { paddingX: 1, children: [
17103
- /* @__PURE__ */ jsx16(Text15, { color: THEME.yellow, bold: true, children: "\u25B8 " }),
17087
+ return /* @__PURE__ */ jsxs11(Box13, { flexDirection: "column", children: [
17088
+ /* @__PURE__ */ jsx16(Box13, { children: /* @__PURE__ */ jsx16(Text14, { color: THEME.yellow, children: borderLine }) }),
17089
+ /* @__PURE__ */ jsxs11(Box13, { paddingX: 1, children: [
17090
+ /* @__PURE__ */ jsx16(Text14, { color: THEME.yellow, bold: true, children: "\u25B8 " }),
17104
17091
  /* @__PURE__ */ jsx16(
17105
17092
  TextInput,
17106
17093
  {
@@ -17112,14 +17099,14 @@ var SecretInputArea = ({
17112
17099
  }
17113
17100
  )
17114
17101
  ] }),
17115
- /* @__PURE__ */ jsx16(Box13, { children: /* @__PURE__ */ jsx16(Text15, { color: THEME.yellow, children: borderLine }) })
17102
+ /* @__PURE__ */ jsx16(Box13, { children: /* @__PURE__ */ jsx16(Text14, { color: THEME.yellow, children: borderLine }) })
17116
17103
  ] });
17117
17104
  };
17118
17105
 
17119
17106
  // src/platform/tui/components/input/NormalInputArea.tsx
17120
- import { Box as Box14, Text as Text16, useStdout as useStdout2 } from "ink";
17107
+ import { Box as Box14, Text as Text15, useStdout as useStdout2 } from "ink";
17121
17108
  import TextInput2 from "ink-text-input";
17122
- import { jsx as jsx17, jsxs as jsxs13 } from "react/jsx-runtime";
17109
+ import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
17123
17110
  var OUTER_PADDING2 = 2;
17124
17111
  var NormalInputArea = ({
17125
17112
  inputKey,
@@ -17131,10 +17118,10 @@ var NormalInputArea = ({
17131
17118
  const { stdout } = useStdout2();
17132
17119
  const borderWidth = Math.max(10, (stdout?.columns ?? 80) - OUTER_PADDING2);
17133
17120
  const borderLine = "\u2500".repeat(borderWidth);
17134
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
17135
- /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsx17(Text16, { dimColor: true, color: THEME.dimGray, children: borderLine }) }),
17136
- /* @__PURE__ */ jsxs13(Box14, { paddingX: 1, children: [
17137
- /* @__PURE__ */ jsx17(Text16, { color: THEME.primary, children: "\u276F " }),
17121
+ return /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", children: [
17122
+ /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, color: THEME.dimGray, children: borderLine }) }),
17123
+ /* @__PURE__ */ jsxs12(Box14, { paddingX: 1, children: [
17124
+ /* @__PURE__ */ jsx17(Text15, { color: THEME.primary, children: "\u276F " }),
17138
17125
  /* @__PURE__ */ jsx17(
17139
17126
  TextInput2,
17140
17127
  {
@@ -17146,12 +17133,12 @@ var NormalInputArea = ({
17146
17133
  inputKey
17147
17134
  )
17148
17135
  ] }),
17149
- /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsx17(Text16, { dimColor: true, color: THEME.dimGray, children: borderLine }) })
17136
+ /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, color: THEME.dimGray, children: borderLine }) })
17150
17137
  ] });
17151
17138
  };
17152
17139
 
17153
17140
  // src/platform/tui/components/ChatInput.tsx
17154
- import { jsx as jsx18, jsxs as jsxs14 } from "react/jsx-runtime";
17141
+ import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
17155
17142
  var MAX_SUGGESTIONS = 6;
17156
17143
  var ChatInput = memo11(({
17157
17144
  value,
@@ -17171,25 +17158,25 @@ var ChatInput = memo11(({
17171
17158
  return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
17172
17159
  }, [isSlashMode, partialCmd, hasArgs]);
17173
17160
  const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
17174
- const [selectedIdx, setSelectedIdx] = useState6(0);
17161
+ const [selectedIdx, setSelectedIdx] = useState5(0);
17175
17162
  const clampedIdx = Math.min(selectedIdx, Math.max(0, suggestions.length - 1));
17176
- const selectedIdxRef = useRef8(clampedIdx);
17163
+ const selectedIdxRef = useRef6(clampedIdx);
17177
17164
  selectedIdxRef.current = clampedIdx;
17178
- const suggestionsRef = useRef8(suggestions);
17165
+ const suggestionsRef = useRef6(suggestions);
17179
17166
  suggestionsRef.current = suggestions;
17180
- const isSlashModeRef = useRef8(isSlashMode);
17167
+ const isSlashModeRef = useRef6(isSlashMode);
17181
17168
  isSlashModeRef.current = isSlashMode;
17182
- const hasArgsRef = useRef8(hasArgs);
17169
+ const hasArgsRef = useRef6(hasArgs);
17183
17170
  hasArgsRef.current = hasArgs;
17184
- const showPreviewRef = useRef8(showPreview);
17171
+ const showPreviewRef = useRef6(showPreview);
17185
17172
  showPreviewRef.current = showPreview;
17186
- const inputRequestRef = useRef8(inputRequest);
17173
+ const inputRequestRef = useRef6(inputRequest);
17187
17174
  inputRequestRef.current = inputRequest;
17188
- const onChangeRef = useRef8(onChange);
17175
+ const onChangeRef = useRef6(onChange);
17189
17176
  onChangeRef.current = onChange;
17190
- const [pastedHint, setPastedHint] = useState6(null);
17191
- const prevValueRef = useRef8(value);
17192
- const pasteTimerRef = useRef8(null);
17177
+ const [pastedHint, setPastedHint] = useState5(null);
17178
+ const prevValueRef = useRef6(value);
17179
+ const pasteTimerRef = useRef6(null);
17193
17180
  useEffect7(() => {
17194
17181
  const diff = value.length - prevValueRef.current.length;
17195
17182
  if (diff > 20) {
@@ -17202,8 +17189,8 @@ var ChatInput = memo11(({
17202
17189
  if (pasteTimerRef.current) clearTimeout(pasteTimerRef.current);
17203
17190
  };
17204
17191
  }, [value]);
17205
- const [inputKey, setInputKey] = useState6(0);
17206
- const completeCommand = useCallback9((idx) => {
17192
+ const [inputKey, setInputKey] = useState5(0);
17193
+ const completeCommand = useCallback7((idx) => {
17207
17194
  const sug = suggestionsRef.current;
17208
17195
  if (!sug.length) return;
17209
17196
  const best = sug[Math.min(idx, sug.length - 1)];
@@ -17213,9 +17200,9 @@ var ChatInput = memo11(({
17213
17200
  setSelectedIdx(0);
17214
17201
  setInputKey((k) => k + 1);
17215
17202
  }, []);
17216
- const onSubmitRef = useRef8(onSubmit);
17203
+ const onSubmitRef = useRef6(onSubmit);
17217
17204
  onSubmitRef.current = onSubmit;
17218
- const wrappedOnSubmit = useCallback9((val) => {
17205
+ const wrappedOnSubmit = useCallback7((val) => {
17219
17206
  if (showPreviewRef.current) {
17220
17207
  const sug = suggestionsRef.current;
17221
17208
  if (!sug.length) {
@@ -17234,7 +17221,7 @@ var ChatInput = memo11(({
17234
17221
  }
17235
17222
  onSubmitRef.current(val);
17236
17223
  }, [completeCommand]);
17237
- useInput3(useCallback9((_input, key) => {
17224
+ useInput2(useCallback7((_input, key) => {
17238
17225
  if (inputRequestRef.current.status === "active") return;
17239
17226
  const sug = suggestionsRef.current;
17240
17227
  const visible = showPreviewRef.current;
@@ -17250,7 +17237,7 @@ var ChatInput = memo11(({
17250
17237
  completeCommand(selectedIdxRef.current);
17251
17238
  }
17252
17239
  }, [completeCommand]));
17253
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
17240
+ return /* @__PURE__ */ jsxs13(Box15, { flexDirection: "column", children: [
17254
17241
  showPreview && /* @__PURE__ */ jsx18(
17255
17242
  AutocompletePreview,
17256
17243
  {
@@ -17282,14 +17269,14 @@ var ChatInput = memo11(({
17282
17269
  }
17283
17270
  )
17284
17271
  ),
17285
- pastedHint && /* @__PURE__ */ jsx18(Box15, { paddingX: 2, children: /* @__PURE__ */ jsx18(Text17, { dimColor: true, color: THEME.dimGray, children: pastedHint }) })
17272
+ pastedHint && /* @__PURE__ */ jsx18(Box15, { paddingX: 2, children: /* @__PURE__ */ jsx18(Text16, { dimColor: true, color: THEME.dimGray, children: pastedHint }) })
17286
17273
  ] });
17287
17274
  });
17288
17275
 
17289
17276
  // src/platform/tui/components/footer.tsx
17290
17277
  import { memo as memo12 } from "react";
17291
- import { Box as Box16, Text as Text18 } from "ink";
17292
- import { jsx as jsx19, jsxs as jsxs15 } from "react/jsx-runtime";
17278
+ import { Box as Box16, Text as Text17 } from "ink";
17279
+ import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
17293
17280
  var CTX_WARN_THRESHOLD = 0.8;
17294
17281
  var MAX_CONTEXT_TOKENS = LLM_LIMITS.streamMaxTokens;
17295
17282
  var formatElapsed = (totalSeconds) => {
@@ -17305,7 +17292,7 @@ var formatElapsed = (totalSeconds) => {
17305
17292
  var Footer = memo12(({ phase, targets, findings, todo, elapsedTime, isProcessing, totalTokens, turnCount }) => {
17306
17293
  const ctxPct = totalTokens > 0 ? Math.round(totalTokens / MAX_CONTEXT_TOKENS * 100) : 0;
17307
17294
  const ctxColor = ctxPct >= CTX_WARN_THRESHOLD * 100 ? THEME.yellow : THEME.dimGray;
17308
- return /* @__PURE__ */ jsxs15(
17295
+ return /* @__PURE__ */ jsxs14(
17309
17296
  Box16,
17310
17297
  {
17311
17298
  width: "100%",
@@ -17313,44 +17300,44 @@ var Footer = memo12(({ phase, targets, findings, todo, elapsedTime, isProcessing
17313
17300
  justifyContent: "space-between",
17314
17301
  overflow: "hidden",
17315
17302
  children: [
17316
- /* @__PURE__ */ jsxs15(Box16, { gap: 2, children: [
17317
- /* @__PURE__ */ jsxs15(Text18, { color: THEME.gray, children: [
17303
+ /* @__PURE__ */ jsxs14(Box16, { gap: 2, children: [
17304
+ /* @__PURE__ */ jsxs14(Text17, { color: THEME.gray, children: [
17318
17305
  "Phase: ",
17319
- /* @__PURE__ */ jsx19(Text18, { color: THEME.white, children: phase })
17306
+ /* @__PURE__ */ jsx19(Text17, { color: THEME.white, children: phase })
17320
17307
  ] }),
17321
- /* @__PURE__ */ jsxs15(Text18, { color: THEME.gray, children: [
17308
+ /* @__PURE__ */ jsxs14(Text17, { color: THEME.gray, children: [
17322
17309
  "Targets: ",
17323
- /* @__PURE__ */ jsx19(Text18, { color: THEME.white, children: targets })
17310
+ /* @__PURE__ */ jsx19(Text17, { color: THEME.white, children: targets })
17324
17311
  ] }),
17325
- /* @__PURE__ */ jsxs15(Text18, { color: THEME.gray, children: [
17312
+ /* @__PURE__ */ jsxs14(Text17, { color: THEME.gray, children: [
17326
17313
  "Findings: ",
17327
- /* @__PURE__ */ jsx19(Text18, { color: THEME.white, children: findings })
17314
+ /* @__PURE__ */ jsx19(Text17, { color: THEME.white, children: findings })
17328
17315
  ] }),
17329
- /* @__PURE__ */ jsxs15(Text18, { color: THEME.gray, children: [
17316
+ /* @__PURE__ */ jsxs14(Text17, { color: THEME.gray, children: [
17330
17317
  "Tasks: ",
17331
- /* @__PURE__ */ jsx19(Text18, { color: THEME.white, children: todo })
17318
+ /* @__PURE__ */ jsx19(Text17, { color: THEME.white, children: todo })
17332
17319
  ] })
17333
17320
  ] }),
17334
- /* @__PURE__ */ jsxs15(Box16, { gap: 2, children: [
17335
- isProcessing ? /* @__PURE__ */ jsxs15(Box16, { gap: 1, children: [
17336
- /* @__PURE__ */ jsx19(Text18, { dimColor: true, color: THEME.dimGray, children: "[ESC] abort" }),
17337
- /* @__PURE__ */ jsx19(Text18, { dimColor: true, color: THEME.dimGray, children: "[^C\xD72] exit" })
17338
- ] }) : /* @__PURE__ */ jsx19(Text18, { dimColor: true, color: THEME.dimGray, children: "[/help] commands" }),
17339
- turnCount > 0 && /* @__PURE__ */ jsxs15(Text18, { dimColor: true, color: THEME.dimGray, children: [
17321
+ /* @__PURE__ */ jsxs14(Box16, { gap: 2, children: [
17322
+ isProcessing ? /* @__PURE__ */ jsxs14(Box16, { gap: 1, children: [
17323
+ /* @__PURE__ */ jsx19(Text17, { dimColor: true, color: THEME.dimGray, children: "[ESC] abort" }),
17324
+ /* @__PURE__ */ jsx19(Text17, { dimColor: true, color: THEME.dimGray, children: "[^C\xD72] exit" })
17325
+ ] }) : /* @__PURE__ */ jsx19(Text17, { dimColor: true, color: THEME.dimGray, children: "[/help] commands" }),
17326
+ turnCount > 0 && /* @__PURE__ */ jsxs14(Text17, { dimColor: true, color: THEME.dimGray, children: [
17340
17327
  "turn:",
17341
17328
  turnCount
17342
17329
  ] }),
17343
- totalTokens > 0 && /* @__PURE__ */ jsxs15(Text18, { dimColor: true, color: ctxColor, children: [
17330
+ totalTokens > 0 && /* @__PURE__ */ jsxs14(Text17, { dimColor: true, color: ctxColor, children: [
17344
17331
  "ctx:",
17345
17332
  ctxPct,
17346
17333
  "%"
17347
17334
  ] }),
17348
- totalTokens > 0 && /* @__PURE__ */ jsxs15(Text18, { dimColor: true, color: THEME.dimGray, children: [
17335
+ totalTokens > 0 && /* @__PURE__ */ jsxs14(Text17, { dimColor: true, color: THEME.dimGray, children: [
17349
17336
  "\u2191",
17350
17337
  formatTokens(totalTokens)
17351
17338
  ] }),
17352
- /* @__PURE__ */ jsx19(Text18, { color: isProcessing ? THEME.primary : THEME.gray, children: isProcessing ? "Running " : "Idle " }),
17353
- /* @__PURE__ */ jsx19(Text18, { color: THEME.white, children: formatElapsed(elapsedTime) })
17339
+ /* @__PURE__ */ jsx19(Text17, { color: isProcessing ? THEME.primary : THEME.gray, children: isProcessing ? "Running " : "Idle " }),
17340
+ /* @__PURE__ */ jsx19(Text17, { color: THEME.white, children: formatElapsed(elapsedTime) })
17354
17341
  ] })
17355
17342
  ]
17356
17343
  }
@@ -17359,9 +17346,9 @@ var Footer = memo12(({ phase, targets, findings, todo, elapsedTime, isProcessing
17359
17346
  var footer_default = Footer;
17360
17347
 
17361
17348
  // src/platform/tui/components/Modal.tsx
17362
- import { useMemo as useMemo2, memo as memo13, useCallback as useCallback10, useRef as useRef9 } from "react";
17363
- import { Box as Box17, Text as Text19, useStdout as useStdout3, useInput as useInput4 } from "ink";
17364
- import { jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
17349
+ import { useMemo as useMemo2, memo as memo13, useCallback as useCallback8, useRef as useRef7 } from "react";
17350
+ import { Box as Box17, Text as Text18, useStdout as useStdout3, useInput as useInput3 } from "ink";
17351
+ import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
17365
17352
  var MODAL_TITLES = {
17366
17353
  findings: "\u25C8 FINDINGS \u25C8",
17367
17354
  graph: "\u25C8 ATTACK GRAPH \u25C8",
@@ -17374,9 +17361,9 @@ var Modal = memo13(({
17374
17361
  onScroll,
17375
17362
  onClose
17376
17363
  }) => {
17377
- const onScrollRef = useRef9(onScroll);
17364
+ const onScrollRef = useRef7(onScroll);
17378
17365
  onScrollRef.current = onScroll;
17379
- const onCloseRef = useRef9(onClose);
17366
+ const onCloseRef = useRef7(onClose);
17380
17367
  onCloseRef.current = onClose;
17381
17368
  const { stdout } = useStdout3();
17382
17369
  const terminalHeight = stdout?.rows ?? 24;
@@ -17388,7 +17375,7 @@ var Modal = memo13(({
17388
17375
  () => lines.slice(scrollOffset, scrollOffset + maxHeight),
17389
17376
  [lines, scrollOffset, maxHeight]
17390
17377
  );
17391
- useInput4(useCallback10((input, key) => {
17378
+ useInput3(useCallback8((input, key) => {
17392
17379
  if (key.escape || input === "q") {
17393
17380
  onCloseRef.current();
17394
17381
  } else if (key.upArrow || input === "k") {
@@ -17405,14 +17392,14 @@ var Modal = memo13(({
17405
17392
  const endLine = Math.min(scrollOffset + maxHeight, totalLines);
17406
17393
  const scrollbarHeight = Math.max(1, Math.floor(maxHeight * (maxHeight / Math.max(totalLines, 1))));
17407
17394
  const scrollbarPosition = totalLines > maxHeight ? Math.floor(scrollOffset / (totalLines - maxHeight) * (maxHeight - scrollbarHeight)) : 0;
17408
- return /* @__PURE__ */ jsxs16(
17395
+ return /* @__PURE__ */ jsxs15(
17409
17396
  Box17,
17410
17397
  {
17411
17398
  flexDirection: "column",
17412
17399
  width: terminalWidth,
17413
17400
  height: terminalHeight,
17414
17401
  children: [
17415
- /* @__PURE__ */ jsx20(Box17, { justifyContent: "center", marginBottom: 0, children: /* @__PURE__ */ jsx20(Text19, { color: THEME.cyan, bold: true, children: (() => {
17402
+ /* @__PURE__ */ jsx20(Box17, { justifyContent: "center", marginBottom: 0, children: /* @__PURE__ */ jsx20(Text18, { color: THEME.cyan, bold: true, children: (() => {
17416
17403
  const title = MODAL_TITLES[type];
17417
17404
  const sideWidth = Math.max(3, Math.floor((terminalWidth - title.length - 2) / 2));
17418
17405
  return `${"\u2500".repeat(sideWidth)} ${title} ${"\u2500".repeat(sideWidth)}`;
@@ -17427,17 +17414,17 @@ var Modal = memo13(({
17427
17414
  flexGrow: 1,
17428
17415
  children: visibleLines.map((line, i) => {
17429
17416
  const showScrollbar = totalLines > maxHeight && i >= scrollbarPosition && i < scrollbarPosition + scrollbarHeight;
17430
- return /* @__PURE__ */ jsxs16(Box17, { children: [
17431
- /* @__PURE__ */ jsx20(Text19, { color: THEME.white, wrap: "truncate", children: line }),
17417
+ return /* @__PURE__ */ jsxs15(Box17, { children: [
17418
+ /* @__PURE__ */ jsx20(Text18, { color: THEME.white, wrap: "truncate", children: line }),
17432
17419
  /* @__PURE__ */ jsx20(Box17, { flexGrow: 1 }),
17433
- totalLines > maxHeight && /* @__PURE__ */ jsx20(Text19, { color: showScrollbar ? THEME.cyan : THEME.dimGray, children: showScrollbar ? "\u2588" : "\u2502" })
17420
+ totalLines > maxHeight && /* @__PURE__ */ jsx20(Text18, { color: showScrollbar ? THEME.cyan : THEME.dimGray, children: showScrollbar ? "\u2588" : "\u2502" })
17434
17421
  ] }, i);
17435
17422
  })
17436
17423
  }
17437
17424
  ),
17438
- /* @__PURE__ */ jsxs16(Box17, { justifyContent: "space-between", paddingX: 1, children: [
17439
- /* @__PURE__ */ jsx20(Text19, { dimColor: true, color: THEME.gray, children: "\u2191\u2193/jk: scroll | g/G: top/bottom | ESC/q: close" }),
17440
- /* @__PURE__ */ jsxs16(Text19, { dimColor: true, color: THEME.cyan, children: [
17425
+ /* @__PURE__ */ jsxs15(Box17, { justifyContent: "space-between", paddingX: 1, children: [
17426
+ /* @__PURE__ */ jsx20(Text18, { dimColor: true, color: THEME.gray, children: "\u2191\u2193/jk: scroll | g/G: top/bottom | ESC/q: close" }),
17427
+ /* @__PURE__ */ jsxs15(Text18, { dimColor: true, color: THEME.cyan, children: [
17441
17428
  startLine,
17442
17429
  "-",
17443
17430
  endLine,
@@ -17452,7 +17439,7 @@ var Modal = memo13(({
17452
17439
 
17453
17440
  // src/platform/tui/components/app/bottom-region.tsx
17454
17441
  import { Box as Box18 } from "ink";
17455
- import { jsx as jsx21, jsxs as jsxs17 } from "react/jsx-runtime";
17442
+ import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
17456
17443
  var BottomRegion = ({
17457
17444
  input,
17458
17445
  setInput,
@@ -17476,7 +17463,7 @@ var BottomRegion = ({
17476
17463
  const suggestionCount = isSlashMode && !hasArgs && inputRequest.status !== "active" ? Math.min(getMatchingCommands(partialCmd).length, MAX_SUGGESTIONS) : 0;
17477
17464
  const previewHeight = suggestionCount;
17478
17465
  const bottomMinHeight = 7;
17479
- return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", minHeight: bottomMinHeight, children: [
17466
+ return /* @__PURE__ */ jsxs16(Box18, { flexDirection: "column", minHeight: bottomMinHeight, children: [
17480
17467
  /* @__PURE__ */ jsx21(
17481
17468
  StatusDisplay,
17482
17469
  {
@@ -17517,16 +17504,16 @@ var BottomRegion = ({
17517
17504
  };
17518
17505
 
17519
17506
  // src/platform/tui/app.tsx
17520
- import { jsx as jsx22, jsxs as jsxs18 } from "react/jsx-runtime";
17507
+ import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
17521
17508
  var MODEL_NAME = getModel() || DEFAULT_MODEL;
17522
17509
  var App = ({ autoApprove = false, target }) => {
17523
17510
  const { exit } = useApp();
17524
17511
  const { stdout } = useStdout4();
17525
17512
  const terminalWidth = stdout?.columns ?? 80;
17526
- const [input, setInput] = useState7("");
17527
- const [secretInput, setSecretInput] = useState7("");
17528
- const [autoApproveMode, setAutoApproveMode] = useState7(autoApprove);
17529
- const [modal, setModal] = useState7({ type: null, content: "", scrollOffset: 0 });
17513
+ const [input, setInput] = useState6("");
17514
+ const [secretInput, setSecretInput] = useState6("");
17515
+ const [autoApproveMode, setAutoApproveMode] = useState6(autoApprove);
17516
+ const [modal, setModal] = useState6({ type: null, content: "", scrollOffset: 0 });
17530
17517
  const {
17531
17518
  agent,
17532
17519
  messages,
@@ -17546,37 +17533,26 @@ var App = ({ autoApprove = false, target }) => {
17546
17533
  addMessage,
17547
17534
  refreshStats
17548
17535
  } = useAgent(autoApproveMode, target);
17549
- const isProcessingRef = useRef10(isProcessing);
17536
+ const isProcessingRef = useRef8(isProcessing);
17550
17537
  isProcessingRef.current = isProcessing;
17551
- const autoApproveModeRef = useRef10(autoApproveMode);
17538
+ const autoApproveModeRef = useRef8(autoApproveMode);
17552
17539
  autoApproveModeRef.current = autoApproveMode;
17553
- const inputRequestRef = useRef10(inputRequest);
17540
+ const inputRequestRef = useRef8(inputRequest);
17554
17541
  inputRequestRef.current = inputRequest;
17555
- const isModalOpenRef = useRef10(!!modal.type);
17542
+ const isModalOpenRef = useRef8(!!modal.type);
17556
17543
  isModalOpenRef.current = !!modal.type;
17557
- const inputRef = useRef10(input);
17544
+ const inputRef = useRef8(input);
17558
17545
  inputRef.current = input;
17559
- const clearInput = useCallback11(() => {
17546
+ const clearInput = useCallback9(() => {
17560
17547
  setInput("");
17561
17548
  }, []);
17562
- const [historyScrollOffset, setHistoryScrollOffset] = useState7(0);
17563
- const handleScroll = useCallback11((delta) => {
17564
- setHistoryScrollOffset((prev) => Math.max(0, prev - delta));
17565
- }, []);
17566
- const messageCountRef = useRef10(messages.length);
17567
- if (messages.length !== messageCountRef.current) {
17568
- messageCountRef.current = messages.length;
17569
- if (historyScrollOffset > 0) {
17570
- setHistoryScrollOffset(0);
17571
- }
17572
- }
17573
- const showModal = useCallback11((type, content) => {
17549
+ const showModal = useCallback9((type, content) => {
17574
17550
  setModal({ type, content, scrollOffset: 0 });
17575
17551
  }, []);
17576
- const closeModal = useCallback11(() => {
17552
+ const closeModal = useCallback9(() => {
17577
17553
  setModal({ type: null, content: "", scrollOffset: 0 });
17578
17554
  }, []);
17579
- const handleModalScroll = useCallback11((delta) => {
17555
+ const handleModalScroll = useCallback9((delta) => {
17580
17556
  setModal((prev) => {
17581
17557
  const lines = prev.content.split("\n");
17582
17558
  const maxHeight = (stdout?.rows ?? 24) - 6;
@@ -17585,7 +17561,7 @@ var App = ({ autoApprove = false, target }) => {
17585
17561
  return { ...prev, scrollOffset: newOffset };
17586
17562
  });
17587
17563
  }, [stdout?.rows]);
17588
- const handleExit = useCallback11(() => {
17564
+ const handleExit = useCallback9(() => {
17589
17565
  const ir = inputRequestRef.current;
17590
17566
  if (ir.status === "active") {
17591
17567
  ir.resolve(null);
@@ -17608,7 +17584,7 @@ var App = ({ autoApprove = false, target }) => {
17608
17584
  isProcessingRef,
17609
17585
  autoApproveModeRef
17610
17586
  });
17611
- const handleSubmit = useCallback11(async (value) => {
17587
+ const handleSubmit = useCallback9(async (value) => {
17612
17588
  const trimmed = value.trim();
17613
17589
  if (!trimmed) return;
17614
17590
  setInput("");
@@ -17623,7 +17599,7 @@ var App = ({ autoApprove = false, target }) => {
17623
17599
  await executeTask(trimmed);
17624
17600
  }
17625
17601
  }, [agent, addMessage, executeTask, handleCommand]);
17626
- const handleSecretSubmit = useCallback11((value) => {
17602
+ const handleSecretSubmit = useCallback9((value) => {
17627
17603
  const ir = inputRequestRef.current;
17628
17604
  if (ir.status !== "active") return;
17629
17605
  const displayText = ir.isPassword ? "\u2022".repeat(Math.min(value.length, 20)) : value;
@@ -17641,8 +17617,7 @@ var App = ({ autoApprove = false, target }) => {
17641
17617
  inputRequestRef,
17642
17618
  isModalOpenRef,
17643
17619
  inputRef,
17644
- clearInput,
17645
- onScroll: handleScroll
17620
+ clearInput
17646
17621
  });
17647
17622
  if (modal.type) {
17648
17623
  return /* @__PURE__ */ jsx22(Box19, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: /* @__PURE__ */ jsx22(
@@ -17656,16 +17631,14 @@ var App = ({ autoApprove = false, target }) => {
17656
17631
  }
17657
17632
  ) });
17658
17633
  }
17659
- return /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: [
17634
+ return /* @__PURE__ */ jsxs17(Box19, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: [
17660
17635
  /* @__PURE__ */ jsx22(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsx22(
17661
17636
  MessageList,
17662
17637
  {
17663
17638
  messages,
17664
- isModalOpen: !!modal.type,
17665
17639
  modelName: MODEL_NAME,
17666
17640
  autoApproveMode,
17667
- version: APP_VERSION,
17668
- scrollOffset: historyScrollOffset
17641
+ version: APP_VERSION
17669
17642
  }
17670
17643
  ) }),
17671
17644
  /* @__PURE__ */ jsx22(