@pensar/apex 2.5.0 → 2.6.1-canary.0cc77dcc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -0
- package/bin/pensar.js +12 -4
- package/build/agent-6mzhkzxj.js +13 -0
- package/build/agent-9ymr36jt.js +13 -0
- package/build/{agent-m30a62kh.js → agent-fb1d2x2a.js} +48 -50
- package/build/agent-h8z7zb26.js +15 -0
- package/build/agent-runs-awgj4sat.js +313 -0
- package/build/{apps-x4ef2gpc.js → apps-d50kkp3g.js} +50 -58
- package/build/{auth-7ay8qgxm.js → auth-396rc66q.js} +61 -68
- package/build/blackboxAgent-j3ncq55f.js +13 -0
- package/build/blackboxPentest-aw69ect4.js +59 -0
- package/build/cli-0ym9qz7r.js +175 -0
- package/build/{cli-zav1yx5k.js → cli-2dqvgkh8.js} +990 -632
- package/build/cli-38j2jc0m.js +62 -0
- package/build/{cli-ee3axgts.js → cli-3gt761d2.js} +7 -1
- package/build/{cli-sx3jkv7p.js → cli-3p4gyc9m.js} +28872 -25951
- package/build/cli-3qznjsnr.js +14 -0
- package/build/cli-5ppxe6qz.js +355 -0
- package/build/cli-691cfgdy.js +186 -0
- package/build/{cli-x92fkt2f.js → cli-6y2mzdz2.js} +55 -97
- package/build/{cli-73k43t1r.js → cli-8s4ew1hv.js} +53 -78
- package/build/{cli-53rm0rrz.js → cli-8w8k4dph.js} +1 -1
- package/build/{cli-68260ckj.js → cli-91gs9whc.js} +1658 -1606
- package/build/{cli-7vg06q5r.js → cli-9s7snz6x.js} +135 -125
- package/build/{cli-bw2ddttk.js → cli-a1m3pmke.js} +4730 -267
- package/build/{cli-knd276ed.js → cli-bb02pjt0.js} +31 -31
- package/build/{cli-p4r7q012.js → cli-c0ha5wh1.js} +16072 -12743
- package/build/{cli-j54qa6tm.js → cli-c2k4xxaw.js} +42 -42
- package/build/{cli-k71t434r.js → cli-ec2hwkv3.js} +34 -76
- package/build/{cli-vkbzcnw1.js → cli-fapvkkbh.js} +43 -40
- package/build/cli-g06s7edq.js +11 -0
- package/build/cli-gsaf4jk3.js +280 -0
- package/build/{cli-bc05qbss.js → cli-h6y3bt60.js} +13 -21
- package/build/cli-jdrx44ge.js +14 -0
- package/build/{cli-z5c0ypg1.js → cli-jp6wytx1.js} +4 -4
- package/build/cli-mz5pk30x.js +5508 -0
- package/build/{cli-kff2bbq7.js → cli-nmg32cfd.js} +43 -43
- package/build/cli-pckpvwbd.js +22 -0
- package/build/cli-pwm29ks7.js +6337 -0
- package/build/cli-retw3rw5.js +12 -0
- package/build/cli-se8ae8z3.js +2250 -0
- package/build/{cli-dm0fc0bm.js → cli-v1sarr4y.js} +5 -9
- package/build/cli-v9rrvh12.js +429 -0
- package/build/{cli-3py0a0wg.js → cli-vfta27tv.js} +206 -136
- package/build/cli-xcy2vxj2.js +1732 -0
- package/build/{cli-y3dpm18e.js → cli-xn12g67s.js} +75 -92
- package/build/cli.js +769 -195
- package/build/{config-4kzh88xz.js → config-hddxxr27.js} +16 -19
- package/build/{doctor-rz7sx6ev.js → doctor-z94afr10.js} +26 -15
- package/build/export-trajectory-rt4tf07k.js +112 -0
- package/build/{fastStrike-a54atjc8.js → fastStrike-mgws2nav.js} +56 -31
- package/build/{fixes-fv66yn1z.js → fixes-9qtsvwxx.js} +22 -30
- package/build/{getMachineId-bsd-xj3bn2fv.js → getMachineId-bsd-zr67nd9p.js} +5 -5
- package/build/{getMachineId-darwin-07jzjcx6.js → getMachineId-darwin-fd1d562d.js} +5 -5
- package/build/{getMachineId-linux-ahngynxd.js → getMachineId-linux-kjmg243j.js} +4 -4
- package/build/{getMachineId-unsupported-vy43b44z.js → getMachineId-unsupported-n4r58vke.js} +4 -4
- package/build/{getMachineId-win-8x66cynz.js → getMachineId-win-yzgpfsrb.js} +5 -5
- package/build/index-0d05xcz4.js +25 -0
- package/build/index-0ef0j7yp.js +12 -0
- package/build/index-557xary4.js +121 -0
- package/build/index-a0bxw5zq.js +17477 -0
- package/build/{index-cbwn03kz.js → index-a2xycbjs.js} +12 -31
- package/build/{index-psqjpw5m.js → index-c1h0g35p.js} +11 -11
- package/build/index-cfbd4zej.js +17 -0
- package/build/index-mqaw1ac6.js +9 -0
- package/build/{index-kyd1jm89.js → index-nfhetcq8.js} +159 -169
- package/build/index-qnyevb6q.js +1563 -0
- package/build/{index-fpwr10qm.js → index-wyn4g2c1.js} +5048 -3786
- package/build/index-zbq3rvwx.js +36 -0
- package/build/index-zhr65sg3.js +102 -0
- package/build/{issues-019678t0.js → issues-9zdszfq8.js} +51 -47
- package/build/knowledge-r6rmer3d.js +139 -0
- package/build/lib/chunk-02awpt3c.js +431 -0
- package/build/lib/chunk-0me4mrrd.js +7150 -0
- package/build/lib/chunk-2j49k090.js +922 -0
- package/build/lib/chunk-2njv7wz3.js +178 -0
- package/build/lib/chunk-2t65f749.js +1412 -0
- package/build/lib/chunk-3502z798.js +9232 -0
- package/build/lib/chunk-3q89v4xv.js +21 -0
- package/build/lib/chunk-3w05jmk6.js +685 -0
- package/build/lib/chunk-48zms2q1.js +572 -0
- package/build/lib/chunk-4ydrmwd9.js +980 -0
- package/build/lib/chunk-517rf135.js +143 -0
- package/build/lib/chunk-5cp1fp0x.js +186 -0
- package/build/lib/chunk-7gjdygcx.js +171 -0
- package/build/lib/chunk-8vhr65w3.js +272 -0
- package/build/lib/chunk-9nsrd4xg.js +12 -0
- package/build/lib/chunk-asbemhm5.js +56 -0
- package/build/lib/chunk-b8eq85d2.js +143 -0
- package/build/lib/chunk-bx94rwt5.js +24 -0
- package/build/lib/chunk-eb4h4k1f.js +213 -0
- package/build/lib/chunk-ebkq3bcq.js +102 -0
- package/build/lib/chunk-esymacwg.js +14 -0
- package/build/lib/chunk-f04pr76h.js +317 -0
- package/build/lib/chunk-fy3bbbqy.js +7896 -0
- package/build/lib/chunk-g8d2x00m.js +2429 -0
- package/build/lib/chunk-ja7j565c.js +743 -0
- package/build/lib/chunk-jah67hkt.js +120 -0
- package/build/lib/chunk-jdjagvgr.js +85 -0
- package/build/lib/chunk-k5m5rasy.js +439 -0
- package/build/lib/chunk-mp1czx2y.js +510 -0
- package/build/lib/chunk-mrjbsaf4.js +31 -0
- package/build/lib/chunk-mv74mq5n.js +59 -0
- package/build/lib/chunk-n0ba7khr.js +64 -0
- package/build/lib/chunk-ngp6p527.js +317 -0
- package/build/lib/chunk-q5e23acz.js +52 -0
- package/build/lib/chunk-sk8xnywf.js +212 -0
- package/build/lib/chunk-t5bt5vn1.js +146 -0
- package/build/lib/chunk-tsnk2k9x.js +345 -0
- package/build/lib/chunk-vt9spgd1.js +62 -0
- package/build/lib/chunk-vx228eeq.js +82 -0
- package/build/lib/chunk-wr6ke041.js +11157 -0
- package/build/lib/chunk-xcdtrzk5.js +27 -0
- package/build/lib/chunk-xpfg2fbk.js +90 -0
- package/build/lib/chunk-zrbqn62x.js +697 -0
- package/build/lib/core/agents/agentRuntime.d.ts +47 -0
- package/build/lib/core/agents/defineAgent.d.ts +82 -0
- package/build/lib/core/agents/offSecAgent/index.d.ts +9 -0
- package/build/lib/core/agents/offSecAgent/index.js +225 -0
- package/build/lib/core/agents/offSecAgent/interruptedStepFinalization.d.ts +23 -0
- package/build/lib/core/agents/offSecAgent/interruptedStepMessages.d.ts +42 -0
- package/build/lib/core/agents/offSecAgent/messagePersistence.d.ts +39 -0
- package/build/lib/core/agents/offSecAgent/offensiveSecurityAgent.d.ts +233 -0
- package/build/lib/core/agents/offSecAgent/prompt.d.ts +35 -0
- package/build/lib/core/agents/offSecAgent/recordedTools.d.ts +11 -0
- package/build/lib/core/agents/offSecAgent/runtimeContext.d.ts +43 -0
- package/build/lib/core/agents/offSecAgent/streamDiagnostics.d.ts +67 -0
- package/build/lib/core/agents/offSecAgent/subagentSpawner.d.ts +223 -0
- package/build/lib/core/agents/offSecAgent/subagentSpawner.js +19 -0
- package/build/lib/core/agents/offSecAgent/toolLifecycle.d.ts +54 -0
- package/build/lib/core/agents/offSecAgent/tools/__tests__/sandboxScript.d.ts +1 -0
- package/build/lib/core/agents/offSecAgent/tools/addMemory.d.ts +16 -0
- package/build/lib/core/agents/offSecAgent/tools/agentScratch.d.ts +17 -0
- package/build/lib/core/agents/offSecAgent/tools/applyPatch.d.ts +8 -0
- package/build/lib/core/agents/offSecAgent/tools/applyPatchImpl.d.ts +24 -0
- package/build/lib/core/agents/offSecAgent/tools/askUserQuestions.d.ts +42 -0
- package/build/lib/core/agents/offSecAgent/tools/browserHeaderRouting.d.ts +9 -0
- package/build/lib/core/agents/offSecAgent/tools/browserToolFactories.d.ts +40 -0
- package/build/lib/core/agents/offSecAgent/tools/browserTools.d.ts +96 -0
- package/build/lib/core/agents/offSecAgent/tools/callbackListener.d.ts +212 -0
- package/build/lib/core/agents/offSecAgent/tools/camoufox.d.ts +106 -0
- package/build/lib/core/agents/offSecAgent/tools/checkpointState.d.ts +17 -0
- package/build/lib/core/agents/offSecAgent/tools/completeAuthentication.d.ts +39 -0
- package/build/lib/core/agents/offSecAgent/tools/crawlAuthenticated.d.ts +36 -0
- package/build/lib/core/agents/offSecAgent/tools/createAttackSurfaceReport.d.ts +31 -0
- package/build/lib/core/agents/offSecAgent/tools/createFile.d.ts +12 -0
- package/build/lib/core/agents/offSecAgent/tools/createTask.d.ts +20 -0
- package/build/lib/core/agents/offSecAgent/tools/delegateAuth.d.ts +72 -0
- package/build/lib/core/agents/offSecAgent/tools/deleteFile.d.ts +10 -0
- package/build/lib/core/agents/offSecAgent/tools/destructiveGuard.d.ts +94 -0
- package/build/lib/core/agents/offSecAgent/tools/detectAuthScheme.d.ts +72 -0
- package/build/lib/core/agents/offSecAgent/tools/documentApp.d.ts +38 -0
- package/build/lib/core/agents/offSecAgent/tools/documentEndpoint.d.ts +111 -0
- package/build/lib/core/agents/offSecAgent/tools/documentFinding.d.ts +285 -0
- package/build/lib/core/agents/offSecAgent/tools/email/adapters.d.ts +45 -0
- package/build/lib/core/agents/offSecAgent/tools/email/adapters.js +8 -0
- package/build/lib/core/agents/offSecAgent/tools/email/getAttachments.d.ts +33 -0
- package/build/lib/core/agents/offSecAgent/tools/email/getMessage.d.ts +20 -0
- package/build/lib/core/agents/offSecAgent/tools/email/index.d.ts +145 -0
- package/build/lib/core/agents/offSecAgent/tools/email/listInboxes.d.ts +23 -0
- package/build/lib/core/agents/offSecAgent/tools/email/listMessages.d.ts +22 -0
- package/build/lib/core/agents/offSecAgent/tools/email/markRead.d.ts +20 -0
- package/build/lib/core/agents/offSecAgent/tools/email/searchMessages.d.ts +24 -0
- package/build/lib/core/agents/offSecAgent/tools/email/sendEmail.d.ts +28 -0
- package/build/lib/core/agents/offSecAgent/tools/email/types.d.ts +40 -0
- package/build/lib/core/agents/offSecAgent/tools/email/types.js +0 -0
- package/build/lib/core/agents/offSecAgent/tools/executeCommand.d.ts +84 -0
- package/build/lib/core/agents/offSecAgent/tools/extractJsEndpoints.d.ts +13 -0
- package/build/lib/core/agents/offSecAgent/tools/fileWorkspace.d.ts +21 -0
- package/build/lib/core/agents/offSecAgent/tools/fileWorkspaceRemote.d.ts +13 -0
- package/build/lib/core/agents/offSecAgent/tools/getMemory.d.ts +12 -0
- package/build/lib/core/agents/offSecAgent/tools/getPage.d.ts +17 -0
- package/build/lib/core/agents/offSecAgent/tools/gitDiff.d.ts +12 -0
- package/build/lib/core/agents/offSecAgent/tools/gitStatus.d.ts +10 -0
- package/build/lib/core/agents/offSecAgent/tools/glob.d.ts +8 -0
- package/build/lib/core/agents/offSecAgent/tools/globImpl.d.ts +13 -0
- package/build/lib/core/agents/offSecAgent/tools/globPattern.d.ts +7 -0
- package/build/lib/core/agents/offSecAgent/tools/grep.d.ts +10 -0
- package/build/lib/core/agents/offSecAgent/tools/grepImpl.d.ts +34 -0
- package/build/lib/core/agents/offSecAgent/tools/httpRequest.d.ts +70 -0
- package/build/lib/core/agents/offSecAgent/tools/index.d.ts +466 -0
- package/build/lib/core/agents/offSecAgent/tools/index.js +262 -0
- package/build/lib/core/agents/offSecAgent/tools/listFiles.d.ts +9 -0
- package/build/lib/core/agents/offSecAgent/tools/listFilesImpl.d.ts +27 -0
- package/build/lib/core/agents/offSecAgent/tools/listMemories.d.ts +13 -0
- package/build/lib/core/agents/offSecAgent/tools/listPromptInjections.d.ts +18 -0
- package/build/lib/core/agents/offSecAgent/tools/listTasks.d.ts +24 -0
- package/build/lib/core/agents/offSecAgent/tools/patchApply.d.ts +24 -0
- package/build/lib/core/agents/offSecAgent/tools/patchParse.d.ts +24 -0
- package/build/lib/core/agents/offSecAgent/tools/perCommandShell.d.ts +82 -0
- package/build/lib/core/agents/offSecAgent/tools/playwrightMcp.d.ts +369 -0
- package/build/lib/core/agents/offSecAgent/tools/probeAuthEndpoints.d.ts +28 -0
- package/build/lib/core/agents/offSecAgent/tools/profileCodebase.d.ts +33 -0
- package/build/lib/core/agents/offSecAgent/tools/provideComparisonResults.d.ts +31 -0
- package/build/lib/core/agents/offSecAgent/tools/queryWhiteboxCatalog.d.ts +17 -0
- package/build/lib/core/agents/offSecAgent/tools/readFile.d.ts +11 -0
- package/build/lib/core/agents/offSecAgent/tools/readFileImpl.d.ts +31 -0
- package/build/lib/core/agents/offSecAgent/tools/readSkill.d.ts +15 -0
- package/build/lib/core/agents/offSecAgent/tools/reportError.d.ts +31 -0
- package/build/lib/core/agents/offSecAgent/tools/response.d.ts +15 -0
- package/build/lib/core/agents/offSecAgent/tools/runAttackSurface.d.ts +81 -0
- package/build/lib/core/agents/offSecAgent/tools/runCodeQuery.d.ts +44 -0
- package/build/lib/core/agents/offSecAgent/tools/runPentestWorkflow.d.ts +32 -0
- package/build/lib/core/agents/offSecAgent/tools/runWhiteboxScan.d.ts +52 -0
- package/build/lib/core/agents/offSecAgent/tools/sandbox.d.ts +30 -0
- package/build/lib/core/agents/offSecAgent/tools/sandboxPlaywright.d.ts +137 -0
- package/build/lib/core/agents/offSecAgent/tools/sandboxScript.d.ts +6 -0
- package/build/lib/core/agents/offSecAgent/tools/scopeGuard.d.ts +91 -0
- package/build/lib/core/agents/offSecAgent/tools/scriptSyntaxCheck.d.ts +45 -0
- package/build/lib/core/agents/offSecAgent/tools/smsInbox.d.ts +67 -0
- package/build/lib/core/agents/offSecAgent/tools/smsInbox.js +6 -0
- package/build/lib/core/agents/offSecAgent/tools/smsListMessages.d.ts +29 -0
- package/build/lib/core/agents/offSecAgent/tools/spawnCodingAgent.d.ts +43 -0
- package/build/lib/core/agents/offSecAgent/tools/spawnPentestAgent.d.ts +51 -0
- package/build/lib/core/agents/offSecAgent/tools/spawnPentestSwarm.d.ts +52 -0
- package/build/lib/core/agents/offSecAgent/tools/submitPlan.d.ts +12 -0
- package/build/lib/core/agents/offSecAgent/tools/testEndpointVariations.d.ts +34 -0
- package/build/lib/core/agents/offSecAgent/tools/threatModelGenerator.d.ts +32 -0
- package/build/lib/core/agents/offSecAgent/tools/toolOutput.d.ts +23 -0
- package/build/lib/core/agents/offSecAgent/tools/types.d.ts +215 -0
- package/build/lib/core/agents/offSecAgent/tools/updateFile.d.ts +14 -0
- package/build/lib/core/agents/offSecAgent/tools/updateTask.d.ts +20 -0
- package/build/lib/core/agents/offSecAgent/tools/validateDiscovery.d.ts +24 -0
- package/build/lib/core/agents/offSecAgent/tools/webSearch.d.ts +16 -0
- package/build/lib/core/agents/offSecAgent/tools/whiteboxCandidates.d.ts +108 -0
- package/build/lib/core/agents/offSecAgent/tools/whiteboxJobs.d.ts +157 -0
- package/build/lib/core/agents/offSecAgent/tools/windowsCurl.d.ts +20 -0
- package/build/lib/core/agents/offSecAgent/tools/workspaceApps.d.ts +120 -0
- package/build/lib/core/agents/offSecAgent/tools/workspaceDomains.d.ts +22 -0
- package/build/lib/core/agents/offSecAgent/tools/workspaceFailure.d.ts +6 -0
- package/build/lib/core/agents/offSecAgent/tools/writePlan.d.ts +13 -0
- package/build/lib/core/agents/offSecAgent/trace.d.ts +268 -0
- package/build/lib/core/agents/offSecAgent/types.d.ts +543 -0
- package/build/lib/core/agents/offSecAgent/types.js +7 -0
- package/build/lib/core/agents/specialized/attackSurface/blackboxAgent.d.ts +66 -0
- package/build/lib/core/agents/specialized/attackSurface/blackboxAgent.js +33 -0
- package/build/lib/core/agents/specialized/attackSurface/blackboxRiskScoring.d.ts +30 -0
- package/build/lib/core/agents/specialized/attackSurface/grpcSchema.d.ts +93 -0
- package/build/lib/core/agents/specialized/attackSurface/jsExtraction.d.ts +42 -0
- package/build/lib/core/agents/specialized/attackSurface/prompts.d.ts +1 -0
- package/build/lib/core/agents/specialized/attackSurface/schemas.d.ts +284 -0
- package/build/lib/core/agents/specialized/attackSurface/schemas.js +144 -0
- package/build/lib/core/agents/specialized/attackSurface/types.d.ts +32 -0
- package/build/lib/core/agents/specialized/authenticationAgent/agent.d.ts +73 -0
- package/build/lib/core/agents/specialized/authenticationAgent/index.d.ts +4 -0
- package/build/lib/core/agents/specialized/authenticationAgent/index.js +71 -0
- package/build/lib/core/agents/specialized/authenticationAgent/prompts.d.ts +10 -0
- package/build/lib/core/agents/specialized/authenticationAgent/types.d.ts +620 -0
- package/build/lib/core/agents/specialized/benchmark/comparisonAgent.d.ts +10 -0
- package/build/lib/core/agents/specialized/benchmark/docker-utils.d.ts +17 -0
- package/build/lib/core/agents/specialized/benchmark/flag-detector.d.ts +60 -0
- package/build/lib/core/agents/specialized/benchmark/index.d.ts +8 -0
- package/build/lib/core/agents/specialized/benchmark/prompts.d.ts +1 -0
- package/build/lib/core/agents/specialized/benchmark/remote/circuit-breaker.d.ts +22 -0
- package/build/lib/core/agents/specialized/benchmark/remote/daytona-wrapper.d.ts +33 -0
- package/build/lib/core/agents/specialized/benchmark/remote/index.d.ts +2 -0
- package/build/lib/core/agents/specialized/benchmark/types.d.ts +92 -0
- package/build/lib/core/agents/specialized/benchmarkComparisonAgent.d.ts +27 -0
- package/build/lib/core/agents/specialized/codeAgent/agent.d.ts +50 -0
- package/build/lib/core/agents/specialized/codeAgent/agent.js +31 -0
- package/build/lib/core/agents/specialized/codeAgent/prompts.d.ts +1 -0
- package/build/lib/core/agents/specialized/codeAgent/schemas.d.ts +11 -0
- package/build/lib/core/agents/specialized/codeAgent/schemas.js +10 -0
- package/build/lib/core/agents/specialized/cvssScorer/index.d.ts +56 -0
- package/build/lib/core/agents/specialized/environment/agent.d.ts +43 -0
- package/build/lib/core/agents/specialized/environment/index.d.ts +4 -0
- package/build/lib/core/agents/specialized/environment/prompts.d.ts +10 -0
- package/build/lib/core/agents/specialized/environment/types.d.ts +37 -0
- package/build/lib/core/agents/specialized/findingJudge/agent.d.ts +22 -0
- package/build/lib/core/agents/specialized/findingJudge/agent.js +224 -0
- package/build/lib/core/agents/specialized/findingJudge/index.d.ts +31 -0
- package/build/lib/core/agents/specialized/findingJudge/index.js +13 -0
- package/build/lib/core/agents/specialized/findingJudge/prompts.d.ts +3 -0
- package/build/lib/core/agents/specialized/findingJudge/types.d.ts +77 -0
- package/build/lib/core/agents/specialized/googleSignInPrompt.d.ts +4 -0
- package/build/lib/core/agents/specialized/mobileOtpPrompt.d.ts +1 -0
- package/build/lib/core/agents/specialized/patching/agent.d.ts +64 -0
- package/build/lib/core/agents/specialized/patching/index.d.ts +4 -0
- package/build/lib/core/agents/specialized/patching/index.js +44 -0
- package/build/lib/core/agents/specialized/patching/prompts.d.ts +17 -0
- package/build/lib/core/agents/specialized/patching/types.d.ts +40 -0
- package/build/lib/core/agents/specialized/pentest/agent.d.ts +172 -0
- package/build/lib/core/agents/specialized/pentest/agent.js +43 -0
- package/build/lib/core/agents/specialized/pentest/helperWorkspace.d.ts +16 -0
- package/build/lib/core/agents/specialized/pentest/planPrompt.d.ts +13 -0
- package/build/lib/core/agents/specialized/utils.d.ts +7 -0
- package/build/lib/core/agents/specialized/vulnerabilityReproduction/agent.d.ts +94 -0
- package/build/lib/core/agents/specialized/whiteboxAttackSurface/agent.d.ts +148 -0
- package/build/lib/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.d.ts +61 -0
- package/build/lib/core/agents/specialized/whiteboxAttackSurface/index.d.ts +5 -0
- package/build/lib/core/agents/specialized/whiteboxAttackSurface/index.js +47 -0
- package/build/lib/core/agents/specialized/whiteboxAttackSurface/prompts.d.ts +34 -0
- package/build/lib/core/agents/specialized/whiteboxAttackSurface/types.d.ts +382 -0
- package/build/lib/core/agents/specialized/whiteboxAttackSurface/types.js +21 -0
- package/build/lib/core/ai/ai.d.ts +332 -0
- package/build/lib/core/ai/ai.js +68 -0
- package/build/lib/core/ai/caching.d.ts +26 -0
- package/build/lib/core/ai/compactionEvidence.d.ts +15 -0
- package/build/lib/core/ai/compactionTelemetry.d.ts +55 -0
- package/build/lib/core/ai/contextManagement.d.ts +85 -0
- package/build/lib/core/ai/index.d.ts +10 -0
- package/build/lib/core/ai/index.js +72 -0
- package/build/lib/core/ai/inference-attempt/adapters.d.ts +15 -0
- package/build/lib/core/ai/inference-attempt/builder.d.ts +31 -0
- package/build/lib/core/ai/inference-attempt/envelope.d.ts +120 -0
- package/build/lib/core/ai/inference-attempt/errors.d.ts +6 -0
- package/build/lib/core/ai/inference-attempt/identity.d.ts +13 -0
- package/build/lib/core/ai/inference-attempt/index.d.ts +36 -0
- package/build/lib/core/ai/inference-attempt/project.d.ts +21 -0
- package/build/lib/core/ai/inference-attempt/recording.d.ts +23 -0
- package/build/lib/core/ai/inference-attempt/tokens.d.ts +13 -0
- package/build/lib/core/ai/inference-attempt/validate.d.ts +2 -0
- package/build/lib/core/ai/mantle.d.ts +28 -0
- package/build/lib/core/ai/model-history.d.ts +4 -0
- package/build/lib/core/ai/models/anthropic.d.ts +2 -0
- package/build/lib/core/ai/models/bedrock.d.ts +2 -0
- package/build/lib/core/ai/models/claude-capabilities.d.ts +6 -0
- package/build/lib/core/ai/models/concentrate.d.ts +2 -0
- package/build/lib/core/ai/models/google.d.ts +2 -0
- package/build/lib/core/ai/models/inception.d.ts +2 -0
- package/build/lib/core/ai/models/index.d.ts +29 -0
- package/build/lib/core/ai/models/mantle.d.ts +2 -0
- package/build/lib/core/ai/models/openai.d.ts +2 -0
- package/build/lib/core/ai/models/openrouter.d.ts +2 -0
- package/build/lib/core/ai/models/pensar.d.ts +2 -0
- package/build/lib/core/ai/native-rollout-evidence/capture.d.ts +31 -0
- package/build/lib/core/ai/native-rollout-evidence/content.d.ts +14 -0
- package/build/lib/core/ai/native-rollout-evidence/index.d.ts +9 -0
- package/build/lib/core/ai/native-rollout-evidence/provider-metadata.d.ts +15 -0
- package/build/lib/core/ai/native-rollout-evidence/schema.d.ts +554 -0
- package/build/lib/core/ai/native-rollout-evidence/validate.d.ts +8 -0
- package/build/lib/core/ai/providers/anthropic.d.ts +2 -0
- package/build/lib/core/ai/providers/concentrate.d.ts +9 -0
- package/build/lib/core/ai/providers/custom.d.ts +3 -0
- package/build/lib/core/ai/providers/hoonify.d.ts +6 -0
- package/build/lib/core/ai/providers/openai-pro-fetch.d.ts +2 -0
- package/build/lib/core/ai/providers/pensar.d.ts +34 -0
- package/build/lib/core/ai/providers/pensarFormatters.d.ts +2 -0
- package/build/lib/core/ai/providers/pensarResponsesStream.d.ts +15 -0
- package/build/lib/core/ai/providers/pensarSSE.d.ts +23 -0
- package/build/lib/core/ai/providers/pensarSigning.d.ts +1 -0
- package/build/lib/core/ai/providers/pensarStreamUsage.d.ts +19 -0
- package/build/lib/core/ai/streamTelemetry.d.ts +22 -0
- package/build/lib/core/ai/utils.d.ts +90 -0
- package/build/lib/core/ai/utils.js +22 -0
- package/build/lib/core/api/apiClient.d.ts +7 -0
- package/build/lib/core/api/apps.d.ts +213 -0
- package/build/lib/core/api/attackSurface.d.ts +14 -0
- package/build/lib/core/api/authentication.d.ts +3 -0
- package/build/lib/core/api/benchmark.d.ts +5 -0
- package/build/lib/core/api/blackboxPentest.d.ts +11 -0
- package/build/lib/core/api/clientIdentity.d.ts +10 -0
- package/build/lib/core/api/constants.d.ts +5 -0
- package/build/lib/core/api/domains.d.ts +19 -0
- package/build/lib/core/api/environment.d.ts +16 -0
- package/build/lib/core/api/index.d.ts +41 -0
- package/build/lib/core/api/index.js +187 -0
- package/build/lib/core/api/issues.d.ts +273 -0
- package/build/lib/core/api/knowledge.d.ts +33 -0
- package/build/lib/core/api/localWorker.d.ts +4 -0
- package/build/lib/core/api/offesecAgent.d.ts +34 -0
- package/build/lib/core/api/patching.d.ts +15 -0
- package/build/lib/core/api/recordedRun.d.ts +36 -0
- package/build/lib/core/api/targetedPentest.d.ts +15 -0
- package/build/lib/core/api/targetedPentest.js +32 -0
- package/build/lib/core/api/threatModel.d.ts +1 -0
- package/build/lib/core/api/trajectoryExport.d.ts +38 -0
- package/build/lib/core/api/vulnerabilityReproduction.d.ts +10 -0
- package/build/lib/core/assets/wordlists.d.ts +31 -0
- package/build/lib/core/atif/convert.d.ts +27 -0
- package/build/lib/core/atif/index.d.ts +7 -0
- package/build/lib/core/atif/schema.d.ts +459 -0
- package/build/lib/core/atif/serialize.d.ts +2 -0
- package/build/lib/core/atif/test-fixtures.d.ts +28 -0
- package/build/lib/core/atif/validate.d.ts +12 -0
- package/build/lib/core/auth/connection.d.ts +12 -0
- package/build/lib/core/auth/device-flow.d.ts +38 -0
- package/build/lib/core/auth/gateway.d.ts +25 -0
- package/build/lib/core/auth/index.d.ts +8 -0
- package/build/lib/core/auth/signing.d.ts +14 -0
- package/build/lib/core/auth/token.d.ts +17 -0
- package/build/lib/core/auth/types.d.ts +85 -0
- package/build/lib/core/auth/workspaces.d.ts +34 -0
- package/build/lib/core/benchmark/index.d.ts +3 -0
- package/build/lib/core/benchmark/report.d.ts +3 -0
- package/build/lib/core/benchmark/runner.d.ts +3 -0
- package/build/lib/core/benchmark/types.d.ts +80 -0
- package/build/lib/core/cli/logLevelArgs.d.ts +18 -0
- package/build/lib/core/cli/model.d.ts +9 -0
- package/build/lib/core/cli/pentestMode.d.ts +5 -0
- package/build/lib/core/cli/tuiArgs.d.ts +5 -0
- package/build/lib/core/config/config.d.ts +86 -0
- package/build/lib/core/config/customProviders.d.ts +62 -0
- package/build/lib/core/config/index.d.ts +6 -0
- package/build/lib/core/config/index.js +6 -0
- package/build/lib/core/credentials/index.d.ts +2 -0
- package/build/lib/core/credentials/manager.d.ts +85 -0
- package/build/lib/core/credentials/types.d.ts +79 -0
- package/build/lib/core/doctor.d.ts +1 -0
- package/build/lib/core/eventBus.d.ts +195 -0
- package/build/lib/core/eventBus.js +6 -0
- package/build/lib/core/findings/attackSurfaceRegistry.d.ts +88 -0
- package/build/lib/core/findings/attackSurfaceRegistry.js +6 -0
- package/build/lib/core/findings/registry.d.ts +177 -0
- package/build/lib/core/findings/registry.js +21 -0
- package/build/lib/core/history.d.ts +23 -0
- package/build/lib/core/hoonify.d.ts +8 -0
- package/build/lib/core/http/parse.d.ts +50 -0
- package/build/lib/core/http/redirects.d.ts +23 -0
- package/build/lib/core/http/targetHeaders.d.ts +30 -0
- package/build/lib/core/http/targetScope.d.ts +12 -0
- package/build/lib/core/http/types.d.ts +11 -0
- package/build/lib/core/id/id.d.ts +60 -0
- package/build/lib/core/id/id.js +28 -0
- package/build/lib/core/installation/index.d.ts +25 -0
- package/build/lib/core/integrations/herdr.d.ts +13 -0
- package/build/lib/core/integrations/surface/index.d.ts +37 -0
- package/build/lib/core/integrations/surface/types.d.ts +22 -0
- package/build/lib/core/integrations/wandb/client.d.ts +23 -0
- package/build/lib/core/integrations/wandb/upload.d.ts +36 -0
- package/build/lib/core/logger/index.d.ts +62 -0
- package/build/lib/core/logger/structured.d.ts +30 -0
- package/build/lib/core/memory/index.d.ts +92 -0
- package/build/lib/core/memory/index.js +21 -0
- package/build/lib/core/messages/types.d.ts +96 -0
- package/build/lib/core/obfuscation/engine.d.ts +51 -0
- package/build/lib/core/obfuscation/index.d.ts +20 -0
- package/build/lib/core/observability/active-root-spans.d.ts +5 -0
- package/build/lib/core/observability/active-spans.d.ts +22 -0
- package/build/lib/core/observability/index.d.ts +36 -0
- package/build/lib/core/observability/index.js +27 -0
- package/build/lib/core/observability/runtime.d.ts +63 -0
- package/build/lib/core/observability/telemetry.d.ts +52 -0
- package/build/lib/core/observability/testkit.d.ts +24 -0
- package/build/lib/core/operator/approvalGate.d.ts +61 -0
- package/build/lib/core/operator/index.d.ts +9 -0
- package/build/lib/core/operator/types.d.ts +167 -0
- package/build/lib/core/plan/index.d.ts +16 -0
- package/build/lib/core/prompt-injections/index.d.ts +111 -0
- package/build/lib/core/prompt-injections/index.js +24 -0
- package/build/lib/core/providers/index.d.ts +5 -0
- package/build/lib/core/providers/types.d.ts +13 -0
- package/build/lib/core/providers/utils.d.ts +15 -0
- package/build/lib/core/providers/verify.d.ts +11 -0
- package/build/lib/core/report/builder.d.ts +9 -0
- package/build/lib/core/report/index.d.ts +7 -0
- package/build/lib/core/report/renderers/json.d.ts +2 -0
- package/build/lib/core/report/renderers/markdown.d.ts +2 -0
- package/build/lib/core/report/schemas.d.ts +115 -0
- package/build/lib/core/runtime/agentExecution.d.ts +15 -0
- package/build/lib/core/runtime/launchLocalWorker.d.ts +33 -0
- package/build/lib/core/runtime/localRunLock.d.ts +9 -0
- package/build/lib/core/runtime/localRunWorker.d.ts +18 -0
- package/build/lib/core/runtime/localWorkerEndpoint.d.ts +14 -0
- package/build/lib/core/runtime/localWorkerProtocol.d.ts +294 -0
- package/build/lib/core/runtime/localWorkerTransport.d.ts +30 -0
- package/build/lib/core/runtime/persistenceError.d.ts +9 -0
- package/build/lib/core/runtime/recordedExecution.d.ts +6 -0
- package/build/lib/core/runtime/recordedRunClient.d.ts +34 -0
- package/build/lib/core/runtime/runCheckpointStore.d.ts +11 -0
- package/build/lib/core/runtime/runContext.d.ts +48 -0
- package/build/lib/core/runtime/runControl.d.ts +44 -0
- package/build/lib/core/runtime/runControlStore.d.ts +83 -0
- package/build/lib/core/runtime/runDeadline.d.ts +4 -0
- package/build/lib/core/runtime/runEvidence.d.ts +37 -0
- package/build/lib/core/runtime/runInference.d.ts +9 -0
- package/build/lib/core/runtime/runModelStore.d.ts +27 -0
- package/build/lib/core/runtime/runObservation.d.ts +19 -0
- package/build/lib/core/runtime/runRecoveryContext.d.ts +20 -0
- package/build/lib/core/runtime/runRecoveryPlan.d.ts +38 -0
- package/build/lib/core/runtime/runRecoveryStore.d.ts +77 -0
- package/build/lib/core/runtime/runStore.d.ts +133 -0
- package/build/lib/core/runtime/runStorePath.d.ts +1 -0
- package/build/lib/core/runtime/runToolStore.d.ts +50 -0
- package/build/lib/core/runtime/runTools.d.ts +16 -0
- package/build/lib/core/runtime/sqliteControlStore.d.ts +41 -0
- package/build/lib/core/runtime/sqliteModelStore.d.ts +20 -0
- package/build/lib/core/runtime/sqliteRecoveryStore.d.ts +35 -0
- package/build/lib/core/runtime/sqliteRunStore.d.ts +8 -0
- package/build/lib/core/runtime/sqliteToolStore.d.ts +30 -0
- package/build/lib/core/runtime/testfixtures/localWorkerChild.d.ts +1 -0
- package/build/lib/core/runtime/testfixtures/localWorkerLauncher.d.ts +1 -0
- package/build/lib/core/runtime/testfixtures/recoveryChild.d.ts +1 -0
- package/build/lib/core/services/rateLimiter/index.d.ts +36 -0
- package/build/lib/core/services/rateLimiter/types.d.ts +10 -0
- package/build/lib/core/session/execution-metrics.d.ts +37 -0
- package/build/lib/core/session/index.d.ts +586 -0
- package/build/lib/core/session/index.js +36 -0
- package/build/lib/core/session/loader.d.ts +30 -0
- package/build/lib/core/session/persistence.d.ts +215 -0
- package/build/lib/core/session/persistence.js +35 -0
- package/build/lib/core/session/types.d.ts +59 -0
- package/build/lib/core/session/usage.d.ts +37 -0
- package/build/lib/core/skills/builtins/index.d.ts +13 -0
- package/build/lib/core/skills/builtins/pentest.d.ts +19 -0
- package/build/lib/core/skills/builtins/promptInjection.d.ts +2 -0
- package/build/lib/core/skills/builtins/threatModel.d.ts +11 -0
- package/build/lib/core/skills/index.d.ts +5 -0
- package/build/lib/core/skills/parser.d.ts +11 -0
- package/build/lib/core/skills/registry.d.ts +30 -0
- package/build/lib/core/skills/scanner.d.ts +12 -0
- package/build/lib/core/skills/types.d.ts +44 -0
- package/build/lib/core/skills/utils.d.ts +6 -0
- package/build/lib/core/storage/index.d.ts +248 -0
- package/build/lib/core/tasks/index.d.ts +78 -0
- package/build/lib/core/tools/backends/capture.d.ts +10 -0
- package/build/lib/core/tools/backends/collectCommand.d.ts +9 -0
- package/build/lib/core/tools/backends/helpers.d.ts +43 -0
- package/build/lib/core/tools/backends/httpTransport.d.ts +18 -0
- package/build/lib/core/tools/backends/index.d.ts +8 -0
- package/build/lib/core/tools/backends/index.js +31 -0
- package/build/lib/core/tools/backends/local.d.ts +11 -0
- package/build/lib/core/tools/backends/policy.d.ts +39 -0
- package/build/lib/core/tools/backends/resolve.d.ts +25 -0
- package/build/lib/core/tools/backends/types.d.ts +240 -0
- package/build/lib/core/tools/backends/whitebox.d.ts +6 -0
- package/build/lib/core/tools/backends/whiteboxJobs.d.ts +18 -0
- package/build/lib/core/tools/backends/windowsProgram.d.ts +4 -0
- package/build/lib/core/tools/daytonaSandbox.d.ts +14 -0
- package/build/lib/core/toolset/index.d.ts +28 -0
- package/build/lib/core/util/lazyLogger.d.ts +16 -0
- package/build/lib/core/utils/concurrency.d.ts +16 -0
- package/build/lib/core/utils/prompt.d.ts +12 -0
- package/build/lib/core/whitebox/adapters.d.ts +29 -0
- package/build/lib/core/whitebox/artifacts.d.ts +32 -0
- package/build/lib/core/whitebox/boundedProcess.d.ts +35 -0
- package/build/lib/core/whitebox/candidates.d.ts +29 -0
- package/build/lib/core/whitebox/catalog.d.ts +10 -0
- package/build/lib/core/whitebox/index.d.ts +9 -0
- package/build/lib/core/whitebox/jobKernel.d.ts +33 -0
- package/build/lib/core/whitebox/jobSupervisor.d.ts +14 -0
- package/build/lib/core/whitebox/jobs.d.ts +17 -0
- package/build/lib/core/whitebox/paths.d.ts +22 -0
- package/build/lib/core/whitebox/profileTransport.d.ts +10 -0
- package/build/lib/core/whitebox/profiles.d.ts +3 -0
- package/build/lib/core/whitebox/repoProfile.d.ts +4 -0
- package/build/lib/core/whitebox/types.d.ts +108 -0
- package/build/lib/core/workflows/fastStrike.d.ts +4 -0
- package/build/lib/core/workflows/pentest.d.ts +167 -0
- package/build/lib/core/workflows/pentest.js +42 -0
- package/build/lib/core/workflows/seams.d.ts +102 -0
- package/build/lib/core/workflows/seams.js +37 -0
- package/build/lib/core/workflows/threatModel.d.ts +26 -0
- package/build/lib/core/workflows/whiteboxAttackSurface.d.ts +142 -0
- package/build/lib/core/workflows/whiteboxAttackSurface.js +36 -0
- package/build/lib/index.d.ts +26 -0
- package/build/lib/index.js +96 -0
- package/build/lib/lib/attack-path/types.d.ts +9 -0
- package/build/lib/lib/cvss/calculator.d.ts +31 -0
- package/build/lib/lib/cvss/index.d.ts +24 -0
- package/build/lib/lib/cvss/index.js +24 -0
- package/build/lib/lib/cvss/macrovector-scores.d.ts +59 -0
- package/build/lib/lib/cvss/types.d.ts +114 -0
- package/build/lib/lib/cvss/types.js +8 -0
- package/build/lib/lib/cwe/cwe-catalog.d.ts +13 -0
- package/build/lib/lib/cwe/types.d.ts +28 -0
- package/build/lib/lib/cwe/validate.d.ts +15 -0
- package/build/lib/lib/evidence/types.d.ts +12 -0
- package/build/lib/util/errors.d.ts +186 -0
- package/build/lib/util/lock.d.ts +2 -0
- package/build/lib/util/name.d.ts +14 -0
- package/build/lib/util/url.d.ts +40 -0
- package/build/{logs-wzr5176k.js → logs-1r7nkzhg.js} +22 -30
- package/build/{multipart-parser-g2xxkezz.js → multipart-parser-vjjdbpd1.js} +10 -10
- package/build/native-rollout-evidence-y0vdtjje.js +135 -0
- package/build/offesecAgent-dfj42jkj.js +22 -0
- package/build/{parse-rtccymzn.js → parse-cqecqrc5.js} +0 -4
- package/build/pentest-n8k0dd5q.js +22 -0
- package/build/pentests-jh9az51h.js +143 -0
- package/build/recordedRunClient-mvendk9n.js +11 -0
- package/build/{targetedPentest-tdwtgmxm.js → targetedPentest-rgffxsgd.js} +22 -22
- package/build/{targets-763n91t7.js → targets-wn0gmdhe.js} +24 -32
- package/build/threatModel-6efzjpbv.js +21 -0
- package/build/{token-3bbp59y6.js → token-mk7t8y0y.js} +2 -2
- package/build/{token-util-6xjme11b.js → token-util-vd776hrv.js} +1 -2
- package/build/{uninstall-d33159cz.js → uninstall-cmv057jn.js} +3 -3
- package/build/{upload-9qj1kpt9.js → upload-vkkj6h54.js} +8 -14
- package/build/{utils-t7gc2903.js → utils-b1kae4f3.js} +29 -29
- package/package.json +230 -5
- package/src/cli/agent-runs.attach.test.ts +359 -0
- package/src/cli/agent-runs.test.ts +1172 -0
- package/src/cli/agent-runs.ts +430 -0
- package/src/cli/apps.test.ts +63 -0
- package/src/cli/apps.ts +526 -0
- package/src/cli/auth.ts +375 -0
- package/src/cli/command-exit.test.ts +15 -0
- package/src/cli/command-exit.ts +3 -0
- package/src/cli/config.ts +210 -0
- package/src/cli/export-trajectory.test.ts +224 -0
- package/src/cli/export-trajectory.ts +119 -0
- package/src/cli/fixes.ts +56 -0
- package/src/cli/issues.test.ts +214 -0
- package/src/cli/issues.ts +242 -0
- package/src/cli/json-output.test.ts +62 -0
- package/src/cli/knowledge.test.ts +140 -0
- package/src/cli/knowledge.ts +146 -0
- package/src/cli/launcher.test.ts +81 -0
- package/src/cli/logs.ts +111 -0
- package/src/cli/native-rollout-evidence.test.ts +322 -0
- package/src/cli/native-rollout-evidence.ts +206 -0
- package/src/cli/pentests.test.ts +194 -0
- package/src/cli/pentests.ts +159 -0
- package/src/cli/resume.test.ts +107 -0
- package/src/cli/targets.ts +125 -0
- package/src/cli/uninstall.ts +302 -0
- package/src/cli.ts +882 -0
- package/src/core/agents/agentRuntime.test.ts +242 -0
- package/src/core/agents/agentRuntime.ts +120 -0
- package/src/core/agents/defineAgent.ts +134 -0
- package/src/core/agents/offSecAgent/index.ts +162 -0
- package/src/core/agents/offSecAgent/interruptedStepFinalization.test.ts +295 -0
- package/src/core/agents/offSecAgent/interruptedStepFinalization.ts +162 -0
- package/src/core/agents/offSecAgent/interruptedStepMessages.test.ts +296 -0
- package/src/core/agents/offSecAgent/interruptedStepMessages.ts +137 -0
- package/src/core/agents/offSecAgent/messagePersistence.test.ts +281 -0
- package/src/core/agents/offSecAgent/messagePersistence.ts +111 -0
- package/src/core/agents/offSecAgent/offensiveSecurityAgent.runtimeContext.test.ts +689 -0
- package/src/core/agents/offSecAgent/offensiveSecurityAgent.test.ts +2899 -0
- package/src/core/agents/offSecAgent/offensiveSecurityAgent.ts +1610 -0
- package/src/core/agents/offSecAgent/prompt.test.ts +72 -0
- package/src/core/agents/offSecAgent/prompt.ts +368 -0
- package/src/core/agents/offSecAgent/recordedTools.test.ts +580 -0
- package/src/core/agents/offSecAgent/recordedTools.ts +162 -0
- package/src/core/agents/offSecAgent/runtimeContext.test.ts +531 -0
- package/src/core/agents/offSecAgent/runtimeContext.ts +387 -0
- package/src/core/agents/offSecAgent/selectiveConstruction.publicPath.test.ts +295 -0
- package/src/core/agents/offSecAgent/selectiveConstruction.test.ts +498 -0
- package/src/core/agents/offSecAgent/streamDiagnostics.test.ts +310 -0
- package/src/core/agents/offSecAgent/streamDiagnostics.ts +237 -0
- package/src/core/agents/offSecAgent/subagentSpawner.hooks.test.ts +102 -0
- package/src/core/agents/offSecAgent/subagentSpawner.test.ts +384 -0
- package/src/core/agents/offSecAgent/subagentSpawner.ts +664 -0
- package/src/core/agents/offSecAgent/toolLifecycle.test.ts +199 -0
- package/src/core/agents/offSecAgent/toolLifecycle.ts +171 -0
- package/src/core/agents/offSecAgent/tools/__tests__/sandboxScript.ts +14 -0
- package/src/core/agents/offSecAgent/tools/addMemory.ts +87 -0
- package/src/core/agents/offSecAgent/tools/agentScratch.test.ts +36 -0
- package/src/core/agents/offSecAgent/tools/agentScratch.ts +23 -0
- package/src/core/agents/offSecAgent/tools/applyPatch.test.ts +546 -0
- package/src/core/agents/offSecAgent/tools/applyPatch.ts +58 -0
- package/src/core/agents/offSecAgent/tools/applyPatchImpl.ts +300 -0
- package/src/core/agents/offSecAgent/tools/askUserQuestions.ts +81 -0
- package/src/core/agents/offSecAgent/tools/authBackendStatus.test.ts +112 -0
- package/src/core/agents/offSecAgent/tools/browserHeaderRouting.test.ts +136 -0
- package/src/core/agents/offSecAgent/tools/browserHeaderRouting.ts +81 -0
- package/src/core/agents/offSecAgent/tools/browserToolFactories.ts +372 -0
- package/src/core/agents/offSecAgent/tools/browserTools.test.ts +243 -0
- package/src/core/agents/offSecAgent/tools/browserTools.ts +285 -0
- package/src/core/agents/offSecAgent/tools/callbackListener.test.ts +1011 -0
- package/src/core/agents/offSecAgent/tools/callbackListener.ts +1038 -0
- package/src/core/agents/offSecAgent/tools/camoufox.test.ts +119 -0
- package/src/core/agents/offSecAgent/tools/camoufox.ts +199 -0
- package/src/core/agents/offSecAgent/tools/checkpointState.ts +79 -0
- package/src/core/agents/offSecAgent/tools/codingTools.windows.test.ts +625 -0
- package/src/core/agents/offSecAgent/tools/completeAuthentication.ts +136 -0
- package/src/core/agents/offSecAgent/tools/crawlAuthenticated.test.ts +267 -0
- package/src/core/agents/offSecAgent/tools/crawlAuthenticated.ts +170 -0
- package/src/core/agents/offSecAgent/tools/createAttackSurfaceReport.ts +75 -0
- package/src/core/agents/offSecAgent/tools/createFile.test.ts +111 -0
- package/src/core/agents/offSecAgent/tools/createFile.ts +44 -0
- package/src/core/agents/offSecAgent/tools/createTask.ts +118 -0
- package/src/core/agents/offSecAgent/tools/delegateAuth.ts +391 -0
- package/src/core/agents/offSecAgent/tools/deleteFile.test.ts +102 -0
- package/src/core/agents/offSecAgent/tools/deleteFile.ts +39 -0
- package/src/core/agents/offSecAgent/tools/destructiveGuard.test.ts +827 -0
- package/src/core/agents/offSecAgent/tools/destructiveGuard.ts +747 -0
- package/src/core/agents/offSecAgent/tools/detectAuthScheme.ts +191 -0
- package/src/core/agents/offSecAgent/tools/documentApp.ts +242 -0
- package/src/core/agents/offSecAgent/tools/documentEndpoint.ts +291 -0
- package/src/core/agents/offSecAgent/tools/documentFinding.test.ts +2091 -0
- package/src/core/agents/offSecAgent/tools/documentFinding.ts +1240 -0
- package/src/core/agents/offSecAgent/tools/email/adapters.test.ts +279 -0
- package/src/core/agents/offSecAgent/tools/email/adapters.ts +1121 -0
- package/src/core/agents/offSecAgent/tools/email/getAttachments.ts +73 -0
- package/src/core/agents/offSecAgent/tools/email/getMessage.ts +56 -0
- package/src/core/agents/offSecAgent/tools/email/inboxBackend.test.ts +137 -0
- package/src/core/agents/offSecAgent/tools/email/index.ts +55 -0
- package/src/core/agents/offSecAgent/tools/email/listInboxes.ts +47 -0
- package/src/core/agents/offSecAgent/tools/email/listMessages.ts +67 -0
- package/src/core/agents/offSecAgent/tools/email/markRead.ts +57 -0
- package/src/core/agents/offSecAgent/tools/email/searchMessages.ts +76 -0
- package/src/core/agents/offSecAgent/tools/email/sendEmail.ts +165 -0
- package/src/core/agents/offSecAgent/tools/email/types.ts +45 -0
- package/src/core/agents/offSecAgent/tools/executeCommand.backends.test.ts +171 -0
- package/src/core/agents/offSecAgent/tools/executeCommand.test.ts +1153 -0
- package/src/core/agents/offSecAgent/tools/executeCommand.ts +604 -0
- package/src/core/agents/offSecAgent/tools/extractJsEndpoints.ts +44 -0
- package/src/core/agents/offSecAgent/tools/fileBackends.test.ts +66 -0
- package/src/core/agents/offSecAgent/tools/fileMutations.test.ts +159 -0
- package/src/core/agents/offSecAgent/tools/fileToolBackendPurity.test.ts +72 -0
- package/src/core/agents/offSecAgent/tools/fileWorkspace.test.ts +357 -0
- package/src/core/agents/offSecAgent/tools/fileWorkspace.ts +434 -0
- package/src/core/agents/offSecAgent/tools/fileWorkspaceRemote.ts +263 -0
- package/src/core/agents/offSecAgent/tools/getMemory.ts +69 -0
- package/src/core/agents/offSecAgent/tools/getPage.test.ts +1127 -0
- package/src/core/agents/offSecAgent/tools/getPage.ts +70 -0
- package/src/core/agents/offSecAgent/tools/gitDiff.ts +67 -0
- package/src/core/agents/offSecAgent/tools/gitStatus.ts +61 -0
- package/src/core/agents/offSecAgent/tools/gitTools.test.ts +237 -0
- package/src/core/agents/offSecAgent/tools/glob.test.ts +695 -0
- package/src/core/agents/offSecAgent/tools/glob.ts +51 -0
- package/src/core/agents/offSecAgent/tools/globImpl.ts +313 -0
- package/src/core/agents/offSecAgent/tools/globPattern.ts +203 -0
- package/src/core/agents/offSecAgent/tools/grep.test.ts +922 -0
- package/src/core/agents/offSecAgent/tools/grep.ts +73 -0
- package/src/core/agents/offSecAgent/tools/grepImpl.ts +718 -0
- package/src/core/agents/offSecAgent/tools/httpBackends.test.ts +534 -0
- package/src/core/agents/offSecAgent/tools/httpRequest.test.ts +2409 -0
- package/src/core/agents/offSecAgent/tools/httpRequest.ts +425 -0
- package/src/core/agents/offSecAgent/tools/index.ts +787 -0
- package/src/core/agents/offSecAgent/tools/listFiles.bounded.test.ts +514 -0
- package/src/core/agents/offSecAgent/tools/listFiles.public-counters.test.ts +163 -0
- package/src/core/agents/offSecAgent/tools/listFiles.test.ts +521 -0
- package/src/core/agents/offSecAgent/tools/listFiles.ts +49 -0
- package/src/core/agents/offSecAgent/tools/listFilesImpl.ts +351 -0
- package/src/core/agents/offSecAgent/tools/listMemories.ts +71 -0
- package/src/core/agents/offSecAgent/tools/listPromptInjections.test.ts +201 -0
- package/src/core/agents/offSecAgent/tools/listPromptInjections.ts +78 -0
- package/src/core/agents/offSecAgent/tools/listTasks.ts +100 -0
- package/src/core/agents/offSecAgent/tools/patchApply.test.ts +573 -0
- package/src/core/agents/offSecAgent/tools/patchApply.ts +430 -0
- package/src/core/agents/offSecAgent/tools/patchParse.test.ts +383 -0
- package/src/core/agents/offSecAgent/tools/patchParse.ts +354 -0
- package/src/core/agents/offSecAgent/tools/perCommandShell.drain.test.ts +109 -0
- package/src/core/agents/offSecAgent/tools/perCommandShell.test.ts +565 -0
- package/src/core/agents/offSecAgent/tools/perCommandShell.ts +516 -0
- package/src/core/agents/offSecAgent/tools/perCommandShell.windows.test.ts +142 -0
- package/src/core/agents/offSecAgent/tools/playwrightMcp.test.ts +778 -0
- package/src/core/agents/offSecAgent/tools/playwrightMcp.ts +1426 -0
- package/src/core/agents/offSecAgent/tools/probeAuthEndpoints.ts +262 -0
- package/src/core/agents/offSecAgent/tools/profileCodebase.ts +110 -0
- package/src/core/agents/offSecAgent/tools/provideComparisonResults.ts +106 -0
- package/src/core/agents/offSecAgent/tools/queryWhiteboxCatalog.coalescing-cost.test.ts +108 -0
- package/src/core/agents/offSecAgent/tools/queryWhiteboxCatalog.test.ts +183 -0
- package/src/core/agents/offSecAgent/tools/queryWhiteboxCatalog.ts +143 -0
- package/src/core/agents/offSecAgent/tools/readFile.test.ts +1242 -0
- package/src/core/agents/offSecAgent/tools/readFile.ts +89 -0
- package/src/core/agents/offSecAgent/tools/readFileImpl.ts +956 -0
- package/src/core/agents/offSecAgent/tools/readSkill.ts +38 -0
- package/src/core/agents/offSecAgent/tools/reportError.test.ts +48 -0
- package/src/core/agents/offSecAgent/tools/reportError.ts +65 -0
- package/src/core/agents/offSecAgent/tools/response.ts +27 -0
- package/src/core/agents/offSecAgent/tools/runAttackSurface.ts +198 -0
- package/src/core/agents/offSecAgent/tools/runCodeQuery.test.ts +82 -0
- package/src/core/agents/offSecAgent/tools/runCodeQuery.ts +237 -0
- package/src/core/agents/offSecAgent/tools/runPentestWorkflow.ts +122 -0
- package/src/core/agents/offSecAgent/tools/runWhiteboxScan.ts +189 -0
- package/src/core/agents/offSecAgent/tools/runtimeBackendConsumers.test.ts +143 -0
- package/src/core/agents/offSecAgent/tools/sandbox.ts +37 -0
- package/src/core/agents/offSecAgent/tools/sandboxPlaywright.test.ts +235 -0
- package/src/core/agents/offSecAgent/tools/sandboxPlaywright.ts +1029 -0
- package/src/core/agents/offSecAgent/tools/sandboxScript.test.ts +31 -0
- package/src/core/agents/offSecAgent/tools/sandboxScript.ts +60 -0
- package/src/core/agents/offSecAgent/tools/scopeGuard.test.ts +574 -0
- package/src/core/agents/offSecAgent/tools/scopeGuard.ts +317 -0
- package/src/core/agents/offSecAgent/tools/scriptSyntaxCheck.test.ts +566 -0
- package/src/core/agents/offSecAgent/tools/scriptSyntaxCheck.ts +240 -0
- package/src/core/agents/offSecAgent/tools/selectiveFactories.test.ts +476 -0
- package/src/core/agents/offSecAgent/tools/smsInbox.ts +130 -0
- package/src/core/agents/offSecAgent/tools/smsListMessages.test.ts +443 -0
- package/src/core/agents/offSecAgent/tools/smsListMessages.ts +123 -0
- package/src/core/agents/offSecAgent/tools/spawnCodingAgent.ts +229 -0
- package/src/core/agents/offSecAgent/tools/spawnPentestAgent.test.ts +413 -0
- package/src/core/agents/offSecAgent/tools/spawnPentestAgent.ts +266 -0
- package/src/core/agents/offSecAgent/tools/spawnPentestSwarm.ts +146 -0
- package/src/core/agents/offSecAgent/tools/submitPlan.ts +51 -0
- package/src/core/agents/offSecAgent/tools/testEndpointVariations.ts +127 -0
- package/src/core/agents/offSecAgent/tools/threatModelGenerator.ts +664 -0
- package/src/core/agents/offSecAgent/tools/toolOutput.test.ts +480 -0
- package/src/core/agents/offSecAgent/tools/toolOutput.ts +160 -0
- package/src/core/agents/offSecAgent/tools/types.ts +267 -0
- package/src/core/agents/offSecAgent/tools/updateFile.ts +121 -0
- package/src/core/agents/offSecAgent/tools/updateTask.ts +107 -0
- package/src/core/agents/offSecAgent/tools/validateDiscovery.ts +123 -0
- package/src/core/agents/offSecAgent/tools/webSearch.ts +265 -0
- package/src/core/agents/offSecAgent/tools/whiteboxCandidates.ts +197 -0
- package/src/core/agents/offSecAgent/tools/whiteboxJobs.ts +312 -0
- package/src/core/agents/offSecAgent/tools/windowsCurl.test.ts +801 -0
- package/src/core/agents/offSecAgent/tools/windowsCurl.ts +202 -0
- package/src/core/agents/offSecAgent/tools/workspaceApps.test.ts +257 -0
- package/src/core/agents/offSecAgent/tools/workspaceApps.ts +334 -0
- package/src/core/agents/offSecAgent/tools/workspaceDomains.test.ts +113 -0
- package/src/core/agents/offSecAgent/tools/workspaceDomains.ts +57 -0
- package/src/core/agents/offSecAgent/tools/workspaceFailure.ts +11 -0
- package/src/core/agents/offSecAgent/tools/writePlan.ts +65 -0
- package/src/core/agents/offSecAgent/trace.test.ts +796 -0
- package/src/core/agents/offSecAgent/trace.ts +633 -0
- package/src/core/agents/offSecAgent/types.test.ts +224 -0
- package/src/core/agents/offSecAgent/types.ts +676 -0
- package/src/core/agents/specialized/attackSurface/blackboxAgent.test.ts +31 -0
- package/src/core/agents/specialized/attackSurface/blackboxAgent.ts +272 -0
- package/src/core/agents/specialized/attackSurface/blackboxRiskScoring.ts +276 -0
- package/src/core/agents/specialized/attackSurface/grpcSchema.ts +142 -0
- package/src/core/agents/specialized/attackSurface/jsExtraction.test.ts +237 -0
- package/src/core/agents/specialized/attackSurface/jsExtraction.ts +174 -0
- package/src/core/agents/specialized/attackSurface/prompts.ts +349 -0
- package/src/core/agents/specialized/attackSurface/schemas.ts +300 -0
- package/src/core/agents/specialized/attackSurface/types.ts +100 -0
- package/src/core/agents/specialized/authenticationAgent/agent.test.ts +111 -0
- package/src/core/agents/specialized/authenticationAgent/agent.ts +300 -0
- package/src/core/agents/specialized/authenticationAgent/index.ts +74 -0
- package/src/core/agents/specialized/authenticationAgent/prompts.test.ts +36 -0
- package/src/core/agents/specialized/authenticationAgent/prompts.ts +645 -0
- package/src/core/agents/specialized/authenticationAgent/types.ts +833 -0
- package/src/core/agents/specialized/benchmark/comparisonAgent.ts +344 -0
- package/src/core/agents/specialized/benchmark/docker-utils.ts +257 -0
- package/src/core/agents/specialized/benchmark/flag-detector.ts +686 -0
- package/src/core/agents/specialized/benchmark/index.ts +28 -0
- package/src/core/agents/specialized/benchmark/prompts.ts +257 -0
- package/src/core/agents/specialized/benchmark/remote/circuit-breaker.ts +108 -0
- package/src/core/agents/specialized/benchmark/remote/daytona-wrapper.ts +1005 -0
- package/src/core/agents/specialized/benchmark/remote/index.ts +2 -0
- package/src/core/agents/specialized/benchmark/types.ts +98 -0
- package/src/core/agents/specialized/benchmarkComparisonAgent.test.ts +111 -0
- package/src/core/agents/specialized/benchmarkComparisonAgent.ts +203 -0
- package/src/core/agents/specialized/codeAgent/agent.test.ts +89 -0
- package/src/core/agents/specialized/codeAgent/agent.ts +134 -0
- package/src/core/agents/specialized/codeAgent/prompts.ts +48 -0
- package/src/core/agents/specialized/codeAgent/schemas.ts +33 -0
- package/src/core/agents/specialized/cvssScorer/index.test.ts +87 -0
- package/src/core/agents/specialized/cvssScorer/index.ts +546 -0
- package/src/core/agents/specialized/environment/agent.ts +75 -0
- package/src/core/agents/specialized/environment/index.ts +16 -0
- package/src/core/agents/specialized/environment/prompts.ts +194 -0
- package/src/core/agents/specialized/environment/types.ts +48 -0
- package/src/core/agents/specialized/findingJudge/agent.test.ts +114 -0
- package/src/core/agents/specialized/findingJudge/agent.ts +59 -0
- package/src/core/agents/specialized/findingJudge/index.test.ts +155 -0
- package/src/core/agents/specialized/findingJudge/index.ts +166 -0
- package/src/core/agents/specialized/findingJudge/prompts.ts +140 -0
- package/src/core/agents/specialized/findingJudge/types.ts +115 -0
- package/src/core/agents/specialized/googleSignInPrompt.test.ts +29 -0
- package/src/core/agents/specialized/googleSignInPrompt.ts +18 -0
- package/src/core/agents/specialized/mobileOtpPrompt.ts +9 -0
- package/src/core/agents/specialized/patching/agent.test.ts +220 -0
- package/src/core/agents/specialized/patching/agent.ts +146 -0
- package/src/core/agents/specialized/patching/index.ts +20 -0
- package/src/core/agents/specialized/patching/prompts.test.ts +172 -0
- package/src/core/agents/specialized/patching/prompts.ts +396 -0
- package/src/core/agents/specialized/patching/types.ts +49 -0
- package/src/core/agents/specialized/pentest/agent.test.ts +1046 -0
- package/src/core/agents/specialized/pentest/agent.ts +1482 -0
- package/src/core/agents/specialized/pentest/helperLifecycle.test.ts +425 -0
- package/src/core/agents/specialized/pentest/helperWorkspace.test.ts +84 -0
- package/src/core/agents/specialized/pentest/helperWorkspace.ts +33 -0
- package/src/core/agents/specialized/pentest/planPrompt.ts +68 -0
- package/src/core/agents/specialized/pentest/results.test.ts +198 -0
- package/src/core/agents/specialized/utils.test.ts +16 -0
- package/src/core/agents/specialized/utils.ts +28 -0
- package/src/core/agents/specialized/vulnerabilityReproduction/agent.test.ts +195 -0
- package/src/core/agents/specialized/vulnerabilityReproduction/agent.ts +159 -0
- package/src/core/agents/specialized/whiteboxAttackSurface/agent.test.ts +39 -0
- package/src/core/agents/specialized/whiteboxAttackSurface/agent.ts +227 -0
- package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.concurrency.test.ts +138 -0
- package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.test.ts +199 -0
- package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.ts +330 -0
- package/src/core/agents/specialized/whiteboxAttackSurface/index.ts +24 -0
- package/src/core/agents/specialized/whiteboxAttackSurface/prompts.ts +345 -0
- package/src/core/agents/specialized/whiteboxAttackSurface/types.test.ts +67 -0
- package/src/core/agents/specialized/whiteboxAttackSurface/types.ts +234 -0
- package/src/core/ai/ai.contract.test.ts +43 -0
- package/src/core/ai/ai.durability-hooks.test.ts +527 -0
- package/src/core/ai/ai.effective-tools.test.ts +739 -0
- package/src/core/ai/ai.native-rollout-evidence.test.ts +373 -0
- package/src/core/ai/ai.outer-catch-recovery.test.ts +82 -0
- package/src/core/ai/ai.proactive-recovery.test.ts +69 -0
- package/src/core/ai/ai.recorded-attempts.test.ts +621 -0
- package/src/core/ai/ai.recorded-context.test.ts +448 -0
- package/src/core/ai/ai.recorded-tools.test.ts +850 -0
- package/src/core/ai/ai.request-retention.test.ts +296 -0
- package/src/core/ai/ai.test.ts +636 -0
- package/src/core/ai/ai.ts +2349 -0
- package/src/core/ai/caching.test.ts +179 -0
- package/src/core/ai/caching.ts +119 -0
- package/src/core/ai/compactionEvidence.test.ts +52 -0
- package/src/core/ai/compactionEvidence.ts +101 -0
- package/src/core/ai/compactionTelemetry.test.ts +621 -0
- package/src/core/ai/compactionTelemetry.ts +287 -0
- package/src/core/ai/concentrate.test.ts +28 -0
- package/src/core/ai/contextManagement.test.ts +350 -0
- package/src/core/ai/contextManagement.ts +594 -0
- package/src/core/ai/index.ts +73 -0
- package/src/core/ai/inference-attempt/adapters.test.ts +84 -0
- package/src/core/ai/inference-attempt/adapters.ts +391 -0
- package/src/core/ai/inference-attempt/attempt.test.ts +198 -0
- package/src/core/ai/inference-attempt/builder.ts +171 -0
- package/src/core/ai/inference-attempt/envelope.ts +120 -0
- package/src/core/ai/inference-attempt/errors.ts +15 -0
- package/src/core/ai/inference-attempt/fixtures/anthropic-messages-cache-create.json +18 -0
- package/src/core/ai/inference-attempt/fixtures/anthropic-messages-cache-read.json +18 -0
- package/src/core/ai/inference-attempt/fixtures/anthropic-messages-incomplete.json +16 -0
- package/src/core/ai/inference-attempt/fixtures/anthropic-messages-mixed.json +22 -0
- package/src/core/ai/inference-attempt/fixtures/anthropic-messages-non-cache.json +26 -0
- package/src/core/ai/inference-attempt/fixtures/bedrock-converse-cache-create.json +18 -0
- package/src/core/ai/inference-attempt/fixtures/bedrock-converse-cache-read.json +26 -0
- package/src/core/ai/inference-attempt/fixtures/bedrock-converse-incomplete.json +19 -0
- package/src/core/ai/inference-attempt/fixtures/bedrock-converse-mixed.json +20 -0
- package/src/core/ai/inference-attempt/fixtures/bedrock-converse-non-cache.json +24 -0
- package/src/core/ai/inference-attempt/fixtures/openai-responses-cache-read.json +17 -0
- package/src/core/ai/inference-attempt/fixtures/openai-responses-incomplete.json +16 -0
- package/src/core/ai/inference-attempt/fixtures/openai-responses-non-cache.json +23 -0
- package/src/core/ai/inference-attempt/fixtures/pensar-anthropic-rest-incomplete.json +21 -0
- package/src/core/ai/inference-attempt/fixtures/pensar-anthropic-rest-native.json +16 -0
- package/src/core/ai/inference-attempt/fixtures/pensar-anthropic-stream-cache-create.json +20 -0
- package/src/core/ai/inference-attempt/fixtures/pensar-anthropic-stream-cache-read.json +20 -0
- package/src/core/ai/inference-attempt/fixtures/pensar-anthropic-stream-incomplete.json +16 -0
- package/src/core/ai/inference-attempt/fixtures/pensar-anthropic-stream-mixed.json +21 -0
- package/src/core/ai/inference-attempt/fixtures/pensar-anthropic-stream-non-cache.json +19 -0
- package/src/core/ai/inference-attempt/fixtures/reject-cache-read-exceeds-inclusive.json +11 -0
- package/src/core/ai/inference-attempt/fixtures/reject-components-exceed-inclusive.json +12 -0
- package/src/core/ai/inference-attempt/fixtures/reject-identity-mismatch.json +13 -0
- package/src/core/ai/inference-attempt/fixtures/reject-negative-token.json +9 -0
- package/src/core/ai/inference-attempt/fixtures/reject-non-integer-token.json +8 -0
- package/src/core/ai/inference-attempt/fixtures/sdk-normalized-cache-read.json +19 -0
- package/src/core/ai/inference-attempt/fixtures/sdk-normalized-incomplete.json +15 -0
- package/src/core/ai/inference-attempt/fixtures/sdk-normalized-mixed.json +20 -0
- package/src/core/ai/inference-attempt/fixtures/sdk-normalized-non-cache.json +16 -0
- package/src/core/ai/inference-attempt/identity.ts +25 -0
- package/src/core/ai/inference-attempt/index.ts +70 -0
- package/src/core/ai/inference-attempt/project.test.ts +118 -0
- package/src/core/ai/inference-attempt/project.ts +60 -0
- package/src/core/ai/inference-attempt/recording.ts +46 -0
- package/src/core/ai/inference-attempt/tokens.ts +165 -0
- package/src/core/ai/inference-attempt/validate.ts +20 -0
- package/src/core/ai/mantle.test.ts +50 -0
- package/src/core/ai/mantle.ts +45 -0
- package/src/core/ai/model-history.test.ts +53 -0
- package/src/core/ai/model-history.ts +38 -0
- package/src/core/ai/models/anthropic.ts +145 -0
- package/src/core/ai/models/bedrock.ts +600 -0
- package/src/core/ai/models/claude-capabilities.ts +22 -0
- package/src/core/ai/models/concentrate.ts +192 -0
- package/src/core/ai/models/google.ts +154 -0
- package/src/core/ai/models/inception.ts +10 -0
- package/src/core/ai/models/index.ts +250 -0
- package/src/core/ai/models/mantle.ts +14 -0
- package/src/core/ai/models/models.test.ts +290 -0
- package/src/core/ai/models/openai.ts +409 -0
- package/src/core/ai/models/openrouter.ts +336 -0
- package/src/core/ai/models/pensar.ts +66 -0
- package/src/core/ai/native-rollout-evidence/capture.test.ts +1836 -0
- package/src/core/ai/native-rollout-evidence/capture.ts +1572 -0
- package/src/core/ai/native-rollout-evidence/content.test.ts +55 -0
- package/src/core/ai/native-rollout-evidence/content.ts +129 -0
- package/src/core/ai/native-rollout-evidence/index.ts +68 -0
- package/src/core/ai/native-rollout-evidence/provider-metadata.test.ts +85 -0
- package/src/core/ai/native-rollout-evidence/provider-metadata.ts +129 -0
- package/src/core/ai/native-rollout-evidence/recording.test.ts +398 -0
- package/src/core/ai/native-rollout-evidence/schema.test.ts +210 -0
- package/src/core/ai/native-rollout-evidence/schema.ts +353 -0
- package/src/core/ai/native-rollout-evidence/validate.ts +141 -0
- package/src/core/ai/providers/anthropic-models.test.ts +289 -0
- package/src/core/ai/providers/anthropic.ts +73 -0
- package/src/core/ai/providers/concentrate.live.test.ts +71 -0
- package/src/core/ai/providers/concentrate.test.ts +160 -0
- package/src/core/ai/providers/concentrate.ts +145 -0
- package/src/core/ai/providers/custom.live.test.ts +42 -0
- package/src/core/ai/providers/custom.test.ts +474 -0
- package/src/core/ai/providers/custom.ts +70 -0
- package/src/core/ai/providers/gateway-models.test.ts +638 -0
- package/src/core/ai/providers/hoonify.live.test.ts +72 -0
- package/src/core/ai/providers/hoonify.test.ts +284 -0
- package/src/core/ai/providers/hoonify.ts +32 -0
- package/src/core/ai/providers/openai-models.test.ts +356 -0
- package/src/core/ai/providers/openai-pro-fetch.test.ts +64 -0
- package/src/core/ai/providers/openai-pro-fetch.ts +32 -0
- package/src/core/ai/providers/pensar.ts +645 -0
- package/src/core/ai/providers/pensarFormatters.test.ts +272 -0
- package/src/core/ai/providers/pensarFormatters.ts +533 -0
- package/src/core/ai/providers/pensarResponsesStream.test.ts +191 -0
- package/src/core/ai/providers/pensarResponsesStream.ts +294 -0
- package/src/core/ai/providers/pensarSSE.test.ts +83 -0
- package/src/core/ai/providers/pensarSSE.ts +128 -0
- package/src/core/ai/providers/pensarSigning.ts +2 -0
- package/src/core/ai/providers/pensarStreamUsage.test.ts +79 -0
- package/src/core/ai/providers/pensarStreamUsage.ts +62 -0
- package/src/core/ai/stepContext.test.ts +61 -0
- package/src/core/ai/streamIdleTimeout.test.ts +390 -0
- package/src/core/ai/streamTelemetry.ts +90 -0
- package/src/core/ai/usage-callback.test.ts +511 -0
- package/src/core/ai/utils.native-rollout-evidence.test.ts +135 -0
- package/src/core/ai/utils.pensar.test.ts +59 -0
- package/src/core/ai/utils.ts +796 -0
- package/src/core/api/apiClient.test.ts +58 -0
- package/src/core/api/apiClient.ts +73 -0
- package/src/core/api/apps.test.ts +78 -0
- package/src/core/api/apps.ts +388 -0
- package/src/core/api/attackSurface.test.ts +24 -0
- package/src/core/api/attackSurface.ts +89 -0
- package/src/core/api/authentication.ts +8 -0
- package/src/core/api/benchmark.ts +35 -0
- package/src/core/api/blackboxPentest.test.ts +89 -0
- package/src/core/api/blackboxPentest.ts +64 -0
- package/src/core/api/clientIdentity.test.ts +61 -0
- package/src/core/api/clientIdentity.ts +57 -0
- package/src/core/api/comments.test.ts +69 -0
- package/src/core/api/constants.ts +15 -0
- package/src/core/api/domains.test.ts +35 -0
- package/src/core/api/domains.ts +32 -0
- package/src/core/api/environment.ts +47 -0
- package/src/core/api/index.ts +219 -0
- package/src/core/api/issues.test.ts +82 -0
- package/src/core/api/issues.ts +439 -0
- package/src/core/api/knowledge.ts +70 -0
- package/src/core/api/localWorker.ts +29 -0
- package/src/core/api/offesecAgent.test.ts +289 -0
- package/src/core/api/offesecAgent.ts +77 -0
- package/src/core/api/patching.ts +44 -0
- package/src/core/api/recordedRun.control.test.ts +830 -0
- package/src/core/api/recordedRun.inference.test.ts +529 -0
- package/src/core/api/recordedRun.loop.test.ts +290 -0
- package/src/core/api/recordedRun.recovery.test.ts +651 -0
- package/src/core/api/recordedRun.test.ts +646 -0
- package/src/core/api/recordedRun.ts +542 -0
- package/src/core/api/targetedPentest.test.ts +177 -0
- package/src/core/api/targetedPentest.ts +83 -0
- package/src/core/api/threatModel.ts +5 -0
- package/src/core/api/trajectoryExport.test.ts +299 -0
- package/src/core/api/trajectoryExport.ts +362 -0
- package/src/core/api/vulnerabilityReproduction.test.ts +56 -0
- package/src/core/api/vulnerabilityReproduction.ts +54 -0
- package/src/core/assets/wordlists.test.ts +85 -0
- package/src/core/assets/wordlists.ts +111 -0
- package/src/core/atif/compatibility.test.ts +107 -0
- package/src/core/atif/convert.test.ts +815 -0
- package/src/core/atif/convert.ts +1658 -0
- package/src/core/atif/fixtures/portable-bundle-v1.json +813 -0
- package/src/core/atif/index.ts +34 -0
- package/src/core/atif/schema.ts +345 -0
- package/src/core/atif/serialize.test.ts +244 -0
- package/src/core/atif/serialize.ts +419 -0
- package/src/core/atif/test-fixtures.ts +184 -0
- package/src/core/atif/validate.test.ts +188 -0
- package/src/core/atif/validate.ts +289 -0
- package/src/core/auth/connection.ts +26 -0
- package/src/core/auth/device-flow.ts +203 -0
- package/src/core/auth/gateway.ts +66 -0
- package/src/core/auth/index.ts +29 -0
- package/src/core/auth/signing.ts +25 -0
- package/src/core/auth/token.ts +140 -0
- package/src/core/auth/types.ts +106 -0
- package/src/core/auth/workspaces.ts +192 -0
- package/src/core/benchmark/index.ts +11 -0
- package/src/core/benchmark/report.ts +257 -0
- package/src/core/benchmark/runner.ts +698 -0
- package/src/core/benchmark/types.ts +80 -0
- package/src/core/cli/logLevelArgs.test.ts +83 -0
- package/src/core/cli/logLevelArgs.ts +62 -0
- package/src/core/cli/model.ts +35 -0
- package/src/core/cli/pentestMode.test.ts +24 -0
- package/src/core/cli/pentestMode.ts +23 -0
- package/src/core/cli/tuiArgs.test.ts +58 -0
- package/src/core/cli/tuiArgs.ts +28 -0
- package/src/core/config/config.test.ts +183 -0
- package/src/core/config/config.ts +201 -0
- package/src/core/config/customProviders.test.ts +183 -0
- package/src/core/config/customProviders.ts +190 -0
- package/src/core/config/index.ts +7 -0
- package/src/core/credentials/index.ts +6 -0
- package/src/core/credentials/manager.ts +313 -0
- package/src/core/credentials/types.ts +112 -0
- package/src/core/doctor.ts +138 -0
- package/src/core/eventBus.test.ts +374 -0
- package/src/core/eventBus.ts +410 -0
- package/src/core/findings/attackSurfaceRegistry.ts +294 -0
- package/src/core/findings/registry.test.ts +1680 -0
- package/src/core/findings/registry.ts +800 -0
- package/src/core/history.ts +82 -0
- package/src/core/hoonify.test.ts +250 -0
- package/src/core/hoonify.ts +163 -0
- package/src/core/http/headers.contract.test.ts +1161 -0
- package/src/core/http/parse.ts +257 -0
- package/src/core/http/redirects.test.ts +207 -0
- package/src/core/http/redirects.ts +145 -0
- package/src/core/http/targetHeaders.ts +604 -0
- package/src/core/http/targetScope.ts +52 -0
- package/src/core/http/types.ts +39 -0
- package/src/core/id/id.test.ts +88 -0
- package/src/core/id/id.ts +138 -0
- package/src/core/installation/index.ts +222 -0
- package/src/core/installation/installation.test.ts +415 -0
- package/src/core/integrations/herdr.test.ts +335 -0
- package/src/core/integrations/herdr.ts +163 -0
- package/src/core/integrations/surface/index.test.ts +534 -0
- package/src/core/integrations/surface/index.ts +224 -0
- package/src/core/integrations/surface/types.ts +29 -0
- package/src/core/integrations/wandb/client.ts +167 -0
- package/src/core/integrations/wandb/upload.test.ts +415 -0
- package/src/core/integrations/wandb/upload.ts +76 -0
- package/src/core/logger/errorLog.test.ts +98 -0
- package/src/core/logger/index.ts +214 -0
- package/src/core/logger/structured.test.ts +212 -0
- package/src/core/logger/structured.ts +207 -0
- package/src/core/memory/backend.test.ts +186 -0
- package/src/core/memory/index.ts +279 -0
- package/src/core/memory/memory.test.ts +362 -0
- package/src/core/messages/types.ts +141 -0
- package/src/core/obfuscation/engine.test.ts +314 -0
- package/src/core/obfuscation/engine.ts +647 -0
- package/src/core/obfuscation/index.ts +27 -0
- package/src/core/observability/active-root-spans.ts +30 -0
- package/src/core/observability/active-spans.ts +80 -0
- package/src/core/observability/index.ts +91 -0
- package/src/core/observability/log-correlation.test.ts +276 -0
- package/src/core/observability/payload-capture.test.ts +59 -0
- package/src/core/observability/runtime.test.ts +743 -0
- package/src/core/observability/runtime.ts +371 -0
- package/src/core/observability/shutdown.test.ts +551 -0
- package/src/core/observability/telemetry.test.ts +895 -0
- package/src/core/observability/telemetry.ts +220 -0
- package/src/core/observability/testkit.test.ts +66 -0
- package/src/core/observability/testkit.ts +97 -0
- package/src/core/observability/trace-contract.test.ts +1031 -0
- package/src/core/observability.test.ts +135 -0
- package/src/core/operator/approvalGate.test.ts +300 -0
- package/src/core/operator/approvalGate.ts +287 -0
- package/src/core/operator/index.ts +30 -0
- package/src/core/operator/types.ts +355 -0
- package/src/core/plan/index.ts +44 -0
- package/src/core/prompt-injections/index.test.ts +248 -0
- package/src/core/prompt-injections/index.ts +350 -0
- package/src/core/providers/index.ts +5 -0
- package/src/core/providers/types.ts +87 -0
- package/src/core/providers/utils.test.ts +189 -0
- package/src/core/providers/utils.ts +183 -0
- package/src/core/providers/verify.test.ts +49 -0
- package/src/core/providers/verify.ts +198 -0
- package/src/core/report/builder.test.ts +229 -0
- package/src/core/report/builder.ts +58 -0
- package/src/core/report/index.ts +13 -0
- package/src/core/report/renderers/json.test.ts +76 -0
- package/src/core/report/renderers/json.ts +5 -0
- package/src/core/report/renderers/markdown.test.ts +366 -0
- package/src/core/report/renderers/markdown.ts +133 -0
- package/src/core/report/schemas.ts +48 -0
- package/src/core/runtime/agentExecution.test.ts +94 -0
- package/src/core/runtime/agentExecution.ts +27 -0
- package/src/core/runtime/launchLocalWorker.test.ts +707 -0
- package/src/core/runtime/launchLocalWorker.ts +273 -0
- package/src/core/runtime/localRunLock.ts +118 -0
- package/src/core/runtime/localRunWorker.test.ts +381 -0
- package/src/core/runtime/localRunWorker.ts +287 -0
- package/src/core/runtime/localWorker.acceptance.test.ts +548 -0
- package/src/core/runtime/localWorkerEndpoint.test.ts +145 -0
- package/src/core/runtime/localWorkerEndpoint.ts +82 -0
- package/src/core/runtime/localWorkerProtocol.ts +176 -0
- package/src/core/runtime/localWorkerTransport.test.ts +841 -0
- package/src/core/runtime/localWorkerTransport.ts +434 -0
- package/src/core/runtime/persistenceError.ts +14 -0
- package/src/core/runtime/recordedExecution.ts +43 -0
- package/src/core/runtime/recordedRunClient.integration.test.ts +106 -0
- package/src/core/runtime/recordedRunClient.test.ts +677 -0
- package/src/core/runtime/recordedRunClient.ts +303 -0
- package/src/core/runtime/runCheckpointStore.ts +23 -0
- package/src/core/runtime/runContext.test.ts +445 -0
- package/src/core/runtime/runContext.ts +184 -0
- package/src/core/runtime/runControl.test.ts +923 -0
- package/src/core/runtime/runControl.ts +419 -0
- package/src/core/runtime/runControlStore.ts +106 -0
- package/src/core/runtime/runDeadline.test.ts +44 -0
- package/src/core/runtime/runDeadline.ts +28 -0
- package/src/core/runtime/runEvidence.test.ts +384 -0
- package/src/core/runtime/runEvidence.ts +267 -0
- package/src/core/runtime/runInference.test.ts +408 -0
- package/src/core/runtime/runInference.ts +125 -0
- package/src/core/runtime/runModelStore.ts +55 -0
- package/src/core/runtime/runObservation.ts +24 -0
- package/src/core/runtime/runRecoveryContext.test.ts +602 -0
- package/src/core/runtime/runRecoveryContext.ts +195 -0
- package/src/core/runtime/runRecoveryPlan.test.ts +851 -0
- package/src/core/runtime/runRecoveryPlan.ts +540 -0
- package/src/core/runtime/runRecoveryStore.ts +78 -0
- package/src/core/runtime/runStore.ts +148 -0
- package/src/core/runtime/runStorePath.ts +13 -0
- package/src/core/runtime/runToolStore.ts +66 -0
- package/src/core/runtime/runTools.crash.test.ts +414 -0
- package/src/core/runtime/runTools.test.ts +689 -0
- package/src/core/runtime/runTools.ts +208 -0
- package/src/core/runtime/sqliteControlStore.test.ts +917 -0
- package/src/core/runtime/sqliteControlStore.ts +555 -0
- package/src/core/runtime/sqliteModelStore.test.ts +619 -0
- package/src/core/runtime/sqliteModelStore.ts +286 -0
- package/src/core/runtime/sqliteRecoveryStore.public.test.ts +374 -0
- package/src/core/runtime/sqliteRecoveryStore.test.ts +836 -0
- package/src/core/runtime/sqliteRecoveryStore.ts +435 -0
- package/src/core/runtime/sqliteRunContext.test.ts +1000 -0
- package/src/core/runtime/sqliteRunObservation.test.ts +432 -0
- package/src/core/runtime/sqliteRunStore.test.ts +698 -0
- package/src/core/runtime/sqliteRunStore.ts +593 -0
- package/src/core/runtime/sqliteToolStore.test.ts +1231 -0
- package/src/core/runtime/sqliteToolStore.ts +419 -0
- package/src/core/runtime/testfixtures/localWorkerChild.ts +254 -0
- package/src/core/runtime/testfixtures/localWorkerLauncher.ts +15 -0
- package/src/core/runtime/testfixtures/recoveryChild.ts +159 -0
- package/src/core/services/rateLimiter/index.test.ts +106 -0
- package/src/core/services/rateLimiter/index.ts +157 -0
- package/src/core/services/rateLimiter/types.ts +10 -0
- package/src/core/session/admitted-config.test.ts +50 -0
- package/src/core/session/deprecated-headers-shim.test.ts +111 -0
- package/src/core/session/execution-metrics.test.ts +297 -0
- package/src/core/session/execution-metrics.ts +176 -0
- package/src/core/session/index.ts +1085 -0
- package/src/core/session/loader.ts +212 -0
- package/src/core/session/persistence.test.ts +1210 -0
- package/src/core/session/persistence.ts +1076 -0
- package/src/core/session/rehydrate.test.ts +393 -0
- package/src/core/session/types.ts +139 -0
- package/src/core/session/usage.test.ts +99 -0
- package/src/core/session/usage.ts +84 -0
- package/src/core/skills/builtins/index.ts +22 -0
- package/src/core/skills/builtins/pentest.ts +135 -0
- package/src/core/skills/builtins/promptInjection.ts +44 -0
- package/src/core/skills/builtins/threatModel.ts +336 -0
- package/src/core/skills/index.ts +15 -0
- package/src/core/skills/parser.test.ts +120 -0
- package/src/core/skills/parser.ts +106 -0
- package/src/core/skills/registry.test.ts +202 -0
- package/src/core/skills/registry.ts +109 -0
- package/src/core/skills/scanner.test.ts +157 -0
- package/src/core/skills/scanner.ts +115 -0
- package/src/core/skills/types.ts +45 -0
- package/src/core/skills/utils.ts +16 -0
- package/src/core/storage/index.ts +150 -0
- package/src/core/tasks/index.ts +242 -0
- package/src/core/tools/backends/capture.ts +38 -0
- package/src/core/tools/backends/collectCommand.test.ts +33 -0
- package/src/core/tools/backends/collectCommand.ts +28 -0
- package/src/core/tools/backends/helpers.ts +140 -0
- package/src/core/tools/backends/httpTransport.ts +534 -0
- package/src/core/tools/backends/index.ts +61 -0
- package/src/core/tools/backends/local.test.ts +307 -0
- package/src/core/tools/backends/local.ts +820 -0
- package/src/core/tools/backends/policy.test.ts +137 -0
- package/src/core/tools/backends/policy.ts +129 -0
- package/src/core/tools/backends/resolve.test.ts +309 -0
- package/src/core/tools/backends/resolve.ts +152 -0
- package/src/core/tools/backends/types.ts +279 -0
- package/src/core/tools/backends/whitebox.test.ts +275 -0
- package/src/core/tools/backends/whitebox.ts +14 -0
- package/src/core/tools/backends/whiteboxJobs.test.ts +375 -0
- package/src/core/tools/backends/whiteboxJobs.ts +157 -0
- package/src/core/tools/backends/windowsProgram.ts +36 -0
- package/src/core/tools/daytonaSandbox.test.ts +491 -0
- package/src/core/tools/daytonaSandbox.ts +232 -0
- package/src/core/toolset/index.ts +583 -0
- package/src/core/util/lazyLogger.ts +32 -0
- package/src/core/utils/concurrency.test.ts +204 -0
- package/src/core/utils/concurrency.ts +75 -0
- package/src/core/utils/prompt.ts +28 -0
- package/src/core/whitebox/adapters.ts +277 -0
- package/src/core/whitebox/artifacts.ts +128 -0
- package/src/core/whitebox/boundedProcess.ts +265 -0
- package/src/core/whitebox/candidates.ts +268 -0
- package/src/core/whitebox/catalog.ts +82 -0
- package/src/core/whitebox/index.ts +53 -0
- package/src/core/whitebox/jobKernel.test.ts +91 -0
- package/src/core/whitebox/jobKernel.ts +347 -0
- package/src/core/whitebox/jobSupervisor.ts +264 -0
- package/src/core/whitebox/jobs.ts +21 -0
- package/src/core/whitebox/paths.ts +94 -0
- package/src/core/whitebox/profileTransport.ts +225 -0
- package/src/core/whitebox/profiles.ts +294 -0
- package/src/core/whitebox/repoProfile.test.ts +217 -0
- package/src/core/whitebox/repoProfile.ts +256 -0
- package/src/core/whitebox/types.ts +189 -0
- package/src/core/whitebox/whitebox.test.ts +789 -0
- package/src/core/workflows/fastStrike.seams.test.ts +217 -0
- package/src/core/workflows/fastStrike.test.ts +29 -0
- package/src/core/workflows/fastStrike.ts +239 -0
- package/src/core/workflows/pentest.test.ts +575 -0
- package/src/core/workflows/pentest.ts +1022 -0
- package/src/core/workflows/seams.test.ts +386 -0
- package/src/core/workflows/seams.ts +276 -0
- package/src/core/workflows/threatModel.ts +108 -0
- package/src/core/workflows/usage-forwarding.test.ts +203 -0
- package/src/core/workflows/whiteboxAttackSurface.test.ts +695 -0
- package/src/core/workflows/whiteboxAttackSurface.ts +1740 -0
- package/src/index.ts +49 -0
- package/src/lib/attack-path/types.ts +13 -0
- package/src/lib/cvss/calculator.test.ts +2090 -0
- package/src/lib/cvss/calculator.ts +643 -0
- package/src/lib/cvss/index.ts +65 -0
- package/src/lib/cvss/macrovector-scores.ts +402 -0
- package/src/lib/cvss/types.ts +241 -0
- package/src/lib/cwe/cwe-catalog.ts +444 -0
- package/src/lib/cwe/types.test.ts +142 -0
- package/src/lib/cwe/types.ts +37 -0
- package/src/lib/cwe/validate.test.ts +110 -0
- package/src/lib/cwe/validate.ts +53 -0
- package/src/lib/evidence/types.ts +9 -0
- package/src/tests/agentWorkingDirectory.test.ts +175 -0
- package/src/tests/attackSurface.test.ts +35 -0
- package/src/tests/auth.test.ts +196 -0
- package/src/tests/authCookieExport.test.ts +143 -0
- package/src/tests/authPentestHandoff.test.ts +231 -0
- package/src/tests/browserCookies.test.ts +128 -0
- package/src/tests/credentialManager.test.ts +657 -0
- package/src/tests/data/console.openapi.json +1037 -0
- package/src/tests/data/console.sitemap.xml +348 -0
- package/src/tests/delegateAuthMerge.test.ts +48 -0
- package/src/tui/auto-copy.ts +36 -0
- package/src/tui/clipboard.ts +65 -0
- package/src/tui/command-registry.test.ts +124 -0
- package/src/tui/command-registry.ts +645 -0
- package/src/tui/command-router.ts +81 -0
- package/src/tui/components/agent-display.tsx +480 -0
- package/src/tui/components/ascii-art.tsx +225 -0
- package/src/tui/components/chat/approval-inline.tsx +51 -0
- package/src/tui/components/chat/config-view.tsx +279 -0
- package/src/tui/components/chat/home-view.tsx +288 -0
- package/src/tui/components/chat/index.tsx +161 -0
- package/src/tui/components/chat/input-area.tsx +529 -0
- package/src/tui/components/chat/lib/play-core/num.ts +102 -0
- package/src/tui/components/chat/lib/wave-simulation.ts +124 -0
- package/src/tui/components/chat/loading-indicator.test.ts +33 -0
- package/src/tui/components/chat/loading-indicator.tsx +115 -0
- package/src/tui/components/chat/message-list.tsx +264 -0
- package/src/tui/components/chat/petri-animation.tsx +175 -0
- package/src/tui/components/chat/plan-review-message.tsx +49 -0
- package/src/tui/components/chat/questions-form.tsx +756 -0
- package/src/tui/components/commands/__tests__/recorded-runs-lifecycle.tsx +907 -0
- package/src/tui/components/commands/advanced-settings.tsx +94 -0
- package/src/tui/components/commands/api-key-input.tsx +122 -0
- package/src/tui/components/commands/auth-flow-logic.test.ts +58 -0
- package/src/tui/components/commands/auth-flow-logic.ts +30 -0
- package/src/tui/components/commands/auth-flow.tsx +702 -0
- package/src/tui/components/commands/credits-flow.tsx +210 -0
- package/src/tui/components/commands/help-dialog.tsx +238 -0
- package/src/tui/components/commands/models-display.tsx +111 -0
- package/src/tui/components/commands/operator-wizard.tsx +328 -0
- package/src/tui/components/commands/provider-manager.tsx +241 -0
- package/src/tui/components/commands/provider-selection.tsx +129 -0
- package/src/tui/components/commands/recorded-runs.test.ts +40 -0
- package/src/tui/components/commands/recorded-runs.tsx +793 -0
- package/src/tui/components/commands/sessions-display.tsx +365 -0
- package/src/tui/components/commands/shortcuts-dialog.tsx +52 -0
- package/src/tui/components/commands/skills-dialog.tsx +308 -0
- package/src/tui/components/commands/theme-picker.tsx +177 -0
- package/src/tui/components/commands/web-wizard.tsx +965 -0
- package/src/tui/components/dialog-layout.tsx +73 -0
- package/src/tui/components/error-boundary.tsx +82 -0
- package/src/tui/components/footer.tsx +101 -0
- package/src/tui/components/input.tsx +43 -0
- package/src/tui/components/loaders.tsx +784 -0
- package/src/tui/components/model-picker/ModelPicker.tsx +907 -0
- package/src/tui/components/model-picker/ModelPickerDialog.tsx +75 -0
- package/src/tui/components/model-picker/index.ts +2 -0
- package/src/tui/components/model-picker/model-navigation.test.ts +102 -0
- package/src/tui/components/model-picker/model-navigation.ts +54 -0
- package/src/tui/components/model-picker/model-search.test.ts +62 -0
- package/src/tui/components/model-picker/model-search.ts +42 -0
- package/src/tui/components/model-picker/model-visibility.test.ts +65 -0
- package/src/tui/components/model-picker/model-visibility.ts +59 -0
- package/src/tui/components/operator-dashboard/conversation.test.ts +431 -0
- package/src/tui/components/operator-dashboard/conversation.ts +179 -0
- package/src/tui/components/operator-dashboard/display-state.test.ts +369 -0
- package/src/tui/components/operator-dashboard/display-state.ts +163 -0
- package/src/tui/components/operator-dashboard/herdr-state.test.ts +162 -0
- package/src/tui/components/operator-dashboard/herdr-state.ts +81 -0
- package/src/tui/components/operator-dashboard/index.tsx +2044 -0
- package/src/tui/components/operator-dashboard/logic.test.ts +753 -0
- package/src/tui/components/operator-dashboard/logic.ts +465 -0
- package/src/tui/components/operator-dashboard/queue.test.ts +147 -0
- package/src/tui/components/operator-dashboard/queue.ts +55 -0
- package/src/tui/components/operator-dashboard/queued-messages.tsx +50 -0
- package/src/tui/components/operator-dashboard/run-events.test.ts +913 -0
- package/src/tui/components/operator-dashboard/run-events.ts +507 -0
- package/src/tui/components/operator-dashboard/run-tracing.test.ts +206 -0
- package/src/tui/components/operator-dashboard/run-tracing.ts +105 -0
- package/src/tui/components/operator-dashboard/subagent-detail-view.tsx +51 -0
- package/src/tui/components/operator-dashboard/subagent-dialog.tsx +142 -0
- package/src/tui/components/operator-dashboard/subagent-hub.test.ts +162 -0
- package/src/tui/components/operator-dashboard/subagent-hub.tsx +266 -0
- package/src/tui/components/operator-dashboard/subagent-state.test.ts +431 -0
- package/src/tui/components/operator-dashboard/subagent-state.ts +404 -0
- package/src/tui/components/operator-dashboard/subagent-status-bar.tsx +80 -0
- package/src/tui/components/operator-dashboard/tool-argument-buffer.test.ts +317 -0
- package/src/tui/components/operator-dashboard/tool-arguments.perf.test.ts +121 -0
- package/src/tui/components/operator-dashboard/welcome-logo.tsx +79 -0
- package/src/tui/components/operator-dashboard/workflow-data.test.ts +291 -0
- package/src/tui/components/operator-dashboard/workflow-data.ts +130 -0
- package/src/tui/components/report-viewer-dialog.tsx +98 -0
- package/src/tui/components/responsible-use-disclosure.tsx +55 -0
- package/src/tui/components/screenshot-modal.tsx +204 -0
- package/src/tui/components/shared/__tests__/prompt-input-lifecycle.tsx +64 -0
- package/src/tui/components/shared/__tests__/tool-renderer-lifecycle.tsx +69 -0
- package/src/tui/components/shared/action-label.test.ts +133 -0
- package/src/tui/components/shared/action-label.ts +18 -0
- package/src/tui/components/shared/ascii-spinner.tsx +40 -0
- package/src/tui/components/shared/dialog-controls.tsx +76 -0
- package/src/tui/components/shared/index.ts +37 -0
- package/src/tui/components/shared/markdown-viewer.tsx +188 -0
- package/src/tui/components/shared/markdown.ts +239 -0
- package/src/tui/components/shared/message-renderer.tsx +178 -0
- package/src/tui/components/shared/message-utils.ts +175 -0
- package/src/tui/components/shared/pentest-workflow-display.tsx +330 -0
- package/src/tui/components/shared/prompt-input-lifecycle.test.ts +22 -0
- package/src/tui/components/shared/prompt-input-logic.test.ts +782 -0
- package/src/tui/components/shared/prompt-input-logic.ts +423 -0
- package/src/tui/components/shared/prompt-input.tsx +782 -0
- package/src/tui/components/shared/result-registry.test.ts +90 -0
- package/src/tui/components/shared/result-registry.ts +900 -0
- package/src/tui/components/shared/syntax-highlight.ts +242 -0
- package/src/tui/components/shared/tool-registry.test.ts +59 -0
- package/src/tui/components/shared/tool-registry.ts +279 -0
- package/src/tui/components/shared/tool-renderer.test.ts +13 -0
- package/src/tui/components/shared/tool-renderer.tsx +223 -0
- package/src/tui/components/shared/type-guards.ts +64 -0
- package/src/tui/components/shared/use-paste-extmarks.ts +109 -0
- package/src/tui/components/switch.tsx +81 -0
- package/src/tui/components/terminal-focus-handler.tsx +34 -0
- package/src/tui/components/toast.tsx +82 -0
- package/src/tui/components/usage-labels.test.ts +191 -0
- package/src/tui/components/usage-labels.ts +78 -0
- package/src/tui/console-theme.ts +59 -0
- package/src/tui/context/agent.tsx +251 -0
- package/src/tui/context/command.tsx +291 -0
- package/src/tui/context/config.tsx +59 -0
- package/src/tui/context/dialog.tsx +206 -0
- package/src/tui/context/dimensions.tsx +31 -0
- package/src/tui/context/focus-ownership.test.ts +26 -0
- package/src/tui/context/focus-ownership.ts +12 -0
- package/src/tui/context/focus.tsx +86 -0
- package/src/tui/context/herdr.tsx +28 -0
- package/src/tui/context/input.tsx +45 -0
- package/src/tui/context/keybinding.tsx +92 -0
- package/src/tui/context/obfuscation.tsx +114 -0
- package/src/tui/context/route.tsx +127 -0
- package/src/tui/context/session-usage.test.ts +295 -0
- package/src/tui/context/session-usage.ts +204 -0
- package/src/tui/context/session.tsx +62 -0
- package/src/tui/context/toast.tsx +66 -0
- package/src/tui/generated-ascii-art.json +1 -0
- package/src/tui/generated-ascii-art.json.d.ts +2 -0
- package/src/tui/herdr-startup.test.ts +157 -0
- package/src/tui/hooks/use-sessions-list.ts +165 -0
- package/src/tui/index.tsx +838 -0
- package/src/tui/keybindings/index.ts +126 -0
- package/src/tui/keybindings/registry.ts +146 -0
- package/src/tui/keybindings-registry.ts +43 -0
- package/src/tui/obfuscation/patch.ts +139 -0
- package/src/tui/terminal-focus.ts +218 -0
- package/src/tui/theme/colors.ts +43 -0
- package/src/tui/theme/context.tsx +137 -0
- package/src/tui/theme/detect-mode.ts +56 -0
- package/src/tui/theme/index.ts +75 -0
- package/src/tui/theme/registry.ts +29 -0
- package/src/tui/theme/themes/apex.ts +190 -0
- package/src/tui/theme/themes/ayu.ts +189 -0
- package/src/tui/theme/themes/catppuccin.ts +189 -0
- package/src/tui/theme/themes/dracula.ts +190 -0
- package/src/tui/theme/themes/everforest.ts +189 -0
- package/src/tui/theme/themes/flexoki.ts +190 -0
- package/src/tui/theme/themes/github.ts +189 -0
- package/src/tui/theme/themes/gruvbox.ts +189 -0
- package/src/tui/theme/themes/index.ts +54 -0
- package/src/tui/theme/themes/kanagawa.ts +189 -0
- package/src/tui/theme/themes/material.ts +190 -0
- package/src/tui/theme/themes/monokai.ts +190 -0
- package/src/tui/theme/themes/nightfox.ts +189 -0
- package/src/tui/theme/themes/nord.ts +190 -0
- package/src/tui/theme/themes/onedark.ts +190 -0
- package/src/tui/theme/themes/rose-pine.ts +189 -0
- package/src/tui/theme/themes/solarized.ts +189 -0
- package/src/tui/theme/themes/tokyonight.ts +189 -0
- package/src/tui/theme/themes/vesper.ts +191 -0
- package/src/tui/theme/types.ts +143 -0
- package/src/tui/utils/command-flags.test.ts +269 -0
- package/src/tui/utils/command-flags.ts +377 -0
- package/src/tui/utils/kitty-graphics.ts +76 -0
- package/src/tui/utils/open-file.ts +37 -0
- package/src/tui/utils/open-report.ts +34 -0
- package/src/tui/utils/open-url.ts +39 -0
- package/src/tui/utils/paste.ts +8 -0
- package/src/tui/utils/scroll.ts +81 -0
- package/src/util/errors.ts +58 -0
- package/src/util/lock.ts +100 -0
- package/src/util/name.ts +148 -0
- package/src/util/url.test.ts +162 -0
- package/src/util/url.ts +107 -0
- package/build/agent-13rrhp8d.js +0 -20
- package/build/agent-zbj2331s.js +0 -30
- package/build/authentication-f4j3w5vs.js +0 -20
- package/build/blackboxAgent-v2vx9ws4.js +0 -20
- package/build/blackboxPentest-w5nwzpvk.js +0 -70
- package/build/cli-5wemv37a.js +0 -194
- package/build/cli-7s062hr6.js +0 -22
- package/build/cli-9qbxkhye.js +0 -55
- package/build/cli-avmhkjtv.js +0 -22961
- package/build/cli-g3czc3jz.js +0 -204
- package/build/cli-ww7hbazg.js +0 -88
- package/build/index-18vyy6mj.js +0 -19
- package/build/index-5wh7gq37.js +0 -12858
- package/build/index-e23zc8g4.js +0 -34
- package/build/index-hnwhswqj.js +0 -213
- package/build/index-wkmnr4nm.js +0 -39
- package/build/index-ywmfnd30.js +0 -12
- package/build/offesecAgent-c97pqzcn.js +0 -27
- package/build/pentest-4p5jt7gj.js +0 -30
- package/build/pentests-v98eg8n3.js +0 -92
- package/build/threatModel-81d4rcbk.js +0 -29
- /package/build/{cli-c8131c4q.js → cli-0wsh8j2t.js} +0 -0
- /package/build/{cli-9pbk8m32.js → cli-ag6jtndp.js} +0 -0
- /package/build/{cli-e08r86zk.js → cli-b3dvxjgk.js} +0 -0
- /package/build/{cli-fw5r7pfj.js → cli-q126f6ef.js} +0 -0
- /package/build/{cli-9fsre5pt.js → cli-qba2x69h.js} +0 -0
- /package/build/{cli-7q3ae3ft.js → cli-z82n3qk1.js} +0 -0
|
@@ -0,0 +1,2899 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for OffensiveSecurityAgent.consume().
|
|
3
|
+
*
|
|
4
|
+
* Verifies that the command shell's dispose() is called even when the
|
|
5
|
+
* fullStream throws — the shell-leak bug described in the PR.
|
|
6
|
+
*
|
|
7
|
+
* Heavy transitive dependencies (tools, AI SDK, zod) are stubbed so
|
|
8
|
+
* the test loads cleanly without external provider keys.
|
|
9
|
+
*/
|
|
10
|
+
import {
|
|
11
|
+
mkdirSync,
|
|
12
|
+
mkdtempSync,
|
|
13
|
+
readFileSync,
|
|
14
|
+
rmSync,
|
|
15
|
+
writeFileSync,
|
|
16
|
+
} from "node:fs";
|
|
17
|
+
import { tmpdir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
import { describe, expect, it, vi } from "vitest";
|
|
20
|
+
// Direct file import: the barrel "./tools" is mocked below, and this test
|
|
21
|
+
// needs the real registry to exercise its retained-handle retry semantics.
|
|
22
|
+
import { CallbackListenerRegistry } from "./tools/callbackListener";
|
|
23
|
+
|
|
24
|
+
const toolContexts = vi.hoisted(() => [] as Array<Record<string, unknown>>);
|
|
25
|
+
const streamResponseCalls = vi.hoisted(
|
|
26
|
+
() => [] as Array<Record<string, unknown>>,
|
|
27
|
+
);
|
|
28
|
+
const traceRecordStepCalls = vi.hoisted(() => [] as unknown[][]);
|
|
29
|
+
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
// Module stubs — prevent the full tool/AI/zod import chain from loading.
|
|
32
|
+
//
|
|
33
|
+
// vi.mock factories are hoisted above all imports, so they cannot
|
|
34
|
+
// reference top-level variables. All helper logic must be inlined.
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
vi.mock("zod", () => {
|
|
38
|
+
const handler: ProxyHandler<CallableFunction> = {
|
|
39
|
+
get(_target, _prop) {
|
|
40
|
+
return new Proxy(() => {}, handler);
|
|
41
|
+
},
|
|
42
|
+
apply(_target, _thisArg, _args) {
|
|
43
|
+
return new Proxy(() => {}, handler);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
const z = new Proxy(() => {}, handler);
|
|
47
|
+
return { z, default: z };
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
vi.mock("./tools", () => ({
|
|
51
|
+
// The agent constructs via the selective entrypoint; record the tool
|
|
52
|
+
// context there (createAllTools kept for any direct legacy callers).
|
|
53
|
+
createToolsForNames: (
|
|
54
|
+
ctx: Record<string, unknown>,
|
|
55
|
+
requested: readonly string[] | undefined,
|
|
56
|
+
) => {
|
|
57
|
+
toolContexts.push(ctx);
|
|
58
|
+
void requested;
|
|
59
|
+
return {};
|
|
60
|
+
},
|
|
61
|
+
listToolRegistryNames: () => [],
|
|
62
|
+
createAllTools: (ctx: Record<string, unknown>) => {
|
|
63
|
+
toolContexts.push(ctx);
|
|
64
|
+
return {};
|
|
65
|
+
},
|
|
66
|
+
EMAIL_TOOL_NAMES_ACTIVE: [],
|
|
67
|
+
SEND_EMAIL_TOOL_NAME: "send_email",
|
|
68
|
+
SMS_TOOL_NAMES_ACTIVE: [],
|
|
69
|
+
sessionHasSmsPasswordless: () => false,
|
|
70
|
+
PLAN_MODE_TOOL_NAMES: [],
|
|
71
|
+
FAST_STRIKE_EXCLUDED_TOOL_NAMES: [],
|
|
72
|
+
createResponseTool: () => {},
|
|
73
|
+
RESPONSE_TOOL_NAME: "response",
|
|
74
|
+
ASK_USER_QUESTIONS_TOOL_NAME: "ask_user_questions",
|
|
75
|
+
WORKSPACE_TOOL_NAMES: [
|
|
76
|
+
"list_workspace_domains",
|
|
77
|
+
"create_workspace_domain",
|
|
78
|
+
"list_workspace_apps",
|
|
79
|
+
"create_workspace_app",
|
|
80
|
+
"update_workspace_app",
|
|
81
|
+
"list_workspace_endpoints",
|
|
82
|
+
"create_workspace_endpoint",
|
|
83
|
+
"update_workspace_endpoint",
|
|
84
|
+
],
|
|
85
|
+
WORKSPACE_WRITE_TOOL_NAMES: [
|
|
86
|
+
"create_workspace_domain",
|
|
87
|
+
"create_workspace_app",
|
|
88
|
+
"update_workspace_app",
|
|
89
|
+
"create_workspace_endpoint",
|
|
90
|
+
"update_workspace_endpoint",
|
|
91
|
+
],
|
|
92
|
+
// Minimal registry stub: the agent constructs one per instance and
|
|
93
|
+
// finalization drains it; these tests never start listeners.
|
|
94
|
+
CallbackListenerRegistry: class {
|
|
95
|
+
register() {}
|
|
96
|
+
get() {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
remove() {}
|
|
100
|
+
async stopAll() {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
PerCommandShell: class {},
|
|
105
|
+
PlaywrightMcpSession: class {},
|
|
106
|
+
resolveBrowserHeaderPolicy: () => ({ allowedHosts: [], headers: {} }),
|
|
107
|
+
}));
|
|
108
|
+
vi.mock("../../ai", () => ({
|
|
109
|
+
streamResponse: (opts: Record<string, unknown>) => {
|
|
110
|
+
streamResponseCalls.push(opts);
|
|
111
|
+
return { fullStream: (async function* () {})() };
|
|
112
|
+
},
|
|
113
|
+
normalizeStepUsage: (step: {
|
|
114
|
+
usage?: {
|
|
115
|
+
inputTokens?: number;
|
|
116
|
+
outputTokens?: number;
|
|
117
|
+
inputTokenDetails?: {
|
|
118
|
+
cacheReadTokens?: number;
|
|
119
|
+
cacheWriteTokens?: number;
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
providerMetadata?: {
|
|
123
|
+
anthropic?: {
|
|
124
|
+
cacheReadInputTokens?: number;
|
|
125
|
+
cacheCreationInputTokens?: number;
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
}) => {
|
|
129
|
+
const meta = step.providerMetadata?.anthropic;
|
|
130
|
+
const details = step.usage?.inputTokenDetails;
|
|
131
|
+
return {
|
|
132
|
+
inputTokens: step.usage?.inputTokens ?? 0,
|
|
133
|
+
outputTokens: step.usage?.outputTokens ?? 0,
|
|
134
|
+
cacheReadTokens:
|
|
135
|
+
meta?.cacheReadInputTokens ?? details?.cacheReadTokens ?? 0,
|
|
136
|
+
cacheWriteTokens:
|
|
137
|
+
meta?.cacheCreationInputTokens ?? details?.cacheWriteTokens ?? 0,
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
}));
|
|
141
|
+
vi.mock("../../session", () => ({ create: () => {} }));
|
|
142
|
+
vi.mock("../specialized/utils", () => ({
|
|
143
|
+
detectOSAndEnhancePrompt: (p: string) => p,
|
|
144
|
+
}));
|
|
145
|
+
vi.mock("./prompt", async (importOriginal) => ({
|
|
146
|
+
...(await importOriginal<typeof import("./prompt")>()),
|
|
147
|
+
buildBaseSystemPrompt: () => "system",
|
|
148
|
+
}));
|
|
149
|
+
vi.mock("./trace", () => ({
|
|
150
|
+
StepTraceWriter: class {
|
|
151
|
+
writeInit() {}
|
|
152
|
+
recordStep(...args: unknown[]) {
|
|
153
|
+
traceRecordStepCalls.push(args);
|
|
154
|
+
}
|
|
155
|
+
markSummarized() {}
|
|
156
|
+
},
|
|
157
|
+
}));
|
|
158
|
+
vi.mock("../../operator", () => ({
|
|
159
|
+
ApprovalDeniedError: class extends Error {},
|
|
160
|
+
}));
|
|
161
|
+
vi.mock("ai", () => ({ hasToolCall: () => () => false }));
|
|
162
|
+
|
|
163
|
+
import type { InferenceRecorder } from "../../ai";
|
|
164
|
+
import { AgentEventBus } from "../../eventBus";
|
|
165
|
+
import type { RunContextRecorder } from "../../runtime/runContext";
|
|
166
|
+
import { createInterruptedStepFinalizer } from "./interruptedStepFinalization";
|
|
167
|
+
import { AgentMessageWriter } from "./messagePersistence";
|
|
168
|
+
import {
|
|
169
|
+
filterWorkspaceToolsForRun,
|
|
170
|
+
OffensiveSecurityAgent,
|
|
171
|
+
} from "./offensiveSecurityAgent";
|
|
172
|
+
|
|
173
|
+
describe("assembled file-workspace instructions", () => {
|
|
174
|
+
it("does not treat Fast Strike helper tools as permission to edit target source", () => {
|
|
175
|
+
const rootPath = mkdtempSync(join(tmpdir(), "apex-fast-strike-scope-"));
|
|
176
|
+
const helperRoot = join(rootPath, "subagents", "worker", "helpers");
|
|
177
|
+
streamResponseCalls.length = 0;
|
|
178
|
+
try {
|
|
179
|
+
const agent = new OffensiveSecurityAgent({
|
|
180
|
+
prompt: "test",
|
|
181
|
+
system: "Worker instructions",
|
|
182
|
+
model: "test-model",
|
|
183
|
+
mode: "fast-strike",
|
|
184
|
+
session: {
|
|
185
|
+
id: "ses_fast_strike_scope",
|
|
186
|
+
rootPath,
|
|
187
|
+
scratchpadPath: join(rootPath, "scratchpad"),
|
|
188
|
+
config: { codebasePath: join(rootPath, "target-repo") },
|
|
189
|
+
},
|
|
190
|
+
fileWorkspaceRoot: helperRoot,
|
|
191
|
+
activeTools: [],
|
|
192
|
+
extraTools: { profile_codebase: {}, create_file: {} },
|
|
193
|
+
sandbox: {},
|
|
194
|
+
} as never);
|
|
195
|
+
void agent.streamResult;
|
|
196
|
+
const request = streamResponseCalls[0];
|
|
197
|
+
expect(request.activeTools).toContain("profile_codebase");
|
|
198
|
+
expect(request.activeTools).toContain("create_file");
|
|
199
|
+
const assessment = (request.system as string).split(
|
|
200
|
+
"# Source Code Assessment",
|
|
201
|
+
)[1];
|
|
202
|
+
expect(assessment).toBeDefined();
|
|
203
|
+
expect(assessment).toContain("Source access is read-only");
|
|
204
|
+
expect(assessment).toContain(helperRoot);
|
|
205
|
+
expect(assessment).toContain(
|
|
206
|
+
"Do not use shell commands to edit target source",
|
|
207
|
+
);
|
|
208
|
+
expect(assessment).not.toContain(
|
|
209
|
+
"Limit repo edits to the change you were dispatched to make",
|
|
210
|
+
);
|
|
211
|
+
expect(assessment).not.toContain(join(rootPath, "scratchpad"));
|
|
212
|
+
} finally {
|
|
213
|
+
rmSync(rootPath, { recursive: true, force: true });
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it.each([
|
|
218
|
+
"spawned",
|
|
219
|
+
"root",
|
|
220
|
+
"custom-cwd",
|
|
221
|
+
])("keeps %s worker file paths separate from shell and provided-file paths", (kind) => {
|
|
222
|
+
const rootPath = mkdtempSync(join(tmpdir(), "apex-workspace-prompt-"));
|
|
223
|
+
const scratchpadPath = join(rootPath, "scratchpad");
|
|
224
|
+
const fileWorkspaceRoot =
|
|
225
|
+
kind === "root"
|
|
226
|
+
? scratchpadPath
|
|
227
|
+
: join(rootPath, "subagents", "worker", "helpers");
|
|
228
|
+
const agentCwd =
|
|
229
|
+
kind === "custom-cwd" ? join(rootPath, "commands") : rootPath;
|
|
230
|
+
const providedPath = join(rootPath, "provided_files");
|
|
231
|
+
mkdirSync(providedPath);
|
|
232
|
+
writeFileSync(join(providedPath, "sample.txt"), "user-supplied input");
|
|
233
|
+
streamResponseCalls.length = 0;
|
|
234
|
+
try {
|
|
235
|
+
const agent = new OffensiveSecurityAgent({
|
|
236
|
+
prompt: "test",
|
|
237
|
+
system: "Worker instructions",
|
|
238
|
+
model: "test-model",
|
|
239
|
+
session: { id: "ses_workspace", rootPath, scratchpadPath },
|
|
240
|
+
agentCwd,
|
|
241
|
+
fileWorkspaceRoot,
|
|
242
|
+
activeTools: [
|
|
243
|
+
"read_file",
|
|
244
|
+
"list_files",
|
|
245
|
+
"create_file",
|
|
246
|
+
"execute_command",
|
|
247
|
+
],
|
|
248
|
+
sandbox: {},
|
|
249
|
+
} as never);
|
|
250
|
+
void agent.streamResult;
|
|
251
|
+
const system = streamResponseCalls[0].system as string;
|
|
252
|
+
expect(system).toContain(`Your shell starts in ${agentCwd}`);
|
|
253
|
+
expect(system).toContain(
|
|
254
|
+
`Native file tools are confined to ${fileWorkspaceRoot}`,
|
|
255
|
+
);
|
|
256
|
+
expect(system).toContain(
|
|
257
|
+
"Use absolute helper paths with `execute_command`",
|
|
258
|
+
);
|
|
259
|
+
expect(system).toContain(providedPath);
|
|
260
|
+
expect(system).toContain("sample.txt");
|
|
261
|
+
expect(system).toContain("copy inputs into the file workspace");
|
|
262
|
+
expect(system).not.toContain("Use relative paths for everything");
|
|
263
|
+
expect(system).not.toContain("temporary scripts");
|
|
264
|
+
expect(system).not.toContain("`list_files provided_files/`");
|
|
265
|
+
} finally {
|
|
266
|
+
rmSync(rootPath, { recursive: true, force: true });
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("falls back to the session's remote file workspace for the real tool context and prompt", () => {
|
|
271
|
+
const rootPath = mkdtempSync(join(tmpdir(), "apex-remote-ws-"));
|
|
272
|
+
const agentCwd = join(rootPath, "agent-cwd");
|
|
273
|
+
const remoteRoot = join(rootPath, "remote-workspace");
|
|
274
|
+
toolContexts.length = 0;
|
|
275
|
+
streamResponseCalls.length = 0;
|
|
276
|
+
try {
|
|
277
|
+
const agent = new OffensiveSecurityAgent({
|
|
278
|
+
prompt: "test",
|
|
279
|
+
system: "Worker instructions",
|
|
280
|
+
model: "test-model",
|
|
281
|
+
session: {
|
|
282
|
+
id: "ses_remote_ws",
|
|
283
|
+
rootPath,
|
|
284
|
+
scratchpadPath: join(rootPath, "scratchpad"),
|
|
285
|
+
config: { agentCwd, remoteFileWorkspaceRoot: remoteRoot },
|
|
286
|
+
},
|
|
287
|
+
activeTools: ["create_file", "read_file"],
|
|
288
|
+
sandbox: {},
|
|
289
|
+
} as never);
|
|
290
|
+
void agent.streamResult;
|
|
291
|
+
expect(toolContexts[0].fileWorkspaceRoot).toBe(remoteRoot);
|
|
292
|
+
const system = streamResponseCalls[0].system as string;
|
|
293
|
+
expect(system).toContain(
|
|
294
|
+
`Native file tools are confined to ${remoteRoot}`,
|
|
295
|
+
);
|
|
296
|
+
expect(system).not.toContain(join(rootPath, "scratchpad"));
|
|
297
|
+
} finally {
|
|
298
|
+
rmSync(rootPath, { recursive: true, force: true });
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("lets an explicit child helper root win over the session's remote workspace", () => {
|
|
303
|
+
const rootPath = mkdtempSync(join(tmpdir(), "apex-remote-ws-explicit-"));
|
|
304
|
+
const remoteRoot = join(rootPath, "remote-workspace");
|
|
305
|
+
const childRoot = join(remoteRoot, "subagents", "sub_1", "helpers");
|
|
306
|
+
toolContexts.length = 0;
|
|
307
|
+
streamResponseCalls.length = 0;
|
|
308
|
+
try {
|
|
309
|
+
const agent = new OffensiveSecurityAgent({
|
|
310
|
+
prompt: "test",
|
|
311
|
+
system: "Worker instructions",
|
|
312
|
+
model: "test-model",
|
|
313
|
+
session: {
|
|
314
|
+
id: "ses_remote_ws_explicit",
|
|
315
|
+
rootPath,
|
|
316
|
+
scratchpadPath: join(rootPath, "scratchpad"),
|
|
317
|
+
config: { remoteFileWorkspaceRoot: remoteRoot },
|
|
318
|
+
},
|
|
319
|
+
fileWorkspaceRoot: childRoot,
|
|
320
|
+
activeTools: ["create_file"],
|
|
321
|
+
sandbox: {},
|
|
322
|
+
} as never);
|
|
323
|
+
void agent.streamResult;
|
|
324
|
+
expect(toolContexts[0].fileWorkspaceRoot).toBe(childRoot);
|
|
325
|
+
const system = streamResponseCalls[0].system as string;
|
|
326
|
+
expect(system).toContain(
|
|
327
|
+
`Native file tools are confined to ${childRoot}`,
|
|
328
|
+
);
|
|
329
|
+
} finally {
|
|
330
|
+
rmSync(rootPath, { recursive: true, force: true });
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe("spawned-agent usage callbacks", () => {
|
|
336
|
+
it("requires explicit forwarding when trace events own subagent accounting", () => {
|
|
337
|
+
toolContexts.length = 0;
|
|
338
|
+
const onStepFinish = vi.fn();
|
|
339
|
+
const onCacheMetrics = vi.fn();
|
|
340
|
+
const input = {
|
|
341
|
+
prompt: "test",
|
|
342
|
+
model: "test-model",
|
|
343
|
+
session: { id: "ses_test", rootPath: "/tmp/apex-usage-test" },
|
|
344
|
+
activeTools: [],
|
|
345
|
+
sandbox: {},
|
|
346
|
+
onStepFinish,
|
|
347
|
+
onCacheMetrics,
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
new OffensiveSecurityAgent(input as never);
|
|
351
|
+
new OffensiveSecurityAgent({
|
|
352
|
+
...input,
|
|
353
|
+
forwardUsageCallbacksToSpawnedAgents: true,
|
|
354
|
+
} as never);
|
|
355
|
+
|
|
356
|
+
expect(toolContexts[0]?.onStepFinish).toBeUndefined();
|
|
357
|
+
expect(toolContexts[0]?.onCacheMetrics).toBeUndefined();
|
|
358
|
+
expect(toolContexts[1]?.onStepFinish).toBe(onStepFinish);
|
|
359
|
+
expect(toolContexts[1]?.onCacheMetrics).toBe(onCacheMetrics);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
describe("auxiliary model events", () => {
|
|
364
|
+
it("keeps the last complete message snapshot while forwarding usage", async () => {
|
|
365
|
+
streamResponseCalls.length = 0;
|
|
366
|
+
traceRecordStepCalls.length = 0;
|
|
367
|
+
const rootPath = join(
|
|
368
|
+
"/tmp",
|
|
369
|
+
`apex-auxiliary-model-event-${Date.now()}-${Math.random()}`,
|
|
370
|
+
);
|
|
371
|
+
const initialMessages = [
|
|
372
|
+
{ role: "user" as const, content: "original request" },
|
|
373
|
+
];
|
|
374
|
+
const completedMessages = [
|
|
375
|
+
{ role: "assistant" as const, content: "completed step" },
|
|
376
|
+
];
|
|
377
|
+
const onStepFinish = vi.fn();
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
const agent = new OffensiveSecurityAgent({
|
|
381
|
+
prompt: "test",
|
|
382
|
+
model: "test-model",
|
|
383
|
+
messages: initialMessages,
|
|
384
|
+
session: { id: "ses_auxiliary", rootPath },
|
|
385
|
+
activeTools: [],
|
|
386
|
+
sandbox: {},
|
|
387
|
+
onStepFinish,
|
|
388
|
+
} as never);
|
|
389
|
+
|
|
390
|
+
void agent.streamResult;
|
|
391
|
+
const call = streamResponseCalls[0] as {
|
|
392
|
+
onStepFinish: (event: unknown) => Promise<void>;
|
|
393
|
+
};
|
|
394
|
+
await call.onStepFinish({
|
|
395
|
+
response: { id: "step-1", messages: completedMessages },
|
|
396
|
+
usage: { inputTokens: 10, outputTokens: 2 },
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const writer = (agent as unknown as { writer: AgentMessageWriter })
|
|
400
|
+
.writer;
|
|
401
|
+
expect(writer.latest).toEqual([...initialMessages, ...completedMessages]);
|
|
402
|
+
|
|
403
|
+
await call.onStepFinish({
|
|
404
|
+
response: { id: "summarization", messages: [] },
|
|
405
|
+
usage: {
|
|
406
|
+
inputTokens: 5,
|
|
407
|
+
outputTokens: 1,
|
|
408
|
+
inputTokenDetails: { cacheReadTokens: 4, cacheWriteTokens: 1 },
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
expect(writer.latest).toEqual([...initialMessages, ...completedMessages]);
|
|
413
|
+
expect(onStepFinish).toHaveBeenCalledTimes(2);
|
|
414
|
+
expect(traceRecordStepCalls.at(-1)?.[1]).toEqual({
|
|
415
|
+
inputTokens: 5,
|
|
416
|
+
outputTokens: 1,
|
|
417
|
+
cacheReadTokens: 4,
|
|
418
|
+
cacheWriteTokens: 1,
|
|
419
|
+
});
|
|
420
|
+
expect(traceRecordStepCalls.at(-1)?.[2]).toEqual({ usageOnly: true });
|
|
421
|
+
writer.cancelTimer();
|
|
422
|
+
} finally {
|
|
423
|
+
rmSync(rootPath, { recursive: true, force: true });
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("records cache from the step event without onCacheMetrics", async () => {
|
|
428
|
+
streamResponseCalls.length = 0;
|
|
429
|
+
traceRecordStepCalls.length = 0;
|
|
430
|
+
const rootPath = join(
|
|
431
|
+
"/tmp",
|
|
432
|
+
`apex-step-cache-${Date.now()}-${Math.random()}`,
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
try {
|
|
436
|
+
const agent = new OffensiveSecurityAgent({
|
|
437
|
+
prompt: "test",
|
|
438
|
+
model: "test-model",
|
|
439
|
+
session: { id: "ses_step_cache", rootPath },
|
|
440
|
+
activeTools: [],
|
|
441
|
+
sandbox: {},
|
|
442
|
+
} as never);
|
|
443
|
+
|
|
444
|
+
void agent.streamResult;
|
|
445
|
+
const call = streamResponseCalls[0] as {
|
|
446
|
+
onStepFinish: (event: unknown) => Promise<void>;
|
|
447
|
+
onCacheMetrics?: unknown;
|
|
448
|
+
};
|
|
449
|
+
expect(call.onCacheMetrics).toBeUndefined();
|
|
450
|
+
|
|
451
|
+
await call.onStepFinish({
|
|
452
|
+
response: {
|
|
453
|
+
id: "step-1",
|
|
454
|
+
messages: [{ role: "assistant", content: "ok" }],
|
|
455
|
+
},
|
|
456
|
+
usage: {
|
|
457
|
+
inputTokens: 40,
|
|
458
|
+
outputTokens: 6,
|
|
459
|
+
inputTokenDetails: { cacheReadTokens: 30, cacheWriteTokens: 2 },
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
expect(traceRecordStepCalls.at(-1)?.[1]).toEqual({
|
|
464
|
+
inputTokens: 40,
|
|
465
|
+
outputTokens: 6,
|
|
466
|
+
cacheReadTokens: 30,
|
|
467
|
+
cacheWriteTokens: 2,
|
|
468
|
+
});
|
|
469
|
+
const writer = (agent as unknown as { writer: AgentMessageWriter })
|
|
470
|
+
.writer;
|
|
471
|
+
writer.cancelTimer();
|
|
472
|
+
} finally {
|
|
473
|
+
rmSync(rootPath, { recursive: true, force: true });
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
describe("tool context forwarding", () => {
|
|
479
|
+
it("forwards structured System scope to spawning tools", () => {
|
|
480
|
+
toolContexts.length = 0;
|
|
481
|
+
const systemScope = {
|
|
482
|
+
systemId: "sys_test",
|
|
483
|
+
memberHosts: ["https://app.example.com", "https://api.partner.test"],
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
new OffensiveSecurityAgent({
|
|
487
|
+
prompt: "test",
|
|
488
|
+
model: "test-model",
|
|
489
|
+
session: { id: "ses_test", rootPath: "/tmp/apex-scope-test" },
|
|
490
|
+
activeTools: [],
|
|
491
|
+
sandbox: {},
|
|
492
|
+
systemScope,
|
|
493
|
+
} as never);
|
|
494
|
+
|
|
495
|
+
expect(toolContexts[0]?.systemScope).toBe(systemScope);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// ---------------------------------------------------------------------------
|
|
500
|
+
// Helpers
|
|
501
|
+
// ---------------------------------------------------------------------------
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Builds a minimal OffensiveSecurityAgent with mocked internals.
|
|
505
|
+
*
|
|
506
|
+
* Bypasses the constructor (which needs a real session, model, tools, etc.)
|
|
507
|
+
* via Object.create so we can unit-test consume() in isolation.
|
|
508
|
+
*/
|
|
509
|
+
function buildStubAgent(overrides: {
|
|
510
|
+
fullStream: AsyncIterable<unknown>;
|
|
511
|
+
commandShell?: { dispose: () => Promise<void> };
|
|
512
|
+
abortSignal?: AbortSignal;
|
|
513
|
+
resolveResult?: (sr: unknown) => unknown;
|
|
514
|
+
browserSession?: { disconnect: () => Promise<void> };
|
|
515
|
+
ownsBrowserSession?: boolean;
|
|
516
|
+
/** Overrides the default listener-registry stub (real registry in the finalization test). */
|
|
517
|
+
callbackListeners?: unknown;
|
|
518
|
+
messagesPath?: string | null;
|
|
519
|
+
latestMessages?: unknown[] | null;
|
|
520
|
+
writeImpl?: (messagesPath: string, contents: string) => Promise<void>;
|
|
521
|
+
responseCaptured?: Promise<unknown>;
|
|
522
|
+
responseToolResult?: unknown;
|
|
523
|
+
subagentId?: string;
|
|
524
|
+
subagentName?: string;
|
|
525
|
+
agentMode?: "default" | "plan" | "fast-strike";
|
|
526
|
+
streamIdFactory?: (context: unknown) => string;
|
|
527
|
+
contextRecorder?: RunContextRecorder;
|
|
528
|
+
}): OffensiveSecurityAgent<unknown> {
|
|
529
|
+
const agent = Object.create(
|
|
530
|
+
OffensiveSecurityAgent.prototype,
|
|
531
|
+
) as OffensiveSecurityAgent<unknown>;
|
|
532
|
+
|
|
533
|
+
const bus = new AgentEventBus();
|
|
534
|
+
vi.spyOn(bus, "emitStreamPart").mockImplementation(() => {});
|
|
535
|
+
|
|
536
|
+
Object.defineProperty(agent, "eventBus", { value: bus });
|
|
537
|
+
Object.defineProperty(agent, "streamResult", {
|
|
538
|
+
value: { fullStream: overrides.fullStream },
|
|
539
|
+
});
|
|
540
|
+
// Stub bypasses the constructor, so pre-settle the initialization seam —
|
|
541
|
+
// consume() awaits streamReady before touching the (stubbed) streamResult.
|
|
542
|
+
Object.defineProperty(agent, "streamInit", {
|
|
543
|
+
value: Promise.resolve(),
|
|
544
|
+
});
|
|
545
|
+
Object.defineProperty(agent, "subagentId", {
|
|
546
|
+
value: overrides.subagentId,
|
|
547
|
+
});
|
|
548
|
+
Object.defineProperty(agent, "subagentName", {
|
|
549
|
+
value: overrides.subagentName,
|
|
550
|
+
});
|
|
551
|
+
Object.defineProperty(agent, "agentMode", {
|
|
552
|
+
value: overrides.agentMode ?? "default",
|
|
553
|
+
});
|
|
554
|
+
// Never resolves by default, so consume() settles via the drain path.
|
|
555
|
+
Object.defineProperty(agent, "responseCaptured", {
|
|
556
|
+
value: overrides.responseCaptured ?? new Promise(() => {}),
|
|
557
|
+
});
|
|
558
|
+
Object.defineProperty(agent, "_responseToolResult", {
|
|
559
|
+
value: overrides.responseToolResult,
|
|
560
|
+
});
|
|
561
|
+
// Stub bypasses the constructor, so provide a minimal session for busSessionId.
|
|
562
|
+
Object.defineProperty(agent, "_session", {
|
|
563
|
+
value: { id: "ses_stub" },
|
|
564
|
+
});
|
|
565
|
+
Object.defineProperty(agent, "currentMessageId", {
|
|
566
|
+
value: null,
|
|
567
|
+
writable: true,
|
|
568
|
+
});
|
|
569
|
+
Object.defineProperty(agent, "commandShell", {
|
|
570
|
+
value: overrides.commandShell,
|
|
571
|
+
});
|
|
572
|
+
Object.defineProperty(agent, "abortSignal", {
|
|
573
|
+
value: overrides.abortSignal,
|
|
574
|
+
});
|
|
575
|
+
Object.defineProperty(agent, "resolveResult", {
|
|
576
|
+
value: overrides.resolveResult,
|
|
577
|
+
});
|
|
578
|
+
Object.defineProperty(agent, "browserSession", {
|
|
579
|
+
value: overrides.browserSession,
|
|
580
|
+
});
|
|
581
|
+
Object.defineProperty(agent, "ownsBrowserSession", {
|
|
582
|
+
value: overrides.ownsBrowserSession ?? false,
|
|
583
|
+
});
|
|
584
|
+
// The constructor is bypassed, so provide the listener registry that
|
|
585
|
+
// finalizeRun drains; these tests never start listeners.
|
|
586
|
+
Object.defineProperty(agent, "callbackListeners", {
|
|
587
|
+
value:
|
|
588
|
+
overrides.callbackListeners ??
|
|
589
|
+
({
|
|
590
|
+
register() {},
|
|
591
|
+
get() {
|
|
592
|
+
return undefined;
|
|
593
|
+
},
|
|
594
|
+
remove() {},
|
|
595
|
+
async stopAll() {
|
|
596
|
+
return [];
|
|
597
|
+
},
|
|
598
|
+
} as object),
|
|
599
|
+
});
|
|
600
|
+
Object.defineProperty(agent, "messagesPath", {
|
|
601
|
+
value: overrides.messagesPath ?? null,
|
|
602
|
+
});
|
|
603
|
+
const writer = new AgentMessageWriter({
|
|
604
|
+
messagesPath: overrides.messagesPath ?? null,
|
|
605
|
+
...(overrides.writeImpl ? { writeImpl: overrides.writeImpl } : {}),
|
|
606
|
+
});
|
|
607
|
+
if (overrides.latestMessages) {
|
|
608
|
+
writer.setLatest(overrides.latestMessages as never[]);
|
|
609
|
+
}
|
|
610
|
+
Object.defineProperty(agent, "writer", { value: writer });
|
|
611
|
+
Object.defineProperty(agent, "finalizeInterruptedStep", {
|
|
612
|
+
value: createInterruptedStepFinalizer({
|
|
613
|
+
eventBus: bus,
|
|
614
|
+
sessionId: "ses_stub",
|
|
615
|
+
writer,
|
|
616
|
+
responseToolName: "response",
|
|
617
|
+
responseToolFired: () => false,
|
|
618
|
+
}),
|
|
619
|
+
});
|
|
620
|
+
Object.defineProperty(agent, "streamIdFactory", {
|
|
621
|
+
value: overrides.streamIdFactory,
|
|
622
|
+
});
|
|
623
|
+
Object.defineProperty(agent, "contextRecorder", {
|
|
624
|
+
value: overrides.contextRecorder,
|
|
625
|
+
});
|
|
626
|
+
|
|
627
|
+
return agent;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
async function* yieldChunks(
|
|
631
|
+
chunks: unknown[],
|
|
632
|
+
): AsyncGenerator<unknown, void, undefined> {
|
|
633
|
+
for (const c of chunks) yield c;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async function* yieldThenThrow(
|
|
637
|
+
chunks: unknown[],
|
|
638
|
+
error: Error,
|
|
639
|
+
): AsyncGenerator<unknown, void, undefined> {
|
|
640
|
+
for (const c of chunks) yield c;
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// ---------------------------------------------------------------------------
|
|
645
|
+
// Tests
|
|
646
|
+
// ---------------------------------------------------------------------------
|
|
647
|
+
|
|
648
|
+
describe("workspace tool access", () => {
|
|
649
|
+
it("requires both an interactive operator and an explicit current request", () => {
|
|
650
|
+
const tools = [
|
|
651
|
+
"execute_command",
|
|
652
|
+
"list_workspace_domains",
|
|
653
|
+
"create_workspace_domain",
|
|
654
|
+
"list_workspace_apps",
|
|
655
|
+
"create_workspace_app",
|
|
656
|
+
"update_workspace_app",
|
|
657
|
+
"list_workspace_endpoints",
|
|
658
|
+
"create_workspace_endpoint",
|
|
659
|
+
"update_workspace_endpoint",
|
|
660
|
+
];
|
|
661
|
+
const request =
|
|
662
|
+
"Can you add a new app called Test App with the endpoint health to console pls";
|
|
663
|
+
|
|
664
|
+
expect(filterWorkspaceToolsForRun(tools, request, true)).toEqual(tools);
|
|
665
|
+
expect(
|
|
666
|
+
filterWorkspaceToolsForRun(
|
|
667
|
+
tools,
|
|
668
|
+
"Use create_workspace_app for this record",
|
|
669
|
+
true,
|
|
670
|
+
),
|
|
671
|
+
).toEqual(tools);
|
|
672
|
+
expect(filterWorkspaceToolsForRun(tools, request, false)).toEqual([
|
|
673
|
+
"execute_command",
|
|
674
|
+
]);
|
|
675
|
+
expect(
|
|
676
|
+
filterWorkspaceToolsForRun(
|
|
677
|
+
tools,
|
|
678
|
+
"Pentest the console app and enumerate its endpoints",
|
|
679
|
+
true,
|
|
680
|
+
),
|
|
681
|
+
).toEqual(["execute_command"]);
|
|
682
|
+
for (const request of [
|
|
683
|
+
"Set up a pentest against the endpoints in the console workspace",
|
|
684
|
+
"Move on to the console app's admin endpoints",
|
|
685
|
+
"Change your approach and fuzz the console app endpoints",
|
|
686
|
+
"Set the scope to the console application and its endpoints",
|
|
687
|
+
"Update me on progress against the workspace endpoints",
|
|
688
|
+
"Correct me if I'm wrong but the console app has 3 endpoints",
|
|
689
|
+
"Change the workspace app testing strategy and continue",
|
|
690
|
+
"Set the workspace endpoint scan scope to /api",
|
|
691
|
+
"Move the console endpoint fuzzing to a new phase",
|
|
692
|
+
"Update the workspace application pentest plan",
|
|
693
|
+
"Correct the console endpoint test assumptions",
|
|
694
|
+
"Repair the workspace app vulnerability",
|
|
695
|
+
"Test whether the workspace app can link to an external domain",
|
|
696
|
+
"Check whether the workspace domain can link to the app during the test",
|
|
697
|
+
"Can I link this workspace domain to the Payments app?",
|
|
698
|
+
"Is it possible to link this workspace domain to the Payments app?",
|
|
699
|
+
"Tell me whether I should link this workspace domain to the Payments app",
|
|
700
|
+
"Please don't link the workspace app to its domain",
|
|
701
|
+
"Please do not create a workspace domain",
|
|
702
|
+
"Never create a workspace app without asking first",
|
|
703
|
+
"Can I create a workspace app for Billing?",
|
|
704
|
+
"How do I update the workspace endpoint transport to grpc?",
|
|
705
|
+
"Can I link example.com to my workspace?",
|
|
706
|
+
"How can I link this workspace domain to the Payments app?",
|
|
707
|
+
"How would we update the workspace application description?",
|
|
708
|
+
"Get started on the console workspace endpoints",
|
|
709
|
+
"What if we move on to the workspace apps next?",
|
|
710
|
+
// A coordinated write verb after `and`/`but` must not escape the leading
|
|
711
|
+
// interrogative — the whole capability question stays read-only.
|
|
712
|
+
"How do I enumerate the workspace apps and link this workspace domain to the Payments application?",
|
|
713
|
+
"Is it possible to review the workspace apps but create a workspace domain?",
|
|
714
|
+
]) {
|
|
715
|
+
expect(filterWorkspaceToolsForRun(tools, request, true)).toEqual([
|
|
716
|
+
"execute_command",
|
|
717
|
+
]);
|
|
718
|
+
}
|
|
719
|
+
expect(
|
|
720
|
+
filterWorkspaceToolsForRun(
|
|
721
|
+
tools,
|
|
722
|
+
`<skill name="pentest">${request}</skill>`,
|
|
723
|
+
true,
|
|
724
|
+
),
|
|
725
|
+
).toEqual(["execute_command"]);
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
it("exposes read-only listing tools but never mutations for recon phrasing", () => {
|
|
729
|
+
const tools = [
|
|
730
|
+
"execute_command",
|
|
731
|
+
"list_workspace_domains",
|
|
732
|
+
"create_workspace_domain",
|
|
733
|
+
"list_workspace_apps",
|
|
734
|
+
"create_workspace_app",
|
|
735
|
+
"update_workspace_app",
|
|
736
|
+
"list_workspace_endpoints",
|
|
737
|
+
"create_workspace_endpoint",
|
|
738
|
+
"update_workspace_endpoint",
|
|
739
|
+
];
|
|
740
|
+
|
|
741
|
+
// Recon verbs (find/search/show/list) targeting the console/workspace must
|
|
742
|
+
// only unlock the read-only `list_*` tools, never mutations.
|
|
743
|
+
for (const request of [
|
|
744
|
+
"Find all endpoints on the console workspace and pentest them",
|
|
745
|
+
"Search the console workspace for apps in scope",
|
|
746
|
+
"Show me the endpoints registered in the workspace",
|
|
747
|
+
"List the apps in my console workspace",
|
|
748
|
+
"Show the domains in my console workspace",
|
|
749
|
+
"What domains are in my workspace?",
|
|
750
|
+
"Get the workspace endpoints",
|
|
751
|
+
"Which of the workspace apps are registered?",
|
|
752
|
+
"Show me a list of all workspace endpoints",
|
|
753
|
+
"What are the domains in my workspace?",
|
|
754
|
+
"Which of our workspace apps are available?",
|
|
755
|
+
"Get a list of the workspace endpoints",
|
|
756
|
+
// A coordinated write verb ("create") after `and` must not escape the
|
|
757
|
+
// leading capability question: the read verb still exposes `list_*`, but
|
|
758
|
+
// the mutation tools stay gated.
|
|
759
|
+
"Can I list the workspace endpoints and create a workspace endpoint?",
|
|
760
|
+
]) {
|
|
761
|
+
expect(filterWorkspaceToolsForRun(tools, request, true)).toEqual([
|
|
762
|
+
"execute_command",
|
|
763
|
+
"list_workspace_domains",
|
|
764
|
+
"list_workspace_apps",
|
|
765
|
+
"list_workspace_endpoints",
|
|
766
|
+
]);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
it("exposes mutation tools only for explicit write requests", () => {
|
|
771
|
+
const tools = [
|
|
772
|
+
"execute_command",
|
|
773
|
+
"list_workspace_domains",
|
|
774
|
+
"create_workspace_domain",
|
|
775
|
+
"list_workspace_apps",
|
|
776
|
+
"create_workspace_app",
|
|
777
|
+
"update_workspace_app",
|
|
778
|
+
"list_workspace_endpoints",
|
|
779
|
+
"create_workspace_endpoint",
|
|
780
|
+
"update_workspace_endpoint",
|
|
781
|
+
];
|
|
782
|
+
|
|
783
|
+
// Write verbs and "break down <threat model>" imports open the mutation set.
|
|
784
|
+
expect(
|
|
785
|
+
filterWorkspaceToolsForRun(
|
|
786
|
+
tools,
|
|
787
|
+
"Register a new app called Billing in the console workspace",
|
|
788
|
+
true,
|
|
789
|
+
),
|
|
790
|
+
).toEqual(tools);
|
|
791
|
+
expect(
|
|
792
|
+
filterWorkspaceToolsForRun(
|
|
793
|
+
tools,
|
|
794
|
+
"Break down this threat model into apps and endpoints in the workspace",
|
|
795
|
+
true,
|
|
796
|
+
),
|
|
797
|
+
).toEqual(tools);
|
|
798
|
+
expect(
|
|
799
|
+
filterWorkspaceToolsForRun(
|
|
800
|
+
tools,
|
|
801
|
+
"Change the workspace endpoint transport to grpc",
|
|
802
|
+
true,
|
|
803
|
+
),
|
|
804
|
+
).toEqual(tools);
|
|
805
|
+
expect(
|
|
806
|
+
filterWorkspaceToolsForRun(
|
|
807
|
+
tools,
|
|
808
|
+
"Link the console application to this domain",
|
|
809
|
+
true,
|
|
810
|
+
),
|
|
811
|
+
).toEqual(tools);
|
|
812
|
+
expect(
|
|
813
|
+
filterWorkspaceToolsForRun(
|
|
814
|
+
tools,
|
|
815
|
+
"Unlink the workspace app from its domain",
|
|
816
|
+
true,
|
|
817
|
+
),
|
|
818
|
+
).toEqual(tools);
|
|
819
|
+
expect(
|
|
820
|
+
filterWorkspaceToolsForRun(
|
|
821
|
+
tools,
|
|
822
|
+
"Link this workspace domain to the Payments application",
|
|
823
|
+
true,
|
|
824
|
+
),
|
|
825
|
+
).toEqual(tools);
|
|
826
|
+
expect(
|
|
827
|
+
filterWorkspaceToolsForRun(
|
|
828
|
+
tools,
|
|
829
|
+
"Can you link this workspace domain to the Payments application?",
|
|
830
|
+
true,
|
|
831
|
+
),
|
|
832
|
+
).toEqual(tools);
|
|
833
|
+
expect(
|
|
834
|
+
filterWorkspaceToolsForRun(
|
|
835
|
+
tools,
|
|
836
|
+
"How can you link this workspace domain to the Payments application?",
|
|
837
|
+
true,
|
|
838
|
+
),
|
|
839
|
+
).toEqual(tools);
|
|
840
|
+
expect(
|
|
841
|
+
filterWorkspaceToolsForRun(
|
|
842
|
+
tools,
|
|
843
|
+
"How would you update the workspace application description to Primary API?",
|
|
844
|
+
true,
|
|
845
|
+
),
|
|
846
|
+
).toEqual(tools);
|
|
847
|
+
expect(
|
|
848
|
+
filterWorkspaceToolsForRun(
|
|
849
|
+
tools,
|
|
850
|
+
"Please unlink the workspace app from its domain",
|
|
851
|
+
true,
|
|
852
|
+
),
|
|
853
|
+
).toEqual(tools);
|
|
854
|
+
expect(
|
|
855
|
+
filterWorkspaceToolsForRun(
|
|
856
|
+
tools,
|
|
857
|
+
"I need you to link this workspace domain to the Payments application",
|
|
858
|
+
true,
|
|
859
|
+
),
|
|
860
|
+
).toEqual(tools);
|
|
861
|
+
// "I'd like to …" / "I would like to …" without an explicit "you" must
|
|
862
|
+
// expose writes, matching the "I want to …" / "I need to …" branch.
|
|
863
|
+
expect(
|
|
864
|
+
filterWorkspaceToolsForRun(
|
|
865
|
+
tools,
|
|
866
|
+
"I'd like to link this workspace domain to the Payments application",
|
|
867
|
+
true,
|
|
868
|
+
),
|
|
869
|
+
).toEqual(tools);
|
|
870
|
+
expect(
|
|
871
|
+
filterWorkspaceToolsForRun(
|
|
872
|
+
tools,
|
|
873
|
+
"I would like to link this workspace domain to the Payments application",
|
|
874
|
+
true,
|
|
875
|
+
),
|
|
876
|
+
).toEqual(tools);
|
|
877
|
+
// A separate prohibition in another clause must not veto an explicit write.
|
|
878
|
+
expect(
|
|
879
|
+
filterWorkspaceToolsForRun(
|
|
880
|
+
tools,
|
|
881
|
+
"Please link this workspace domain to the Payments application, but do not create any new endpoints",
|
|
882
|
+
true,
|
|
883
|
+
),
|
|
884
|
+
).toEqual(tools);
|
|
885
|
+
expect(
|
|
886
|
+
filterWorkspaceToolsForRun(
|
|
887
|
+
tools,
|
|
888
|
+
"Link this workspace domain to the Payments application; do not create any endpoints",
|
|
889
|
+
true,
|
|
890
|
+
),
|
|
891
|
+
).toEqual(tools);
|
|
892
|
+
expect(
|
|
893
|
+
filterWorkspaceToolsForRun(
|
|
894
|
+
tools,
|
|
895
|
+
"Don't create any workspace endpoints, but link this workspace domain to the Payments application",
|
|
896
|
+
true,
|
|
897
|
+
),
|
|
898
|
+
).toEqual(tools);
|
|
899
|
+
expect(
|
|
900
|
+
filterWorkspaceToolsForRun(
|
|
901
|
+
tools,
|
|
902
|
+
"Don't create any workspace endpoints and link this workspace domain to the Payments application",
|
|
903
|
+
true,
|
|
904
|
+
),
|
|
905
|
+
).toEqual(tools);
|
|
906
|
+
expect(
|
|
907
|
+
filterWorkspaceToolsForRun(
|
|
908
|
+
tools,
|
|
909
|
+
"Link example.com to the Payments application in my workspace",
|
|
910
|
+
true,
|
|
911
|
+
),
|
|
912
|
+
).toEqual(tools);
|
|
913
|
+
expect(
|
|
914
|
+
filterWorkspaceToolsForRun(
|
|
915
|
+
tools,
|
|
916
|
+
"Unlink example.com from the Payments app in my workspace",
|
|
917
|
+
true,
|
|
918
|
+
),
|
|
919
|
+
).toEqual(tools);
|
|
920
|
+
expect(
|
|
921
|
+
filterWorkspaceToolsForRun(
|
|
922
|
+
tools,
|
|
923
|
+
"Update the workspace application description to Primary API",
|
|
924
|
+
true,
|
|
925
|
+
),
|
|
926
|
+
).toEqual(tools);
|
|
927
|
+
expect(
|
|
928
|
+
filterWorkspaceToolsForRun(
|
|
929
|
+
tools,
|
|
930
|
+
"Rename the console app to Payments",
|
|
931
|
+
true,
|
|
932
|
+
),
|
|
933
|
+
).toEqual(tools);
|
|
934
|
+
expect(
|
|
935
|
+
filterWorkspaceToolsForRun(
|
|
936
|
+
tools,
|
|
937
|
+
"Set the workspace endpoint parent application to app-2",
|
|
938
|
+
true,
|
|
939
|
+
),
|
|
940
|
+
).toEqual(tools);
|
|
941
|
+
expect(
|
|
942
|
+
filterWorkspaceToolsForRun(
|
|
943
|
+
tools,
|
|
944
|
+
"Add example.com to my console workspace",
|
|
945
|
+
true,
|
|
946
|
+
),
|
|
947
|
+
).toEqual(tools);
|
|
948
|
+
expect(
|
|
949
|
+
filterWorkspaceToolsForRun(
|
|
950
|
+
tools,
|
|
951
|
+
"Can you link example.com to my workspace?",
|
|
952
|
+
true,
|
|
953
|
+
),
|
|
954
|
+
).toEqual(tools);
|
|
955
|
+
|
|
956
|
+
// A direct mention of a read tool never unlocks the mutation tools.
|
|
957
|
+
expect(
|
|
958
|
+
filterWorkspaceToolsForRun(
|
|
959
|
+
tools,
|
|
960
|
+
"Use list_workspace_apps for this record",
|
|
961
|
+
true,
|
|
962
|
+
),
|
|
963
|
+
).toEqual([
|
|
964
|
+
"execute_command",
|
|
965
|
+
"list_workspace_domains",
|
|
966
|
+
"list_workspace_apps",
|
|
967
|
+
"list_workspace_endpoints",
|
|
968
|
+
]);
|
|
969
|
+
});
|
|
970
|
+
});
|
|
971
|
+
|
|
972
|
+
describe("OffensiveSecurityAgent.consume()", () => {
|
|
973
|
+
const textDelta = { type: "text-delta", text: "hi" };
|
|
974
|
+
|
|
975
|
+
describe("command shell disposal on stream error (leak fix)", () => {
|
|
976
|
+
it("disposes shell after a successful stream", async () => {
|
|
977
|
+
const dispose = vi.fn().mockResolvedValue(undefined);
|
|
978
|
+
const agent = buildStubAgent({
|
|
979
|
+
fullStream: yieldChunks([textDelta]),
|
|
980
|
+
commandShell: { dispose },
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
await agent.consume();
|
|
984
|
+
expect(dispose).toHaveBeenCalledOnce();
|
|
985
|
+
});
|
|
986
|
+
|
|
987
|
+
it("disposes shell when the stream throws mid-iteration", async () => {
|
|
988
|
+
const dispose = vi.fn().mockResolvedValue(undefined);
|
|
989
|
+
const agent = buildStubAgent({
|
|
990
|
+
fullStream: yieldThenThrow([textDelta], new Error("stream exploded")),
|
|
991
|
+
commandShell: { dispose },
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
await expect(agent.consume()).rejects.toThrow("stream exploded");
|
|
995
|
+
expect(dispose).toHaveBeenCalledOnce();
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
it("disposes shell when the stream throws immediately (no chunks)", async () => {
|
|
999
|
+
const dispose = vi.fn().mockResolvedValue(undefined);
|
|
1000
|
+
const agent = buildStubAgent({
|
|
1001
|
+
fullStream: yieldThenThrow([], new Error("instant failure")),
|
|
1002
|
+
commandShell: { dispose },
|
|
1003
|
+
});
|
|
1004
|
+
|
|
1005
|
+
await expect(agent.consume()).rejects.toThrow("instant failure");
|
|
1006
|
+
expect(dispose).toHaveBeenCalledOnce();
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
it("drain does not settle before the shell's kill barrier resolves", async () => {
|
|
1010
|
+
// Stream error while a command is still active: dispose() returns a
|
|
1011
|
+
// pending barrier (bounded in production by the runner's kill
|
|
1012
|
+
// protocol). Hosts rely on drained before closeout, so consume() must
|
|
1013
|
+
// not settle while that barrier is pending.
|
|
1014
|
+
let releaseBarrier: (() => void) | undefined;
|
|
1015
|
+
const barrier = new Promise<void>((res) => {
|
|
1016
|
+
releaseBarrier = res;
|
|
1017
|
+
});
|
|
1018
|
+
const dispose = vi.fn(() => barrier);
|
|
1019
|
+
const agent = buildStubAgent({
|
|
1020
|
+
fullStream: yieldThenThrow([textDelta], new Error("stream exploded")),
|
|
1021
|
+
commandShell: { dispose },
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
let consumeSettled = false;
|
|
1025
|
+
const consumeDone = agent.consume().then(
|
|
1026
|
+
() => {
|
|
1027
|
+
consumeSettled = true;
|
|
1028
|
+
},
|
|
1029
|
+
() => {
|
|
1030
|
+
consumeSettled = true;
|
|
1031
|
+
},
|
|
1032
|
+
);
|
|
1033
|
+
|
|
1034
|
+
// finalizeRun has called dispose, but its barrier is pending — the
|
|
1035
|
+
// drain must still be unsettled.
|
|
1036
|
+
await vi.waitFor(() => expect(dispose).toHaveBeenCalledOnce());
|
|
1037
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1038
|
+
expect(consumeSettled).toBe(false);
|
|
1039
|
+
|
|
1040
|
+
releaseBarrier?.();
|
|
1041
|
+
await consumeDone;
|
|
1042
|
+
expect(consumeSettled).toBe(true);
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
it("disposes shell when emitStreamPart throws", async () => {
|
|
1046
|
+
const dispose = vi.fn().mockResolvedValue(undefined);
|
|
1047
|
+
const agent = buildStubAgent({
|
|
1048
|
+
fullStream: yieldChunks([textDelta]),
|
|
1049
|
+
commandShell: { dispose },
|
|
1050
|
+
});
|
|
1051
|
+
|
|
1052
|
+
vi.spyOn(agent.eventBus, "emitStreamPart").mockImplementation(() => {
|
|
1053
|
+
throw new Error("bus emit failed");
|
|
1054
|
+
});
|
|
1055
|
+
|
|
1056
|
+
await expect(agent.consume()).rejects.toThrow("bus emit failed");
|
|
1057
|
+
expect(dispose).toHaveBeenCalledOnce();
|
|
1058
|
+
});
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
describe("browser session teardown on completion (Chromium leak fix)", () => {
|
|
1062
|
+
it("disconnects the owned browser session after a successful stream", async () => {
|
|
1063
|
+
const disconnect = vi.fn().mockResolvedValue(undefined);
|
|
1064
|
+
const agent = buildStubAgent({
|
|
1065
|
+
fullStream: yieldChunks([textDelta]),
|
|
1066
|
+
browserSession: { disconnect },
|
|
1067
|
+
ownsBrowserSession: true,
|
|
1068
|
+
});
|
|
1069
|
+
|
|
1070
|
+
await agent.consume();
|
|
1071
|
+
expect(disconnect).toHaveBeenCalledOnce();
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
it("disconnects the owned browser session when the stream throws", async () => {
|
|
1075
|
+
const disconnect = vi.fn().mockResolvedValue(undefined);
|
|
1076
|
+
const agent = buildStubAgent({
|
|
1077
|
+
fullStream: yieldThenThrow([textDelta], new Error("stream exploded")),
|
|
1078
|
+
browserSession: { disconnect },
|
|
1079
|
+
ownsBrowserSession: true,
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
await expect(agent.consume()).rejects.toThrow("stream exploded");
|
|
1083
|
+
expect(disconnect).toHaveBeenCalledOnce();
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
it("does NOT disconnect an inherited (not-owned) browser session", async () => {
|
|
1087
|
+
const disconnect = vi.fn().mockResolvedValue(undefined);
|
|
1088
|
+
const agent = buildStubAgent({
|
|
1089
|
+
fullStream: yieldChunks([textDelta]),
|
|
1090
|
+
browserSession: { disconnect },
|
|
1091
|
+
ownsBrowserSession: false,
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
await agent.consume();
|
|
1095
|
+
expect(disconnect).not.toHaveBeenCalled();
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
it("swallows a disconnect failure so it never masks the run result", async () => {
|
|
1099
|
+
const disconnect = vi.fn().mockRejectedValue(new Error("kill failed"));
|
|
1100
|
+
const agent = buildStubAgent({
|
|
1101
|
+
fullStream: yieldChunks([textDelta]),
|
|
1102
|
+
browserSession: { disconnect },
|
|
1103
|
+
ownsBrowserSession: true,
|
|
1104
|
+
resolveResult: () => "ok",
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
await expect(agent.consume()).resolves.toBe("ok");
|
|
1108
|
+
expect(disconnect).toHaveBeenCalledOnce();
|
|
1109
|
+
});
|
|
1110
|
+
});
|
|
1111
|
+
|
|
1112
|
+
describe("no-shell scenarios (sandbox mode)", () => {
|
|
1113
|
+
it("succeeds without shell when stream completes normally", async () => {
|
|
1114
|
+
const agent = buildStubAgent({
|
|
1115
|
+
fullStream: yieldChunks([textDelta]),
|
|
1116
|
+
commandShell: undefined,
|
|
1117
|
+
});
|
|
1118
|
+
|
|
1119
|
+
await expect(agent.consume()).resolves.toBeUndefined();
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
it("propagates stream error without shell (no dispose to call)", async () => {
|
|
1123
|
+
const agent = buildStubAgent({
|
|
1124
|
+
fullStream: yieldThenThrow([], new Error("sandbox boom")),
|
|
1125
|
+
commandShell: undefined,
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
await expect(agent.consume()).rejects.toThrow("sandbox boom");
|
|
1129
|
+
});
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
describe("disposal ordering", () => {
|
|
1133
|
+
it("disposes shell before resolveResult runs", async () => {
|
|
1134
|
+
const callOrder: string[] = [];
|
|
1135
|
+
|
|
1136
|
+
const dispose = vi.fn(() => {
|
|
1137
|
+
callOrder.push("dispose");
|
|
1138
|
+
return Promise.resolve();
|
|
1139
|
+
});
|
|
1140
|
+
const resolveResult = vi.fn(() => {
|
|
1141
|
+
callOrder.push("resolveResult");
|
|
1142
|
+
return "result";
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
const agent = buildStubAgent({
|
|
1146
|
+
fullStream: yieldChunks([textDelta]),
|
|
1147
|
+
commandShell: { dispose },
|
|
1148
|
+
resolveResult,
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
const result = await agent.consume();
|
|
1152
|
+
expect(result).toBe("result");
|
|
1153
|
+
expect(dispose).toHaveBeenCalledOnce();
|
|
1154
|
+
expect(resolveResult).toHaveBeenCalledOnce();
|
|
1155
|
+
expect(callOrder).toEqual(["dispose", "resolveResult"]);
|
|
1156
|
+
});
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
describe("abort signal", () => {
|
|
1160
|
+
it.each([
|
|
1161
|
+
new Error("Execution broker receipt unavailable"),
|
|
1162
|
+
new DOMException("Recorded run deadline expired", "AbortError"),
|
|
1163
|
+
new DOMException("Attempt deadline expired", "TimeoutError"),
|
|
1164
|
+
Object.assign(
|
|
1165
|
+
new Error("Aborted", { cause: new Error("Backend unavailable") }),
|
|
1166
|
+
{ name: "AbortError" },
|
|
1167
|
+
),
|
|
1168
|
+
"host shutdown",
|
|
1169
|
+
0,
|
|
1170
|
+
null,
|
|
1171
|
+
])("preserves an explicit abort reason: %s", async (reason) => {
|
|
1172
|
+
const controller = new AbortController();
|
|
1173
|
+
controller.abort(reason);
|
|
1174
|
+
const agent = buildStubAgent({
|
|
1175
|
+
fullStream: yieldChunks([textDelta]),
|
|
1176
|
+
abortSignal: controller.signal,
|
|
1177
|
+
});
|
|
1178
|
+
await expect(agent.consume()).rejects.toBe(reason);
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
it("uses the originating reason when the SDK echoes a generic abort", async () => {
|
|
1182
|
+
const controller = new AbortController();
|
|
1183
|
+
const cause = new Error("Execution outcome could not be confirmed");
|
|
1184
|
+
controller.abort(cause);
|
|
1185
|
+
const agent = buildStubAgent({
|
|
1186
|
+
fullStream: yieldThenThrow(
|
|
1187
|
+
[],
|
|
1188
|
+
new DOMException("Aborted", "AbortError"),
|
|
1189
|
+
),
|
|
1190
|
+
abortSignal: controller.signal,
|
|
1191
|
+
});
|
|
1192
|
+
await expect(agent.consume()).rejects.toBe(cause);
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
it("preserves the runtime's AbortSignal.timeout reason", async () => {
|
|
1196
|
+
const signal = AbortSignal.timeout(1);
|
|
1197
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
1198
|
+
expect(signal.aborted).toBe(true);
|
|
1199
|
+
const agent = buildStubAgent({
|
|
1200
|
+
fullStream: yieldChunks([]),
|
|
1201
|
+
abortSignal: signal,
|
|
1202
|
+
});
|
|
1203
|
+
await expect(agent.consume()).rejects.toBe(signal.reason);
|
|
1204
|
+
expect(signal.reason.name).toBe("TimeoutError");
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
it("keeps a generic stream abort when the caller signal was not aborted", async () => {
|
|
1208
|
+
const cause = new DOMException("Aborted", "AbortError");
|
|
1209
|
+
const agent = buildStubAgent({
|
|
1210
|
+
fullStream: yieldThenThrow([], cause),
|
|
1211
|
+
abortSignal: new AbortController().signal,
|
|
1212
|
+
});
|
|
1213
|
+
await expect(agent.consume()).rejects.toBe(cause);
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
it("preserves a stream abort carrying its own cause even after a user abort", async () => {
|
|
1217
|
+
const cause = Object.assign(
|
|
1218
|
+
new Error("Aborted", { cause: new Error("Backend unavailable") }),
|
|
1219
|
+
{ name: "AbortError" },
|
|
1220
|
+
);
|
|
1221
|
+
const controller = new AbortController();
|
|
1222
|
+
controller.abort();
|
|
1223
|
+
const agent = buildStubAgent({
|
|
1224
|
+
fullStream: yieldThenThrow([], cause),
|
|
1225
|
+
abortSignal: controller.signal,
|
|
1226
|
+
});
|
|
1227
|
+
await expect(agent.consume()).rejects.toBe(cause);
|
|
1228
|
+
});
|
|
1229
|
+
|
|
1230
|
+
it("keeps user-abort wording for a generic SDK echo", async () => {
|
|
1231
|
+
const controller = new AbortController();
|
|
1232
|
+
controller.abort();
|
|
1233
|
+
const agent = buildStubAgent({
|
|
1234
|
+
fullStream: yieldThenThrow(
|
|
1235
|
+
[],
|
|
1236
|
+
new DOMException("Aborted", "AbortError"),
|
|
1237
|
+
),
|
|
1238
|
+
abortSignal: controller.signal,
|
|
1239
|
+
});
|
|
1240
|
+
await expect(agent.consume()).rejects.toMatchObject({
|
|
1241
|
+
name: "AbortError",
|
|
1242
|
+
message: "Agent aborted by user",
|
|
1243
|
+
});
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
it("keeps a prior abort cause when later cleanup fails", async () => {
|
|
1247
|
+
const controller = new AbortController();
|
|
1248
|
+
const cause = new Error("Target session disconnected");
|
|
1249
|
+
controller.abort(cause);
|
|
1250
|
+
const dispose = vi.fn().mockRejectedValue(new Error("Cleanup failed"));
|
|
1251
|
+
const agent = buildStubAgent({
|
|
1252
|
+
fullStream: yieldChunks([textDelta]),
|
|
1253
|
+
abortSignal: controller.signal,
|
|
1254
|
+
commandShell: { dispose },
|
|
1255
|
+
});
|
|
1256
|
+
await expect(agent.consume()).rejects.toBe(cause);
|
|
1257
|
+
expect(dispose).toHaveBeenCalledOnce();
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
it("waits for owned cleanup before rejecting with the abort cause", async () => {
|
|
1261
|
+
const controller = new AbortController();
|
|
1262
|
+
const cause = new Error("Execution infrastructure failed");
|
|
1263
|
+
controller.abort(cause);
|
|
1264
|
+
let release!: () => void;
|
|
1265
|
+
const disposal = new Promise<void>((resolve) => {
|
|
1266
|
+
release = resolve;
|
|
1267
|
+
});
|
|
1268
|
+
const dispose = vi.fn(() => disposal);
|
|
1269
|
+
const agent = buildStubAgent({
|
|
1270
|
+
fullStream: yieldChunks([textDelta]),
|
|
1271
|
+
abortSignal: controller.signal,
|
|
1272
|
+
commandShell: { dispose },
|
|
1273
|
+
});
|
|
1274
|
+
let settled = false;
|
|
1275
|
+
const result = agent.consume().catch((error) => {
|
|
1276
|
+
settled = true;
|
|
1277
|
+
return error;
|
|
1278
|
+
});
|
|
1279
|
+
try {
|
|
1280
|
+
await vi.waitFor(() => expect(dispose).toHaveBeenCalledOnce());
|
|
1281
|
+
expect(settled).toBe(false);
|
|
1282
|
+
} finally {
|
|
1283
|
+
release();
|
|
1284
|
+
}
|
|
1285
|
+
expect(await result).toBe(cause);
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
it.each([
|
|
1289
|
+
"abort",
|
|
1290
|
+
"provider",
|
|
1291
|
+
"cleanup",
|
|
1292
|
+
])("saves the primary cause in interrupted tool evidence (%s)", async (source) => {
|
|
1293
|
+
const directory = mkdtempSync(join(tmpdir(), "apex-abort-cause-"));
|
|
1294
|
+
const controller = new AbortController();
|
|
1295
|
+
const infrastructure = new Error("Execution infrastructure failed");
|
|
1296
|
+
const provider = new Error("Provider stream failed first");
|
|
1297
|
+
const cause = source === "provider" ? provider : infrastructure;
|
|
1298
|
+
async function* stream() {
|
|
1299
|
+
yield {
|
|
1300
|
+
type: "tool-call",
|
|
1301
|
+
toolCallId: "pending",
|
|
1302
|
+
toolName: "run_command",
|
|
1303
|
+
input: { command: "fixture" },
|
|
1304
|
+
};
|
|
1305
|
+
if (source === "provider") throw provider;
|
|
1306
|
+
if (source === "abort") controller.abort(infrastructure);
|
|
1307
|
+
}
|
|
1308
|
+
const agent = buildStubAgent({
|
|
1309
|
+
fullStream: stream(),
|
|
1310
|
+
abortSignal: controller.signal,
|
|
1311
|
+
commandShell: {
|
|
1312
|
+
dispose: async () => {
|
|
1313
|
+
controller.abort(infrastructure);
|
|
1314
|
+
},
|
|
1315
|
+
},
|
|
1316
|
+
messagesPath: join(directory, "messages.json"),
|
|
1317
|
+
latestMessages: [{ role: "user", content: "fixture" }],
|
|
1318
|
+
});
|
|
1319
|
+
const results: unknown[] = [];
|
|
1320
|
+
agent.eventBus.on("tool-result", (event) => results.push(event.result));
|
|
1321
|
+
try {
|
|
1322
|
+
await expect(agent.consume()).rejects.toBe(cause);
|
|
1323
|
+
expect(results).toContainEqual({
|
|
1324
|
+
type: "error-text",
|
|
1325
|
+
value: expect.stringContaining(cause.message),
|
|
1326
|
+
});
|
|
1327
|
+
const saved = readFileSync(join(directory, "messages.json"), "utf8");
|
|
1328
|
+
expect(saved).toContain(cause.message);
|
|
1329
|
+
expect(saved).not.toContain("Agent aborted by user");
|
|
1330
|
+
if (source === "provider")
|
|
1331
|
+
expect(saved).not.toContain(infrastructure.message);
|
|
1332
|
+
} finally {
|
|
1333
|
+
rmSync(directory, { recursive: true, force: true });
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1337
|
+
it("disposes shell even when abort signal causes throw", async () => {
|
|
1338
|
+
const controller = new AbortController();
|
|
1339
|
+
controller.abort();
|
|
1340
|
+
|
|
1341
|
+
const dispose = vi.fn().mockResolvedValue(undefined);
|
|
1342
|
+
const agent = buildStubAgent({
|
|
1343
|
+
fullStream: yieldChunks([textDelta]),
|
|
1344
|
+
commandShell: { dispose },
|
|
1345
|
+
abortSignal: controller.signal,
|
|
1346
|
+
});
|
|
1347
|
+
|
|
1348
|
+
await expect(agent.consume()).rejects.toThrow("Agent aborted by user");
|
|
1349
|
+
expect(dispose).toHaveBeenCalledOnce();
|
|
1350
|
+
});
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
describe("synthetic tool-result on stream abort/error", () => {
|
|
1354
|
+
const toolCallChunk = {
|
|
1355
|
+
type: "tool-call",
|
|
1356
|
+
toolCallId: "tc1",
|
|
1357
|
+
toolName: "execute_command",
|
|
1358
|
+
};
|
|
1359
|
+
const otherToolCallChunk = {
|
|
1360
|
+
type: "tool-call",
|
|
1361
|
+
toolCallId: "tc2",
|
|
1362
|
+
toolName: "read_file",
|
|
1363
|
+
};
|
|
1364
|
+
const toolResultChunk = {
|
|
1365
|
+
type: "tool-result",
|
|
1366
|
+
toolCallId: "tc1",
|
|
1367
|
+
result: { type: "text", value: "done" },
|
|
1368
|
+
};
|
|
1369
|
+
|
|
1370
|
+
it("emits synthetic tool-result for an in-flight tool when stream throws", async () => {
|
|
1371
|
+
const agent = buildStubAgent({
|
|
1372
|
+
fullStream: yieldThenThrow(
|
|
1373
|
+
[toolCallChunk],
|
|
1374
|
+
new Error("connection reset"),
|
|
1375
|
+
),
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
const emittedResults: Array<{
|
|
1379
|
+
toolCallId: string;
|
|
1380
|
+
result: unknown;
|
|
1381
|
+
}> = [];
|
|
1382
|
+
agent.eventBus.on("tool-result", (e) => {
|
|
1383
|
+
emittedResults.push({ toolCallId: e.toolCallId, result: e.result });
|
|
1384
|
+
});
|
|
1385
|
+
|
|
1386
|
+
await expect(agent.consume()).rejects.toThrow("connection reset");
|
|
1387
|
+
|
|
1388
|
+
expect(emittedResults).toHaveLength(1);
|
|
1389
|
+
expect(emittedResults[0].toolCallId).toBe("tc1");
|
|
1390
|
+
expect(emittedResults[0].result).toMatchObject({
|
|
1391
|
+
type: "error-text",
|
|
1392
|
+
value: expect.stringContaining("connection reset"),
|
|
1393
|
+
});
|
|
1394
|
+
});
|
|
1395
|
+
|
|
1396
|
+
it("does not emit a synthetic result when the matching tool-result already streamed", async () => {
|
|
1397
|
+
const agent = buildStubAgent({
|
|
1398
|
+
fullStream: yieldChunks([toolCallChunk, toolResultChunk]),
|
|
1399
|
+
});
|
|
1400
|
+
|
|
1401
|
+
const emittedResults: unknown[] = [];
|
|
1402
|
+
agent.eventBus.on("tool-result", (e) => emittedResults.push(e));
|
|
1403
|
+
|
|
1404
|
+
await agent.consume();
|
|
1405
|
+
|
|
1406
|
+
expect(emittedResults).toHaveLength(0);
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
it("closes a truncated tool-call (started, args never completed) at finish-step", async () => {
|
|
1410
|
+
const agent = buildStubAgent({
|
|
1411
|
+
fullStream: yieldChunks([
|
|
1412
|
+
{ type: "tool-input-start", id: "tc1", toolName: "response" },
|
|
1413
|
+
{ type: "finish-step" },
|
|
1414
|
+
]),
|
|
1415
|
+
});
|
|
1416
|
+
|
|
1417
|
+
const emittedResults: Array<{ toolCallId: string; result: unknown }> = [];
|
|
1418
|
+
agent.eventBus.on("tool-result", (e) => {
|
|
1419
|
+
emittedResults.push({ toolCallId: e.toolCallId, result: e.result });
|
|
1420
|
+
});
|
|
1421
|
+
|
|
1422
|
+
await agent.consume();
|
|
1423
|
+
|
|
1424
|
+
expect(emittedResults).toHaveLength(1);
|
|
1425
|
+
expect(emittedResults[0].toolCallId).toBe("tc1");
|
|
1426
|
+
expect(emittedResults[0].result).toMatchObject({ type: "error-text" });
|
|
1427
|
+
});
|
|
1428
|
+
|
|
1429
|
+
it("emits one synthetic per in-flight tool when multiple are open at abort", async () => {
|
|
1430
|
+
const controller = new AbortController();
|
|
1431
|
+
controller.abort();
|
|
1432
|
+
|
|
1433
|
+
const agent = buildStubAgent({
|
|
1434
|
+
fullStream: yieldThenThrow(
|
|
1435
|
+
[toolCallChunk, otherToolCallChunk],
|
|
1436
|
+
new DOMException("Aborted", "AbortError"),
|
|
1437
|
+
),
|
|
1438
|
+
abortSignal: controller.signal,
|
|
1439
|
+
});
|
|
1440
|
+
|
|
1441
|
+
const emittedResults: Array<{ toolCallId: string; result: unknown }> = [];
|
|
1442
|
+
agent.eventBus.on("tool-result", (e) => {
|
|
1443
|
+
emittedResults.push({ toolCallId: e.toolCallId, result: e.result });
|
|
1444
|
+
});
|
|
1445
|
+
|
|
1446
|
+
await expect(agent.consume()).rejects.toBeInstanceOf(DOMException);
|
|
1447
|
+
|
|
1448
|
+
expect(emittedResults).toHaveLength(2);
|
|
1449
|
+
const ids = emittedResults.map((r) => r.toolCallId).sort();
|
|
1450
|
+
expect(ids).toEqual(["tc1", "tc2"]);
|
|
1451
|
+
for (const r of emittedResults) {
|
|
1452
|
+
expect(r.result).toMatchObject({
|
|
1453
|
+
type: "error-text",
|
|
1454
|
+
value: expect.stringContaining("aborted"),
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
|
|
1459
|
+
it("uses 'Agent aborted by user' as reason when abortSignal is set", async () => {
|
|
1460
|
+
const controller = new AbortController();
|
|
1461
|
+
controller.abort();
|
|
1462
|
+
|
|
1463
|
+
const agent = buildStubAgent({
|
|
1464
|
+
fullStream: yieldThenThrow(
|
|
1465
|
+
[toolCallChunk],
|
|
1466
|
+
new DOMException("Aborted", "AbortError"),
|
|
1467
|
+
),
|
|
1468
|
+
abortSignal: controller.signal,
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
const emittedResults: Array<{ result: unknown }> = [];
|
|
1472
|
+
agent.eventBus.on("tool-result", (e) =>
|
|
1473
|
+
emittedResults.push({ result: e.result }),
|
|
1474
|
+
);
|
|
1475
|
+
|
|
1476
|
+
await expect(agent.consume()).rejects.toBeInstanceOf(DOMException);
|
|
1477
|
+
|
|
1478
|
+
expect(emittedResults[0].result).toMatchObject({
|
|
1479
|
+
type: "error-text",
|
|
1480
|
+
value: expect.stringContaining("aborted by user"),
|
|
1481
|
+
});
|
|
1482
|
+
});
|
|
1483
|
+
|
|
1484
|
+
it("flushes a tool-error into the snapshot when the stream aborts before finish-step", async () => {
|
|
1485
|
+
// finally must flush deferred toolErrors even if the stream throws first.
|
|
1486
|
+
const agent = buildStubAgent({
|
|
1487
|
+
fullStream: yieldThenThrow(
|
|
1488
|
+
[
|
|
1489
|
+
toolCallChunk,
|
|
1490
|
+
{
|
|
1491
|
+
type: "tool-error",
|
|
1492
|
+
toolCallId: "tc1",
|
|
1493
|
+
toolName: "execute_command",
|
|
1494
|
+
error: new Error("Zod: expected string"),
|
|
1495
|
+
},
|
|
1496
|
+
],
|
|
1497
|
+
new Error("connection reset"),
|
|
1498
|
+
),
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
const emittedResults: Array<{ toolCallId: string; result: unknown }> = [];
|
|
1502
|
+
agent.eventBus.on("tool-result", (e) => {
|
|
1503
|
+
emittedResults.push({ toolCallId: e.toolCallId, result: e.result });
|
|
1504
|
+
});
|
|
1505
|
+
|
|
1506
|
+
await expect(agent.consume()).rejects.toThrow("connection reset");
|
|
1507
|
+
|
|
1508
|
+
expect(emittedResults).toHaveLength(1);
|
|
1509
|
+
expect(emittedResults[0].toolCallId).toBe("tc1");
|
|
1510
|
+
expect(emittedResults[0].result).toMatchObject({
|
|
1511
|
+
type: "error-text",
|
|
1512
|
+
value: expect.stringContaining("Zod: expected string"),
|
|
1513
|
+
});
|
|
1514
|
+
});
|
|
1515
|
+
});
|
|
1516
|
+
|
|
1517
|
+
describe("resolveResult", () => {
|
|
1518
|
+
it("returns resolved value when resolveResult is provided", async () => {
|
|
1519
|
+
const agent = buildStubAgent({
|
|
1520
|
+
fullStream: yieldChunks([textDelta]),
|
|
1521
|
+
resolveResult: () => 42,
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
const result = await agent.consume();
|
|
1525
|
+
expect(result).toBe(42);
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
it("returns undefined when resolveResult is absent", async () => {
|
|
1529
|
+
const agent = buildStubAgent({
|
|
1530
|
+
fullStream: yieldChunks([textDelta]),
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1533
|
+
const result = await agent.consume();
|
|
1534
|
+
expect(result).toBeUndefined();
|
|
1535
|
+
});
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
describe("result/drain decoupling", () => {
|
|
1539
|
+
it("returns the captured result without waiting for the stream to drain", async () => {
|
|
1540
|
+
// Wedged stream: consume() must settle from responseCaptured, not the drain.
|
|
1541
|
+
const neverDrains = (async function* () {
|
|
1542
|
+
await new Promise(() => {});
|
|
1543
|
+
})();
|
|
1544
|
+
const agent = buildStubAgent({
|
|
1545
|
+
fullStream: neverDrains,
|
|
1546
|
+
responseCaptured: Promise.resolve("captured"),
|
|
1547
|
+
resolveResult: () => "from-drain",
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
await expect(agent.consume()).resolves.toBe("captured");
|
|
1551
|
+
});
|
|
1552
|
+
});
|
|
1553
|
+
|
|
1554
|
+
describe("callback listener finalization (owned-resource drain)", () => {
|
|
1555
|
+
it("consume() drains listeners; one failed stop never blocks another, and abortAndDrain retries", async () => {
|
|
1556
|
+
const registry = new CallbackListenerRegistry();
|
|
1557
|
+
const stopped: string[] = [];
|
|
1558
|
+
registry.register({
|
|
1559
|
+
jobId: "wjob_77777_fail01",
|
|
1560
|
+
nonce: "a".repeat(32),
|
|
1561
|
+
scriptPath: "/a.cjs",
|
|
1562
|
+
bindAddress: "0.0.0.0",
|
|
1563
|
+
port: 1,
|
|
1564
|
+
selfTestConfirmed: false,
|
|
1565
|
+
stop: async () => {
|
|
1566
|
+
throw new Error("stop a failed");
|
|
1567
|
+
},
|
|
1568
|
+
});
|
|
1569
|
+
registry.register({
|
|
1570
|
+
jobId: "wjob_77777_pass01",
|
|
1571
|
+
nonce: "b".repeat(32),
|
|
1572
|
+
scriptPath: "/b.cjs",
|
|
1573
|
+
bindAddress: "0.0.0.0",
|
|
1574
|
+
port: 2,
|
|
1575
|
+
selfTestConfirmed: false,
|
|
1576
|
+
stop: async () => {
|
|
1577
|
+
stopped.push("b");
|
|
1578
|
+
return undefined;
|
|
1579
|
+
},
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
const agent = buildStubAgent({
|
|
1583
|
+
fullStream: yieldThenThrow([], new Error("stream boom")),
|
|
1584
|
+
messagesPath: null,
|
|
1585
|
+
callbackListeners: registry,
|
|
1586
|
+
});
|
|
1587
|
+
|
|
1588
|
+
// The stream error stays primary; listener cleanup still ran.
|
|
1589
|
+
await expect(agent.consume()).rejects.toThrow("stream boom");
|
|
1590
|
+
expect(stopped).toEqual(["b"]);
|
|
1591
|
+
expect(registry.get("wjob_77777_fail01")).toBeDefined();
|
|
1592
|
+
expect(registry.get("wjob_77777_pass01")).toBeUndefined();
|
|
1593
|
+
|
|
1594
|
+
// Host teardown path: best-effort retry, never throws, keeps the
|
|
1595
|
+
// failed owner registered for a later cleanup path.
|
|
1596
|
+
await expect(agent.abortAndDrain()).resolves.toBeUndefined();
|
|
1597
|
+
expect(stopped).toEqual(["b"]);
|
|
1598
|
+
expect(registry.get("wjob_77777_fail01")).toBeDefined();
|
|
1599
|
+
});
|
|
1600
|
+
});
|
|
1601
|
+
|
|
1602
|
+
describe("synthetic tool-result: persist timer fallback (bugbot fix)", () => {
|
|
1603
|
+
const toolCallChunk = {
|
|
1604
|
+
type: "tool-call",
|
|
1605
|
+
toolCallId: "tc1",
|
|
1606
|
+
toolName: "execute_command",
|
|
1607
|
+
};
|
|
1608
|
+
|
|
1609
|
+
it("reads persisted messages from disk when latestMessages is null", async () => {
|
|
1610
|
+
const tmpDir = join("/tmp", `pensar-test-${Date.now()}-fallback`);
|
|
1611
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
1612
|
+
const messagesPath = join(tmpDir, "messages.json");
|
|
1613
|
+
|
|
1614
|
+
const existingMessages = [
|
|
1615
|
+
{ role: "user", content: [{ type: "text", text: "run nmap" }] },
|
|
1616
|
+
{ role: "assistant", content: [{ type: "text", text: "Running..." }] },
|
|
1617
|
+
];
|
|
1618
|
+
writeFileSync(messagesPath, JSON.stringify(existingMessages));
|
|
1619
|
+
|
|
1620
|
+
// latestMessages is null — simulates persist timer having flushed
|
|
1621
|
+
const agent = buildStubAgent({
|
|
1622
|
+
fullStream: yieldThenThrow([toolCallChunk], new Error("timeout")),
|
|
1623
|
+
messagesPath,
|
|
1624
|
+
latestMessages: null,
|
|
1625
|
+
});
|
|
1626
|
+
|
|
1627
|
+
try {
|
|
1628
|
+
await agent.consume();
|
|
1629
|
+
} catch {}
|
|
1630
|
+
|
|
1631
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1632
|
+
|
|
1633
|
+
const written = JSON.parse(readFileSync(messagesPath, "utf-8"));
|
|
1634
|
+
// original 2 + assistant (tool-call) + tool (synthetic result)
|
|
1635
|
+
expect(written).toHaveLength(4);
|
|
1636
|
+
expect(written[0].role).toBe("user");
|
|
1637
|
+
expect(written[1].role).toBe("assistant");
|
|
1638
|
+
expect(written[2].role).toBe("assistant");
|
|
1639
|
+
expect(written[2].content[0].type).toBe("tool-call");
|
|
1640
|
+
expect(written[2].content[0].toolCallId).toBe("tc1");
|
|
1641
|
+
expect(written[3].role).toBe("tool");
|
|
1642
|
+
expect(written[3].content[0].toolCallId).toBe("tc1");
|
|
1643
|
+
expect(written[3].content[0].output.type).toBe("error-text");
|
|
1644
|
+
|
|
1645
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
1646
|
+
});
|
|
1647
|
+
|
|
1648
|
+
it("does not lose history when persist timer nulled latestMessages", async () => {
|
|
1649
|
+
const tmpDir = join("/tmp", `pensar-test-${Date.now()}-noloss`);
|
|
1650
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
1651
|
+
const messagesPath = join(tmpDir, "messages.json");
|
|
1652
|
+
|
|
1653
|
+
const existingMessages = [
|
|
1654
|
+
{ role: "user", content: [{ type: "text", text: "scan target" }] },
|
|
1655
|
+
];
|
|
1656
|
+
writeFileSync(messagesPath, JSON.stringify(existingMessages));
|
|
1657
|
+
|
|
1658
|
+
const agent = buildStubAgent({
|
|
1659
|
+
fullStream: yieldThenThrow([toolCallChunk], new Error("network")),
|
|
1660
|
+
messagesPath,
|
|
1661
|
+
latestMessages: null,
|
|
1662
|
+
});
|
|
1663
|
+
|
|
1664
|
+
try {
|
|
1665
|
+
await agent.consume();
|
|
1666
|
+
} catch {}
|
|
1667
|
+
|
|
1668
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1669
|
+
|
|
1670
|
+
const written = JSON.parse(readFileSync(messagesPath, "utf-8"));
|
|
1671
|
+
// Must NOT be just [{ role: "tool" ... }] — must include original messages
|
|
1672
|
+
expect(written.length).toBeGreaterThan(1);
|
|
1673
|
+
expect(written[0].role).toBe("user");
|
|
1674
|
+
|
|
1675
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
1676
|
+
});
|
|
1677
|
+
|
|
1678
|
+
it("waits for an in-flight persist before writing the abort snapshot", async () => {
|
|
1679
|
+
const tmpDir = join("/tmp", `pensar-test-${Date.now()}-persist-race`);
|
|
1680
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
1681
|
+
const messagesPath = join(tmpDir, "messages.json");
|
|
1682
|
+
|
|
1683
|
+
const staleMessages = [
|
|
1684
|
+
{ role: "user", content: [{ type: "text", text: "scan target" }] },
|
|
1685
|
+
];
|
|
1686
|
+
const persistedMessages = [
|
|
1687
|
+
...staleMessages,
|
|
1688
|
+
{
|
|
1689
|
+
role: "assistant",
|
|
1690
|
+
content: [{ type: "text", text: "Starting scan" }],
|
|
1691
|
+
},
|
|
1692
|
+
];
|
|
1693
|
+
writeFileSync(messagesPath, JSON.stringify(staleMessages));
|
|
1694
|
+
|
|
1695
|
+
// Gate the writer's write sink so an in-flight persist can be held
|
|
1696
|
+
// while the stream throws and the abort snapshot waits its turn.
|
|
1697
|
+
let releasePersist!: () => void;
|
|
1698
|
+
const gate = new Promise<void>((resolve) => {
|
|
1699
|
+
releasePersist = resolve;
|
|
1700
|
+
});
|
|
1701
|
+
const writeImpl = async (path: string, contents: string) => {
|
|
1702
|
+
await gate;
|
|
1703
|
+
writeFileSync(path, contents);
|
|
1704
|
+
};
|
|
1705
|
+
|
|
1706
|
+
const agent = buildStubAgent({
|
|
1707
|
+
fullStream: yieldThenThrow([toolCallChunk], new Error("timeout")),
|
|
1708
|
+
messagesPath,
|
|
1709
|
+
latestMessages: null,
|
|
1710
|
+
writeImpl,
|
|
1711
|
+
});
|
|
1712
|
+
|
|
1713
|
+
// Start the in-flight persist against the gated sink.
|
|
1714
|
+
const writer = (agent as unknown as { writer: AgentMessageWriter })
|
|
1715
|
+
.writer;
|
|
1716
|
+
const inFlight = writer
|
|
1717
|
+
.enqueueWrite(persistedMessages as never[])
|
|
1718
|
+
.catch(() => {});
|
|
1719
|
+
|
|
1720
|
+
const consume = agent.consume().catch(() => {});
|
|
1721
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1722
|
+
// The gated persist is still in flight — the abort snapshot must be
|
|
1723
|
+
// queued behind it, not interleaved.
|
|
1724
|
+
releasePersist();
|
|
1725
|
+
await inFlight;
|
|
1726
|
+
await consume;
|
|
1727
|
+
|
|
1728
|
+
const written = JSON.parse(readFileSync(messagesPath, "utf-8"));
|
|
1729
|
+
expect(written.slice(0, persistedMessages.length)).toEqual(
|
|
1730
|
+
persistedMessages,
|
|
1731
|
+
);
|
|
1732
|
+
expect(written.at(-2)).toMatchObject({
|
|
1733
|
+
role: "assistant",
|
|
1734
|
+
content: [{ type: "tool-call", toolCallId: "tc1" }],
|
|
1735
|
+
});
|
|
1736
|
+
expect(written.at(-1)).toMatchObject({
|
|
1737
|
+
role: "tool",
|
|
1738
|
+
content: [{ type: "tool-result", toolCallId: "tc1" }],
|
|
1739
|
+
});
|
|
1740
|
+
|
|
1741
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
1742
|
+
});
|
|
1743
|
+
|
|
1744
|
+
it("does not add assistant tool-call msg when base already contains them", async () => {
|
|
1745
|
+
const tmpDir = join("/tmp", `pensar-test-${Date.now()}-nodup`);
|
|
1746
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
1747
|
+
const messagesPath = join(tmpDir, "messages.json");
|
|
1748
|
+
|
|
1749
|
+
// Simulate onStepFinish having already persisted the assistant tool-call
|
|
1750
|
+
const existingMessages = [
|
|
1751
|
+
{ role: "user", content: [{ type: "text", text: "scan" }] },
|
|
1752
|
+
{
|
|
1753
|
+
role: "assistant",
|
|
1754
|
+
content: [
|
|
1755
|
+
{
|
|
1756
|
+
type: "tool-call",
|
|
1757
|
+
toolCallId: "tc1",
|
|
1758
|
+
toolName: "execute_command",
|
|
1759
|
+
input: {},
|
|
1760
|
+
},
|
|
1761
|
+
],
|
|
1762
|
+
},
|
|
1763
|
+
];
|
|
1764
|
+
writeFileSync(messagesPath, JSON.stringify(existingMessages));
|
|
1765
|
+
|
|
1766
|
+
const agent = buildStubAgent({
|
|
1767
|
+
fullStream: yieldThenThrow([toolCallChunk], new Error("abort")),
|
|
1768
|
+
messagesPath,
|
|
1769
|
+
latestMessages: null,
|
|
1770
|
+
});
|
|
1771
|
+
|
|
1772
|
+
try {
|
|
1773
|
+
await agent.consume();
|
|
1774
|
+
} catch {}
|
|
1775
|
+
|
|
1776
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1777
|
+
|
|
1778
|
+
const written = JSON.parse(readFileSync(messagesPath, "utf-8"));
|
|
1779
|
+
// Should NOT duplicate the assistant message — just user + assistant + tool
|
|
1780
|
+
expect(written).toHaveLength(3);
|
|
1781
|
+
expect(written[0].role).toBe("user");
|
|
1782
|
+
expect(written[1].role).toBe("assistant");
|
|
1783
|
+
expect(written[2].role).toBe("tool");
|
|
1784
|
+
expect(written[2].content[0].output.type).toBe("error-text");
|
|
1785
|
+
|
|
1786
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
1787
|
+
});
|
|
1788
|
+
});
|
|
1789
|
+
|
|
1790
|
+
describe("parallel tool abort persists completed tools (bugbot fix)", () => {
|
|
1791
|
+
it("includes completed tool results alongside synthetics on abort", async () => {
|
|
1792
|
+
const tmpDir = join("/tmp", `pensar-test-${Date.now()}-parallel`);
|
|
1793
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
1794
|
+
const messagesPath = join(tmpDir, "messages.json");
|
|
1795
|
+
|
|
1796
|
+
const existingMessages = [
|
|
1797
|
+
{ role: "user", content: [{ type: "text", text: "scan" }] },
|
|
1798
|
+
];
|
|
1799
|
+
writeFileSync(messagesPath, JSON.stringify(existingMessages));
|
|
1800
|
+
|
|
1801
|
+
// Two tools called; tool A completes, tool B is still in-flight on abort.
|
|
1802
|
+
const toolCallA = {
|
|
1803
|
+
type: "tool-call",
|
|
1804
|
+
toolCallId: "tcA",
|
|
1805
|
+
toolName: "read_file",
|
|
1806
|
+
};
|
|
1807
|
+
const toolCallB = {
|
|
1808
|
+
type: "tool-call",
|
|
1809
|
+
toolCallId: "tcB",
|
|
1810
|
+
toolName: "execute_command",
|
|
1811
|
+
};
|
|
1812
|
+
const toolResultA = {
|
|
1813
|
+
type: "tool-result",
|
|
1814
|
+
toolCallId: "tcA",
|
|
1815
|
+
toolName: "read_file",
|
|
1816
|
+
result: { type: "text", value: "file contents" },
|
|
1817
|
+
};
|
|
1818
|
+
|
|
1819
|
+
const agent = buildStubAgent({
|
|
1820
|
+
fullStream: yieldThenThrow(
|
|
1821
|
+
[toolCallA, toolCallB, toolResultA],
|
|
1822
|
+
new Error("connection lost"),
|
|
1823
|
+
),
|
|
1824
|
+
messagesPath,
|
|
1825
|
+
latestMessages: null,
|
|
1826
|
+
});
|
|
1827
|
+
|
|
1828
|
+
try {
|
|
1829
|
+
await agent.consume();
|
|
1830
|
+
} catch {}
|
|
1831
|
+
|
|
1832
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1833
|
+
|
|
1834
|
+
const written = JSON.parse(readFileSync(messagesPath, "utf-8"));
|
|
1835
|
+
// user + assistant (with both tool-calls) + tool (with both results)
|
|
1836
|
+
expect(written).toHaveLength(3);
|
|
1837
|
+
expect(written[0].role).toBe("user");
|
|
1838
|
+
expect(written[1].role).toBe("assistant");
|
|
1839
|
+
expect(written[1].content).toHaveLength(2);
|
|
1840
|
+
const assistantToolIds = written[1].content.map(
|
|
1841
|
+
(c: { toolCallId: string }) => c.toolCallId,
|
|
1842
|
+
);
|
|
1843
|
+
expect(assistantToolIds.sort()).toEqual(["tcA", "tcB"]);
|
|
1844
|
+
|
|
1845
|
+
expect(written[2].role).toBe("tool");
|
|
1846
|
+
expect(written[2].content).toHaveLength(2);
|
|
1847
|
+
const toolResultIds = written[2].content.map(
|
|
1848
|
+
(c: { toolCallId: string }) => c.toolCallId,
|
|
1849
|
+
);
|
|
1850
|
+
expect(toolResultIds.sort()).toEqual(["tcA", "tcB"]);
|
|
1851
|
+
|
|
1852
|
+
// Tool A has real result, Tool B has synthetic error-text
|
|
1853
|
+
const resultA = written[2].content.find(
|
|
1854
|
+
(c: { toolCallId: string }) => c.toolCallId === "tcA",
|
|
1855
|
+
);
|
|
1856
|
+
const resultB = written[2].content.find(
|
|
1857
|
+
(c: { toolCallId: string }) => c.toolCallId === "tcB",
|
|
1858
|
+
);
|
|
1859
|
+
expect(resultA.output.type).toBe("text");
|
|
1860
|
+
expect(resultB.output.type).toBe("error-text");
|
|
1861
|
+
|
|
1862
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
1863
|
+
});
|
|
1864
|
+
});
|
|
1865
|
+
|
|
1866
|
+
describe("multi-step abort does not duplicate prior steps (bugbot fix)", () => {
|
|
1867
|
+
it("clears completed results at finish-step so earlier steps aren't re-appended", async () => {
|
|
1868
|
+
const tmpDir = join("/tmp", `pensar-test-${Date.now()}-multistep`);
|
|
1869
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
1870
|
+
const messagesPath = join(tmpDir, "messages.json");
|
|
1871
|
+
|
|
1872
|
+
// Step 1 already persisted by onStepFinish: tool A called and resolved.
|
|
1873
|
+
const persistedAfterStep1 = [
|
|
1874
|
+
{ role: "user", content: [{ type: "text", text: "scan" }] },
|
|
1875
|
+
{
|
|
1876
|
+
role: "assistant",
|
|
1877
|
+
content: [
|
|
1878
|
+
{ type: "tool-call", toolCallId: "tcA", toolName: "read_file" },
|
|
1879
|
+
],
|
|
1880
|
+
},
|
|
1881
|
+
{
|
|
1882
|
+
role: "tool",
|
|
1883
|
+
content: [
|
|
1884
|
+
{
|
|
1885
|
+
type: "tool-result",
|
|
1886
|
+
toolCallId: "tcA",
|
|
1887
|
+
toolName: "read_file",
|
|
1888
|
+
output: { type: "text", value: "file contents" },
|
|
1889
|
+
},
|
|
1890
|
+
],
|
|
1891
|
+
},
|
|
1892
|
+
];
|
|
1893
|
+
writeFileSync(messagesPath, JSON.stringify(persistedAfterStep1));
|
|
1894
|
+
|
|
1895
|
+
// Replays step 1's chunks, finishes the step, then opens tool B in step 2 before erroring.
|
|
1896
|
+
const agent = buildStubAgent({
|
|
1897
|
+
fullStream: yieldThenThrow(
|
|
1898
|
+
[
|
|
1899
|
+
{ type: "tool-call", toolCallId: "tcA", toolName: "read_file" },
|
|
1900
|
+
{
|
|
1901
|
+
type: "tool-result",
|
|
1902
|
+
toolCallId: "tcA",
|
|
1903
|
+
toolName: "read_file",
|
|
1904
|
+
result: { type: "text", value: "file contents" },
|
|
1905
|
+
},
|
|
1906
|
+
{ type: "finish-step" },
|
|
1907
|
+
{
|
|
1908
|
+
type: "tool-call",
|
|
1909
|
+
toolCallId: "tcB",
|
|
1910
|
+
toolName: "execute_command",
|
|
1911
|
+
},
|
|
1912
|
+
],
|
|
1913
|
+
new Error("connection lost"),
|
|
1914
|
+
),
|
|
1915
|
+
messagesPath,
|
|
1916
|
+
latestMessages: persistedAfterStep1,
|
|
1917
|
+
});
|
|
1918
|
+
|
|
1919
|
+
try {
|
|
1920
|
+
await agent.consume();
|
|
1921
|
+
} catch {}
|
|
1922
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1923
|
+
|
|
1924
|
+
const written = JSON.parse(readFileSync(messagesPath, "utf-8"));
|
|
1925
|
+
const toolCallIds = written
|
|
1926
|
+
.filter((m: { role: string }) => m.role === "assistant")
|
|
1927
|
+
.flatMap((m: { content: { toolCallId?: string }[] }) =>
|
|
1928
|
+
m.content.map((c) => c.toolCallId),
|
|
1929
|
+
);
|
|
1930
|
+
// tcA must appear exactly once — the abort snapshot must not re-append it.
|
|
1931
|
+
expect(toolCallIds.filter((id: string) => id === "tcA")).toHaveLength(1);
|
|
1932
|
+
expect(toolCallIds.filter((id: string) => id === "tcB")).toHaveLength(1);
|
|
1933
|
+
|
|
1934
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
1935
|
+
});
|
|
1936
|
+
});
|
|
1937
|
+
|
|
1938
|
+
describe("failed abort write leaves flag unset (bugbot fix)", () => {
|
|
1939
|
+
const toolCallChunk = {
|
|
1940
|
+
type: "tool-call",
|
|
1941
|
+
toolCallId: "tc1",
|
|
1942
|
+
toolName: "execute_command",
|
|
1943
|
+
};
|
|
1944
|
+
|
|
1945
|
+
it("does not mark synthetics persisted when the write fails", async () => {
|
|
1946
|
+
// A path inside a non-existent directory makes writeFile reject.
|
|
1947
|
+
const messagesPath = join(
|
|
1948
|
+
"/tmp",
|
|
1949
|
+
`pensar-test-${Date.now()}-missing`,
|
|
1950
|
+
"nope",
|
|
1951
|
+
"messages.json",
|
|
1952
|
+
);
|
|
1953
|
+
|
|
1954
|
+
const agent = buildStubAgent({
|
|
1955
|
+
fullStream: yieldThenThrow([toolCallChunk], new Error("stream broke")),
|
|
1956
|
+
messagesPath,
|
|
1957
|
+
latestMessages: [
|
|
1958
|
+
{ role: "user", content: [{ type: "text", text: "scan" }] },
|
|
1959
|
+
],
|
|
1960
|
+
});
|
|
1961
|
+
|
|
1962
|
+
try {
|
|
1963
|
+
await agent.consume();
|
|
1964
|
+
} catch {}
|
|
1965
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1966
|
+
|
|
1967
|
+
expect(
|
|
1968
|
+
(
|
|
1969
|
+
agent as unknown as {
|
|
1970
|
+
writer: { syntheticsPersisted: boolean };
|
|
1971
|
+
}
|
|
1972
|
+
).writer.syntheticsPersisted,
|
|
1973
|
+
).toBeFalsy();
|
|
1974
|
+
});
|
|
1975
|
+
});
|
|
1976
|
+
|
|
1977
|
+
describe("emitSyntheticToolResults error does not mask stream error (bugbot fix)", () => {
|
|
1978
|
+
const toolCallChunk = {
|
|
1979
|
+
type: "tool-call",
|
|
1980
|
+
toolCallId: "tc1",
|
|
1981
|
+
toolName: "execute_command",
|
|
1982
|
+
};
|
|
1983
|
+
|
|
1984
|
+
it("propagates original stream error when event listener throws", async () => {
|
|
1985
|
+
const agent = buildStubAgent({
|
|
1986
|
+
fullStream: yieldThenThrow(
|
|
1987
|
+
[toolCallChunk],
|
|
1988
|
+
new Error("original stream error"),
|
|
1989
|
+
),
|
|
1990
|
+
});
|
|
1991
|
+
|
|
1992
|
+
agent.eventBus.on("tool-result", () => {
|
|
1993
|
+
throw new Error("listener explosion");
|
|
1994
|
+
});
|
|
1995
|
+
|
|
1996
|
+
// The original stream error must propagate, not the listener error
|
|
1997
|
+
await expect(agent.consume()).rejects.toThrow("original stream error");
|
|
1998
|
+
});
|
|
1999
|
+
|
|
2000
|
+
it("still disposes shell when emitSyntheticToolResults throws", async () => {
|
|
2001
|
+
const dispose = vi.fn().mockResolvedValue(undefined);
|
|
2002
|
+
const agent = buildStubAgent({
|
|
2003
|
+
fullStream: yieldThenThrow([toolCallChunk], new Error("stream broke")),
|
|
2004
|
+
commandShell: { dispose },
|
|
2005
|
+
});
|
|
2006
|
+
|
|
2007
|
+
agent.eventBus.on("tool-result", () => {
|
|
2008
|
+
throw new Error("listener crash");
|
|
2009
|
+
});
|
|
2010
|
+
|
|
2011
|
+
await expect(agent.consume()).rejects.toThrow("stream broke");
|
|
2012
|
+
expect(dispose).toHaveBeenCalledOnce();
|
|
2013
|
+
});
|
|
2014
|
+
|
|
2015
|
+
it("preserves the stream error and disposes resources when deferred emission throws", async () => {
|
|
2016
|
+
const tmpDir = join("/tmp", `pensar-test-${Date.now()}-listener-error`);
|
|
2017
|
+
mkdirSync(tmpDir, { recursive: true });
|
|
2018
|
+
const messagesPath = join(tmpDir, "messages.json");
|
|
2019
|
+
const existingMessages = [
|
|
2020
|
+
{ role: "user", content: [{ type: "text", text: "scan" }] },
|
|
2021
|
+
];
|
|
2022
|
+
writeFileSync(messagesPath, JSON.stringify(existingMessages));
|
|
2023
|
+
|
|
2024
|
+
const streamError = new Error("original stream error");
|
|
2025
|
+
const dispose = vi.fn().mockResolvedValue(undefined);
|
|
2026
|
+
const disconnect = vi.fn().mockResolvedValue(undefined);
|
|
2027
|
+
const agent = buildStubAgent({
|
|
2028
|
+
fullStream: yieldThenThrow(
|
|
2029
|
+
[
|
|
2030
|
+
{
|
|
2031
|
+
type: "tool-error",
|
|
2032
|
+
toolCallId: "tc1",
|
|
2033
|
+
toolName: "execute_command",
|
|
2034
|
+
error: new Error("tool validation failed"),
|
|
2035
|
+
},
|
|
2036
|
+
],
|
|
2037
|
+
streamError,
|
|
2038
|
+
),
|
|
2039
|
+
commandShell: { dispose },
|
|
2040
|
+
browserSession: { disconnect },
|
|
2041
|
+
ownsBrowserSession: true,
|
|
2042
|
+
messagesPath,
|
|
2043
|
+
latestMessages: existingMessages,
|
|
2044
|
+
});
|
|
2045
|
+
|
|
2046
|
+
agent.eventBus.on("tool-result", () => {
|
|
2047
|
+
throw new Error("listener explosion");
|
|
2048
|
+
});
|
|
2049
|
+
|
|
2050
|
+
await expect(agent.consume()).rejects.toBe(streamError);
|
|
2051
|
+
expect(dispose).toHaveBeenCalledOnce();
|
|
2052
|
+
expect(disconnect).toHaveBeenCalledOnce();
|
|
2053
|
+
|
|
2054
|
+
const written = JSON.parse(readFileSync(messagesPath, "utf-8"));
|
|
2055
|
+
expect(written.at(-2)).toMatchObject({
|
|
2056
|
+
role: "assistant",
|
|
2057
|
+
content: [{ type: "tool-call", toolCallId: "tc1" }],
|
|
2058
|
+
});
|
|
2059
|
+
expect(written.at(-1)).toMatchObject({
|
|
2060
|
+
role: "tool",
|
|
2061
|
+
content: [{ type: "tool-result", toolCallId: "tc1" }],
|
|
2062
|
+
});
|
|
2063
|
+
|
|
2064
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
2065
|
+
});
|
|
2066
|
+
});
|
|
2067
|
+
});
|
|
2068
|
+
|
|
2069
|
+
// ---------------------------------------------------------------------------
|
|
2070
|
+
// Owned-resource disposal — explicit idempotent operations (A6)
|
|
2071
|
+
// ---------------------------------------------------------------------------
|
|
2072
|
+
|
|
2073
|
+
describe("owned-resource disposal", () => {
|
|
2074
|
+
const toolCallChunk = {
|
|
2075
|
+
type: "tool-call",
|
|
2076
|
+
toolCallId: "tc1",
|
|
2077
|
+
toolName: "execute_command",
|
|
2078
|
+
};
|
|
2079
|
+
|
|
2080
|
+
it("disposeOwnedShell is idempotent and returns the same cached barrier", async () => {
|
|
2081
|
+
const dispose = vi.fn().mockResolvedValue(undefined);
|
|
2082
|
+
const agent = buildStubAgent({
|
|
2083
|
+
fullStream: yieldChunks([]),
|
|
2084
|
+
commandShell: { dispose },
|
|
2085
|
+
});
|
|
2086
|
+
|
|
2087
|
+
const first = agent.disposeOwnedShell();
|
|
2088
|
+
const second = agent.disposeOwnedShell();
|
|
2089
|
+
const third = agent.disposeOwnedShell();
|
|
2090
|
+
expect(second).toBe(first);
|
|
2091
|
+
expect(third).toBe(first);
|
|
2092
|
+
|
|
2093
|
+
await first;
|
|
2094
|
+
expect(dispose).toHaveBeenCalledOnce();
|
|
2095
|
+
});
|
|
2096
|
+
|
|
2097
|
+
it("disposeOwnedShell is a no-op with no shell (construction without one)", () => {
|
|
2098
|
+
const agent = buildStubAgent({ fullStream: yieldChunks([]) });
|
|
2099
|
+
expect(() => agent.disposeOwnedShell()).not.toThrow();
|
|
2100
|
+
});
|
|
2101
|
+
|
|
2102
|
+
it("browser disconnect is idempotent across consume finalization and later teardown", async () => {
|
|
2103
|
+
const disconnect = vi.fn().mockResolvedValue(undefined);
|
|
2104
|
+
const agent = buildStubAgent({
|
|
2105
|
+
fullStream: yieldThenThrow([toolCallChunk], new Error("stream broke")),
|
|
2106
|
+
browserSession: { disconnect },
|
|
2107
|
+
ownsBrowserSession: true,
|
|
2108
|
+
});
|
|
2109
|
+
|
|
2110
|
+
// consume()'s finalization disconnects; later host teardown must not repeat it.
|
|
2111
|
+
try {
|
|
2112
|
+
await agent.consume();
|
|
2113
|
+
} catch {}
|
|
2114
|
+
await agent.abortAndDrain();
|
|
2115
|
+
await agent.abortAndDrain();
|
|
2116
|
+
|
|
2117
|
+
expect(disconnect).toHaveBeenCalledOnce();
|
|
2118
|
+
});
|
|
2119
|
+
|
|
2120
|
+
it("abortAndDrain disconnects the browser exactly once", async () => {
|
|
2121
|
+
const disconnect = vi.fn().mockResolvedValue(undefined);
|
|
2122
|
+
const agent = buildStubAgent({
|
|
2123
|
+
fullStream: yieldThenThrow([toolCallChunk], new Error("aborted")),
|
|
2124
|
+
browserSession: { disconnect },
|
|
2125
|
+
ownsBrowserSession: true,
|
|
2126
|
+
});
|
|
2127
|
+
|
|
2128
|
+
const consume = agent.consume().catch(() => {});
|
|
2129
|
+
await agent.abortAndDrain();
|
|
2130
|
+
await agent.abortAndDrain();
|
|
2131
|
+
await consume;
|
|
2132
|
+
|
|
2133
|
+
expect(disconnect).toHaveBeenCalledOnce();
|
|
2134
|
+
});
|
|
2135
|
+
|
|
2136
|
+
it("browser disconnect failure is swallowed and the run still settles", async () => {
|
|
2137
|
+
const disconnect = vi.fn().mockRejectedValue(new Error("disconnect boom"));
|
|
2138
|
+
const agent = buildStubAgent({
|
|
2139
|
+
fullStream: yieldThenThrow([toolCallChunk], new Error("stream broke")),
|
|
2140
|
+
browserSession: { disconnect },
|
|
2141
|
+
ownsBrowserSession: true,
|
|
2142
|
+
});
|
|
2143
|
+
|
|
2144
|
+
// The stream error propagates; the disconnect failure does not replace it
|
|
2145
|
+
// and does not reject the teardown await.
|
|
2146
|
+
await expect(agent.consume()).rejects.toThrow("stream broke");
|
|
2147
|
+
expect(disconnect).toHaveBeenCalledOnce();
|
|
2148
|
+
});
|
|
2149
|
+
|
|
2150
|
+
it("early result capture disconnects the browser without waiting for the drain", async () => {
|
|
2151
|
+
const disconnect = vi.fn().mockResolvedValue(undefined);
|
|
2152
|
+
// Stream that never ends on its own — only result capture unblocks consume.
|
|
2153
|
+
async function* endless(): AsyncGenerator<unknown, void, undefined> {
|
|
2154
|
+
yield toolCallChunk;
|
|
2155
|
+
await new Promise(() => {});
|
|
2156
|
+
}
|
|
2157
|
+
const agent = buildStubAgent({
|
|
2158
|
+
fullStream: endless(),
|
|
2159
|
+
browserSession: { disconnect },
|
|
2160
|
+
ownsBrowserSession: true,
|
|
2161
|
+
responseCaptured: Promise.resolve("captured"),
|
|
2162
|
+
});
|
|
2163
|
+
Object.defineProperty(agent, "resolveResult", {
|
|
2164
|
+
value: undefined,
|
|
2165
|
+
});
|
|
2166
|
+
|
|
2167
|
+
const result = await Promise.race([
|
|
2168
|
+
agent.consume(),
|
|
2169
|
+
new Promise<never>((_, reject) =>
|
|
2170
|
+
setTimeout(() => reject(new Error("capture path stalled")), 1000),
|
|
2171
|
+
),
|
|
2172
|
+
]);
|
|
2173
|
+
|
|
2174
|
+
expect(result).toBe("captured");
|
|
2175
|
+
expect(disconnect).toHaveBeenCalledOnce();
|
|
2176
|
+
});
|
|
2177
|
+
});
|
|
2178
|
+
|
|
2179
|
+
// ---------------------------------------------------------------------------
|
|
2180
|
+
// O3: root agent-run spans — every consume() execution runs inside an
|
|
2181
|
+
// invoke_agent span with full run attribution.
|
|
2182
|
+
// ---------------------------------------------------------------------------
|
|
2183
|
+
|
|
2184
|
+
describe("root agent-run spans", () => {
|
|
2185
|
+
const textChunk = { type: "text-delta", id: "t1", delta: "hi" };
|
|
2186
|
+
|
|
2187
|
+
async function* singleStepStream(): AsyncGenerator<unknown, void, undefined> {
|
|
2188
|
+
yield textChunk;
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
function setupOtel() {
|
|
2192
|
+
// Local in-memory harness (mirrors observability/testkit, inlined per
|
|
2193
|
+
// this file's "helpers must be inlined" rule).
|
|
2194
|
+
const { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } =
|
|
2195
|
+
require("@opentelemetry/sdk-trace-base") as typeof import("@opentelemetry/sdk-trace-base");
|
|
2196
|
+
const { AsyncLocalStorageContextManager } =
|
|
2197
|
+
require("@opentelemetry/context-async-hooks") as typeof import("@opentelemetry/context-async-hooks");
|
|
2198
|
+
const api =
|
|
2199
|
+
require("@opentelemetry/api") as typeof import("@opentelemetry/api");
|
|
2200
|
+
const exporter = new InMemorySpanExporter();
|
|
2201
|
+
const provider = new BasicTracerProvider({
|
|
2202
|
+
spanProcessors: [new SimpleSpanProcessor(exporter)],
|
|
2203
|
+
});
|
|
2204
|
+
const contextManager = new AsyncLocalStorageContextManager();
|
|
2205
|
+
contextManager.enable();
|
|
2206
|
+
api.trace.setGlobalTracerProvider(provider);
|
|
2207
|
+
api.context.setGlobalContextManager(contextManager);
|
|
2208
|
+
return {
|
|
2209
|
+
exporter,
|
|
2210
|
+
spans: () => exporter.getFinishedSpans(),
|
|
2211
|
+
async teardown() {
|
|
2212
|
+
await provider.shutdown();
|
|
2213
|
+
contextManager.disable();
|
|
2214
|
+
api.trace.disable();
|
|
2215
|
+
api.context.disable();
|
|
2216
|
+
},
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
it("one root span per top-level run, with canonical identity", async () => {
|
|
2221
|
+
const otel = setupOtel();
|
|
2222
|
+
try {
|
|
2223
|
+
const agent = buildStubAgent({ fullStream: singleStepStream() });
|
|
2224
|
+
await agent.consume();
|
|
2225
|
+
|
|
2226
|
+
const spans = otel.spans();
|
|
2227
|
+
const invokeSpans = spans.filter(
|
|
2228
|
+
(s) => s.name === "invoke_agent default",
|
|
2229
|
+
);
|
|
2230
|
+
expect(invokeSpans).toHaveLength(1);
|
|
2231
|
+
const attrs = invokeSpans[0]?.attributes as Record<string, string>;
|
|
2232
|
+
expect(attrs["gen_ai.operation.name"]).toBe("invoke_agent");
|
|
2233
|
+
expect(attrs["gen_ai.agent.id"]).toBe("ses_stub");
|
|
2234
|
+
expect(attrs["gen_ai.agent.name"]).toBe("default");
|
|
2235
|
+
expect(attrs["gen_ai.conversation.id"]).toBe("ses_stub");
|
|
2236
|
+
expect(attrs["session.id"]).toBe("ses_stub");
|
|
2237
|
+
expect(attrs["pensar.session.id"]).toBe("ses_stub");
|
|
2238
|
+
expect(attrs["pensar.root_session.id"]).toBe("ses_stub");
|
|
2239
|
+
expect(attrs["pensar.agent.mode"]).toBe("default");
|
|
2240
|
+
expect(attrs["pensar.run.id"]).toBeUndefined();
|
|
2241
|
+
expect(attrs["pensar.agent.execution.id"]).toBeUndefined();
|
|
2242
|
+
} finally {
|
|
2243
|
+
await otel.teardown();
|
|
2244
|
+
}
|
|
2245
|
+
});
|
|
2246
|
+
|
|
2247
|
+
it("successful runs leave status unset", async () => {
|
|
2248
|
+
const otel = setupOtel();
|
|
2249
|
+
try {
|
|
2250
|
+
const agent = buildStubAgent({ fullStream: singleStepStream() });
|
|
2251
|
+
await agent.consume();
|
|
2252
|
+
|
|
2253
|
+
const span = otel.spans().find((s) => s.name === "invoke_agent default");
|
|
2254
|
+
expect(span).toBeDefined();
|
|
2255
|
+
expect(span?.status.code).not.toBe(2); // SpanStatusCode.ERROR
|
|
2256
|
+
expect(span?.ended).toBe(true);
|
|
2257
|
+
} finally {
|
|
2258
|
+
await otel.teardown();
|
|
2259
|
+
}
|
|
2260
|
+
});
|
|
2261
|
+
|
|
2262
|
+
it("failed runs use error status with exception and low-cardinality error.type", async () => {
|
|
2263
|
+
const otel = setupOtel();
|
|
2264
|
+
try {
|
|
2265
|
+
async function* failingStream(): AsyncGenerator<
|
|
2266
|
+
unknown,
|
|
2267
|
+
void,
|
|
2268
|
+
undefined
|
|
2269
|
+
> {
|
|
2270
|
+
yield textChunk;
|
|
2271
|
+
throw new Error("provider exploded");
|
|
2272
|
+
}
|
|
2273
|
+
const agent = buildStubAgent({ fullStream: failingStream() });
|
|
2274
|
+
await expect(agent.consume()).rejects.toThrow("provider exploded");
|
|
2275
|
+
|
|
2276
|
+
const span = otel.spans().find((s) => s.name === "invoke_agent default");
|
|
2277
|
+
expect(span).toBeDefined();
|
|
2278
|
+
expect(span?.status.code).toBe(2);
|
|
2279
|
+
expect(
|
|
2280
|
+
span?.events.some(
|
|
2281
|
+
(e) =>
|
|
2282
|
+
e.name === "exception" &&
|
|
2283
|
+
(e.attributes?.["exception.message"] as string) ===
|
|
2284
|
+
"provider exploded",
|
|
2285
|
+
),
|
|
2286
|
+
).toBe(true);
|
|
2287
|
+
expect(span?.attributes["error.type"]).toBe("Error");
|
|
2288
|
+
expect(span?.ended).toBe(true);
|
|
2289
|
+
} finally {
|
|
2290
|
+
await otel.teardown();
|
|
2291
|
+
}
|
|
2292
|
+
});
|
|
2293
|
+
|
|
2294
|
+
it("records the originating infrastructure cause on the root span", async () => {
|
|
2295
|
+
const otel = setupOtel();
|
|
2296
|
+
const controller = new AbortController();
|
|
2297
|
+
const cause = new Error("Execution receipt unavailable");
|
|
2298
|
+
cause.name = "ExecutionInfrastructureError";
|
|
2299
|
+
controller.abort(cause);
|
|
2300
|
+
try {
|
|
2301
|
+
const agent = buildStubAgent({
|
|
2302
|
+
fullStream: singleStepStream(),
|
|
2303
|
+
abortSignal: controller.signal,
|
|
2304
|
+
});
|
|
2305
|
+
await expect(agent.consume()).rejects.toBe(cause);
|
|
2306
|
+
const span = otel
|
|
2307
|
+
.spans()
|
|
2308
|
+
.find((entry) => entry.name === "invoke_agent default");
|
|
2309
|
+
expect(span?.status.code).toBe(2);
|
|
2310
|
+
expect(span?.attributes["error.type"]).toBe(cause.name);
|
|
2311
|
+
expect(
|
|
2312
|
+
span?.events.some(
|
|
2313
|
+
(event) =>
|
|
2314
|
+
event.name === "exception" &&
|
|
2315
|
+
event.attributes?.["exception.message"] === cause.message,
|
|
2316
|
+
),
|
|
2317
|
+
).toBe(true);
|
|
2318
|
+
expect(span?.ended).toBe(true);
|
|
2319
|
+
} finally {
|
|
2320
|
+
await otel.teardown();
|
|
2321
|
+
}
|
|
2322
|
+
});
|
|
2323
|
+
|
|
2324
|
+
it("aborted runs end every span", async () => {
|
|
2325
|
+
const otel = setupOtel();
|
|
2326
|
+
try {
|
|
2327
|
+
const controller = new AbortController();
|
|
2328
|
+
async function* abortingStream(): AsyncGenerator<
|
|
2329
|
+
unknown,
|
|
2330
|
+
void,
|
|
2331
|
+
undefined
|
|
2332
|
+
> {
|
|
2333
|
+
yield textChunk;
|
|
2334
|
+
controller.abort();
|
|
2335
|
+
throw new DOMException("Agent aborted by user", "AbortError");
|
|
2336
|
+
}
|
|
2337
|
+
const agent = buildStubAgent({
|
|
2338
|
+
fullStream: abortingStream(),
|
|
2339
|
+
abortSignal: controller.signal,
|
|
2340
|
+
});
|
|
2341
|
+
await expect(agent.consume()).rejects.toThrow();
|
|
2342
|
+
|
|
2343
|
+
const spans = otel.spans();
|
|
2344
|
+
const invokeSpan = spans.find((s) => s.name === "invoke_agent default");
|
|
2345
|
+
expect(invokeSpan?.ended).toBe(true);
|
|
2346
|
+
expect(invokeSpan?.attributes["error.type"]).toBe("AbortError");
|
|
2347
|
+
} finally {
|
|
2348
|
+
await otel.teardown();
|
|
2349
|
+
}
|
|
2350
|
+
});
|
|
2351
|
+
|
|
2352
|
+
it("persistence and disposal finish before the root span ends", async () => {
|
|
2353
|
+
const otel = setupOtel();
|
|
2354
|
+
try {
|
|
2355
|
+
// Finalization ordering: the stub's shell disposal runs inside
|
|
2356
|
+
// runConsume's finally; the span ends only after that settles. Since
|
|
2357
|
+
// export happens exclusively on span.end(), observing the exported
|
|
2358
|
+
// span proves the span ended — and consume() resolving proves disposal
|
|
2359
|
+
// completed first (both are awaited in order inside runInSpan).
|
|
2360
|
+
const ordering: string[] = [];
|
|
2361
|
+
const dispose = vi.fn(() => {
|
|
2362
|
+
ordering.push("shell-disposed");
|
|
2363
|
+
return Promise.resolve();
|
|
2364
|
+
});
|
|
2365
|
+
const agent = buildStubAgent({
|
|
2366
|
+
fullStream: singleStepStream(),
|
|
2367
|
+
commandShell: { dispose },
|
|
2368
|
+
});
|
|
2369
|
+
await agent.consume();
|
|
2370
|
+
|
|
2371
|
+
// Consume resolved: disposal ran during finalization; the span ended
|
|
2372
|
+
// after (finally order in runInSpan). The exporter saw the span after
|
|
2373
|
+
// disposal because export only happens on span.end().
|
|
2374
|
+
expect(dispose).toHaveBeenCalledOnce();
|
|
2375
|
+
const span = otel.spans().find((s) => s.name === "invoke_agent default");
|
|
2376
|
+
expect(span?.ended).toBe(true);
|
|
2377
|
+
// Direct ordering proof: a span-end observer sees disposal already done.
|
|
2378
|
+
expect(ordering).toEqual(["shell-disposed"]);
|
|
2379
|
+
} finally {
|
|
2380
|
+
await otel.teardown();
|
|
2381
|
+
}
|
|
2382
|
+
});
|
|
2383
|
+
|
|
2384
|
+
it("subagent runs keep current and root identity distinct", async () => {
|
|
2385
|
+
const otel = setupOtel();
|
|
2386
|
+
try {
|
|
2387
|
+
const agent = buildStubAgent({
|
|
2388
|
+
fullStream: singleStepStream(),
|
|
2389
|
+
subagentId: "ses_sub_1",
|
|
2390
|
+
subagentName: "recon-sub",
|
|
2391
|
+
});
|
|
2392
|
+
await agent.consume();
|
|
2393
|
+
|
|
2394
|
+
const span = otel
|
|
2395
|
+
.spans()
|
|
2396
|
+
.find((s) => s.name === "invoke_agent recon-sub");
|
|
2397
|
+
expect(span).toBeDefined();
|
|
2398
|
+
const attrs = span?.attributes as Record<string, string>;
|
|
2399
|
+
expect(attrs["gen_ai.agent.id"]).toBe("ses_sub_1");
|
|
2400
|
+
expect(attrs["gen_ai.agent.name"]).toBe("recon-sub");
|
|
2401
|
+
expect(attrs["gen_ai.conversation.id"]).toBe("ses_stub");
|
|
2402
|
+
expect(attrs["session.id"]).toBe("ses_stub");
|
|
2403
|
+
expect(attrs["pensar.session.id"]).toBe("ses_sub_1");
|
|
2404
|
+
expect(attrs["pensar.root_session.id"]).toBe("ses_stub");
|
|
2405
|
+
expect(attrs["pensar.agent.execution.id"]).toBeUndefined();
|
|
2406
|
+
expect(attrs["pensar.run.id"]).toBeUndefined();
|
|
2407
|
+
expect(attrs["pensar.agent.mode"]).toBeUndefined();
|
|
2408
|
+
} finally {
|
|
2409
|
+
await otel.teardown();
|
|
2410
|
+
}
|
|
2411
|
+
});
|
|
2412
|
+
});
|
|
2413
|
+
|
|
2414
|
+
describe("OffensiveSecurityAgent.consume() streamIdFactory", () => {
|
|
2415
|
+
const startStep = { type: "start-step" };
|
|
2416
|
+
const textStart = { type: "text-start", id: "t0" };
|
|
2417
|
+
const textDelta = { type: "text-delta", id: "t0", text: "hi" };
|
|
2418
|
+
|
|
2419
|
+
it("mints message and text-part ids from the factory with positional context", async () => {
|
|
2420
|
+
const streamIdFactory = vi.fn((context: unknown) => {
|
|
2421
|
+
const ctx = context as {
|
|
2422
|
+
kind: string;
|
|
2423
|
+
stepIndex?: number;
|
|
2424
|
+
textPartIndex?: number;
|
|
2425
|
+
};
|
|
2426
|
+
if (ctx.kind === "message") return `msg-${ctx.stepIndex}`;
|
|
2427
|
+
if (ctx.kind === "text-part")
|
|
2428
|
+
return `part-${ctx.stepIndex}-${ctx.textPartIndex}`;
|
|
2429
|
+
return "tool";
|
|
2430
|
+
});
|
|
2431
|
+
const agent = buildStubAgent({
|
|
2432
|
+
fullStream: yieldChunks([startStep, textStart, textDelta]),
|
|
2433
|
+
streamIdFactory,
|
|
2434
|
+
});
|
|
2435
|
+
const emitted: Array<{ messageId?: string; textPartId?: string }> = [];
|
|
2436
|
+
vi.spyOn(agent.eventBus, "emitStreamPart").mockImplementation(((
|
|
2437
|
+
_chunk: unknown,
|
|
2438
|
+
ids: { messageId?: string; textPartId?: string },
|
|
2439
|
+
) => {
|
|
2440
|
+
emitted.push({ messageId: ids.messageId, textPartId: ids.textPartId });
|
|
2441
|
+
}) as unknown as AgentEventBus["emitStreamPart"]);
|
|
2442
|
+
|
|
2443
|
+
await agent.consume();
|
|
2444
|
+
|
|
2445
|
+
expect(streamIdFactory).toHaveBeenCalledWith({
|
|
2446
|
+
kind: "message",
|
|
2447
|
+
stepIndex: 0,
|
|
2448
|
+
});
|
|
2449
|
+
expect(streamIdFactory).toHaveBeenCalledWith({
|
|
2450
|
+
kind: "text-part",
|
|
2451
|
+
stepIndex: 0,
|
|
2452
|
+
textPartIndex: 0,
|
|
2453
|
+
});
|
|
2454
|
+
const deltaEmit = emitted.at(-1);
|
|
2455
|
+
expect(deltaEmit?.messageId).toBe("msg-0");
|
|
2456
|
+
expect(deltaEmit?.textPartId).toBe("part-0-0");
|
|
2457
|
+
});
|
|
2458
|
+
|
|
2459
|
+
it("falls back to random ids when no factory is provided", async () => {
|
|
2460
|
+
const agent = buildStubAgent({
|
|
2461
|
+
fullStream: yieldChunks([startStep, textStart, textDelta]),
|
|
2462
|
+
});
|
|
2463
|
+
const emitted: Array<{ messageId?: string }> = [];
|
|
2464
|
+
vi.spyOn(agent.eventBus, "emitStreamPart").mockImplementation(((
|
|
2465
|
+
_chunk: unknown,
|
|
2466
|
+
ids: { messageId?: string },
|
|
2467
|
+
) => {
|
|
2468
|
+
emitted.push({ messageId: ids.messageId });
|
|
2469
|
+
}) as unknown as AgentEventBus["emitStreamPart"]);
|
|
2470
|
+
|
|
2471
|
+
await agent.consume();
|
|
2472
|
+
|
|
2473
|
+
const deltaEmit = emitted.at(-1);
|
|
2474
|
+
expect(typeof deltaEmit?.messageId).toBe("string");
|
|
2475
|
+
expect(deltaEmit?.messageId).not.toBe("msg-0");
|
|
2476
|
+
});
|
|
2477
|
+
});
|
|
2478
|
+
|
|
2479
|
+
// ---------------------------------------------------------------------------
|
|
2480
|
+
// O3/PR3: in-stream error parts complete every span (agent + generation)
|
|
2481
|
+
// ---------------------------------------------------------------------------
|
|
2482
|
+
|
|
2483
|
+
describe("error-part span completion", () => {
|
|
2484
|
+
it("an error-part run exports the agent span with error status", async () => {
|
|
2485
|
+
// Local in-memory harness (inlined per this file's rule).
|
|
2486
|
+
const { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } =
|
|
2487
|
+
require("@opentelemetry/sdk-trace-base") as typeof import("@opentelemetry/sdk-trace-base");
|
|
2488
|
+
const { AsyncLocalStorageContextManager } =
|
|
2489
|
+
require("@opentelemetry/context-async-hooks") as typeof import("@opentelemetry/context-async-hooks");
|
|
2490
|
+
const api =
|
|
2491
|
+
require("@opentelemetry/api") as typeof import("@opentelemetry/api");
|
|
2492
|
+
const exporter = new InMemorySpanExporter();
|
|
2493
|
+
const provider = new BasicTracerProvider({
|
|
2494
|
+
spanProcessors: [new SimpleSpanProcessor(exporter)],
|
|
2495
|
+
});
|
|
2496
|
+
const contextManager = new AsyncLocalStorageContextManager();
|
|
2497
|
+
contextManager.enable();
|
|
2498
|
+
api.trace.setGlobalTracerProvider(provider);
|
|
2499
|
+
api.context.setGlobalContextManager(contextManager);
|
|
2500
|
+
|
|
2501
|
+
try {
|
|
2502
|
+
// The stream yields a chunk then an error — the raw generator mimics
|
|
2503
|
+
// the AI SDK's error part (consume() sees it as a thrown error after
|
|
2504
|
+
// the drain in the real pipeline).
|
|
2505
|
+
async function* errorPartStream(): AsyncGenerator<
|
|
2506
|
+
unknown,
|
|
2507
|
+
void,
|
|
2508
|
+
undefined
|
|
2509
|
+
> {
|
|
2510
|
+
yield { type: "text-delta", id: "t1", delta: "partial" };
|
|
2511
|
+
throw new Error("model exploded mid-stream");
|
|
2512
|
+
}
|
|
2513
|
+
const agent = buildStubAgent({ fullStream: errorPartStream() });
|
|
2514
|
+
await expect(agent.consume()).rejects.toThrow("model exploded");
|
|
2515
|
+
|
|
2516
|
+
const span = exporter
|
|
2517
|
+
.getFinishedSpans()
|
|
2518
|
+
.find((s) => s.name === "invoke_agent default");
|
|
2519
|
+
expect(span).toBeDefined();
|
|
2520
|
+
expect(span?.status.code).toBe(api.SpanStatusCode.ERROR);
|
|
2521
|
+
expect(span?.attributes["error.type"]).toBe("Error");
|
|
2522
|
+
expect(span?.ended).toBe(true);
|
|
2523
|
+
} finally {
|
|
2524
|
+
await provider.shutdown();
|
|
2525
|
+
contextManager.disable();
|
|
2526
|
+
api.trace.disable();
|
|
2527
|
+
api.context.disable();
|
|
2528
|
+
}
|
|
2529
|
+
});
|
|
2530
|
+
});
|
|
2531
|
+
|
|
2532
|
+
// ---------------------------------------------------------------------------
|
|
2533
|
+
// PR4: root IO + consistent trace identity
|
|
2534
|
+
// ---------------------------------------------------------------------------
|
|
2535
|
+
|
|
2536
|
+
describe("trace identity and root IO", () => {
|
|
2537
|
+
const textChunk = { type: "text-delta", id: "t1", delta: "hi" };
|
|
2538
|
+
async function* oneStep(): AsyncGenerator<unknown, void, undefined> {
|
|
2539
|
+
yield textChunk;
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
function setupOtel() {
|
|
2543
|
+
const { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } =
|
|
2544
|
+
require("@opentelemetry/sdk-trace-base") as typeof import("@opentelemetry/sdk-trace-base");
|
|
2545
|
+
const { AsyncLocalStorageContextManager } =
|
|
2546
|
+
require("@opentelemetry/context-async-hooks") as typeof import("@opentelemetry/context-async-hooks");
|
|
2547
|
+
const api =
|
|
2548
|
+
require("@opentelemetry/api") as typeof import("@opentelemetry/api");
|
|
2549
|
+
const exporter = new InMemorySpanExporter();
|
|
2550
|
+
const provider = new BasicTracerProvider({
|
|
2551
|
+
spanProcessors: [new SimpleSpanProcessor(exporter)],
|
|
2552
|
+
});
|
|
2553
|
+
const contextManager = new AsyncLocalStorageContextManager();
|
|
2554
|
+
contextManager.enable();
|
|
2555
|
+
api.trace.setGlobalTracerProvider(provider);
|
|
2556
|
+
api.context.setGlobalContextManager(contextManager);
|
|
2557
|
+
return {
|
|
2558
|
+
spans: () => exporter.getFinishedSpans(),
|
|
2559
|
+
async teardown() {
|
|
2560
|
+
await provider.shutdown();
|
|
2561
|
+
contextManager.disable();
|
|
2562
|
+
api.trace.disable();
|
|
2563
|
+
api.context.disable();
|
|
2564
|
+
},
|
|
2565
|
+
};
|
|
2566
|
+
}
|
|
2567
|
+
|
|
2568
|
+
it("root spans carry canonical conversation and agent identity", async () => {
|
|
2569
|
+
const otel = setupOtel();
|
|
2570
|
+
try {
|
|
2571
|
+
const agent = buildStubAgent({ fullStream: oneStep() });
|
|
2572
|
+
await agent.consume();
|
|
2573
|
+
|
|
2574
|
+
const span = otel.spans().find((s) => s.name === "invoke_agent default");
|
|
2575
|
+
const attrs = span?.attributes as Record<string, string>;
|
|
2576
|
+
expect(attrs["gen_ai.agent.id"]).toBe("ses_stub");
|
|
2577
|
+
expect(attrs["gen_ai.conversation.id"]).toBe("ses_stub");
|
|
2578
|
+
expect(attrs["session.id"]).toBe("ses_stub");
|
|
2579
|
+
expect(attrs["pensar.session.id"]).toBe("ses_stub");
|
|
2580
|
+
expect(attrs["pensar.root_session.id"]).toBe("ses_stub");
|
|
2581
|
+
expect(attrs["pensar.run.id"]).toBeUndefined();
|
|
2582
|
+
expect(attrs["pensar.agent.execution.id"]).toBeUndefined();
|
|
2583
|
+
} finally {
|
|
2584
|
+
await otel.teardown();
|
|
2585
|
+
}
|
|
2586
|
+
});
|
|
2587
|
+
|
|
2588
|
+
it("subagent spans separate agent identity from the root conversation", async () => {
|
|
2589
|
+
const otel = setupOtel();
|
|
2590
|
+
try {
|
|
2591
|
+
const agent = buildStubAgent({
|
|
2592
|
+
fullStream: oneStep(),
|
|
2593
|
+
subagentId: "ses_exec_1",
|
|
2594
|
+
subagentName: "recon-sub",
|
|
2595
|
+
});
|
|
2596
|
+
await agent.consume();
|
|
2597
|
+
|
|
2598
|
+
const span = otel
|
|
2599
|
+
.spans()
|
|
2600
|
+
.find((s) => s.name === "invoke_agent recon-sub");
|
|
2601
|
+
const attrs = span?.attributes as Record<string, string>;
|
|
2602
|
+
expect(attrs["gen_ai.agent.id"]).toBe("ses_exec_1");
|
|
2603
|
+
expect(attrs["gen_ai.conversation.id"]).toBe("ses_stub");
|
|
2604
|
+
expect(attrs["session.id"]).toBe("ses_stub");
|
|
2605
|
+
expect(attrs["pensar.session.id"]).toBe("ses_exec_1");
|
|
2606
|
+
expect(attrs["pensar.root_session.id"]).toBe("ses_stub");
|
|
2607
|
+
expect(attrs["pensar.agent.execution.id"]).toBeUndefined();
|
|
2608
|
+
expect(attrs["pensar.run.id"]).toBeUndefined();
|
|
2609
|
+
} finally {
|
|
2610
|
+
await otel.teardown();
|
|
2611
|
+
}
|
|
2612
|
+
});
|
|
2613
|
+
|
|
2614
|
+
it("root span IO appears only under full payload capture", async () => {
|
|
2615
|
+
process.env.AI_TRACE_RECORD_PAYLOADS = "true";
|
|
2616
|
+
const otel = setupOtel();
|
|
2617
|
+
try {
|
|
2618
|
+
const agent = buildStubAgent({ fullStream: oneStep() });
|
|
2619
|
+
Object.defineProperty(agent, "userPrompt", {
|
|
2620
|
+
value: "pentest the acme api",
|
|
2621
|
+
});
|
|
2622
|
+
await agent.consume();
|
|
2623
|
+
|
|
2624
|
+
const span = otel.spans().find((s) => s.name === "invoke_agent default");
|
|
2625
|
+
expect(span?.attributes["gen_ai.prompt"]).toBe("pentest the acme api");
|
|
2626
|
+
} finally {
|
|
2627
|
+
await otel.teardown();
|
|
2628
|
+
process.env.AI_TRACE_RECORD_PAYLOADS = undefined;
|
|
2629
|
+
}
|
|
2630
|
+
|
|
2631
|
+
// Default mode: metadata only — no prompt or result payload.
|
|
2632
|
+
const otel2 = setupOtel();
|
|
2633
|
+
try {
|
|
2634
|
+
const agent = buildStubAgent({ fullStream: oneStep() });
|
|
2635
|
+
Object.defineProperty(agent, "userPrompt", {
|
|
2636
|
+
value: "pentest the acme api",
|
|
2637
|
+
});
|
|
2638
|
+
await agent.consume();
|
|
2639
|
+
const span = otel2.spans().find((s) => s.name === "invoke_agent default");
|
|
2640
|
+
expect(span?.attributes["gen_ai.prompt"]).toBeUndefined();
|
|
2641
|
+
expect(span?.attributes["gen_ai.completion"]).toBeUndefined();
|
|
2642
|
+
} finally {
|
|
2643
|
+
await otel2.teardown();
|
|
2644
|
+
}
|
|
2645
|
+
});
|
|
2646
|
+
|
|
2647
|
+
it("records root IO for the primary Fast Strike run", async () => {
|
|
2648
|
+
process.env.AI_TRACE_RECORD_PAYLOADS = "true";
|
|
2649
|
+
const otel = setupOtel();
|
|
2650
|
+
try {
|
|
2651
|
+
const agent = buildStubAgent({
|
|
2652
|
+
fullStream: oneStep(),
|
|
2653
|
+
resolveResult: () => ({ summary: "strike complete" }),
|
|
2654
|
+
subagentId: "fast-strike",
|
|
2655
|
+
agentMode: "fast-strike",
|
|
2656
|
+
});
|
|
2657
|
+
Object.defineProperty(agent, "userPrompt", {
|
|
2658
|
+
value: "strike the acme api",
|
|
2659
|
+
});
|
|
2660
|
+
|
|
2661
|
+
await agent.consume();
|
|
2662
|
+
|
|
2663
|
+
const span = otel
|
|
2664
|
+
.spans()
|
|
2665
|
+
.find((candidate) => candidate.name === "invoke_agent fast-strike");
|
|
2666
|
+
expect(span?.attributes["gen_ai.prompt"]).toBe("strike the acme api");
|
|
2667
|
+
expect(String(span?.attributes["gen_ai.completion"])).toContain(
|
|
2668
|
+
'"summary":"strike complete"',
|
|
2669
|
+
);
|
|
2670
|
+
} finally {
|
|
2671
|
+
await otel.teardown();
|
|
2672
|
+
process.env.AI_TRACE_RECORD_PAYLOADS = undefined;
|
|
2673
|
+
}
|
|
2674
|
+
});
|
|
2675
|
+
|
|
2676
|
+
it("records the captured response tool result as root output", async () => {
|
|
2677
|
+
process.env.AI_TRACE_RECORD_PAYLOADS = "true";
|
|
2678
|
+
const otel = setupOtel();
|
|
2679
|
+
try {
|
|
2680
|
+
const agent = buildStubAgent({
|
|
2681
|
+
fullStream: oneStep(),
|
|
2682
|
+
responseCaptured: Promise.resolve({ summary: "scan complete" }),
|
|
2683
|
+
responseToolResult: { summary: "scan complete" },
|
|
2684
|
+
});
|
|
2685
|
+
Object.defineProperty(agent, "_responseToolFired", { value: true });
|
|
2686
|
+
|
|
2687
|
+
await agent.consume();
|
|
2688
|
+
await agent.drained;
|
|
2689
|
+
|
|
2690
|
+
const span = otel.spans().find((s) => s.name === "invoke_agent default");
|
|
2691
|
+
expect(String(span?.attributes["gen_ai.completion"])).toContain(
|
|
2692
|
+
'"summary":"scan complete"',
|
|
2693
|
+
);
|
|
2694
|
+
} finally {
|
|
2695
|
+
await otel.teardown();
|
|
2696
|
+
process.env.AI_TRACE_RECORD_PAYLOADS = undefined;
|
|
2697
|
+
}
|
|
2698
|
+
});
|
|
2699
|
+
|
|
2700
|
+
it("does not wait on response resolution before ending a response-tool run", async () => {
|
|
2701
|
+
process.env.AI_TRACE_RECORD_PAYLOADS = "true";
|
|
2702
|
+
const otel = setupOtel();
|
|
2703
|
+
let resolveCaptured!: (value: unknown) => void;
|
|
2704
|
+
const responseCaptured = new Promise<unknown>((resolve) => {
|
|
2705
|
+
resolveCaptured = resolve;
|
|
2706
|
+
});
|
|
2707
|
+
try {
|
|
2708
|
+
const agent = buildStubAgent({
|
|
2709
|
+
fullStream: oneStep(),
|
|
2710
|
+
responseCaptured,
|
|
2711
|
+
responseToolResult: { summary: "submitted" },
|
|
2712
|
+
});
|
|
2713
|
+
Object.defineProperty(agent, "_responseToolFired", { value: true });
|
|
2714
|
+
|
|
2715
|
+
const consume = agent.consume();
|
|
2716
|
+
const drainRejection = Reflect.get(
|
|
2717
|
+
agent,
|
|
2718
|
+
"_drainRejection",
|
|
2719
|
+
) as Promise<never>;
|
|
2720
|
+
void drainRejection.catch(() => {
|
|
2721
|
+
resolveCaptured({ summary: "resolved" });
|
|
2722
|
+
});
|
|
2723
|
+
|
|
2724
|
+
const result = await Promise.race([
|
|
2725
|
+
consume,
|
|
2726
|
+
new Promise<never>((_, reject) =>
|
|
2727
|
+
setTimeout(
|
|
2728
|
+
() => reject(new Error("response resolution stalled")),
|
|
2729
|
+
1000,
|
|
2730
|
+
),
|
|
2731
|
+
),
|
|
2732
|
+
]);
|
|
2733
|
+
await agent.drained;
|
|
2734
|
+
|
|
2735
|
+
expect(result).toEqual({ summary: "resolved" });
|
|
2736
|
+
const span = otel.spans().find((s) => s.name === "invoke_agent default");
|
|
2737
|
+
expect(span?.ended).toBe(true);
|
|
2738
|
+
expect(String(span?.attributes["gen_ai.completion"])).toContain(
|
|
2739
|
+
'"summary":"submitted"',
|
|
2740
|
+
);
|
|
2741
|
+
} finally {
|
|
2742
|
+
resolveCaptured(undefined);
|
|
2743
|
+
await otel.teardown();
|
|
2744
|
+
process.env.AI_TRACE_RECORD_PAYLOADS = undefined;
|
|
2745
|
+
}
|
|
2746
|
+
});
|
|
2747
|
+
|
|
2748
|
+
it("preserves captured output when the background drain later fails", async () => {
|
|
2749
|
+
process.env.AI_TRACE_RECORD_PAYLOADS = "true";
|
|
2750
|
+
const otel = setupOtel();
|
|
2751
|
+
const captured = { summary: "scan complete" };
|
|
2752
|
+
try {
|
|
2753
|
+
async function* failingAfterResponse(): AsyncGenerator<
|
|
2754
|
+
unknown,
|
|
2755
|
+
void,
|
|
2756
|
+
undefined
|
|
2757
|
+
> {
|
|
2758
|
+
yield textChunk;
|
|
2759
|
+
throw new Error("late drain failure");
|
|
2760
|
+
}
|
|
2761
|
+
const agent = buildStubAgent({
|
|
2762
|
+
fullStream: failingAfterResponse(),
|
|
2763
|
+
responseCaptured: Promise.resolve(captured),
|
|
2764
|
+
responseToolResult: captured,
|
|
2765
|
+
});
|
|
2766
|
+
Object.defineProperty(agent, "_responseToolFired", { value: true });
|
|
2767
|
+
|
|
2768
|
+
await expect(agent.consume()).resolves.toEqual(captured);
|
|
2769
|
+
await agent.drained;
|
|
2770
|
+
|
|
2771
|
+
const span = otel.spans().find((s) => s.name === "invoke_agent default");
|
|
2772
|
+
expect(String(span?.attributes["gen_ai.completion"])).toContain(
|
|
2773
|
+
'"summary":"scan complete"',
|
|
2774
|
+
);
|
|
2775
|
+
expect(String(span?.attributes["gen_ai.completion"])).not.toContain(
|
|
2776
|
+
"Failure: late drain failure",
|
|
2777
|
+
);
|
|
2778
|
+
} finally {
|
|
2779
|
+
await otel.teardown();
|
|
2780
|
+
process.env.AI_TRACE_RECORD_PAYLOADS = undefined;
|
|
2781
|
+
}
|
|
2782
|
+
});
|
|
2783
|
+
|
|
2784
|
+
it("failed runs record the failure as root output under full capture", async () => {
|
|
2785
|
+
process.env.AI_TRACE_RECORD_PAYLOADS = "true";
|
|
2786
|
+
const otel = setupOtel();
|
|
2787
|
+
try {
|
|
2788
|
+
async function* failing(): AsyncGenerator<unknown, void, undefined> {
|
|
2789
|
+
yield textChunk;
|
|
2790
|
+
throw new Error("final failure");
|
|
2791
|
+
}
|
|
2792
|
+
const agent = buildStubAgent({ fullStream: failing() });
|
|
2793
|
+
await expect(agent.consume()).rejects.toThrow("final failure");
|
|
2794
|
+
|
|
2795
|
+
const span = otel.spans().find((s) => s.name === "invoke_agent default");
|
|
2796
|
+
expect(String(span?.attributes["gen_ai.completion"])).toContain(
|
|
2797
|
+
"Failure: final failure",
|
|
2798
|
+
);
|
|
2799
|
+
} finally {
|
|
2800
|
+
await otel.teardown();
|
|
2801
|
+
process.env.AI_TRACE_RECORD_PAYLOADS = undefined;
|
|
2802
|
+
}
|
|
2803
|
+
});
|
|
2804
|
+
});
|
|
2805
|
+
|
|
2806
|
+
describe("contextRecorder integration", () => {
|
|
2807
|
+
const streamTextChunk = { type: "text-delta", id: "t1", delta: "hi" };
|
|
2808
|
+
const CANONICAL = [
|
|
2809
|
+
{ role: "user", content: "recorded prompt" },
|
|
2810
|
+
{ role: "assistant", content: "recorded answer" },
|
|
2811
|
+
] as never[];
|
|
2812
|
+
|
|
2813
|
+
it("flush failure in finalization fails the run after the stream completed", async () => {
|
|
2814
|
+
// Stream completes normally, but the last step's commit failed and the
|
|
2815
|
+
// recorder latched it — finalizeRun's flush must surface it (the SDK
|
|
2816
|
+
// swallows onStepFinish errors, so nothing else would).
|
|
2817
|
+
const flushError = new Error("latched persistence failure");
|
|
2818
|
+
const recorder: RunContextRecorder = {
|
|
2819
|
+
checkpoint: vi.fn(async () => {}),
|
|
2820
|
+
flush: vi.fn(async () => {
|
|
2821
|
+
throw flushError;
|
|
2822
|
+
}),
|
|
2823
|
+
latest: vi.fn(() => undefined),
|
|
2824
|
+
};
|
|
2825
|
+
|
|
2826
|
+
async function* complete(): AsyncGenerator<unknown, void, undefined> {
|
|
2827
|
+
yield streamTextChunk;
|
|
2828
|
+
}
|
|
2829
|
+
const agent = buildStubAgent({
|
|
2830
|
+
fullStream: complete(),
|
|
2831
|
+
contextRecorder: recorder,
|
|
2832
|
+
});
|
|
2833
|
+
|
|
2834
|
+
await expect(agent.consume()).rejects.toBe(flushError);
|
|
2835
|
+
});
|
|
2836
|
+
|
|
2837
|
+
it("canonical latest drives the projection on steps and finish; no duplicated prefix", async () => {
|
|
2838
|
+
const rootPath = mkdtempSync(join(tmpdir(), "apex-recorder-projection-"));
|
|
2839
|
+
streamResponseCalls.length = 0;
|
|
2840
|
+
const recorder: RunContextRecorder = {
|
|
2841
|
+
checkpoint: vi.fn(async () => {}),
|
|
2842
|
+
flush: vi.fn(async () => {}),
|
|
2843
|
+
latest: vi.fn(() => structuredClone(CANONICAL)),
|
|
2844
|
+
};
|
|
2845
|
+
const inferenceRecorder: InferenceRecorder = {
|
|
2846
|
+
runId: "run_recorder_projection",
|
|
2847
|
+
beforeDispatch: vi.fn(async () => {}),
|
|
2848
|
+
beforeToolCall: vi.fn(async () => {}),
|
|
2849
|
+
settle: vi.fn(),
|
|
2850
|
+
retry: vi.fn(async () => {}),
|
|
2851
|
+
flush: vi.fn(async () => {}),
|
|
2852
|
+
};
|
|
2853
|
+
const agent = new OffensiveSecurityAgent({
|
|
2854
|
+
prompt: "recorded prompt",
|
|
2855
|
+
model: "test-model",
|
|
2856
|
+
session: {
|
|
2857
|
+
id: "ses_recorder_projection",
|
|
2858
|
+
rootPath,
|
|
2859
|
+
scratchpadPath: join(rootPath, "scratchpad"),
|
|
2860
|
+
},
|
|
2861
|
+
activeTools: [],
|
|
2862
|
+
contextRecorder: recorder,
|
|
2863
|
+
inferenceRecorder,
|
|
2864
|
+
} as never);
|
|
2865
|
+
void agent.streamResult; // createStream is lazy — force construction
|
|
2866
|
+
|
|
2867
|
+
const streamOpts = streamResponseCalls[0] as {
|
|
2868
|
+
onStepFinish: (event: unknown) => Promise<void>;
|
|
2869
|
+
onFinish: (event: unknown) => Promise<void>;
|
|
2870
|
+
};
|
|
2871
|
+
expect(
|
|
2872
|
+
(streamResponseCalls[0] as { contextRecorder: unknown }).contextRecorder,
|
|
2873
|
+
).toBe(recorder);
|
|
2874
|
+
expect(streamResponseCalls[0]?.inferenceRecorder).toBe(inferenceRecorder);
|
|
2875
|
+
|
|
2876
|
+
const stepEvent = {
|
|
2877
|
+
response: { id: "resp_1", messages: CANONICAL.slice(1) },
|
|
2878
|
+
usage: {},
|
|
2879
|
+
};
|
|
2880
|
+
await streamOpts.onStepFinish(stepEvent);
|
|
2881
|
+
|
|
2882
|
+
// Writer snapshot equals canonical latest() exactly — the step's
|
|
2883
|
+
// response.messages are already part of the committed context, so the
|
|
2884
|
+
// projection must not re-prepend a base or duplicate them.
|
|
2885
|
+
const writer = agent["writer" as keyof typeof agent] as unknown as {
|
|
2886
|
+
waitForPendingWrites: () => Promise<void>;
|
|
2887
|
+
latest: unknown[] | null;
|
|
2888
|
+
};
|
|
2889
|
+
await writer.waitForPendingWrites().catch(() => {});
|
|
2890
|
+
const captured = writer.latest;
|
|
2891
|
+
|
|
2892
|
+
// onFinish falls back to canonical latest when the writer has nothing.
|
|
2893
|
+
const finishEvent = { response: { messages: [] } };
|
|
2894
|
+
await streamOpts.onFinish(finishEvent);
|
|
2895
|
+
|
|
2896
|
+
expect(recorder.latest).toHaveBeenCalled();
|
|
2897
|
+
expect((captured as unknown[]).length).toBe(CANONICAL.length);
|
|
2898
|
+
});
|
|
2899
|
+
});
|