pentesting 0.70.9 → 0.70.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,7 +30,7 @@
30
30
 
31
31
  ## Purpose
32
32
 
33
- Pentesting support tool
33
+ Pentesting support tool. Can autonomously execute network penetration tests or assist with generic Capture The Flag (CTF) challenges (such as Reverse Engineering, Cryptography, and binary analysis) without requiring a specific network target.
34
34
 
35
35
  ---
36
36
 
package/dist/main.js CHANGED
@@ -759,7 +759,7 @@ var INPUT_PROMPT_PATTERNS = [
759
759
 
760
760
  // src/shared/constants/agent.ts
761
761
  var APP_NAME = "Pentest AI";
762
- var APP_VERSION = "0.70.9";
762
+ var APP_VERSION = "0.70.10";
763
763
  var APP_DESCRIPTION = "Autonomous Penetration Testing AI Agent";
764
764
  var LLM_ROLES = {
765
765
  SYSTEM: "system",
@@ -814,7 +814,7 @@ import { render } from "ink";
814
814
  import chalk from "chalk";
815
815
 
816
816
  // src/platform/tui/app.tsx
817
- import { useState as useState6, useCallback as useCallback9, useRef as useRef8 } from "react";
817
+ import { useState as useState7, useCallback as useCallback9, useRef as useRef9 } from "react";
818
818
  import { Box as Box20, useApp, useStdout as useStdout4 } from "ink";
819
819
 
820
820
  // src/platform/tui/hooks/useAgent.ts
@@ -1026,7 +1026,8 @@ var NOISE_CLASSIFICATION = {
1026
1026
  "set_scope",
1027
1027
  "bg_status",
1028
1028
  "bg_cleanup",
1029
- "health_check"
1029
+ "health_check",
1030
+ "ask_user"
1030
1031
  ]
1031
1032
  };
1032
1033
 
@@ -1173,7 +1174,9 @@ var EVENT_TYPES = {
1173
1174
  USAGE_UPDATE: "usage_update",
1174
1175
  INPUT_REQUEST: "input_request",
1175
1176
  FLAG_FOUND: "flag_found",
1176
- NOTIFICATION: "notification"
1177
+ NOTIFICATION: "notification",
1178
+ AUXILIARY_WORK_START: "auxiliary_work_start",
1179
+ AUXILIARY_WORK_END: "auxiliary_work_end"
1177
1180
  };
1178
1181
  var COMMAND_EVENT_TYPES = {
1179
1182
  TOOL_MISSING: "tool_missing",
@@ -3395,11 +3398,25 @@ var SharedState = class {
3395
3398
  * 이 필드로 turn N의 reflection → turn N+1 prompt 레이어(epistemic-check)에 주입된다.
3396
3399
  */
3397
3400
  lastReflection = null;
3401
+ /**
3402
+ * Artifacts for CTF / Non-Network tasks (e.g., .bin files, source code).
3403
+ */
3404
+ artifacts = [];
3405
+ /**
3406
+ * Current objective for flexible task management.
3407
+ */
3408
+ currentObjective = null;
3398
3409
  constructor() {
3399
3410
  this.targetState = new TargetState(this.attackGraph);
3400
3411
  this.findingState = new FindingState();
3401
3412
  this.lootState = new LootState();
3402
3413
  }
3414
+ /**
3415
+ * Checks if the agent has a valid target, artifact, or general objective to work on.
3416
+ */
3417
+ hasActiveEngagement() {
3418
+ return this.targetState.getAll().length > 0 || this.artifacts.length > 0 || this.currentObjective !== null;
3419
+ }
3403
3420
  reset() {
3404
3421
  this.targetState.clear();
3405
3422
  this.findingState.clear();
@@ -3413,6 +3430,8 @@ var SharedState = class {
3413
3430
  this.episodicMemory.clear();
3414
3431
  this.dynamicTechniques.clear();
3415
3432
  this.lastReflection = null;
3433
+ this.artifacts = [];
3434
+ this.currentObjective = null;
3416
3435
  }
3417
3436
  // Delegation to MissionState
3418
3437
  setMissionSummary(summary) {
@@ -5108,6 +5127,7 @@ var StateSerializer = class {
5108
5127
  static toPrompt(state) {
5109
5128
  const lines = [];
5110
5129
  this.formatContextAndMission(state, lines);
5130
+ this.formatArtifactsAndObjectives(state, lines);
5111
5131
  this.formatTargets(state, lines);
5112
5132
  this.formatFindings(state, lines);
5113
5133
  this.formatLoot(state, lines);
@@ -5118,6 +5138,17 @@ var StateSerializer = class {
5118
5138
  lines.push(`Phase: ${state.getPhase()}`);
5119
5139
  return lines.join("\n");
5120
5140
  }
5141
+ static formatArtifactsAndObjectives(state, lines) {
5142
+ if (state.currentObjective) {
5143
+ lines.push(`Current Objective: ${state.currentObjective}`);
5144
+ }
5145
+ if (state.artifacts && state.artifacts.length > 0) {
5146
+ lines.push(`Artifacts (${state.artifacts.length}):`);
5147
+ for (const a of state.artifacts) {
5148
+ lines.push(` [${a.type}] ${a.id}: ${a.description}`);
5149
+ }
5150
+ }
5151
+ }
5121
5152
  static formatContextAndMission(state, lines) {
5122
5153
  const engagement = state.getEngagement();
5123
5154
  const scope = state.getScope();
@@ -14759,6 +14790,9 @@ var MainAgent = class extends CoreAgent {
14759
14790
  }
14760
14791
  async execute(userInput) {
14761
14792
  this.userInput = userInput;
14793
+ if (!this.state.hasActiveEngagement() && userInput.trim().length > 0) {
14794
+ this.state.currentObjective = userInput.trim();
14795
+ }
14762
14796
  emitStart(this.events, userInput, this.state);
14763
14797
  initializeTask(this.state);
14764
14798
  try {
@@ -14790,8 +14824,30 @@ var MainAgent = class extends CoreAgent {
14790
14824
  const turnToolJournal = this.getTurnToolJournal();
14791
14825
  const turnMemo = this.getTurnMemo();
14792
14826
  const turnReflections = [];
14827
+ this.events.emit({
14828
+ type: "auxiliary_work_start",
14829
+ timestamp: Date.now(),
14830
+ data: { type: "context_extraction" }
14831
+ });
14832
+ const extStart = Date.now();
14793
14833
  await processContextExtraction(messages, this.contextExtractor);
14834
+ this.events.emit({
14835
+ type: "auxiliary_work_end",
14836
+ timestamp: Date.now(),
14837
+ data: { type: "context_extraction", durationMs: Date.now() - extStart, success: true }
14838
+ });
14839
+ this.events.emit({
14840
+ type: "auxiliary_work_start",
14841
+ timestamp: Date.now(),
14842
+ data: { type: "reflection" }
14843
+ });
14844
+ const refStart = Date.now();
14794
14845
  const reflection = await processReflection(turnToolJournal, turnMemo, this.state.getPhase(), this.reflector);
14846
+ this.events.emit({
14847
+ type: "auxiliary_work_end",
14848
+ timestamp: Date.now(),
14849
+ data: { type: "reflection", durationMs: Date.now() - refStart, success: !!reflection }
14850
+ });
14795
14851
  if (reflection) {
14796
14852
  turnReflections.push(reflection);
14797
14853
  this.state.lastReflection = reflection;
@@ -15280,6 +15336,13 @@ function createLifecycleHandlers(agent, state, _reasoningBufferRef) {
15280
15336
  const prefix = e.data.level === "error" ? "\u274C" : e.data.level === "warning" ? "\u26A0\uFE0F" : e.data.level === "success" ? "\u2705" : "\u{1F514}";
15281
15337
  addMessage("system", `${prefix} [${e.data.title}] ${e.data.message}`);
15282
15338
  };
15339
+ const onAuxiliaryWorkStart = (e) => {
15340
+ const msg = e.data.type === "context_extraction" ? "Compressing context..." : "Reflecting on actions...";
15341
+ setCurrentStatus(msg);
15342
+ };
15343
+ const onAuxiliaryWorkEnd = () => {
15344
+ setCurrentStatus("");
15345
+ };
15283
15346
  return {
15284
15347
  onComplete,
15285
15348
  onRetry,
@@ -15289,7 +15352,9 @@ function createLifecycleHandlers(agent, state, _reasoningBufferRef) {
15289
15352
  onUsageUpdate,
15290
15353
  onFlagFound,
15291
15354
  onPhaseChange,
15292
- onNotification
15355
+ onNotification,
15356
+ onAuxiliaryWorkStart,
15357
+ onAuxiliaryWorkEnd
15293
15358
  };
15294
15359
  }
15295
15360
 
@@ -15455,6 +15520,8 @@ var useAgentEvents = (agent, eventsRef, state) => {
15455
15520
  events.on(EVENT_TYPES.FLAG_FOUND, lifecycleHandlers.onFlagFound);
15456
15521
  events.on(EVENT_TYPES.PHASE_CHANGE, lifecycleHandlers.onPhaseChange);
15457
15522
  events.on(EVENT_TYPES.NOTIFICATION, lifecycleHandlers.onNotification);
15523
+ events.on(EVENT_TYPES.AUXILIARY_WORK_START, lifecycleHandlers.onAuxiliaryWorkStart);
15524
+ events.on(EVENT_TYPES.AUXILIARY_WORK_END, lifecycleHandlers.onAuxiliaryWorkEnd);
15458
15525
  events.on(EVENT_TYPES.STATE_CHANGE, updateStats);
15459
15526
  events.on(EVENT_TYPES.START, updateStats);
15460
15527
  events.on(EVENT_TYPES.REASONING_START, reasoningHandlers.onStart);
@@ -15526,30 +15593,46 @@ var useAgent = (shouldAutoApprove, target) => {
15526
15593
  useAgentEvents(agent, eventsRef, state);
15527
15594
  const abortedRef = useRef3(false);
15528
15595
  const executeTask = useCallback2(async (task) => {
15529
- abortedRef.current = false;
15530
- setTurnCount((n) => n + 1);
15531
- setIsProcessing(true);
15532
- manageTimer("start");
15533
- setCurrentStatus("Thinking");
15534
- resetCumulativeCounters();
15535
- try {
15536
- const response = await agent.execute(task);
15537
- if (abortedRef.current) return;
15538
- const meta = lastResponseMetaRef.current;
15539
- const suffix = meta ? ` ${formatMeta(meta.durationMs || 0, (meta.tokens?.input || 0) + (meta.tokens?.output || 0))}` : "";
15540
- addMessage("ai", response + suffix);
15541
- } catch (e) {
15542
- if (!abortedRef.current) {
15596
+ let currentTask = task;
15597
+ while (true) {
15598
+ abortedRef.current = false;
15599
+ setTurnCount((n) => n + 1);
15600
+ setIsProcessing(true);
15601
+ manageTimer("start");
15602
+ setCurrentStatus("Thinking");
15603
+ resetCumulativeCounters();
15604
+ try {
15605
+ const response = await agent.execute(currentTask);
15606
+ if (abortedRef.current) {
15607
+ if (agent.hasPendingUserInput()) {
15608
+ currentTask = "";
15609
+ continue;
15610
+ }
15611
+ return;
15612
+ }
15613
+ const meta = lastResponseMetaRef.current;
15614
+ const suffix = meta ? ` ${formatMeta(meta.durationMs || 0, (meta.tokens?.input || 0) + (meta.tokens?.output || 0))}` : "";
15615
+ addMessage("ai", response + suffix);
15616
+ break;
15617
+ } catch (e) {
15618
+ if (abortedRef.current) {
15619
+ if (agent.hasPendingUserInput()) {
15620
+ currentTask = "";
15621
+ continue;
15622
+ }
15623
+ return;
15624
+ }
15543
15625
  addMessage("error", e instanceof Error ? e.message : String(e));
15544
- }
15545
- } finally {
15546
- if (!abortedRef.current) {
15547
- manageTimer("stop");
15548
- setIsProcessing(false);
15549
- setCurrentStatus("");
15626
+ break;
15627
+ } finally {
15628
+ if (!abortedRef.current) {
15629
+ manageTimer("stop");
15630
+ setIsProcessing(false);
15631
+ setCurrentStatus("");
15632
+ }
15550
15633
  }
15551
15634
  }
15552
- }, [agent, addMessage, manageTimer, resetCumulativeCounters, setIsProcessing, lastResponseMetaRef, setCurrentStatus]);
15635
+ }, [agent, addMessage, manageTimer, resetCumulativeCounters, setIsProcessing, lastResponseMetaRef, setCurrentStatus, setTurnCount]);
15553
15636
  const abort = useCallback2(() => {
15554
15637
  abortedRef.current = true;
15555
15638
  agent.abort();
@@ -15603,7 +15686,7 @@ var useAgent = (shouldAutoApprove, target) => {
15603
15686
  };
15604
15687
 
15605
15688
  // src/platform/tui/hooks/commands/index.ts
15606
- import { useCallback as useCallback3 } from "react";
15689
+ import { useCallback as useCallback3, useMemo } from "react";
15607
15690
 
15608
15691
  // src/platform/tui/constants/commands.ts
15609
15692
  var COMMAND_DEFINITIONS = [
@@ -15710,7 +15793,7 @@ var createTargetCommands = (ctx) => ({
15710
15793
  ctx.addMessage("error", "Usage: /target <ip>");
15711
15794
  return;
15712
15795
  }
15713
- if (ctx.agent.getState().getTargets().size > 0) {
15796
+ if (ctx.agent.getState().hasActiveEngagement()) {
15714
15797
  await ctx.agent.resetSession();
15715
15798
  ctx.addMessage("system", "Previous target data cleared. Starting fresh session for the new target.");
15716
15799
  }
@@ -15732,8 +15815,8 @@ var createTargetCommands = (ctx) => ({
15732
15815
  ctx.addMessage("system", `Target \u2192 ${args[0]}`);
15733
15816
  },
15734
15817
  [UI_COMMANDS.START]: async (args) => {
15735
- if (!ctx.agent.getState().getTargets().size) {
15736
- ctx.addMessage("error", "Set target first: /target <ip>");
15818
+ if (!ctx.agent.getState().hasActiveEngagement()) {
15819
+ ctx.addMessage("error", "Set a target first: /target <ip>, or ask me to perform a specific task.");
15737
15820
  return;
15738
15821
  }
15739
15822
  if (!ctx.autoApproveModeRef.current) {
@@ -15747,8 +15830,8 @@ var createTargetCommands = (ctx) => ({
15747
15830
  await ctx.executeTask(args.join(" ") || `Perform comprehensive penetration testing${targetInfo}`);
15748
15831
  },
15749
15832
  [UI_COMMANDS.START_SHORT]: async (args) => {
15750
- if (!ctx.agent.getState().getTargets().size) {
15751
- ctx.addMessage("error", "Set target first: /target <ip>");
15833
+ if (!ctx.agent.getState().hasActiveEngagement()) {
15834
+ ctx.addMessage("error", "Set a target first: /target <ip>, or ask me to perform a specific task.");
15752
15835
  return;
15753
15836
  }
15754
15837
  if (!ctx.autoApproveModeRef.current) {
@@ -16090,7 +16173,7 @@ var createToggleCommands = (ctx) => ({
16090
16173
 
16091
16174
  // src/platform/tui/hooks/commands/index.ts
16092
16175
  var useCommands = (props) => {
16093
- const ctx = {
16176
+ const ctx = useMemo(() => ({
16094
16177
  agent: props.agent,
16095
16178
  addMessage: props.addMessage,
16096
16179
  showModal: props.showModal,
@@ -16101,13 +16184,24 @@ var useCommands = (props) => {
16101
16184
  handleExit: props.handleExit,
16102
16185
  isProcessingRef: props.isProcessingRef,
16103
16186
  autoApproveModeRef: props.autoApproveModeRef
16104
- };
16105
- const handlers = {
16187
+ }), [
16188
+ props.agent,
16189
+ props.addMessage,
16190
+ props.showModal,
16191
+ props.setMessages,
16192
+ props.executeTask,
16193
+ props.refreshStats,
16194
+ props.setAutoApproveMode,
16195
+ props.handleExit,
16196
+ props.isProcessingRef,
16197
+ props.autoApproveModeRef
16198
+ ]);
16199
+ const handlers = useMemo(() => ({
16106
16200
  ...createSessionCommands(ctx),
16107
16201
  ...createTargetCommands(ctx),
16108
16202
  ...createDisplayCommands(ctx),
16109
16203
  ...createToggleCommands(ctx)
16110
- };
16204
+ }), [ctx]);
16111
16205
  const handleCommand = useCallback3(async (cmd, args) => {
16112
16206
  const handler = handlers[cmd];
16113
16207
  if (handler) {
@@ -16860,7 +16954,7 @@ var BrandingHeader = memo7(({ modelName, autoApproveMode, version, showGuide = f
16860
16954
  /* @__PURE__ */ jsxs7(Box8, { gap: 2, children: [
16861
16955
  /* @__PURE__ */ jsx9(Text8, { color: THEME.primary, children: "\u276F" }),
16862
16956
  /* @__PURE__ */ jsx9(Text8, { color: THEME.white, children: "/target <ip>" }),
16863
- /* @__PURE__ */ jsx9(Text8, { color: THEME.dimGray, children: "Set target IP or domain" })
16957
+ /* @__PURE__ */ jsx9(Text8, { color: THEME.dimGray, children: "Set a network target" })
16864
16958
  ] }),
16865
16959
  /* @__PURE__ */ jsxs7(Box8, { gap: 2, children: [
16866
16960
  /* @__PURE__ */ jsx9(Text8, { color: THEME.primary, children: "\u276F" }),
@@ -16872,7 +16966,7 @@ var BrandingHeader = memo7(({ modelName, autoApproveMode, version, showGuide = f
16872
16966
  /* @__PURE__ */ jsx9(Text8, { color: THEME.white, children: "/help" }),
16873
16967
  /* @__PURE__ */ jsx9(Text8, { color: THEME.dimGray, children: " Show all commands" })
16874
16968
  ] }),
16875
- /* @__PURE__ */ jsx9(Box8, { paddingTop: 1, children: /* @__PURE__ */ jsx9(Text8, { color: THEME.dimGray, children: "Or just type a task and press Enter." }) })
16969
+ /* @__PURE__ */ jsx9(Box8, { paddingTop: 1, children: /* @__PURE__ */ jsx9(Text8, { color: THEME.gray, children: 'Or type an objective (e.g. "Solve this python script").' }) })
16876
16970
  ] })
16877
16971
  ] })
16878
16972
  ] });
@@ -17093,8 +17187,8 @@ var StatusDisplay = memo11(({
17093
17187
  });
17094
17188
 
17095
17189
  // src/platform/tui/components/ChatInput.tsx
17096
- import { useMemo, useCallback as useCallback7, useRef as useRef6, memo as memo12, useState as useState5, useEffect as useEffect7 } from "react";
17097
- import { Box as Box16, Text as Text17, useInput as useInput2 } from "ink";
17190
+ import { useMemo as useMemo2, useCallback as useCallback7, useRef as useRef7, memo as memo12, useState as useState6, useEffect as useEffect8 } from "react";
17191
+ import { Box as Box16, Text as Text18, useInput as useInput3 } from "ink";
17098
17192
 
17099
17193
  // src/platform/tui/components/input/AutocompletePreview.tsx
17100
17194
  import { Box as Box13, Text as Text14 } from "ink";
@@ -17123,9 +17217,74 @@ var AutocompletePreview = ({
17123
17217
  };
17124
17218
 
17125
17219
  // src/platform/tui/components/input/SecretInputArea.tsx
17126
- import { Box as Box14, Text as Text15, useStdout } from "ink";
17127
- import TextInput from "ink-text-input";
17220
+ import { Box as Box14, Text as Text16, useStdout } from "ink";
17221
+
17222
+ // src/platform/tui/components/input/SimpleTextInput.tsx
17223
+ import { useState as useState5, useEffect as useEffect7, useRef as useRef6 } from "react";
17224
+ import { Text as Text15, useInput as useInput2 } from "ink";
17128
17225
  import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
17226
+ var SimpleTextInput = ({
17227
+ value,
17228
+ onChange,
17229
+ onSubmit,
17230
+ placeholder,
17231
+ isPassword
17232
+ }) => {
17233
+ const [cursor, setCursor] = useState5(Array.from(value || "").length);
17234
+ const valueRef = useRef6(value || "");
17235
+ valueRef.current = value || "";
17236
+ useEffect7(() => {
17237
+ const len = Array.from(valueRef.current).length;
17238
+ if (cursor > len) setCursor(len);
17239
+ }, [value, cursor]);
17240
+ useInput2((input, key) => {
17241
+ if (key.ctrl && input === "c") return;
17242
+ if (key.upArrow || key.downArrow || key.tab) return;
17243
+ if (key.return) {
17244
+ onSubmit?.(valueRef.current);
17245
+ return;
17246
+ }
17247
+ const chars2 = Array.from(valueRef.current);
17248
+ let c2 = cursor;
17249
+ if (key.leftArrow) {
17250
+ c2 = Math.max(0, c2 - 1);
17251
+ } else if (key.rightArrow) {
17252
+ c2 = Math.min(chars2.length, c2 + 1);
17253
+ } else if (key.backspace || key.delete) {
17254
+ if (c2 > 0) {
17255
+ chars2.splice(c2 - 1, 1);
17256
+ c2--;
17257
+ onChange(chars2.join(""));
17258
+ }
17259
+ } else if (input) {
17260
+ const inputChars = Array.from(input);
17261
+ chars2.splice(c2, 0, ...inputChars);
17262
+ c2 += inputChars.length;
17263
+ onChange(chars2.join(""));
17264
+ }
17265
+ setCursor(c2);
17266
+ }, { isActive: true });
17267
+ const displayValue = isPassword ? "\u2022".repeat(Array.from(valueRef.current).length) : valueRef.current;
17268
+ const chars = Array.from(displayValue);
17269
+ if (chars.length === 0) {
17270
+ return /* @__PURE__ */ jsxs12(Text15, { children: [
17271
+ /* @__PURE__ */ jsx17(Text15, { inverse: true, children: " " }),
17272
+ placeholder ? /* @__PURE__ */ jsx17(Text15, { color: "gray", children: placeholder }) : null
17273
+ ] });
17274
+ }
17275
+ const c = Math.min(cursor, chars.length);
17276
+ const before = chars.slice(0, c).join("");
17277
+ const charAtCursor = c < chars.length ? chars[c] : " ";
17278
+ const after = chars.slice(c + 1).join("");
17279
+ return /* @__PURE__ */ jsxs12(Text15, { children: [
17280
+ before,
17281
+ /* @__PURE__ */ jsx17(Text15, { inverse: true, children: charAtCursor }),
17282
+ after
17283
+ ] });
17284
+ };
17285
+
17286
+ // src/platform/tui/components/input/SecretInputArea.tsx
17287
+ import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
17129
17288
  var OUTER_PADDING = 2;
17130
17289
  var SecretInputArea = ({
17131
17290
  inputRequest,
@@ -17136,29 +17295,28 @@ var SecretInputArea = ({
17136
17295
  const { stdout } = useStdout();
17137
17296
  const borderWidth = Math.max(10, (stdout?.columns ?? 80) - OUTER_PADDING);
17138
17297
  const borderLine = "\u2501".repeat(borderWidth);
17139
- return /* @__PURE__ */ jsxs12(Box14, { flexDirection: "column", children: [
17140
- /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsx17(Text15, { color: THEME.yellow, children: borderLine }) }),
17141
- /* @__PURE__ */ jsxs12(Box14, { paddingX: 1, children: [
17142
- /* @__PURE__ */ jsx17(Text15, { color: THEME.yellow, bold: true, children: "\u25B8 " }),
17143
- /* @__PURE__ */ jsx17(
17144
- TextInput,
17298
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
17299
+ /* @__PURE__ */ jsx18(Box14, { children: /* @__PURE__ */ jsx18(Text16, { color: THEME.yellow, children: borderLine }) }),
17300
+ /* @__PURE__ */ jsxs13(Box14, { paddingX: 1, children: [
17301
+ /* @__PURE__ */ jsx18(Text16, { color: THEME.yellow, bold: true, children: "\u25B8 " }),
17302
+ /* @__PURE__ */ jsx18(
17303
+ SimpleTextInput,
17145
17304
  {
17146
17305
  value: secretInput,
17147
17306
  onChange: setSecretInput,
17148
17307
  onSubmit: onSecretSubmit,
17149
17308
  placeholder: inputRequest.isPassword ? "(hidden \xB7 press Enter)" : "(type and press Enter)",
17150
- mask: inputRequest.isPassword ? "\u2022" : void 0
17309
+ isPassword: inputRequest.isPassword
17151
17310
  }
17152
17311
  )
17153
17312
  ] }),
17154
- /* @__PURE__ */ jsx17(Box14, { children: /* @__PURE__ */ jsx17(Text15, { color: THEME.yellow, children: borderLine }) })
17313
+ /* @__PURE__ */ jsx18(Box14, { children: /* @__PURE__ */ jsx18(Text16, { color: THEME.yellow, children: borderLine }) })
17155
17314
  ] });
17156
17315
  };
17157
17316
 
17158
17317
  // src/platform/tui/components/input/NormalInputArea.tsx
17159
- import { Box as Box15, Text as Text16, useStdout as useStdout2 } from "ink";
17160
- import TextInput2 from "ink-text-input";
17161
- import { jsx as jsx18, jsxs as jsxs13 } from "react/jsx-runtime";
17318
+ import { Box as Box15, Text as Text17, useStdout as useStdout2 } from "ink";
17319
+ import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
17162
17320
  var OUTER_PADDING2 = 2;
17163
17321
  var NormalInputArea = ({
17164
17322
  inputKey,
@@ -17170,12 +17328,12 @@ var NormalInputArea = ({
17170
17328
  const { stdout } = useStdout2();
17171
17329
  const borderWidth = Math.max(10, (stdout?.columns ?? 80) - OUTER_PADDING2);
17172
17330
  const borderLine = "\u2500".repeat(borderWidth);
17173
- return /* @__PURE__ */ jsxs13(Box15, { flexDirection: "column", children: [
17174
- /* @__PURE__ */ jsx18(Box15, { children: /* @__PURE__ */ jsx18(Text16, { dimColor: true, color: THEME.dimGray, children: borderLine }) }),
17175
- /* @__PURE__ */ jsxs13(Box15, { paddingX: 1, children: [
17176
- /* @__PURE__ */ jsx18(Text16, { color: THEME.primary, children: "\u276F " }),
17177
- /* @__PURE__ */ jsx18(
17178
- TextInput2,
17331
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
17332
+ /* @__PURE__ */ jsx19(Box15, { children: /* @__PURE__ */ jsx19(Text17, { dimColor: true, color: THEME.dimGray, children: borderLine }) }),
17333
+ /* @__PURE__ */ jsxs14(Box15, { paddingX: 1, children: [
17334
+ /* @__PURE__ */ jsx19(Text17, { color: THEME.primary, children: "\u276F " }),
17335
+ /* @__PURE__ */ jsx19(
17336
+ SimpleTextInput,
17179
17337
  {
17180
17338
  value,
17181
17339
  onChange,
@@ -17185,12 +17343,12 @@ var NormalInputArea = ({
17185
17343
  inputKey
17186
17344
  )
17187
17345
  ] }),
17188
- /* @__PURE__ */ jsx18(Box15, { children: /* @__PURE__ */ jsx18(Text16, { dimColor: true, color: THEME.dimGray, children: borderLine }) })
17346
+ /* @__PURE__ */ jsx19(Box15, { children: /* @__PURE__ */ jsx19(Text17, { dimColor: true, color: THEME.dimGray, children: borderLine }) })
17189
17347
  ] });
17190
17348
  };
17191
17349
 
17192
17350
  // src/platform/tui/components/ChatInput.tsx
17193
- import { jsx as jsx19, jsxs as jsxs14 } from "react/jsx-runtime";
17351
+ import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
17194
17352
  var MAX_SUGGESTIONS = 6;
17195
17353
  var ChatInput = memo12(({
17196
17354
  value,
@@ -17205,31 +17363,43 @@ var ChatInput = memo12(({
17205
17363
  const isSlashMode = value.startsWith("/");
17206
17364
  const partialCmd = isSlashMode ? value.slice(1).split(" ")[0] : "";
17207
17365
  const hasArgs = isSlashMode && value.includes(" ");
17208
- const suggestions = useMemo(() => {
17366
+ const suggestions = useMemo2(() => {
17209
17367
  if (!isSlashMode || hasArgs) return [];
17210
17368
  return getMatchingCommands(partialCmd).slice(0, MAX_SUGGESTIONS);
17211
17369
  }, [isSlashMode, partialCmd, hasArgs]);
17212
17370
  const showPreview = isSlashMode && !hasArgs && suggestions.length > 0;
17213
- const [selectedIdx, setSelectedIdx] = useState5(0);
17371
+ const [selectedIdx, setSelectedIdx] = useState6(0);
17214
17372
  const clampedIdx = Math.min(selectedIdx, Math.max(0, suggestions.length - 1));
17215
- const selectedIdxRef = useRef6(clampedIdx);
17373
+ const selectedIdxRef = useRef7(clampedIdx);
17216
17374
  selectedIdxRef.current = clampedIdx;
17217
- const suggestionsRef = useRef6(suggestions);
17375
+ const suggestionsRef = useRef7(suggestions);
17218
17376
  suggestionsRef.current = suggestions;
17219
- const isSlashModeRef = useRef6(isSlashMode);
17377
+ const isSlashModeRef = useRef7(isSlashMode);
17220
17378
  isSlashModeRef.current = isSlashMode;
17221
- const hasArgsRef = useRef6(hasArgs);
17379
+ const hasArgsRef = useRef7(hasArgs);
17222
17380
  hasArgsRef.current = hasArgs;
17223
- const showPreviewRef = useRef6(showPreview);
17381
+ const showPreviewRef = useRef7(showPreview);
17224
17382
  showPreviewRef.current = showPreview;
17225
- const inputRequestRef = useRef6(inputRequest);
17383
+ const inputRequestRef = useRef7(inputRequest);
17226
17384
  inputRequestRef.current = inputRequest;
17227
- const onChangeRef = useRef6(onChange);
17385
+ const onChangeRef = useRef7(onChange);
17228
17386
  onChangeRef.current = onChange;
17229
- const [pastedHint, setPastedHint] = useState5(null);
17230
- const prevValueRef = useRef6(value);
17231
- const pasteTimerRef = useRef6(null);
17232
- useEffect7(() => {
17387
+ const latestValueRef = useRef7(value);
17388
+ latestValueRef.current = value;
17389
+ const handleLocalChange = useCallback7((newVal) => {
17390
+ latestValueRef.current = newVal;
17391
+ onChange(newVal);
17392
+ }, [onChange]);
17393
+ const latestSecretRef = useRef7(secretInput);
17394
+ latestSecretRef.current = secretInput;
17395
+ const handleSecretChange = useCallback7((newVal) => {
17396
+ latestSecretRef.current = newVal;
17397
+ setSecretInput(newVal);
17398
+ }, [setSecretInput]);
17399
+ const [pastedHint, setPastedHint] = useState6(null);
17400
+ const prevValueRef = useRef7(value);
17401
+ const pasteTimerRef = useRef7(null);
17402
+ useEffect8(() => {
17233
17403
  const diff = value.length - prevValueRef.current.length;
17234
17404
  if (diff > 20) {
17235
17405
  if (pasteTimerRef.current) clearTimeout(pasteTimerRef.current);
@@ -17241,7 +17411,7 @@ var ChatInput = memo12(({
17241
17411
  if (pasteTimerRef.current) clearTimeout(pasteTimerRef.current);
17242
17412
  };
17243
17413
  }, [value]);
17244
- const [inputKey, setInputKey] = useState5(0);
17414
+ const [inputKey, setInputKey] = useState6(0);
17245
17415
  const completeCommand = useCallback7((idx) => {
17246
17416
  const sug = suggestionsRef.current;
17247
17417
  if (!sug.length) return;
@@ -17252,13 +17422,14 @@ var ChatInput = memo12(({
17252
17422
  setSelectedIdx(0);
17253
17423
  setInputKey((k) => k + 1);
17254
17424
  }, []);
17255
- const onSubmitRef = useRef6(onSubmit);
17425
+ const onSubmitRef = useRef7(onSubmit);
17256
17426
  onSubmitRef.current = onSubmit;
17257
- const wrappedOnSubmit = useCallback7((val) => {
17427
+ const wrappedOnSubmit = useCallback7((_staleVal) => {
17428
+ const finalValue = latestValueRef.current;
17258
17429
  if (showPreviewRef.current) {
17259
17430
  const sug = suggestionsRef.current;
17260
17431
  if (!sug.length) {
17261
- onSubmitRef.current(val);
17432
+ onSubmitRef.current(finalValue);
17262
17433
  return;
17263
17434
  }
17264
17435
  const best = sug[Math.min(selectedIdxRef.current, sug.length - 1)];
@@ -17271,9 +17442,14 @@ var ChatInput = memo12(({
17271
17442
  }
17272
17443
  return;
17273
17444
  }
17274
- onSubmitRef.current(val);
17445
+ onSubmitRef.current(finalValue);
17275
17446
  }, [completeCommand]);
17276
- useInput2(useCallback7((_input, key) => {
17447
+ const onSecretSubmitRef = useRef7(onSecretSubmit);
17448
+ onSecretSubmitRef.current = onSecretSubmit;
17449
+ const wrappedSecretSubmit = useCallback7((_staleVal) => {
17450
+ onSecretSubmitRef.current(latestSecretRef.current);
17451
+ }, []);
17452
+ useInput3(useCallback7((_input, key) => {
17277
17453
  if (inputRequestRef.current.status === "active") return;
17278
17454
  const sug = suggestionsRef.current;
17279
17455
  const visible = showPreviewRef.current;
@@ -17289,8 +17465,8 @@ var ChatInput = memo12(({
17289
17465
  completeCommand(selectedIdxRef.current);
17290
17466
  }
17291
17467
  }, [completeCommand]));
17292
- return /* @__PURE__ */ jsxs14(Box16, { flexDirection: "column", children: [
17293
- showPreview && /* @__PURE__ */ jsx19(
17468
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
17469
+ showPreview && /* @__PURE__ */ jsx20(
17294
17470
  AutocompletePreview,
17295
17471
  {
17296
17472
  suggestions,
@@ -17299,36 +17475,36 @@ var ChatInput = memo12(({
17299
17475
  ),
17300
17476
  inputRequest.status === "active" ? (
17301
17477
  /* Active input request — yellow top/bottom border */
17302
- /* @__PURE__ */ jsx19(
17478
+ /* @__PURE__ */ jsx20(
17303
17479
  SecretInputArea,
17304
17480
  {
17305
17481
  inputRequest,
17306
17482
  secretInput,
17307
- setSecretInput,
17308
- onSecretSubmit
17483
+ setSecretInput: handleSecretChange,
17484
+ onSecretSubmit: wrappedSecretSubmit
17309
17485
  }
17310
17486
  )
17311
17487
  ) : (
17312
17488
  /* Normal input — dim top/bottom border */
17313
- /* @__PURE__ */ jsx19(
17489
+ /* @__PURE__ */ jsx20(
17314
17490
  NormalInputArea,
17315
17491
  {
17316
17492
  inputKey,
17317
17493
  value,
17318
- onChange,
17494
+ onChange: handleLocalChange,
17319
17495
  onSubmit: wrappedOnSubmit,
17320
17496
  placeholder
17321
17497
  }
17322
17498
  )
17323
17499
  ),
17324
- pastedHint && /* @__PURE__ */ jsx19(Box16, { paddingX: 2, children: /* @__PURE__ */ jsx19(Text17, { dimColor: true, color: THEME.dimGray, children: pastedHint }) })
17500
+ pastedHint && /* @__PURE__ */ jsx20(Box16, { paddingX: 2, children: /* @__PURE__ */ jsx20(Text18, { dimColor: true, color: THEME.dimGray, children: pastedHint }) })
17325
17501
  ] });
17326
17502
  });
17327
17503
 
17328
17504
  // src/platform/tui/components/footer.tsx
17329
17505
  import { memo as memo13 } from "react";
17330
- import { Box as Box17, Text as Text18 } from "ink";
17331
- import { jsx as jsx20, jsxs as jsxs15 } from "react/jsx-runtime";
17506
+ import { Box as Box17, Text as Text19 } from "ink";
17507
+ import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
17332
17508
  var CTX_WARN_THRESHOLD = 0.8;
17333
17509
  var MAX_CONTEXT_TOKENS = LLM_LIMITS.streamMaxTokens;
17334
17510
  var formatElapsed = (totalSeconds) => {
@@ -17344,7 +17520,7 @@ var formatElapsed = (totalSeconds) => {
17344
17520
  var Footer = memo13(({ phase, targets, findings, todo, elapsedTime, isProcessing, totalTokens, turnCount }) => {
17345
17521
  const ctxPct = totalTokens > 0 ? Math.round(totalTokens / MAX_CONTEXT_TOKENS * 100) : 0;
17346
17522
  const ctxColor = ctxPct >= CTX_WARN_THRESHOLD * 100 ? THEME.yellow : THEME.dimGray;
17347
- return /* @__PURE__ */ jsxs15(
17523
+ return /* @__PURE__ */ jsxs16(
17348
17524
  Box17,
17349
17525
  {
17350
17526
  width: "100%",
@@ -17352,44 +17528,44 @@ var Footer = memo13(({ phase, targets, findings, todo, elapsedTime, isProcessing
17352
17528
  justifyContent: "space-between",
17353
17529
  overflow: "hidden",
17354
17530
  children: [
17355
- /* @__PURE__ */ jsxs15(Box17, { gap: 2, children: [
17356
- /* @__PURE__ */ jsxs15(Text18, { color: THEME.gray, children: [
17531
+ /* @__PURE__ */ jsxs16(Box17, { gap: 2, children: [
17532
+ /* @__PURE__ */ jsxs16(Text19, { color: THEME.gray, children: [
17357
17533
  "Phase: ",
17358
- /* @__PURE__ */ jsx20(Text18, { color: THEME.white, children: phase })
17534
+ /* @__PURE__ */ jsx21(Text19, { color: THEME.white, children: phase })
17359
17535
  ] }),
17360
- /* @__PURE__ */ jsxs15(Text18, { color: THEME.gray, children: [
17536
+ /* @__PURE__ */ jsxs16(Text19, { color: THEME.gray, children: [
17361
17537
  "Targets: ",
17362
- /* @__PURE__ */ jsx20(Text18, { color: THEME.white, children: targets })
17538
+ /* @__PURE__ */ jsx21(Text19, { color: THEME.white, children: targets })
17363
17539
  ] }),
17364
- /* @__PURE__ */ jsxs15(Text18, { color: THEME.gray, children: [
17540
+ /* @__PURE__ */ jsxs16(Text19, { color: THEME.gray, children: [
17365
17541
  "Findings: ",
17366
- /* @__PURE__ */ jsx20(Text18, { color: THEME.white, children: findings })
17542
+ /* @__PURE__ */ jsx21(Text19, { color: THEME.white, children: findings })
17367
17543
  ] }),
17368
- /* @__PURE__ */ jsxs15(Text18, { color: THEME.gray, children: [
17544
+ /* @__PURE__ */ jsxs16(Text19, { color: THEME.gray, children: [
17369
17545
  "Tasks: ",
17370
- /* @__PURE__ */ jsx20(Text18, { color: THEME.white, children: todo })
17546
+ /* @__PURE__ */ jsx21(Text19, { color: THEME.white, children: todo })
17371
17547
  ] })
17372
17548
  ] }),
17373
- /* @__PURE__ */ jsxs15(Box17, { gap: 2, children: [
17374
- isProcessing ? /* @__PURE__ */ jsxs15(Box17, { gap: 1, children: [
17375
- /* @__PURE__ */ jsx20(Text18, { dimColor: true, color: THEME.dimGray, children: "[ESC] abort" }),
17376
- /* @__PURE__ */ jsx20(Text18, { dimColor: true, color: THEME.dimGray, children: "[^C\xD72] exit" })
17377
- ] }) : /* @__PURE__ */ jsx20(Text18, { dimColor: true, color: THEME.dimGray, children: "[/help] commands" }),
17378
- turnCount > 0 && /* @__PURE__ */ jsxs15(Text18, { dimColor: true, color: THEME.dimGray, children: [
17549
+ /* @__PURE__ */ jsxs16(Box17, { gap: 2, children: [
17550
+ isProcessing ? /* @__PURE__ */ jsxs16(Box17, { gap: 1, children: [
17551
+ /* @__PURE__ */ jsx21(Text19, { dimColor: true, color: THEME.dimGray, children: "[ESC] abort" }),
17552
+ /* @__PURE__ */ jsx21(Text19, { dimColor: true, color: THEME.dimGray, children: "[^C\xD72] exit" })
17553
+ ] }) : /* @__PURE__ */ jsx21(Text19, { dimColor: true, color: THEME.dimGray, children: "[/help] commands" }),
17554
+ turnCount > 0 && /* @__PURE__ */ jsxs16(Text19, { dimColor: true, color: THEME.dimGray, children: [
17379
17555
  "turn:",
17380
17556
  turnCount
17381
17557
  ] }),
17382
- totalTokens > 0 && /* @__PURE__ */ jsxs15(Text18, { dimColor: true, color: ctxColor, children: [
17558
+ totalTokens > 0 && /* @__PURE__ */ jsxs16(Text19, { dimColor: true, color: ctxColor, children: [
17383
17559
  "ctx:",
17384
17560
  ctxPct,
17385
17561
  "%"
17386
17562
  ] }),
17387
- totalTokens > 0 && /* @__PURE__ */ jsxs15(Text18, { dimColor: true, color: THEME.dimGray, children: [
17563
+ totalTokens > 0 && /* @__PURE__ */ jsxs16(Text19, { dimColor: true, color: THEME.dimGray, children: [
17388
17564
  "\u2191",
17389
17565
  formatTokens(totalTokens)
17390
17566
  ] }),
17391
- /* @__PURE__ */ jsx20(Text18, { color: isProcessing ? THEME.primary : THEME.gray, children: isProcessing ? "Running " : "Idle " }),
17392
- /* @__PURE__ */ jsx20(Text18, { color: THEME.white, children: formatElapsed(elapsedTime) })
17567
+ /* @__PURE__ */ jsx21(Text19, { color: isProcessing ? THEME.primary : THEME.gray, children: isProcessing ? "Running " : "Idle " }),
17568
+ /* @__PURE__ */ jsx21(Text19, { color: THEME.white, children: formatElapsed(elapsedTime) })
17393
17569
  ] })
17394
17570
  ]
17395
17571
  }
@@ -17398,9 +17574,9 @@ var Footer = memo13(({ phase, targets, findings, todo, elapsedTime, isProcessing
17398
17574
  var footer_default = Footer;
17399
17575
 
17400
17576
  // src/platform/tui/components/Modal.tsx
17401
- import { useMemo as useMemo2, memo as memo14, useCallback as useCallback8, useRef as useRef7 } from "react";
17402
- import { Box as Box18, Text as Text19, useStdout as useStdout3, useInput as useInput3 } from "ink";
17403
- import { jsx as jsx21, jsxs as jsxs16 } from "react/jsx-runtime";
17577
+ import { useMemo as useMemo3, memo as memo14, useCallback as useCallback8, useRef as useRef8 } from "react";
17578
+ import { Box as Box18, Text as Text20, useStdout as useStdout3, useInput as useInput4 } from "ink";
17579
+ import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
17404
17580
  var MODAL_TITLES = {
17405
17581
  findings: "\u25C8 FINDINGS \u25C8",
17406
17582
  graph: "\u25C8 ATTACK GRAPH \u25C8",
@@ -17413,21 +17589,21 @@ var Modal = memo14(({
17413
17589
  onScroll,
17414
17590
  onClose
17415
17591
  }) => {
17416
- const onScrollRef = useRef7(onScroll);
17592
+ const onScrollRef = useRef8(onScroll);
17417
17593
  onScrollRef.current = onScroll;
17418
- const onCloseRef = useRef7(onClose);
17594
+ const onCloseRef = useRef8(onClose);
17419
17595
  onCloseRef.current = onClose;
17420
17596
  const { stdout } = useStdout3();
17421
17597
  const terminalHeight = stdout?.rows ?? 24;
17422
17598
  const terminalWidth = (stdout?.columns ?? 80) - 4;
17423
17599
  const maxHeight = Math.max(1, terminalHeight - 6);
17424
- const lines = useMemo2(() => content.split("\n"), [content]);
17600
+ const lines = useMemo3(() => content.split("\n"), [content]);
17425
17601
  const totalLines = lines.length;
17426
- const visibleLines = useMemo2(
17602
+ const visibleLines = useMemo3(
17427
17603
  () => lines.slice(scrollOffset, scrollOffset + maxHeight),
17428
17604
  [lines, scrollOffset, maxHeight]
17429
17605
  );
17430
- useInput3(useCallback8((input, key) => {
17606
+ useInput4(useCallback8((input, key) => {
17431
17607
  if (key.escape || input === "q") {
17432
17608
  onCloseRef.current();
17433
17609
  } else if (key.upArrow || input === "k") {
@@ -17444,19 +17620,19 @@ var Modal = memo14(({
17444
17620
  const endLine = Math.min(scrollOffset + maxHeight, totalLines);
17445
17621
  const scrollbarHeight = Math.max(1, Math.floor(maxHeight * (maxHeight / Math.max(totalLines, 1))));
17446
17622
  const scrollbarPosition = totalLines > maxHeight ? Math.floor(scrollOffset / (totalLines - maxHeight) * (maxHeight - scrollbarHeight)) : 0;
17447
- return /* @__PURE__ */ jsxs16(
17623
+ return /* @__PURE__ */ jsxs17(
17448
17624
  Box18,
17449
17625
  {
17450
17626
  flexDirection: "column",
17451
17627
  width: terminalWidth,
17452
17628
  height: terminalHeight,
17453
17629
  children: [
17454
- /* @__PURE__ */ jsx21(Box18, { justifyContent: "center", marginBottom: 0, children: /* @__PURE__ */ jsx21(Text19, { color: THEME.cyan, bold: true, children: (() => {
17630
+ /* @__PURE__ */ jsx22(Box18, { justifyContent: "center", marginBottom: 0, children: /* @__PURE__ */ jsx22(Text20, { color: THEME.cyan, bold: true, children: (() => {
17455
17631
  const title = MODAL_TITLES[type];
17456
17632
  const sideWidth = Math.max(3, Math.floor((terminalWidth - title.length - 2) / 2));
17457
17633
  return `${"\u2500".repeat(sideWidth)} ${title} ${"\u2500".repeat(sideWidth)}`;
17458
17634
  })() }) }),
17459
- /* @__PURE__ */ jsx21(
17635
+ /* @__PURE__ */ jsx22(
17460
17636
  Box18,
17461
17637
  {
17462
17638
  flexDirection: "column",
@@ -17466,17 +17642,17 @@ var Modal = memo14(({
17466
17642
  flexGrow: 1,
17467
17643
  children: visibleLines.map((line, i) => {
17468
17644
  const showScrollbar = totalLines > maxHeight && i >= scrollbarPosition && i < scrollbarPosition + scrollbarHeight;
17469
- return /* @__PURE__ */ jsxs16(Box18, { children: [
17470
- /* @__PURE__ */ jsx21(Text19, { color: THEME.white, wrap: "truncate", children: line }),
17471
- /* @__PURE__ */ jsx21(Box18, { flexGrow: 1 }),
17472
- totalLines > maxHeight && /* @__PURE__ */ jsx21(Text19, { color: showScrollbar ? THEME.cyan : THEME.dimGray, children: showScrollbar ? "\u2588" : "\u2502" })
17645
+ return /* @__PURE__ */ jsxs17(Box18, { children: [
17646
+ /* @__PURE__ */ jsx22(Text20, { color: THEME.white, wrap: "truncate", children: line }),
17647
+ /* @__PURE__ */ jsx22(Box18, { flexGrow: 1 }),
17648
+ totalLines > maxHeight && /* @__PURE__ */ jsx22(Text20, { color: showScrollbar ? THEME.cyan : THEME.dimGray, children: showScrollbar ? "\u2588" : "\u2502" })
17473
17649
  ] }, i);
17474
17650
  })
17475
17651
  }
17476
17652
  ),
17477
- /* @__PURE__ */ jsxs16(Box18, { justifyContent: "space-between", paddingX: 1, children: [
17478
- /* @__PURE__ */ jsx21(Text19, { dimColor: true, color: THEME.gray, children: "\u2191\u2193/jk: scroll | g/G: top/bottom | ESC/q: close" }),
17479
- /* @__PURE__ */ jsxs16(Text19, { dimColor: true, color: THEME.cyan, children: [
17653
+ /* @__PURE__ */ jsxs17(Box18, { justifyContent: "space-between", paddingX: 1, children: [
17654
+ /* @__PURE__ */ jsx22(Text20, { dimColor: true, color: THEME.gray, children: "\u2191\u2193/jk: scroll | g/G: top/bottom | ESC/q: close" }),
17655
+ /* @__PURE__ */ jsxs17(Text20, { dimColor: true, color: THEME.cyan, children: [
17480
17656
  startLine,
17481
17657
  "-",
17482
17658
  endLine,
@@ -17491,7 +17667,7 @@ var Modal = memo14(({
17491
17667
 
17492
17668
  // src/platform/tui/components/app/bottom-region.tsx
17493
17669
  import { Box as Box19 } from "ink";
17494
- import { jsx as jsx22, jsxs as jsxs17 } from "react/jsx-runtime";
17670
+ import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
17495
17671
  var BottomRegion = ({
17496
17672
  input,
17497
17673
  setInput,
@@ -17515,8 +17691,8 @@ var BottomRegion = ({
17515
17691
  const suggestionCount = isSlashMode && !hasArgs && inputRequest.status !== "active" ? Math.min(getMatchingCommands(partialCmd).length, MAX_SUGGESTIONS) : 0;
17516
17692
  const previewHeight = suggestionCount;
17517
17693
  const bottomMinHeight = 7;
17518
- return /* @__PURE__ */ jsxs17(Box19, { flexDirection: "column", minHeight: bottomMinHeight, children: [
17519
- /* @__PURE__ */ jsx22(
17694
+ return /* @__PURE__ */ jsxs18(Box19, { flexDirection: "column", minHeight: bottomMinHeight, children: [
17695
+ /* @__PURE__ */ jsx23(
17520
17696
  StatusDisplay,
17521
17697
  {
17522
17698
  retryState,
@@ -17526,7 +17702,7 @@ var BottomRegion = ({
17526
17702
  inputRequest
17527
17703
  }
17528
17704
  ),
17529
- /* @__PURE__ */ jsx22(
17705
+ /* @__PURE__ */ jsx23(
17530
17706
  ChatInput,
17531
17707
  {
17532
17708
  value: input,
@@ -17539,7 +17715,7 @@ var BottomRegion = ({
17539
17715
  onSecretSubmit: handleSecretSubmit
17540
17716
  }
17541
17717
  ),
17542
- /* @__PURE__ */ jsx22(Box19, { marginTop: 1, children: /* @__PURE__ */ jsx22(
17718
+ /* @__PURE__ */ jsx23(Box19, { marginTop: 1, children: /* @__PURE__ */ jsx23(
17543
17719
  footer_default,
17544
17720
  {
17545
17721
  phase: stats.phase,
@@ -17556,16 +17732,16 @@ var BottomRegion = ({
17556
17732
  };
17557
17733
 
17558
17734
  // src/platform/tui/app.tsx
17559
- import { jsx as jsx23, jsxs as jsxs18 } from "react/jsx-runtime";
17735
+ import { jsx as jsx24, jsxs as jsxs19 } from "react/jsx-runtime";
17560
17736
  var MODEL_NAME = getModel() || DEFAULT_MODEL;
17561
17737
  var App = ({ autoApprove = false, target }) => {
17562
17738
  const { exit } = useApp();
17563
17739
  const { stdout } = useStdout4();
17564
17740
  const terminalWidth = stdout?.columns ?? 80;
17565
- const [input, setInput] = useState6("");
17566
- const [secretInput, setSecretInput] = useState6("");
17567
- const [autoApproveMode, setAutoApproveMode] = useState6(autoApprove);
17568
- const [modal, setModal] = useState6({ type: null, content: "", scrollOffset: 0 });
17741
+ const [input, setInput] = useState7("");
17742
+ const [secretInput, setSecretInput] = useState7("");
17743
+ const [autoApproveMode, setAutoApproveMode] = useState7(autoApprove);
17744
+ const [modal, setModal] = useState7({ type: null, content: "", scrollOffset: 0 });
17569
17745
  const {
17570
17746
  agent,
17571
17747
  messages,
@@ -17587,15 +17763,15 @@ var App = ({ autoApprove = false, target }) => {
17587
17763
  liveReasoning,
17588
17764
  isReasoning
17589
17765
  } = useAgent(autoApproveMode, target);
17590
- const isProcessingRef = useRef8(isProcessing);
17766
+ const isProcessingRef = useRef9(isProcessing);
17591
17767
  isProcessingRef.current = isProcessing;
17592
- const autoApproveModeRef = useRef8(autoApproveMode);
17768
+ const autoApproveModeRef = useRef9(autoApproveMode);
17593
17769
  autoApproveModeRef.current = autoApproveMode;
17594
- const inputRequestRef = useRef8(inputRequest);
17770
+ const inputRequestRef = useRef9(inputRequest);
17595
17771
  inputRequestRef.current = inputRequest;
17596
- const isModalOpenRef = useRef8(!!modal.type);
17772
+ const isModalOpenRef = useRef9(!!modal.type);
17597
17773
  isModalOpenRef.current = !!modal.type;
17598
- const inputRef = useRef8(input);
17774
+ const inputRef = useRef9(input);
17599
17775
  inputRef.current = input;
17600
17776
  const clearInput = useCallback9(() => {
17601
17777
  setInput("");
@@ -17638,6 +17814,15 @@ var App = ({ autoApprove = false, target }) => {
17638
17814
  isProcessingRef,
17639
17815
  autoApproveModeRef
17640
17816
  });
17817
+ const handleSecretSubmit = useCallback9((value) => {
17818
+ const ir = inputRequestRef.current;
17819
+ if (ir.status !== "active") return;
17820
+ const displayText = ir.isPassword ? "\u2022".repeat(Math.min(value.length, TUI_DISPLAY_LIMITS.PASSWORD_MASK_MAX)) : value;
17821
+ addMessage("system", `${UI_MESSAGES.SECRET_PREFIX}${displayText}`);
17822
+ ir.resolve(value);
17823
+ setInputRequest({ status: "inactive" });
17824
+ setSecretInput("");
17825
+ }, [addMessage, setInputRequest]);
17641
17826
  const handleSubmit = useCallback9(async (value) => {
17642
17827
  const trimmed = value.trim();
17643
17828
  if (!trimmed) return;
@@ -17646,22 +17831,15 @@ var App = ({ autoApprove = false, target }) => {
17646
17831
  if (trimmed.startsWith("/")) {
17647
17832
  const [cmd, ...args] = trimmed.slice(1).split(" ");
17648
17833
  await handleCommand(cmd, args);
17834
+ } else if (inputRequestRef.current.status === "active") {
17835
+ handleSecretSubmit(trimmed);
17649
17836
  } else if (isProcessingRef.current) {
17650
17837
  agent.enqueueUserInput(trimmed);
17651
- addMessage("system", UI_MESSAGES.QUEUED);
17838
+ agent.abort();
17652
17839
  } else {
17653
17840
  await executeTask(trimmed);
17654
17841
  }
17655
- }, [agent, addMessage, executeTask, handleCommand]);
17656
- const handleSecretSubmit = useCallback9((value) => {
17657
- const ir = inputRequestRef.current;
17658
- if (ir.status !== "active") return;
17659
- const displayText = ir.isPassword ? "\u2022".repeat(Math.min(value.length, TUI_DISPLAY_LIMITS.PASSWORD_MASK_MAX)) : value;
17660
- addMessage("system", `${UI_MESSAGES.SECRET_PREFIX}${displayText}`);
17661
- ir.resolve(value);
17662
- setInputRequest({ status: "inactive" });
17663
- setSecretInput("");
17664
- }, [addMessage, setInputRequest]);
17842
+ }, [agent, addMessage, executeTask, handleCommand, handleSecretSubmit]);
17665
17843
  useKeyboardShortcuts({
17666
17844
  addMessage,
17667
17845
  handleExit,
@@ -17674,7 +17852,7 @@ var App = ({ autoApprove = false, target }) => {
17674
17852
  clearInput
17675
17853
  });
17676
17854
  if (modal.type) {
17677
- return /* @__PURE__ */ jsx23(Box20, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: /* @__PURE__ */ jsx23(
17855
+ return /* @__PURE__ */ jsx24(Box20, { flexDirection: "column", paddingX: 1, width: terminalWidth, children: /* @__PURE__ */ jsx24(
17678
17856
  Modal,
17679
17857
  {
17680
17858
  type: modal.type,
@@ -17685,8 +17863,8 @@ var App = ({ autoApprove = false, target }) => {
17685
17863
  }
17686
17864
  ) });
17687
17865
  }
17688
- return /* @__PURE__ */ jsxs18(Box20, { flexDirection: "column", width: terminalWidth, children: [
17689
- /* @__PURE__ */ jsx23(Box20, { flexDirection: "column", children: /* @__PURE__ */ jsx23(
17866
+ return /* @__PURE__ */ jsxs19(Box20, { flexDirection: "column", width: terminalWidth, children: [
17867
+ /* @__PURE__ */ jsx24(Box20, { flexDirection: "column", children: /* @__PURE__ */ jsx24(
17690
17868
  MessageList,
17691
17869
  {
17692
17870
  messages,
@@ -17695,14 +17873,14 @@ var App = ({ autoApprove = false, target }) => {
17695
17873
  version: APP_VERSION
17696
17874
  }
17697
17875
  ) }),
17698
- /* @__PURE__ */ jsx23(
17876
+ /* @__PURE__ */ jsx24(
17699
17877
  LiveReasoningPanel,
17700
17878
  {
17701
17879
  content: liveReasoning,
17702
17880
  isReasoning
17703
17881
  }
17704
17882
  ),
17705
- /* @__PURE__ */ jsx23(
17883
+ /* @__PURE__ */ jsx24(AnimationProvider, { children: /* @__PURE__ */ jsx24(
17706
17884
  BottomRegion,
17707
17885
  {
17708
17886
  input,
@@ -17721,13 +17899,13 @@ var App = ({ autoApprove = false, target }) => {
17721
17899
  totalTokens: currentTokens,
17722
17900
  turnCount
17723
17901
  }
17724
- )
17902
+ ) })
17725
17903
  ] });
17726
17904
  };
17727
17905
  var app_default = App;
17728
17906
 
17729
17907
  // src/platform/tui/cli/commands/interactive.tsx
17730
- import { jsx as jsx24 } from "react/jsx-runtime";
17908
+ import { jsx as jsx25 } from "react/jsx-runtime";
17731
17909
  async function interactiveAction(options) {
17732
17910
  const { dangerouslySkipPermissions: skipPermissions = false, target } = options;
17733
17911
  console.clear();
@@ -17736,13 +17914,13 @@ async function interactiveAction(options) {
17736
17914
  console.log(chalk.hex(HEX.red)("[!] All tool executions will be auto-approved!\n"));
17737
17915
  }
17738
17916
  const { waitUntilExit } = render(
17739
- /* @__PURE__ */ jsx24(AnimationProvider, { children: /* @__PURE__ */ jsx24(
17917
+ /* @__PURE__ */ jsx25(
17740
17918
  app_default,
17741
17919
  {
17742
17920
  autoApprove: skipPermissions,
17743
17921
  target
17744
17922
  }
17745
- ) })
17923
+ )
17746
17924
  );
17747
17925
  await waitUntilExit();
17748
17926
  }
@@ -8,10 +8,11 @@ You have direct access to all tools. **If a tool or PoC doesn't exist, build it
8
8
 
9
9
  **On the first turn, classify intent BEFORE any action:**
10
10
 
11
- 1. **Greeting/Small Talk** → `ask_user` to greet and ask for target. No other tools.
12
- 2. **Question/Help** → Answer via `ask_user`. No attack tools.
13
- 3. **Unclear input** → `ask_user` to clarify. Do not assume it's a target.
14
- 4. **Pentesting request** (IP/domain/CTF) Execute reconnaissance immediately.
11
+ 1. **Network Pentest** (IP/domain) Execute reconnaissance immediately.
12
+ 2. **Artifact / CTF Task** (file, code snippet, math problem, reversing/crypto task) Treat the provided input as the Engagement Objective. Start local static analysis, write solver scripts, or use tools immediately. **Do NOT ask for a target IP.**
13
+ 3. **Greeting/Small Talk** → `ask_user` to greet and ask for the objective. No other tools.
14
+ 4. **Question/Help** → Answer via `ask_user`.
15
+ 5. **Unclear input** → `ask_user` to clarify. Do not assume it's a network target.
15
16
 
16
17
  ## Subsequent Turns: Every Turn Must Produce Tool Calls
17
18
 
@@ -134,14 +135,15 @@ Self-check every turn: Did I find a vuln but not call `add_finding`? Call it now
134
135
 
135
136
  ### 2.5. Phase Transition Signals — When to Call `update_phase`
136
137
  ```
137
- RECON → vuln_analysis: 1+ service identified (version optional) — ATTACK IMMEDIATELY
138
- vuln_analysis exploit: 1+ finding (confidence 50) with exploit path identified
139
- OR brute-force/credential testing in progress
138
+ RECON → vuln_analysis: [Network] 1+ service identified — ATTACK IMMEDIATELY
139
+ [Artifact] File type identified, strings/static analysis complete
140
+ vuln_analysis → exploit: [Network] Exploit path identified OR brute-force ready
141
+ [Artifact] Logic understood (e.g. crypto flaw, reverse engineering logic mapped) — ready to write solver
140
142
  exploit → post_exploitation: Shell obtained AND promoted (active_shell process active)
141
143
  post_exploitation → lateral: root/SYSTEM achieved on current host
142
- ANY_PHASE → report: All targets compromised OR time is up
144
+ ANY_PHASE → report: All targets compromised, flag obtained, OR time is up
143
145
  ```
144
- **ATTACK OVER RECON: Transition to vuln_analysis as soon as ANY service is found.**
146
+ **ATTACK OVER RECON: Transition to vuln_analysis as soon as ANY attack surface or file property is found.**
145
147
  **NEVER transition away from a phase while HIGH-priority vectors remain untested.**
146
148
 
147
149
  ### 3. ask_user Rules
@@ -26,11 +26,20 @@ Your thought process must be visible. Before each tool call: OBSERVE what change
26
26
 
27
27
  ## Kill Chain Position — Know Where You Are
28
28
 
29
+ Determine your engagement type and track your position:
30
+
31
+ **[Network Pentest Chain]**
29
32
  ```
30
33
  External Recon → Service Discovery → Vuln ID → Initial Access → Shell Stabilization
31
34
  → Situational Awareness → Privilege Escalation → Credential Harvest → Lateral Movement → Objective
32
35
  ```
33
36
 
37
+ **[Artifact / CTF Chain (Rev, Crypto, Forensics)]**
38
+ ```
39
+ File/Input ID (file, strings) → Static Analysis (Code Review, Decompilation) → Logic Mapping
40
+ → Dynamic Analysis (Debugger, Interaction) → Exploit/Solver Script Generation → Flag Capture
41
+ ```
42
+
34
43
  Know your position before every turn. Act accordingly.
35
44
 
36
45
  ## After First Shell — See base.md "Shell Lifecycle" + post.md pipeline
@@ -62,11 +62,28 @@ STAGE 5 — FORK PLAN: If STAGE 4 fails, which PATH becomes Priority 2? Decla
62
62
  ├─ Initial access granted but no obvious privesc → hidden connector exists
63
63
  ├─ AD environment → lateral chain required before final objective
64
64
  ├─ Multiple hops needed (pivot → internal host → target)
65
- └─ Standard tools all return clean/negative (custom path required)
65
+ ├─ Standard tools all return clean/negative (custom path required)
66
+ └─ Complex Cryptography/Reverse Engineering logic is encountered (requires solver script)
66
67
  ```
67
68
 
68
69
  After 3 consecutive failures on the current path → **re-derive STAGE 3 entirely** with new hypotheses.
69
70
 
71
+ ## MISSION FLEXIBILITY & INTENT ADAPTATION
72
+
73
+ You must be hypersensitive to changes in user intent. If new user input appears in the snapshot, analyze it immediately.
74
+
75
+ ### 1. MISSION ABANDONMENT / PIVOT
76
+ If the user explicitly changes the topic (e.g., "Stop hacking, help me with development", "Explain this code", "Let's just chat"):
77
+ ├─ IMMEDIATE PIVOT: Abandon current pentesting priorities.
78
+ ├─ RE-CLASSIFY: Transition to CONVERSATION or DEVELOPMENT mode.
79
+ └─ DO NOT: Do not demand a pentesting target if the user wants to do something else.
80
+
81
+ ### 2. INTERACTIVE INTERVENTION
82
+ If the user provides feedback during an active attack (e.g., "Try this payload instead", "Don't scan that port"):
83
+ ├─ SUPERCEDE: User instructions supercede your previous tactical plan.
84
+ ├─ ACKNOWLEDGE: Incorporate the user's specific hint into PRIORITY 1.
85
+ └─ ADAPT: Explain how the user's input changes the current attack chain.
86
+
70
87
  ---
71
88
 
72
89
  ## STRATEGIC REASONING FRAMEWORK
@@ -324,13 +341,15 @@ ORDER update_phase when these conditions are met:
324
341
  recon → vuln_analysis:
325
342
  ├─ 1+ service identified (version optional) — ATTACK IMMEDIATELY, refine during exploitation
326
343
  ├─ OSINT complete (shodan/github/crt.sh checked)
327
- └─ Web surface mapped (get_web_attack_surface called if HTTP found)
344
+ ├─ Web surface mapped (get_web_attack_surface called if HTTP found)
345
+ └─ [Artifact] File type identified, strings/static analysis complete
328
346
 
329
347
  vuln_analysis → exploit:
330
348
  ├─ 1+ finding with confidence ≥ 50 AND a concrete exploit path identified
331
349
  ├─ Specific CVE confirmed applicable (version matches, PoC available)
332
350
  ├─ Or: critical misconfiguration found (default creds, exposed .env, anon access)
333
- └─ Or: brute-force/credential testing ready on identified service
351
+ ├─ Or: brute-force/credential testing ready on identified service
352
+ └─ [Artifact] Logic understood (e.g. crypto flaw, reverse engineering logic mapped) — ready to write solver
334
353
 
335
354
  exploit → post_exploitation:
336
355
  ├─ Shell obtained AND promoted (active_shell process is running)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pentesting",
3
- "version": "0.70.9",
3
+ "version": "0.70.10",
4
4
  "description": "Autonomous Penetration Testing AI Agent",
5
5
  "type": "module",
6
6
  "main": "dist/main.js",