@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
@@ -4,6 +4,7 @@ import { z } from "zod";
4
4
  import { AttackPathSchema } from "../../../../lib/attack-path/types";
5
5
  import { hasCanonicalName } from "../../../../lib/cwe/types";
6
6
  import type { EvidenceFileEntry } from "../../../../lib/evidence/types";
7
+ import { getModelSettings } from "../../../ai";
7
8
  import { createLogger } from "../../../logger/structured";
8
9
  import { collectCommand } from "../../../tools/backends/collectCommand";
9
10
  import {
@@ -11,7 +12,7 @@ import {
11
12
  resolveArtifactFs,
12
13
  resolveBackends,
13
14
  resolveScriptRunner,
14
- SANDBOX_ARTIFACTS_ROOT,
15
+ sandboxArtifactsRoot,
15
16
  } from "../../../tools/backends/resolve";
16
17
  import type { WriteOpts, WriteResult } from "../../../tools/backends/types";
17
18
  import { scopedLogger } from "../../../util/lazyLogger";
@@ -200,18 +201,20 @@ function artifactPath(ctx: ToolContext, ...parts: string[]): string {
200
201
  }
201
202
 
202
203
  function artifactsRoot(ctx: ToolContext): string {
203
- return isRealSandbox(ctx) ? SANDBOX_ARTIFACTS_ROOT : ctx.session.rootPath;
204
+ return isRealSandbox(ctx)
205
+ ? sandboxArtifactsRoot(ctx.session)
206
+ : ctx.session.rootPath;
204
207
  }
205
208
 
206
209
  function pocsRoot(ctx: ToolContext): string {
207
210
  return isRealSandbox(ctx)
208
- ? `${SANDBOX_ARTIFACTS_ROOT}/pocs`
211
+ ? `${sandboxArtifactsRoot(ctx.session)}/pocs`
209
212
  : ctx.session.pocsPath;
210
213
  }
211
214
 
212
215
  function findingsRoot(ctx: ToolContext): string {
213
216
  return isRealSandbox(ctx)
214
- ? `${SANDBOX_ARTIFACTS_ROOT}/findings`
217
+ ? `${sandboxArtifactsRoot(ctx.session)}/findings`
215
218
  : ctx.session.findingsPath;
216
219
  }
217
220
 
@@ -497,16 +500,13 @@ CRITICAL RULES — READ BEFORE CALLING:
497
500
  spec: { type: "finding-judge", judgeInput, target: ctx.target },
498
501
  runtime: {
499
502
  session: ctx.session,
500
- model: ctx.model!,
501
- authConfig: ctx.authConfig,
503
+ ...getModelSettings(ctx),
502
504
  abortSignal: ctx.abortSignal,
503
505
  sandbox: ctx.sandbox,
504
506
  backends: ctx.backends,
505
- enableThinking: ctx.enableThinking,
506
- thinkingEffort: ctx.thinkingEffort,
507
- openAIReasoningEffort: ctx.openAIReasoningEffort,
508
507
  languageModelMiddleware: ctx.languageModelMiddleware,
509
508
  usageRecorder: ctx.usageRecorder,
509
+ toolExecutionContext: ctx.toolExecutionContext,
510
510
  streamIdFactory: ctx.streamIdFactory,
511
511
  },
512
512
  parentBus: ctx.eventBus,
@@ -626,6 +626,7 @@ CRITICAL RULES — READ BEFORE CALLING:
626
626
  ctx.abortSignal,
627
627
  ctx.session.id,
628
628
  {
629
+ reasoning: ctx.reasoning,
629
630
  languageModelMiddleware: ctx.languageModelMiddleware,
630
631
  usageRecorder: ctx.usageRecorder,
631
632
  },
@@ -0,0 +1,230 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import {
3
+ type Memory,
4
+ type MemoryBackend,
5
+ type MemorySummary,
6
+ withMemoryBackend,
7
+ } from "../../../memory";
8
+ import { type GetMemoryResult, getMemory } from "./getMemory";
9
+ import { type ListMemoriesResult, listMemories } from "./listMemories";
10
+ import type { ToolContext } from "./types";
11
+
12
+ const ctx = { session: { id: "synthetic" } } as ToolContext;
13
+ const options = { toolCallId: "synthetic-call", messages: [] };
14
+ const list = listMemories(ctx).execute;
15
+ const get = getMemory(ctx).execute;
16
+ if (!list || !get) throw new Error("Memory tools must be executable");
17
+
18
+ describe("list_memories pagination", () => {
19
+ it("continues from the first unreturned summary when the character budget fills", async () => {
20
+ const summaries: MemorySummary[] = Array.from({ length: 30 }, (_, i) => ({
21
+ id: `large-${i}`,
22
+ category: "general",
23
+ title: "t".repeat(300),
24
+ tags: Array.from({ length: 8 }, () => "tag".repeat(40)),
25
+ createdAt: new Date(Date.UTC(2026, 0, 1) - i * 1_000).toISOString(),
26
+ }));
27
+ const backend: MemoryBackend = {
28
+ add: vi.fn(async () => {
29
+ throw new Error("unexpected add");
30
+ }),
31
+ list: vi.fn(async () => summaries),
32
+ get: vi.fn(async () => null),
33
+ };
34
+ await withMemoryBackend(backend, async () => {
35
+ const first = (await list(
36
+ { toolCallDescription: "List large synthetic memories" },
37
+ options,
38
+ )) as ListMemoriesResult;
39
+ expect(first.memories.length).toBeLessThan(20);
40
+ expect(JSON.stringify(first).length).toBeLessThan(12_500);
41
+ expect(first.nextCursor).toEqual({
42
+ id: first.memories.at(-1)?.id,
43
+ category: first.memories.at(-1)?.category,
44
+ createdAt: first.memories.at(-1)?.createdAt,
45
+ });
46
+ const second = (await list(
47
+ {
48
+ cursor: first.nextCursor ?? undefined,
49
+ toolCallDescription: "Continue large synthetic memories",
50
+ },
51
+ options,
52
+ )) as ListMemoriesResult;
53
+ expect(second.memories[0]?.id).toBe(summaries[first.memories.length]?.id);
54
+ });
55
+ });
56
+
57
+ it("continues past the last returned memory when a newer one is added", async () => {
58
+ const original: MemorySummary[] = Array.from({ length: 30 }, (_, i) => ({
59
+ id: `memory-${i}`,
60
+ category: "general",
61
+ title: `Memory ${i}`,
62
+ tags: [],
63
+ createdAt: new Date(Date.UTC(2026, 0, 1) - i * 1_000).toISOString(),
64
+ }));
65
+ let summaries = original;
66
+ const backend: MemoryBackend = {
67
+ add: vi.fn(async () => {
68
+ throw new Error("unexpected add");
69
+ }),
70
+ list: vi.fn(async () => summaries),
71
+ get: vi.fn(async () => null),
72
+ };
73
+ await withMemoryBackend(backend, async () => {
74
+ const first = (await list(
75
+ { toolCallDescription: "List first memory page" },
76
+ options,
77
+ )) as ListMemoriesResult;
78
+ summaries = [
79
+ {
80
+ ...original[0],
81
+ id: "memory-new",
82
+ createdAt: new Date(Date.UTC(2026, 0, 2)).toISOString(),
83
+ },
84
+ ...original,
85
+ ];
86
+ const second = (await list(
87
+ {
88
+ cursor: first.nextCursor ?? undefined,
89
+ toolCallDescription: "Continue after an insertion",
90
+ },
91
+ options,
92
+ )) as ListMemoriesResult;
93
+
94
+ expect(first.memories.map((memory) => memory.id)).toEqual(
95
+ original.slice(0, 20).map((memory) => memory.id),
96
+ );
97
+ expect(second.memories.map((memory) => memory.id)).toEqual(
98
+ original.slice(20).map((memory) => memory.id),
99
+ );
100
+ expect(second.count).toBe(31);
101
+ expect(second.nextCursor).toBeNull();
102
+ });
103
+ });
104
+
105
+ it("uses stable tie ordering when the cursor memory is deleted", async () => {
106
+ const original: MemorySummary[] = Array.from({ length: 25 }, (_, i) => ({
107
+ id: `memory-${i.toString().padStart(2, "0")}`,
108
+ category: "general",
109
+ title: `Memory ${i}`,
110
+ tags: [],
111
+ createdAt: "2026-01-01T00:00:00.000Z",
112
+ }));
113
+ let summaries = [...original].reverse();
114
+ const backend: MemoryBackend = {
115
+ add: vi.fn(async () => {
116
+ throw new Error("unexpected add");
117
+ }),
118
+ list: vi.fn(async () => summaries),
119
+ get: vi.fn(async () => null),
120
+ };
121
+ await withMemoryBackend(backend, async () => {
122
+ const first = (await list(
123
+ { toolCallDescription: "List tied memories" },
124
+ options,
125
+ )) as ListMemoriesResult;
126
+ expect(first.memories.map((memory) => memory.id)).toEqual(
127
+ original.slice(0, 20).map((memory) => memory.id),
128
+ );
129
+ summaries = summaries.filter(
130
+ (memory) => memory.id !== first.nextCursor?.id,
131
+ );
132
+ const second = (await list(
133
+ {
134
+ cursor: first.nextCursor ?? undefined,
135
+ toolCallDescription: "Continue after anchor deletion",
136
+ },
137
+ options,
138
+ )) as ListMemoriesResult;
139
+ expect(second.memories.map((memory) => memory.id)).toEqual(
140
+ original.slice(20).map((memory) => memory.id),
141
+ );
142
+ expect(second.nextCursor).toBeNull();
143
+ });
144
+ });
145
+
146
+ it("bounds each result while keeping every older memory discoverable", async () => {
147
+ const summaries: MemorySummary[] = Array.from({ length: 200 }, (_, i) => ({
148
+ id: `memory-${i}`,
149
+ category: "general",
150
+ title: `Memory ${i} ${"x".repeat(480)}`,
151
+ tags: ["synthetic", "history"],
152
+ createdAt: new Date(Date.UTC(2026, 0, 1) - i * 1_000).toISOString(),
153
+ }));
154
+ const backend: MemoryBackend = {
155
+ add: vi.fn(async () => {
156
+ throw new Error("unexpected add");
157
+ }),
158
+ list: vi.fn(async () =>
159
+ summaries.map((summary, index) =>
160
+ index === 0 ? { ...summary, content: "full-body-marker" } : summary,
161
+ ),
162
+ ),
163
+ get: vi.fn(async (_category, id) => {
164
+ const summary = summaries.find((item) => item.id === id);
165
+ return summary
166
+ ? ({
167
+ ...summary,
168
+ content: `Full ${id}`,
169
+ updatedAt: summary.createdAt,
170
+ } satisfies Memory)
171
+ : null;
172
+ }),
173
+ };
174
+
175
+ await withMemoryBackend(backend, async () => {
176
+ const allIds: string[] = [];
177
+ let cursor: ListMemoriesResult["nextCursor"] = null;
178
+ let firstResultChars = 0;
179
+ let pages = 0;
180
+ for (;;) {
181
+ const result = (await list(
182
+ {
183
+ cursor: cursor ?? undefined,
184
+ toolCallDescription: "List synthetic memories",
185
+ },
186
+ options,
187
+ )) as ListMemoriesResult;
188
+ expect(result.success).toBe(true);
189
+ expect(result.count).toBe(200);
190
+ expect(result.memories.length).toBeGreaterThan(0);
191
+ expect(result.memories.length).toBeLessThanOrEqual(20);
192
+ expect(JSON.stringify(result).length).toBeLessThan(12_500);
193
+ if (pages === 0) {
194
+ firstResultChars = JSON.stringify(result).length;
195
+ expect(JSON.stringify(result)).not.toContain("full-body-marker");
196
+ expect(result.memories[0]?.truncated).toBe(true);
197
+ expect(result.memories[0]?.title.length).toBe(160);
198
+ }
199
+ allIds.push(...result.memories.map((memory) => memory.id));
200
+ pages++;
201
+ if (result.nextCursor === null) break;
202
+ expect(result.nextCursor.id).toBe(result.memories.at(-1)?.id);
203
+ cursor = result.nextCursor;
204
+ }
205
+
206
+ expect(allIds).toEqual(summaries.map((memory) => memory.id));
207
+ const oldest = (await get(
208
+ {
209
+ category: "general",
210
+ id: allIds.at(-1) ?? "",
211
+ toolCallDescription: "Read oldest synthetic memory",
212
+ },
213
+ options,
214
+ )) as GetMemoryResult;
215
+ expect(oldest.memory?.content).toBe("Full memory-199");
216
+ if (process.env.APEX_CONTEXT_BENCH === "1") {
217
+ const previousChars = JSON.stringify({
218
+ success: true,
219
+ error: "",
220
+ memories: summaries,
221
+ count: summaries.length,
222
+ }).length;
223
+ console.log(
224
+ "MEMORY_LIST_BENCH",
225
+ JSON.stringify({ previousChars, firstResultChars, pages }),
226
+ );
227
+ }
228
+ });
229
+ });
230
+ });
@@ -7,6 +7,9 @@ import {
7
7
  } from "../../../memory";
8
8
  import type { ToolContext } from "./types";
9
9
 
10
+ const PAGE_SIZE = 20;
11
+ const PAGE_CHAR_BUDGET = 12_000;
12
+
10
13
  const listMemoriesInputSchema = z.object({
11
14
  category: z
12
15
  .enum(MEMORY_CATEGORIES)
@@ -19,6 +22,14 @@ const listMemoriesInputSchema = z.object({
19
22
  .string()
20
23
  .optional()
21
24
  .describe("Optional tag to filter memories by (exact match)"),
25
+ cursor: z
26
+ .object({
27
+ createdAt: z.string(),
28
+ category: z.enum(MEMORY_CATEGORIES),
29
+ id: z.string(),
30
+ })
31
+ .optional()
32
+ .describe("Pass the previous nextCursor to continue a listing"),
22
33
  toolCallDescription: z
23
34
  .string()
24
35
  .describe(
@@ -27,24 +38,53 @@ const listMemoriesInputSchema = z.object({
27
38
  });
28
39
 
29
40
  type ListMemoriesInput = z.infer<typeof listMemoriesInputSchema>;
41
+ type MemoryCursor = Pick<MemorySummary, "createdAt" | "category" | "id">;
30
42
 
31
43
  export type ListMemoriesResult = {
32
44
  success: boolean;
33
45
  error: string;
34
- memories: MemorySummary[];
46
+ memories: Array<MemorySummary & { truncated?: true }>;
35
47
  count: number;
48
+ nextCursor: MemoryCursor | null;
36
49
  };
37
50
 
51
+ function compareMemories(a: MemoryCursor, b: MemoryCursor): number {
52
+ const byCreatedAt = Date.parse(b.createdAt) - Date.parse(a.createdAt);
53
+ if (byCreatedAt > 0 || byCreatedAt < 0) return byCreatedAt;
54
+ return a.category.localeCompare(b.category) || a.id.localeCompare(b.id);
55
+ }
56
+
57
+ function previewSummary(memory: MemorySummary) {
58
+ const title = memory.title.slice(0, 160);
59
+ const tags = memory.tags.slice(0, 8).map((tag) => tag.slice(0, 48));
60
+ const truncated =
61
+ title.length < memory.title.length ||
62
+ tags.length < memory.tags.length ||
63
+ tags.some(
64
+ (tag, index) => tag.length < (memory.tags[index]?.length ?? tag.length),
65
+ );
66
+ return {
67
+ id: memory.id,
68
+ category: memory.category,
69
+ title,
70
+ tags,
71
+ createdAt: memory.createdAt,
72
+ ...(truncated ? { truncated: true as const } : {}),
73
+ };
74
+ }
75
+
38
76
  export function listMemories(ctx: ToolContext) {
39
77
  return tool({
40
78
  description: `List saved memories, optionally filtered by category and/or tag.
41
79
 
42
- Returns lightweight summaries (id, category, title, tags, createdAt) sorted by
43
- most recent first. Use the returned category + id with get_memory to retrieve
44
- full content.`,
80
+ Returns up to 20 lightweight summaries (id, category, title, tags, createdAt)
81
+ sorted by most recent first. Use nextCursor to continue until it is null;
82
+ count is the current total matching memories. A fresh listing includes newer
83
+ memories added ahead of a cursor. Titles and tags may be abbreviated in the
84
+ listing. Use category + id with get_memory to retrieve full content.`,
45
85
  inputSchema: listMemoriesInputSchema,
46
86
  execute: async (
47
- { category, tag },
87
+ { category, tag, cursor },
48
88
  { toolCallId },
49
89
  ): Promise<ListMemoriesResult> => {
50
90
  try {
@@ -52,11 +92,37 @@ full content.`,
52
92
  { category, tag },
53
93
  { sessionId: ctx.session.id, toolCallId },
54
94
  );
95
+ const ordered = [...memories].sort(compareMemories);
96
+ let start = 0;
97
+ if (cursor) {
98
+ start = ordered.findIndex(
99
+ (memory) => compareMemories(memory, cursor) > 0,
100
+ );
101
+ if (start < 0) start = ordered.length;
102
+ }
103
+ const page: ListMemoriesResult["memories"] = [];
104
+ let pageChars = 0;
105
+ for (const memory of ordered.slice(start, start + PAGE_SIZE)) {
106
+ const summary = previewSummary(memory);
107
+ const chars = JSON.stringify(summary).length + 1;
108
+ if (page.length > 0 && pageChars + chars > PAGE_CHAR_BUDGET) break;
109
+ page.push(summary);
110
+ pageChars += chars;
111
+ }
112
+ const last = page.at(-1);
55
113
  return {
56
114
  success: true,
57
115
  error: "",
58
- memories,
116
+ memories: page,
59
117
  count: memories.length,
118
+ nextCursor:
119
+ start + page.length < ordered.length && last
120
+ ? {
121
+ createdAt: last.createdAt,
122
+ category: last.category,
123
+ id: last.id,
124
+ }
125
+ : null,
60
126
  };
61
127
  } catch (err: unknown) {
62
128
  return {
@@ -64,6 +130,7 @@ full content.`,
64
130
  error: err instanceof Error ? err.message : String(err),
65
131
  memories: [],
66
132
  count: 0,
133
+ nextCursor: null,
67
134
  };
68
135
  }
69
136
  },
@@ -11,6 +11,12 @@ describe("report_error tool", () => {
11
11
  expect(REPORT_ERROR_TOOL_NAME).toBe("report_error");
12
12
  });
13
13
 
14
+ it("does not name the unavailable finding tool under external grading", () => {
15
+ const external = createReportErrorTool(vi.fn(), "external");
16
+ expect(external.description).toContain("final response");
17
+ expect(external.description).not.toContain("document_vulnerability");
18
+ });
19
+
14
20
  it("forwards the reason + message to the onError callback", async () => {
15
21
  const onError = vi.fn<(error: ReportedError) => void>();
16
22
  const tool = createReportErrorTool(onError);
@@ -1,5 +1,6 @@
1
1
  import { tool } from "ai";
2
2
  import { z } from "zod";
3
+ import type { FindingPolicy } from "../types";
3
4
 
4
5
  export const REPORT_ERROR_TOOL_NAME = "report_error" as const;
5
6
 
@@ -22,7 +23,14 @@ export type ReportedError = {
22
23
  * of a successful result it surfaces a blocking error so the caller can turn
23
24
  * the run into a failure (e.g. mark a scan endpoint failed with this message).
24
25
  */
25
- export function createReportErrorTool(onError: (error: ReportedError) => void) {
26
+ export function createReportErrorTool(
27
+ onError: (error: ReportedError) => void,
28
+ findingPolicy: FindingPolicy = "native",
29
+ ) {
30
+ const vulnerabilityGuidance =
31
+ findingPolicy === "external"
32
+ ? "Do NOT use this for vulnerabilities (include them in your final response)"
33
+ : "Do NOT use this for vulnerabilities (use document_vulnerability)";
26
34
  return tool({
27
35
  description: `Report a blocking error that prevents you from completing the pentest, and END your run.
28
36
 
@@ -31,7 +39,7 @@ Call this ONLY when you genuinely cannot continue, for example:
31
39
  - The target is unreachable or responds in a way that makes testing impossible.
32
40
  - Some other runtime condition blocks you from carrying out the objectives.
33
41
 
34
- Do NOT use this for vulnerabilities (use document_vulnerability) or for normal completion (use response). Provide a clear, specific message describing exactly what blocked you — include the failing URL, status codes, or the auth step that failed where relevant — so it can be surfaced to the operator.`,
42
+ ${vulnerabilityGuidance} or for normal completion (use response). Provide a clear, specific message describing exactly what blocked you — include the failing URL, status codes, or the auth step that failed where relevant — so it can be surfaced to the operator.`,
35
43
  inputSchema: z.object({
36
44
  reason: ReportErrorReasonSchema.describe(
37
45
  "Coarse category of the blocking error.",
@@ -1,5 +1,6 @@
1
1
  import { tool } from "ai";
2
2
  import { z } from "zod";
3
+ import { getModelSettings } from "../../../ai";
3
4
  import { createLogger } from "../../../logger/structured";
4
5
  import { scopedLogger } from "../../../util/lazyLogger";
5
6
  import type { AttackSurfaceResult } from "../../specialized/attackSurface/blackboxAgent";
@@ -74,13 +75,14 @@ should be passed directly to spawn_pentest_swarm for deep testing.`,
74
75
  attackSurfaceRegistry: ctx.attackSurfaceRegistry,
75
76
  },
76
77
  runtime: {
78
+ findingPolicy: ctx.findingPolicy,
77
79
  session: ctx.session,
78
- model: ctx.model,
79
- authConfig: ctx.authConfig,
80
+ ...getModelSettings(ctx),
80
81
  abortSignal: ctx.abortSignal,
81
82
  backends: ctx.backends,
82
83
  languageModelMiddleware: ctx.languageModelMiddleware,
83
84
  usageRecorder: ctx.usageRecorder,
85
+ toolExecutionContext: ctx.toolExecutionContext,
84
86
  streamIdFactory: ctx.streamIdFactory,
85
87
  },
86
88
  parentBus: ctx.eventBus,
@@ -144,13 +146,14 @@ should be passed directly to spawn_pentest_swarm for deep testing.`,
144
146
  attackSurfaceRegistry: ctx.attackSurfaceRegistry,
145
147
  },
146
148
  runtime: {
149
+ findingPolicy: ctx.findingPolicy,
147
150
  session: ctx.session,
148
- model: ctx.model,
149
- authConfig: ctx.authConfig,
151
+ ...getModelSettings(ctx),
150
152
  abortSignal: ctx.abortSignal,
151
153
  backends: ctx.backends,
152
154
  languageModelMiddleware: ctx.languageModelMiddleware,
153
155
  usageRecorder: ctx.usageRecorder,
156
+ toolExecutionContext: ctx.toolExecutionContext,
154
157
  streamIdFactory: ctx.streamIdFactory,
155
158
  },
156
159
  parentBus: ctx.eventBus,
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ const workflow = vi.hoisted(() => vi.fn());
4
+
5
+ vi.mock("../../../workflows/pentest", () => ({
6
+ runPentestWorkflow: workflow,
7
+ }));
8
+
9
+ import { runPentestWorkflow } from "./runPentestWorkflow";
10
+ import type { ToolContext } from "./types";
11
+
12
+ describe("run_pentest_workflow external finding handoff", () => {
13
+ it("returns reported findings to the parent instead of claiming zero were documented", async () => {
14
+ workflow.mockResolvedValue({
15
+ findings: [],
16
+ reportedFindings: ["Cross-tenant read at GET /private"],
17
+ objectiveResults: [
18
+ {
19
+ objective: "Extract the assigned flag",
20
+ completed: true,
21
+ result: "Extracted FLAG{example}",
22
+ },
23
+ ],
24
+ findingsPath: "/session/findings",
25
+ pocsPath: "/session/pocs",
26
+ reportPath: null,
27
+ });
28
+ const ctx = {
29
+ findingPolicy: "external",
30
+ model: "test",
31
+ session: { id: "ses_external", rootPath: "/session" },
32
+ } as ToolContext;
33
+ const agentTool = runPentestWorkflow(ctx);
34
+ expect(agentTool.description).toContain("caller's evaluation");
35
+ const result = await agentTool.execute?.(
36
+ { target: "https://example.com", toolCallDescription: "test" },
37
+ { toolCallId: "tc_external", messages: [] },
38
+ );
39
+
40
+ expect(workflow).toHaveBeenCalledWith(
41
+ expect.objectContaining({ findingPolicy: "external" }),
42
+ );
43
+ expect(result).toMatchObject({
44
+ success: true,
45
+ totalFindings: 1,
46
+ reportedFindings: ["Cross-tenant read at GET /private"],
47
+ objectiveResults: [
48
+ {
49
+ objective: "Extract the assigned flag",
50
+ completed: true,
51
+ result: "Extracted FLAG{example}",
52
+ },
53
+ ],
54
+ });
55
+ expect((result as { message: string }).message).not.toContain("documented");
56
+ });
57
+ });
@@ -11,13 +11,17 @@ import type { ToolContext } from "./types";
11
11
  * need to orchestrate multiple tool calls manually.
12
12
  */
13
13
  export function runPentestWorkflow(ctx: ToolContext) {
14
+ const resultStep =
15
+ ctx.findingPolicy === "external"
16
+ ? "3. Return evidence-backed findings for the caller's evaluation"
17
+ : "3. Aggregate findings and generate the final report";
14
18
  return tool({
15
19
  description: `Run the full autonomous penetration test pipeline against a target.
16
20
 
17
21
  This single tool call executes the entire pentest workflow:
18
22
  1. Attack surface discovery (blackbox or whitebox)
19
23
  2. Spawn a swarm of targeted pentest agents
20
- 3. Aggregate findings and generate the final report
24
+ ${resultStep}
21
25
 
22
26
  Use this when you want to run a complete, end-to-end pentest. The workflow
23
27
  handles session creation, concurrency, persistence, and reporting automatically.
@@ -54,6 +58,7 @@ Omit \`cwd\` for blackbox mode (live target probing only).`,
54
58
 
55
59
  try {
56
60
  const result = await workflow({
61
+ findingPolicy: ctx.findingPolicy,
57
62
  target,
58
63
  cwd,
59
64
  model: ctx.model,
@@ -64,12 +69,15 @@ Omit \`cwd\` for blackbox mode (live target probing only).`,
64
69
  enableThinking: ctx.enableThinking,
65
70
  thinkingEffort: ctx.thinkingEffort,
66
71
  openAIReasoningEffort: ctx.openAIReasoningEffort,
72
+ reasoning: ctx.reasoning,
67
73
  surfaceIntegrationEnabled: ctx.surfaceIntegrationEnabled,
68
74
  hooks: {
75
+ findingPolicy: ctx.findingPolicy,
69
76
  backends: ctx.backends,
70
77
  subagentSpawner: ctx.subagentSpawner,
71
78
  languageModelMiddleware: ctx.languageModelMiddleware,
72
79
  usageRecorder: ctx.usageRecorder,
80
+ toolExecutionContext: ctx.toolExecutionContext,
73
81
  streamIdFactory: ctx.streamIdFactory,
74
82
  smsInbox: ctx.smsInbox,
75
83
  emailAdapterFor: ctx.emailAdapterFor,
@@ -81,6 +89,18 @@ Omit \`cwd\` for blackbox mode (live target probing only).`,
81
89
  const minutes = Math.floor(elapsedSeconds / 60);
82
90
  const seconds = elapsedSeconds % 60;
83
91
 
92
+ if (ctx.findingPolicy === "external") {
93
+ const reportedFindings = result.reportedFindings ?? [];
94
+ return {
95
+ success: true,
96
+ totalFindings: reportedFindings.length,
97
+ reportedFindings,
98
+ objectiveResults: result.objectiveResults ?? [],
99
+ duration: `${minutes}m ${seconds}s`,
100
+ message: `Pentest complete. ${reportedFindings.length} finding(s) returned for external evaluation.`,
101
+ };
102
+ }
103
+
84
104
  return {
85
105
  success: true,
86
106
  totalFindings: result.findings.length,
@@ -22,13 +22,16 @@ const ScanKindSchema = z.enum([
22
22
  ]);
23
23
 
24
24
  export function runWhiteboxScan(ctx: ToolContext) {
25
+ const verificationGuidance =
26
+ ctx.findingPolicy === "external"
27
+ ? "as recoverable conditions. Static results are candidates only; assess them against the caller's evidence requirements."
28
+ : "as recoverable conditions. Static results are candidates only; verify before\ncalling document_vulnerability.";
25
29
  return tool({
26
30
  description: `Run installed whitebox security scanners with bounded output.
27
31
 
28
32
  The tool detects applicable scanner adapters for the repository and normalizes
29
33
  results into concise summaries plus artifact paths. Missing tools are reported
30
- as recoverable conditions. Static results are candidates only; verify before
31
- calling document_vulnerability.`,
34
+ ${verificationGuidance}`,
32
35
  inputSchema: z.object({
33
36
  path: z
34
37
  .string()