pentesting 0.44.1 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -12,11 +12,11 @@ import { Command } from "commander";
12
12
  import chalk from "chalk";
13
13
 
14
14
  // src/platform/tui/app.tsx
15
- import { useState as useState4, useCallback as useCallback4, useEffect as useEffect4, useRef as useRef4 } from "react";
15
+ import { useState as useState5, useCallback as useCallback4, useEffect as useEffect4, useRef as useRef5 } from "react";
16
16
  import { Box as Box6, useInput as useInput2, useApp } from "ink";
17
17
 
18
18
  // src/platform/tui/hooks/useAgent.ts
19
- import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef2 } from "react";
19
+ import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2, useRef as useRef3 } from "react";
20
20
 
21
21
  // src/shared/constants/timing.ts
22
22
  var TOOL_TIMEOUTS = {
@@ -311,7 +311,7 @@ var ORPHAN_PROCESS_NAMES = [
311
311
 
312
312
  // src/shared/constants/agent.ts
313
313
  var APP_NAME = "Pentest AI";
314
- var APP_VERSION = "0.44.1";
314
+ var APP_VERSION = "0.45.0";
315
315
  var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
316
316
  var LLM_ROLES = {
317
317
  SYSTEM: "system",
@@ -676,6 +676,7 @@ var EVENT_TYPES = {
676
676
  REASONING_START: "reasoning_start",
677
677
  REASONING_DELTA: "reasoning_delta",
678
678
  REASONING_END: "reasoning_end",
679
+ AI_RESPONSE: "ai_response",
679
680
  TOOL_CALL: "tool_call",
680
681
  TOOL_RESULT: "tool_result",
681
682
  ERROR: "error",
@@ -3903,6 +3904,21 @@ var NOISE_CLASSIFICATION = {
3903
3904
  "gobuster",
3904
3905
  "dirsearch",
3905
3906
  "feroxbuster"
3907
+ ],
3908
+ LOW_VISIBILITY: [
3909
+ // State management — pure bookkeeping, zero operator value
3910
+ "update_mission",
3911
+ "get_state",
3912
+ "update_phase",
3913
+ "update_todo",
3914
+ "add_target",
3915
+ "add_finding",
3916
+ "add_loot",
3917
+ "set_scope",
3918
+ // Resource health checks — noisy periodic calls
3919
+ "bg_status",
3920
+ "bg_cleanup",
3921
+ "health_check"
3906
3922
  ]
3907
3923
  };
3908
3924
 
@@ -5138,7 +5154,9 @@ var ENV_KEYS = {
5138
5154
  BASE_URL: "PENTEST_BASE_URL",
5139
5155
  MODEL: "PENTEST_MODEL",
5140
5156
  SEARCH_API_KEY: "SEARCH_API_KEY",
5141
- SEARCH_API_URL: "SEARCH_API_URL"
5157
+ SEARCH_API_URL: "SEARCH_API_URL",
5158
+ THINKING: "PENTEST_THINKING",
5159
+ THINKING_BUDGET: "PENTEST_THINKING_BUDGET"
5142
5160
  };
5143
5161
  var DEFAULT_SEARCH_API_URL = "https://api.search.brave.com/res/v1/web/search";
5144
5162
  function getApiKey() {
@@ -5156,6 +5174,13 @@ function getSearchApiKey() {
5156
5174
  function getSearchApiUrl() {
5157
5175
  return process.env[ENV_KEYS.SEARCH_API_URL] || DEFAULT_SEARCH_API_URL;
5158
5176
  }
5177
+ function isThinkingEnabled() {
5178
+ return process.env[ENV_KEYS.THINKING] === "true";
5179
+ }
5180
+ function getThinkingBudget() {
5181
+ const val = parseInt(process.env[ENV_KEYS.THINKING_BUDGET] || "", 10);
5182
+ return isNaN(val) ? 8e3 : Math.max(1024, val);
5183
+ }
5159
5184
  function isBrowserHeadless() {
5160
5185
  return true;
5161
5186
  }
@@ -8818,6 +8843,8 @@ var LLM_BLOCK_TYPE = {
8818
8843
  var LLM_DELTA_TYPE = {
8819
8844
  TEXT_DELTA: "text_delta",
8820
8845
  THINKING_DELTA: "thinking_delta",
8846
+ REASONING_DELTA: "reasoning_delta",
8847
+ // Used by some providers (GLM, DeepSeek)
8821
8848
  INPUT_JSON_DELTA: "input_json_delta"
8822
8849
  };
8823
8850
 
@@ -8967,6 +8994,8 @@ var LLMClient = class {
8967
8994
  headers: {
8968
8995
  [LLM_HEADER.CONTENT_TYPE]: LLM_CONTENT_TYPE.JSON,
8969
8996
  [LLM_HEADER.API_KEY]: this.apiKey,
8997
+ // Also send as Bearer for OpenAI-compat proxies (z.ai, GLM, etc.)
8998
+ [LLM_HEADER.AUTHORIZATION]: `${LLM_HEADER.BEARER_PREFIX} ${this.apiKey}`,
8970
8999
  [LLM_HEADER.ANTHROPIC_VERSION]: LLM_API.VERSION
8971
9000
  },
8972
9001
  body: JSON.stringify(body),
@@ -8981,14 +9010,16 @@ var LLMClient = class {
8981
9010
  return response;
8982
9011
  }
8983
9012
  async executeNonStream(messages, tools, systemPrompt) {
9013
+ const thinking = isThinkingEnabled() ? { type: "enabled", budget_tokens: getThinkingBudget() } : void 0;
8984
9014
  const requestBody = {
8985
9015
  model: this.model,
8986
9016
  max_tokens: LLM_LIMITS.nonStreamMaxTokens,
8987
9017
  system: systemPrompt,
8988
9018
  messages: this.convertMessages(messages),
8989
- tools
9019
+ tools,
9020
+ ...thinking && { thinking }
8990
9021
  };
8991
- debugLog("llm", "Non-stream request", { model: this.model, toolCount: tools?.length });
9022
+ debugLog("llm", "Non-stream request", { model: this.model, toolCount: tools?.length, thinking: !!thinking });
8992
9023
  const response = await this.makeRequest(requestBody);
8993
9024
  const data = await response.json();
8994
9025
  const textBlock = data.content.find((b) => b.type === "text");
@@ -9004,15 +9035,17 @@ var LLMClient = class {
9004
9035
  async executeStream(messages, tools, systemPrompt, callbacks) {
9005
9036
  this.requestCount++;
9006
9037
  const requestId = this.requestCount;
9038
+ const thinking = isThinkingEnabled() ? { type: "enabled", budget_tokens: getThinkingBudget() } : void 0;
9007
9039
  const requestBody = {
9008
9040
  model: this.model,
9009
9041
  max_tokens: LLM_LIMITS.streamMaxTokens,
9010
9042
  system: systemPrompt,
9011
9043
  messages: this.convertMessages(messages),
9012
9044
  tools,
9013
- stream: true
9045
+ stream: true,
9046
+ ...thinking && { thinking }
9014
9047
  };
9015
- debugLog("llm", `[${requestId}] Stream request START`, { model: this.model, toolCount: tools?.length, toolNames: tools?.map((t) => t.name) });
9048
+ debugLog("llm", `[${requestId}] Stream request START`, { model: this.model, toolCount: tools?.length, toolNames: tools?.map((t) => t.name), thinking: !!thinking });
9016
9049
  const response = await this.makeRequest(requestBody, callbacks?.abortSignal);
9017
9050
  let fullContent = "";
9018
9051
  let fullReasoning = "";
@@ -9020,6 +9053,7 @@ var LLMClient = class {
9020
9053
  let usage = { input_tokens: 0, output_tokens: 0 };
9021
9054
  let totalChars = 0;
9022
9055
  let wasAborted = false;
9056
+ const currentBlockRef = { value: null };
9023
9057
  const reader = response.body?.getReader();
9024
9058
  if (!reader) throw new Error("Response body is not readable");
9025
9059
  const decoder = new TextDecoder();
@@ -9070,7 +9104,8 @@ var LLMClient = class {
9070
9104
  onUsage: (u) => {
9071
9105
  usage = u;
9072
9106
  },
9073
- getTotalChars: () => totalChars
9107
+ getTotalChars: () => totalChars,
9108
+ currentBlockRef
9074
9109
  });
9075
9110
  } catch {
9076
9111
  }
@@ -9100,28 +9135,53 @@ var LLMClient = class {
9100
9135
  toolCalls.push({ id: toolCall.id, name: toolCall.name, input: toolCall.input });
9101
9136
  }
9102
9137
  debugLog("llm", `[${requestId}] FINAL toolCalls`, { count: toolCalls.length, tools: toolCalls.map((t) => ({ id: t.id, name: t.name, input: t.input })) });
9138
+ const stripped = this.stripThinkTags(fullContent, fullReasoning);
9103
9139
  return {
9104
- content: fullContent,
9140
+ content: stripped.cleanText,
9105
9141
  toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
9106
9142
  rawResponse: null,
9107
- reasoning: fullReasoning || void 0,
9143
+ reasoning: stripped.extractedReasoning || void 0,
9108
9144
  usage: usage.input_tokens > 0 || usage.output_tokens > 0 ? usage : void 0,
9109
9145
  error: wasAborted ? { type: LLM_ERROR_TYPES.UNKNOWN, message: "Stream aborted", isRetryable: false } : void 0
9110
9146
  };
9111
9147
  }
9148
+ /**
9149
+ * Strip inline <think>...</think> XML reasoning tags from LLM text output.
9150
+ *
9151
+ * WHY: Some providers (GLM, DeepSeek, Qwen) output reasoning inline
9152
+ * in text deltas as <think>content</think> instead of separate SSE blocks.
9153
+ * Without stripping, raw tags like "</think>" appear literally in TUI output.
9154
+ *
9155
+ * @param text - Raw text content potentially containing think tags
9156
+ * @param existingReasoning - Accumulated reasoning from SSE reasoning blocks
9157
+ * @returns cleanText (tags removed) and extractedReasoning (tag content appended)
9158
+ */
9159
+ stripThinkTags(text, existingReasoning) {
9160
+ let cleanText = text;
9161
+ let extractedReasoning = existingReasoning;
9162
+ cleanText = cleanText.replace(/<think>([\s\S]*?)<\/think>/gi, (_match, inner) => {
9163
+ extractedReasoning += inner;
9164
+ return "";
9165
+ });
9166
+ cleanText = cleanText.replace(/<\/?think>/gi, "");
9167
+ return { cleanText, extractedReasoning };
9168
+ }
9112
9169
  processStreamEvent(event, requestId, context) {
9113
- const { toolCallsMap, callbacks, onTextStart, onReasoningStart, onTextEnd, onReasoningEnd, onContent, onReasoning, onUsage, getTotalChars } = context;
9170
+ const { toolCallsMap, callbacks, onTextStart, onReasoningStart, onTextEnd, onReasoningEnd, onContent, onReasoning, onUsage, getTotalChars, currentBlockRef } = context;
9114
9171
  switch (event.type) {
9115
9172
  case LLM_SSE_EVENT.CONTENT_BLOCK_START:
9116
9173
  if (event.content_block) {
9117
9174
  const blockType = event.content_block.type;
9118
9175
  if (blockType === LLM_BLOCK_TYPE.TEXT) {
9176
+ currentBlockRef.value = "text";
9119
9177
  onTextStart();
9120
9178
  callbacks?.onOutputStart?.();
9121
9179
  } else if (blockType === LLM_BLOCK_TYPE.THINKING || blockType === LLM_BLOCK_TYPE.REASONING) {
9180
+ currentBlockRef.value = "reasoning";
9122
9181
  onReasoningStart();
9123
9182
  callbacks?.onReasoningStart?.();
9124
9183
  } else if (blockType === LLM_BLOCK_TYPE.TOOL_USE) {
9184
+ currentBlockRef.value = "tool_use";
9125
9185
  toolCallsMap.set(event.content_block.id || "", {
9126
9186
  id: event.content_block.id || "",
9127
9187
  name: event.content_block.name || "",
@@ -9137,9 +9197,13 @@ var LLMClient = class {
9137
9197
  if (event.delta.type === LLM_DELTA_TYPE.TEXT_DELTA && event.delta.text) {
9138
9198
  onContent(event.delta.text);
9139
9199
  callbacks?.onOutputDelta?.(event.delta.text);
9140
- } else if (event.delta.type === LLM_DELTA_TYPE.THINKING_DELTA && event.delta.thinking) {
9141
- onReasoning(event.delta.thinking);
9142
- callbacks?.onReasoningDelta?.(event.delta.thinking);
9200
+ } else if (
9201
+ // Anthropic: thinking_delta / GLM-DeepSeek: reasoning_delta
9202
+ event.delta.type === LLM_DELTA_TYPE.THINKING_DELTA && event.delta.thinking || event.delta.type === LLM_DELTA_TYPE.REASONING_DELTA && event.delta.reasoning
9203
+ ) {
9204
+ const chunk = event.delta.thinking || event.delta.reasoning || "";
9205
+ onReasoning(chunk);
9206
+ callbacks?.onReasoningDelta?.(chunk);
9143
9207
  } else if (event.delta.type === LLM_DELTA_TYPE.INPUT_JSON_DELTA && event.delta.partial_json) {
9144
9208
  const index = event.index;
9145
9209
  if (index !== void 0) {
@@ -9156,12 +9220,18 @@ var LLMClient = class {
9156
9220
  callbacks?.onUsageUpdate?.({ input_tokens: 0, output_tokens: estimatedOutput });
9157
9221
  }
9158
9222
  break;
9159
- case LLM_SSE_EVENT.CONTENT_BLOCK_STOP:
9160
- callbacks?.onReasoningEnd?.();
9161
- callbacks?.onOutputEnd?.();
9162
- onTextEnd();
9163
- onReasoningEnd();
9223
+ case LLM_SSE_EVENT.CONTENT_BLOCK_STOP: {
9224
+ const stoppedType = currentBlockRef.value;
9225
+ currentBlockRef.value = null;
9226
+ if (stoppedType === "text") {
9227
+ onTextEnd();
9228
+ callbacks?.onOutputEnd?.();
9229
+ } else if (stoppedType === "reasoning") {
9230
+ onReasoningEnd();
9231
+ callbacks?.onReasoningEnd?.();
9232
+ }
9164
9233
  break;
9234
+ }
9165
9235
  case LLM_SSE_EVENT.MESSAGE_START:
9166
9236
  if (event.message?.usage) {
9167
9237
  onUsage({ input_tokens: event.message.usage.input_tokens || 0, output_tokens: event.message.usage.output_tokens || 0 });
@@ -10130,7 +10200,7 @@ Please decide how to handle this error and continue.`;
10130
10200
  async step(iteration, messages, systemPrompt, progress) {
10131
10201
  const phase = this.state.getPhase();
10132
10202
  const stepStartTime = Date.now();
10133
- this.emitThink(iteration);
10203
+ this.emitThink(iteration, progress);
10134
10204
  const callbacks = this.buildStreamCallbacks(phase);
10135
10205
  const response = await this.llm.generateResponseStream(
10136
10206
  messages,
@@ -10138,12 +10208,24 @@ Please decide how to handle this error and continue.`;
10138
10208
  systemPrompt,
10139
10209
  callbacks
10140
10210
  );
10211
+ if (response.reasoning && !callbacks.hadReasoningEnd()) {
10212
+ this.emitReasoningStart(phase);
10213
+ this.emitReasoningDelta(response.reasoning, phase);
10214
+ this.emitReasoningEnd(phase);
10215
+ }
10216
+ if (response.content?.trim()) {
10217
+ this.events.emit({
10218
+ type: EVENT_TYPES.AI_RESPONSE,
10219
+ timestamp: Date.now(),
10220
+ data: { content: response.content.trim(), phase }
10221
+ });
10222
+ }
10141
10223
  messages.push({ role: LLM_ROLES.ASSISTANT, content: response.content });
10142
10224
  const stepDuration = Date.now() - stepStartTime;
10143
10225
  const tokens = response.usage ? { input: response.usage.input_tokens, output: response.usage.output_tokens } : void 0;
10144
10226
  if (!response.toolCalls?.length) {
10145
- const hasDonemeaningfulWork = (progress?.totalToolsExecuted ?? 0) > 0;
10146
- if (hasDonemeaningfulWork) {
10227
+ const hasDoneMeaningfulWork = (progress?.totalToolsExecuted ?? 0) > 0;
10228
+ if (hasDoneMeaningfulWork) {
10147
10229
  this.emitComplete(response.content, iteration, 0, stepDuration, tokens);
10148
10230
  return { output: response.content, toolsExecuted: 0, isCompleted: true };
10149
10231
  }
@@ -10157,11 +10239,26 @@ Please decide how to handle this error and continue.`;
10157
10239
  // SUBSECTION: Callback Builder
10158
10240
  // ─────────────────────────────────────────────────────────────────
10159
10241
  buildStreamCallbacks(phase) {
10160
- return {
10242
+ let _reasoningEndFired = false;
10243
+ let _outputBuffer = "";
10244
+ const callbacks = {
10161
10245
  onReasoningStart: () => this.emitReasoningStart(phase),
10162
10246
  onReasoningDelta: (content) => this.emitReasoningDelta(content, phase),
10163
- onReasoningEnd: () => this.emitReasoningEnd(phase),
10164
- onOutputDelta: () => {
10247
+ onReasoningEnd: () => {
10248
+ _reasoningEndFired = true;
10249
+ this.emitReasoningEnd(phase);
10250
+ },
10251
+ // WHY: Show AI text as it streams, not after completion.
10252
+ // The user sees what the AI is writing in real-time via the status bar.
10253
+ onOutputDelta: (text) => {
10254
+ _outputBuffer += text;
10255
+ const firstLine = _outputBuffer.split("\n")[0]?.slice(0, 120) || "";
10256
+ this.events.emit({
10257
+ type: EVENT_TYPES.THINK,
10258
+ timestamp: Date.now(),
10259
+ data: { thought: `Writing\u2026 ${_outputBuffer.length} chars
10260
+ ${firstLine}`, phase }
10261
+ });
10165
10262
  },
10166
10263
  onRetry: (attempt, maxRetries, delayMs, error) => {
10167
10264
  this.events.emit({
@@ -10177,19 +10274,40 @@ Please decide how to handle this error and continue.`;
10177
10274
  data: { inputTokens: usage.input_tokens, outputTokens: usage.output_tokens }
10178
10275
  });
10179
10276
  },
10180
- abortSignal: this.abortController?.signal
10277
+ abortSignal: this.abortController?.signal,
10278
+ // WHY: Used by step() to detect if SSE reasoning blocks fired.
10279
+ // If not, we know it was an inline <think> model and must emit post-stream.
10280
+ hadReasoningEnd: () => _reasoningEndFired
10181
10281
  };
10282
+ return callbacks;
10182
10283
  }
10183
10284
  // ─────────────────────────────────────────────────────────────────
10184
10285
  // SUBSECTION: Event Emitters
10185
10286
  // ─────────────────════════════════════════════════════════════
10186
- emitThink(iteration) {
10287
+ emitThink(iteration, progress) {
10288
+ const phase = this.state.getPhase();
10289
+ const targets = this.state.getTargets().size;
10290
+ const findings = this.state.getFindings().length;
10291
+ const toolsUsed = progress?.totalToolsExecuted ?? 0;
10292
+ const hasErrors = (progress?.toolErrors ?? 0) > 0;
10293
+ let thought;
10294
+ if (iteration === 0) {
10295
+ thought = targets > 0 ? `Analyzing ${targets} target${targets > 1 ? "s" : ""} \xB7 Planning ${phase} approach` : `Reviewing task \xB7 Building ${phase} strategy`;
10296
+ } else if (toolsUsed === 0) {
10297
+ thought = `Iteration ${iteration + 1} \xB7 No actions yet \xB7 Reconsidering approach`;
10298
+ } else if (hasErrors) {
10299
+ thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tools \xB7 ${progress?.toolErrors} error${(progress?.toolErrors ?? 0) > 1 ? "s" : ""} \xB7 Adapting strategy`;
10300
+ } else if (findings > 0) {
10301
+ thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${findings} finding${findings > 1 ? "s" : ""} \xB7 ${toolsUsed} tools \xB7 Continuing`;
10302
+ } else {
10303
+ thought = `Iteration ${iteration + 1} \xB7 [${phase}] ${toolsUsed} tool${toolsUsed !== 1 ? "s" : ""} executed \xB7 Evaluating results`;
10304
+ }
10187
10305
  this.events.emit({
10188
10306
  type: EVENT_TYPES.THINK,
10189
10307
  timestamp: Date.now(),
10190
10308
  data: {
10191
- thought: `${this.agentType} agent iteration ${iteration + 1}: Decision making`,
10192
- phase: this.state.getPhase()
10309
+ thought,
10310
+ phase
10193
10311
  }
10194
10312
  });
10195
10313
  }
@@ -11461,63 +11579,56 @@ import { useState, useRef, useCallback } from "react";
11461
11579
 
11462
11580
  // src/shared/constants/theme.ts
11463
11581
  var HEX = {
11464
- primary: "#FF968A",
11465
- pink: "#FFAEAE",
11466
- peach: "#FFC5BF"
11582
+ primary: "#2496ED",
11583
+ // Docker blue
11584
+ cyan: "#58C4F0",
11585
+ // bright sky blue
11586
+ teal: "#4EC9B0",
11587
+ // mint-teal (success states)
11588
+ yellow: "#E0AF68",
11589
+ // warm amber (warning)
11590
+ gray: "#C8C8C8",
11591
+ // secondary text
11592
+ white: "#FFFFFF",
11593
+ red: "#F7768E"
11594
+ // pastel red (error)
11467
11595
  };
11468
11596
  var COLORS = {
11469
- // Primary accent - coral/salmon (ANSI 210 is light coral)
11470
- primary: "ansi256(210)",
11471
- // Pastel pink (ANSI 217 is light pink)
11472
- pink: "ansi256(217)",
11473
- // Pastel peach (ANSI 216 is light salmon)
11474
- peach: "ansi256(216)",
11475
- // Warning yellow (ANSI 222 is light goldenrod)
11476
- yellow: "ansi256(222)",
11477
- // Orange (ANSI 215)
11478
- orange: "ansi256(215)",
11479
- // Bright white for main text
11597
+ primary: "ansi256(33)",
11598
+ // Docker blue (#0087ff) — closest to #2496ED
11599
+ cyan: "ansi256(74)",
11600
+ // bright sky blue (#5fafd7)
11601
+ teal: "ansi256(79)",
11602
+ // mint-teal (#5fd7af) — success states
11603
+ yellow: "ansi256(179)",
11604
+ // warm amber (warning)
11480
11605
  white: "white",
11481
- // Light gray for secondary text (ANSI 252 is light gray)
11482
- gray: "ansi256(252)",
11483
- // Bright red for errors
11484
- red: "red"
11606
+ // main text unchanged
11607
+ gray: "ansi256(250)",
11608
+ // secondary text bright enough to read
11609
+ dimGray: "ansi256(240)",
11610
+ // dimmer text for sub-lines
11611
+ red: "ansi256(204)"
11612
+ // pastel red (error)
11485
11613
  };
11486
11614
  var THEME = {
11487
11615
  ...COLORS,
11488
- bg: {
11489
- primary: "#050505",
11490
- input: "#020617"
11491
- },
11492
- text: {
11493
- primary: COLORS.white,
11494
- // AI responses - main text
11495
- secondary: COLORS.gray,
11496
- // Secondary info
11497
- muted: COLORS.gray,
11498
- // Metadata, hints
11499
- accent: COLORS.gray
11500
- // Very subtle
11501
- },
11502
11616
  status: {
11503
- success: COLORS.gray,
11504
- // Keep it subtle
11617
+ success: COLORS.teal,
11618
+ // teal for success
11505
11619
  warning: COLORS.yellow,
11506
- // Pastel yellow
11507
- error: COLORS.red,
11508
- // Bright red for errors
11509
- running: COLORS.gray
11510
- // Processing indicator
11620
+ // amber for warnings
11621
+ error: COLORS.red
11622
+ // red for errors
11511
11623
  },
11512
11624
  border: {
11513
- // ANSI 241 is a medium slate gray for borders
11514
- default: "ansi256(241)",
11625
+ default: "ansi256(25)",
11626
+ // muted Docker blue border (#005faf)
11515
11627
  focus: COLORS.primary,
11516
11628
  error: COLORS.red
11517
11629
  },
11518
11630
  gradient: {
11519
- // Hex colors required for gradient-string
11520
- cyber: [HEX.primary, HEX.pink, HEX.peach]
11631
+ cyber: [HEX.primary, HEX.cyan, HEX.teal]
11521
11632
  },
11522
11633
  spinner: COLORS.primary
11523
11634
  };
@@ -11537,9 +11648,8 @@ var ICONS = {
11537
11648
  info: "\u2139",
11538
11649
  running: "\u25C9",
11539
11650
  thinking: "\u25D0",
11540
- // Actions (pentesting-specific)
11651
+ // Actions
11541
11652
  target: "\u25C8",
11542
- // Diamond for target
11543
11653
  scan: "\u25CE",
11544
11654
  exploit: "\u2607",
11545
11655
  shell: "\u276F",
@@ -11557,9 +11667,7 @@ var ICONS = {
11557
11667
  unlock: "\u2B1A",
11558
11668
  key: "\u26B7",
11559
11669
  flag: "\u2691",
11560
- // Simple flag
11561
11670
  pwned: "\u25C8"
11562
- // Compromised
11563
11671
  };
11564
11672
 
11565
11673
  // src/platform/tui/constants/display.ts
@@ -11570,7 +11678,11 @@ var TUI_DISPLAY_LIMITS = {
11570
11678
  reasoningPreview: 500,
11571
11679
  /** Reasoning history slice for display */
11572
11680
  reasoningHistorySlice: 1e3,
11573
- /** Tool input preview length (for command display) */
11681
+ /** Max number of output lines shown from a tool result (success) */
11682
+ toolOutputLines: 20,
11683
+ /** Max number of output lines shown from a tool result (error — show more) */
11684
+ errorOutputLines: 25,
11685
+ /** Tool output preview length (for command display) */
11574
11686
  toolInputPreview: 200,
11575
11687
  /** Tool input truncated length */
11576
11688
  toolInputTruncated: 197,
@@ -11601,7 +11713,11 @@ var TUI_DISPLAY_LIMITS = {
11601
11713
  /** Max flow nodes to display */
11602
11714
  maxFlowNodes: 50,
11603
11715
  /** Max stopped processes to show */
11604
- maxStoppedProcesses: 3
11716
+ maxStoppedProcesses: 3,
11717
+ /** Max chars for live reasoning preview in status sub-line */
11718
+ reasoningPreviewChars: 100,
11719
+ /** Max chars for thinking block first-line summary */
11720
+ thinkingSummaryChars: 72
11605
11721
  };
11606
11722
  var MESSAGE_STYLES = {
11607
11723
  colors: {
@@ -11619,8 +11735,10 @@ var MESSAGE_STYLES = {
11619
11735
  // Tool commands - light gray
11620
11736
  result: THEME.gray,
11621
11737
  // Tool results - light gray
11622
- status: THEME.primary
11623
- // Status - pastel red accent
11738
+ status: THEME.primary,
11739
+ // Status - teal accent
11740
+ thinking: THEME.cyan
11741
+ // Extended reasoning — sky blue (distinct from primary)
11624
11742
  },
11625
11743
  prefixes: {
11626
11744
  user: "\u276F",
@@ -11630,7 +11748,9 @@ var MESSAGE_STYLES = {
11630
11748
  error: "\u2717",
11631
11749
  tool: "\u23BF",
11632
11750
  result: "\u21B3",
11633
- status: "\u25C8"
11751
+ status: "\u25C8",
11752
+ thinking: "\u25D0"
11753
+ // Thinking/reasoning icon
11634
11754
  }
11635
11755
  };
11636
11756
  var COMMAND_DEFINITIONS = [
@@ -11753,7 +11873,7 @@ var useAgentState = () => {
11753
11873
  };
11754
11874
 
11755
11875
  // src/platform/tui/hooks/useAgentEvents.ts
11756
- import { useEffect } from "react";
11876
+ import { useEffect, useRef as useRef2 } from "react";
11757
11877
  var useAgentEvents = (agent, eventsRef, state) => {
11758
11878
  const {
11759
11879
  addMessage,
@@ -11769,35 +11889,51 @@ var useAgentEvents = (agent, eventsRef, state) => {
11769
11889
  lastStepTokensRef,
11770
11890
  clearAllTimers
11771
11891
  } = state;
11892
+ const reasoningBufferRef = useRef2("");
11772
11893
  useEffect(() => {
11773
11894
  const events = eventsRef.current;
11774
11895
  const onToolCall = (e) => {
11775
- setCurrentStatus(`Executing: ${e.data.toolName}`);
11896
+ if (NOISE_CLASSIFICATION.LOW_VISIBILITY.includes(e.data.toolName)) return;
11897
+ setCurrentStatus(`${e.data.toolName}\u2026`);
11776
11898
  const inputStr = formatToolInput(e.data.toolName, e.data.input);
11777
- addMessage("tool", inputStr ? `${e.data.toolName} ${inputStr}` : e.data.toolName);
11899
+ const label = inputStr ? `${toDisplayName(e.data.toolName)}(${inputStr})` : `${toDisplayName(e.data.toolName)}`;
11900
+ addMessage("tool", label);
11778
11901
  };
11779
11902
  const onToolResult = (e) => {
11903
+ if (NOISE_CLASSIFICATION.LOW_VISIBILITY.includes(e.data.toolName)) {
11904
+ return;
11905
+ }
11780
11906
  const icon = e.data.success ? "\u2713" : "\u2717";
11781
- const content = e.data.success ? e.data.outputSummary || e.data.output || "" : e.data.error || e.data.output || "Unknown error";
11782
- const maxLen = e.data.success ? TUI_DISPLAY_LIMITS.toolOutputPreview : TUI_DISPLAY_LIMITS.errorPreview;
11783
- const preview = content.slice(0, maxLen).replace(/\n/g, " ");
11784
- const more = content.length > maxLen ? "..." : "";
11785
- addMessage("result", `${icon} ${preview}${more}`);
11907
+ const rawContent = e.data.success ? e.data.outputSummary || e.data.output || "" : e.data.error || e.data.output || "Unknown error";
11908
+ if (!rawContent.trim()) {
11909
+ addMessage("result", `${icon}`);
11910
+ return;
11911
+ }
11912
+ const lines = rawContent.replace(/^\s*-n\s+/gm, "").split("\n").map((l) => l.trimEnd()).filter((l, i, arr) => {
11913
+ if (l === "" && arr[i - 1] === "") return false;
11914
+ return true;
11915
+ });
11916
+ addMessage("result", `${icon} ${lines.join("\n")}`);
11786
11917
  };
11787
11918
  const onComplete = (e) => {
11788
- addMessage("system", `\u2713 Complete ${formatMeta(e.data.durationMs || 0, (e.data.tokens?.input || 0) + (e.data.tokens?.output || 0))}`);
11919
+ const meta = formatMeta(e.data.durationMs || 0, (e.data.tokens?.input || 0) + (e.data.tokens?.output || 0));
11920
+ addMessage("system", `\u2713 Done ${meta}`);
11789
11921
  lastResponseMetaRef.current = { durationMs: e.data.durationMs, tokens: e.data.tokens };
11790
11922
  };
11791
11923
  const onRetry = (e) => {
11792
11924
  handleRetry(e, addMessage, setRetryState, retryCountdownRef, retryCountRef);
11793
11925
  };
11794
11926
  const onThink = (e) => {
11795
- const t = e.data.thought;
11796
- setCurrentStatus(t.length > TUI_DISPLAY_LIMITS.statusThoughtPreview ? t.substring(0, TUI_DISPLAY_LIMITS.statusThoughtTruncated) + "..." : t);
11927
+ setCurrentStatus(e.data.thought);
11797
11928
  };
11798
11929
  const onError = (e) => {
11799
11930
  addMessage("error", e.data.message || "An error occurred");
11800
11931
  };
11932
+ const onAIResponse = (e) => {
11933
+ if (e.data.content?.trim()) {
11934
+ addMessage("ai", e.data.content.trim());
11935
+ }
11936
+ };
11801
11937
  const onUsageUpdate = (e) => {
11802
11938
  const stepTokens = e.data.inputTokens + e.data.outputTokens;
11803
11939
  if (stepTokens < lastStepTokensRef.current) {
@@ -11810,7 +11946,7 @@ var useAgentEvents = (agent, eventsRef, state) => {
11810
11946
  addMessage("system", `[FLAG] ${e.data.flag} (total: ${e.data.totalFlags})`);
11811
11947
  };
11812
11948
  const onPhaseChange = (e) => {
11813
- addMessage("system", `[Phase] ${e.data.fromPhase} -> ${e.data.toPhase} (${e.data.reason})`);
11949
+ addMessage("system", `Phase ${e.data.fromPhase} \u2192 ${e.data.toPhase} (${e.data.reason})`);
11814
11950
  const s = agent.getState();
11815
11951
  setStats({
11816
11952
  phase: e.data.toPhase,
@@ -11819,6 +11955,25 @@ var useAgentEvents = (agent, eventsRef, state) => {
11819
11955
  todo: s.getTodo().length
11820
11956
  });
11821
11957
  };
11958
+ const onReasoningStart = () => {
11959
+ reasoningBufferRef.current = "";
11960
+ setCurrentStatus("Thinking\u2026");
11961
+ };
11962
+ const onReasoningDelta = (e) => {
11963
+ reasoningBufferRef.current += e.data.content;
11964
+ const chars = reasoningBufferRef.current.length;
11965
+ const firstLine = reasoningBufferRef.current.split("\n")[0]?.slice(0, TUI_DISPLAY_LIMITS.reasoningPreviewChars) || "";
11966
+ setCurrentStatus(`Thinking\u2026 ${chars} chars
11967
+ ${firstLine}`);
11968
+ };
11969
+ const onReasoningEnd = () => {
11970
+ const text = reasoningBufferRef.current.trim();
11971
+ reasoningBufferRef.current = "";
11972
+ setCurrentStatus("");
11973
+ if (text) {
11974
+ addMessage("thinking", text);
11975
+ }
11976
+ };
11822
11977
  setInputHandler((p) => {
11823
11978
  return new Promise((resolve) => {
11824
11979
  const isPassword = /password|passphrase/i.test(p);
@@ -11850,9 +12005,11 @@ var useAgentEvents = (agent, eventsRef, state) => {
11850
12005
  });
11851
12006
  });
11852
12007
  setCommandEventEmitter((event) => {
12008
+ if (event.type === COMMAND_EVENT_TYPES.COMMAND_START || event.type === COMMAND_EVENT_TYPES.COMMAND_SUCCESS) {
12009
+ return;
12010
+ }
11853
12011
  const icon = getCommandEventIcon(event.type);
11854
- const msg = event.detail ? `${icon} ${event.message}
11855
- ${event.detail}` : `${icon} ${event.message}`;
12012
+ const msg = event.detail ? `${icon} ${event.message} ${event.detail}` : `${icon} ${event.message}`;
11856
12013
  addMessage("system", msg);
11857
12014
  });
11858
12015
  const updateStats = () => {
@@ -11875,6 +12032,10 @@ var useAgentEvents = (agent, eventsRef, state) => {
11875
12032
  events.on(EVENT_TYPES.PHASE_CHANGE, onPhaseChange);
11876
12033
  events.on(EVENT_TYPES.STATE_CHANGE, updateStats);
11877
12034
  events.on(EVENT_TYPES.START, updateStats);
12035
+ events.on(EVENT_TYPES.REASONING_START, onReasoningStart);
12036
+ events.on(EVENT_TYPES.REASONING_DELTA, onReasoningDelta);
12037
+ events.on(EVENT_TYPES.REASONING_END, onReasoningEnd);
12038
+ events.on(EVENT_TYPES.AI_RESPONSE, onAIResponse);
11878
12039
  updateStats();
11879
12040
  return () => {
11880
12041
  events.removeAllListeners();
@@ -11900,6 +12061,16 @@ var useAgentEvents = (agent, eventsRef, state) => {
11900
12061
  eventsRef
11901
12062
  ]);
11902
12063
  };
12064
+ function toDisplayName(toolName) {
12065
+ const MAP = {
12066
+ [TOOL_NAMES.RUN_CMD]: "Bash",
12067
+ [TOOL_NAMES.BG_PROCESS]: "Process",
12068
+ [TOOL_NAMES.READ_FILE]: "Read",
12069
+ [TOOL_NAMES.WRITE_FILE]: "Write",
12070
+ [TOOL_NAMES.ASK_USER]: "Input"
12071
+ };
12072
+ return MAP[toolName] ?? toolName.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
12073
+ }
11903
12074
  function formatToolInput(toolName, input) {
11904
12075
  if (!input || Object.keys(input).length === 0) return "";
11905
12076
  try {
@@ -11955,24 +12126,24 @@ Options: ${request.options.join(", ")}`;
11955
12126
  }
11956
12127
  function getCommandEventIcon(eventType) {
11957
12128
  const icons = {
11958
- [COMMAND_EVENT_TYPES.TOOL_MISSING]: "[!]",
11959
- [COMMAND_EVENT_TYPES.TOOL_INSTALL]: "[install]",
11960
- [COMMAND_EVENT_TYPES.TOOL_INSTALLED]: "[ok]",
11961
- [COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED]: "[fail]",
11962
- [COMMAND_EVENT_TYPES.TOOL_RETRY]: "[retry]",
11963
- [COMMAND_EVENT_TYPES.COMMAND_START]: "[>]",
11964
- [COMMAND_EVENT_TYPES.COMMAND_SUCCESS]: "[ok]",
11965
- [COMMAND_EVENT_TYPES.COMMAND_FAILED]: "[x]",
11966
- [COMMAND_EVENT_TYPES.COMMAND_ERROR]: "[err]",
11967
- [COMMAND_EVENT_TYPES.INPUT_REQUIRED]: "[auth]"
12129
+ [COMMAND_EVENT_TYPES.TOOL_MISSING]: "!",
12130
+ [COMMAND_EVENT_TYPES.TOOL_INSTALL]: "\u2193",
12131
+ [COMMAND_EVENT_TYPES.TOOL_INSTALLED]: "\u2713",
12132
+ [COMMAND_EVENT_TYPES.TOOL_INSTALL_FAILED]: "\u2717",
12133
+ [COMMAND_EVENT_TYPES.TOOL_RETRY]: "\u21BA",
12134
+ [COMMAND_EVENT_TYPES.COMMAND_START]: "\u25B6",
12135
+ [COMMAND_EVENT_TYPES.COMMAND_SUCCESS]: "\u2713",
12136
+ [COMMAND_EVENT_TYPES.COMMAND_FAILED]: "\u2717",
12137
+ [COMMAND_EVENT_TYPES.COMMAND_ERROR]: "\u26A0",
12138
+ [COMMAND_EVENT_TYPES.INPUT_REQUIRED]: "\u{1F512}"
11968
12139
  };
11969
- return icons[eventType] || "[-]";
12140
+ return icons[eventType] || "\u2022";
11970
12141
  }
11971
12142
 
11972
12143
  // src/platform/tui/hooks/useAgent.ts
11973
12144
  var useAgent = (shouldAutoApprove, target) => {
11974
12145
  const [agent] = useState2(() => AgentFactory.createMainAgent(shouldAutoApprove));
11975
- const eventsRef = useRef2(agent.getEventEmitter());
12146
+ const eventsRef = useRef3(agent.getEventEmitter());
11976
12147
  const state = useAgentState();
11977
12148
  const {
11978
12149
  messages,
@@ -12025,7 +12196,7 @@ var useAgent = (shouldAutoApprove, target) => {
12025
12196
  setCurrentStatus("");
12026
12197
  addMessage("system", "Interrupted");
12027
12198
  }, [agent, addMessage, manageTimer, setIsProcessing, setCurrentStatus]);
12028
- const inputRequestRef = useRef2(inputRequest);
12199
+ const inputRequestRef = useRef3(inputRequest);
12029
12200
  inputRequestRef.current = inputRequest;
12030
12201
  const cancelInputRequest = useCallback2(() => {
12031
12202
  const ir = inputRequestRef.current;
@@ -12244,9 +12415,76 @@ var MessageList = memo(({ messages }) => {
12244
12415
  }
12245
12416
  ) }, msg.id);
12246
12417
  }
12247
- }
12248
- return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: MESSAGE_STYLES.colors[msg.type], dimColor: msg.type === "result", children: [
12249
- MESSAGE_STYLES.prefixes[msg.type],
12418
+ return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: THEME.gray, children: [
12419
+ "\u2022 ",
12420
+ msg.content
12421
+ ] }) }, msg.id);
12422
+ }
12423
+ if (msg.type === "thinking") {
12424
+ const lines = msg.content.split("\n");
12425
+ const charCount = msg.content.length;
12426
+ const firstLine = lines[0]?.slice(0, TUI_DISPLAY_LIMITS.thinkingSummaryChars) || "";
12427
+ const isMultiLine = lines.length > 1 || charCount > TUI_DISPLAY_LIMITS.thinkingSummaryChars;
12428
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: 0, marginBottom: 1, children: [
12429
+ /* @__PURE__ */ jsxs2(Box2, { children: [
12430
+ /* @__PURE__ */ jsx2(Text2, { color: THEME.cyan, children: "\u25D0 " }),
12431
+ /* @__PURE__ */ jsx2(Text2, { color: THEME.cyan, bold: true, children: "Thinking" }),
12432
+ /* @__PURE__ */ jsx2(Text2, { color: THEME.gray, children: ` (${lines.length} line${lines.length !== 1 ? "s" : ""}, ${charCount} chars)` })
12433
+ ] }),
12434
+ lines.map((line, i) => /* @__PURE__ */ jsxs2(Box2, { children: [
12435
+ /* @__PURE__ */ jsx2(Text2, { color: THEME.cyan, children: "\u2502 " }),
12436
+ /* @__PURE__ */ jsx2(Text2, { color: THEME.gray, children: line })
12437
+ ] }, i)),
12438
+ /* @__PURE__ */ jsxs2(Box2, { children: [
12439
+ /* @__PURE__ */ jsx2(Text2, { color: THEME.cyan, children: "\u2570\u2500" }),
12440
+ isMultiLine && /* @__PURE__ */ jsx2(Text2, { color: THEME.gray, children: ` ${firstLine}\u2026` })
12441
+ ] })
12442
+ ] }, msg.id);
12443
+ }
12444
+ if (msg.type === "tool") {
12445
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
12446
+ /* @__PURE__ */ jsx2(Text2, { color: THEME.gray, children: "\u23BF " }),
12447
+ /* @__PURE__ */ jsx2(Text2, { color: THEME.primary, children: msg.content })
12448
+ ] }, msg.id);
12449
+ }
12450
+ if (msg.type === "result") {
12451
+ const isSuccess = msg.content.startsWith("\u2713");
12452
+ const isFailure = msg.content.startsWith("\u2717");
12453
+ const color = isSuccess ? THEME.primary : isFailure ? THEME.red : THEME.gray;
12454
+ const lines = msg.content.split("\n");
12455
+ const firstLine = lines[0] || "";
12456
+ const restLines = lines.slice(1);
12457
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
12458
+ /* @__PURE__ */ jsxs2(Box2, { children: [
12459
+ /* @__PURE__ */ jsx2(Text2, { color: THEME.gray, children: "\u21B3 " }),
12460
+ /* @__PURE__ */ jsx2(Text2, { color, children: firstLine })
12461
+ ] }),
12462
+ restLines.map((line, i) => /* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, children: /* @__PURE__ */ jsx2(Text2, { color: THEME.gray, children: line }) }, i))
12463
+ ] }, msg.id);
12464
+ }
12465
+ if (msg.type === "ai" || msg.type === "assistant") {
12466
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginBottom: 0, children: /* @__PURE__ */ jsx2(Text2, { color: THEME.white, children: msg.content }) }, msg.id);
12467
+ }
12468
+ if (msg.type === "error") {
12469
+ return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: THEME.red, children: [
12470
+ "\u2717 ",
12471
+ msg.content
12472
+ ] }) }, msg.id);
12473
+ }
12474
+ if (msg.type === "user") {
12475
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
12476
+ /* @__PURE__ */ jsx2(Text2, { color: THEME.primary, children: "\u276F " }),
12477
+ /* @__PURE__ */ jsx2(Text2, { color: THEME.white, children: msg.content })
12478
+ ] }, msg.id);
12479
+ }
12480
+ if (msg.type === "system") {
12481
+ return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: THEME.gray, children: [
12482
+ "\u2022 ",
12483
+ msg.content
12484
+ ] }) }, msg.id);
12485
+ }
12486
+ return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { color: MESSAGE_STYLES.colors[msg.type] ?? THEME.gray, children: [
12487
+ MESSAGE_STYLES.prefixes[msg.type] ?? "\u2022",
12250
12488
  " ",
12251
12489
  msg.content
12252
12490
  ] }) }, msg.id);
@@ -12287,39 +12525,48 @@ var StatusDisplay = memo3(({
12287
12525
  return err.length > DISPLAY_LIMITS.RETRY_ERROR_PREVIEW ? err.substring(0, DISPLAY_LIMITS.RETRY_ERROR_TRUNCATED) + "..." : err;
12288
12526
  };
12289
12527
  const meta = formatMeta(elapsedTime * 1e3, currentTokens);
12528
+ const statusLines = currentStatus ? currentStatus.split("\n").filter(Boolean) : [];
12529
+ const statusMain = statusLines[0] || "Processing...";
12530
+ const statusSub = statusLines.slice(1).join(" ");
12531
+ const isThinkingStatus = statusMain.startsWith("Thinking");
12290
12532
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 0, children: [
12291
12533
  retryState.status === "retrying" && /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
12292
12534
  /* @__PURE__ */ jsx4(Text4, { color: THEME.yellow, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: THEME.yellow }) }),
12293
- /* @__PURE__ */ jsxs3(Text4, { color: THEME.yellow, children: [
12535
+ /* @__PURE__ */ jsxs3(Text4, { color: THEME.yellow, bold: true, children: [
12294
12536
  " \u27F3 Retry #",
12295
- retryState.attempt
12537
+ retryState.attempt,
12538
+ "/",
12539
+ retryState.maxRetries
12296
12540
  ] }),
12297
12541
  /* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
12298
- " \xB7 ",
12299
- truncateError(retryState.error)
12300
- ] }),
12301
- /* @__PURE__ */ jsxs3(Text4, { color: THEME.primary, bold: true, children: [
12302
- " \xB7 ",
12542
+ " \u2014 ",
12303
12543
  retryState.countdown,
12304
- "s"
12544
+ "s \xB7 ",
12545
+ truncateError(retryState.error)
12305
12546
  ] })
12306
12547
  ] }),
12307
- isProcessing && retryState.status !== "retrying" && /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
12308
- /* @__PURE__ */ jsx4(Text4, { color: THEME.primary, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: THEME.primary }) }),
12309
- /* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
12310
- " ",
12311
- currentStatus || "Processing"
12548
+ isProcessing && retryState.status !== "retrying" && /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginBottom: 1, children: [
12549
+ /* @__PURE__ */ jsxs3(Box3, { children: [
12550
+ /* @__PURE__ */ jsx4(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, children: /* @__PURE__ */ jsx4(MusicSpinner, { color: isThinkingStatus ? THEME.cyan : THEME.primary }) }),
12551
+ /* @__PURE__ */ jsxs3(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.primary, bold: true, children: [
12552
+ " ",
12553
+ statusMain
12554
+ ] }),
12555
+ /* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
12556
+ " ",
12557
+ meta
12558
+ ] })
12312
12559
  ] }),
12313
- /* @__PURE__ */ jsxs3(Text4, { color: THEME.gray, children: [
12314
- " ",
12315
- meta
12316
- ] })
12560
+ statusSub ? /* @__PURE__ */ jsx4(Box3, { paddingLeft: 2, children: /* @__PURE__ */ jsxs3(Text4, { color: isThinkingStatus ? THEME.cyan : THEME.gray, children: [
12561
+ "\u2502 ",
12562
+ statusSub
12563
+ ] }) }) : null
12317
12564
  ] })
12318
12565
  ] });
12319
12566
  });
12320
12567
 
12321
12568
  // src/platform/tui/components/ChatInput.tsx
12322
- import { useMemo, useCallback as useCallback3, useRef as useRef3, memo as memo4 } from "react";
12569
+ import { useMemo, useCallback as useCallback3, useRef as useRef4, memo as memo4, useState as useState4 } from "react";
12323
12570
  import { Box as Box4, Text as Text5, useInput } from "ink";
12324
12571
  import TextInput from "ink-text-input";
12325
12572
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
@@ -12342,24 +12589,25 @@ var ChatInput = memo4(({
12342
12589
  return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
12343
12590
  }, [isSlashMode, partialCmd, hasArgs]);
12344
12591
  const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
12345
- const suggestionsRef = useRef3(suggestions);
12592
+ const suggestionsRef = useRef4(suggestions);
12346
12593
  suggestionsRef.current = suggestions;
12347
- const isSlashModeRef = useRef3(isSlashMode);
12594
+ const isSlashModeRef = useRef4(isSlashMode);
12348
12595
  isSlashModeRef.current = isSlashMode;
12349
- const hasArgsRef = useRef3(hasArgs);
12596
+ const hasArgsRef = useRef4(hasArgs);
12350
12597
  hasArgsRef.current = hasArgs;
12351
- const inputRequestRef = useRef3(inputRequest);
12598
+ const inputRequestRef = useRef4(inputRequest);
12352
12599
  inputRequestRef.current = inputRequest;
12353
- const onChangeRef = useRef3(onChange);
12600
+ const onChangeRef = useRef4(onChange);
12354
12601
  onChangeRef.current = onChange;
12602
+ const [inputKey, setInputKey] = useState4(0);
12355
12603
  useInput(useCallback3((_input, key) => {
12356
12604
  if (inputRequestRef.current.status === "active") return;
12357
12605
  if (key.tab && isSlashModeRef.current && !hasArgsRef.current && suggestionsRef.current.length > 0) {
12358
12606
  const best = suggestionsRef.current[0];
12359
12607
  const argsHint = best.args ? " " : "";
12360
12608
  const completed = `/${best.name}${argsHint}`;
12361
- onChangeRef.current("");
12362
- setTimeout(() => onChangeRef.current(completed), 0);
12609
+ onChangeRef.current(completed);
12610
+ setInputKey((k) => k + 1);
12363
12611
  }
12364
12612
  }, []));
12365
12613
  return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
@@ -12425,7 +12673,8 @@ var ChatInput = memo4(({
12425
12673
  onChange,
12426
12674
  onSubmit,
12427
12675
  placeholder
12428
- }
12676
+ },
12677
+ inputKey
12429
12678
  )
12430
12679
  ] })
12431
12680
  }
@@ -12487,9 +12736,9 @@ var footer_default = Footer;
12487
12736
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
12488
12737
  var App = ({ autoApprove = false, target }) => {
12489
12738
  const { exit } = useApp();
12490
- const [input, setInput] = useState4("");
12491
- const [secretInput, setSecretInput] = useState4("");
12492
- const [autoApproveMode, setAutoApproveMode] = useState4(autoApprove);
12739
+ const [input, setInput] = useState5("");
12740
+ const [secretInput, setSecretInput] = useState5("");
12741
+ const [autoApproveMode, setAutoApproveMode] = useState5(autoApprove);
12493
12742
  const {
12494
12743
  agent,
12495
12744
  messages,
@@ -12508,11 +12757,11 @@ var App = ({ autoApprove = false, target }) => {
12508
12757
  addMessage,
12509
12758
  refreshStats
12510
12759
  } = useAgent(autoApproveMode, target);
12511
- const isProcessingRef = useRef4(isProcessing);
12760
+ const isProcessingRef = useRef5(isProcessing);
12512
12761
  isProcessingRef.current = isProcessing;
12513
- const autoApproveModeRef = useRef4(autoApproveMode);
12762
+ const autoApproveModeRef = useRef5(autoApproveMode);
12514
12763
  autoApproveModeRef.current = autoApproveMode;
12515
- const inputRequestRef = useRef4(inputRequest);
12764
+ const inputRequestRef = useRef5(inputRequest);
12516
12765
  inputRequestRef.current = inputRequest;
12517
12766
  const handleExit = useCallback4(() => {
12518
12767
  const ir = inputRequestRef.current;
@@ -12755,6 +13004,7 @@ var CLI_SCAN_TYPES = Object.freeze([
12755
13004
  ]);
12756
13005
 
12757
13006
  // src/platform/tui/main.tsx
13007
+ import gradient from "gradient-string";
12758
13008
  import { jsx as jsx8 } from "react/jsx-runtime";
12759
13009
  initDebugLogger();
12760
13010
  var program = new Command();
@@ -12763,13 +13013,13 @@ program.command("interactive", { isDefault: true }).alias("i").description("Star
12763
13013
  const opts = program.opts();
12764
13014
  const skipPermissions = opts.dangerouslySkipPermissions || false;
12765
13015
  console.clear();
12766
- console.log(chalk.hex(THEME.primary)(ASCII_BANNER));
13016
+ console.log(gradient([HEX.primary, HEX.cyan, HEX.teal]).multiline(ASCII_BANNER));
12767
13017
  console.log(
12768
- " " + chalk.hex(THEME.text.secondary)(`v${APP_VERSION}`) + chalk.hex(THEME.text.muted)(" \u2502 ") + chalk.hex(THEME.primary)("Type /help for commands") + "\n"
13018
+ " " + chalk.hex(HEX.gray)(`v${APP_VERSION}`) + chalk.hex(HEX.gray)(" \u2502 ") + chalk.hex(HEX.primary)("Type /help for commands") + "\n"
12769
13019
  );
12770
13020
  if (skipPermissions) {
12771
- console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions"));
12772
- console.log(chalk.hex(THEME.status.error)("[!] All tool executions will be auto-approved!\n"));
13021
+ console.log(chalk.hex(HEX.red)("[!] WARNING: Running with --dangerously-skip-permissions"));
13022
+ console.log(chalk.hex(HEX.red)("[!] All tool executions will be auto-approved!\n"));
12773
13023
  }
12774
13024
  const { waitUntilExit } = render(
12775
13025
  /* @__PURE__ */ jsx8(
@@ -12785,11 +13035,11 @@ program.command("interactive", { isDefault: true }).alias("i").description("Star
12785
13035
  program.command("run <objective>").alias("r").description("Run a single objective and exit").option("-o, --output <file>", "Output file for results").option("--max-steps <n>", "Maximum number of steps", String(CLI_DEFAULT.MAX_STEPS)).action(async (objective, options) => {
12786
13036
  const opts = program.opts();
12787
13037
  const skipPermissions = opts.dangerouslySkipPermissions || false;
12788
- console.log(chalk.hex(THEME.primary)(ASCII_BANNER));
13038
+ console.log(gradient([HEX.primary, HEX.cyan, HEX.teal]).multiline(ASCII_BANNER));
12789
13039
  if (skipPermissions) {
12790
- console.log(chalk.hex(THEME.status.error)("[!] WARNING: Running with --dangerously-skip-permissions\n"));
13040
+ console.log(chalk.hex(HEX.red)("[!] WARNING: Running with --dangerously-skip-permissions\n"));
12791
13041
  }
12792
- console.log(chalk.hex(THEME.primary)(`[target] Objective: ${objective}
13042
+ console.log(chalk.hex(HEX.primary)(`[target] Objective: ${objective}
12793
13043
  `));
12794
13044
  const agent = AgentFactory.createMainAgent(skipPermissions);
12795
13045
  if (skipPermissions) {
@@ -12806,18 +13056,18 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
12806
13056
  process.on("SIGTERM", () => shutdown(EXIT_CODES.SIGTERM));
12807
13057
  try {
12808
13058
  const result2 = await agent.execute(objective);
12809
- console.log(chalk.hex(THEME.status.success)("\n[+] Assessment complete!\n"));
13059
+ console.log(chalk.hex(HEX.gray)("\n[+] Assessment complete!\n"));
12810
13060
  console.log(result2);
12811
13061
  if (options.output) {
12812
13062
  const fs = await import("fs/promises");
12813
13063
  await fs.writeFile(options.output, JSON.stringify({ result: result2 }, null, 2));
12814
- console.log(chalk.hex(THEME.primary)(`
13064
+ console.log(chalk.hex(HEX.primary)(`
12815
13065
  [+] Report saved to: ${options.output}`));
12816
13066
  }
12817
13067
  await shutdown(0);
12818
13068
  } catch (error) {
12819
13069
  const errorMessage = error instanceof Error ? error.message : String(error);
12820
- console.error(chalk.hex(THEME.status.error)(`
13070
+ console.error(chalk.hex(HEX.red)(`
12821
13071
  [-] Failed: ${errorMessage}`));
12822
13072
  await shutdown(1);
12823
13073
  }
@@ -12825,8 +13075,8 @@ program.command("run <objective>").alias("r").description("Run a single objectiv
12825
13075
  program.command("scan <target>").description("Quick scan a target").option("-s, --scan-type <type>", `Scan type (${CLI_SCAN_TYPES.join("|")})`, CLI_DEFAULT.SCAN_TYPE).option("-p, --ports <ports>", "Specific ports to scan").action(async (target, options) => {
12826
13076
  const opts = program.opts();
12827
13077
  const skipPermissions = opts.dangerouslySkipPermissions || false;
12828
- console.log(chalk.hex(THEME.primary)(ASCII_BANNER));
12829
- console.log(chalk.hex(THEME.primary)(`
13078
+ console.log(gradient([HEX.primary, HEX.cyan, HEX.teal]).multiline(ASCII_BANNER));
13079
+ console.log(chalk.hex(HEX.primary)(`
12830
13080
  [scan] Target: ${target} (${options.scanType})
12831
13081
  `));
12832
13082
  const agent = AgentFactory.createMainAgent(skipPermissions);
@@ -12839,62 +13089,61 @@ program.command("scan <target>").description("Quick scan a target").option("-s,
12839
13089
  process.on("SIGTERM", () => shutdown(EXIT_CODES.SIGTERM));
12840
13090
  try {
12841
13091
  await agent.execute(`Perform a ${options.scanType} scan on ${target}${options.ports ? ` focusing on ports ${options.ports}` : ""}. Analyze the results and identify potential vulnerabilities.`);
12842
- console.log(chalk.hex(THEME.status.success)("[+] Scan complete!"));
13092
+ console.log(chalk.hex(HEX.gray)("[+] Scan complete!"));
12843
13093
  await shutdown(0);
12844
13094
  } catch (error) {
12845
13095
  const errorMessage = error instanceof Error ? error.message : String(error);
12846
- console.error(chalk.hex(THEME.status.error)(`[-] Scan failed: ${errorMessage}`));
13096
+ console.error(chalk.hex(HEX.red)(`[-] Scan failed: ${errorMessage}`));
12847
13097
  await shutdown(1);
12848
13098
  }
12849
13099
  });
12850
13100
  program.command("help-extended").description("Show extended help with examples").action(() => {
12851
- console.log(chalk.hex(THEME.primary)(ASCII_BANNER));
13101
+ console.log(gradient([HEX.primary, HEX.cyan, HEX.teal]).multiline(ASCII_BANNER));
12852
13102
  console.log(`
12853
- ${chalk.hex(THEME.primary)(APP_NAME + " - Autonomous Penetration Testing AI")}
13103
+ ${chalk.hex(HEX.primary)(APP_NAME + " - Autonomous Penetration Testing AI")}
12854
13104
 
12855
- ${chalk.hex(THEME.status.warning)("Usage:")}
13105
+ ${chalk.hex(HEX.yellow)("Usage:")}
12856
13106
 
12857
- ${chalk.hex(THEME.status.success)("$ pentesting")} Start interactive mode
12858
- ${chalk.hex(THEME.status.success)("$ pentesting -t 192.168.1.1")} Start with target
12859
- ${chalk.hex(THEME.status.success)("$ pentesting --dangerously-skip-permissions")} Auto-approve all tools
13107
+ ${chalk.hex(HEX.gray)("$ pentesting")} Start interactive mode
13108
+ ${chalk.hex(HEX.gray)("$ pentesting -t 192.168.1.1")} Start with target
13109
+ ${chalk.hex(HEX.gray)("$ pentesting --dangerously-skip-permissions")} Auto-approve all tools
12860
13110
 
12861
- ${chalk.hex(THEME.status.warning)("Commands:")}
13111
+ ${chalk.hex(HEX.yellow)("Commands:")}
12862
13112
 
12863
- ${chalk.hex(THEME.primary)("pentesting")} Interactive TUI mode
12864
- ${chalk.hex(THEME.primary)("pentesting run <objective>")} Run single objective
12865
- ${chalk.hex(THEME.primary)("pentesting scan <target>")} Quick scan target
13113
+ ${chalk.hex(HEX.primary)("pentesting")} Interactive TUI mode
13114
+ ${chalk.hex(HEX.primary)("pentesting run <objective>")} Run single objective
13115
+ ${chalk.hex(HEX.primary)("pentesting scan <target>")} Quick scan target
12866
13116
 
12867
- ${chalk.hex(THEME.status.warning)("Options:")}
13117
+ ${chalk.hex(HEX.yellow)("Options:")}
12868
13118
 
12869
- ${chalk.hex(THEME.primary)("--dangerously-skip-permissions")} Skip all permission prompts
12870
- ${chalk.hex(THEME.primary)("-t, --target <ip>")} Set target
12871
- ${chalk.hex(THEME.primary)("-o, --output <file>")} Save results to file
13119
+ ${chalk.hex(HEX.primary)("--dangerously-skip-permissions")} Skip all permission prompts
13120
+ ${chalk.hex(HEX.primary)("-t, --target <ip>")} Set target
13121
+ ${chalk.hex(HEX.primary)("-o, --output <file>")} Save results to file
12872
13122
 
12873
- ${chalk.hex(THEME.status.warning)("Interactive Commands:")}
13123
+ ${chalk.hex(HEX.yellow)("Interactive Commands:")}
12874
13124
 
12875
- ${chalk.hex(THEME.primary)("/target <ip>")} Set target
12876
- ${chalk.hex(THEME.primary)("/start")} Start autonomous mode
12877
- ${chalk.hex(THEME.primary)("/config")} Manage configuration
12878
- ${chalk.hex(THEME.primary)("/hint <text>")} Provide hint
12879
- ${chalk.hex(THEME.primary)("/findings")} Show findings
12880
- ${chalk.hex(THEME.primary)("/reset")} Reset session
13125
+ ${chalk.hex(HEX.primary)("/target <ip>")} Set target
13126
+ ${chalk.hex(HEX.primary)("/start")} Start autonomous mode
13127
+ ${chalk.hex(HEX.primary)("/hint <text>")} Provide hint
13128
+ ${chalk.hex(HEX.primary)("/findings")} Show findings
13129
+ ${chalk.hex(HEX.primary)("/clear")} Reset session
12881
13130
 
12882
- ${chalk.hex(THEME.status.warning)("Examples:")}
13131
+ ${chalk.hex(HEX.yellow)("Examples:")}
12883
13132
 
12884
- ${chalk.hex(THEME.text.muted)("# Full autonomous mode")}
13133
+ ${chalk.hex(HEX.gray)("# Full autonomous mode")}
12885
13134
  $ pentesting --dangerously-skip-permissions -t 10.10.10.5
12886
13135
 
12887
- ${chalk.hex(THEME.text.muted)("# Run specific objective")}
13136
+ ${chalk.hex(HEX.gray)("# Run specific objective")}
12888
13137
  $ pentesting run "Find SQL injection" -t http://target.com -o report.json
12889
13138
 
12890
- ${chalk.hex(THEME.text.muted)("# Quick vulnerability scan")}
13139
+ ${chalk.hex(HEX.gray)("# Quick vulnerability scan")}
12891
13140
  $ pentesting scan 192.168.1.1 -s vuln
12892
13141
 
12893
- ${chalk.hex(THEME.status.warning)("Environment:")}
13142
+ ${chalk.hex(HEX.yellow)("Environment:")}
12894
13143
 
12895
- ${chalk.hex(THEME.primary)("PENTEST_API_KEY")} Required - LLM API key
12896
- ${chalk.hex(THEME.primary)("PENTEST_BASE_URL")} Optional - AI API base URL
12897
- ${chalk.hex(THEME.primary)("PENTEST_MODEL")} Optional - Model override
13144
+ ${chalk.hex(HEX.primary)("PENTEST_API_KEY")} Required - LLM API key
13145
+ ${chalk.hex(HEX.primary)("PENTEST_BASE_URL")} Optional - AI API base URL
13146
+ ${chalk.hex(HEX.primary)("PENTEST_MODEL")} Optional - Model override
12898
13147
  `);
12899
13148
  });
12900
13149
  program.parse();
@@ -4,48 +4,46 @@ You are an **elite autonomous penetration testing AI** conducting authorized ope
4
4
  You think and act like a **senior offensive security researcher competing in a CTF**.
5
5
  You have direct access to all tools. **You can write your own code** — if a tool or PoC doesn't exist, build it yourself.
6
6
 
7
- ## FIRST TURN: ANALYZE USER INTENT
7
+ ## FIRST TURN: ANALYZE USER INTENT (OVERRIDES ALL OTHER RULES)
8
8
 
9
- **Before taking any action, you MUST analyze the user's input on the FIRST turn.**
9
+ **⚠️ ON THE FIRST TURN, THIS SECTION TAKES ABSOLUTE PRIORITY OVER EVERY OTHER RULE — including "EVERY TURN MUST PRODUCE TOOL CALLS" below.**
10
+
11
+ **Before taking any action, you MUST classify the user's input:**
10
12
 
11
13
  ### Intent Classification (Check in Order)
12
14
  1. **Greeting/Small Talk** → Examples: "hi", "hello", "hey", "안녕", "what's up", "how are you"
13
15
  - **Response**: Brief friendly greeting + ask what target they want to attack
14
- - **DO NOT**: Start scanning, searching, or running any commands
16
+ - **ZERO TOOL CALLS** — just respond with text. Do NOT call update_mission, get_state, or ANY tool.
15
17
 
16
18
  2. **Question/Help Request** → Examples: "how do I...", "what is...", "can you explain...", "help"
17
19
  - **Response**: Answer the question directly using your knowledge
18
- - **DO NOT**: Start pentesting operations unless explicitly requested
20
+ - **ZERO TOOL CALLS** unless answering requires a data lookup
21
+
22
+ 3. **Hint/Additional Context** → Examples: contextual info, strategy suggestions, single words that aren't targets
23
+ - **Response**: Acknowledge, store mentally, ask for clarification if needed
24
+ - **ZERO TOOL CALLS** — hints are NOT targets
19
25
 
20
- 3. **Unclear/Ambiguous Input** → Examples: single word that's not a target, incomplete sentences
26
+ 4. **Unclear/Ambiguous Input** → Examples: single word that's not a target, incomplete sentences
21
27
  - **Response**: Ask clarifying question: "What target would you like me to attack?"
22
- - **DO NOT**: Assume it's a target and start scanning
28
+ - **ZERO TOOL CALLS** — do NOT assume it's a target and start scanning
23
29
 
24
- 4. **Pentesting Request** → Examples: IP address, domain, "scan X", "attack Y", "find vulnerabilities in..."
30
+ 5. **Pentesting Request** → Examples: IP address, domain, "scan X", "attack Y", "find vulnerabilities in..."
25
31
  - **Response**: Proceed with reconnaissance and attack workflow
26
32
  - **REQUIRED**: Call tools and execute the pentesting loop
27
33
 
28
34
  ### Greeting Response Template
29
35
  ```
30
- 👋 Hello! I'm your pentesting agent. I can help you with:
36
+ I'm your pentesting agent, ready to help with:
31
37
  - Network reconnaissance and scanning
32
38
  - Vulnerability discovery and exploitation
33
39
  - Post-exploitation and privilege escalation
34
- - CTF challenges and security assessments
35
40
 
36
- What target would you like me to attack? (e.g., IP address, domain, or CTF challenge)
41
+ What target would you like me to attack? (IP, domain, or CTF challenge)
37
42
  ```
38
43
 
39
44
  ## SUBSEQUENT TURNS: EVERY TURN MUST PRODUCE TOOL CALLS
40
45
 
41
- **Once pentesting has started, you MUST call at least one tool on EVERY SINGLE TURN.** No exceptions.
42
-
43
- - FORBIDDEN: Outputting text without tool calls (planning, summarizing, asking)
44
- - FORBIDDEN: "Let me think about this..." or "I'll plan my approach..."
45
- - FORBIDDEN: Asking the user "Should I do X?" — **just do it**
46
- - REQUIRED: Think briefly in your reasoning, then IMMEDIATELY call tools
47
- - REQUIRED: When uncertain, `web_search` is ALWAYS a valid action
48
- - REQUIRED: Multiple parallel tool calls when possible (maximize throughput)
46
+ **Once pentesting has started (target is set and attack is underway), you MUST call at least one tool on EVERY SINGLE TURN.** No exceptions.
49
47
 
50
48
  **Speed mindset: Treat every engagement like a 4-hour CTF.** Every second without a tool call is wasted time.
51
49
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pentesting",
3
- "version": "0.44.1",
3
+ "version": "0.45.0",
4
4
  "description": "Autonomous Penetration Testing AI Agent",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",