@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
@@ -8,7 +8,7 @@ import type {
8
8
  TextStreamPart,
9
9
  ToolSet,
10
10
  } from "ai";
11
- import { hasToolCall } from "ai";
11
+ import { hasToolCall, stepCountIs } from "ai";
12
12
  import { normalizeStepUsage, streamResponse } from "../../ai";
13
13
  import { AgentEventBus, type StreamIdContext } from "../../eventBus";
14
14
  import { newMessageId, newPartId } from "../../id/id";
@@ -25,6 +25,8 @@ import { ApprovalDeniedError } from "../../operator";
25
25
  import type { RunContextRecorder } from "../../runtime/runContext";
26
26
  import { create as createSession, type SessionInfo } from "../../session";
27
27
  import { scopedLogger } from "../../util/lazyLogger";
28
+ import { LANCE_CONFIG, LANCE_SYSTEM_PROMPT, LanceRuntime } from "../lance";
29
+ import { createLanceJudgeVerifier } from "../lanceJudge";
28
30
  import {
29
31
  createInterruptedStepFinalizer,
30
32
  type FinalizeInterruptedStepInput,
@@ -43,6 +45,7 @@ import {
43
45
  } from "./runtimeContext";
44
46
  import { responseArgBytes, StreamDiagnostics } from "./streamDiagnostics";
45
47
  import { inProcessSubagentSpawner } from "./subagentSpawner";
48
+ import { wrapToolExecutionContext } from "./toolExecutionContext";
46
49
  import { ToolLifecycleTracker } from "./toolLifecycle";
47
50
  import {
48
51
  ASK_USER_QUESTIONS_TOOL_NAME,
@@ -298,6 +301,7 @@ export class OffensiveSecurityAgent<TResult = void> {
298
301
 
299
302
  private readonly resolveResult?: (
300
303
  streamResult: StreamTextResult<ToolSet, never>,
304
+ validatedResponse?: unknown,
301
305
  ) => TResult | Promise<TResult>;
302
306
 
303
307
  /** The structured result captured by the `response` tool, or `null` until it fires. */
@@ -423,8 +427,18 @@ export class OffensiveSecurityAgent<TResult = void> {
423
427
  let session = input.session;
424
428
  if (!session) {
425
429
  session = await createSession({
430
+ ...(input.mode === "lance" ? { name: "Lance" } : {}),
426
431
  targets: input.target ? [input.target] : [],
427
- config: input.sessionConfig,
432
+ config:
433
+ input.reasoning === undefined
434
+ ? input.sessionConfig
435
+ : {
436
+ ...input.sessionConfig,
437
+ modelReasoning: {
438
+ model: input.model,
439
+ setting: input.reasoning,
440
+ },
441
+ },
428
442
  model: input.model,
429
443
  authConfig: input.authConfig,
430
444
  userMessage: input.prompt,
@@ -444,7 +458,65 @@ export class OffensiveSecurityAgent<TResult = void> {
444
458
  return this.subagentId ?? this._session.id;
445
459
  }
446
460
 
461
+ readonly lanceRuntime?: LanceRuntime;
462
+ private lanceModel?: string;
463
+
447
464
  constructor(input: OffensiveSecurityAgentInput<TResult>) {
465
+ if (
466
+ input.mode === "lance" &&
467
+ !input.lance?.interactive &&
468
+ existsSync(join(input.session.rootPath, "lance-result.json"))
469
+ ) {
470
+ throw new Error("This Lance attempt is terminal; use a new session");
471
+ }
472
+ if (input.mode === "lance") {
473
+ if (
474
+ input.extraTools ||
475
+ input.responseSchema ||
476
+ input.subagentSpawner ||
477
+ input.system ||
478
+ input.resolveResult ||
479
+ input.subagentId
480
+ ) {
481
+ throw new Error(
482
+ "Lance does not permit tool, prompt, response, or spawner overrides",
483
+ );
484
+ }
485
+ const lanceOptions = { ...input.lance };
486
+ this.lanceRuntime = new LanceRuntime(
487
+ lanceOptions,
488
+ input.abortSignal,
489
+ input.session.rootPath,
490
+ );
491
+ if (input.findingPolicy === "native" && !lanceOptions.verifyObjective) {
492
+ lanceOptions.verificationSource = "apex_judge";
493
+ lanceOptions.verifyObjective = createLanceJudgeVerifier(
494
+ input,
495
+ this.lanceRuntime,
496
+ );
497
+ }
498
+ this.lanceModel = input.model;
499
+ const middleware = input.languageModelMiddleware;
500
+ input = {
501
+ ...input,
502
+ system: LANCE_SYSTEM_PROMPT,
503
+ abortSignal: this.lanceRuntime.signal,
504
+ subagentSpawner: this.lanceRuntime.spawner,
505
+ languageModelMiddleware: [
506
+ this.lanceRuntime.middleware,
507
+ ...(Array.isArray(middleware)
508
+ ? middleware
509
+ : middleware
510
+ ? [middleware]
511
+ : []),
512
+ ],
513
+ stopWhen: [
514
+ () => this.lanceRuntime!.terminal,
515
+ stepCountIs(LANCE_CONFIG.maxSteps),
516
+ ],
517
+ skillsRegistry: undefined,
518
+ };
519
+ }
448
520
  this._session = input.session;
449
521
  this.subagentId = input.subagentId;
450
522
  this.subagentName = input.subagentName;
@@ -552,6 +624,7 @@ export class OffensiveSecurityAgent<TResult = void> {
552
624
  input.credentialManager ?? input.session.credentialManager;
553
625
 
554
626
  const toolCtx = {
627
+ findingPolicy: input.findingPolicy,
555
628
  ...(input.attackSurfaceArtifactsPath
556
629
  ? { attackSurfaceArtifactsPath: input.attackSurfaceArtifactsPath }
557
630
  : {}),
@@ -589,6 +662,7 @@ export class OffensiveSecurityAgent<TResult = void> {
589
662
  enableThinking: input.enableThinking,
590
663
  thinkingEffort: input.thinkingEffort,
591
664
  openAIReasoningEffort: input.openAIReasoningEffort,
665
+ reasoning: input.reasoning,
592
666
  surfaceIntegrationEnabled: input.surfaceIntegrationEnabled,
593
667
  projectThreatModel: input.projectThreatModel,
594
668
  planSubagentId: input.planSubagentId,
@@ -607,6 +681,7 @@ export class OffensiveSecurityAgent<TResult = void> {
607
681
  emailAdapterFor: input.emailAdapterFor,
608
682
  languageModelMiddleware: input.languageModelMiddleware,
609
683
  usageRecorder: input.usageRecorder,
684
+ toolExecutionContext: input.toolExecutionContext,
610
685
  streamIdFactory: input.streamIdFactory,
611
686
  };
612
687
 
@@ -630,7 +705,9 @@ export class OffensiveSecurityAgent<TResult = void> {
630
705
  let activeTools = (input.activeTools as string[]).filter(
631
706
  passesEmailSmsGates,
632
707
  );
633
- if (input.mode === "plan") {
708
+ if (input.mode === "lance") {
709
+ activeTools = [...LANCE_CONFIG.tools];
710
+ } else if (input.mode === "plan") {
634
711
  const planSet = new Set<string>(PLAN_MODE_TOOL_NAMES);
635
712
  activeTools = activeTools.filter((t) => planSet.has(t));
636
713
  } else if (input.mode === "fast-strike") {
@@ -658,6 +735,12 @@ export class OffensiveSecurityAgent<TResult = void> {
658
735
  }
659
736
  }
660
737
 
738
+ if (input.findingPolicy === "external") {
739
+ activeTools = activeTools.filter(
740
+ (name) => name !== "document_vulnerability",
741
+ );
742
+ }
743
+
661
744
  activeTools = filterWorkspaceToolsForRun(
662
745
  activeTools,
663
746
  input.prompt,
@@ -666,10 +749,37 @@ export class OffensiveSecurityAgent<TResult = void> {
666
749
 
667
750
  const builtinTools = createToolsForNames(toolCtx, activeTools);
668
751
 
669
- let tools: ToolSet = input.extraTools
670
- ? { ...builtinTools, ...input.extraTools }
752
+ const extraTools =
753
+ input.findingPolicy === "external" && input.extraTools
754
+ ? Object.fromEntries(
755
+ Object.entries(input.extraTools).filter(
756
+ ([name]) => name !== "document_vulnerability",
757
+ ),
758
+ )
759
+ : input.extraTools;
760
+ let tools: ToolSet = extraTools
761
+ ? { ...builtinTools, ...extraTools }
671
762
  : { ...builtinTools };
672
763
 
764
+ if (this.lanceRuntime) {
765
+ const runtime = this.lanceRuntime;
766
+ tools = Object.fromEntries(
767
+ Object.entries(tools).map(([name, definition]) => [
768
+ name,
769
+ {
770
+ ...definition,
771
+ execute: async (
772
+ args: unknown,
773
+ options: Parameters<NonNullable<typeof definition.execute>>[1],
774
+ ) => {
775
+ runtime.checkAgent();
776
+ return definition.execute!(args, options);
777
+ },
778
+ },
779
+ ]),
780
+ );
781
+ }
782
+
673
783
  // Only approved calls may reach the execution journal.
674
784
  if (input.toolExecutionRecorder) {
675
785
  tools = wrapRecordedTools(tools, input.toolExecutionRecorder);
@@ -681,9 +791,15 @@ export class OffensiveSecurityAgent<TResult = void> {
681
791
  tools,
682
792
  input.approvalGate,
683
793
  AGENT_PAUSE_TOOLS,
794
+ this.lanceRuntime?.signal,
684
795
  );
685
796
  }
686
797
 
798
+ if (this.lanceRuntime) {
799
+ tools = this.lanceRuntime.tools(tools);
800
+ activeTools = Object.keys(tools);
801
+ }
802
+
687
803
  // -- Response schema → auto capture / stop / resolve ----------------------
688
804
  if (input.responseSchema) {
689
805
  tools = {
@@ -712,7 +828,7 @@ export class OffensiveSecurityAgent<TResult = void> {
712
828
  Promise.race([sr.response, this._drainRejection]),
713
829
  },
714
830
  });
715
- return customResolve(guarded);
831
+ return customResolve(guarded, result);
716
832
  })
717
833
  .then((resolved) => {
718
834
  this._capturedResponse = resolved;
@@ -730,6 +846,10 @@ export class OffensiveSecurityAgent<TResult = void> {
730
846
  };
731
847
  }
732
848
 
849
+ if (input.toolExecutionContext) {
850
+ tools = wrapToolExecutionContext(tools, input.toolExecutionContext);
851
+ }
852
+
733
853
  if (input.resolveResult) {
734
854
  this.resolveResult = input.resolveResult;
735
855
  } else if (input.responseSchema) {
@@ -759,16 +879,36 @@ export class OffensiveSecurityAgent<TResult = void> {
759
879
  }
760
880
  this.messagesPath = join(messagesDir, "messages.json");
761
881
 
762
- // Mutable so that summarization can clear stale history.
763
- const initialMessagesRef: { current: ModelMessage[] } = {
764
- current: input.messages
765
- ? [...input.messages]
766
- : [
767
- {
768
- role: "user" as const,
769
- content: [{ type: "text", text: input.prompt }],
770
- },
771
- ],
882
+ const preparedContextRef = {
883
+ current: {
884
+ messages: input.messages
885
+ ? [...input.messages]
886
+ : [
887
+ {
888
+ role: "user" as const,
889
+ content: [{ type: "text" as const, text: input.prompt }],
890
+ },
891
+ ],
892
+ responseMessageOffset: 0,
893
+ },
894
+ };
895
+ const legacyMessages = (responseMessages: ModelMessage[]) => [
896
+ ...preparedContextRef.current.messages,
897
+ ...responseMessages.slice(
898
+ preparedContextRef.current.responseMessageOffset,
899
+ ),
900
+ ];
901
+ const persistedMessages = (messages: ModelMessage[]) => {
902
+ // Cache-marked system messages are transport context; the next turn
903
+ // supplies a fresh system prompt and must not replay saved copies.
904
+ let first = 0;
905
+ while (
906
+ messages[first]?.role === "system" &&
907
+ messages[first]?.providerOptions
908
+ ) {
909
+ first++;
910
+ }
911
+ return first === 0 ? messages : messages.slice(first);
772
912
  };
773
913
 
774
914
  // Agent-local persistence: debounce, latest snapshot, and the
@@ -796,6 +936,7 @@ export class OffensiveSecurityAgent<TResult = void> {
796
936
  input.system ??
797
937
  buildBaseSystemPrompt({
798
938
  sandboxMode: agentCwd === input.session.rootPath,
939
+ findingPolicy: input.findingPolicy,
799
940
  });
800
941
  const commandPlatform = resolveCommandPlatform(toolCtx);
801
942
  const workspaceSection = buildSessionWorkspaceSection(
@@ -803,6 +944,7 @@ export class OffensiveSecurityAgent<TResult = void> {
803
944
  agentCwd,
804
945
  activeTools,
805
946
  fileWorkspaceRoot,
947
+ input.findingPolicy,
806
948
  );
807
949
  // Bundled wordlist paths are host-local: advertise them only when
808
950
  // commands execute locally.
@@ -824,9 +966,9 @@ export class OffensiveSecurityAgent<TResult = void> {
824
966
  // Only agents whose tools can execute commands, touch files, or run
825
967
  // PoCs get a runtime probe — for anyone else the facts are noise and the
826
968
  // probe is a wasted subprocess.
827
- this.probesRuntime = activeTools.some((name) =>
828
- RUNTIME_CAPABLE_TOOL_NAMES.has(name),
829
- );
969
+ this.probesRuntime =
970
+ input.mode !== "lance" &&
971
+ activeTools.some((name) => RUNTIME_CAPABLE_TOOL_NAMES.has(name));
830
972
 
831
973
  // -- Init record + stream --------------------------------------------------
832
974
  // Created by async initialization (streamReady) so the AI SDK telemetry
@@ -840,7 +982,10 @@ export class OffensiveSecurityAgent<TResult = void> {
840
982
  // so the hash is stable across runs with identical prompt versions. No
841
983
  // step record can precede this init line: steps only originate from the
842
984
  // stream created below.
843
- const baseWithFacts = assembleBaseSystemPrompt(facts);
985
+ const baseWithFacts =
986
+ input.mode === "lance"
987
+ ? LANCE_SYSTEM_PROMPT
988
+ : assembleBaseSystemPrompt(facts);
844
989
  traceWriter.writeInit({
845
990
  model: input.model,
846
991
  systemPrompt: baseWithFacts,
@@ -848,11 +993,30 @@ export class OffensiveSecurityAgent<TResult = void> {
848
993
  sessionId: input.session.id,
849
994
  target: input.target,
850
995
  });
996
+ this.lanceRuntime?.start();
997
+ const continuation = this.lanceRuntime?.continuationContext();
851
998
  return streamResponse({
852
- prompt: input.prompt,
853
- system: baseWithFacts + workspaceSection,
999
+ prompt: continuation
1000
+ ? `${input.prompt}\n\n${continuation}`
1001
+ : input.prompt,
1002
+ system:
1003
+ input.mode === "lance"
1004
+ ? `${LANCE_SYSTEM_PROMPT}\nWorking directory (not automatically in scope): ${agentCwd}\nTarget: ${input.target ?? "Use the user's explicit target, or ask if missing"}\nDeadline: ${new Date(this.lanceRuntime!.softDeadlineAt).toISOString()}`
1005
+ : baseWithFacts + workspaceSection,
1006
+ primaryModelOnly: input.mode === "lance",
1007
+ _initialPrompt: input.mode === "lance" ? input.prompt : undefined,
1008
+ runContextCompaction: this.lanceRuntime?.runContextCompaction,
1009
+ getContinuationContext: this.lanceRuntime?.continuationContext,
1010
+ onInvalidToolCall: (name) => {
1011
+ if (this.lanceRuntime && !activeTools.includes(name)) {
1012
+ this.lanceRuntime.violation("forbidden_tool", name);
1013
+ }
1014
+ },
854
1015
  model: input.model,
855
- messages: input.messages,
1016
+ messages:
1017
+ continuation && input.messages
1018
+ ? [...input.messages, { role: "user", content: continuation }]
1019
+ : input.messages,
856
1020
  tools,
857
1021
  activeTools,
858
1022
  stopWhen,
@@ -861,6 +1025,15 @@ export class OffensiveSecurityAgent<TResult = void> {
861
1025
  usageRecorder: input.usageRecorder,
862
1026
  contextRecorder: input.contextRecorder,
863
1027
  inferenceRecorder: input.inferenceRecorder,
1028
+ onContextPrepared: (context) => {
1029
+ preparedContextRef.current = {
1030
+ messages: [...context.messages],
1031
+ responseMessageOffset: context.responseMessageOffset,
1032
+ };
1033
+ if (this.lanceRuntime) {
1034
+ this.writer.setLatest(persistedMessages(context.messages));
1035
+ }
1036
+ },
864
1037
  // Per-subagent so the overflow tool-result dumps land next to this
865
1038
  // agent's messages.json (`subagents/{id}/tool-results/`) and a host
866
1039
  // can reclaim them when the subagent finishes, instead of piling up
@@ -880,16 +1053,11 @@ export class OffensiveSecurityAgent<TResult = void> {
880
1053
  return;
881
1054
  }
882
1055
 
883
- // Recorded runs project the canonical committed context; the
884
- // legacy path reconstructs from the (possibly stale after a fit)
885
- // initial prefix. Canonical latest() already includes this step's
886
- // messages — committing happens upstream in the ai layer's
887
- // onStepFinish wrapper, before this user callback runs.
888
- const canonical = input.contextRecorder?.latest() ?? [
889
- ...initialMessagesRef.current,
890
- ...event.response.messages,
891
- ];
892
- this.writer.setLatest(canonical);
1056
+ this.lanceRuntime?.stepFinished();
1057
+ const canonical =
1058
+ input.contextRecorder?.latest() ??
1059
+ legacyMessages(event.response.messages);
1060
+ this.writer.setLatest(persistedMessages(canonical));
893
1061
  schedulePersist();
894
1062
  traceWriter.recordStep(
895
1063
  event.response.messages as ModelMessage[],
@@ -905,9 +1073,10 @@ export class OffensiveSecurityAgent<TResult = void> {
905
1073
  await input.onStepFinish?.(event);
906
1074
  },
907
1075
  onSummarized: () => {
908
- // Context was reset by summarization — discard the old history so
909
- // subsequent onStepFinish writes only persist post-summary messages.
910
- initialMessagesRef.current = [];
1076
+ preparedContextRef.current = {
1077
+ messages: [],
1078
+ responseMessageOffset: 0,
1079
+ };
911
1080
  traceWriter.markSummarized();
912
1081
  },
913
1082
  onFinish: async (event) => {
@@ -915,11 +1084,11 @@ export class OffensiveSecurityAgent<TResult = void> {
915
1084
  this.writer.cancelTimer();
916
1085
  // Skip if the interrupted-step finalizer already wrote the abort snapshot.
917
1086
  if (!this.writer.syntheticsPersisted) {
918
- const finalMessages = input.contextRecorder?.latest() ??
919
- this.writer.latest ?? [
920
- ...initialMessagesRef.current,
921
- ...event.response.messages,
922
- ];
1087
+ const finalMessages = persistedMessages(
1088
+ input.contextRecorder?.latest() ??
1089
+ this.writer.latest ??
1090
+ legacyMessages(event.response.messages),
1091
+ );
923
1092
  this.writer.setLatest(finalMessages);
924
1093
  await this.writer.enqueueWrite(finalMessages).catch(() => {});
925
1094
  }
@@ -931,6 +1100,7 @@ export class OffensiveSecurityAgent<TResult = void> {
931
1100
  enableThinking: input.enableThinking,
932
1101
  thinkingEffort: input.thinkingEffort,
933
1102
  openAIReasoningEffort: input.openAIReasoningEffort,
1103
+ reasoning: input.reasoning,
934
1104
  silent: true,
935
1105
  });
936
1106
  };
@@ -1039,6 +1209,21 @@ export class OffensiveSecurityAgent<TResult = void> {
1039
1209
  * @returns The value produced by `resolveResult`, or `void` if none was provided.
1040
1210
  */
1041
1211
  async consume(): Promise<TResult> {
1212
+ if (!this.lanceRuntime) return this.consumeRun();
1213
+ this.lanceRuntime.start();
1214
+ let error: unknown;
1215
+ try {
1216
+ return await this.consumeRun();
1217
+ } catch (cause) {
1218
+ error = cause;
1219
+ return undefined as TResult;
1220
+ } finally {
1221
+ this.lanceRuntime.finish(error);
1222
+ this.lanceRuntime.persist(this.session.rootPath, this.lanceModel!);
1223
+ }
1224
+ }
1225
+
1226
+ private async consumeRun(): Promise<TResult> {
1042
1227
  const sid = this.subagentId;
1043
1228
  const bus = this.eventBus;
1044
1229
 
@@ -1196,6 +1381,13 @@ export class OffensiveSecurityAgent<TResult = void> {
1196
1381
  }
1197
1382
  return result;
1198
1383
  } catch (err) {
1384
+ if (this.lanceRuntime?.terminal) {
1385
+ span.setAttribute(
1386
+ "pensar.lance.outcome",
1387
+ this.lanceRuntime.metrics(this.lanceModel!).outcome,
1388
+ );
1389
+ throw err;
1390
+ }
1199
1391
  // Record once, keep the cardinality low, preserve the original.
1200
1392
  span.recordException(err as Error);
1201
1393
  span.setStatus({ code: SpanStatusCode.ERROR });
@@ -1550,6 +1742,7 @@ function wrapToolsWithApprovalGate(
1550
1742
  tools: ToolSet,
1551
1743
  gate: ApprovalGate,
1552
1744
  exemptToolNames?: Set<string>,
1745
+ signal?: AbortSignal,
1553
1746
  ): ToolSet {
1554
1747
  const wrapped: ToolSet = {};
1555
1748
 
@@ -1578,7 +1771,15 @@ function wrapToolsWithApprovalGate(
1578
1771
  `tc_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1579
1772
 
1580
1773
  log.debug(`${name} (${toolCallId}): checking`);
1774
+ const cancelApproval = () => {
1775
+ for (const pending of gate.getPendingApprovals()) {
1776
+ if (pending.toolCallId === String(toolCallId))
1777
+ gate.deny(pending.id);
1778
+ }
1779
+ };
1581
1780
  try {
1781
+ signal?.throwIfAborted();
1782
+ signal?.addEventListener("abort", cancelApproval, { once: true });
1582
1783
  await gate.check(name, String(toolCallId), args);
1583
1784
  } catch (err) {
1584
1785
  if (err instanceof ApprovalDeniedError) {
@@ -1586,6 +1787,8 @@ function wrapToolsWithApprovalGate(
1586
1787
  return { blocked: true, reason: "Denied by operator" };
1587
1788
  }
1588
1789
  throw err;
1790
+ } finally {
1791
+ signal?.removeEventListener("abort", cancelApproval);
1589
1792
  }
1590
1793
  log.debug(`${name} (${toolCallId}): approved, executing`);
1591
1794
 
@@ -70,3 +70,49 @@ describe("buildBaseSystemPrompt workspace management", () => {
70
70
  );
71
71
  });
72
72
  });
73
+
74
+ describe("external finding policy prompts", () => {
75
+ const session = {
76
+ rootPath: "/tmp/apex-external-prompt",
77
+ findingsPath: "/tmp/apex-external-prompt/findings",
78
+ pocsPath: "/tmp/apex-external-prompt/pocs",
79
+ scratchpadPath: "/tmp/apex-external-prompt/scratchpad",
80
+ logsPath: "/tmp/apex-external-prompt/logs",
81
+ };
82
+
83
+ it("keeps exploration guidance without directing an unavailable finding tool", () => {
84
+ const prompt = buildBaseSystemPrompt({ findingPolicy: "external" });
85
+ expect(prompt).toContain("Source code analysis");
86
+ expect(prompt).toContain("caller grades them after the run");
87
+ expect(prompt).not.toContain("document_vulnerability");
88
+ expect(prompt).not.toContain("published PoCs");
89
+ });
90
+
91
+ it.each([
92
+ [session.rootPath, undefined],
93
+ ["/project", undefined],
94
+ ["/project", "/tmp/apex-external-prompt/helpers"],
95
+ ])("uses the caller's finding contract for cwd %s", (cwd, fileWorkspaceRoot) => {
96
+ const prompt = buildSessionWorkspaceSection(
97
+ session,
98
+ cwd,
99
+ ["execute_command", "read_file"],
100
+ fileWorkspaceRoot,
101
+ "external",
102
+ );
103
+ expect(prompt).toContain("caller's output contract");
104
+ expect(prompt).not.toContain("document_vulnerability");
105
+ });
106
+
107
+ it("keeps source guidance policy-safe even when tool names are not supplied", () => {
108
+ const prompt = buildSessionWorkspaceSection(
109
+ { ...session, config: { codebasePath: "/project" } },
110
+ "/project",
111
+ undefined,
112
+ undefined,
113
+ "external",
114
+ );
115
+ expect(prompt).toContain("Source Code Assessment");
116
+ expect(prompt).not.toContain("document_vulnerability");
117
+ });
118
+ });