@pensar/apex 2.6.1 → 2.7.0-canary.52c132f4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/build/{agent-yby11zaq.js → agent-112gdg69.js} +6 -6
  2. package/build/agent-9bh42v64.js +13 -0
  3. package/build/agent-jg7v2yn0.js +13 -0
  4. package/build/{agent-runs-g37vkrjr.js → agent-runs-hhjzsysf.js} +18 -18
  5. package/build/{agent-ayreaj6z.js → agent-ydy5fq92.js} +5 -5
  6. package/build/{apps-xn6km9yn.js → apps-v4jc7eqb.js} +13 -13
  7. package/build/{auth-d105wc64.js → auth-bxkn5pcb.js} +13 -13
  8. package/build/{blackboxAgent-s56sgx9b.js → blackboxAgent-260yqknt.js} +6 -6
  9. package/build/{blackboxPentest-ps78v3w7.js → blackboxPentest-nbn588pb.js} +17 -12
  10. package/build/{cli-3sp6nd8d.js → cli-087apjgk.js} +2 -2
  11. package/build/{cli-1ve18npr.js → cli-1wtxf161.js} +3 -2
  12. package/build/{cli-d37a42dc.js → cli-3158x9wb.js} +101 -18
  13. package/build/{cli-fy6vh2a7.js → cli-35gz0ahm.js} +1 -1
  14. package/build/{cli-65g6rcat.js → cli-5xdefy58.js} +1 -1
  15. package/build/{cli-709rvekb.js → cli-6vpprmxb.js} +4 -2
  16. package/build/{cli-az3xq70v.js → cli-8hrdnq0e.js} +1 -1
  17. package/build/{cli-7eea4e2y.js → cli-ahk627k3.js} +6 -6
  18. package/build/{cli-c05y640n.js → cli-bc793xfp.js} +1 -1
  19. package/build/{cli-ae8jqxqr.js → cli-cs7hsb1w.js} +2 -2
  20. package/build/{cli-qgz1q9g5.js → cli-ene4g09p.js} +223 -76
  21. package/build/{cli-gx71jcke.js → cli-k8m6737j.js} +1 -1
  22. package/build/{cli-dpsw2fe6.js → cli-mm2vxyne.js} +360 -187
  23. package/build/{cli-ngq9dr8n.js → cli-mpmpfce6.js} +3 -3
  24. package/build/{cli-kb21qhvm.js → cli-np9x97h3.js} +4 -3
  25. package/build/{cli-k2nwxta0.js → cli-tb77javm.js} +2 -2
  26. package/build/{cli-xtc2t6me.js → cli-wa52mx4p.js} +103 -39
  27. package/build/{cli-n2en7j5b.js → cli-xe4wq07r.js} +1 -1
  28. package/build/cli.js +70 -40
  29. package/build/{config-zpqkd5cz.js → config-gpxx6m5z.js} +1 -1
  30. package/build/{fastStrike-mcjdvwez.js → fastStrike-v0r461wn.js} +67 -15
  31. package/build/{fixes-kytx5dmj.js → fixes-c51d83b4.js} +13 -13
  32. package/build/{index-qv4cc65p.js → index-269a90mm.js} +13 -13
  33. package/build/{index-wqetp6gq.js → index-3ggmqh6x.js} +12 -7
  34. package/build/{index-4vqdgb42.js → index-e97991ve.js} +1473 -1217
  35. package/build/{index-n65z6v7b.js → index-ffay4gb2.js} +6 -6
  36. package/build/{index-ns4m4s01.js → index-gw6t92t5.js} +4 -4
  37. package/build/{index-dr3tg1w3.js → index-kkt51z6t.js} +1 -1
  38. package/build/{index-fv64eb32.js → index-kmaq2ym2.js} +2 -2
  39. package/build/{index-je6hcsqr.js → index-wgas3zgb.js} +1 -1
  40. package/build/{index-r9yjkfrv.js → index-xfqh0wte.js} +4 -4
  41. package/build/{issues-eb3q9d3a.js → issues-sq3zya6y.js} +13 -13
  42. package/build/{knowledge-fqsvtx0f.js → knowledge-kzd0cgcm.js} +13 -13
  43. package/build/lib/{chunk-r4j8ybn9.js → chunk-0parq6kp.js} +87 -36
  44. package/build/lib/{chunk-v3dk9j7k.js → chunk-2rcfbtpx.js} +80 -21
  45. package/build/lib/{chunk-acgbyf5h.js → chunk-5axh2ztr.js} +3 -2
  46. package/build/lib/{chunk-yrnrbjes.js → chunk-5qdc4rc6.js} +4 -2
  47. package/build/lib/{chunk-xhdpgpkm.js → chunk-61pnyq94.js} +2 -2
  48. package/build/lib/{chunk-1azdancq.js → chunk-7p9f6cxx.js} +1 -1
  49. package/build/lib/{chunk-z137x08b.js → chunk-7sexq62c.js} +7 -5
  50. package/build/lib/{chunk-ba41jh41.js → chunk-7zayxxn6.js} +304 -129
  51. package/build/lib/{chunk-3c00n0cy.js → chunk-83nets1m.js} +8 -3
  52. package/build/lib/{chunk-e8yy2k73.js → chunk-ajfd3s37.js} +28 -2
  53. package/build/lib/{chunk-4hwzqthr.js → chunk-ay1fx18z.js} +3 -2
  54. package/build/lib/{chunk-1fy8azee.js → chunk-bgnq735r.js} +1 -1
  55. package/build/lib/{chunk-mg03jdmt.js → chunk-eaanxcjj.js} +1 -1
  56. package/build/lib/{chunk-w66ce82a.js → chunk-f28sa6x2.js} +4 -2
  57. package/build/lib/{chunk-z96pbjhd.js → chunk-f6xde44n.js} +13 -6
  58. package/build/lib/{chunk-1jvwqnem.js → chunk-fvgehnst.js} +104 -40
  59. package/build/lib/{chunk-2qsq76xq.js → chunk-fy2a1dm3.js} +128 -34
  60. package/build/lib/{chunk-7pwa92rn.js → chunk-jqy0ftnp.js} +1 -1
  61. package/build/lib/{chunk-bb0ekhkw.js → chunk-k6erk6fv.js} +69 -18
  62. package/build/lib/{chunk-9tt2021m.js → chunk-s338j54x.js} +9 -9
  63. package/build/lib/{chunk-1yct6cra.js → chunk-se6sk7e4.js} +1 -1
  64. package/build/lib/{chunk-nhfax4gb.js → chunk-tdprhz5x.js} +1 -1
  65. package/build/lib/{chunk-r2dkp4tb.js → chunk-wbt03786.js} +1 -1
  66. package/build/lib/{chunk-9yksqgzr.js → chunk-yk43cvgb.js} +20 -13
  67. package/build/lib/core/agents/agentRuntime.d.ts +2 -1
  68. package/build/lib/core/agents/defineAgent.d.ts +3 -2
  69. package/build/lib/core/agents/offSecAgent/index.d.ts +3 -1
  70. package/build/lib/core/agents/offSecAgent/index.js +10 -8
  71. package/build/lib/core/agents/offSecAgent/prompt.d.ts +3 -1
  72. package/build/lib/core/agents/offSecAgent/subagentSpawner.d.ts +2 -1
  73. package/build/lib/core/agents/offSecAgent/subagentSpawner.js +5 -5
  74. package/build/lib/core/agents/offSecAgent/tools/index.js +4 -4
  75. package/build/lib/core/agents/offSecAgent/tools/listMemories.d.ts +11 -1
  76. package/build/lib/core/agents/offSecAgent/tools/reportError.d.ts +2 -1
  77. package/build/lib/core/agents/offSecAgent/tools/runPentestWorkflow.d.ts +20 -1
  78. package/build/lib/core/agents/offSecAgent/tools/spawnPentestAgent.d.ts +4 -7
  79. package/build/lib/core/agents/offSecAgent/tools/spawnPentestSwarm.d.ts +7 -1
  80. package/build/lib/core/agents/offSecAgent/tools/types.d.ts +2 -1
  81. package/build/lib/core/agents/offSecAgent/types.d.ts +7 -1
  82. package/build/lib/core/agents/specialized/attackSurface/blackboxAgent.js +9 -9
  83. package/build/lib/core/agents/specialized/attackSurface/schemas.js +9 -9
  84. package/build/lib/core/agents/specialized/authenticationAgent/index.js +10 -10
  85. package/build/lib/core/agents/specialized/codeAgent/agent.js +9 -9
  86. package/build/lib/core/agents/specialized/codeAgent/prompts.d.ts +1 -0
  87. package/build/lib/core/agents/specialized/findingJudge/agent.js +8 -8
  88. package/build/lib/core/agents/specialized/findingJudge/index.js +3 -3
  89. package/build/lib/core/agents/specialized/patching/index.js +10 -10
  90. package/build/lib/core/agents/specialized/pentest/agent.d.ts +18 -4
  91. package/build/lib/core/agents/specialized/pentest/agent.js +11 -9
  92. package/build/lib/core/agents/specialized/whiteboxAttackSurface/index.js +9 -9
  93. package/build/lib/core/ai/ai.d.ts +23 -6
  94. package/build/lib/core/ai/ai.js +2 -2
  95. package/build/lib/core/ai/index.js +2 -2
  96. package/build/lib/core/ai/models/claude-capabilities.d.ts +3 -2
  97. package/build/lib/core/ai/models/index.d.ts +1 -1
  98. package/build/lib/core/ai/utils.d.ts +2 -0
  99. package/build/lib/core/ai/utils.js +4 -2
  100. package/build/lib/core/api/index.d.ts +3 -0
  101. package/build/lib/core/api/index.js +22 -19
  102. package/build/lib/core/api/targetedPentest.d.ts +1 -0
  103. package/build/lib/core/api/targetedPentest.js +10 -10
  104. package/build/lib/core/config/config.d.ts +3 -0
  105. package/build/lib/core/config/index.js +1 -1
  106. package/build/lib/core/findings/registry.js +3 -3
  107. package/build/lib/core/session/index.d.ts +12 -0
  108. package/build/lib/core/session/index.js +2 -2
  109. package/build/lib/core/session/persistence.d.ts +7 -0
  110. package/build/lib/core/session/persistence.js +7 -3
  111. package/build/lib/core/workflows/fastStrike.d.ts +3 -1
  112. package/build/lib/core/workflows/pentest.d.ts +8 -1
  113. package/build/lib/core/workflows/pentest.js +15 -15
  114. package/build/lib/core/workflows/seams.js +4 -4
  115. package/build/lib/core/workflows/whiteboxAttackSurface.js +11 -11
  116. package/build/lib/index.js +22 -22
  117. package/build/{logs-x62b7hk1.js → logs-yk8dk6aa.js} +13 -13
  118. package/build/{native-rollout-evidence-c9yhfqcs.js → native-rollout-evidence-krpdfdv7.js} +3 -3
  119. package/build/{offesecAgent-5whnpeta.js → offesecAgent-aw87gf37.js} +4 -4
  120. package/build/pentest-t0p4nw2a.js +22 -0
  121. package/build/{pentests-e0f32aqx.js → pentests-577n3x75.js} +13 -13
  122. package/build/recordedRunClient-d7947b0z.js +11 -0
  123. package/build/{targetedPentest-rcbs4nvp.js → targetedPentest-7v849dtm.js} +18 -11
  124. package/build/{targets-xj9g3q0z.js → targets-3vjxv3yj.js} +13 -13
  125. package/build/threatModel-n0grwkav.js +21 -0
  126. package/build/{uninstall-7s784q4a.js → uninstall-ctv33v8j.js} +1 -1
  127. package/build/{upload-4gvhwmvr.js → upload-rnec235r.js} +4 -4
  128. package/build/{utils-cjrz64pc.js → utils-6420gkkg.js} +3 -3
  129. package/package.json +1 -1
  130. package/src/cli.ts +48 -14
  131. package/src/core/agents/agentRuntime.test.ts +38 -0
  132. package/src/core/agents/agentRuntime.ts +5 -2
  133. package/src/core/agents/defineAgent.ts +3 -1
  134. package/src/core/agents/offSecAgent/index.ts +3 -0
  135. package/src/core/agents/offSecAgent/offensiveSecurityAgent.test.ts +92 -5
  136. package/src/core/agents/offSecAgent/offensiveSecurityAgent.ts +70 -31
  137. package/src/core/agents/offSecAgent/prompt.test.ts +46 -0
  138. package/src/core/agents/offSecAgent/prompt.ts +52 -8
  139. package/src/core/agents/offSecAgent/selectiveConstruction.publicPath.test.ts +38 -0
  140. package/src/core/agents/offSecAgent/selectiveConstruction.test.ts +64 -0
  141. package/src/core/agents/offSecAgent/subagentSpawner.ts +12 -1
  142. package/src/core/agents/offSecAgent/tools/delegateAuth.ts +1 -0
  143. package/src/core/agents/offSecAgent/tools/listMemories.test.ts +230 -0
  144. package/src/core/agents/offSecAgent/tools/listMemories.ts +73 -6
  145. package/src/core/agents/offSecAgent/tools/reportError.test.ts +6 -0
  146. package/src/core/agents/offSecAgent/tools/reportError.ts +10 -2
  147. package/src/core/agents/offSecAgent/tools/runAttackSurface.ts +2 -0
  148. package/src/core/agents/offSecAgent/tools/runPentestWorkflow.test.ts +57 -0
  149. package/src/core/agents/offSecAgent/tools/runPentestWorkflow.ts +19 -1
  150. package/src/core/agents/offSecAgent/tools/runWhiteboxScan.ts +5 -2
  151. package/src/core/agents/offSecAgent/tools/spawnCodingAgent.ts +1 -0
  152. package/src/core/agents/offSecAgent/tools/spawnPentestAgent.test.ts +72 -2
  153. package/src/core/agents/offSecAgent/tools/spawnPentestAgent.ts +32 -11
  154. package/src/core/agents/offSecAgent/tools/spawnPentestSwarm.ts +25 -5
  155. package/src/core/agents/offSecAgent/tools/threatModelGenerator.ts +1 -0
  156. package/src/core/agents/offSecAgent/tools/types.ts +6 -1
  157. package/src/core/agents/offSecAgent/tools/updateTask.ts +5 -1
  158. package/src/core/agents/offSecAgent/tools/whiteboxCandidates.ts +11 -4
  159. package/src/core/agents/offSecAgent/types.ts +9 -0
  160. package/src/core/agents/specialized/codeAgent/agent.test.ts +7 -0
  161. package/src/core/agents/specialized/codeAgent/agent.ts +9 -2
  162. package/src/core/agents/specialized/codeAgent/prompts.ts +6 -0
  163. package/src/core/agents/specialized/pentest/agent.test.ts +116 -0
  164. package/src/core/agents/specialized/pentest/agent.ts +178 -49
  165. package/src/core/agents/specialized/pentest/results.test.ts +59 -4
  166. package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.ts +1 -0
  167. package/src/core/ai/ai.operator-context.bench.test.ts +1408 -0
  168. package/src/core/ai/ai.recorded-context.test.ts +74 -0
  169. package/src/core/ai/ai.test.ts +54 -0
  170. package/src/core/ai/ai.ts +299 -80
  171. package/src/core/ai/compactionTelemetry.test.ts +2 -2
  172. package/src/core/ai/models/anthropic.ts +6 -0
  173. package/src/core/ai/models/bedrock.ts +18 -0
  174. package/src/core/ai/models/claude-capabilities.ts +29 -8
  175. package/src/core/ai/models/index.ts +4 -1
  176. package/src/core/ai/models/models.test.ts +23 -0
  177. package/src/core/ai/models/openrouter.ts +6 -0
  178. package/src/core/ai/providers/anthropic-models.test.ts +60 -1
  179. package/src/core/ai/providers/bedrock-models.test.ts +81 -0
  180. package/src/core/ai/providers/gateway-models.test.ts +16 -6
  181. package/src/core/ai/utils.native-rollout-evidence.test.ts +1 -0
  182. package/src/core/ai/utils.ts +189 -143
  183. package/src/core/api/blackboxPentest.ts +9 -1
  184. package/src/core/api/index.ts +9 -0
  185. package/src/core/api/targetedPentest.ts +19 -5
  186. package/src/core/config/config.test.ts +35 -0
  187. package/src/core/config/config.ts +5 -0
  188. package/src/core/observability/telemetry.test.ts +2 -3
  189. package/src/core/session/index.ts +5 -0
  190. package/src/core/session/persistence.test.ts +62 -0
  191. package/src/core/session/persistence.ts +56 -0
  192. package/src/core/workflows/fastStrike.seams.test.ts +149 -0
  193. package/src/core/workflows/fastStrike.test.ts +18 -1
  194. package/src/core/workflows/fastStrike.ts +103 -11
  195. package/src/core/workflows/pentest.test.ts +104 -0
  196. package/src/core/workflows/pentest.ts +110 -7
  197. package/src/core/workflows/seams.test.ts +13 -0
  198. package/src/core/workflows/seams.ts +5 -1
  199. package/src/core/workflows/whiteboxAttackSurface.ts +1 -0
  200. package/src/tui/components/chat/loading-indicator.tsx +20 -4
  201. package/src/tui/components/chat/message-list.tsx +1 -0
  202. package/src/tui/components/commands/__tests__/advanced-settings-lifecycle.tsx +176 -0
  203. package/src/tui/components/commands/advanced-settings.test.ts +28 -0
  204. package/src/tui/components/commands/advanced-settings.tsx +237 -19
  205. package/src/tui/components/operator-dashboard/index.tsx +46 -9
  206. package/src/tui/components/operator-dashboard/logic.test.ts +106 -0
  207. package/src/tui/components/operator-dashboard/logic.ts +42 -2
  208. package/src/tui/components/operator-dashboard/run-events.test.ts +3 -1
  209. package/src/tui/components/shared/__tests__/tool-renderer-lifecycle.tsx +18 -0
  210. package/src/tui/components/shared/action-label.test.ts +9 -0
  211. package/src/tui/components/shared/compacting-icon.tsx +18 -0
  212. package/src/tui/components/shared/index.ts +1 -0
  213. package/src/tui/components/shared/tool-registry.ts +1 -0
  214. package/src/tui/components/shared/tool-renderer.tsx +21 -7
  215. package/src/tui/context/route.tsx +1 -0
  216. package/src/tui/keybindings/registry.ts +2 -0
  217. package/build/agent-6w7egcf1.js +0 -13
  218. package/build/agent-9zx75feh.js +0 -13
  219. package/build/pentest-e61hc38s.js +0 -22
  220. package/build/recordedRunClient-dp032c32.js +0 -11
  221. package/build/threatModel-jkxfa40s.js +0 -21
@@ -1,22 +1,88 @@
1
1
  import { useKeyboard } from "@opentui/react";
2
2
  import { useCallback, useState } from "react";
3
+ import type { FindingPolicy } from "../../../core/agents/offSecAgent";
3
4
  import { useConfig } from "../../context/config";
4
5
  import { Dialog } from "../../context/dialog";
6
+ import { useDimensions } from "../../context/dimensions";
5
7
  import { useTheme } from "../../theme";
6
8
  import DialogLayout from "../dialog-layout";
7
9
 
10
+ const FINDING_POLICIES: { value: FindingPolicy; label: string }[] = [
11
+ { value: "native", label: "Apex judge (default)" },
12
+ { value: "external", label: "Off (report findings only)" },
13
+ ];
14
+
8
15
  interface AdvancedSettingsProps {
9
16
  onClose: () => void;
10
17
  }
11
18
 
19
+ type FocusedSetting = "strike" | "judge" | "save";
20
+
12
21
  export default function AdvancedSettings({ onClose }: AdvancedSettingsProps) {
13
22
  const { colors } = useTheme();
23
+ const dimensions = useDimensions();
24
+ const compact = dimensions.width < 65 || dimensions.height < 24;
14
25
  const config = useConfig();
15
26
  const [saving, setSaving] = useState(false);
16
27
  const [error, setError] = useState<string | null>(null);
17
28
  const [draftStrikeMode, setDraftStrikeMode] = useState(
18
29
  config.data.strikeMode ?? false,
19
30
  );
31
+ const [draftFindingPolicy, setDraftFindingPolicy] = useState<FindingPolicy>(
32
+ config.data.strikeMode ? (config.data.findingPolicy ?? "native") : "native",
33
+ );
34
+ const [findingSelectOpen, setFindingSelectOpen] = useState(false);
35
+ const [highlightedFindingIndex, setHighlightedFindingIndex] = useState(0);
36
+ const [focusedSetting, setFocusedSetting] =
37
+ useState<FocusedSetting>("strike");
38
+
39
+ const openFindingSelect = useCallback(() => {
40
+ if (!draftStrikeMode) return;
41
+ setHighlightedFindingIndex(
42
+ Math.max(
43
+ 0,
44
+ FINDING_POLICIES.findIndex(
45
+ (option) => option.value === draftFindingPolicy,
46
+ ),
47
+ ),
48
+ );
49
+ setFindingSelectOpen(true);
50
+ }, [draftFindingPolicy, draftStrikeMode]);
51
+
52
+ const toggleStrikeMode = useCallback(() => {
53
+ if (draftStrikeMode) {
54
+ setDraftFindingPolicy("native");
55
+ setFindingSelectOpen(false);
56
+ setFocusedSetting((focused) =>
57
+ focused === "judge" ? "strike" : focused,
58
+ );
59
+ }
60
+ setDraftStrikeMode(!draftStrikeMode);
61
+ }, [draftStrikeMode]);
62
+
63
+ const toggleFindingPolicy = useCallback(() => {
64
+ if (!draftStrikeMode) return;
65
+ setDraftFindingPolicy((policy) =>
66
+ policy === "native" ? "external" : "native",
67
+ );
68
+ setFindingSelectOpen(false);
69
+ }, [draftStrikeMode]);
70
+
71
+ const moveFocus = useCallback(
72
+ (direction: number) => {
73
+ const visibleSettings: FocusedSetting[] = draftStrikeMode
74
+ ? ["strike", "judge", "save"]
75
+ : ["strike", "save"];
76
+ setFocusedSetting((focused) => {
77
+ const currentIndex = Math.max(0, visibleSettings.indexOf(focused));
78
+ const nextIndex =
79
+ (currentIndex + direction + visibleSettings.length) %
80
+ visibleSettings.length;
81
+ return visibleSettings[nextIndex] ?? "strike";
82
+ });
83
+ },
84
+ [draftStrikeMode],
85
+ );
20
86
 
21
87
  const saveSettings = useCallback(async () => {
22
88
  if (saving) return;
@@ -24,34 +90,72 @@ export default function AdvancedSettings({ onClose }: AdvancedSettingsProps) {
24
90
  setSaving(true);
25
91
  setError(null);
26
92
  try {
27
- await config.update({ strikeMode: draftStrikeMode });
93
+ await config.update({
94
+ strikeMode: draftStrikeMode,
95
+ findingPolicy: draftStrikeMode ? draftFindingPolicy : "native",
96
+ });
28
97
  onClose();
29
98
  } catch (cause) {
30
99
  setError(
31
100
  cause instanceof Error
32
- ? `Could not save Strike Mode: ${cause.message}`
33
- : "Could not save Strike Mode.",
101
+ ? `Could not save advanced settings: ${cause.message}`
102
+ : "Could not save advanced settings.",
34
103
  );
35
104
  setSaving(false);
36
105
  }
37
- }, [config, draftStrikeMode, onClose, saving]);
106
+ }, [config, draftFindingPolicy, draftStrikeMode, onClose, saving]);
38
107
 
39
108
  useKeyboard((key) => {
109
+ if (key.name === "tab") {
110
+ key.preventDefault();
111
+ if (focusedSetting === "strike") toggleStrikeMode();
112
+ else if (focusedSetting === "judge") toggleFindingPolicy();
113
+ return;
114
+ }
115
+
116
+ if (findingSelectOpen) {
117
+ key.preventDefault();
118
+
119
+ if (key.name === "escape") {
120
+ setFindingSelectOpen(false);
121
+ return;
122
+ }
123
+
124
+ if (key.name === "up" || key.name === "down") {
125
+ const direction = key.name === "up" ? -1 : 1;
126
+ setHighlightedFindingIndex(
127
+ (index) =>
128
+ (index + direction + FINDING_POLICIES.length) %
129
+ FINDING_POLICIES.length,
130
+ );
131
+ return;
132
+ }
133
+
134
+ if (key.name === "return") {
135
+ const selected = FINDING_POLICIES[highlightedFindingIndex];
136
+ if (selected) setDraftFindingPolicy(selected.value);
137
+ setFindingSelectOpen(false);
138
+ }
139
+ return;
140
+ }
141
+
40
142
  if (key.name === "escape") {
41
143
  key.preventDefault();
42
144
  onClose();
43
145
  return;
44
146
  }
45
147
 
46
- if (key.name === "tab") {
148
+ if (key.name === "up" || key.name === "down") {
47
149
  key.preventDefault();
48
- setDraftStrikeMode((enabled) => !enabled);
150
+ moveFocus(key.name === "up" ? -1 : 1);
49
151
  return;
50
152
  }
51
153
 
52
154
  if (key.name === "return") {
53
155
  key.preventDefault();
54
- void saveSettings();
156
+ if (focusedSetting === "strike") toggleStrikeMode();
157
+ else if (focusedSetting === "judge") openFindingSelect();
158
+ else void saveSettings();
55
159
  }
56
160
  });
57
161
 
@@ -59,31 +163,145 @@ export default function AdvancedSettings({ onClose }: AdvancedSettingsProps) {
59
163
  <Dialog size="medium" onClose={onClose}>
60
164
  <DialogLayout
61
165
  title="Advanced Settings"
166
+ escLabel={findingSelectOpen ? "back" : "close"}
62
167
  footerActions={[
63
- { key: "Enter", label: "Save & Close", variant: "primary" },
64
- { key: "Tab", label: "Toggle" },
168
+ findingSelectOpen
169
+ ? { key: "Enter", label: "Select", variant: "primary" }
170
+ : { key: "Enter", label: "Activate", variant: "primary" },
171
+ findingSelectOpen
172
+ ? { key: "↑/↓", label: "Choose" }
173
+ : { key: "↑/↓", label: "Navigate" },
174
+ ...(dimensions.width >= 60 && focusedSetting !== "save"
175
+ ? [{ key: "Tab", label: "Toggle" }]
176
+ : []),
65
177
  ]}
66
178
  >
67
- <box flexDirection="column" gap={1} overflow="hidden">
179
+ <box flexDirection="column" gap={compact ? 0 : 1} overflow="hidden">
68
180
  <box
69
181
  flexDirection="row"
70
182
  justifyContent="space-between"
71
183
  overflow="hidden"
184
+ backgroundColor={
185
+ focusedSetting === "strike" && !findingSelectOpen
186
+ ? colors.backgroundSelected
187
+ : undefined
188
+ }
189
+ onMouseDown={() => {
190
+ setFocusedSetting("strike");
191
+ toggleStrikeMode();
192
+ }}
72
193
  >
73
- <text fg={colors.text}>Strike Mode</text>
194
+ <text
195
+ fg={focusedSetting === "strike" ? colors.primary : colors.text}
196
+ >
197
+ {focusedSetting === "strike" ? "❯ " : " "}Strike Mode
198
+ </text>
74
199
  <text fg={draftStrikeMode ? colors.warning : colors.textMuted}>
75
200
  {draftStrikeMode ? "● Enabled" : "○ Disabled"}
76
201
  </text>
77
202
  </box>
78
203
 
79
- <text fg={colors.textMuted}>
80
- Use one focused operator for iterative vulnerability research.
81
- Disables automatic discovery and agent fan-out.
82
- </text>
83
- <text fg={colors.textMuted}>
84
- Applies to new operator sessions. Existing sessions keep their saved
85
- mode.
86
- </text>
204
+ {!compact && (
205
+ <text fg={colors.textMuted}>
206
+ Focused research; no auto-discovery or agent fan-out.
207
+ </text>
208
+ )}
209
+
210
+ {draftStrikeMode && (
211
+ <>
212
+ <box
213
+ flexDirection="row"
214
+ justifyContent="space-between"
215
+ overflow="hidden"
216
+ backgroundColor={
217
+ focusedSetting === "judge" && !findingSelectOpen
218
+ ? colors.backgroundSelected
219
+ : undefined
220
+ }
221
+ onMouseDown={() => {
222
+ setFocusedSetting("judge");
223
+ openFindingSelect();
224
+ }}
225
+ >
226
+ <text
227
+ fg={focusedSetting === "judge" ? colors.primary : colors.text}
228
+ >
229
+ {focusedSetting === "judge" ? "❯ " : " "}Finding judge
230
+ </text>
231
+ <text
232
+ fg={
233
+ draftFindingPolicy === "native"
234
+ ? colors.primary
235
+ : colors.warning
236
+ }
237
+ >
238
+ {draftFindingPolicy === "native" ? "Apex judge" : "Disabled"}
239
+ {" ▾"}
240
+ </text>
241
+ </box>
242
+
243
+ {findingSelectOpen && (
244
+ <box flexDirection="column" overflow="hidden">
245
+ {FINDING_POLICIES.map((option, index) => {
246
+ const highlighted = index === highlightedFindingIndex;
247
+ return (
248
+ <box
249
+ key={option.value}
250
+ flexDirection="row"
251
+ paddingLeft={1}
252
+ paddingRight={1}
253
+ overflow="hidden"
254
+ backgroundColor={
255
+ highlighted ? colors.backgroundSelected : undefined
256
+ }
257
+ onMouseDown={() => {
258
+ setDraftFindingPolicy(option.value);
259
+ setFindingSelectOpen(false);
260
+ }}
261
+ >
262
+ <text fg={highlighted ? colors.primary : colors.text}>
263
+ {highlighted ? "❯ " : " "}
264
+ {option.label}
265
+ </text>
266
+ {draftFindingPolicy === option.value && (
267
+ <text fg={colors.primary}> ✓</text>
268
+ )}
269
+ </box>
270
+ );
271
+ })}
272
+ </box>
273
+ )}
274
+
275
+ {!compact && (
276
+ <>
277
+ <text fg={colors.textMuted}>
278
+ Judge off skips in-run review and report.
279
+ </text>
280
+ <text fg={colors.textMuted}>
281
+ New sessions only; TUI has no external evaluator.
282
+ </text>
283
+ </>
284
+ )}
285
+ </>
286
+ )}
287
+
288
+ <box
289
+ flexDirection="row"
290
+ overflow="hidden"
291
+ backgroundColor={
292
+ focusedSetting === "save" && !findingSelectOpen
293
+ ? colors.backgroundSelected
294
+ : undefined
295
+ }
296
+ onMouseDown={() => {
297
+ setFocusedSetting("save");
298
+ void saveSettings();
299
+ }}
300
+ >
301
+ <text fg={focusedSetting === "save" ? colors.primary : colors.text}>
302
+ {focusedSetting === "save" ? "❯ " : " "}Save settings
303
+ </text>
304
+ </box>
87
305
 
88
306
  {saving && <text fg={colors.textMuted}>Saving…</text>}
89
307
  {error && <text fg={colors.error}>{error}</text>}
@@ -29,6 +29,7 @@ import {
29
29
  type AgentMode,
30
30
  ALL_TOOL_NAMES,
31
31
  ASK_USER_QUESTIONS_TOOL_NAME,
32
+ type FindingPolicy,
32
33
  PLAN_MODE_TOOL_NAMES,
33
34
  SKILL_TOOL_NAMES,
34
35
  } from "../../../core/agents/offSecAgent";
@@ -108,12 +109,15 @@ import {
108
109
  type DashboardStatus,
109
110
  filterOperatorAutocomplete,
110
111
  resolveAbortAction,
112
+ resolveActiveFindingPolicy,
111
113
  resolveInputFocused,
112
114
  resolveKeyboardShortcut,
113
115
  resolveOperatorAgentMode,
116
+ resolveOperatorFindingPolicy,
114
117
  resolveOperatorStrikeMode,
115
118
  resolveSubmit,
116
119
  routeCommand,
120
+ shouldHandleQueuedMessageKey,
117
121
  } from "./logic";
118
122
  import {
119
123
  createQueuedMessage,
@@ -159,6 +163,7 @@ export default function OperatorDashboard({
159
163
  target?: string;
160
164
  operatorMode?: OperatorMode;
161
165
  strikeMode?: boolean;
166
+ findingPolicy?: FindingPolicy;
162
167
  sandbox?: boolean;
163
168
  taskDriven?: boolean;
164
169
  headers?: Record<string, string>;
@@ -206,6 +211,17 @@ export default function OperatorDashboard({
206
211
  }),
207
212
  );
208
213
  const [strikeMode, setStrikeMode] = useState(initialStrikeModeRef.current);
214
+ const initialFindingPolicyRef = useRef(
215
+ resolveOperatorFindingPolicy({
216
+ resuming: !!sessionId,
217
+ strikeMode: initialStrikeModeRef.current,
218
+ routeOverride: initialConfig?.findingPolicy,
219
+ persistedDefault: config.data.findingPolicy,
220
+ }),
221
+ );
222
+ const [findingPolicy, setFindingPolicy] = useState(
223
+ initialFindingPolicyRef.current,
224
+ );
209
225
 
210
226
  // biome-ignore lint/correctness/useExhaustiveDependencies: `skillsVersion` is an intentional cache-buster — forces recomputation when skills are refreshed even though `skillsRegistry` (stable ref) hasn't changed.
211
227
  const autocompleteOptions = useMemo(() => {
@@ -361,6 +377,10 @@ export default function OperatorDashboard({
361
377
  operatorMode,
362
378
  strikeMode,
363
379
  );
380
+ const activeFindingPolicy = resolveActiveFindingPolicy(
381
+ agentMode,
382
+ findingPolicy,
383
+ );
364
384
  const requireApproval = operatorMode === "manual";
365
385
  // Plan mode review state
366
386
  const [approvedPlanContent, setApprovedPlanContent] = useState<string | null>(
@@ -427,10 +447,16 @@ export default function OperatorDashboard({
427
447
  const s = await sessions.get(sessionId);
428
448
  setSession(s);
429
449
  setSessionCwd(s.rootPath);
430
- setStrikeMode(
431
- resolveOperatorStrikeMode({
450
+ const resumedStrikeMode = resolveOperatorStrikeMode({
451
+ resuming: true,
452
+ savedSessionValue: s.config?.operatorSettings?.strikeMode,
453
+ });
454
+ setStrikeMode(resumedStrikeMode);
455
+ setFindingPolicy(
456
+ resolveOperatorFindingPolicy({
432
457
  resuming: true,
433
- savedSessionValue: s.config?.operatorSettings?.strikeMode,
458
+ strikeMode: resumedStrikeMode,
459
+ savedSessionValue: s.config?.operatorSettings?.findingPolicy,
434
460
  }),
435
461
  );
436
462
 
@@ -516,6 +542,7 @@ export default function OperatorDashboard({
516
542
  // New session — just set up operator config; the agent creates the
517
543
  // session on the first runAgent call.
518
544
  setStrikeMode(initialStrikeModeRef.current);
545
+ setFindingPolicy(initialFindingPolicyRef.current);
519
546
  const newMode = initialConfig?.operatorMode ?? "manual";
520
547
  setOperatorMode(newMode);
521
548
  const requireApproval = newMode === "manual";
@@ -871,6 +898,7 @@ export default function OperatorDashboard({
871
898
  ...SKILL_TOOL_NAMES,
872
899
  ] as string[],
873
900
  mode: agentMode,
901
+ findingPolicy: activeFindingPolicy,
874
902
  authConfig: buildAuthConfig(config.data),
875
903
  approvalGate: approvalGateRef.current,
876
904
  commandCancelHandle: cancelHandleRef.current,
@@ -926,6 +954,7 @@ export default function OperatorDashboard({
926
954
  agentMode !== "plan" ? approvedPlanRef.current : null,
927
955
  taskDriven:
928
956
  session?.config?.taskDriven ?? initialConfig?.taskDriven,
957
+ findingPolicy: activeFindingPolicy,
929
958
  },
930
959
  );
931
960
 
@@ -945,6 +974,7 @@ export default function OperatorDashboard({
945
974
  requireApproval,
946
975
  enableSuggestions: true,
947
976
  strikeMode,
977
+ findingPolicy,
948
978
  },
949
979
  agentCwd: initialConfig?.sandbox ? undefined : process.cwd(),
950
980
  codebasePath: initialConfig?.sandbox ? undefined : process.cwd(),
@@ -1082,6 +1112,8 @@ export default function OperatorDashboard({
1082
1112
  initialConfig?.headers,
1083
1113
  initialConfig?.promptInjectionLibrarySource,
1084
1114
  strikeMode,
1115
+ findingPolicy,
1116
+ activeFindingPolicy,
1085
1117
  route.data,
1086
1118
  setSessionCwd,
1087
1119
  skillsRegistry.buildCatalog,
@@ -1634,11 +1666,16 @@ This three-phase flow is specific to the TUI \`/threat-model\` command. The same
1634
1666
 
1635
1667
  // Keyboard shortcuts
1636
1668
  useKeyboard((key) => {
1669
+ const dialogOpen =
1670
+ stack.length > 0 || externalDialogOpen || !!pendingQuestions;
1637
1671
  // Queue navigation: handle before general shortcuts
1638
1672
  if (
1639
- status === "running" &&
1640
- queuedMessages.length > 0 &&
1641
- !inputValue.trim()
1673
+ shouldHandleQueuedMessageKey(
1674
+ status,
1675
+ queuedMessages.length,
1676
+ inputValue,
1677
+ dialogOpen,
1678
+ )
1642
1679
  ) {
1643
1680
  if (key.name === "up") {
1644
1681
  key.preventDefault?.();
@@ -1765,9 +1802,6 @@ This three-phase flow is specific to the TUI \`/threat-model\` command. The same
1765
1802
 
1766
1803
  // Treat the questions form as a dialog so that dashboard-level shortcuts
1767
1804
  // (Shift+Tab cycle-mode, etc.) don't fire while the form owns the keyboard.
1768
- const dialogOpen =
1769
- stack.length > 0 || externalDialogOpen || !!pendingQuestions;
1770
-
1771
1805
  // Ctrl+A to open subagent dialog (skip if another dialog is open)
1772
1806
  if (
1773
1807
  key.ctrl &&
@@ -1926,6 +1960,9 @@ This three-phase flow is specific to the TUI \`/threat-model\` command. The same
1926
1960
  <box flexDirection="row" gap={2}>
1927
1961
  <text fg={colors.text}>{session?.name ?? "New Session"}</text>
1928
1962
  {strikeMode && <text fg={colors.warning}>STRIKE</text>}
1963
+ {activeFindingPolicy === "external" && (
1964
+ <text fg={colors.textMuted}>JUDGE OFF</text>
1965
+ )}
1929
1966
  {(session?.targets[0] || initialConfig?.target) && (
1930
1967
  <>
1931
1968
  <text fg={colors.textMuted}>•</text>
@@ -6,12 +6,15 @@ import {
6
6
  type DashboardStatus,
7
7
  filterOperatorAutocomplete,
8
8
  resolveAbortAction,
9
+ resolveActiveFindingPolicy,
9
10
  resolveInputFocused,
10
11
  resolveKeyboardShortcut,
11
12
  resolveOperatorAgentMode,
13
+ resolveOperatorFindingPolicy,
12
14
  resolveOperatorStrikeMode,
13
15
  resolveSubmit,
14
16
  routeCommand,
17
+ shouldHandleQueuedMessageKey,
15
18
  } from "./logic";
16
19
 
17
20
  // ---------------------------------------------------------------------------
@@ -130,6 +133,98 @@ describe("resolveOperatorStrikeMode", () => {
130
133
  });
131
134
  });
132
135
 
136
+ describe("resolveOperatorFindingPolicy", () => {
137
+ it("defaults new sessions to Apex finding review", () => {
138
+ expect(
139
+ resolveOperatorFindingPolicy({ resuming: false, strikeMode: true }),
140
+ ).toBe("native");
141
+ });
142
+
143
+ it("uses the saved preference for new Strike sessions", () => {
144
+ expect(
145
+ resolveOperatorFindingPolicy({
146
+ resuming: false,
147
+ strikeMode: true,
148
+ persistedDefault: "external",
149
+ }),
150
+ ).toBe("external");
151
+ });
152
+
153
+ it("lets an explicit workflow override the saved preference", () => {
154
+ expect(
155
+ resolveOperatorFindingPolicy({
156
+ resuming: false,
157
+ strikeMode: true,
158
+ routeOverride: "native",
159
+ persistedDefault: "external",
160
+ }),
161
+ ).toBe("native");
162
+ });
163
+
164
+ it("restores the policy saved with a resumed Strike session", () => {
165
+ expect(
166
+ resolveOperatorFindingPolicy({
167
+ resuming: true,
168
+ strikeMode: true,
169
+ savedSessionValue: "external",
170
+ persistedDefault: "native",
171
+ }),
172
+ ).toBe("external");
173
+ });
174
+
175
+ it("keeps legacy resumed sessions native despite a newer global preference", () => {
176
+ expect(
177
+ resolveOperatorFindingPolicy({
178
+ resuming: true,
179
+ strikeMode: true,
180
+ persistedDefault: "external",
181
+ }),
182
+ ).toBe("native");
183
+ });
184
+
185
+ it("keeps standard sessions native despite external preferences or overrides", () => {
186
+ expect(
187
+ resolveOperatorFindingPolicy({
188
+ resuming: false,
189
+ strikeMode: false,
190
+ routeOverride: "external",
191
+ persistedDefault: "external",
192
+ }),
193
+ ).toBe("native");
194
+ expect(
195
+ resolveOperatorFindingPolicy({
196
+ resuming: true,
197
+ strikeMode: false,
198
+ savedSessionValue: "external",
199
+ }),
200
+ ).toBe("native");
201
+ });
202
+ });
203
+
204
+ describe("resolveActiveFindingPolicy", () => {
205
+ it("allows judge off only while the agent is in Fast Strike", () => {
206
+ expect(resolveActiveFindingPolicy("fast-strike", "external")).toBe(
207
+ "external",
208
+ );
209
+ expect(resolveActiveFindingPolicy("default", "external")).toBe("native");
210
+ expect(resolveActiveFindingPolicy("plan", "external")).toBe("native");
211
+ });
212
+ });
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // Queue navigation
216
+ // ---------------------------------------------------------------------------
217
+
218
+ describe("shouldHandleQueuedMessageKey", () => {
219
+ it("leaves queue navigation to the dashboard only when no dialog owns the keyboard", () => {
220
+ expect(shouldHandleQueuedMessageKey("running", 2, "", false)).toBe(true);
221
+ expect(shouldHandleQueuedMessageKey("running", 2, "", true)).toBe(false);
222
+ expect(
223
+ shouldHandleQueuedMessageKey("running", 2, " /advanced ", false),
224
+ ).toBe(false);
225
+ });
226
+ });
227
+
133
228
  // ---------------------------------------------------------------------------
134
229
  // resolveSubmit
135
230
  // ---------------------------------------------------------------------------
@@ -623,6 +718,17 @@ describe("buildOperatorSystemPrompt", () => {
623
718
  expect(prompt).toContain("account for each viable primitive");
624
719
  });
625
720
 
721
+ it("keeps Strike's research loop while leaving finding review to the caller", () => {
722
+ const prompt = buildOperatorSystemPrompt(target, state, "fast-strike", {
723
+ findingPolicy: "external",
724
+ });
725
+ expect(prompt).toContain("# Strike Mode");
726
+ expect(prompt).toContain("requested output format");
727
+ expect(prompt).not.toContain("document_vulnerability");
728
+ expect(prompt).not.toContain("document confirmed vulnerabilities");
729
+ expect(prompt).not.toContain("official report is generated automatically");
730
+ });
731
+
626
732
  it("does not include Strike Mode instructions in the standard mode", () => {
627
733
  const prompt = buildOperatorSystemPrompt(target, state, "default");
628
734
  expect(prompt).not.toContain("# Strike Mode");