0sec-cli 0.22.1 → 0.22.4
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/0.js +56 -45
- package/README.md +12 -0
- package/chunks/{adapt-loop-J6QCZZTB.js → adapt-loop-3CCOWE43.js} +4 -4
- package/chunks/{artifact-scraper-MFKT6OI4.js → artifact-scraper-Z4UUIOH3.js} +2 -2
- package/chunks/{assumption-mining-646LYSJY.js → assumption-mining-2UAKQB6V.js} +18 -14
- package/chunks/{chunk-OHHCGDJ2.js → chunk-2WWAPKAW.js} +126 -1089
- package/chunks/{chunk-XDKB2P5X.js → chunk-3BPX75OR.js} +1 -1
- package/chunks/chunk-4ZYYX5GY.js +74 -0
- package/chunks/{chunk-3E2KALZ5.js → chunk-5JNTMWPK.js} +2 -2
- package/chunks/{chunk-CPWA6RWE.js → chunk-5WAJGLCW.js} +3 -3
- package/chunks/{chunk-KAU6NLGP.js → chunk-6KFB6AYN.js} +515 -20
- package/chunks/{chunk-XA2O4WXA.js → chunk-6WQDXWQC.js} +169 -15
- package/chunks/chunk-6YZE552K.js +5603 -0
- package/chunks/chunk-BGX53365.js +309 -0
- package/chunks/{chunk-X7LAEEUW.js → chunk-C6JPA32H.js} +1 -1
- package/chunks/chunk-CF3MNS3O.js +990 -0
- package/chunks/{chunk-4VGLO2YS.js → chunk-CPCHLL6A.js} +5 -2
- package/chunks/{chunk-ZKQ2I3S2.js → chunk-DPUXXMJW.js} +1 -1
- package/chunks/{chunk-XCISCI4T.js → chunk-E3OOBVOK.js} +30 -12
- package/chunks/{chunk-4G7OVYJX.js → chunk-E74LXQFY.js} +1 -1
- package/chunks/{chunk-OSBPGWTE.js → chunk-ELI3BVIK.js} +3 -3
- package/chunks/chunk-FFHRANYC.js +547 -0
- package/chunks/{chunk-WSDDGFA6.js → chunk-G2BE2FC7.js} +1 -1
- package/chunks/chunk-GQ3DGHWE.js +1659 -0
- package/chunks/{chunk-V36HVTUL.js → chunk-GVY3S5E5.js} +21 -18
- package/chunks/{chunk-5BTJMINS.js → chunk-H4DH7COV.js} +192 -40
- package/chunks/{chunk-HHPWRY2M.js → chunk-H6Q2QAIY.js} +23475 -22747
- package/chunks/{chunk-7XCKYNLO.js → chunk-IHPTOTLL.js} +1 -1
- package/chunks/chunk-IXAHQICR.js +31 -0
- package/chunks/{chunk-TH2LW437.js → chunk-KCA35IL3.js} +2 -190
- package/chunks/{chunk-VWITLWXB.js → chunk-L2CCO4KE.js} +282 -34
- package/chunks/{chunk-6YNKRUMC.js → chunk-NJLKFC66.js} +321 -5
- package/chunks/{chunk-W6UWZFKL.js → chunk-O6KPVYOI.js} +100 -5
- package/chunks/{chunk-LGCASF2C.js → chunk-OAS5SAVZ.js} +6767 -2100
- package/chunks/chunk-PS5E7LUQ.js +277 -0
- package/chunks/{chunk-AV2LG2L7.js → chunk-QVIRWJ45.js} +257 -145
- package/chunks/{chunk-TV27OWIW.js → chunk-QWK4R3DB.js} +9 -9
- package/chunks/{chunk-WZRVS7SB.js → chunk-RCRTSKQN.js} +1 -1
- package/chunks/chunk-RS4MAN3Q.js +73 -0
- package/chunks/{chunk-V6GHX34G.js → chunk-RTBZ36BD.js} +8 -274
- package/chunks/{chunk-YL6YAGLL.js → chunk-SCWWLABR.js} +1 -1
- package/chunks/chunk-VDOWARMJ.js +63 -0
- package/chunks/{chunk-IFME676H.js → chunk-WBRGXCU2.js} +79 -18
- package/chunks/{chunk-IVQV574Y.js → chunk-WHUAUFSX.js} +1 -1
- package/chunks/{chunk-GOEA3NW6.js → chunk-XODDLNBI.js} +1534 -1305
- package/chunks/chunk-YAZARMJY.js +355 -0
- package/chunks/{chunk-NLH4LMWH.js → chunk-YED25PA4.js} +1 -1
- package/chunks/{codex-models-NHQW45SO.js → codex-models-Z43V3427.js} +9 -3
- package/chunks/{commands-N6GN5SBF.js → commands-EUGF4FUZ.js} +5842 -4093
- package/chunks/{db-5IWEJUYV.js → db-NVAW4WWR.js} +3 -3
- package/chunks/{dist-L3XJWWZ6.js → dist-27BNG6WD.js} +193 -112
- package/chunks/{dist-FSS7MBDF.js → dist-7RUOKPRJ.js} +12 -2
- package/chunks/{dist-KEULE5IQ.js → dist-LNXGATR2.js} +2 -2
- package/chunks/{dist-XZB53PS5.js → dist-M2X367VW.js} +2 -2
- package/chunks/{dist-BS4CY4RG.js → dist-NG2HT5XU.js} +27 -1
- package/chunks/{eval-runner-EMEGF24P.js → eval-runner-6Q3H52SP.js} +13 -9
- package/chunks/{exploit-agent-KW4EB7BE.js → exploit-agent-FCCR2HYX.js} +1 -1
- package/chunks/{exploit-autoclimb-CXJ2ERRO.js → exploit-autoclimb-X5ZS5FAO.js} +1 -1
- package/chunks/{exploit-climb-TJWLY4XC.js → exploit-climb-3JHGEO36.js} +2 -2
- package/chunks/{fix-UUKPXCFB.js → fix-NSNPU33B.js} +12 -6
- package/chunks/{harness-C5HOKIEV.js → harness-KXQOVIDQ.js} +3 -3
- package/chunks/{hunt-scan-KZZ444V2.js → hunt-scan-ELTKGKC4.js} +16 -12
- package/chunks/{kernel-primitive-Z77IR7YS.js → kernel-primitive-MT7YZSIG.js} +2 -2
- package/chunks/{kernel-vm-runner-JW77P276.js → kernel-vm-runner-YFRHYC6G.js} +2 -2
- package/chunks/{native-loop-P6P5GYVJ.js → native-loop-OFJDRHPI.js} +18 -8
- package/chunks/orchestrate-DRZ2SVCP.js +62 -0
- package/chunks/{prepare-KYHGFWCJ.js → prepare-72GWTRVI.js} +6 -2
- package/chunks/{process-N245QIQG.js → process-MJYJ2K5E.js} +2 -1
- package/chunks/{run-GAFKBCTN.js → run-WTJCZZHV.js} +1901 -4816
- package/chunks/{runtime-H7YBR5IZ.js → runtime-FCKOUB7V.js} +10 -4
- package/chunks/{runtime-XFWJTBUO.js → runtime-LCJ4XSKE.js} +5 -1
- package/chunks/{scan-stream-BMOCPKQX.js → scan-stream-GT6GKQ3U.js} +7 -1
- package/chunks/{scope-QWY4VQZ7.js → scope-E5QZUOOH.js} +2 -2
- package/chunks/service-plugins-MSK2EQP6.js +62 -0
- package/chunks/{session-store-XIDFGG7K.js → session-store-H3Q7KCYG.js} +6 -4
- package/chunks/{variant-candidates-7D4F5E4H.js → variant-candidates-SZKDCOLK.js} +9 -3
- package/chunks/{web-recon-prepass-VXXBPDAV.js → web-recon-prepass-IO3GXQMW.js} +11 -6
- package/dashboard/apple-touch-icon.png +0 -0
- package/dashboard/assets/operations-BvB5Qpmk.css +1 -0
- package/dashboard/assets/operations-D8Ii01SV.js +114 -0
- package/dashboard/assets/zero-peek-Dzm3MT3X.png +0 -0
- package/dashboard/favicon.ico +0 -0
- package/dashboard/favicon.svg +10 -0
- package/dashboard/index.html +5 -3
- package/package.json +1 -1
- package/chunks/chunk-6GHSR47F.js +0 -147
- package/chunks/chunk-BCSRRQG5.js +0 -3514
- package/chunks/chunk-BOUGKEKA.js +0 -193
- package/chunks/chunk-GHWODZMR.js +0 -16
- package/chunks/orchestrate-DWWUT5RC.js +0 -58
- package/dashboard/assets/bot-Bg1Tp9_d.js +0 -1
- package/dashboard/assets/dist-B_dDCPbu.js +0 -1
- package/dashboard/assets/findings-page-mayGhNx6.js +0 -5
- package/dashboard/assets/format--susmHX7.js +0 -1
- package/dashboard/assets/input-TuVPR3Zl.js +0 -1
- package/dashboard/assets/jsx-runtime-MGseXjj5.js +0 -3
- package/dashboard/assets/live-page-HWWHX99U.js +0 -1
- package/dashboard/assets/meta-tile-ckHo-w7k.js +0 -1
- package/dashboard/assets/operations-BxZ0OoXO.js +0 -10
- package/dashboard/assets/operations-CMFVrXEe.css +0 -2
- package/dashboard/assets/operations-app-D3d35rgo.js +0 -46
- package/dashboard/assets/overview-page-W_4IteEr.js +0 -1
- package/dashboard/assets/page-header-Bw9AJgBV.js +0 -1
- package/dashboard/assets/scans-page-CgzVXHnn.js +0 -1
- package/dashboard/assets/siren-KQj8QAnJ.js +0 -1
- package/dashboard/assets/table-8EDklulN.js +0 -1
- package/dashboard/assets/tabs-CFHPOwg8.js +0 -1
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __0CreateRequire } from "node:module";
|
|
3
|
+
const require = __0CreateRequire(import.meta.url);
|
|
4
|
+
import {
|
|
5
|
+
eventBus
|
|
6
|
+
} from "./chunk-4ZYYX5GY.js";
|
|
7
|
+
import {
|
|
8
|
+
estimateCost,
|
|
9
|
+
modelProvider,
|
|
10
|
+
splitCost,
|
|
11
|
+
validateScanPlan
|
|
12
|
+
} from "./chunk-6KFB6AYN.js";
|
|
13
|
+
|
|
14
|
+
// packages/core/dist/agent/cost-ledger.js
|
|
15
|
+
var recordedResults = /* @__PURE__ */ new WeakMap();
|
|
16
|
+
function addRuntimeUsage(ledger, result, model) {
|
|
17
|
+
if (!ledger || recordedResults.get(result) === ledger || !result.usage && !result.usageByModel?.length)
|
|
18
|
+
return;
|
|
19
|
+
if (result.usageByModel?.length) {
|
|
20
|
+
for (const bucket of result.usageByModel)
|
|
21
|
+
ledger.add(bucket.usage, bucket.model);
|
|
22
|
+
} else if (result.usage)
|
|
23
|
+
ledger.add(result.usage, model);
|
|
24
|
+
recordedResults.set(result, ledger);
|
|
25
|
+
}
|
|
26
|
+
var ScanCostLedger = class _ScanCostLedger {
|
|
27
|
+
parent;
|
|
28
|
+
inputTokens = 0;
|
|
29
|
+
outputTokens = 0;
|
|
30
|
+
cachedInputTokens = 0;
|
|
31
|
+
cacheWriteTokens = 0;
|
|
32
|
+
perModel = /* @__PURE__ */ new Map();
|
|
33
|
+
listeners = /* @__PURE__ */ new Set();
|
|
34
|
+
unpricedUsage = false;
|
|
35
|
+
markUnpricedUsage() {
|
|
36
|
+
if (this.unpricedUsage)
|
|
37
|
+
return;
|
|
38
|
+
this.unpricedUsage = true;
|
|
39
|
+
this.parent?.markUnpricedUsage();
|
|
40
|
+
for (const listener of this.listeners)
|
|
41
|
+
listener();
|
|
42
|
+
}
|
|
43
|
+
hasUnpricedUsage() {
|
|
44
|
+
return this.unpricedUsage;
|
|
45
|
+
}
|
|
46
|
+
constructor(parent) {
|
|
47
|
+
this.parent = parent;
|
|
48
|
+
}
|
|
49
|
+
/** Isolated attribution, with the same root budget for every concurrent run. */
|
|
50
|
+
fork() {
|
|
51
|
+
return new _ScanCostLedger(this);
|
|
52
|
+
}
|
|
53
|
+
onSpend(listener) {
|
|
54
|
+
const root = this.parent ?? this;
|
|
55
|
+
if (root !== this)
|
|
56
|
+
return root.onSpend(listener);
|
|
57
|
+
this.listeners.add(listener);
|
|
58
|
+
return () => {
|
|
59
|
+
this.listeners.delete(listener);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/** Spend attributable to this run, not its siblings or earlier attempts. */
|
|
63
|
+
runCostUsd() {
|
|
64
|
+
return this.costBreakdown()?.costUsd ?? 0;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Fold one turn's token usage into the cumulative total. `model` (the
|
|
68
|
+
* session's pricing model) keys the per-model buckets the cost_breakdown
|
|
69
|
+
* is derived from; sessions without a model pick land in "unknown".
|
|
70
|
+
*/
|
|
71
|
+
add(usage, model) {
|
|
72
|
+
this.inputTokens += usage.inputTokens;
|
|
73
|
+
this.outputTokens += usage.outputTokens;
|
|
74
|
+
this.cachedInputTokens += usage.cachedInputTokens ?? 0;
|
|
75
|
+
this.cacheWriteTokens += usage.cacheWriteTokens ?? 0;
|
|
76
|
+
const key = model ?? "unknown";
|
|
77
|
+
const bucket = this.perModel.get(key) ?? { inputTokens: 0, outputTokens: 0, cachedInputTokens: 0, cacheWriteTokens: 0 };
|
|
78
|
+
bucket.inputTokens += usage.inputTokens;
|
|
79
|
+
bucket.outputTokens += usage.outputTokens;
|
|
80
|
+
bucket.cachedInputTokens += usage.cachedInputTokens ?? 0;
|
|
81
|
+
bucket.cacheWriteTokens += usage.cacheWriteTokens ?? 0;
|
|
82
|
+
this.perModel.set(key, bucket);
|
|
83
|
+
this.parent?.add(usage, model);
|
|
84
|
+
for (const listener of this.listeners)
|
|
85
|
+
listener();
|
|
86
|
+
}
|
|
87
|
+
/** Cumulative estimated cost (USD) across every contributing session. */
|
|
88
|
+
costUsd(model) {
|
|
89
|
+
return estimateCost({
|
|
90
|
+
inputTokens: this.inputTokens,
|
|
91
|
+
outputTokens: this.outputTokens,
|
|
92
|
+
cachedInputTokens: this.cachedInputTokens,
|
|
93
|
+
cacheWriteTokens: this.cacheWriteTokens
|
|
94
|
+
}, model);
|
|
95
|
+
}
|
|
96
|
+
/** Exact cumulative spend derived from the per-model buckets. */
|
|
97
|
+
totalCostUsd() {
|
|
98
|
+
return this.parent?.totalCostUsd() ?? this.runCostUsd();
|
|
99
|
+
}
|
|
100
|
+
/** Cumulative usage for the final cloud cost_update event. */
|
|
101
|
+
tokenUsage() {
|
|
102
|
+
return {
|
|
103
|
+
inputTokens: this.inputTokens,
|
|
104
|
+
outputTokens: this.outputTokens,
|
|
105
|
+
cachedInputTokens: this.cachedInputTokens,
|
|
106
|
+
...this.cacheWriteTokens > 0 ? { cacheWriteTokens: this.cacheWriteTokens } : {}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* The model every contributing session used, when they all agree (and it
|
|
111
|
+
* is a real model id, not the "unknown" bucket). Undefined for multi-model
|
|
112
|
+
* runs and for runs whose sessions never named one — the caller then
|
|
113
|
+
* leaves the model field to its own resolution.
|
|
114
|
+
*/
|
|
115
|
+
soleModel() {
|
|
116
|
+
if (this.perModel.size !== 1)
|
|
117
|
+
return void 0;
|
|
118
|
+
const [key] = this.perModel.keys();
|
|
119
|
+
return key === "unknown" ? void 0 : key;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Aggregate the recorded usage into the cloud's per-(provider, model)
|
|
123
|
+
* cost_breakdown shape — the same aggregation agentic-scanner's
|
|
124
|
+
* emitScanCompleted applies to its per-stage tally, sourced here from the
|
|
125
|
+
* true cross-session cumulative. Zero-usage buckets are skipped so the
|
|
126
|
+
* breakdown stays honest. Returns null when nothing was recorded at all,
|
|
127
|
+
* so callers omit cost_usd / cost_breakdown rather than emit a
|
|
128
|
+
* misleading $0.
|
|
129
|
+
*/
|
|
130
|
+
costBreakdown() {
|
|
131
|
+
const acc = /* @__PURE__ */ new Map();
|
|
132
|
+
for (const [model, usage] of this.perModel) {
|
|
133
|
+
if (usage.inputTokens === 0 && usage.outputTokens === 0 && usage.cachedInputTokens === 0 && usage.cacheWriteTokens === 0) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const priced = model === "unknown" ? void 0 : model;
|
|
137
|
+
const provider = modelProvider(priced);
|
|
138
|
+
const key = `${provider}${model}`;
|
|
139
|
+
const split = splitCost(usage, priced);
|
|
140
|
+
const existing = acc.get(key);
|
|
141
|
+
if (existing) {
|
|
142
|
+
existing.cost_in += split.cost_in;
|
|
143
|
+
existing.cost_out += split.cost_out;
|
|
144
|
+
if (split.cost_cache_read !== void 0) {
|
|
145
|
+
existing.cost_cache_read = (existing.cost_cache_read ?? 0) + split.cost_cache_read;
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
acc.set(key, {
|
|
149
|
+
provider,
|
|
150
|
+
model,
|
|
151
|
+
cost_in: split.cost_in,
|
|
152
|
+
cost_out: split.cost_out,
|
|
153
|
+
...split.cost_cache_read !== void 0 ? { cost_cache_read: split.cost_cache_read } : {}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (acc.size === 0)
|
|
158
|
+
return null;
|
|
159
|
+
const breakdown = Array.from(acc.values());
|
|
160
|
+
const costUsd = breakdown.reduce((sum, e) => sum + e.cost_in + e.cost_out + (e.cost_cache_read ?? 0), 0);
|
|
161
|
+
return { costUsd, breakdown };
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
// packages/core/dist/scan-plan.js
|
|
166
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
167
|
+
var executionBudget = new AsyncLocalStorage();
|
|
168
|
+
function scanExecutionSignal() {
|
|
169
|
+
return executionBudget.getStore()?.signal;
|
|
170
|
+
}
|
|
171
|
+
function scanExecutionTimeout(requestedMs) {
|
|
172
|
+
const budget = executionBudget.getStore();
|
|
173
|
+
if (!budget)
|
|
174
|
+
return requestedMs;
|
|
175
|
+
budget.signal.throwIfAborted();
|
|
176
|
+
const remainingMs = budget.deadline - Date.now();
|
|
177
|
+
if (remainingMs <= 0)
|
|
178
|
+
throw new ScanBudgetError("time_cap_exceeded", "Scan plan wall-clock limit reached before dispatch.");
|
|
179
|
+
return Math.min(requestedMs, remainingMs);
|
|
180
|
+
}
|
|
181
|
+
var ScanBudgetError = class extends Error {
|
|
182
|
+
status;
|
|
183
|
+
constructor(status, message) {
|
|
184
|
+
super(message);
|
|
185
|
+
this.status = status;
|
|
186
|
+
this.name = "ScanBudgetError";
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
function scanGoalPrompt(plan) {
|
|
190
|
+
if (!plan)
|
|
191
|
+
return "";
|
|
192
|
+
const objective = plan.goal === "known-vulnerabilities" ? "Investigate known vulnerabilities: identify exact component versions, match published advisories/CVEs, and reproduce applicability. Prioritize existing advisory and dependency-audit leads; do not spend this run on speculative novel vulnerability hunting." : plan.goal === "misconfigurations" ? "Investigate security misconfigurations: prioritize exposed services/debug features, unsafe defaults, authentication/authorization configuration, secrets, permissions and deployment trust boundaries. Demonstrate the configured exposure rather than claiming hypothetical vulnerabilities." : "Investigate unknown vulnerabilities: prioritize previously unreported logic, authorization, injection and memory-safety flaws. Use known advisories as context, but do not treat a known CVE match alone as completion of this objective.";
|
|
193
|
+
const depth = plan.depth === "quick" ? "Use a focused quick pass: highest-risk surfaces first, minimal non-destructive probes and one independent reproduction per credible lead." : plan.depth === "default" ? "Use a standard pass: cover the relevant attack surfaces and independently reproduce credible leads before reporting." : "Use a deep pass: trace trust boundaries and adjacent variants, investigate alternate paths and independently reproduce credible leads within the shared limits.";
|
|
194
|
+
return `
|
|
195
|
+
|
|
196
|
+
## Operator scan execution policy
|
|
197
|
+
${objective}
|
|
198
|
+
${depth}
|
|
199
|
+
The shared wall-clock and spend limits include discovery, investigation, verification, reporting and all descendants. Preserve partial evidence when a limit interrupts work.
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
function budgetNativeRuntime(runtime, ledger, signal, ceiling, plan) {
|
|
203
|
+
return {
|
|
204
|
+
type: runtime.type,
|
|
205
|
+
outputTokenLimit: runtime.outputTokenLimit,
|
|
206
|
+
isAvailable: () => runtime.isAvailable(),
|
|
207
|
+
resolvedModel: runtime.resolvedModel?.bind(runtime),
|
|
208
|
+
resolvedProvider: runtime.resolvedProvider?.bind(runtime),
|
|
209
|
+
resolvedPricingModel: runtime.resolvedPricingModel?.bind(runtime),
|
|
210
|
+
accessibleModels: runtime.accessibleModels?.bind(runtime),
|
|
211
|
+
reconfigure: runtime.reconfigure?.bind(runtime),
|
|
212
|
+
forkForSubagent: runtime.forkForSubagent ? async (timeout, selection) => budgetNativeRuntime(await runtime.forkForSubagent(timeout, selection), ledger, signal, ceiling, plan) : void 0,
|
|
213
|
+
async executeNative(system, messages, tools, callbacks, callSignal) {
|
|
214
|
+
const effectiveSignal = signal && callSignal ? AbortSignal.any([signal, callSignal]) : signal ?? callSignal;
|
|
215
|
+
effectiveSignal?.throwIfAborted();
|
|
216
|
+
if (ceiling !== void 0 && ledger.totalCostUsd() >= ceiling) {
|
|
217
|
+
throw new ScanBudgetError("cost_ceiling_exceeded", "Shared scan cost ceiling exhausted before model dispatch.");
|
|
218
|
+
}
|
|
219
|
+
let reportedUsage;
|
|
220
|
+
const result = await runtime.executeNative(system + scanGoalPrompt(plan), messages, tools, {
|
|
221
|
+
...callbacks,
|
|
222
|
+
onUsage: (usage) => {
|
|
223
|
+
reportedUsage = usage;
|
|
224
|
+
callbacks?.onUsage?.(usage);
|
|
225
|
+
}
|
|
226
|
+
}, effectiveSignal);
|
|
227
|
+
if (!result.usage && reportedUsage)
|
|
228
|
+
result.usage = reportedUsage;
|
|
229
|
+
addRuntimeUsage(ledger, result, runtime.resolvedPricingModel?.() ?? runtime.resolvedModel?.());
|
|
230
|
+
if (plan && !result.usage) {
|
|
231
|
+
ledger.markUnpricedUsage();
|
|
232
|
+
throw new ScanBudgetError("failed", "Runtime did not report usage; cannot safely continue a cost-bounded scan plan.");
|
|
233
|
+
}
|
|
234
|
+
return result;
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
async function executeScanPlan(options) {
|
|
239
|
+
const { plan } = options;
|
|
240
|
+
validateScanPlan(plan);
|
|
241
|
+
const ledger = (options.ledger ?? new ScanCostLedger()).fork();
|
|
242
|
+
const ceiling = Math.min(options.costCeilingUsd ?? plan.costCapUsd, plan.costCapUsd);
|
|
243
|
+
const startedAt = Date.now();
|
|
244
|
+
const controller = new AbortController();
|
|
245
|
+
const signal = options.signal ? AbortSignal.any([controller.signal, options.signal]) : controller.signal;
|
|
246
|
+
const timer = setTimeout(() => controller.abort(new ScanBudgetError("time_cap_exceeded", "Scan plan wall-clock limit reached.")), plan.timeCapMs);
|
|
247
|
+
const unsubscribe = ledger.onSpend(() => {
|
|
248
|
+
if (ledger.hasUnpricedUsage() && !controller.signal.aborted) {
|
|
249
|
+
controller.abort(new ScanBudgetError("failed", "Runtime did not report usage; plan spend is unknown."));
|
|
250
|
+
}
|
|
251
|
+
if (ledger.totalCostUsd() >= ceiling && !controller.signal.aborted) {
|
|
252
|
+
controller.abort(new ScanBudgetError("cost_ceiling_exceeded", "Scan plan shared cost ceiling reached."));
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
const attempts = Array.from({ length: plan.runCount }, (_, index) => ({
|
|
256
|
+
runIndex: index + 1,
|
|
257
|
+
status: "rejected",
|
|
258
|
+
durationMs: 0,
|
|
259
|
+
costUsd: 0
|
|
260
|
+
}));
|
|
261
|
+
const reports = new Array(plan.runCount);
|
|
262
|
+
let next = 0;
|
|
263
|
+
const stopStatus = () => {
|
|
264
|
+
if (ledger.totalCostUsd() >= ceiling)
|
|
265
|
+
return "cost_ceiling_exceeded";
|
|
266
|
+
if (ledger.hasUnpricedUsage())
|
|
267
|
+
return "failed";
|
|
268
|
+
if (Date.now() - startedAt >= plan.timeCapMs)
|
|
269
|
+
return "time_cap_exceeded";
|
|
270
|
+
if (signal.aborted)
|
|
271
|
+
return signal.reason instanceof ScanBudgetError ? signal.reason.status : "cancelled";
|
|
272
|
+
return void 0;
|
|
273
|
+
};
|
|
274
|
+
const worker = async () => {
|
|
275
|
+
while (next < plan.runCount) {
|
|
276
|
+
const index = next++;
|
|
277
|
+
const attempt = attempts[index];
|
|
278
|
+
const stopped = stopStatus();
|
|
279
|
+
if (stopped) {
|
|
280
|
+
attempt.status = stopped;
|
|
281
|
+
attempt.error = "Attempt not dispatched: shared scan plan limit or cancellation.";
|
|
282
|
+
options.onAttempt?.({ runIndex: attempt.runIndex, phase: "settled", outcome: { ...attempt } });
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const runLedger = ledger.fork();
|
|
286
|
+
const runStartedAt = Date.now();
|
|
287
|
+
const remainingMs = plan.timeCapMs - (runStartedAt - startedAt);
|
|
288
|
+
options.onAttempt?.({ runIndex: attempt.runIndex, phase: "started" });
|
|
289
|
+
try {
|
|
290
|
+
const report = await executionBudget.run({ signal, deadline: startedAt + plan.timeCapMs }, () => options.dispatch({ runIndex: index + 1, plan: { ...plan, runCount: 1, timeCapMs: remainingMs }, ledger: runLedger, signal, costCeilingUsd: ceiling }));
|
|
291
|
+
reports[index] = report;
|
|
292
|
+
attempt.status = report.costCeilingExceeded ? "cost_ceiling_exceeded" : report.exitReason === "time_cap_exceeded" ? "time_cap_exceeded" : report.exitReason === "cancelled" ? "cancelled" : report.researchFailed || report.executionSuccessful === false || report.exitReason === "failed" ? "failed" : stopStatus() ?? "completed";
|
|
293
|
+
if (attempt.status !== "completed")
|
|
294
|
+
attempt.error = report.error;
|
|
295
|
+
} catch (error) {
|
|
296
|
+
attempt.status = error instanceof ScanBudgetError ? error.status : stopStatus() ?? "rejected";
|
|
297
|
+
attempt.error = error instanceof Error ? error.message : String(error);
|
|
298
|
+
} finally {
|
|
299
|
+
attempt.durationMs = Date.now() - runStartedAt;
|
|
300
|
+
attempt.costUsd = runLedger.hasUnpricedUsage() ? void 0 : runLedger.runCostUsd();
|
|
301
|
+
options.onAttempt?.({ runIndex: attempt.runIndex, phase: "settled", outcome: { ...attempt } });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
try {
|
|
306
|
+
const concurrency = plan.executionMode === "parallel" ? Math.min(3, plan.runCount) : 1;
|
|
307
|
+
await Promise.all(Array.from({ length: concurrency }, worker));
|
|
308
|
+
const fulfilled = reports.filter((report2) => report2 !== void 0);
|
|
309
|
+
const report = { ...fulfilled[0] ?? options.emptyReport() };
|
|
310
|
+
report.findings = reports.flatMap((current, index) => current?.findings.map((finding) => ({ ...finding, runIndex: index + 1 })) ?? []);
|
|
311
|
+
const keys = ["totalAttacks", "totalFindings", "critical", "high", "medium", "low", "info"];
|
|
312
|
+
report.summary = Object.fromEntries(keys.map((key) => [key, fulfilled.reduce((sum, current) => sum + current.summary[key], 0)]));
|
|
313
|
+
const incomplete = attempts.filter((attempt) => attempt.status !== "completed");
|
|
314
|
+
report.warnings = [...fulfilled.flatMap((current) => current.warnings), ...incomplete.map((attempt) => ({ stage: "report", message: `Run ${attempt.runIndex}/${plan.runCount}: ${attempt.status}${attempt.error ? ` \u2014 ${attempt.error}` : ""}. Findings are partial.` }))];
|
|
315
|
+
report.startedAt = new Date(startedAt).toISOString();
|
|
316
|
+
report.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
317
|
+
report.durationMs = Date.now() - startedAt;
|
|
318
|
+
report.plan = plan;
|
|
319
|
+
report.plannedRuns = plan.runCount;
|
|
320
|
+
report.completedRuns = attempts.filter((attempt) => attempt.status === "completed").length;
|
|
321
|
+
report.attempts = attempts;
|
|
322
|
+
report.estimatedCostUsd = ledger.hasUnpricedUsage() ? void 0 : ledger.runCostUsd();
|
|
323
|
+
report.usage = ledger.tokenUsage();
|
|
324
|
+
report.executionSuccessful = incomplete.length === 0;
|
|
325
|
+
report.costCeilingExceeded = attempts.some((attempt) => attempt.status === "cost_ceiling_exceeded");
|
|
326
|
+
report.researchFailed = attempts.some((attempt) => attempt.status === "failed" || attempt.status === "rejected");
|
|
327
|
+
report.error = incomplete.length ? incomplete.find((attempt) => attempt.error)?.error ?? `Scan plan interrupted: ${incomplete[0].status}.` : void 0;
|
|
328
|
+
report.exitReason = report.costCeilingExceeded ? "cost_ceiling_exceeded" : attempts.some((attempt) => attempt.status === "time_cap_exceeded") ? "time_cap_exceeded" : attempts.some((attempt) => attempt.status === "cancelled") ? "cancelled" : incomplete.length ? "partial" : "completed";
|
|
329
|
+
if (options.emitTerminalEvent !== false) {
|
|
330
|
+
const cost = ledger.hasUnpricedUsage() ? null : ledger.costBreakdown();
|
|
331
|
+
eventBus.emit("scan_completed", {
|
|
332
|
+
exit_reason: report.costCeilingExceeded ? "cost_exceeded" : incomplete.length ? "failed" : "completed",
|
|
333
|
+
duration_ms: report.durationMs,
|
|
334
|
+
findings_count: report.findings.length,
|
|
335
|
+
summary: `Scan plan ${report.completedRuns}/${plan.runCount} completed; ${report.exitReason}.`,
|
|
336
|
+
...cost ? { cost_usd: cost.costUsd, cost_breakdown: cost.breakdown } : {}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
return report;
|
|
340
|
+
} finally {
|
|
341
|
+
clearTimeout(timer);
|
|
342
|
+
unsubscribe();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export {
|
|
347
|
+
addRuntimeUsage,
|
|
348
|
+
ScanCostLedger,
|
|
349
|
+
scanExecutionSignal,
|
|
350
|
+
scanExecutionTimeout,
|
|
351
|
+
ScanBudgetError,
|
|
352
|
+
scanGoalPrompt,
|
|
353
|
+
budgetNativeRuntime,
|
|
354
|
+
executeScanPlan
|
|
355
|
+
};
|
|
@@ -3,7 +3,7 @@ import { createRequire as __0CreateRequire } from "node:module";
|
|
|
3
3
|
const require = __0CreateRequire(import.meta.url);
|
|
4
4
|
import {
|
|
5
5
|
homeStateDir
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-6KFB6AYN.js";
|
|
7
7
|
|
|
8
8
|
// packages/core/dist/agent/journal/types.js
|
|
9
9
|
var JOURNAL_SCHEMA_VERSION = 1;
|
|
@@ -5,9 +5,15 @@ import {
|
|
|
5
5
|
CodexCatalogRefreshError,
|
|
6
6
|
loadCodexModelCatalog,
|
|
7
7
|
parseCodexModels
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
10
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-G2BE2FC7.js";
|
|
9
|
+
import "./chunk-QVIRWJ45.js";
|
|
10
|
+
import "./chunk-2WWAPKAW.js";
|
|
11
|
+
import "./chunk-RS4MAN3Q.js";
|
|
12
|
+
import "./chunk-UELXHK2Y.js";
|
|
13
|
+
import "./chunk-4ZYYX5GY.js";
|
|
14
|
+
import "./chunk-C6JPA32H.js";
|
|
15
|
+
import "./chunk-5IBCBQEH.js";
|
|
16
|
+
import "./chunk-6KFB6AYN.js";
|
|
11
17
|
import "./chunk-VIT5ALXF.js";
|
|
12
18
|
import "./chunk-VCSFCHAJ.js";
|
|
13
19
|
export {
|