pentesting 0.70.4 → 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 +630 -646
  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.4";
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 useState8, 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
 
@@ -15237,11 +15310,12 @@ ${firstLine}`);
15237
15310
  }
15238
15311
 
15239
15312
  // src/platform/tui/hooks/useAgentEvents/handlers/input.ts
15240
- function setupInputHandlers(setInputRequest) {
15313
+ function setupInputHandlers(setInputRequest, addMessage) {
15241
15314
  setInputHandler((p) => {
15242
15315
  return new Promise((resolve) => {
15243
15316
  const isPassword = /password|passphrase/i.test(p);
15244
15317
  const inputType = /sudo/i.test(p) ? INPUT_TYPES.SUDO_PASSWORD : isPassword ? INPUT_TYPES.PASSWORD : INPUT_TYPES.TEXT;
15318
+ addMessage("ai", `\u{1F512} ${p.trim()}`);
15245
15319
  setInputRequest({
15246
15320
  status: "active",
15247
15321
  prompt: p.trim(),
@@ -15255,6 +15329,7 @@ function setupInputHandlers(setInputRequest) {
15255
15329
  return new Promise((resolve) => {
15256
15330
  const isPassword = SENSITIVE_INPUT_TYPES.includes(request.type);
15257
15331
  const displayPrompt = buildCredentialPrompt(request);
15332
+ addMessage("ai", `\u{1F512} ${displayPrompt}`);
15258
15333
  setInputRequest({
15259
15334
  status: "active",
15260
15335
  prompt: displayPrompt,
@@ -15274,9 +15349,26 @@ function setupInputHandlers(setInputRequest) {
15274
15349
  }
15275
15350
 
15276
15351
  // src/platform/tui/hooks/useAgentEvents/handlers/command.ts
15277
- function setupCommandHandlers(addMessage) {
15352
+ function setupCommandHandlers(addMessage, setCurrentStatus) {
15353
+ let lastStatusBase = "";
15278
15354
  setCommandEventEmitter((event) => {
15279
- 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}`);
15280
15372
  return;
15281
15373
  }
15282
15374
  const icon = getCommandEventIcon(event.type);
@@ -15322,8 +15414,8 @@ var useAgentEvents = (agent, eventsRef, state) => {
15322
15414
  lastStepTokensRef
15323
15415
  }, reasoningBufferRef);
15324
15416
  const reasoningHandlers = createReasoningHandlers({ addMessage, setCurrentStatus }, reasoningBufferRef);
15325
- const cleanupInput = setupInputHandlers(setInputRequest);
15326
- const cleanupCommand = setupCommandHandlers(addMessage);
15417
+ const cleanupInput = setupInputHandlers(setInputRequest, addMessage);
15418
+ const cleanupCommand = setupCommandHandlers(addMessage, setCurrentStatus);
15327
15419
  const updateStats = () => {
15328
15420
  const s = agent.getState();
15329
15421
  setStats({
@@ -15342,6 +15434,7 @@ var useAgentEvents = (agent, eventsRef, state) => {
15342
15434
  events.on(EVENT_TYPES.USAGE_UPDATE, lifecycleHandlers.onUsageUpdate);
15343
15435
  events.on(EVENT_TYPES.FLAG_FOUND, lifecycleHandlers.onFlagFound);
15344
15436
  events.on(EVENT_TYPES.PHASE_CHANGE, lifecycleHandlers.onPhaseChange);
15437
+ events.on(EVENT_TYPES.NOTIFICATION, lifecycleHandlers.onNotification);
15345
15438
  events.on(EVENT_TYPES.STATE_CHANGE, updateStats);
15346
15439
  events.on(EVENT_TYPES.START, updateStats);
15347
15440
  events.on(EVENT_TYPES.REASONING_START, reasoningHandlers.onStart);
@@ -16001,7 +16094,7 @@ var useCommands = (props) => {
16001
16094
  };
16002
16095
 
16003
16096
  // src/platform/tui/hooks/useKeyboardShortcuts.ts
16004
- import { useCallback as useCallback7, useEffect as useEffect4 } from "react";
16097
+ import { useCallback as useCallback6, useEffect as useEffect4 } from "react";
16005
16098
  import { useInput } from "ink";
16006
16099
 
16007
16100
  // src/platform/tui/hooks/keyboard/useDoubleTap.ts
@@ -16091,17 +16184,6 @@ var useEscHandler = ({
16091
16184
  return { handleEsc };
16092
16185
  };
16093
16186
 
16094
- // src/platform/tui/hooks/keyboard/useScrollHandler.ts
16095
- import { useRef as useRef5, useCallback as useCallback6 } from "react";
16096
- var useScrollHandler = ({ onScroll }) => {
16097
- const onScrollRef = useRef5(onScroll);
16098
- onScrollRef.current = onScroll;
16099
- const handleScroll = useCallback6((delta) => {
16100
- onScrollRef.current?.(delta);
16101
- }, []);
16102
- return { handleScroll };
16103
- };
16104
-
16105
16187
  // src/platform/tui/hooks/useKeyboardShortcuts.ts
16106
16188
  var DOUBLE_TAP_WINDOW = 3e3;
16107
16189
  var useKeyboardShortcuts = ({
@@ -16113,8 +16195,7 @@ var useKeyboardShortcuts = ({
16113
16195
  inputRequestRef,
16114
16196
  isModalOpenRef,
16115
16197
  inputRef,
16116
- clearInput,
16117
- onScroll
16198
+ clearInput
16118
16199
  }) => {
16119
16200
  const { handleCtrlC } = useExitHandler({
16120
16201
  handleExit,
@@ -16133,18 +16214,11 @@ var useKeyboardShortcuts = ({
16133
16214
  inputRef,
16134
16215
  windowMs: DOUBLE_TAP_WINDOW
16135
16216
  });
16136
- const { handleScroll } = useScrollHandler({ onScroll });
16137
- useInput(useCallback7((ch, key) => {
16217
+ useInput(useCallback6((ch, key) => {
16138
16218
  if (isModalOpenRef.current) return;
16139
16219
  if (key.escape) handleEsc();
16140
16220
  if (key.ctrl && ch === "c") handleCtrlC();
16141
- if (key.pageUp) {
16142
- handleScroll(-10);
16143
- }
16144
- if (key.pageDown) {
16145
- handleScroll(10);
16146
- }
16147
- }, [handleEsc, handleCtrlC, handleScroll, isModalOpenRef]));
16221
+ }, [handleEsc, handleCtrlC, isModalOpenRef]));
16148
16222
  useEffect4(() => {
16149
16223
  const onSignal = () => handleCtrlC();
16150
16224
  process.on("SIGINT", onSignal);
@@ -16157,36 +16231,52 @@ var useKeyboardShortcuts = ({
16157
16231
  return { handleCtrlC };
16158
16232
  };
16159
16233
 
16234
+ // src/platform/tui/hooks/useAnimationTick.tsx
16235
+ import { createContext, useContext, useState as useState3, useEffect as useEffect5 } from "react";
16236
+ import { jsx } from "react/jsx-runtime";
16237
+ var ANIM_TICK_MS = 100;
16238
+ var AnimationContext = createContext(0);
16239
+ var AnimationProvider = ({ children }) => {
16240
+ const [tick, setTick] = useState3(0);
16241
+ useEffect5(() => {
16242
+ const timer = setInterval(() => {
16243
+ setTick((t) => t + 1);
16244
+ }, ANIM_TICK_MS);
16245
+ return () => clearInterval(timer);
16246
+ }, []);
16247
+ return /* @__PURE__ */ jsx(AnimationContext.Provider, { value: tick, children });
16248
+ };
16249
+ var useAnimationTick = () => useContext(AnimationContext);
16250
+
16160
16251
  // src/platform/tui/components/MessageList.tsx
16161
- import { memo as memo7, useState as useState3, useCallback as useCallback8, useRef as useRef6 } from "react";
16162
- 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";
16163
16254
 
16164
16255
  // src/platform/tui/components/messages/ThinkingBlock.tsx
16165
16256
  import { memo } from "react";
16166
16257
  import { Box, Text } from "ink";
16167
- import { jsx, jsxs } from "react/jsx-runtime";
16258
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
16168
16259
  var THINKING_PREVIEW_LINES = 3;
16169
- var ThinkingBlock = memo(({ msg, isExpanded }) => {
16260
+ var ThinkingBlock = memo(({ msg }) => {
16170
16261
  const lines = msg.content.split("\n");
16171
16262
  const charCount = msg.content.length;
16172
16263
  const estTokens = Math.round(charCount / LLM_LIMITS.charsPerTokenEstimate);
16173
16264
  const hiddenCount = lines.length - THINKING_PREVIEW_LINES;
16174
- const visibleLines = isExpanded ? lines : lines.slice(0, THINKING_PREVIEW_LINES);
16265
+ const visibleLines = lines.slice(0, THINKING_PREVIEW_LINES);
16175
16266
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [
16176
16267
  /* @__PURE__ */ jsxs(Box, { children: [
16177
- /* @__PURE__ */ jsx(Text, { color: THEME.dimGray, children: " \u25C6 " }),
16178
- /* @__PURE__ */ jsx(Text, { color: THEME.gray, children: "Reasoning" }),
16179
- /* @__PURE__ */ jsx(Text, { color: THEME.dimGray, children: ` (~${estTokens} tokens)` }),
16180
- !isExpanded && hiddenCount > 0 && /* @__PURE__ */ jsx(Text, { color: THEME.dimGray, children: ` [+${hiddenCount} lines \xB7 R]` }),
16181
- isExpanded && /* @__PURE__ */ jsx(Text, { color: THEME.dimGray, children: " [R to collapse]" })
16268
+ /* @__PURE__ */ jsx2(Text, { color: THEME.dimGray, children: " \u25C6 " }),
16269
+ /* @__PURE__ */ jsx2(Text, { color: THEME.gray, children: "Reasoning" }),
16270
+ /* @__PURE__ */ jsx2(Text, { color: THEME.dimGray, children: ` (~${estTokens} tokens)` }),
16271
+ hiddenCount > 0 && /* @__PURE__ */ jsx2(Text, { color: THEME.dimGray, children: ` [+${hiddenCount} lines]` })
16182
16272
  ] }),
16183
16273
  visibleLines.map((line, i) => /* @__PURE__ */ jsxs(Box, { children: [
16184
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
16185
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: line })
16274
+ /* @__PURE__ */ jsx2(Text, { dimColor: true, children: " \u2502 " }),
16275
+ /* @__PURE__ */ jsx2(Text, { dimColor: true, children: line })
16186
16276
  ] }, i)),
16187
- !isExpanded && hiddenCount > 0 && /* @__PURE__ */ jsxs(Box, { children: [
16188
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " \u2502 " }),
16189
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "..." })
16277
+ hiddenCount > 0 && /* @__PURE__ */ jsxs(Box, { children: [
16278
+ /* @__PURE__ */ jsx2(Text, { dimColor: true, children: " \u2502 " }),
16279
+ /* @__PURE__ */ jsx2(Text, { dimColor: true, children: "..." })
16190
16280
  ] })
16191
16281
  ] });
16192
16282
  });
@@ -16233,7 +16323,7 @@ var MESSAGE_STYLES = {
16233
16323
 
16234
16324
  // src/platform/tui/components/inline-status.tsx
16235
16325
  import { Box as Box2, Text as Text2 } from "ink";
16236
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
16326
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
16237
16327
  function formatDuration3(ms) {
16238
16328
  const seconds = Math.floor(ms / 1e3);
16239
16329
  if (seconds < 60) return `${seconds}s`;
@@ -16281,13 +16371,13 @@ function ProcessRow({ proc, isCompact }) {
16281
16371
  const purpose = proc.purpose || proc.description || "";
16282
16372
  const truncatedPurpose = isCompact && purpose.length > TUI_DISPLAY_LIMITS.purposeMaxLength ? purpose.slice(0, TUI_DISPLAY_LIMITS.purposeTruncated) + "..." : purpose;
16283
16373
  return /* @__PURE__ */ jsxs2(Box2, { children: [
16284
- /* @__PURE__ */ jsx2(StatusIndicator, { isRunning: proc.isRunning, exitCode: proc.exitCode }),
16374
+ /* @__PURE__ */ jsx3(StatusIndicator, { isRunning: proc.isRunning, exitCode: proc.exitCode }),
16285
16375
  /* @__PURE__ */ jsxs2(Text2, { color: THEME.gray, children: [
16286
16376
  "[",
16287
16377
  proc.id,
16288
16378
  "]"
16289
16379
  ] }),
16290
- /* @__PURE__ */ jsx2(Text2, { children: " " }),
16380
+ /* @__PURE__ */ jsx3(Text2, { children: " " }),
16291
16381
  /* @__PURE__ */ jsxs2(Text2, { color: getRoleColor(proc.role), bold: true, children: [
16292
16382
  proc.role,
16293
16383
  port
@@ -16298,8 +16388,8 @@ function ProcessRow({ proc, isCompact }) {
16298
16388
  ")"
16299
16389
  ] }),
16300
16390
  truncatedPurpose && /* @__PURE__ */ jsxs2(Fragment, { children: [
16301
- /* @__PURE__ */ jsx2(Text2, { color: THEME.gray, children: " - " }),
16302
- /* @__PURE__ */ jsx2(Text2, { color: THEME.gray, children: truncatedPurpose })
16391
+ /* @__PURE__ */ jsx3(Text2, { color: THEME.gray, children: " - " }),
16392
+ /* @__PURE__ */ jsx3(Text2, { color: THEME.gray, children: truncatedPurpose })
16303
16393
  ] })
16304
16394
  ] });
16305
16395
  }
@@ -16310,7 +16400,7 @@ var InlineStatus = ({
16310
16400
  isCompact = true
16311
16401
  }) => {
16312
16402
  if (processes.length === 0 && zombies.length === 0) {
16313
- return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { color: THEME.gray, children: "\u2022 No active background processes" }) });
16403
+ return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx3(Text2, { color: THEME.gray, children: "\u2022 No active background processes" }) });
16314
16404
  }
16315
16405
  const running = processes.filter((p) => p.isRunning);
16316
16406
  const stopped = processes.filter((p) => !p.isRunning);
@@ -16327,7 +16417,7 @@ var InlineStatus = ({
16327
16417
  running.length,
16328
16418
  ")"
16329
16419
  ] }),
16330
- running.map((proc) => /* @__PURE__ */ jsx2(ProcessRow, { proc, isCompact }, proc.id))
16420
+ running.map((proc) => /* @__PURE__ */ jsx3(ProcessRow, { proc, isCompact }, proc.id))
16331
16421
  ] }),
16332
16422
  stopped.length > 0 && !isCompact && /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: running.length > 0 ? 1 : 0, children: [
16333
16423
  /* @__PURE__ */ jsxs2(Text2, { color: THEME.gray, children: [
@@ -16336,7 +16426,7 @@ var InlineStatus = ({
16336
16426
  stopped.length,
16337
16427
  ")"
16338
16428
  ] }),
16339
- stopped.slice(0, TUI_DISPLAY_LIMITS.maxStoppedProcesses).map((proc) => /* @__PURE__ */ jsx2(ProcessRow, { proc, isCompact }, proc.id)),
16429
+ stopped.slice(0, TUI_DISPLAY_LIMITS.maxStoppedProcesses).map((proc) => /* @__PURE__ */ jsx3(ProcessRow, { proc, isCompact }, proc.id)),
16340
16430
  stopped.length > TUI_DISPLAY_LIMITS.maxStoppedProcesses && /* @__PURE__ */ jsxs2(Text2, { color: THEME.gray, children: [
16341
16431
  " ... and ",
16342
16432
  stopped.length - TUI_DISPLAY_LIMITS.maxStoppedProcesses,
@@ -16366,11 +16456,11 @@ var InlineStatus = ({
16366
16456
  " orphaned children"
16367
16457
  ] })
16368
16458
  ] }, z.processId)),
16369
- /* @__PURE__ */ jsx2(Text2, { color: THEME.gray, children: " Run /cleanup to terminate" })
16459
+ /* @__PURE__ */ jsx3(Text2, { color: THEME.gray, children: " Run /cleanup to terminate" })
16370
16460
  ] }),
16371
16461
  /* @__PURE__ */ jsxs2(Box2, { marginTop: running.length > 0 ? 1 : 0, children: [
16372
- /* @__PURE__ */ jsx2(Text2, { color: THEME.gray, children: "Health: " }),
16373
- /* @__PURE__ */ jsx2(Text2, { color: healthColor, bold: true, children: health.toUpperCase() })
16462
+ /* @__PURE__ */ jsx3(Text2, { color: THEME.gray, children: "Health: " }),
16463
+ /* @__PURE__ */ jsx3(Text2, { color: healthColor, bold: true, children: health.toUpperCase() })
16374
16464
  ] })
16375
16465
  ] });
16376
16466
  };
@@ -16378,7 +16468,7 @@ var InlineStatus = ({
16378
16468
  // src/platform/tui/components/ToolCard.tsx
16379
16469
  import { memo as memo2 } from "react";
16380
16470
  import { Box as Box3, Text as Text3 } from "ink";
16381
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
16471
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
16382
16472
  var ARGS_MAX = 72;
16383
16473
  function truncate(s, max) {
16384
16474
  return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
@@ -16423,12 +16513,12 @@ function parseToolContent(content) {
16423
16513
  var ToolCard = memo2(({ content }) => {
16424
16514
  const { name, args } = parseToolContent(content);
16425
16515
  return /* @__PURE__ */ jsxs3(Box3, { children: [
16426
- /* @__PURE__ */ jsx3(Text3, { color: THEME.primary, children: " \u2295 " }),
16427
- /* @__PURE__ */ jsx3(Text3, { color: THEME.white, bold: true, children: name }),
16516
+ /* @__PURE__ */ jsx4(Text3, { color: THEME.primary, children: " \u2295 " }),
16517
+ /* @__PURE__ */ jsx4(Text3, { color: THEME.white, bold: true, children: name }),
16428
16518
  args && /* @__PURE__ */ jsxs3(Fragment2, { children: [
16429
- /* @__PURE__ */ jsx3(Text3, { color: THEME.dimGray, children: "(" }),
16430
- /* @__PURE__ */ jsx3(Text3, { color: THEME.gray, children: args }),
16431
- /* @__PURE__ */ jsx3(Text3, { color: THEME.dimGray, children: ")" })
16519
+ /* @__PURE__ */ jsx4(Text3, { color: THEME.dimGray, children: "(" }),
16520
+ /* @__PURE__ */ jsx4(Text3, { color: THEME.gray, children: args }),
16521
+ /* @__PURE__ */ jsx4(Text3, { color: THEME.dimGray, children: ")" })
16432
16522
  ] })
16433
16523
  ] });
16434
16524
  });
@@ -16436,7 +16526,7 @@ var ToolCard = memo2(({ content }) => {
16436
16526
  // src/platform/tui/components/MarkdownText.tsx
16437
16527
  import { memo as memo3 } from "react";
16438
16528
  import { Box as Box4, Text as Text4 } from "ink";
16439
- import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
16529
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
16440
16530
  function tokenizeLine(line) {
16441
16531
  const tokens = [];
16442
16532
  let i = 0;
@@ -16460,14 +16550,14 @@ function tokenizeLine(line) {
16460
16550
  }
16461
16551
  return tokens;
16462
16552
  }
16463
- var InlineContent = ({ tokens }) => /* @__PURE__ */ jsx4(Fragment3, { children: tokens.map((tok, i) => {
16553
+ var InlineContent = ({ tokens }) => /* @__PURE__ */ jsx5(Fragment3, { children: tokens.map((tok, i) => {
16464
16554
  if (tok.type === "bold") {
16465
- return /* @__PURE__ */ jsx4(Text4, { bold: true, color: THEME.white, children: tok.value }, i);
16555
+ return /* @__PURE__ */ jsx5(Text4, { bold: true, color: THEME.white, children: tok.value }, i);
16466
16556
  }
16467
16557
  if (tok.type === "code") {
16468
- return /* @__PURE__ */ jsx4(Text4, { color: THEME.cyan, children: `\`${tok.value}\`` }, i);
16558
+ return /* @__PURE__ */ jsx5(Text4, { color: THEME.cyan, children: `\`${tok.value}\`` }, i);
16469
16559
  }
16470
- return /* @__PURE__ */ jsx4(Text4, { color: THEME.white, children: tok.value }, i);
16560
+ return /* @__PURE__ */ jsx5(Text4, { color: THEME.white, children: tok.value }, i);
16471
16561
  }) });
16472
16562
  function parseBlocks(lines) {
16473
16563
  const blocks = [];
@@ -16517,12 +16607,12 @@ function parseBlocks(lines) {
16517
16607
  }
16518
16608
  var BlockRenderer = ({ block, blockIdx }) => {
16519
16609
  if (block.type === "blank") {
16520
- return /* @__PURE__ */ jsx4(Text4, { children: " " }, blockIdx);
16610
+ return /* @__PURE__ */ jsx5(Text4, { children: " " }, blockIdx);
16521
16611
  }
16522
16612
  if (block.type === "heading") {
16523
16613
  const prefixes = { 1: "\u25B8 ", 2: " \u25B9 ", 3: " \xB7 " };
16524
16614
  const colors = { 1: THEME.primary, 2: THEME.cyan, 3: THEME.white };
16525
- return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { color: colors[block.level], bold: true, children: [
16615
+ return /* @__PURE__ */ jsx5(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { color: colors[block.level], bold: true, children: [
16526
16616
  prefixes[block.level],
16527
16617
  block.content
16528
16618
  ] }) }, blockIdx);
@@ -16534,7 +16624,7 @@ var BlockRenderer = ({ block, blockIdx }) => {
16534
16624
  bullet,
16535
16625
  " "
16536
16626
  ] }),
16537
- /* @__PURE__ */ jsx4(InlineContent, { tokens: tokenizeLine(block.content) })
16627
+ /* @__PURE__ */ jsx5(InlineContent, { tokens: tokenizeLine(block.content) })
16538
16628
  ] }, blockIdx);
16539
16629
  }
16540
16630
  if (block.type === "codeBlock") {
@@ -16547,19 +16637,19 @@ var BlockRenderer = ({ block, blockIdx }) => {
16547
16637
  paddingX: 1,
16548
16638
  marginY: 0,
16549
16639
  children: [
16550
- block.lang && /* @__PURE__ */ jsx4(Text4, { color: THEME.dimGray, children: block.lang }),
16551
- block.lines.map((l, li) => /* @__PURE__ */ jsx4(Text4, { color: THEME.cyan, children: l }, li))
16640
+ block.lang && /* @__PURE__ */ jsx5(Text4, { color: THEME.dimGray, children: block.lang }),
16641
+ block.lines.map((l, li) => /* @__PURE__ */ jsx5(Text4, { color: THEME.cyan, children: l }, li))
16552
16642
  ]
16553
16643
  },
16554
16644
  blockIdx
16555
16645
  );
16556
16646
  }
16557
- return /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsx4(InlineContent, { tokens: tokenizeLine(block.content) }) }, blockIdx);
16647
+ return /* @__PURE__ */ jsx5(Box4, { children: /* @__PURE__ */ jsx5(InlineContent, { tokens: tokenizeLine(block.content) }) }, blockIdx);
16558
16648
  };
16559
16649
  var MarkdownText = memo3(({ content }) => {
16560
16650
  const lines = content.split("\n");
16561
16651
  const blocks = parseBlocks(lines);
16562
- return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", children: blocks.map((block, i) => /* @__PURE__ */ jsx4(BlockRenderer, { block, blockIdx: i }, i)) });
16652
+ return /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", children: blocks.map((block, i) => /* @__PURE__ */ jsx5(BlockRenderer, { block, blockIdx: i }, i)) });
16563
16653
  });
16564
16654
 
16565
16655
  // src/platform/tui/components/messages/parseStatusContent.ts
@@ -16592,27 +16682,26 @@ function classifyResult(content) {
16592
16682
  if (content.startsWith("\u26A0") || content.startsWith("!")) return RESULT_KINDS.WARNING;
16593
16683
  return RESULT_KINDS.NEUTRAL;
16594
16684
  }
16595
- function computeResultDisplay(content, isExpanded) {
16685
+ function computeResultDisplay(content) {
16596
16686
  const kind = classifyResult(content);
16597
16687
  const isFailure = kind === RESULT_KINDS.FAILURE;
16598
16688
  const lines = content.split("\n");
16599
16689
  const firstLine = lines[0] || "";
16600
16690
  const restLines = lines.slice(1);
16601
16691
  const hiddenCount = restLines.length;
16602
- const effectiveExpanded = isFailure ? true : isExpanded;
16603
- const showCount = effectiveExpanded ? hiddenCount : Math.min(hiddenCount, RESULT_PREVIEW_LINES);
16604
- const visibleRest = restLines.slice(0, showCount);
16605
- const hasMore = !effectiveExpanded && hiddenCount > RESULT_PREVIEW_LINES;
16606
- 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 };
16607
16696
  }
16608
16697
 
16609
16698
  // src/platform/tui/components/messages/MessageRow.tsx
16610
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
16611
- var MessageRow = memo4(({ msg, isExpanded }) => {
16699
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
16700
+ var MessageRow = memo4(({ msg }) => {
16612
16701
  if (msg.type === "status") {
16613
16702
  const statusData = parseStatusContent(msg.content);
16614
16703
  if (statusData) {
16615
- return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", children: /* @__PURE__ */ jsx5(
16704
+ return /* @__PURE__ */ jsx6(Box5, { flexDirection: "column", children: /* @__PURE__ */ jsx6(
16616
16705
  InlineStatus,
16617
16706
  {
16618
16707
  processes: statusData.processes,
@@ -16622,70 +16711,61 @@ var MessageRow = memo4(({ msg, isExpanded }) => {
16622
16711
  ) }, msg.id);
16623
16712
  }
16624
16713
  return /* @__PURE__ */ jsxs5(Box5, { children: [
16625
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, color: THEME.dimGray, children: " \xB7 " }),
16626
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, color: THEME.gray, children: msg.content })
16714
+ /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.dimGray, children: " \xB7 " }),
16715
+ /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.gray, children: msg.content })
16627
16716
  ] }, msg.id);
16628
16717
  }
16629
16718
  if (msg.type === "thinking") {
16630
- return /* @__PURE__ */ jsx5(ThinkingBlock, { msg, isExpanded });
16719
+ return /* @__PURE__ */ jsx6(ThinkingBlock, { msg });
16631
16720
  }
16632
16721
  if (msg.type === "tool") {
16633
- return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(ToolCard, { content: msg.content }) }, msg.id);
16722
+ return /* @__PURE__ */ jsx6(Box5, { children: /* @__PURE__ */ jsx6(ToolCard, { content: msg.content }) }, msg.id);
16634
16723
  }
16635
16724
  if (msg.type === "result") {
16636
16725
  const kind = classifyResult(msg.content);
16637
16726
  const color = kind === "success" ? THEME.primary : kind === "failure" ? THEME.red : kind === "warning" ? THEME.yellow : THEME.gray;
16638
- const { firstLine, visibleRest, hasMore, effectiveExpanded } = computeResultDisplay(msg.content, isExpanded);
16727
+ const { firstLine, visibleRest, hasMore } = computeResultDisplay(msg.content);
16639
16728
  const hiddenCount = msg.content.split("\n").length - 1;
16640
- const isFailure = kind === "failure";
16641
16729
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
16642
16730
  /* @__PURE__ */ jsxs5(Box5, { children: [
16643
- /* @__PURE__ */ jsx5(Text5, { color: THEME.dimGray, children: " \u2514 " }),
16644
- /* @__PURE__ */ jsx5(Text5, { color, children: firstLine }),
16645
- hasMore && /* @__PURE__ */ jsx5(Text5, { dimColor: true, color: THEME.dimGray, children: ` [+${hiddenCount - RESULT_PREVIEW_LINES} \xB7 T]` }),
16646
- effectiveExpanded && hiddenCount > RESULT_PREVIEW_LINES && !isFailure && /* @__PURE__ */ jsx5(Text5, { dimColor: true, color: THEME.dimGray, children: " [T \u2193]" })
16731
+ /* @__PURE__ */ jsx6(Text5, { color: THEME.dimGray, children: " \u2514 " }),
16732
+ /* @__PURE__ */ jsx6(Text5, { color, children: firstLine }),
16733
+ hasMore && /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.dimGray, children: ` [+${hiddenCount - RESULT_PREVIEW_LINES}]` })
16647
16734
  ] }),
16648
16735
  visibleRest.map((line, i) => /* @__PURE__ */ jsxs5(Box5, { children: [
16649
- /* @__PURE__ */ jsx5(Text5, { color: THEME.dimGray, children: " " }),
16650
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, color: THEME.gray, children: line })
16736
+ /* @__PURE__ */ jsx6(Text5, { color: THEME.dimGray, children: " " }),
16737
+ /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.gray, children: line })
16651
16738
  ] }, i))
16652
16739
  ] }, msg.id);
16653
16740
  }
16654
16741
  if (msg.type === "ai" || msg.type === "assistant") {
16655
- return /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", marginBottom: 0, children: /* @__PURE__ */ jsx5(MarkdownText, { content: msg.content }) }, msg.id);
16742
+ return /* @__PURE__ */ jsx6(Box5, { flexDirection: "column", marginBottom: 0, children: /* @__PURE__ */ jsx6(MarkdownText, { content: msg.content }) }, msg.id);
16656
16743
  }
16657
16744
  if (msg.type === "error") {
16658
16745
  const eLines = msg.content.split("\n");
16659
16746
  const eFirst = eLines[0] || msg.content;
16660
16747
  const eHidden = eLines.length - 1;
16661
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
16662
- /* @__PURE__ */ jsxs5(Box5, { children: [
16663
- /* @__PURE__ */ jsx5(Text5, { color: THEME.red, children: " \u2717 " }),
16664
- /* @__PURE__ */ jsx5(Text5, { color: THEME.red, children: eFirst }),
16665
- !isExpanded && eHidden > 0 && /* @__PURE__ */ jsx5(Text5, { dimColor: true, color: THEME.dimGray, children: ` [+${eHidden} \xB7 E]` }),
16666
- isExpanded && eHidden > 0 && /* @__PURE__ */ jsx5(Text5, { dimColor: true, color: THEME.dimGray, children: " [E \u2191]" })
16667
- ] }),
16668
- isExpanded && eLines.slice(1).map((line, i) => /* @__PURE__ */ jsxs5(Box5, { children: [
16669
- /* @__PURE__ */ jsx5(Text5, { color: THEME.dimGray, children: " " }),
16670
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, color: THEME.red, children: line })
16671
- ] }, i))
16672
- ] }, 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);
16673
16753
  }
16674
16754
  if (msg.type === "user") {
16675
16755
  return /* @__PURE__ */ jsxs5(Box5, { children: [
16676
- /* @__PURE__ */ jsx5(Text5, { color: THEME.primary, children: "\u276F " }),
16677
- /* @__PURE__ */ jsx5(Text5, { color: THEME.white, bold: true, children: msg.content })
16756
+ /* @__PURE__ */ jsx6(Text5, { color: THEME.primary, children: "\u276F " }),
16757
+ /* @__PURE__ */ jsx6(Text5, { color: THEME.white, bold: true, children: msg.content })
16678
16758
  ] }, msg.id);
16679
16759
  }
16680
16760
  if (msg.type === "system") {
16681
16761
  return /* @__PURE__ */ jsxs5(Box5, { children: [
16682
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, color: THEME.dimGray, children: " \xB7 " }),
16683
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, color: THEME.gray, children: msg.content })
16762
+ /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.dimGray, children: " \xB7 " }),
16763
+ /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.gray, children: msg.content })
16684
16764
  ] }, msg.id);
16685
16765
  }
16686
16766
  return /* @__PURE__ */ jsxs5(Box5, { children: [
16687
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, color: THEME.dimGray, children: " \xB7 " }),
16688
- /* @__PURE__ */ jsx5(Text5, { color: MESSAGE_STYLES.colors[msg.type] ?? THEME.gray, children: msg.content })
16767
+ /* @__PURE__ */ jsx6(Text5, { dimColor: true, color: THEME.dimGray, children: " \xB7 " }),
16768
+ /* @__PURE__ */ jsx6(Text5, { color: MESSAGE_STYLES.colors[msg.type] ?? THEME.gray, children: msg.content })
16689
16769
  ] }, msg.id);
16690
16770
  });
16691
16771
 
@@ -16696,36 +16776,36 @@ import { Box as Box7, Text as Text7 } from "ink";
16696
16776
  // src/platform/tui/components/ShimmerBanner.tsx
16697
16777
  import { memo as memo5 } from "react";
16698
16778
  import { Box as Box6, Text as Text6 } from "ink";
16699
- import { jsx as jsx6 } from "react/jsx-runtime";
16779
+ import { jsx as jsx7 } from "react/jsx-runtime";
16700
16780
  var ShimmerBanner = memo5(({ banner }) => {
16701
16781
  const lines = banner.split("\n").filter((l) => l.length > 0);
16702
- return /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: lines.map((line, row) => /* @__PURE__ */ jsx6(Text6, { color: HEX.primary, children: line }, row)) });
16782
+ return /* @__PURE__ */ jsx7(Box6, { flexDirection: "column", children: lines.map((line, row) => /* @__PURE__ */ jsx7(Text6, { color: HEX.primary, children: line }, row)) });
16703
16783
  });
16704
16784
 
16705
16785
  // src/platform/tui/components/messages/EmptyState.tsx
16706
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
16786
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
16707
16787
  var EmptyState = memo6(({ modelName, autoApproveMode, version }) => {
16708
16788
  const autoLabel = autoApproveMode ? "ON" : "OFF";
16709
16789
  const autoColor = autoApproveMode ? THEME.primary : THEME.gray;
16710
16790
  return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [
16711
- /* @__PURE__ */ jsx7(ShimmerBanner, { banner: ASCII_BANNER }),
16712
- /* @__PURE__ */ jsx7(Text7, { children: " " }),
16791
+ /* @__PURE__ */ jsx8(ShimmerBanner, { banner: ASCII_BANNER }),
16792
+ /* @__PURE__ */ jsx8(Text7, { children: " " }),
16713
16793
  /* @__PURE__ */ jsxs6(Box7, { gap: 2, children: [
16714
- /* @__PURE__ */ jsx7(Text7, { color: THEME.primary, bold: true, children: "\u25C8 Pentesting" }),
16715
- version && /* @__PURE__ */ jsx7(Text7, { color: THEME.dimGray, children: `v${version}` }),
16794
+ /* @__PURE__ */ jsx8(Text7, { color: THEME.primary, bold: true, children: "\u25C8 Pentesting" }),
16795
+ version && /* @__PURE__ */ jsx8(Text7, { color: THEME.dimGray, children: `v${version}` }),
16716
16796
  modelName && /* @__PURE__ */ jsxs6(Text7, { color: THEME.dimGray, children: [
16717
16797
  "\xB7",
16718
16798
  " ",
16719
- /* @__PURE__ */ jsx7(Text7, { color: THEME.gray, children: modelName })
16799
+ /* @__PURE__ */ jsx8(Text7, { color: THEME.gray, children: modelName })
16720
16800
  ] }),
16721
16801
  /* @__PURE__ */ jsxs6(Text7, { color: THEME.dimGray, children: [
16722
16802
  "\xB7",
16723
16803
  " Auto: ",
16724
- /* @__PURE__ */ jsx7(Text7, { color: autoColor, children: autoLabel })
16804
+ /* @__PURE__ */ jsx8(Text7, { color: autoColor, children: autoLabel })
16725
16805
  ] })
16726
16806
  ] }),
16727
- /* @__PURE__ */ jsx7(Text7, { children: " " }),
16728
- /* @__PURE__ */ jsx7(Text7, { color: THEME.gray, children: " Get started:" }),
16807
+ /* @__PURE__ */ jsx8(Text7, { children: " " }),
16808
+ /* @__PURE__ */ jsx8(Text7, { color: THEME.gray, children: " Get started:" }),
16729
16809
  /* @__PURE__ */ jsxs6(Text7, { color: THEME.gray, children: [
16730
16810
  " ",
16731
16811
  " ",
@@ -16738,88 +16818,25 @@ var EmptyState = memo6(({ modelName, autoApproveMode, version }) => {
16738
16818
  /* @__PURE__ */ jsxs6(Text7, { color: THEME.gray, children: [
16739
16819
  " ",
16740
16820
  " ",
16741
- /* @__PURE__ */ jsx7(Text7, { color: THEME.white, children: "/start" }),
16821
+ /* @__PURE__ */ jsx8(Text7, { color: THEME.white, children: "/start" }),
16742
16822
  " \u2014 Begin autonomous pentest"
16743
16823
  ] }),
16744
16824
  /* @__PURE__ */ jsxs6(Text7, { color: THEME.gray, children: [
16745
16825
  " ",
16746
16826
  " ",
16747
- /* @__PURE__ */ jsx7(Text7, { color: THEME.white, children: "/help" }),
16827
+ /* @__PURE__ */ jsx8(Text7, { color: THEME.white, children: "/help" }),
16748
16828
  " \u2014 Show all commands"
16749
16829
  ] }),
16750
- /* @__PURE__ */ jsx7(Text7, { children: " " }),
16751
- /* @__PURE__ */ jsx7(Text7, { color: THEME.dimGray, children: " Or just type a task and press Enter." })
16830
+ /* @__PURE__ */ jsx8(Text7, { children: " " }),
16831
+ /* @__PURE__ */ jsx8(Text7, { color: THEME.dimGray, children: " Or just type a task and press Enter." })
16752
16832
  ] });
16753
16833
  });
16754
16834
 
16755
- // src/platform/tui/components/messages/sliding-window.ts
16756
- var MAX_VISIBLE_MESSAGES = 200;
16757
- function computeSlidingWindow(messages, scrollOffset) {
16758
- const allVisible = messages.length > MAX_VISIBLE_MESSAGES ? messages.slice(-MAX_VISIBLE_MESSAGES) : messages;
16759
- const clampedOffset = Math.min(scrollOffset, Math.max(0, allVisible.length - 1));
16760
- const visibleMessages = clampedOffset === 0 ? allVisible : allVisible.slice(0, allVisible.length - clampedOffset);
16761
- const hiddenAbove = allVisible.length - visibleMessages.length;
16762
- return { visibleMessages, hiddenAbove, clampedOffset };
16763
- }
16764
-
16765
16835
  // src/platform/tui/components/MessageList.tsx
16766
- import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
16767
- var MessageList = memo7(({ messages, isModalOpen, modelName, autoApproveMode, version, scrollOffset = 0 }) => {
16768
- const [expandedIds, setExpandedIds] = useState3(/* @__PURE__ */ new Set());
16769
- const messagesRef = useRef6(messages);
16770
- messagesRef.current = messages;
16771
- const isModalOpenRef = useRef6(isModalOpen);
16772
- isModalOpenRef.current = isModalOpen;
16773
- useInput2(useCallback8((_ch, key) => {
16774
- if (isModalOpenRef.current || key.ctrl) return;
16775
- if (_ch === "r") {
16776
- const msgs = messagesRef.current;
16777
- for (let i = msgs.length - 1; i >= 0; i--) {
16778
- if (msgs[i].type === "thinking") {
16779
- const id = msgs[i].id;
16780
- setExpandedIds((prev) => {
16781
- const next = new Set(prev);
16782
- if (next.has(id)) next.delete(id);
16783
- else next.add(id);
16784
- return next;
16785
- });
16786
- break;
16787
- }
16788
- }
16789
- }
16790
- if (_ch === "e") {
16791
- const msgs = messagesRef.current;
16792
- for (let i = msgs.length - 1; i >= 0; i--) {
16793
- if (msgs[i].type === "error") {
16794
- const id = msgs[i].id;
16795
- setExpandedIds((prev) => {
16796
- const next = new Set(prev);
16797
- if (next.has(id)) next.delete(id);
16798
- else next.add(id);
16799
- return next;
16800
- });
16801
- break;
16802
- }
16803
- }
16804
- }
16805
- if (_ch === "t") {
16806
- const msgs = messagesRef.current;
16807
- for (let i = msgs.length - 1; i >= 0; i--) {
16808
- if (msgs[i].type === "result") {
16809
- const id = msgs[i].id;
16810
- setExpandedIds((prev) => {
16811
- const next = new Set(prev);
16812
- if (next.has(id)) next.delete(id);
16813
- else next.add(id);
16814
- return next;
16815
- });
16816
- break;
16817
- }
16818
- }
16819
- }
16820
- }, []));
16836
+ import { jsx as jsx9 } from "react/jsx-runtime";
16837
+ var MessageList = memo7(({ messages, modelName, autoApproveMode, version }) => {
16821
16838
  if (messages.length === 0) {
16822
- return /* @__PURE__ */ jsx8(
16839
+ return /* @__PURE__ */ jsx9(
16823
16840
  EmptyState,
16824
16841
  {
16825
16842
  modelName,
@@ -16828,31 +16845,20 @@ var MessageList = memo7(({ messages, isModalOpen, modelName, autoApproveMode, ve
16828
16845
  }
16829
16846
  );
16830
16847
  }
16831
- const { visibleMessages, hiddenAbove, clampedOffset } = computeSlidingWindow(messages, scrollOffset);
16832
- return /* @__PURE__ */ jsxs7(Box8, { flexDirection: "column", children: [
16833
- clampedOffset > 0 && /* @__PURE__ */ jsx8(Box8, { paddingX: 1, children: /* @__PURE__ */ jsx8(Text8, { dimColor: true, color: THEME.dimGray, children: `\u2191 ${hiddenAbove} message${hiddenAbove !== 1 ? "s" : ""} above \xB7 PgDn to scroll down` }) }),
16834
- visibleMessages.map((msg) => /* @__PURE__ */ jsx8(
16835
- MessageRow,
16836
- {
16837
- msg,
16838
- isExpanded: expandedIds.has(msg.id)
16839
- },
16840
- msg.id
16841
- ))
16842
- ] });
16848
+ return /* @__PURE__ */ jsx9(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsx9(Static, { items: messages, children: (msg) => /* @__PURE__ */ jsx9(MessageRow, { msg }, msg.id) }) });
16843
16849
  });
16844
16850
 
16845
16851
  // src/platform/tui/components/StatusDisplay.tsx
16846
16852
  import { memo as memo10 } from "react";
16847
- import { Box as Box11, Text as Text13 } from "ink";
16853
+ import { Box as Box11, Text as Text12 } from "ink";
16848
16854
 
16849
16855
  // src/platform/tui/hooks/useStatusTimer.ts
16850
- import { useState as useState4, useEffect as useEffect5, useRef as useRef7 } from "react";
16856
+ import { useState as useState4, useEffect as useEffect6, useRef as useRef5 } from "react";
16851
16857
  var useStatusTimer = (currentStatus, isProcessing) => {
16852
16858
  const [statusElapsed, setStatusElapsed] = useState4(0);
16853
- const statusTimerRef = useRef7(null);
16854
- const statusStartRef = useRef7(Date.now());
16855
- useEffect5(() => {
16859
+ const statusTimerRef = useRef5(null);
16860
+ const statusStartRef = useRef5(Date.now());
16861
+ useEffect6(() => {
16856
16862
  if (statusTimerRef.current) clearInterval(statusTimerRef.current);
16857
16863
  if (isProcessing && currentStatus) {
16858
16864
  statusStartRef.current = Date.now();
@@ -16872,12 +16878,12 @@ var useStatusTimer = (currentStatus, isProcessing) => {
16872
16878
  };
16873
16879
 
16874
16880
  // src/platform/tui/components/status/RetryView.tsx
16875
- import { Box as Box9, Text as Text10 } from "ink";
16881
+ import { Box as Box9, Text as Text9 } from "ink";
16876
16882
 
16877
- // src/platform/tui/components/MusicSpinner.tsx
16878
- import { useState as useState5, useEffect as useEffect6, memo as memo8 } from "react";
16879
- import { Text as Text9 } from "ink";
16880
- import { jsx as jsx9 } from "react/jsx-runtime";
16883
+ // src/platform/tui/components/StarSpinner.tsx
16884
+ import { memo as memo8 } from "react";
16885
+ import { Text as Text8 } from "ink";
16886
+ import { jsx as jsx10 } from "react/jsx-runtime";
16881
16887
  var FRAMES = [
16882
16888
  "\xB7",
16883
16889
  "\u2726",
@@ -16894,40 +16900,34 @@ var FRAMES = [
16894
16900
  "\u2727",
16895
16901
  "\u2726"
16896
16902
  ];
16897
- var INTERVAL = 150;
16898
- var MusicSpinner = memo8(({ color }) => {
16899
- const [index, setIndex] = useState5(0);
16900
- useEffect6(() => {
16901
- const timer = setInterval(() => {
16902
- setIndex((i) => (i + 1) % FRAMES.length);
16903
- }, INTERVAL);
16904
- return () => clearInterval(timer);
16905
- }, []);
16906
- return /* @__PURE__ */ jsx9(Text9, { color: color || "yellow", children: FRAMES[index] });
16903
+ var StarSpinner = memo8(({ color }) => {
16904
+ const tick = useAnimationTick();
16905
+ const index = tick % FRAMES.length;
16906
+ return /* @__PURE__ */ jsx10(Text8, { color: color || "yellow", children: FRAMES[index] });
16907
16907
  });
16908
16908
 
16909
16909
  // src/platform/tui/components/status/RetryView.tsx
16910
- import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
16910
+ import { jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
16911
16911
  var RetryView = ({ retryState }) => {
16912
16912
  const truncateError = (err) => {
16913
16913
  return err.length > DISPLAY_LIMITS.RETRY_ERROR_PREVIEW ? err.substring(0, DISPLAY_LIMITS.RETRY_ERROR_TRUNCATED) + "..." : err;
16914
16914
  };
16915
- return /* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", height: 2, children: [
16916
- /* @__PURE__ */ jsxs8(Box9, { children: [
16917
- /* @__PURE__ */ jsx10(Text10, { color: THEME.yellow, wrap: "truncate", children: /* @__PURE__ */ jsx10(MusicSpinner, { color: THEME.yellow }) }),
16918
- /* @__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: [
16919
16919
  " \u29F3 Retry #",
16920
16920
  retryState.attempt,
16921
16921
  "/",
16922
16922
  retryState.maxRetries
16923
16923
  ] }),
16924
- /* @__PURE__ */ jsxs8(Text10, { color: THEME.gray, wrap: "truncate", children: [
16924
+ /* @__PURE__ */ jsxs7(Text9, { color: THEME.gray, wrap: "truncate", children: [
16925
16925
  " \u2014 ",
16926
16926
  retryState.countdown,
16927
16927
  "s"
16928
16928
  ] })
16929
16929
  ] }),
16930
- /* @__PURE__ */ jsx10(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: [
16931
16931
  " ",
16932
16932
  truncateError(retryState.error)
16933
16933
  ] }) })
@@ -16935,14 +16935,13 @@ var RetryView = ({ retryState }) => {
16935
16935
  };
16936
16936
 
16937
16937
  // src/platform/tui/components/status/ProcessingView.tsx
16938
- import { Box as Box10, Text as Text12 } from "ink";
16938
+ import { Box as Box10, Text as Text11 } from "ink";
16939
16939
 
16940
16940
  // src/platform/tui/components/ShimmerText.tsx
16941
- import { useState as useState6, useEffect as useEffect7, memo as memo9 } from "react";
16942
- import { Text as Text11 } from "ink";
16943
- import { jsx as jsx11 } from "react/jsx-runtime";
16944
- var FRAME_INTERVAL = 120;
16945
- var WAVE_SPEED = 0.25;
16941
+ import { memo as memo9 } from "react";
16942
+ import { Text as Text10 } from "ink";
16943
+ import { jsx as jsx12 } from "react/jsx-runtime";
16944
+ var WAVE_SPEED = 0.25 * (120 / ANIM_TICK_MS);
16946
16945
  var CHAR_PHASE_GAP = 0.55;
16947
16946
  function sinToColor(sin) {
16948
16947
  const t = (sin + 1) / 2;
@@ -16951,24 +16950,18 @@ function sinToColor(sin) {
16951
16950
  return `#${hex}${hex}${hex}`;
16952
16951
  }
16953
16952
  var ShimmerText = memo9(({ children, bold, phase = 0 }) => {
16954
- const [tick, setTick] = useState6(0);
16955
- useEffect7(() => {
16956
- const timer = setInterval(() => {
16957
- setTick((t) => t + 1);
16958
- }, FRAME_INTERVAL);
16959
- return () => clearInterval(timer);
16960
- }, []);
16953
+ const tick = useAnimationTick();
16961
16954
  const globalPhase = tick * WAVE_SPEED + phase;
16962
- return /* @__PURE__ */ jsx11(Text11, { bold, children: Array.from(children).map((char, i) => {
16955
+ return /* @__PURE__ */ jsx12(Text10, { bold, children: Array.from(children).map((char, i) => {
16963
16956
  const charPhase = globalPhase - i * CHAR_PHASE_GAP;
16964
16957
  const sin = Math.sin(charPhase);
16965
16958
  const color = sinToColor(sin);
16966
- return /* @__PURE__ */ jsx11(Text11, { color, children: char }, i);
16959
+ return /* @__PURE__ */ jsx12(Text10, { color, children: char }, i);
16967
16960
  }) });
16968
16961
  });
16969
16962
 
16970
16963
  // src/platform/tui/components/status/ProcessingView.tsx
16971
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
16964
+ import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
16972
16965
  var ProcessingView = ({
16973
16966
  statusMain,
16974
16967
  detailText,
@@ -16984,41 +16977,53 @@ var ProcessingView = ({
16984
16977
  return parts.length > 0 ? `(${parts.join(" \xB7 ")})` : "";
16985
16978
  };
16986
16979
  const meta = buildMeta();
16987
- return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", height: 2, children: [
16988
- /* @__PURE__ */ jsxs9(Box10, { children: [
16989
- /* @__PURE__ */ jsx12(Text12, { color, wrap: "truncate", children: /* @__PURE__ */ jsx12(MusicSpinner, { color }) }),
16990
- /* @__PURE__ */ jsx12(Text12, { children: " " }),
16991
- /* @__PURE__ */ jsx12(ShimmerText, { bold: true, phase: 0, children: statusMain }),
16992
- /* @__PURE__ */ jsxs9(Text12, { color: THEME.dimGray, wrap: "truncate", children: [
16980
+ const parenIdx = statusMain.indexOf("(");
16981
+ const shimmerPart = parenIdx > -1 ? statusMain.slice(0, parenIdx).trimEnd() : statusMain;
16982
+ const staticSuffix = parenIdx > -1 ? " " + statusMain.slice(parenIdx) : "";
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: " " }),
16987
+ /* @__PURE__ */ jsx13(ShimmerText, { bold: true, phase: 0, children: shimmerPart }),
16988
+ staticSuffix ? /* @__PURE__ */ jsx13(Text11, { color: THEME.dimGray, wrap: "truncate", children: staticSuffix }) : null,
16989
+ /* @__PURE__ */ jsxs8(Text11, { color: THEME.dimGray, wrap: "truncate", children: [
16993
16990
  " ",
16994
16991
  meta
16995
16992
  ] })
16996
16993
  ] }),
16997
- /* @__PURE__ */ jsx12(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: [
16998
16995
  " ",
16999
16996
  detailText
17000
- ] }) : /* @__PURE__ */ jsx12(Text12, { children: " " }) })
16997
+ ] }) : /* @__PURE__ */ jsx13(Text11, { children: " " }) })
17001
16998
  ] });
17002
16999
  };
17003
17000
 
17004
17001
  // src/platform/tui/components/StatusDisplay.tsx
17005
- import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
17002
+ import { jsx as jsx14, jsxs as jsxs9 } from "react/jsx-runtime";
17006
17003
  var StatusDisplay = memo10(({
17007
17004
  retryState,
17008
17005
  isProcessing,
17009
17006
  currentStatus,
17010
- currentTokens
17007
+ currentTokens,
17008
+ inputRequest
17011
17009
  }) => {
17012
- const statusElapsed = useStatusTimer(currentStatus, isProcessing);
17010
+ const isWaitingForInput = inputRequest?.status === "active";
17011
+ const statusElapsed = useStatusTimer(currentStatus, isProcessing && !isWaitingForInput);
17013
17012
  if (retryState && retryState.status === "retrying") {
17014
- return /* @__PURE__ */ jsx13(RetryView, { retryState });
17013
+ return /* @__PURE__ */ jsx14(RetryView, { retryState });
17014
+ }
17015
+ if (isProcessing && isWaitingForInput) {
17016
+ return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", height: 2, children: [
17017
+ /* @__PURE__ */ jsx14(Text12, { children: " " }),
17018
+ /* @__PURE__ */ jsx14(Text12, { children: " " })
17019
+ ] });
17015
17020
  }
17016
17021
  if (isProcessing) {
17017
17022
  const isThinkingStatus = currentStatus.startsWith("Reasoning");
17018
17023
  const statusLines = currentStatus ? currentStatus.split("\n").filter(Boolean) : [];
17019
17024
  const statusMain = statusLines[0] || "Processing...";
17020
17025
  const detailText = isThinkingStatus && statusLines.length > 1 ? statusLines[statusLines.length - 1].slice(0, 120) : "";
17021
- return /* @__PURE__ */ jsx13(
17026
+ return /* @__PURE__ */ jsx14(
17022
17027
  ProcessingView,
17023
17028
  {
17024
17029
  statusMain,
@@ -17029,46 +17034,46 @@ var StatusDisplay = memo10(({
17029
17034
  }
17030
17035
  );
17031
17036
  }
17032
- return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", height: 2, children: [
17033
- /* @__PURE__ */ jsx13(Text13, { children: " " }),
17034
- /* @__PURE__ */ jsx13(Text13, { children: " " })
17037
+ return /* @__PURE__ */ jsxs9(Box11, { flexDirection: "column", height: 2, children: [
17038
+ /* @__PURE__ */ jsx14(Text12, { children: " " }),
17039
+ /* @__PURE__ */ jsx14(Text12, { children: " " })
17035
17040
  ] });
17036
17041
  });
17037
17042
 
17038
17043
  // src/platform/tui/components/ChatInput.tsx
17039
- import { useMemo, useCallback as useCallback9, useRef as useRef8, memo as memo11, useState as useState7, useEffect as useEffect8 } from "react";
17040
- 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";
17041
17046
 
17042
17047
  // src/platform/tui/components/input/AutocompletePreview.tsx
17043
- import { Box as Box12, Text as Text14 } from "ink";
17044
- import { jsx as jsx14, 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";
17045
17050
  var AutocompletePreview = ({
17046
17051
  suggestions,
17047
17052
  clampedIdx
17048
17053
  }) => {
17049
- return /* @__PURE__ */ jsx14(Box12, { flexDirection: "column", paddingX: 1, children: suggestions.map((cmd, i) => {
17054
+ return /* @__PURE__ */ jsx15(Box12, { flexDirection: "column", paddingX: 1, children: suggestions.map((cmd, i) => {
17050
17055
  const isSelected = i === clampedIdx;
17051
17056
  const argsText = cmd.args ? ` ${cmd.args}` : "";
17052
- return /* @__PURE__ */ jsxs11(Box12, { children: [
17053
- /* @__PURE__ */ jsx14(Text14, { color: isSelected ? THEME.primary : THEME.dimGray, children: isSelected ? " \u276F " : " " }),
17054
- /* @__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: [
17055
17060
  "/",
17056
17061
  cmd.name
17057
17062
  ] }),
17058
- /* @__PURE__ */ jsx14(Text14, { dimColor: true, color: THEME.gray, children: argsText }),
17059
- /* @__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: [
17060
17065
  " \u2014 ",
17061
17066
  cmd.description
17062
17067
  ] }),
17063
- isSelected && cmd.alias && /* @__PURE__ */ jsx14(Text14, { dimColor: true, color: THEME.dimGray, children: ` (/${cmd.alias})` })
17068
+ isSelected && cmd.alias && /* @__PURE__ */ jsx15(Text13, { dimColor: true, color: THEME.dimGray, children: ` (/${cmd.alias})` })
17064
17069
  ] }, cmd.name);
17065
17070
  }) });
17066
17071
  };
17067
17072
 
17068
17073
  // src/platform/tui/components/input/SecretInputArea.tsx
17069
- import { Box as Box13, Text as Text15, useStdout } from "ink";
17074
+ import { Box as Box13, Text as Text14, useStdout } from "ink";
17070
17075
  import TextInput from "ink-text-input";
17071
- import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
17076
+ import { jsx as jsx16, jsxs as jsxs11 } from "react/jsx-runtime";
17072
17077
  var OUTER_PADDING = 2;
17073
17078
  var SecretInputArea = ({
17074
17079
  inputRequest,
@@ -17079,35 +17084,29 @@ var SecretInputArea = ({
17079
17084
  const { stdout } = useStdout();
17080
17085
  const borderWidth = Math.max(10, (stdout?.columns ?? 80) - OUTER_PADDING);
17081
17086
  const borderLine = "\u2501".repeat(borderWidth);
17082
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
17083
- /* @__PURE__ */ jsx15(Box13, { children: /* @__PURE__ */ jsx15(Text15, { color: THEME.yellow, children: borderLine }) }),
17084
- /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 1, children: [
17085
- /* @__PURE__ */ jsxs12(Box13, { children: [
17086
- /* @__PURE__ */ jsx15(Text15, { color: THEME.yellow, bold: true, children: "\u{1F512} " }),
17087
- /* @__PURE__ */ jsx15(Text15, { color: THEME.yellow, children: inputRequest.prompt && inputRequest.prompt.length > 68 ? inputRequest.prompt.slice(0, 68) + "\u2026" : inputRequest.prompt })
17088
- ] }),
17089
- /* @__PURE__ */ jsxs12(Box13, { children: [
17090
- /* @__PURE__ */ jsx15(Text15, { color: THEME.yellow, children: " \u25B8 " }),
17091
- /* @__PURE__ */ jsx15(
17092
- TextInput,
17093
- {
17094
- value: secretInput,
17095
- onChange: setSecretInput,
17096
- onSubmit: onSecretSubmit,
17097
- placeholder: "(type and press Enter)",
17098
- mask: inputRequest.isPassword ? "\u2022" : void 0
17099
- }
17100
- )
17101
- ] })
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 " }),
17091
+ /* @__PURE__ */ jsx16(
17092
+ TextInput,
17093
+ {
17094
+ value: secretInput,
17095
+ onChange: setSecretInput,
17096
+ onSubmit: onSecretSubmit,
17097
+ placeholder: inputRequest.isPassword ? "(hidden \xB7 press Enter)" : "(type and press Enter)",
17098
+ mask: inputRequest.isPassword ? "\u2022" : void 0
17099
+ }
17100
+ )
17102
17101
  ] }),
17103
- /* @__PURE__ */ jsx15(Box13, { children: /* @__PURE__ */ jsx15(Text15, { color: THEME.yellow, children: borderLine }) })
17102
+ /* @__PURE__ */ jsx16(Box13, { children: /* @__PURE__ */ jsx16(Text14, { color: THEME.yellow, children: borderLine }) })
17104
17103
  ] });
17105
17104
  };
17106
17105
 
17107
17106
  // src/platform/tui/components/input/NormalInputArea.tsx
17108
- 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";
17109
17108
  import TextInput2 from "ink-text-input";
17110
- import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
17109
+ import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
17111
17110
  var OUTER_PADDING2 = 2;
17112
17111
  var NormalInputArea = ({
17113
17112
  inputKey,
@@ -17119,11 +17118,11 @@ var NormalInputArea = ({
17119
17118
  const { stdout } = useStdout2();
17120
17119
  const borderWidth = Math.max(10, (stdout?.columns ?? 80) - OUTER_PADDING2);
17121
17120
  const borderLine = "\u2500".repeat(borderWidth);
17122
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
17123
- /* @__PURE__ */ jsx16(Box14, { children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, color: THEME.dimGray, children: borderLine }) }),
17124
- /* @__PURE__ */ jsxs13(Box14, { paddingX: 1, children: [
17125
- /* @__PURE__ */ jsx16(Text16, { color: THEME.primary, children: "\u276F " }),
17126
- /* @__PURE__ */ jsx16(
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 " }),
17125
+ /* @__PURE__ */ jsx17(
17127
17126
  TextInput2,
17128
17127
  {
17129
17128
  value,
@@ -17134,12 +17133,12 @@ var NormalInputArea = ({
17134
17133
  inputKey
17135
17134
  )
17136
17135
  ] }),
17137
- /* @__PURE__ */ jsx16(Box14, { children: /* @__PURE__ */ jsx16(Text16, { dimColor: true, color: THEME.dimGray, children: borderLine }) })
17136
+ /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, color: THEME.dimGray, children: borderLine }) })
17138
17137
  ] });
17139
17138
  };
17140
17139
 
17141
17140
  // src/platform/tui/components/ChatInput.tsx
17142
- import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
17141
+ import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
17143
17142
  var MAX_SUGGESTIONS = 6;
17144
17143
  var ChatInput = memo11(({
17145
17144
  value,
@@ -17159,26 +17158,26 @@ var ChatInput = memo11(({
17159
17158
  return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
17160
17159
  }, [isSlashMode, partialCmd, hasArgs]);
17161
17160
  const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
17162
- const [selectedIdx, setSelectedIdx] = useState7(0);
17161
+ const [selectedIdx, setSelectedIdx] = useState5(0);
17163
17162
  const clampedIdx = Math.min(selectedIdx, Math.max(0, suggestions.length - 1));
17164
- const selectedIdxRef = useRef8(clampedIdx);
17163
+ const selectedIdxRef = useRef6(clampedIdx);
17165
17164
  selectedIdxRef.current = clampedIdx;
17166
- const suggestionsRef = useRef8(suggestions);
17165
+ const suggestionsRef = useRef6(suggestions);
17167
17166
  suggestionsRef.current = suggestions;
17168
- const isSlashModeRef = useRef8(isSlashMode);
17167
+ const isSlashModeRef = useRef6(isSlashMode);
17169
17168
  isSlashModeRef.current = isSlashMode;
17170
- const hasArgsRef = useRef8(hasArgs);
17169
+ const hasArgsRef = useRef6(hasArgs);
17171
17170
  hasArgsRef.current = hasArgs;
17172
- const showPreviewRef = useRef8(showPreview);
17171
+ const showPreviewRef = useRef6(showPreview);
17173
17172
  showPreviewRef.current = showPreview;
17174
- const inputRequestRef = useRef8(inputRequest);
17173
+ const inputRequestRef = useRef6(inputRequest);
17175
17174
  inputRequestRef.current = inputRequest;
17176
- const onChangeRef = useRef8(onChange);
17175
+ const onChangeRef = useRef6(onChange);
17177
17176
  onChangeRef.current = onChange;
17178
- const [pastedHint, setPastedHint] = useState7(null);
17179
- const prevValueRef = useRef8(value);
17180
- const pasteTimerRef = useRef8(null);
17181
- useEffect8(() => {
17177
+ const [pastedHint, setPastedHint] = useState5(null);
17178
+ const prevValueRef = useRef6(value);
17179
+ const pasteTimerRef = useRef6(null);
17180
+ useEffect7(() => {
17182
17181
  const diff = value.length - prevValueRef.current.length;
17183
17182
  if (diff > 20) {
17184
17183
  if (pasteTimerRef.current) clearTimeout(pasteTimerRef.current);
@@ -17190,8 +17189,8 @@ var ChatInput = memo11(({
17190
17189
  if (pasteTimerRef.current) clearTimeout(pasteTimerRef.current);
17191
17190
  };
17192
17191
  }, [value]);
17193
- const [inputKey, setInputKey] = useState7(0);
17194
- const completeCommand = useCallback9((idx) => {
17192
+ const [inputKey, setInputKey] = useState5(0);
17193
+ const completeCommand = useCallback7((idx) => {
17195
17194
  const sug = suggestionsRef.current;
17196
17195
  if (!sug.length) return;
17197
17196
  const best = sug[Math.min(idx, sug.length - 1)];
@@ -17201,9 +17200,9 @@ var ChatInput = memo11(({
17201
17200
  setSelectedIdx(0);
17202
17201
  setInputKey((k) => k + 1);
17203
17202
  }, []);
17204
- const onSubmitRef = useRef8(onSubmit);
17203
+ const onSubmitRef = useRef6(onSubmit);
17205
17204
  onSubmitRef.current = onSubmit;
17206
- const wrappedOnSubmit = useCallback9((val) => {
17205
+ const wrappedOnSubmit = useCallback7((val) => {
17207
17206
  if (showPreviewRef.current) {
17208
17207
  const sug = suggestionsRef.current;
17209
17208
  if (!sug.length) {
@@ -17222,7 +17221,7 @@ var ChatInput = memo11(({
17222
17221
  }
17223
17222
  onSubmitRef.current(val);
17224
17223
  }, [completeCommand]);
17225
- useInput3(useCallback9((_input, key) => {
17224
+ useInput2(useCallback7((_input, key) => {
17226
17225
  if (inputRequestRef.current.status === "active") return;
17227
17226
  const sug = suggestionsRef.current;
17228
17227
  const visible = showPreviewRef.current;
@@ -17238,8 +17237,8 @@ var ChatInput = memo11(({
17238
17237
  completeCommand(selectedIdxRef.current);
17239
17238
  }
17240
17239
  }, [completeCommand]));
17241
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
17242
- showPreview && /* @__PURE__ */ jsx17(
17240
+ return /* @__PURE__ */ jsxs13(Box15, { flexDirection: "column", children: [
17241
+ showPreview && /* @__PURE__ */ jsx18(
17243
17242
  AutocompletePreview,
17244
17243
  {
17245
17244
  suggestions,
@@ -17248,7 +17247,7 @@ var ChatInput = memo11(({
17248
17247
  ),
17249
17248
  inputRequest.status === "active" ? (
17250
17249
  /* Active input request — yellow top/bottom border */
17251
- /* @__PURE__ */ jsx17(
17250
+ /* @__PURE__ */ jsx18(
17252
17251
  SecretInputArea,
17253
17252
  {
17254
17253
  inputRequest,
@@ -17259,7 +17258,7 @@ var ChatInput = memo11(({
17259
17258
  )
17260
17259
  ) : (
17261
17260
  /* Normal input — dim top/bottom border */
17262
- /* @__PURE__ */ jsx17(
17261
+ /* @__PURE__ */ jsx18(
17263
17262
  NormalInputArea,
17264
17263
  {
17265
17264
  inputKey,
@@ -17270,14 +17269,14 @@ var ChatInput = memo11(({
17270
17269
  }
17271
17270
  )
17272
17271
  ),
17273
- pastedHint && /* @__PURE__ */ jsx17(Box15, { paddingX: 2, children: /* @__PURE__ */ jsx17(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 }) })
17274
17273
  ] });
17275
17274
  });
17276
17275
 
17277
17276
  // src/platform/tui/components/footer.tsx
17278
17277
  import { memo as memo12 } from "react";
17279
- import { Box as Box16, Text as Text18 } from "ink";
17280
- import { jsx as jsx18, 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";
17281
17280
  var CTX_WARN_THRESHOLD = 0.8;
17282
17281
  var MAX_CONTEXT_TOKENS = LLM_LIMITS.streamMaxTokens;
17283
17282
  var formatElapsed = (totalSeconds) => {
@@ -17293,7 +17292,7 @@ var formatElapsed = (totalSeconds) => {
17293
17292
  var Footer = memo12(({ phase, targets, findings, todo, elapsedTime, isProcessing, totalTokens, turnCount }) => {
17294
17293
  const ctxPct = totalTokens > 0 ? Math.round(totalTokens / MAX_CONTEXT_TOKENS * 100) : 0;
17295
17294
  const ctxColor = ctxPct >= CTX_WARN_THRESHOLD * 100 ? THEME.yellow : THEME.dimGray;
17296
- return /* @__PURE__ */ jsxs15(
17295
+ return /* @__PURE__ */ jsxs14(
17297
17296
  Box16,
17298
17297
  {
17299
17298
  width: "100%",
@@ -17301,44 +17300,44 @@ var Footer = memo12(({ phase, targets, findings, todo, elapsedTime, isProcessing
17301
17300
  justifyContent: "space-between",
17302
17301
  overflow: "hidden",
17303
17302
  children: [
17304
- /* @__PURE__ */ jsxs15(Box16, { gap: 2, children: [
17305
- /* @__PURE__ */ jsxs15(Text18, { color: THEME.gray, children: [
17303
+ /* @__PURE__ */ jsxs14(Box16, { gap: 2, children: [
17304
+ /* @__PURE__ */ jsxs14(Text17, { color: THEME.gray, children: [
17306
17305
  "Phase: ",
17307
- /* @__PURE__ */ jsx18(Text18, { color: THEME.white, children: phase })
17306
+ /* @__PURE__ */ jsx19(Text17, { color: THEME.white, children: phase })
17308
17307
  ] }),
17309
- /* @__PURE__ */ jsxs15(Text18, { color: THEME.gray, children: [
17308
+ /* @__PURE__ */ jsxs14(Text17, { color: THEME.gray, children: [
17310
17309
  "Targets: ",
17311
- /* @__PURE__ */ jsx18(Text18, { color: THEME.white, children: targets })
17310
+ /* @__PURE__ */ jsx19(Text17, { color: THEME.white, children: targets })
17312
17311
  ] }),
17313
- /* @__PURE__ */ jsxs15(Text18, { color: THEME.gray, children: [
17312
+ /* @__PURE__ */ jsxs14(Text17, { color: THEME.gray, children: [
17314
17313
  "Findings: ",
17315
- /* @__PURE__ */ jsx18(Text18, { color: THEME.white, children: findings })
17314
+ /* @__PURE__ */ jsx19(Text17, { color: THEME.white, children: findings })
17316
17315
  ] }),
17317
- /* @__PURE__ */ jsxs15(Text18, { color: THEME.gray, children: [
17316
+ /* @__PURE__ */ jsxs14(Text17, { color: THEME.gray, children: [
17318
17317
  "Tasks: ",
17319
- /* @__PURE__ */ jsx18(Text18, { color: THEME.white, children: todo })
17318
+ /* @__PURE__ */ jsx19(Text17, { color: THEME.white, children: todo })
17320
17319
  ] })
17321
17320
  ] }),
17322
- /* @__PURE__ */ jsxs15(Box16, { gap: 2, children: [
17323
- isProcessing ? /* @__PURE__ */ jsxs15(Box16, { gap: 1, children: [
17324
- /* @__PURE__ */ jsx18(Text18, { dimColor: true, color: THEME.dimGray, children: "[ESC] abort" }),
17325
- /* @__PURE__ */ jsx18(Text18, { dimColor: true, color: THEME.dimGray, children: "[^C\xD72] exit" })
17326
- ] }) : /* @__PURE__ */ jsx18(Text18, { dimColor: true, color: THEME.dimGray, children: "[/help] commands" }),
17327
- 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: [
17328
17327
  "turn:",
17329
17328
  turnCount
17330
17329
  ] }),
17331
- totalTokens > 0 && /* @__PURE__ */ jsxs15(Text18, { dimColor: true, color: ctxColor, children: [
17330
+ totalTokens > 0 && /* @__PURE__ */ jsxs14(Text17, { dimColor: true, color: ctxColor, children: [
17332
17331
  "ctx:",
17333
17332
  ctxPct,
17334
17333
  "%"
17335
17334
  ] }),
17336
- totalTokens > 0 && /* @__PURE__ */ jsxs15(Text18, { dimColor: true, color: THEME.dimGray, children: [
17335
+ totalTokens > 0 && /* @__PURE__ */ jsxs14(Text17, { dimColor: true, color: THEME.dimGray, children: [
17337
17336
  "\u2191",
17338
17337
  formatTokens(totalTokens)
17339
17338
  ] }),
17340
- /* @__PURE__ */ jsx18(Text18, { color: isProcessing ? THEME.primary : THEME.gray, children: isProcessing ? "Running " : "Idle " }),
17341
- /* @__PURE__ */ jsx18(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) })
17342
17341
  ] })
17343
17342
  ]
17344
17343
  }
@@ -17347,9 +17346,9 @@ var Footer = memo12(({ phase, targets, findings, todo, elapsedTime, isProcessing
17347
17346
  var footer_default = Footer;
17348
17347
 
17349
17348
  // src/platform/tui/components/Modal.tsx
17350
- import { useMemo as useMemo2, memo as memo13, useCallback as useCallback10, useRef as useRef9 } from "react";
17351
- import { Box as Box17, Text as Text19, useStdout as useStdout3, useInput as useInput4 } from "ink";
17352
- import { jsx as jsx19, 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";
17353
17352
  var MODAL_TITLES = {
17354
17353
  findings: "\u25C8 FINDINGS \u25C8",
17355
17354
  graph: "\u25C8 ATTACK GRAPH \u25C8",
@@ -17362,9 +17361,9 @@ var Modal = memo13(({
17362
17361
  onScroll,
17363
17362
  onClose
17364
17363
  }) => {
17365
- const onScrollRef = useRef9(onScroll);
17364
+ const onScrollRef = useRef7(onScroll);
17366
17365
  onScrollRef.current = onScroll;
17367
- const onCloseRef = useRef9(onClose);
17366
+ const onCloseRef = useRef7(onClose);
17368
17367
  onCloseRef.current = onClose;
17369
17368
  const { stdout } = useStdout3();
17370
17369
  const terminalHeight = stdout?.rows ?? 24;
@@ -17376,7 +17375,7 @@ var Modal = memo13(({
17376
17375
  () => lines.slice(scrollOffset, scrollOffset + maxHeight),
17377
17376
  [lines, scrollOffset, maxHeight]
17378
17377
  );
17379
- useInput4(useCallback10((input, key) => {
17378
+ useInput3(useCallback8((input, key) => {
17380
17379
  if (key.escape || input === "q") {
17381
17380
  onCloseRef.current();
17382
17381
  } else if (key.upArrow || input === "k") {
@@ -17393,19 +17392,19 @@ var Modal = memo13(({
17393
17392
  const endLine = Math.min(scrollOffset + maxHeight, totalLines);
17394
17393
  const scrollbarHeight = Math.max(1, Math.floor(maxHeight * (maxHeight / Math.max(totalLines, 1))));
17395
17394
  const scrollbarPosition = totalLines > maxHeight ? Math.floor(scrollOffset / (totalLines - maxHeight) * (maxHeight - scrollbarHeight)) : 0;
17396
- return /* @__PURE__ */ jsxs16(
17395
+ return /* @__PURE__ */ jsxs15(
17397
17396
  Box17,
17398
17397
  {
17399
17398
  flexDirection: "column",
17400
17399
  width: terminalWidth,
17401
17400
  height: terminalHeight,
17402
17401
  children: [
17403
- /* @__PURE__ */ jsx19(Box17, { justifyContent: "center", marginBottom: 0, children: /* @__PURE__ */ jsx19(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: (() => {
17404
17403
  const title = MODAL_TITLES[type];
17405
17404
  const sideWidth = Math.max(3, Math.floor((terminalWidth - title.length - 2) / 2));
17406
17405
  return `${"\u2500".repeat(sideWidth)} ${title} ${"\u2500".repeat(sideWidth)}`;
17407
17406
  })() }) }),
17408
- /* @__PURE__ */ jsx19(
17407
+ /* @__PURE__ */ jsx20(
17409
17408
  Box17,
17410
17409
  {
17411
17410
  flexDirection: "column",
@@ -17415,17 +17414,17 @@ var Modal = memo13(({
17415
17414
  flexGrow: 1,
17416
17415
  children: visibleLines.map((line, i) => {
17417
17416
  const showScrollbar = totalLines > maxHeight && i >= scrollbarPosition && i < scrollbarPosition + scrollbarHeight;
17418
- return /* @__PURE__ */ jsxs16(Box17, { children: [
17419
- /* @__PURE__ */ jsx19(Text19, { color: THEME.white, wrap: "truncate", children: line }),
17420
- /* @__PURE__ */ jsx19(Box17, { flexGrow: 1 }),
17421
- totalLines > maxHeight && /* @__PURE__ */ jsx19(Text19, { color: showScrollbar ? THEME.cyan : THEME.dimGray, children: showScrollbar ? "\u2588" : "\u2502" })
17417
+ return /* @__PURE__ */ jsxs15(Box17, { children: [
17418
+ /* @__PURE__ */ jsx20(Text18, { color: THEME.white, wrap: "truncate", children: line }),
17419
+ /* @__PURE__ */ jsx20(Box17, { flexGrow: 1 }),
17420
+ totalLines > maxHeight && /* @__PURE__ */ jsx20(Text18, { color: showScrollbar ? THEME.cyan : THEME.dimGray, children: showScrollbar ? "\u2588" : "\u2502" })
17422
17421
  ] }, i);
17423
17422
  })
17424
17423
  }
17425
17424
  ),
17426
- /* @__PURE__ */ jsxs16(Box17, { justifyContent: "space-between", paddingX: 1, children: [
17427
- /* @__PURE__ */ jsx19(Text19, { dimColor: true, color: THEME.gray, children: "\u2191\u2193/jk: scroll | g/G: top/bottom | ESC/q: close" }),
17428
- /* @__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: [
17429
17428
  startLine,
17430
17429
  "-",
17431
17430
  endLine,
@@ -17440,7 +17439,7 @@ var Modal = memo13(({
17440
17439
 
17441
17440
  // src/platform/tui/components/app/bottom-region.tsx
17442
17441
  import { Box as Box18 } from "ink";
17443
- import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
17442
+ import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
17444
17443
  var BottomRegion = ({
17445
17444
  input,
17446
17445
  setInput,
@@ -17463,19 +17462,19 @@ var BottomRegion = ({
17463
17462
  const hasArgs = isSlashMode && input.includes(" ");
17464
17463
  const suggestionCount = isSlashMode && !hasArgs && inputRequest.status !== "active" ? Math.min(getMatchingCommands(partialCmd).length, MAX_SUGGESTIONS) : 0;
17465
17464
  const previewHeight = suggestionCount;
17466
- const inputExtraHeight = inputRequest.status === "active" ? 1 : 0;
17467
- const bottomMinHeight = 7 + inputExtraHeight;
17468
- return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", minHeight: bottomMinHeight, children: [
17469
- /* @__PURE__ */ jsx20(
17465
+ const bottomMinHeight = 7;
17466
+ return /* @__PURE__ */ jsxs16(Box18, { flexDirection: "column", minHeight: bottomMinHeight, children: [
17467
+ /* @__PURE__ */ jsx21(
17470
17468
  StatusDisplay,
17471
17469
  {
17472
17470
  retryState,
17473
17471
  isProcessing,
17474
17472
  currentStatus,
17475
- currentTokens
17473
+ currentTokens,
17474
+ inputRequest
17476
17475
  }
17477
17476
  ),
17478
- /* @__PURE__ */ jsx20(
17477
+ /* @__PURE__ */ jsx21(
17479
17478
  ChatInput,
17480
17479
  {
17481
17480
  value: input,
@@ -17488,7 +17487,7 @@ var BottomRegion = ({
17488
17487
  onSecretSubmit: handleSecretSubmit
17489
17488
  }
17490
17489
  ),
17491
- /* @__PURE__ */ jsx20(Box18, { marginTop: 1, children: /* @__PURE__ */ jsx20(
17490
+ /* @__PURE__ */ jsx21(Box18, { marginTop: 1, children: /* @__PURE__ */ jsx21(
17492
17491
  footer_default,
17493
17492
  {
17494
17493
  phase: stats.phase,
@@ -17505,16 +17504,16 @@ var BottomRegion = ({
17505
17504
  };
17506
17505
 
17507
17506
  // src/platform/tui/app.tsx
17508
- import { jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
17507
+ import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
17509
17508
  var MODEL_NAME = getModel() || DEFAULT_MODEL;
17510
17509
  var App = ({ autoApprove = false, target }) => {
17511
17510
  const { exit } = useApp();
17512
17511
  const { stdout } = useStdout4();
17513
17512
  const terminalWidth = stdout?.columns ?? 80;
17514
- const [input, setInput] = useState8("");
17515
- const [secretInput, setSecretInput] = useState8("");
17516
- const [autoApproveMode, setAutoApproveMode] = useState8(autoApprove);
17517
- const [modal, setModal] = useState8({ type: null, content: "", scrollOffset: 0 });
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 });
17518
17517
  const {
17519
17518
  agent,
17520
17519
  messages,
@@ -17534,37 +17533,26 @@ var App = ({ autoApprove = false, target }) => {
17534
17533
  addMessage,
17535
17534
  refreshStats
17536
17535
  } = useAgent(autoApproveMode, target);
17537
- const isProcessingRef = useRef10(isProcessing);
17536
+ const isProcessingRef = useRef8(isProcessing);
17538
17537
  isProcessingRef.current = isProcessing;
17539
- const autoApproveModeRef = useRef10(autoApproveMode);
17538
+ const autoApproveModeRef = useRef8(autoApproveMode);
17540
17539
  autoApproveModeRef.current = autoApproveMode;
17541
- const inputRequestRef = useRef10(inputRequest);
17540
+ const inputRequestRef = useRef8(inputRequest);
17542
17541
  inputRequestRef.current = inputRequest;
17543
- const isModalOpenRef = useRef10(!!modal.type);
17542
+ const isModalOpenRef = useRef8(!!modal.type);
17544
17543
  isModalOpenRef.current = !!modal.type;
17545
- const inputRef = useRef10(input);
17544
+ const inputRef = useRef8(input);
17546
17545
  inputRef.current = input;
17547
- const clearInput = useCallback11(() => {
17546
+ const clearInput = useCallback9(() => {
17548
17547
  setInput("");
17549
17548
  }, []);
17550
- const [historyScrollOffset, setHistoryScrollOffset] = useState8(0);
17551
- const handleScroll = useCallback11((delta) => {
17552
- setHistoryScrollOffset((prev) => Math.max(0, prev - delta));
17553
- }, []);
17554
- const messageCountRef = useRef10(messages.length);
17555
- if (messages.length !== messageCountRef.current) {
17556
- messageCountRef.current = messages.length;
17557
- if (historyScrollOffset > 0) {
17558
- setHistoryScrollOffset(0);
17559
- }
17560
- }
17561
- const showModal = useCallback11((type, content) => {
17549
+ const showModal = useCallback9((type, content) => {
17562
17550
  setModal({ type, content, scrollOffset: 0 });
17563
17551
  }, []);
17564
- const closeModal = useCallback11(() => {
17552
+ const closeModal = useCallback9(() => {
17565
17553
  setModal({ type: null, content: "", scrollOffset: 0 });
17566
17554
  }, []);
17567
- const handleModalScroll = useCallback11((delta) => {
17555
+ const handleModalScroll = useCallback9((delta) => {
17568
17556
  setModal((prev) => {
17569
17557
  const lines = prev.content.split("\n");
17570
17558
  const maxHeight = (stdout?.rows ?? 24) - 6;
@@ -17573,7 +17561,7 @@ var App = ({ autoApprove = false, target }) => {
17573
17561
  return { ...prev, scrollOffset: newOffset };
17574
17562
  });
17575
17563
  }, [stdout?.rows]);
17576
- const handleExit = useCallback11(() => {
17564
+ const handleExit = useCallback9(() => {
17577
17565
  const ir = inputRequestRef.current;
17578
17566
  if (ir.status === "active") {
17579
17567
  ir.resolve(null);
@@ -17596,7 +17584,7 @@ var App = ({ autoApprove = false, target }) => {
17596
17584
  isProcessingRef,
17597
17585
  autoApproveModeRef
17598
17586
  });
17599
- const handleSubmit = useCallback11(async (value) => {
17587
+ const handleSubmit = useCallback9(async (value) => {
17600
17588
  const trimmed = value.trim();
17601
17589
  if (!trimmed) return;
17602
17590
  setInput("");
@@ -17611,12 +17599,11 @@ var App = ({ autoApprove = false, target }) => {
17611
17599
  await executeTask(trimmed);
17612
17600
  }
17613
17601
  }, [agent, addMessage, executeTask, handleCommand]);
17614
- const handleSecretSubmit = useCallback11((value) => {
17602
+ const handleSecretSubmit = useCallback9((value) => {
17615
17603
  const ir = inputRequestRef.current;
17616
17604
  if (ir.status !== "active") return;
17617
- const displayText = ir.isPassword ? "\u2022".repeat(value.length) : value;
17618
- const promptLabel = ir.prompt || "Input";
17619
- addMessage("system", `\u21B3 ${promptLabel} ${displayText}`);
17605
+ const displayText = ir.isPassword ? "\u2022".repeat(Math.min(value.length, 20)) : value;
17606
+ addMessage("system", `\u21B3 ${displayText}`);
17620
17607
  ir.resolve(value);
17621
17608
  setInputRequest({ status: "inactive" });
17622
17609
  setSecretInput("");
@@ -17630,11 +17617,10 @@ var App = ({ autoApprove = false, target }) => {
17630
17617
  inputRequestRef,
17631
17618
  isModalOpenRef,
17632
17619
  inputRef,
17633
- clearInput,
17634
- onScroll: handleScroll
17620
+ clearInput
17635
17621
  });
17636
17622
  if (modal.type) {
17637
- return /* @__PURE__ */ jsx21(Box19, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: /* @__PURE__ */ jsx21(
17623
+ return /* @__PURE__ */ jsx22(Box19, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: /* @__PURE__ */ jsx22(
17638
17624
  Modal,
17639
17625
  {
17640
17626
  type: modal.type,
@@ -17645,19 +17631,17 @@ var App = ({ autoApprove = false, target }) => {
17645
17631
  }
17646
17632
  ) });
17647
17633
  }
17648
- return /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: [
17649
- /* @__PURE__ */ jsx21(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsx21(
17634
+ return /* @__PURE__ */ jsxs17(Box19, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: [
17635
+ /* @__PURE__ */ jsx22(Box19, { flexDirection: "column", children: /* @__PURE__ */ jsx22(
17650
17636
  MessageList,
17651
17637
  {
17652
17638
  messages,
17653
- isModalOpen: !!modal.type,
17654
17639
  modelName: MODEL_NAME,
17655
17640
  autoApproveMode,
17656
- version: APP_VERSION,
17657
- scrollOffset: historyScrollOffset
17641
+ version: APP_VERSION
17658
17642
  }
17659
17643
  ) }),
17660
- /* @__PURE__ */ jsx21(
17644
+ /* @__PURE__ */ jsx22(
17661
17645
  BottomRegion,
17662
17646
  {
17663
17647
  input,
@@ -17682,7 +17666,7 @@ var App = ({ autoApprove = false, target }) => {
17682
17666
  var app_default = App;
17683
17667
 
17684
17668
  // src/platform/tui/cli/commands/interactive.tsx
17685
- import { jsx as jsx22 } from "react/jsx-runtime";
17669
+ import { jsx as jsx23 } from "react/jsx-runtime";
17686
17670
  async function interactiveAction(options) {
17687
17671
  const { dangerouslySkipPermissions: skipPermissions = false, target } = options;
17688
17672
  console.clear();
@@ -17691,13 +17675,13 @@ async function interactiveAction(options) {
17691
17675
  console.log(chalk.hex(HEX.red)("[!] All tool executions will be auto-approved!\n"));
17692
17676
  }
17693
17677
  const { waitUntilExit } = render(
17694
- /* @__PURE__ */ jsx22(
17678
+ /* @__PURE__ */ jsx23(AnimationProvider, { children: /* @__PURE__ */ jsx23(
17695
17679
  app_default,
17696
17680
  {
17697
17681
  autoApprove: skipPermissions,
17698
17682
  target
17699
17683
  }
17700
- )
17684
+ ) })
17701
17685
  );
17702
17686
  await waitUntilExit();
17703
17687
  }