@pensar/apex 2.6.1 → 2.7.0-canary.1572d9a4

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 (328) hide show
  1. package/build/{agent-ayreaj6z.js → agent-3g1b578v.js} +6 -5
  2. package/build/{agent-yby11zaq.js → agent-p8cezgbb.js} +7 -6
  3. package/build/{agent-runs-g37vkrjr.js → agent-runs-f86emmgg.js} +23 -20
  4. package/build/agent-vaq31je6.js +14 -0
  5. package/build/agent-vvdqga09.js +14 -0
  6. package/build/{apps-xn6km9yn.js → apps-3stwnmen.js} +16 -13
  7. package/build/{auth-d105wc64.js → auth-7md9kwda.js} +16 -13
  8. package/build/blackboxAgent-ct8bejef.js +14 -0
  9. package/build/{blackboxPentest-ps78v3w7.js → blackboxPentest-w2nr4md3.js} +21 -15
  10. package/build/{cli-c05y640n.js → cli-1j28cbhy.js} +1 -1
  11. package/build/{cli-d37a42dc.js → cli-2jz2jz06.js} +206 -90
  12. package/build/{cli-az3xq70v.js → cli-4a8dsz2k.js} +4 -4
  13. package/build/{cli-qgz1q9g5.js → cli-58781vcf.js} +1192 -1473
  14. package/build/{cli-gx71jcke.js → cli-7067zzeb.js} +1 -1
  15. package/build/{cli-3sp6nd8d.js → cli-81smmd5w.js} +5 -5
  16. package/build/{cli-k2nwxta0.js → cli-8b25dhky.js} +2 -2
  17. package/build/{cli-fy6vh2a7.js → cli-8sw5zpqz.js} +1 -1
  18. package/build/{cli-709rvekb.js → cli-cg3ypx5n.js} +14 -7
  19. package/build/{cli-dpsw2fe6.js → cli-e9v6zg6h.js} +40941 -40172
  20. package/build/cli-fjxmphg1.js +36 -0
  21. package/build/{cli-7eea4e2y.js → cli-gzb6qrzh.js} +31 -60
  22. package/build/{cli-65g6rcat.js → cli-khnf5xrf.js} +2 -1
  23. package/build/{cli-1ve18npr.js → cli-mdzt2e8j.js} +4 -2
  24. package/build/{cli-n2en7j5b.js → cli-n8mmw4c4.js} +1 -1
  25. package/build/cli-pndb7mh9.js +1193 -0
  26. package/build/cli-px6ehzqx.js +54 -0
  27. package/build/{cli-xtc2t6me.js → cli-qs4msn6x.js} +111 -47
  28. package/build/{cli-kb21qhvm.js → cli-rvybdhgn.js} +4 -3
  29. package/build/{cli-ae8jqxqr.js → cli-v739h7hb.js} +2 -2
  30. package/build/{cli-ngq9dr8n.js → cli-wm8ech0x.js} +3 -3
  31. package/build/cli.js +170 -51
  32. package/build/{config-zpqkd5cz.js → config-e5v1bkh1.js} +1 -1
  33. package/build/{fastStrike-mcjdvwez.js → fastStrike-efp0rjs3.js} +74 -17
  34. package/build/{fixes-kytx5dmj.js → fixes-926cw1ej.js} +16 -13
  35. package/build/{index-n65z6v7b.js → index-3t41p258.js} +7 -6
  36. package/build/{index-qv4cc65p.js → index-bv09af6g.js} +19 -15
  37. package/build/{index-je6hcsqr.js → index-bv6dtwse.js} +1 -1
  38. package/build/{index-4vqdgb42.js → index-jt52ffgn.js} +1657 -1400
  39. package/build/{index-ns4m4s01.js → index-np405b9e.js} +11 -7
  40. package/build/{index-r9yjkfrv.js → index-qys178kp.js} +5 -4
  41. package/build/{index-wqetp6gq.js → index-tpyzq2wq.js} +42 -11
  42. package/build/{index-fv64eb32.js → index-vrcdyebh.js} +2 -2
  43. package/build/{index-dr3tg1w3.js → index-z1g8vbtm.js} +1 -1
  44. package/build/{issues-eb3q9d3a.js → issues-sncyqh14.js} +16 -13
  45. package/build/{knowledge-fqsvtx0f.js → knowledge-esgvy5ws.js} +16 -13
  46. package/build/lance-sacfgs1z.js +232 -0
  47. package/build/lib/{chunk-9yksqgzr.js → chunk-4aevegnc.js} +77 -45
  48. package/build/lib/{chunk-9tt2021m.js → chunk-4kf4a15q.js} +13 -13
  49. package/build/lib/{chunk-fy3bbbqy.js → chunk-5avnedwm.js} +7 -1
  50. package/build/lib/{chunk-bb0ekhkw.js → chunk-5nz5tk3p.js} +76 -22
  51. package/build/lib/{chunk-4hwzqthr.js → chunk-7s8e5wrd.js} +3 -2
  52. package/build/lib/{chunk-yrnrbjes.js → chunk-85kpxwzs.js} +4 -2
  53. package/build/lib/{chunk-mg03jdmt.js → chunk-9jkzk9nc.js} +1 -1
  54. package/build/lib/{chunk-r4j8ybn9.js → chunk-9wwts6r6.js} +824 -77
  55. package/build/lib/{chunk-nhfax4gb.js → chunk-bxznzxdy.js} +5 -1
  56. package/build/lib/{chunk-r2dkp4tb.js → chunk-cqxm95md.js} +1 -1
  57. package/build/lib/{chunk-w66ce82a.js → chunk-e3agqspc.js} +12 -4
  58. package/build/lib/{chunk-ba41jh41.js → chunk-eh5yw385.js} +1137 -333
  59. package/build/lib/{chunk-1azdancq.js → chunk-ewc9r84m.js} +5 -1
  60. package/build/lib/{chunk-2njv7wz3.js → chunk-hbyxyxdc.js} +4 -0
  61. package/build/lib/{chunk-e8yy2k73.js → chunk-jhhwts6w.js} +28 -2
  62. package/build/lib/{chunk-1jvwqnem.js → chunk-kbxbd1kn.js} +108 -44
  63. package/build/lib/{chunk-7pwa92rn.js → chunk-mph5fvk7.js} +1 -1
  64. package/build/lib/chunk-psc23epq.js +107 -0
  65. package/build/lib/{chunk-acgbyf5h.js → chunk-qxad7qjd.js} +4 -2
  66. package/build/lib/{chunk-3c00n0cy.js → chunk-r0v3vvgm.js} +54 -33
  67. package/build/lib/{chunk-1yct6cra.js → chunk-rt13120a.js} +1 -1
  68. package/build/lib/{chunk-v3dk9j7k.js → chunk-sgya016v.js} +93 -22
  69. package/build/lib/{chunk-1fy8azee.js → chunk-txef92hq.js} +1 -1
  70. package/build/lib/{chunk-2qsq76xq.js → chunk-vcd90qrn.js} +189 -90
  71. package/build/lib/{chunk-7gjdygcx.js → chunk-vwr9w3ab.js} +2 -0
  72. package/build/lib/{chunk-z137x08b.js → chunk-wf6ak0m3.js} +92 -70
  73. package/build/lib/{chunk-xhdpgpkm.js → chunk-xft0ms9j.js} +2 -2
  74. package/build/lib/core/agents/agentRuntime.d.ts +3 -1
  75. package/build/lib/core/agents/defineAgent.d.ts +3 -2
  76. package/build/lib/core/agents/lance.d.ts +153 -0
  77. package/build/lib/core/agents/lanceJudge.d.ts +3 -0
  78. package/build/lib/core/agents/offSecAgent/index.d.ts +3 -1
  79. package/build/lib/core/agents/offSecAgent/index.js +16 -14
  80. package/build/lib/core/agents/offSecAgent/offensiveSecurityAgent.d.ts +4 -0
  81. package/build/lib/core/agents/offSecAgent/prompt.d.ts +3 -1
  82. package/build/lib/core/agents/offSecAgent/subagentSpawner.d.ts +18 -2
  83. package/build/lib/core/agents/offSecAgent/subagentSpawner.js +10 -8
  84. package/build/lib/core/agents/offSecAgent/toolExecutionContext.d.ts +3 -0
  85. package/build/lib/core/agents/offSecAgent/toolLifecycle.d.ts +1 -0
  86. package/build/lib/core/agents/offSecAgent/tools/index.js +10 -6
  87. package/build/lib/core/agents/offSecAgent/tools/listMemories.d.ts +11 -1
  88. package/build/lib/core/agents/offSecAgent/tools/reportError.d.ts +2 -1
  89. package/build/lib/core/agents/offSecAgent/tools/runPentestWorkflow.d.ts +20 -1
  90. package/build/lib/core/agents/offSecAgent/tools/spawnPentestAgent.d.ts +4 -7
  91. package/build/lib/core/agents/offSecAgent/tools/spawnPentestSwarm.d.ts +7 -1
  92. package/build/lib/core/agents/offSecAgent/tools/types.d.ts +6 -3
  93. package/build/lib/core/agents/offSecAgent/tools/whiteboxCandidates.d.ts +2 -2
  94. package/build/lib/core/agents/offSecAgent/types.d.ts +19 -3
  95. package/build/lib/core/agents/specialized/attackSurface/blackboxAgent.js +13 -13
  96. package/build/lib/core/agents/specialized/attackSurface/schemas.js +12 -12
  97. package/build/lib/core/agents/specialized/authenticationAgent/index.js +14 -14
  98. package/build/lib/core/agents/specialized/codeAgent/agent.js +13 -13
  99. package/build/lib/core/agents/specialized/codeAgent/prompts.d.ts +1 -0
  100. package/build/lib/core/agents/specialized/cvssScorer/index.d.ts +2 -1
  101. package/build/lib/core/agents/specialized/findingJudge/agent.js +11 -11
  102. package/build/lib/core/agents/specialized/findingJudge/index.d.ts +2 -1
  103. package/build/lib/core/agents/specialized/findingJudge/index.js +4 -4
  104. package/build/lib/core/agents/specialized/patching/index.js +14 -14
  105. package/build/lib/core/agents/specialized/pentest/agent.d.ts +18 -4
  106. package/build/lib/core/agents/specialized/pentest/agent.js +15 -13
  107. package/build/lib/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.d.ts +2 -1
  108. package/build/lib/core/agents/specialized/whiteboxAttackSurface/index.js +13 -13
  109. package/build/lib/core/ai/ai.d.ts +29 -70
  110. package/build/lib/core/ai/ai.js +5 -3
  111. package/build/lib/core/ai/index.d.ts +1 -0
  112. package/build/lib/core/ai/index.js +16 -4
  113. package/build/lib/core/ai/models/claude-capabilities.d.ts +3 -2
  114. package/build/lib/core/ai/models/concentrate.d.ts +1 -0
  115. package/build/lib/core/ai/models/index.d.ts +1 -1
  116. package/build/lib/core/ai/providers/concentrate.d.ts +1 -0
  117. package/build/lib/core/ai/reasoning.d.ts +44 -0
  118. package/build/lib/core/ai/utils.d.ts +2 -0
  119. package/build/lib/core/ai/utils.js +5 -3
  120. package/build/lib/core/api/index.d.ts +6 -0
  121. package/build/lib/core/api/index.js +32 -23
  122. package/build/lib/core/api/lance.d.ts +6 -0
  123. package/build/lib/core/api/offesecAgent.d.ts +1 -0
  124. package/build/lib/core/api/reasoning.d.ts +12 -0
  125. package/build/lib/core/api/targetedPentest.d.ts +1 -0
  126. package/build/lib/core/api/targetedPentest.js +14 -14
  127. package/build/lib/core/cli/reasoning.d.ts +4 -0
  128. package/build/lib/core/config/config.d.ts +10 -2
  129. package/build/lib/core/config/customProviders.d.ts +11 -0
  130. package/build/lib/core/config/index.js +1 -1
  131. package/build/lib/core/eventBus.js +1 -1
  132. package/build/lib/core/findings/registry.d.ts +3 -1
  133. package/build/lib/core/findings/registry.js +4 -4
  134. package/build/lib/core/observability/index.js +1 -1
  135. package/build/lib/core/observability/telemetry.d.ts +4 -0
  136. package/build/lib/core/session/index.d.ts +47 -0
  137. package/build/lib/core/session/index.js +6 -4
  138. package/build/lib/core/session/persistence.d.ts +7 -0
  139. package/build/lib/core/session/persistence.js +8 -4
  140. package/build/lib/core/tools/backends/index.js +1 -1
  141. package/build/lib/core/tools/backends/resolve.d.ts +1 -0
  142. package/build/lib/core/workflows/fastStrike.d.ts +3 -1
  143. package/build/lib/core/workflows/pentest.d.ts +12 -2
  144. package/build/lib/core/workflows/pentest.js +19 -19
  145. package/build/lib/core/workflows/seams.js +5 -5
  146. package/build/lib/core/workflows/threatModel.d.ts +1 -0
  147. package/build/lib/core/workflows/whiteboxAttackSurface.d.ts +6 -4
  148. package/build/lib/core/workflows/whiteboxAttackSurface.js +15 -15
  149. package/build/lib/index.js +25 -25
  150. package/build/{logs-x62b7hk1.js → logs-bcbjpjp6.js} +16 -13
  151. package/build/{native-rollout-evidence-c9yhfqcs.js → native-rollout-evidence-rph1774m.js} +5 -5
  152. package/build/offesecAgent-81h1p6ee.js +32 -0
  153. package/build/pentest-2q20q6n1.js +23 -0
  154. package/build/{pentests-e0f32aqx.js → pentests-jgw031mc.js} +16 -13
  155. package/build/recordedRunClient-6axj6fba.js +11 -0
  156. package/build/subagentSpawner-a861gvt0.js +12 -0
  157. package/build/targetedPentest-6yxznt7s.js +91 -0
  158. package/build/{targets-xj9g3q0z.js → targets-bjah19dp.js} +16 -13
  159. package/build/threatModel-739kpexm.js +24 -0
  160. package/build/{uninstall-7s784q4a.js → uninstall-yxrn9edt.js} +1 -1
  161. package/build/{upload-4gvhwmvr.js → upload-34ewrjp5.js} +4 -4
  162. package/build/{utils-cjrz64pc.js → utils-fzm66efw.js} +5 -5
  163. package/package.json +2 -1
  164. package/src/cli/issues.test.ts +17 -1
  165. package/src/cli/lance-verifier.test.ts +57 -0
  166. package/src/cli/lance-verifier.ts +73 -0
  167. package/src/cli/lance.test.ts +71 -0
  168. package/src/cli/lance.ts +48 -0
  169. package/src/cli.ts +109 -19
  170. package/src/core/agents/agentRuntime.test.ts +42 -0
  171. package/src/core/agents/agentRuntime.ts +7 -2
  172. package/src/core/agents/defineAgent.ts +3 -1
  173. package/src/core/agents/lance.test.ts +421 -0
  174. package/src/core/agents/lance.ts +701 -0
  175. package/src/core/agents/lanceJudge.test.ts +166 -0
  176. package/src/core/agents/lanceJudge.ts +115 -0
  177. package/src/core/agents/offSecAgent/index.ts +3 -0
  178. package/src/core/agents/offSecAgent/interruptedStepFinalization.test.ts +51 -0
  179. package/src/core/agents/offSecAgent/lance.test.ts +188 -0
  180. package/src/core/agents/offSecAgent/offensiveSecurityAgent.test.ts +92 -5
  181. package/src/core/agents/offSecAgent/offensiveSecurityAgent.ts +244 -41
  182. package/src/core/agents/offSecAgent/prompt.test.ts +46 -0
  183. package/src/core/agents/offSecAgent/prompt.ts +52 -8
  184. package/src/core/agents/offSecAgent/selectiveConstruction.publicPath.test.ts +38 -0
  185. package/src/core/agents/offSecAgent/selectiveConstruction.test.ts +64 -0
  186. package/src/core/agents/offSecAgent/subagentSpawner.hooks.test.ts +11 -3
  187. package/src/core/agents/offSecAgent/subagentSpawner.reasoning.test.ts +167 -0
  188. package/src/core/agents/offSecAgent/subagentSpawner.ts +82 -28
  189. package/src/core/agents/offSecAgent/toolExecutionContext.test.ts +230 -0
  190. package/src/core/agents/offSecAgent/toolExecutionContext.ts +56 -0
  191. package/src/core/agents/offSecAgent/toolLifecycle.ts +2 -0
  192. package/src/core/agents/offSecAgent/tools/delegateAuth.ts +4 -2
  193. package/src/core/agents/offSecAgent/tools/documentFinding.test.ts +9 -4
  194. package/src/core/agents/offSecAgent/tools/documentFinding.ts +10 -9
  195. package/src/core/agents/offSecAgent/tools/listMemories.test.ts +230 -0
  196. package/src/core/agents/offSecAgent/tools/listMemories.ts +73 -6
  197. package/src/core/agents/offSecAgent/tools/reportError.test.ts +6 -0
  198. package/src/core/agents/offSecAgent/tools/reportError.ts +10 -2
  199. package/src/core/agents/offSecAgent/tools/runAttackSurface.ts +7 -4
  200. package/src/core/agents/offSecAgent/tools/runPentestWorkflow.test.ts +57 -0
  201. package/src/core/agents/offSecAgent/tools/runPentestWorkflow.ts +21 -1
  202. package/src/core/agents/offSecAgent/tools/runWhiteboxScan.ts +5 -2
  203. package/src/core/agents/offSecAgent/tools/spawnCodingAgent.ts +50 -67
  204. package/src/core/agents/offSecAgent/tools/spawnPentestAgent.test.ts +79 -3
  205. package/src/core/agents/offSecAgent/tools/spawnPentestAgent.ts +36 -16
  206. package/src/core/agents/offSecAgent/tools/spawnPentestSwarm.ts +28 -5
  207. package/src/core/agents/offSecAgent/tools/threatModelGenerator.ts +4 -3
  208. package/src/core/agents/offSecAgent/tools/types.ts +12 -2
  209. package/src/core/agents/offSecAgent/tools/updateTask.ts +5 -1
  210. package/src/core/agents/offSecAgent/tools/whiteboxCandidates.ts +11 -4
  211. package/src/core/agents/offSecAgent/types.ts +22 -1
  212. package/src/core/agents/specialized/codeAgent/agent.test.ts +7 -0
  213. package/src/core/agents/specialized/codeAgent/agent.ts +9 -2
  214. package/src/core/agents/specialized/codeAgent/prompts.ts +6 -0
  215. package/src/core/agents/specialized/cvssScorer/index.test.ts +2 -1
  216. package/src/core/agents/specialized/cvssScorer/index.ts +3 -0
  217. package/src/core/agents/specialized/findingJudge/index.ts +6 -0
  218. package/src/core/agents/specialized/pentest/agent.test.ts +155 -0
  219. package/src/core/agents/specialized/pentest/agent.ts +181 -52
  220. package/src/core/agents/specialized/pentest/results.test.ts +59 -4
  221. package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.concurrency.test.ts +59 -2
  222. package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.hooks.test.ts +127 -0
  223. package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.ts +74 -68
  224. package/src/core/ai/ai.durability-hooks.test.ts +25 -7
  225. package/src/core/ai/ai.lance.test.ts +581 -0
  226. package/src/core/ai/ai.operator-context.bench.test.ts +1390 -0
  227. package/src/core/ai/ai.recorded-context.test.ts +89 -8
  228. package/src/core/ai/ai.test.ts +81 -59
  229. package/src/core/ai/ai.ts +453 -327
  230. package/src/core/ai/compactionTelemetry.test.ts +69 -17
  231. package/src/core/ai/index.ts +11 -0
  232. package/src/core/ai/models/anthropic.ts +6 -0
  233. package/src/core/ai/models/bedrock.ts +18 -0
  234. package/src/core/ai/models/claude-capabilities.ts +29 -8
  235. package/src/core/ai/models/concentrate.ts +52 -0
  236. package/src/core/ai/models/index.ts +25 -4
  237. package/src/core/ai/models/models.test.ts +179 -7
  238. package/src/core/ai/models/openrouter-reasoning.json +219 -0
  239. package/src/core/ai/models/openrouter.ts +43 -1
  240. package/src/core/ai/providers/anthropic-models.test.ts +60 -1
  241. package/src/core/ai/providers/bedrock-models.test.ts +81 -0
  242. package/src/core/ai/providers/concentrate.test.ts +31 -0
  243. package/src/core/ai/providers/concentrate.ts +11 -0
  244. package/src/core/ai/providers/custom.test.ts +74 -0
  245. package/src/core/ai/providers/custom.ts +3 -1
  246. package/src/core/ai/providers/gateway-models.test.ts +16 -6
  247. package/src/core/ai/providers/pensarFormatters.ts +8 -1
  248. package/src/core/ai/reasoning.test.ts +313 -0
  249. package/src/core/ai/reasoning.ts +500 -0
  250. package/src/core/ai/streamIdleTimeout.test.ts +21 -0
  251. package/src/core/ai/utils.native-rollout-evidence.test.ts +21 -8
  252. package/src/core/ai/utils.ts +269 -163
  253. package/src/core/api/attackSurface.ts +1 -0
  254. package/src/core/api/blackboxPentest.ts +9 -1
  255. package/src/core/api/index.ts +17 -0
  256. package/src/core/api/lance.ts +23 -0
  257. package/src/core/api/offesecAgent.test.ts +51 -0
  258. package/src/core/api/offesecAgent.ts +15 -1
  259. package/src/core/api/reasoning.test.ts +213 -0
  260. package/src/core/api/reasoning.ts +44 -0
  261. package/src/core/api/recordedRun.loop.test.ts +5 -1
  262. package/src/core/api/targetedPentest.test.ts +14 -2
  263. package/src/core/api/targetedPentest.ts +52 -5
  264. package/src/core/cli/reasoning.test.ts +149 -0
  265. package/src/core/cli/reasoning.ts +95 -0
  266. package/src/core/config/config.test.ts +35 -0
  267. package/src/core/config/config.ts +11 -0
  268. package/src/core/config/customProviders.ts +21 -2
  269. package/src/core/eventBus.ts +1 -0
  270. package/src/core/findings/registry.test.ts +9 -1
  271. package/src/core/findings/registry.ts +6 -0
  272. package/src/core/observability/telemetry.test.ts +131 -79
  273. package/src/core/observability/telemetry.ts +5 -0
  274. package/src/core/runtime/sqliteRunObservation.test.ts +1 -1
  275. package/src/core/session/index.ts +38 -0
  276. package/src/core/session/persistence.test.ts +62 -0
  277. package/src/core/session/persistence.ts +56 -0
  278. package/src/core/tools/backends/resolve.ts +7 -0
  279. package/src/core/tools/backends/whiteboxJobs.test.ts +9 -8
  280. package/src/core/workflows/fastStrike.seams.test.ts +149 -0
  281. package/src/core/workflows/fastStrike.test.ts +18 -1
  282. package/src/core/workflows/fastStrike.ts +106 -11
  283. package/src/core/workflows/pentest.test.ts +104 -0
  284. package/src/core/workflows/pentest.ts +128 -7
  285. package/src/core/workflows/seams.test.ts +13 -0
  286. package/src/core/workflows/seams.ts +5 -1
  287. package/src/core/workflows/threatModel.ts +2 -0
  288. package/src/core/workflows/whiteboxAttackSurface.test.ts +74 -2
  289. package/src/core/workflows/whiteboxAttackSurface.ts +102 -73
  290. package/src/tui/command-registry.test.ts +12 -0
  291. package/src/tui/command-registry.ts +10 -0
  292. package/src/tui/components/chat/config-view.tsx +6 -10
  293. package/src/tui/components/chat/home-view.tsx +2 -2
  294. package/src/tui/components/chat/input-area.tsx +2 -2
  295. package/src/tui/components/chat/loading-indicator.tsx +20 -4
  296. package/src/tui/components/chat/message-list.tsx +1 -0
  297. package/src/tui/components/commands/__tests__/advanced-settings-lifecycle.tsx +164 -0
  298. package/src/tui/components/commands/advanced-settings.test.ts +34 -0
  299. package/src/tui/components/commands/advanced-settings.tsx +220 -31
  300. package/src/tui/components/commands/models-display.tsx +7 -1
  301. package/src/tui/components/model-picker/ModelPicker.tsx +65 -103
  302. package/src/tui/components/model-picker/ModelPickerDialog.tsx +8 -14
  303. package/src/tui/components/model-picker/__tests__/reasoning-control.tsx +162 -0
  304. package/src/tui/components/model-picker/reasoning-control.test.ts +15 -0
  305. package/src/tui/components/operator-dashboard/index.tsx +166 -50
  306. package/src/tui/components/operator-dashboard/logic.test.ts +144 -2
  307. package/src/tui/components/operator-dashboard/logic.ts +54 -3
  308. package/src/tui/components/operator-dashboard/run-events.test.ts +3 -1
  309. package/src/tui/components/shared/__tests__/tool-renderer-lifecycle.tsx +18 -0
  310. package/src/tui/components/shared/action-label.test.ts +9 -0
  311. package/src/tui/components/shared/compacting-icon.tsx +18 -0
  312. package/src/tui/components/shared/index.ts +1 -0
  313. package/src/tui/components/shared/tool-registry.ts +1 -0
  314. package/src/tui/components/shared/tool-renderer.tsx +21 -7
  315. package/src/tui/context/agent.tsx +32 -79
  316. package/src/tui/context/route.tsx +2 -0
  317. package/src/tui/index.tsx +1 -0
  318. package/src/tui/keybindings/registry.ts +2 -0
  319. package/build/agent-6w7egcf1.js +0 -13
  320. package/build/agent-9zx75feh.js +0 -13
  321. package/build/blackboxAgent-s56sgx9b.js +0 -13
  322. package/build/cli-g06s7edq.js +0 -11
  323. package/build/lib/chunk-z96pbjhd.js +0 -56
  324. package/build/offesecAgent-5whnpeta.js +0 -22
  325. package/build/pentest-e61hc38s.js +0 -22
  326. package/build/recordedRunClient-dp032c32.js +0 -11
  327. package/build/targetedPentest-rcbs4nvp.js +0 -57
  328. package/build/threatModel-jkxfa40s.js +0 -21
@@ -10,13 +10,11 @@ import {
10
10
  } from "react";
11
11
  import {
12
12
  type AIModelProvider,
13
- getOpenAIReasoningEfforts,
13
+ getReasoningCapabilities,
14
+ getReasoningChoices,
14
15
  getRecentModels,
15
16
  type ModelInfo,
16
- modelRequiresThinking,
17
- modelSupportsOpenAIReasoning,
18
- modelSupportsThinking,
19
- type OpenAIReasoningEffort,
17
+ type ReasoningSetting,
20
18
  } from "../../../core/ai";
21
19
  import type { Config } from "../../../core/config/config";
22
20
  import { getAvailableModels } from "../../../core/providers/utils";
@@ -84,8 +82,7 @@ type NavigationItem =
84
82
  | { type: "model"; model: ModelInfo }
85
83
  | { type: "recent-model"; model: ModelInfo }
86
84
  | { type: "local-input"; field: LocalInputField }
87
- | { type: "reasoning" }
88
- | { type: "openai-reasoning" };
85
+ | { type: "reasoning" };
89
86
 
90
87
  type LocalInputField = "url" | "model";
91
88
 
@@ -94,7 +91,6 @@ function getNavItemId(item: NavigationItem): string {
94
91
  if (item.type === "model") return `model-${item.model.id}`;
95
92
  if (item.type === "recent-model") return `recent-model-${item.model.id}`;
96
93
  if (item.type === "reasoning") return "reasoning-toggle";
97
- if (item.type === "openai-reasoning") return "openai-reasoning-effort";
98
94
  return `local-input-${item.field}`;
99
95
  }
100
96
 
@@ -107,10 +103,8 @@ export interface ModelPickerProps {
107
103
  onConfigUpdate?: (update: Partial<Config>) => Promise<void>;
108
104
  focused?: boolean;
109
105
  isModelUserSelected?: boolean;
110
- reasoningEnabled?: boolean;
111
- onReasoningToggle?: (enabled: boolean) => void;
112
- openAIReasoningEffort?: OpenAIReasoningEffort;
113
- onOpenAIReasoningEffortChange?: (effort: OpenAIReasoningEffort) => void;
106
+ reasoning?: ReasoningSetting;
107
+ onReasoningChange?: (setting: ReasoningSetting) => void;
114
108
  }
115
109
 
116
110
  export function ModelPicker({
@@ -122,12 +116,19 @@ export function ModelPicker({
122
116
  onConfigUpdate,
123
117
  focused = true,
124
118
  isModelUserSelected = false,
125
- reasoningEnabled = false,
126
- onReasoningToggle,
127
- openAIReasoningEffort = "medium",
128
- onOpenAIReasoningEffortChange,
119
+ reasoning = "default",
120
+ onReasoningChange,
129
121
  }: ModelPickerProps) {
130
122
  const { colors } = useTheme();
123
+ const reasoningCapabilities = useMemo(
124
+ () => getReasoningCapabilities(selectedModel.id, config?.customProviders),
125
+ [selectedModel.id, config?.customProviders],
126
+ );
127
+ const reasoningChoices = useMemo(
128
+ () => getReasoningChoices(reasoningCapabilities),
129
+ [reasoningCapabilities],
130
+ );
131
+ const reasoningAvailable = reasoningChoices.length > 0;
131
132
  const scrollBoxRef = useRef<ScrollBoxRenderable | null>(null);
132
133
  const [availableModels, setAvailableModels] = useState<ModelInfo[]>([]);
133
134
  const [searchQuery, setSearchQuery] = useState("");
@@ -256,21 +257,8 @@ export function ModelPicker({
256
257
  }
257
258
  }
258
259
  }
259
- if (
260
- !isSearching &&
261
- onReasoningToggle &&
262
- modelSupportsThinking(selectedModel.id) &&
263
- !modelRequiresThinking(selectedModel.id)
264
- ) {
260
+ if (onReasoningChange && reasoningAvailable)
265
261
  items.push({ type: "reasoning" });
266
- }
267
- if (
268
- !isSearching &&
269
- onOpenAIReasoningEffortChange &&
270
- modelSupportsOpenAIReasoning(selectedModel.id)
271
- ) {
272
- items.push({ type: "openai-reasoning" });
273
- }
274
262
  return items;
275
263
  }, [
276
264
  groupedModels,
@@ -278,9 +266,8 @@ export function ModelPicker({
278
266
  recentModels,
279
267
  isSearching,
280
268
  expandedProviders,
281
- onReasoningToggle,
282
- onOpenAIReasoningEffortChange,
283
- selectedModel.id,
269
+ onReasoningChange,
270
+ reasoningAvailable,
284
271
  ]);
285
272
 
286
273
  useEffect(() => {
@@ -329,31 +316,21 @@ export function ModelPicker({
329
316
  commitLocalConfig(localUrl, localModelName);
330
317
  }, [localUrl, localModelName, commitLocalConfig]);
331
318
 
332
- const thinkingSupported = modelSupportsThinking(selectedModel.id);
333
- const thinkingRequired = modelRequiresThinking(selectedModel.id);
334
- const openAIReasoningSupported = modelSupportsOpenAIReasoning(
335
- selectedModel.id,
336
- );
337
- const openAIReasoningEfforts = getOpenAIReasoningEfforts(selectedModel.id);
338
319
  const isReasoningFocused =
339
320
  navigationItems[focusedIndex]?.type === "reasoning";
340
- const isOpenAIReasoningFocused =
341
- navigationItems[focusedIndex]?.type === "openai-reasoning";
342
- const cycleOpenAIReasoningEffort = useCallback(() => {
343
- if (!onOpenAIReasoningEffortChange || openAIReasoningEfforts.length === 0) {
344
- return;
345
- }
346
- const currentIndex = openAIReasoningEfforts.indexOf(openAIReasoningEffort);
347
- const nextEffort =
348
- openAIReasoningEfforts[
349
- (Math.max(currentIndex, 0) + 1) % openAIReasoningEfforts.length
350
- ]!;
351
- onOpenAIReasoningEffortChange(nextEffort);
352
- }, [
353
- onOpenAIReasoningEffortChange,
354
- openAIReasoningEffort,
355
- openAIReasoningEfforts,
356
- ]);
321
+ const cycleReasoning = useCallback(
322
+ (direction: -1 | 1 = 1) => {
323
+ if (!onReasoningChange || !reasoningAvailable) return;
324
+ const index = Math.max(0, reasoningChoices.indexOf(reasoning));
325
+ const next =
326
+ reasoningChoices[
327
+ (index + direction + reasoningChoices.length) %
328
+ reasoningChoices.length
329
+ ];
330
+ if (next) onReasoningChange(next);
331
+ },
332
+ [onReasoningChange, reasoningAvailable, reasoningChoices, reasoning],
333
+ );
357
334
 
358
335
  // Handle keyboard navigation (most keys disabled while editing local input)
359
336
  const handleKeyboard = useCallback(
@@ -402,21 +379,19 @@ export function ModelPicker({
402
379
  const currentItem = navigationItems[focusedIndex];
403
380
 
404
381
  if (
405
- key.name === "space" &&
406
- currentItem?.type === "reasoning" &&
407
- onReasoningToggle &&
408
- thinkingSupported
382
+ (key.name === "left" || key.name === "right") &&
383
+ onReasoningChange &&
384
+ reasoningAvailable &&
385
+ !key.ctrl &&
386
+ !key.meta &&
387
+ !key.shift
409
388
  ) {
410
- onReasoningToggle(!reasoningEnabled);
389
+ cycleReasoning(key.name === "left" ? -1 : 1);
411
390
  return true;
412
391
  }
413
- if (
414
- key.name === "space" &&
415
- currentItem?.type === "openai-reasoning" &&
416
- onOpenAIReasoningEffortChange &&
417
- openAIReasoningSupported
418
- ) {
419
- cycleOpenAIReasoningEffort();
392
+
393
+ if (key.name === "space" && currentItem?.type === "reasoning") {
394
+ cycleReasoning();
420
395
  return true;
421
396
  }
422
397
 
@@ -430,9 +405,7 @@ export function ModelPicker({
430
405
  }
431
406
 
432
407
  if (currentItem.type === "reasoning") {
433
- onReasoningToggle?.(!reasoningEnabled);
434
- } else if (currentItem.type === "openai-reasoning") {
435
- cycleOpenAIReasoningEffort();
408
+ cycleReasoning();
436
409
  } else if (currentItem.type === "provider") {
437
410
  const targetProvider = currentItem.provider;
438
411
  setExpandedProviders((prev) => {
@@ -448,8 +421,8 @@ export function ModelPicker({
448
421
  currentItem.type === "model" ||
449
422
  currentItem.type === "recent-model"
450
423
  ) {
451
- onSelectModel(currentItem.model);
452
- onConfirm?.();
424
+ if (currentItem.model.id === selectedModel.id) onConfirm?.();
425
+ else onSelectModel(currentItem.model);
453
426
  }
454
427
  return true;
455
428
  }
@@ -461,8 +434,7 @@ export function ModelPicker({
461
434
  if (
462
435
  !currentItem ||
463
436
  currentItem.type === "recent-model" ||
464
- currentItem.type === "reasoning" ||
465
- currentItem.type === "openai-reasoning"
437
+ currentItem.type === "reasoning"
466
438
  ) {
467
439
  return false;
468
440
  }
@@ -502,15 +474,13 @@ export function ModelPicker({
502
474
  navigationItems,
503
475
  focusedIndex,
504
476
  onSelectModel,
477
+ selectedModel.id,
505
478
  onConfirm,
506
479
  onCancel,
507
480
  searchQuery,
508
- reasoningEnabled,
509
- onReasoningToggle,
510
- thinkingSupported,
511
- onOpenAIReasoningEffortChange,
512
- openAIReasoningSupported,
513
- cycleOpenAIReasoningEffort,
481
+ onReasoningChange,
482
+ reasoningAvailable,
483
+ cycleReasoning,
514
484
  isSearching,
515
485
  ],
516
486
  );
@@ -862,36 +832,28 @@ export function ModelPicker({
862
832
  })}
863
833
  </scrollbox>
864
834
 
865
- {/* Reasoning toggle */}
866
- {!isSearching && onReasoningToggle && thinkingSupported && (
835
+ {onReasoningChange && reasoningAvailable && (
867
836
  <box flexShrink={0} paddingTop={1}>
868
837
  <PickerRow id="reasoning-toggle">
869
838
  <text fg={isReasoningFocused ? colors.primary : colors.text}>
870
- {reasoningEnabled || thinkingRequired ? "[x]" : "[ ]"} Extended
871
- Thinking
872
- {thinkingRequired ? " (Always on)" : " (Experimental)"}
839
+ {isReasoningFocused ? "❯ " : " "}
840
+ {reasoningCapabilities.control === "effort"
841
+ ? "Effort"
842
+ : "Extended Thinking"}
843
+ :{" "}
844
+ {reasoning === "default" &&
845
+ reasoningCapabilities.defaultEnabled === false
846
+ ? "default (off)"
847
+ : reasoning === "default" && reasoningCapabilities.defaultEffort
848
+ ? `default (${reasoningCapabilities.defaultEffort})`
849
+ : reasoning === "xhigh"
850
+ ? "extra high"
851
+ : reasoning}
873
852
  </text>
874
853
  </PickerRow>
875
854
  </box>
876
855
  )}
877
856
 
878
- {!isSearching &&
879
- onOpenAIReasoningEffortChange &&
880
- openAIReasoningSupported && (
881
- <box flexShrink={0} paddingTop={1}>
882
- <PickerRow id="openai-reasoning-effort">
883
- <text
884
- fg={isOpenAIReasoningFocused ? colors.primary : colors.text}
885
- >
886
- Reasoning Effort:{" "}
887
- {openAIReasoningEffort === "xhigh"
888
- ? "extra high"
889
- : openAIReasoningEffort}
890
- </text>
891
- </PickerRow>
892
- </box>
893
- )}
894
-
895
857
  {/* Help text for inline editing only — general controls are in ModelPickerDialog */}
896
858
  {editingLocalField && (
897
859
  <box flexShrink={0}>
@@ -20,18 +20,14 @@ interface ModelPickerDialogProps {
20
20
  export default function ModelPickerDialog({ onClose }: ModelPickerDialogProps) {
21
21
  const { colors } = useTheme();
22
22
  const config = useConfig();
23
- const {
24
- model,
25
- setModel,
26
- isModelUserSelected,
27
- reasoningEnabled,
28
- setReasoningEnabled,
29
- openAIReasoningEffort,
30
- setOpenAIReasoningEffort,
31
- } = useAgent();
23
+ const { model, setModel, isModelUserSelected, reasoning, setReasoning } =
24
+ useAgent();
32
25
 
33
26
  const footerActions: FooterAction[] = [
34
- { key: "Enter", label: "confirm", variant: "primary" },
27
+ { key: "Enter", label: "select / confirm", variant: "primary" },
28
+ ...(reasoning !== undefined
29
+ ? [{ key: "←/→", label: "change reasoning" }]
30
+ : []),
35
31
  ];
36
32
 
37
33
  const title = (
@@ -63,10 +59,8 @@ export default function ModelPickerDialog({ onClose }: ModelPickerDialogProps) {
63
59
  onConfigUpdate={config.update}
64
60
  focused={true}
65
61
  isModelUserSelected={isModelUserSelected}
66
- reasoningEnabled={reasoningEnabled}
67
- onReasoningToggle={setReasoningEnabled}
68
- openAIReasoningEffort={openAIReasoningEffort}
69
- onOpenAIReasoningEffortChange={setOpenAIReasoningEffort}
62
+ reasoning={reasoning}
63
+ onReasoningChange={setReasoning}
70
64
  />
71
65
  </box>
72
66
  </DialogLayout>
@@ -0,0 +1,162 @@
1
+ import { mock } from "bun:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdirSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { testRender } from "@opentui/react/test-utils";
6
+ import { act, useState } from "react";
7
+ import { getModelInfo, type ReasoningSetting } from "../../../../core/ai";
8
+ import type { Config } from "../../../../core/config/config";
9
+
10
+ const scenario = process.argv[2];
11
+ const selected = getModelInfo(
12
+ scenario === "toggle"
13
+ ? "claude-sonnet-4-5"
14
+ : scenario === "unknown"
15
+ ? "gpt-4o"
16
+ : "claude-opus-5-5",
17
+ );
18
+ const config = {
19
+ anthropicAPIKey: "test",
20
+ openAiAPIKey: "test",
21
+ responsibleUseAccepted: true,
22
+ } as Config;
23
+ mock.module(
24
+ join(import.meta.dirname, "../../../../core/providers/utils.ts"),
25
+ () => ({ getAvailableModels: () => [selected] }),
26
+ );
27
+ mock.module(join(import.meta.dirname, "../../../context/config.tsx"), () => ({
28
+ useConfig: () => ({ data: config, update: async () => {} }),
29
+ }));
30
+ const { ModelPicker } = await import("../ModelPicker");
31
+ const { ThemeProvider, registerBuiltinThemes } = await import("../../../theme");
32
+ registerBuiltinThemes();
33
+ const changed: ReasoningSetting[] = [];
34
+ function Harness() {
35
+ const [reasoning, setReasoning] = useState<ReasoningSetting>("default");
36
+ return (
37
+ <ThemeProvider>
38
+ <ModelPicker
39
+ config={config}
40
+ selectedModel={selected}
41
+ onSelectModel={() => {}}
42
+ reasoning={reasoning}
43
+ onReasoningChange={(next) => {
44
+ changed.push(next);
45
+ setReasoning(next);
46
+ }}
47
+ />
48
+ </ThemeProvider>
49
+ );
50
+ }
51
+ const setup = await testRender(<Harness />, {
52
+ width: scenario === "compact" ? 60 : 100,
53
+ height: scenario === "compact" ? 18 : 28,
54
+ exitOnCtrlC: false,
55
+ });
56
+ async function frame() {
57
+ for (let i = 0; i < 3; i++) await act(async () => {});
58
+ await act(setup.renderOnce);
59
+ return setup.captureCharFrame();
60
+ }
61
+ try {
62
+ const initial = await frame();
63
+ assert.ok(initial.includes(selected.name), initial);
64
+ if (scenario === "unknown") {
65
+ assert.ok(!initial.includes("Effort:"), initial);
66
+ assert.ok(!initial.includes("Extended Thinking:"), initial);
67
+ await act(async () => setup.mockInput.pressKey("ARROW_LEFT"));
68
+ await frame();
69
+ await act(async () => setup.mockInput.pressKey("ARROW_RIGHT"));
70
+ await frame();
71
+ assert.deepEqual(changed, []);
72
+ } else {
73
+ assert.ok(
74
+ initial.includes(
75
+ scenario === "toggle"
76
+ ? "Extended Thinking: default"
77
+ : "Effort: default",
78
+ ),
79
+ initial,
80
+ );
81
+ assert.ok(
82
+ !initial.includes(
83
+ scenario === "toggle" ? "Effort:" : "Extended Thinking:",
84
+ ),
85
+ initial,
86
+ );
87
+ const firstChoice = scenario === "toggle" ? "enabled" : "low";
88
+ const lastChoice = scenario === "toggle" ? "disabled" : "max";
89
+ await act(async () => setup.mockInput.pressKey("ARROW_RIGHT"));
90
+ const next = await frame();
91
+ assert.deepEqual(changed, [firstChoice]);
92
+ assert.ok(
93
+ next.includes(
94
+ scenario === "toggle" ? "Extended Thinking: enabled" : "Effort: low",
95
+ ),
96
+ next,
97
+ );
98
+ await act(async () => setup.mockInput.pressKey("ARROW_LEFT"));
99
+ await frame();
100
+ await act(async () => setup.mockInput.pressKey("ARROW_LEFT"));
101
+ await frame();
102
+ await act(async () => setup.mockInput.pressKey("ARROW_RIGHT"));
103
+ await frame();
104
+ assert.deepEqual(changed, [firstChoice, "default", lastChoice, "default"]);
105
+
106
+ await act(async () => setup.mockInput.pressKey("ARROW_UP"));
107
+ await frame();
108
+ await act(async () => setup.mockInput.pressKey("RETURN"));
109
+ const collapsed = await frame();
110
+ assert.ok(!collapsed.includes(selected.name), collapsed);
111
+ await act(async () => setup.mockInput.pressKey("RETURN"));
112
+ const expanded = await frame();
113
+ assert.ok(expanded.includes(selected.name), expanded);
114
+
115
+ await act(async () => setup.mockInput.typeText("claude "));
116
+ const search = await frame();
117
+ assert.ok(search.includes("Search claude"), search);
118
+ assert.ok(search.includes("Results (1)"), search);
119
+ await act(async () => setup.mockInput.pressKey("ARROW_LEFT"));
120
+ await frame();
121
+ await act(async () => setup.mockInput.typeText("s"));
122
+ const afterArrows = await frame();
123
+ assert.ok(afterArrows.includes("Search claude s"), afterArrows);
124
+ await act(async () => setup.mockInput.pressKey("ARROW_RIGHT"));
125
+ await frame();
126
+ assert.deepEqual(changed, [
127
+ firstChoice,
128
+ "default",
129
+ lastChoice,
130
+ "default",
131
+ lastChoice,
132
+ "default",
133
+ ]);
134
+
135
+ await act(async () => setup.mockInput.pressKey("ARROW_UP"));
136
+ const focused = await frame();
137
+ assert.ok(
138
+ focused.includes(
139
+ scenario === "toggle" ? "❯ Extended Thinking:" : "❯ Effort:",
140
+ ),
141
+ focused,
142
+ );
143
+ await act(async () => setup.mockInput.pressKey(" "));
144
+ const updated = await frame();
145
+ assert.equal(changed.at(-1), firstChoice);
146
+ assert.ok(
147
+ updated.includes(
148
+ scenario === "toggle" ? "Extended Thinking: enabled" : "Effort: low",
149
+ ),
150
+ updated,
151
+ );
152
+ }
153
+ if (process.env.APEX_TEST_FRAME_DIR) {
154
+ mkdirSync(process.env.APEX_TEST_FRAME_DIR, { recursive: true });
155
+ writeFileSync(
156
+ join(process.env.APEX_TEST_FRAME_DIR, `${scenario}.txt`),
157
+ await frame(),
158
+ );
159
+ }
160
+ } finally {
161
+ await act(async () => setup.renderer.destroy());
162
+ }
@@ -0,0 +1,15 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { join } from "node:path";
3
+ import { expect, it } from "vitest";
4
+
5
+ for (const scenario of ["effort", "toggle", "unknown", "compact"] as const) {
6
+ it(`renders and operates the ${scenario} reasoning control`, () => {
7
+ const result = spawnSync(
8
+ "bun",
9
+ [join(import.meta.dirname, "__tests__/reasoning-control.tsx"), scenario],
10
+ { encoding: "utf8", timeout: 30000 },
11
+ );
12
+ expect(result.error).toBeUndefined();
13
+ expect(result.status, result.stderr).toBe(0);
14
+ });
15
+ }