@pensar/apex 2.6.1 → 2.7.0-canary.52c132f4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/{agent-yby11zaq.js → agent-112gdg69.js} +6 -6
- package/build/agent-9bh42v64.js +13 -0
- package/build/agent-jg7v2yn0.js +13 -0
- package/build/{agent-runs-g37vkrjr.js → agent-runs-hhjzsysf.js} +18 -18
- package/build/{agent-ayreaj6z.js → agent-ydy5fq92.js} +5 -5
- package/build/{apps-xn6km9yn.js → apps-v4jc7eqb.js} +13 -13
- package/build/{auth-d105wc64.js → auth-bxkn5pcb.js} +13 -13
- package/build/{blackboxAgent-s56sgx9b.js → blackboxAgent-260yqknt.js} +6 -6
- package/build/{blackboxPentest-ps78v3w7.js → blackboxPentest-nbn588pb.js} +17 -12
- package/build/{cli-3sp6nd8d.js → cli-087apjgk.js} +2 -2
- package/build/{cli-1ve18npr.js → cli-1wtxf161.js} +3 -2
- package/build/{cli-d37a42dc.js → cli-3158x9wb.js} +101 -18
- package/build/{cli-fy6vh2a7.js → cli-35gz0ahm.js} +1 -1
- package/build/{cli-65g6rcat.js → cli-5xdefy58.js} +1 -1
- package/build/{cli-709rvekb.js → cli-6vpprmxb.js} +4 -2
- package/build/{cli-az3xq70v.js → cli-8hrdnq0e.js} +1 -1
- package/build/{cli-7eea4e2y.js → cli-ahk627k3.js} +6 -6
- package/build/{cli-c05y640n.js → cli-bc793xfp.js} +1 -1
- package/build/{cli-ae8jqxqr.js → cli-cs7hsb1w.js} +2 -2
- package/build/{cli-qgz1q9g5.js → cli-ene4g09p.js} +223 -76
- package/build/{cli-gx71jcke.js → cli-k8m6737j.js} +1 -1
- package/build/{cli-dpsw2fe6.js → cli-mm2vxyne.js} +360 -187
- package/build/{cli-ngq9dr8n.js → cli-mpmpfce6.js} +3 -3
- package/build/{cli-kb21qhvm.js → cli-np9x97h3.js} +4 -3
- package/build/{cli-k2nwxta0.js → cli-tb77javm.js} +2 -2
- package/build/{cli-xtc2t6me.js → cli-wa52mx4p.js} +103 -39
- package/build/{cli-n2en7j5b.js → cli-xe4wq07r.js} +1 -1
- package/build/cli.js +70 -40
- package/build/{config-zpqkd5cz.js → config-gpxx6m5z.js} +1 -1
- package/build/{fastStrike-mcjdvwez.js → fastStrike-v0r461wn.js} +67 -15
- package/build/{fixes-kytx5dmj.js → fixes-c51d83b4.js} +13 -13
- package/build/{index-qv4cc65p.js → index-269a90mm.js} +13 -13
- package/build/{index-wqetp6gq.js → index-3ggmqh6x.js} +12 -7
- package/build/{index-4vqdgb42.js → index-e97991ve.js} +1473 -1217
- package/build/{index-n65z6v7b.js → index-ffay4gb2.js} +6 -6
- package/build/{index-ns4m4s01.js → index-gw6t92t5.js} +4 -4
- package/build/{index-dr3tg1w3.js → index-kkt51z6t.js} +1 -1
- package/build/{index-fv64eb32.js → index-kmaq2ym2.js} +2 -2
- package/build/{index-je6hcsqr.js → index-wgas3zgb.js} +1 -1
- package/build/{index-r9yjkfrv.js → index-xfqh0wte.js} +4 -4
- package/build/{issues-eb3q9d3a.js → issues-sq3zya6y.js} +13 -13
- package/build/{knowledge-fqsvtx0f.js → knowledge-kzd0cgcm.js} +13 -13
- package/build/lib/{chunk-r4j8ybn9.js → chunk-0parq6kp.js} +87 -36
- package/build/lib/{chunk-v3dk9j7k.js → chunk-2rcfbtpx.js} +80 -21
- package/build/lib/{chunk-acgbyf5h.js → chunk-5axh2ztr.js} +3 -2
- package/build/lib/{chunk-yrnrbjes.js → chunk-5qdc4rc6.js} +4 -2
- package/build/lib/{chunk-xhdpgpkm.js → chunk-61pnyq94.js} +2 -2
- package/build/lib/{chunk-1azdancq.js → chunk-7p9f6cxx.js} +1 -1
- package/build/lib/{chunk-z137x08b.js → chunk-7sexq62c.js} +7 -5
- package/build/lib/{chunk-ba41jh41.js → chunk-7zayxxn6.js} +304 -129
- package/build/lib/{chunk-3c00n0cy.js → chunk-83nets1m.js} +8 -3
- package/build/lib/{chunk-e8yy2k73.js → chunk-ajfd3s37.js} +28 -2
- package/build/lib/{chunk-4hwzqthr.js → chunk-ay1fx18z.js} +3 -2
- package/build/lib/{chunk-1fy8azee.js → chunk-bgnq735r.js} +1 -1
- package/build/lib/{chunk-mg03jdmt.js → chunk-eaanxcjj.js} +1 -1
- package/build/lib/{chunk-w66ce82a.js → chunk-f28sa6x2.js} +4 -2
- package/build/lib/{chunk-z96pbjhd.js → chunk-f6xde44n.js} +13 -6
- package/build/lib/{chunk-1jvwqnem.js → chunk-fvgehnst.js} +104 -40
- package/build/lib/{chunk-2qsq76xq.js → chunk-fy2a1dm3.js} +128 -34
- package/build/lib/{chunk-7pwa92rn.js → chunk-jqy0ftnp.js} +1 -1
- package/build/lib/{chunk-bb0ekhkw.js → chunk-k6erk6fv.js} +69 -18
- package/build/lib/{chunk-9tt2021m.js → chunk-s338j54x.js} +9 -9
- package/build/lib/{chunk-1yct6cra.js → chunk-se6sk7e4.js} +1 -1
- package/build/lib/{chunk-nhfax4gb.js → chunk-tdprhz5x.js} +1 -1
- package/build/lib/{chunk-r2dkp4tb.js → chunk-wbt03786.js} +1 -1
- package/build/lib/{chunk-9yksqgzr.js → chunk-yk43cvgb.js} +20 -13
- package/build/lib/core/agents/agentRuntime.d.ts +2 -1
- package/build/lib/core/agents/defineAgent.d.ts +3 -2
- package/build/lib/core/agents/offSecAgent/index.d.ts +3 -1
- package/build/lib/core/agents/offSecAgent/index.js +10 -8
- package/build/lib/core/agents/offSecAgent/prompt.d.ts +3 -1
- package/build/lib/core/agents/offSecAgent/subagentSpawner.d.ts +2 -1
- package/build/lib/core/agents/offSecAgent/subagentSpawner.js +5 -5
- package/build/lib/core/agents/offSecAgent/tools/index.js +4 -4
- package/build/lib/core/agents/offSecAgent/tools/listMemories.d.ts +11 -1
- package/build/lib/core/agents/offSecAgent/tools/reportError.d.ts +2 -1
- package/build/lib/core/agents/offSecAgent/tools/runPentestWorkflow.d.ts +20 -1
- package/build/lib/core/agents/offSecAgent/tools/spawnPentestAgent.d.ts +4 -7
- package/build/lib/core/agents/offSecAgent/tools/spawnPentestSwarm.d.ts +7 -1
- package/build/lib/core/agents/offSecAgent/tools/types.d.ts +2 -1
- package/build/lib/core/agents/offSecAgent/types.d.ts +7 -1
- package/build/lib/core/agents/specialized/attackSurface/blackboxAgent.js +9 -9
- package/build/lib/core/agents/specialized/attackSurface/schemas.js +9 -9
- package/build/lib/core/agents/specialized/authenticationAgent/index.js +10 -10
- package/build/lib/core/agents/specialized/codeAgent/agent.js +9 -9
- package/build/lib/core/agents/specialized/codeAgent/prompts.d.ts +1 -0
- package/build/lib/core/agents/specialized/findingJudge/agent.js +8 -8
- package/build/lib/core/agents/specialized/findingJudge/index.js +3 -3
- package/build/lib/core/agents/specialized/patching/index.js +10 -10
- package/build/lib/core/agents/specialized/pentest/agent.d.ts +18 -4
- package/build/lib/core/agents/specialized/pentest/agent.js +11 -9
- package/build/lib/core/agents/specialized/whiteboxAttackSurface/index.js +9 -9
- package/build/lib/core/ai/ai.d.ts +23 -6
- package/build/lib/core/ai/ai.js +2 -2
- package/build/lib/core/ai/index.js +2 -2
- package/build/lib/core/ai/models/claude-capabilities.d.ts +3 -2
- package/build/lib/core/ai/models/index.d.ts +1 -1
- package/build/lib/core/ai/utils.d.ts +2 -0
- package/build/lib/core/ai/utils.js +4 -2
- package/build/lib/core/api/index.d.ts +3 -0
- package/build/lib/core/api/index.js +22 -19
- package/build/lib/core/api/targetedPentest.d.ts +1 -0
- package/build/lib/core/api/targetedPentest.js +10 -10
- package/build/lib/core/config/config.d.ts +3 -0
- package/build/lib/core/config/index.js +1 -1
- package/build/lib/core/findings/registry.js +3 -3
- package/build/lib/core/session/index.d.ts +12 -0
- package/build/lib/core/session/index.js +2 -2
- package/build/lib/core/session/persistence.d.ts +7 -0
- package/build/lib/core/session/persistence.js +7 -3
- package/build/lib/core/workflows/fastStrike.d.ts +3 -1
- package/build/lib/core/workflows/pentest.d.ts +8 -1
- package/build/lib/core/workflows/pentest.js +15 -15
- package/build/lib/core/workflows/seams.js +4 -4
- package/build/lib/core/workflows/whiteboxAttackSurface.js +11 -11
- package/build/lib/index.js +22 -22
- package/build/{logs-x62b7hk1.js → logs-yk8dk6aa.js} +13 -13
- package/build/{native-rollout-evidence-c9yhfqcs.js → native-rollout-evidence-krpdfdv7.js} +3 -3
- package/build/{offesecAgent-5whnpeta.js → offesecAgent-aw87gf37.js} +4 -4
- package/build/pentest-t0p4nw2a.js +22 -0
- package/build/{pentests-e0f32aqx.js → pentests-577n3x75.js} +13 -13
- package/build/recordedRunClient-d7947b0z.js +11 -0
- package/build/{targetedPentest-rcbs4nvp.js → targetedPentest-7v849dtm.js} +18 -11
- package/build/{targets-xj9g3q0z.js → targets-3vjxv3yj.js} +13 -13
- package/build/threatModel-n0grwkav.js +21 -0
- package/build/{uninstall-7s784q4a.js → uninstall-ctv33v8j.js} +1 -1
- package/build/{upload-4gvhwmvr.js → upload-rnec235r.js} +4 -4
- package/build/{utils-cjrz64pc.js → utils-6420gkkg.js} +3 -3
- package/package.json +1 -1
- package/src/cli.ts +48 -14
- package/src/core/agents/agentRuntime.test.ts +38 -0
- package/src/core/agents/agentRuntime.ts +5 -2
- package/src/core/agents/defineAgent.ts +3 -1
- package/src/core/agents/offSecAgent/index.ts +3 -0
- package/src/core/agents/offSecAgent/offensiveSecurityAgent.test.ts +92 -5
- package/src/core/agents/offSecAgent/offensiveSecurityAgent.ts +70 -31
- package/src/core/agents/offSecAgent/prompt.test.ts +46 -0
- package/src/core/agents/offSecAgent/prompt.ts +52 -8
- package/src/core/agents/offSecAgent/selectiveConstruction.publicPath.test.ts +38 -0
- package/src/core/agents/offSecAgent/selectiveConstruction.test.ts +64 -0
- package/src/core/agents/offSecAgent/subagentSpawner.ts +12 -1
- package/src/core/agents/offSecAgent/tools/delegateAuth.ts +1 -0
- package/src/core/agents/offSecAgent/tools/listMemories.test.ts +230 -0
- package/src/core/agents/offSecAgent/tools/listMemories.ts +73 -6
- package/src/core/agents/offSecAgent/tools/reportError.test.ts +6 -0
- package/src/core/agents/offSecAgent/tools/reportError.ts +10 -2
- package/src/core/agents/offSecAgent/tools/runAttackSurface.ts +2 -0
- package/src/core/agents/offSecAgent/tools/runPentestWorkflow.test.ts +57 -0
- package/src/core/agents/offSecAgent/tools/runPentestWorkflow.ts +19 -1
- package/src/core/agents/offSecAgent/tools/runWhiteboxScan.ts +5 -2
- package/src/core/agents/offSecAgent/tools/spawnCodingAgent.ts +1 -0
- package/src/core/agents/offSecAgent/tools/spawnPentestAgent.test.ts +72 -2
- package/src/core/agents/offSecAgent/tools/spawnPentestAgent.ts +32 -11
- package/src/core/agents/offSecAgent/tools/spawnPentestSwarm.ts +25 -5
- package/src/core/agents/offSecAgent/tools/threatModelGenerator.ts +1 -0
- package/src/core/agents/offSecAgent/tools/types.ts +6 -1
- package/src/core/agents/offSecAgent/tools/updateTask.ts +5 -1
- package/src/core/agents/offSecAgent/tools/whiteboxCandidates.ts +11 -4
- package/src/core/agents/offSecAgent/types.ts +9 -0
- package/src/core/agents/specialized/codeAgent/agent.test.ts +7 -0
- package/src/core/agents/specialized/codeAgent/agent.ts +9 -2
- package/src/core/agents/specialized/codeAgent/prompts.ts +6 -0
- package/src/core/agents/specialized/pentest/agent.test.ts +116 -0
- package/src/core/agents/specialized/pentest/agent.ts +178 -49
- package/src/core/agents/specialized/pentest/results.test.ts +59 -4
- package/src/core/agents/specialized/whiteboxAttackSurface/endpointDocumentationAgent.ts +1 -0
- package/src/core/ai/ai.operator-context.bench.test.ts +1408 -0
- package/src/core/ai/ai.recorded-context.test.ts +74 -0
- package/src/core/ai/ai.test.ts +54 -0
- package/src/core/ai/ai.ts +299 -80
- package/src/core/ai/compactionTelemetry.test.ts +2 -2
- package/src/core/ai/models/anthropic.ts +6 -0
- package/src/core/ai/models/bedrock.ts +18 -0
- package/src/core/ai/models/claude-capabilities.ts +29 -8
- package/src/core/ai/models/index.ts +4 -1
- package/src/core/ai/models/models.test.ts +23 -0
- package/src/core/ai/models/openrouter.ts +6 -0
- package/src/core/ai/providers/anthropic-models.test.ts +60 -1
- package/src/core/ai/providers/bedrock-models.test.ts +81 -0
- package/src/core/ai/providers/gateway-models.test.ts +16 -6
- package/src/core/ai/utils.native-rollout-evidence.test.ts +1 -0
- package/src/core/ai/utils.ts +189 -143
- package/src/core/api/blackboxPentest.ts +9 -1
- package/src/core/api/index.ts +9 -0
- package/src/core/api/targetedPentest.ts +19 -5
- package/src/core/config/config.test.ts +35 -0
- package/src/core/config/config.ts +5 -0
- package/src/core/observability/telemetry.test.ts +2 -3
- package/src/core/session/index.ts +5 -0
- package/src/core/session/persistence.test.ts +62 -0
- package/src/core/session/persistence.ts +56 -0
- package/src/core/workflows/fastStrike.seams.test.ts +149 -0
- package/src/core/workflows/fastStrike.test.ts +18 -1
- package/src/core/workflows/fastStrike.ts +103 -11
- package/src/core/workflows/pentest.test.ts +104 -0
- package/src/core/workflows/pentest.ts +110 -7
- package/src/core/workflows/seams.test.ts +13 -0
- package/src/core/workflows/seams.ts +5 -1
- package/src/core/workflows/whiteboxAttackSurface.ts +1 -0
- package/src/tui/components/chat/loading-indicator.tsx +20 -4
- package/src/tui/components/chat/message-list.tsx +1 -0
- package/src/tui/components/commands/__tests__/advanced-settings-lifecycle.tsx +176 -0
- package/src/tui/components/commands/advanced-settings.test.ts +28 -0
- package/src/tui/components/commands/advanced-settings.tsx +237 -19
- package/src/tui/components/operator-dashboard/index.tsx +46 -9
- package/src/tui/components/operator-dashboard/logic.test.ts +106 -0
- package/src/tui/components/operator-dashboard/logic.ts +42 -2
- package/src/tui/components/operator-dashboard/run-events.test.ts +3 -1
- package/src/tui/components/shared/__tests__/tool-renderer-lifecycle.tsx +18 -0
- package/src/tui/components/shared/action-label.test.ts +9 -0
- package/src/tui/components/shared/compacting-icon.tsx +18 -0
- package/src/tui/components/shared/index.ts +1 -0
- package/src/tui/components/shared/tool-registry.ts +1 -0
- package/src/tui/components/shared/tool-renderer.tsx +21 -7
- package/src/tui/context/route.tsx +1 -0
- package/src/tui/keybindings/registry.ts +2 -0
- package/build/agent-6w7egcf1.js +0 -13
- package/build/agent-9zx75feh.js +0 -13
- package/build/pentest-e61hc38s.js +0 -22
- package/build/recordedRunClient-dp032c32.js +0 -11
- package/build/threatModel-jkxfa40s.js +0 -21
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
ToolSet,
|
|
8
8
|
} from "ai";
|
|
9
9
|
import { hasToolCall } from "ai";
|
|
10
|
-
import type { AgentHooks, Finding } from "../agents/offSecAgent";
|
|
10
|
+
import type { AgentHooks, Finding, FindingPolicy } from "../agents/offSecAgent";
|
|
11
11
|
import {
|
|
12
12
|
OffensiveSecurityAgent,
|
|
13
13
|
PLAN_MODE_TOOL_NAMES,
|
|
@@ -64,6 +64,8 @@ import {
|
|
|
64
64
|
finalizeManifest,
|
|
65
65
|
getCompletedAgentIds,
|
|
66
66
|
loadSubagentMessages,
|
|
67
|
+
loadSubagentObjectiveResults,
|
|
68
|
+
loadSubagentReportedFindings,
|
|
67
69
|
readAgentManifest,
|
|
68
70
|
reconcileManifestOnResume,
|
|
69
71
|
type SwarmTarget,
|
|
@@ -99,6 +101,7 @@ export const DEFAULT_CONCURRENCY = 10;
|
|
|
99
101
|
// ---------------------------------------------------------------------------
|
|
100
102
|
|
|
101
103
|
export interface PentestWorkflowInput {
|
|
104
|
+
findingPolicy?: FindingPolicy;
|
|
102
105
|
/** Live target URL — always required */
|
|
103
106
|
target: string;
|
|
104
107
|
|
|
@@ -170,6 +173,11 @@ export interface PentestWorkflowInput {
|
|
|
170
173
|
|
|
171
174
|
export interface PentestWorkflowResult {
|
|
172
175
|
findings: Finding[];
|
|
176
|
+
reportedFindings?: string[];
|
|
177
|
+
objectiveResults?: PentestResult["objectiveResults"];
|
|
178
|
+
submissionJson?: string;
|
|
179
|
+
solved?: boolean;
|
|
180
|
+
summary?: string;
|
|
173
181
|
findingsPath: string;
|
|
174
182
|
pocsPath: string;
|
|
175
183
|
reportPath: string | null;
|
|
@@ -178,6 +186,7 @@ export interface PentestWorkflowResult {
|
|
|
178
186
|
export type { SwarmTarget } from "../session/persistence";
|
|
179
187
|
|
|
180
188
|
export interface PentestSwarmInput {
|
|
189
|
+
findingPolicy?: FindingPolicy;
|
|
181
190
|
targets: SwarmTarget[];
|
|
182
191
|
model: AIModel;
|
|
183
192
|
session: SessionInfo;
|
|
@@ -290,7 +299,23 @@ export async function runPentestSwarmTarget(
|
|
|
290
299
|
const subagentId = entry.id;
|
|
291
300
|
const subagentName = target.name ?? entry.name;
|
|
292
301
|
|
|
293
|
-
if (completedIds.has(subagentId))
|
|
302
|
+
if (completedIds.has(subagentId)) {
|
|
303
|
+
if (sharedHooks.findingPolicy !== "external") return null;
|
|
304
|
+
const reportedFindings = loadSubagentReportedFindings(session, subagentId);
|
|
305
|
+
const objectiveResults = loadSubagentObjectiveResults(session, subagentId);
|
|
306
|
+
if (!reportedFindings || !objectiveResults) {
|
|
307
|
+
throw new Error(
|
|
308
|
+
`Cannot resume externally graded pentest: findings or objective results for completed agent ${subagentId} are unavailable`,
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
return {
|
|
312
|
+
findings: [],
|
|
313
|
+
reportedFindings,
|
|
314
|
+
objectiveResults,
|
|
315
|
+
findingsPath: session.findingsPath,
|
|
316
|
+
pocsPath: session.pocsPath,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
294
319
|
|
|
295
320
|
// Per-target AgentHooks: seams.hooksForItem can give this one target
|
|
296
321
|
// (e.g. its own lease/backends, its own politeness budget) different
|
|
@@ -418,15 +443,37 @@ export async function runPentestSwarmTarget(
|
|
|
418
443
|
});
|
|
419
444
|
|
|
420
445
|
const result = await agent.consume();
|
|
446
|
+
if (
|
|
447
|
+
itemHooks.findingPolicy === "external" &&
|
|
448
|
+
(!Array.isArray(result.reportedFindings) ||
|
|
449
|
+
!Array.isArray(result.objectiveResults))
|
|
450
|
+
) {
|
|
451
|
+
throw new Error(
|
|
452
|
+
`Externally graded pentest worker ${subagentId} ended without structured findings and objective results`,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
421
455
|
|
|
422
456
|
saveSubagentData(session, {
|
|
423
457
|
agentName: subagentId,
|
|
424
458
|
target: target.target,
|
|
425
459
|
objective: objectiveStr,
|
|
426
460
|
status: "completed",
|
|
427
|
-
findingsCount: result.findings.length,
|
|
461
|
+
findingsCount: result.reportedFindings?.length ?? result.findings.length,
|
|
462
|
+
...(itemHooks.findingPolicy === "external"
|
|
463
|
+
? {
|
|
464
|
+
reportedFindings: result.reportedFindings,
|
|
465
|
+
objectiveResults: result.objectiveResults,
|
|
466
|
+
}
|
|
467
|
+
: {}),
|
|
428
468
|
messages: [...previousMessages, ...lastMessages],
|
|
429
|
-
systemPrompt: buildPentestSystemPrompt(
|
|
469
|
+
systemPrompt: buildPentestSystemPrompt(
|
|
470
|
+
session,
|
|
471
|
+
"orchestrator",
|
|
472
|
+
"default",
|
|
473
|
+
undefined,
|
|
474
|
+
undefined,
|
|
475
|
+
itemHooks.findingPolicy,
|
|
476
|
+
),
|
|
430
477
|
userPrompt: agent.userPrompt,
|
|
431
478
|
});
|
|
432
479
|
updateManifestEntryStatus(session, subagentId, "completed");
|
|
@@ -450,7 +497,14 @@ export async function runPentestSwarmTarget(
|
|
|
450
497
|
status: "failed",
|
|
451
498
|
error: error instanceof Error ? error.message : String(error),
|
|
452
499
|
messages: [...previousMessages, ...lastMessages],
|
|
453
|
-
systemPrompt: buildPentestSystemPrompt(
|
|
500
|
+
systemPrompt: buildPentestSystemPrompt(
|
|
501
|
+
session,
|
|
502
|
+
"orchestrator",
|
|
503
|
+
"default",
|
|
504
|
+
undefined,
|
|
505
|
+
undefined,
|
|
506
|
+
itemHooks.findingPolicy,
|
|
507
|
+
),
|
|
454
508
|
userPrompt: agent?.userPrompt ?? "",
|
|
455
509
|
});
|
|
456
510
|
updateManifestEntryStatus(session, subagentId, "failed");
|
|
@@ -522,6 +576,7 @@ export async function runPentestSwarm(
|
|
|
522
576
|
usageRecorder,
|
|
523
577
|
streamIdFactory,
|
|
524
578
|
...hooks,
|
|
579
|
+
findingPolicy: hooks?.findingPolicy ?? input.findingPolicy,
|
|
525
580
|
};
|
|
526
581
|
|
|
527
582
|
// Ops-typed by the seam; TargetedPentestAgent/OffensiveSecurityAgent only
|
|
@@ -574,6 +629,15 @@ export async function runPentestSwarm(
|
|
|
574
629
|
|
|
575
630
|
finalizeManifest(session, manifestEntries, results);
|
|
576
631
|
|
|
632
|
+
if (
|
|
633
|
+
sharedHooks.findingPolicy === "external" &&
|
|
634
|
+
results.some((result) => result === null)
|
|
635
|
+
) {
|
|
636
|
+
throw new Error(
|
|
637
|
+
"Externally graded pentest has an incomplete swarm target; findings cannot be aggregated",
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
577
641
|
return results;
|
|
578
642
|
}
|
|
579
643
|
|
|
@@ -613,6 +677,7 @@ export async function runPentestWorkflow(
|
|
|
613
677
|
threatModel,
|
|
614
678
|
surfaceIntegrationEnabled,
|
|
615
679
|
} = input;
|
|
680
|
+
const findingPolicy = input.hooks?.findingPolicy ?? input.findingPolicy;
|
|
616
681
|
|
|
617
682
|
// Default registries mirror today's `FindingsRegistry.fromDirectory` resume
|
|
618
683
|
// behavior (dedup against on-disk findings, model-backed semantic dedup).
|
|
@@ -706,6 +771,7 @@ export async function runPentestWorkflow(
|
|
|
706
771
|
usageRecorder: input.usageRecorder,
|
|
707
772
|
streamIdFactory: input.streamIdFactory,
|
|
708
773
|
...input.hooks,
|
|
774
|
+
findingPolicy,
|
|
709
775
|
},
|
|
710
776
|
seams,
|
|
711
777
|
});
|
|
@@ -726,6 +792,7 @@ export async function runPentestWorkflow(
|
|
|
726
792
|
usageRecorder: input.usageRecorder,
|
|
727
793
|
streamIdFactory: input.streamIdFactory,
|
|
728
794
|
...input.hooks,
|
|
795
|
+
findingPolicy,
|
|
729
796
|
},
|
|
730
797
|
});
|
|
731
798
|
}
|
|
@@ -744,6 +811,16 @@ export async function runPentestWorkflow(
|
|
|
744
811
|
}
|
|
745
812
|
|
|
746
813
|
if (swarmTargets.length === 0) {
|
|
814
|
+
if (findingPolicy === "external") {
|
|
815
|
+
return {
|
|
816
|
+
findings: [],
|
|
817
|
+
reportedFindings: [],
|
|
818
|
+
objectiveResults: [],
|
|
819
|
+
findingsPath: session.findingsPath,
|
|
820
|
+
pocsPath: session.pocsPath,
|
|
821
|
+
reportPath: null,
|
|
822
|
+
};
|
|
823
|
+
}
|
|
747
824
|
const report = buildPentestReport([], {
|
|
748
825
|
target,
|
|
749
826
|
model,
|
|
@@ -778,10 +855,15 @@ export async function runPentestWorkflow(
|
|
|
778
855
|
const findingsRegistry = seams.registries.findings() as FindingsRegistry;
|
|
779
856
|
|
|
780
857
|
const completedCount = getCompletedAgentIds(session).size;
|
|
858
|
+
let externalReportedFindings: string[] = [];
|
|
859
|
+
let externalObjectiveResults: NonNullable<
|
|
860
|
+
PentestResult["objectiveResults"]
|
|
861
|
+
> = [];
|
|
781
862
|
|
|
782
|
-
if (completedCount < swarmTargets.length) {
|
|
783
|
-
await runPentestSwarm({
|
|
863
|
+
if (findingPolicy === "external" || completedCount < swarmTargets.length) {
|
|
864
|
+
const swarmResults = await runPentestSwarm({
|
|
784
865
|
targets: swarmTargets,
|
|
866
|
+
findingPolicy,
|
|
785
867
|
model,
|
|
786
868
|
session,
|
|
787
869
|
authConfig,
|
|
@@ -800,12 +882,33 @@ export async function runPentestWorkflow(
|
|
|
800
882
|
hooks: input.hooks,
|
|
801
883
|
seams,
|
|
802
884
|
});
|
|
885
|
+
externalReportedFindings = swarmResults.flatMap(
|
|
886
|
+
(result) => result?.reportedFindings ?? [],
|
|
887
|
+
);
|
|
888
|
+
externalObjectiveResults = swarmResults.flatMap(
|
|
889
|
+
(result) => result?.objectiveResults ?? [],
|
|
890
|
+
);
|
|
803
891
|
}
|
|
804
892
|
|
|
805
893
|
if (abortSignal?.aborted) {
|
|
806
894
|
throw new DOMException("Pentest aborted by user", "AbortError");
|
|
807
895
|
}
|
|
808
896
|
|
|
897
|
+
if (findingPolicy === "external") {
|
|
898
|
+
eventBus?.emit("workflow-phase-complete", {
|
|
899
|
+
phase: "pentesting",
|
|
900
|
+
summary: { findingsCount: externalReportedFindings.length },
|
|
901
|
+
});
|
|
902
|
+
return {
|
|
903
|
+
findings: [],
|
|
904
|
+
reportedFindings: externalReportedFindings,
|
|
905
|
+
objectiveResults: externalObjectiveResults,
|
|
906
|
+
findingsPath: session.findingsPath,
|
|
907
|
+
pocsPath: session.pocsPath,
|
|
908
|
+
reportPath: null,
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
|
|
809
912
|
// =========================================================================
|
|
810
913
|
// Phase 3: Aggregate results
|
|
811
914
|
// =========================================================================
|
|
@@ -312,6 +312,19 @@ describe("resolveItemHooks recorder scope", () => {
|
|
|
312
312
|
});
|
|
313
313
|
});
|
|
314
314
|
|
|
315
|
+
it("does not let a per-item hook re-enable native finding judging under an external run", () => {
|
|
316
|
+
const seams = inProcessSeams({
|
|
317
|
+
hooksForItem: () => ({ findingPolicy: "native" }),
|
|
318
|
+
});
|
|
319
|
+
const child = resolveItemHooks(
|
|
320
|
+
{ findingPolicy: "external" },
|
|
321
|
+
seams,
|
|
322
|
+
"target",
|
|
323
|
+
0,
|
|
324
|
+
);
|
|
325
|
+
expect(child.findingPolicy).toBe("external");
|
|
326
|
+
});
|
|
327
|
+
|
|
315
328
|
it("requires a child inference binding instead of inheriting the parent's journal", () => {
|
|
316
329
|
const recorder: InferenceRecorder = {
|
|
317
330
|
runId: "parent",
|
|
@@ -266,11 +266,15 @@ export function resolveItemHooks<TItem>(
|
|
|
266
266
|
item: TItem,
|
|
267
267
|
index: number,
|
|
268
268
|
): AgentHooks {
|
|
269
|
+
const itemHooks = seams.hooksForItem?.(item, index);
|
|
269
270
|
return {
|
|
270
271
|
...hooks,
|
|
271
272
|
contextRecorder: undefined,
|
|
272
273
|
inferenceRecorder: undefined,
|
|
273
274
|
toolExecutionRecorder: undefined,
|
|
274
|
-
...
|
|
275
|
+
...itemHooks,
|
|
276
|
+
...(hooks?.findingPolicy === "external"
|
|
277
|
+
? { findingPolicy: "external" as const }
|
|
278
|
+
: {}),
|
|
275
279
|
};
|
|
276
280
|
}
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { useEffect, useRef, useState } from "react";
|
|
8
8
|
import { INTERNAL_ID_PATTERN } from "../../../core/operator";
|
|
9
9
|
import { useTheme } from "../../theme";
|
|
10
|
+
import { CompactingIcon } from "../shared";
|
|
10
11
|
|
|
11
12
|
// Braille spinner frames for smooth animation
|
|
12
13
|
const SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
@@ -16,7 +17,12 @@ const SPINNER_INTERVAL = 80;
|
|
|
16
17
|
const DOTS_FRAMES = ["", ".", "..", "..."];
|
|
17
18
|
const DOTS_INTERVAL = 400;
|
|
18
19
|
|
|
19
|
-
export type LoadingState =
|
|
20
|
+
export type LoadingState =
|
|
21
|
+
| "thinking"
|
|
22
|
+
| "compacting"
|
|
23
|
+
| "executing"
|
|
24
|
+
| "streaming"
|
|
25
|
+
| "waiting";
|
|
20
26
|
|
|
21
27
|
interface LoadingIndicatorProps {
|
|
22
28
|
/** Current loading state */
|
|
@@ -42,19 +48,21 @@ export function LoadingIndicator({
|
|
|
42
48
|
|
|
43
49
|
// Spinner animation
|
|
44
50
|
useEffect(() => {
|
|
51
|
+
if (state === "compacting") return;
|
|
45
52
|
const interval = setInterval(() => {
|
|
46
53
|
setSpinnerFrame((f) => (f + 1) % SPINNER_FRAMES.length);
|
|
47
54
|
}, SPINNER_INTERVAL);
|
|
48
55
|
return () => clearInterval(interval);
|
|
49
|
-
}, []);
|
|
56
|
+
}, [state]);
|
|
50
57
|
|
|
51
58
|
// Dots animation
|
|
52
59
|
useEffect(() => {
|
|
60
|
+
if (state === "compacting") return;
|
|
53
61
|
const interval = setInterval(() => {
|
|
54
62
|
setDotsFrame((f) => (f + 1) % DOTS_FRAMES.length);
|
|
55
63
|
}, DOTS_INTERVAL);
|
|
56
64
|
return () => clearInterval(interval);
|
|
57
|
-
}, []);
|
|
65
|
+
}, [state]);
|
|
58
66
|
|
|
59
67
|
const spinner = SPINNER_FRAMES[spinnerFrame];
|
|
60
68
|
const dots = DOTS_FRAMES[dotsFrame];
|
|
@@ -64,6 +72,8 @@ export function LoadingIndicator({
|
|
|
64
72
|
switch (state) {
|
|
65
73
|
case "thinking":
|
|
66
74
|
return `Thinking${dots}`;
|
|
75
|
+
case "compacting":
|
|
76
|
+
return "Compacting";
|
|
67
77
|
case "streaming":
|
|
68
78
|
return `Responding${dots}`;
|
|
69
79
|
case "waiting":
|
|
@@ -95,6 +105,8 @@ export function LoadingIndicator({
|
|
|
95
105
|
switch (state) {
|
|
96
106
|
case "thinking":
|
|
97
107
|
return colors.warning;
|
|
108
|
+
case "compacting":
|
|
109
|
+
return colors.primary;
|
|
98
110
|
case "streaming":
|
|
99
111
|
return colors.primary;
|
|
100
112
|
case "waiting":
|
|
@@ -108,7 +120,11 @@ export function LoadingIndicator({
|
|
|
108
120
|
|
|
109
121
|
return (
|
|
110
122
|
<box flexDirection="row" marginTop={1} marginLeft={2} gap={1}>
|
|
111
|
-
|
|
123
|
+
{state === "compacting" ? (
|
|
124
|
+
<CompactingIcon fg={getColor()} />
|
|
125
|
+
) : (
|
|
126
|
+
<text fg={getColor()}>{spinner}</text>
|
|
127
|
+
)}
|
|
112
128
|
<text fg={colors.textMuted}>{getMessage()}</text>
|
|
113
129
|
</box>
|
|
114
130
|
);
|
|
@@ -39,6 +39,7 @@ function getLoadingState(
|
|
|
39
39
|
pendingToolName: string | null,
|
|
40
40
|
): LoadingState {
|
|
41
41
|
if (hasPendingTool) {
|
|
42
|
+
if (pendingToolName === "summarize_conversation") return "compacting";
|
|
42
43
|
if (pendingToolName && SUBAGENT_TOOLS.has(pendingToolName)) {
|
|
43
44
|
return "waiting";
|
|
44
45
|
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { mock } from "bun:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { testRender } from "@opentui/react/test-utils";
|
|
5
|
+
import { act } from "react";
|
|
6
|
+
import type { Config } from "../../../../core/config/config";
|
|
7
|
+
|
|
8
|
+
const scenario = process.argv[2];
|
|
9
|
+
assert.ok(
|
|
10
|
+
scenario === "native" || scenario === "external" || scenario === "compact",
|
|
11
|
+
);
|
|
12
|
+
const compact = scenario === "compact";
|
|
13
|
+
|
|
14
|
+
const saved: Partial<Config>[] = [];
|
|
15
|
+
let closes = 0;
|
|
16
|
+
mock.module(join(import.meta.dirname, "../../../context/config.tsx"), () => ({
|
|
17
|
+
useConfig: () => ({
|
|
18
|
+
data: { strikeMode: false, findingPolicy: "native" },
|
|
19
|
+
update: async (patch: Partial<Config>) => {
|
|
20
|
+
saved.push(patch);
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
const { default: AdvancedSettings } = await import("../advanced-settings.tsx");
|
|
26
|
+
const { TerminalDimensionsProvider } = await import(
|
|
27
|
+
"../../../context/dimensions"
|
|
28
|
+
);
|
|
29
|
+
const { registerBuiltinThemes, ThemeProvider } = await import("../../../theme");
|
|
30
|
+
registerBuiltinThemes();
|
|
31
|
+
|
|
32
|
+
const setup = await testRender(
|
|
33
|
+
<ThemeProvider>
|
|
34
|
+
<TerminalDimensionsProvider>
|
|
35
|
+
<AdvancedSettings onClose={() => (closes += 1)} />
|
|
36
|
+
</TerminalDimensionsProvider>
|
|
37
|
+
</ThemeProvider>,
|
|
38
|
+
{
|
|
39
|
+
width: compact ? 60 : 80,
|
|
40
|
+
height: compact ? 18 : 24,
|
|
41
|
+
exitOnCtrlC: false,
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const settle = async () => {
|
|
46
|
+
for (let i = 0; i < 3; i++) await act(async () => {});
|
|
47
|
+
await act(setup.renderOnce);
|
|
48
|
+
};
|
|
49
|
+
const frame = async () => {
|
|
50
|
+
await settle();
|
|
51
|
+
return setup.captureCharFrame();
|
|
52
|
+
};
|
|
53
|
+
const press = async (key: string) => {
|
|
54
|
+
await act(async () => setup.mockInput.pressKey(key));
|
|
55
|
+
await settle();
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const initialFrame = await frame();
|
|
60
|
+
assert.ok(initialFrame.includes("Strike Mode"), initialFrame);
|
|
61
|
+
assert.ok(initialFrame.includes("❯ Strike Mode"), initialFrame);
|
|
62
|
+
assert.ok(initialFrame.includes("Save settings"), initialFrame);
|
|
63
|
+
assert.ok(!initialFrame.includes("Finding judge"), initialFrame);
|
|
64
|
+
assert.ok(!initialFrame.includes("Judge off"), initialFrame);
|
|
65
|
+
assert.deepEqual(saved, []);
|
|
66
|
+
|
|
67
|
+
await press("TAB");
|
|
68
|
+
const strikeOnFrame = await frame();
|
|
69
|
+
assert.ok(strikeOnFrame.includes("● Enabled"), strikeOnFrame);
|
|
70
|
+
assert.ok(strikeOnFrame.includes("❯ Strike Mode"), strikeOnFrame);
|
|
71
|
+
assert.ok(strikeOnFrame.includes("Finding judge"), strikeOnFrame);
|
|
72
|
+
assert.ok(strikeOnFrame.includes("Apex judge"), strikeOnFrame);
|
|
73
|
+
assert.ok(!strikeOnFrame.includes("[J]"), strikeOnFrame);
|
|
74
|
+
if (compact) {
|
|
75
|
+
assert.ok(strikeOnFrame.includes("Save settings"), strikeOnFrame);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
await press("j");
|
|
79
|
+
assert.ok(!(await frame()).includes("Off (report findings only)"));
|
|
80
|
+
|
|
81
|
+
await press("ARROW_DOWN");
|
|
82
|
+
const judgeFocusedFrame = await frame();
|
|
83
|
+
assert.ok(judgeFocusedFrame.includes("❯ Finding judge"), judgeFocusedFrame);
|
|
84
|
+
await press("TAB");
|
|
85
|
+
const judgeOffFrame = await frame();
|
|
86
|
+
assert.ok(judgeOffFrame.includes("❯ Finding judge"), judgeOffFrame);
|
|
87
|
+
assert.ok(judgeOffFrame.includes("● Enabled"), judgeOffFrame);
|
|
88
|
+
assert.ok(judgeOffFrame.includes("Disabled ▾"), judgeOffFrame);
|
|
89
|
+
assert.ok(
|
|
90
|
+
!judgeOffFrame.includes("Off (report findings only)"),
|
|
91
|
+
judgeOffFrame,
|
|
92
|
+
);
|
|
93
|
+
await press("TAB");
|
|
94
|
+
const judgeOnFrame = await frame();
|
|
95
|
+
assert.ok(judgeOnFrame.includes("❯ Finding judge"), judgeOnFrame);
|
|
96
|
+
assert.ok(judgeOnFrame.includes("● Enabled"), judgeOnFrame);
|
|
97
|
+
assert.ok(judgeOnFrame.includes("Apex judge ▾"), judgeOnFrame);
|
|
98
|
+
assert.deepEqual(saved, []);
|
|
99
|
+
|
|
100
|
+
await press("RETURN");
|
|
101
|
+
const findingSelectFrame = await frame();
|
|
102
|
+
if (compact) {
|
|
103
|
+
assert.ok(
|
|
104
|
+
findingSelectFrame.includes("Apex judge (default)"),
|
|
105
|
+
findingSelectFrame,
|
|
106
|
+
);
|
|
107
|
+
assert.ok(findingSelectFrame.includes("Save settings"), findingSelectFrame);
|
|
108
|
+
}
|
|
109
|
+
assert.ok(
|
|
110
|
+
findingSelectFrame.includes("Off (report findings only)"),
|
|
111
|
+
findingSelectFrame,
|
|
112
|
+
);
|
|
113
|
+
await press("ARROW_DOWN");
|
|
114
|
+
const offFocusedFrame = await frame();
|
|
115
|
+
assert.ok(
|
|
116
|
+
offFocusedFrame.includes("❯ Off (report findings only)"),
|
|
117
|
+
offFocusedFrame,
|
|
118
|
+
);
|
|
119
|
+
await press("RETURN");
|
|
120
|
+
assert.ok((await frame()).includes("Disabled ▾"));
|
|
121
|
+
assert.deepEqual(saved, []);
|
|
122
|
+
|
|
123
|
+
if (scenario === "native") {
|
|
124
|
+
await press("ARROW_UP");
|
|
125
|
+
const strikeFocusedFrame = await frame();
|
|
126
|
+
assert.ok(strikeFocusedFrame.includes("❯ Strike Mode"), strikeFocusedFrame);
|
|
127
|
+
assert.ok(strikeFocusedFrame.includes("Disabled ▾"), strikeFocusedFrame);
|
|
128
|
+
await press("TAB");
|
|
129
|
+
const strikeOffFrame = await frame();
|
|
130
|
+
assert.ok(strikeOffFrame.includes("○ Disabled"));
|
|
131
|
+
assert.ok(!strikeOffFrame.includes("Finding judge"), strikeOffFrame);
|
|
132
|
+
assert.ok(!strikeOffFrame.includes("Judge off"), strikeOffFrame);
|
|
133
|
+
assert.ok(!strikeOffFrame.includes("Disabled ▾"));
|
|
134
|
+
assert.ok(strikeOffFrame.includes("❯ Strike Mode"), strikeOffFrame);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
await press("ARROW_DOWN");
|
|
138
|
+
const saveFocusedFrame = await frame();
|
|
139
|
+
assert.ok(saveFocusedFrame.includes("❯ Save settings"), saveFocusedFrame);
|
|
140
|
+
await press("TAB");
|
|
141
|
+
const afterSaveTabFrame = await frame();
|
|
142
|
+
assert.ok(afterSaveTabFrame.includes("❯ Save settings"), afterSaveTabFrame);
|
|
143
|
+
assert.ok(
|
|
144
|
+
afterSaveTabFrame.includes(
|
|
145
|
+
scenario === "native" ? "○ Disabled" : "● Enabled",
|
|
146
|
+
),
|
|
147
|
+
afterSaveTabFrame,
|
|
148
|
+
);
|
|
149
|
+
assert.ok(
|
|
150
|
+
scenario === "native"
|
|
151
|
+
? !afterSaveTabFrame.includes("Finding judge")
|
|
152
|
+
: afterSaveTabFrame.includes("Disabled ▾"),
|
|
153
|
+
afterSaveTabFrame,
|
|
154
|
+
);
|
|
155
|
+
assert.deepEqual(saved, []);
|
|
156
|
+
assert.equal(closes, 0);
|
|
157
|
+
await press("ARROW_UP");
|
|
158
|
+
const previousFocusedFrame = await frame();
|
|
159
|
+
assert.ok(
|
|
160
|
+
previousFocusedFrame.includes(
|
|
161
|
+
scenario === "native" ? "❯ Strike Mode" : "❯ Finding judge",
|
|
162
|
+
),
|
|
163
|
+
previousFocusedFrame,
|
|
164
|
+
);
|
|
165
|
+
await press("ARROW_DOWN");
|
|
166
|
+
assert.ok((await frame()).includes("❯ Save settings"));
|
|
167
|
+
await press("RETURN");
|
|
168
|
+
assert.deepEqual(saved, [
|
|
169
|
+
scenario === "native"
|
|
170
|
+
? { strikeMode: false, findingPolicy: "native" }
|
|
171
|
+
: { strikeMode: true, findingPolicy: "external" },
|
|
172
|
+
]);
|
|
173
|
+
assert.equal(closes, 1);
|
|
174
|
+
} finally {
|
|
175
|
+
await act(async () => setup.renderer.destroy());
|
|
176
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
for (const scenario of [
|
|
6
|
+
{
|
|
7
|
+
name: "Tab toggles the focused Judge and resets it when Strike turns off",
|
|
8
|
+
mode: "native",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: "Tab on Save does not change Strike or Judge",
|
|
12
|
+
mode: "external",
|
|
13
|
+
},
|
|
14
|
+
{ name: "keeps judge choices and Save visible at 60x18", mode: "compact" },
|
|
15
|
+
] as const) {
|
|
16
|
+
it(scenario.name, () => {
|
|
17
|
+
const result = spawnSync(
|
|
18
|
+
"bun",
|
|
19
|
+
[
|
|
20
|
+
join(import.meta.dirname, "__tests__/advanced-settings-lifecycle.tsx"),
|
|
21
|
+
scenario.mode,
|
|
22
|
+
],
|
|
23
|
+
{ encoding: "utf8", timeout: 30_000 },
|
|
24
|
+
);
|
|
25
|
+
expect(result.error).toBeUndefined();
|
|
26
|
+
expect(result.status, result.stderr).toBe(0);
|
|
27
|
+
});
|
|
28
|
+
}
|