@kyo-so/cli 0.10.0 → 0.11.0
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/.agents/skills/kyoso-review/SKILL.md +13 -3
- package/CHANGELOG.md +22 -0
- package/README.ja.md +25 -8
- package/README.md +25 -8
- package/README.zh-CN.md +25 -8
- package/dist/acp/FakeAgentManager.d.ts +1 -1
- package/dist/bin/kyoso.js +1344 -119
- package/dist/cli/knownSkillDigests.d.ts +1 -1
- package/dist/cli/pluginRuntimeContract.d.ts +10 -8
- package/dist/config/schema.d.ts +7 -0
- package/dist/core/constants.d.ts +3 -1
- package/dist/core/requestFingerprint.d.ts +11 -0
- package/dist/core/reviewBudget.d.ts +68 -0
- package/dist/core/tokenUsage.d.ts +2 -0
- package/dist/core/types.d.ts +70 -0
- package/dist/core/verification.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1306 -91
- package/dist/judge/anthropic.d.ts +2 -2
- package/dist/judge/openai.d.ts +2 -2
- package/dist/judge/provider.d.ts +7 -1
- package/dist/mcp/schemas.d.ts +7 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -183779,6 +183779,14 @@ function date4(params) {
|
|
|
183779
183779
|
|
|
183780
183780
|
// node_modules/zod/v4/classic/external.js
|
|
183781
183781
|
config(en_default());
|
|
183782
|
+
// src/core/constants.ts
|
|
183783
|
+
var DEFAULT_AGENT_TIMEOUT_MS = 120000;
|
|
183784
|
+
var MAX_AGENT_OUTPUT_BYTES = 1048576;
|
|
183785
|
+
var JUDGE_MAX_OUTPUT_TOKENS = 4096;
|
|
183786
|
+
var RAW_OUTPUT_MAX_CHARS = 16384;
|
|
183787
|
+
var TRACE_DIR = ".kyoso/traces";
|
|
183788
|
+
var KYOSO_CHILD_AGENT = "KYOSO_CHILD_AGENT";
|
|
183789
|
+
|
|
183782
183790
|
// src/config/schema.ts
|
|
183783
183791
|
var CODEX_OPENROUTER_PROVIDER = "openrouter";
|
|
183784
183792
|
var CODEX_DEFAULT_PROVIDER = "default";
|
|
@@ -183823,6 +183831,13 @@ var codexAgentSchema = baseAgentSchema.extend({
|
|
|
183823
183831
|
}
|
|
183824
183832
|
});
|
|
183825
183833
|
});
|
|
183834
|
+
var reviewBudgetSchema = exports_external.object({
|
|
183835
|
+
maxModelCalls: exports_external.number().int().positive(),
|
|
183836
|
+
maxTotalWallTimeMs: exports_external.number().int().positive(),
|
|
183837
|
+
maxAgentOutputBytes: exports_external.number().int().positive().max(MAX_AGENT_OUTPUT_BYTES),
|
|
183838
|
+
maxFindingsPerAgent: exports_external.number().int().positive(),
|
|
183839
|
+
skipOptionalPhasesWhenTokenUsageUnknown: exports_external.boolean()
|
|
183840
|
+
});
|
|
183826
183841
|
var kyosoConfigSchema = exports_external.object({
|
|
183827
183842
|
entrypoints: exports_external.object({
|
|
183828
183843
|
mcp: exports_external.boolean(),
|
|
@@ -183880,6 +183895,7 @@ var kyosoConfigSchema = exports_external.object({
|
|
|
183880
183895
|
timeoutMs: exports_external.number().int().positive().default(90000),
|
|
183881
183896
|
allowDemotion: exports_external.boolean().default(false)
|
|
183882
183897
|
}),
|
|
183898
|
+
reviewBudget: reviewBudgetSchema,
|
|
183883
183899
|
audit: exports_external.object({
|
|
183884
183900
|
enabled: exports_external.boolean(),
|
|
183885
183901
|
format: exports_external.literal("jsonl"),
|
|
@@ -183887,6 +183903,15 @@ var kyosoConfigSchema = exports_external.object({
|
|
|
183887
183903
|
includeRawAgentOutput: exports_external.boolean(),
|
|
183888
183904
|
includeFileContents: exports_external.boolean()
|
|
183889
183905
|
})
|
|
183906
|
+
}).superRefine((config2, context) => {
|
|
183907
|
+
const enabledPrimaryReviewers = Object.values(config2.agents).filter((agent) => agent.enabled).length;
|
|
183908
|
+
if (config2.reviewBudget.maxModelCalls >= enabledPrimaryReviewers)
|
|
183909
|
+
return;
|
|
183910
|
+
context.addIssue({
|
|
183911
|
+
code: exports_external.ZodIssueCode.custom,
|
|
183912
|
+
path: ["reviewBudget", "maxModelCalls"],
|
|
183913
|
+
message: "must be greater than or equal to the number of enabled primary reviewers."
|
|
183914
|
+
});
|
|
183890
183915
|
});
|
|
183891
183916
|
function agentConfigLeafPaths(agent) {
|
|
183892
183917
|
const paths = [
|
|
@@ -183945,6 +183970,11 @@ var kyosoConfigKnownLeafPaths = [
|
|
|
183945
183970
|
"verification.maxFindings",
|
|
183946
183971
|
"verification.timeoutMs",
|
|
183947
183972
|
"verification.allowDemotion",
|
|
183973
|
+
"reviewBudget.maxModelCalls",
|
|
183974
|
+
"reviewBudget.maxTotalWallTimeMs",
|
|
183975
|
+
"reviewBudget.maxAgentOutputBytes",
|
|
183976
|
+
"reviewBudget.maxFindingsPerAgent",
|
|
183977
|
+
"reviewBudget.skipOptionalPhasesWhenTokenUsageUnknown",
|
|
183948
183978
|
"audit.enabled",
|
|
183949
183979
|
"audit.format",
|
|
183950
183980
|
"audit.directory",
|
|
@@ -183964,6 +183994,7 @@ var kyosoConfigSecuritySensitivePrefixes = [
|
|
|
183964
183994
|
"secrets",
|
|
183965
183995
|
"securityReview",
|
|
183966
183996
|
"verification",
|
|
183997
|
+
"reviewBudget",
|
|
183967
183998
|
"workspace"
|
|
183968
183999
|
];
|
|
183969
184000
|
|
|
@@ -184077,7 +184108,7 @@ var defaultConfig = {
|
|
|
184077
184108
|
}
|
|
184078
184109
|
},
|
|
184079
184110
|
judge: {
|
|
184080
|
-
mode: "
|
|
184111
|
+
mode: "deterministic_only",
|
|
184081
184112
|
provider: "auto",
|
|
184082
184113
|
timeoutMs: 60000
|
|
184083
184114
|
},
|
|
@@ -184087,6 +184118,13 @@ var defaultConfig = {
|
|
|
184087
184118
|
timeoutMs: 90000,
|
|
184088
184119
|
allowDemotion: false
|
|
184089
184120
|
},
|
|
184121
|
+
reviewBudget: {
|
|
184122
|
+
maxModelCalls: 4,
|
|
184123
|
+
maxTotalWallTimeMs: 480000,
|
|
184124
|
+
maxAgentOutputBytes: 65536,
|
|
184125
|
+
maxFindingsPerAgent: 10,
|
|
184126
|
+
skipOptionalPhasesWhenTokenUsageUnknown: true
|
|
184127
|
+
},
|
|
184090
184128
|
audit: {
|
|
184091
184129
|
enabled: true,
|
|
184092
184130
|
format: "jsonl",
|
|
@@ -184151,7 +184189,7 @@ function collectProjectScopeViolations(config2) {
|
|
|
184151
184189
|
const violations = [];
|
|
184152
184190
|
for (const leaf of leaves) {
|
|
184153
184191
|
const path = leaf.path.join(".");
|
|
184154
|
-
const globalOnlyReason =
|
|
184192
|
+
const globalOnlyReason = projectGlobalOnlyReason(leaf.path);
|
|
184155
184193
|
if (globalOnlyReason) {
|
|
184156
184194
|
violations.push({ path, reason: globalOnlyReason });
|
|
184157
184195
|
continue;
|
|
@@ -184166,6 +184204,15 @@ function collectProjectScopeViolations(config2) {
|
|
|
184166
184204
|
}
|
|
184167
184205
|
return violations.sort((left, right) => left.path.localeCompare(right.path));
|
|
184168
184206
|
}
|
|
184207
|
+
function projectGlobalOnlyReason(path) {
|
|
184208
|
+
const exactReason = PROJECT_GLOBAL_ONLY_REASONS[path.join(".")];
|
|
184209
|
+
if (exactReason)
|
|
184210
|
+
return exactReason;
|
|
184211
|
+
if (path[0] === "reviewBudget") {
|
|
184212
|
+
return "must be a user-global review budget ceiling";
|
|
184213
|
+
}
|
|
184214
|
+
return;
|
|
184215
|
+
}
|
|
184169
184216
|
function isAllowedProjectPath(path) {
|
|
184170
184217
|
const [top, second, third, fourth] = path;
|
|
184171
184218
|
if (isAllowedConfigOverridePath(path))
|
|
@@ -184296,12 +184343,6 @@ function isRecord(value) {
|
|
|
184296
184343
|
// src/security/redact.ts
|
|
184297
184344
|
var REDACTION = "[KYOSO_REDACTED]";
|
|
184298
184345
|
|
|
184299
|
-
// src/core/constants.ts
|
|
184300
|
-
var DEFAULT_AGENT_TIMEOUT_MS = 120000;
|
|
184301
|
-
var RAW_OUTPUT_MAX_CHARS = 16384;
|
|
184302
|
-
var TRACE_DIR = ".kyoso/traces";
|
|
184303
|
-
var KYOSO_CHILD_AGENT = "KYOSO_CHILD_AGENT";
|
|
184304
|
-
|
|
184305
184346
|
// src/security/sanitizeText.ts
|
|
184306
184347
|
var SENSITIVE_TEXT_PATTERNS = [
|
|
184307
184348
|
/\bsk-(?:proj-)?[A-Za-z0-9_-]{8,}\b/g,
|
|
@@ -189791,6 +189832,33 @@ var legacyClientNotificationMethods = new Set([
|
|
|
189791
189832
|
CLIENT_METHODS.elicitation_complete
|
|
189792
189833
|
]);
|
|
189793
189834
|
|
|
189835
|
+
// src/core/tokenUsage.ts
|
|
189836
|
+
var TOKEN_USAGE_KEYS = [
|
|
189837
|
+
"totalTokens",
|
|
189838
|
+
"inputTokens",
|
|
189839
|
+
"outputTokens",
|
|
189840
|
+
"thoughtTokens",
|
|
189841
|
+
"cachedReadTokens",
|
|
189842
|
+
"cachedWriteTokens"
|
|
189843
|
+
];
|
|
189844
|
+
function normalizeModelTokenUsage(usage) {
|
|
189845
|
+
if (!isRecord6(usage))
|
|
189846
|
+
return;
|
|
189847
|
+
const normalized = {};
|
|
189848
|
+
for (const key of TOKEN_USAGE_KEYS) {
|
|
189849
|
+
const value = usage[key];
|
|
189850
|
+
if (isTokenCount(value))
|
|
189851
|
+
normalized[key] = value;
|
|
189852
|
+
}
|
|
189853
|
+
return Object.keys(normalized).length > 0 ? normalized : undefined;
|
|
189854
|
+
}
|
|
189855
|
+
function isTokenCount(value) {
|
|
189856
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
189857
|
+
}
|
|
189858
|
+
function isRecord6(value) {
|
|
189859
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
189860
|
+
}
|
|
189861
|
+
|
|
189794
189862
|
// src/utils/env.ts
|
|
189795
189863
|
import { stderr as stderr2 } from "node:process";
|
|
189796
189864
|
var MINIMAL_ENV_KEYS = [
|
|
@@ -190136,7 +190204,7 @@ function isSeverity(value) {
|
|
|
190136
190204
|
return typeof value === "string" && severities.includes(value);
|
|
190137
190205
|
}
|
|
190138
190206
|
function normalizeCisaSecureByDesign(value) {
|
|
190139
|
-
if (!
|
|
190207
|
+
if (!isRecord7(value))
|
|
190140
190208
|
return;
|
|
190141
190209
|
const normalized = {};
|
|
190142
190210
|
const customerSecurityOutcomes = normalizeGateStatus(value.customerSecurityOutcomes);
|
|
@@ -190177,7 +190245,7 @@ function normalizeFindingFiles(value) {
|
|
|
190177
190245
|
if (!Array.isArray(value))
|
|
190178
190246
|
return;
|
|
190179
190247
|
const files = value.flatMap((item) => {
|
|
190180
|
-
if (!
|
|
190248
|
+
if (!isRecord7(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
|
|
190181
190249
|
return [];
|
|
190182
190250
|
}
|
|
190183
190251
|
const file2 = {
|
|
@@ -190196,7 +190264,7 @@ function normalizeFindingFiles(value) {
|
|
|
190196
190264
|
function normalizeLineNumber(value) {
|
|
190197
190265
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
190198
190266
|
}
|
|
190199
|
-
function
|
|
190267
|
+
function isRecord7(value) {
|
|
190200
190268
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
190201
190269
|
}
|
|
190202
190270
|
|
|
@@ -190256,6 +190324,20 @@ class SubprocessAcpAgentManager extends BaseAcpAgentManager {
|
|
|
190256
190324
|
}
|
|
190257
190325
|
async function runSubprocessAgent(agent, agentConfig, input, env) {
|
|
190258
190326
|
const startedAt = new Date().toISOString();
|
|
190327
|
+
const effectiveTimeoutMs = resolveEffectiveTimeoutMs(input);
|
|
190328
|
+
if (effectiveTimeoutMs <= 0) {
|
|
190329
|
+
return {
|
|
190330
|
+
agent,
|
|
190331
|
+
role: input.role,
|
|
190332
|
+
status: "timeout",
|
|
190333
|
+
startedAt,
|
|
190334
|
+
completedAt: startedAt,
|
|
190335
|
+
error: {
|
|
190336
|
+
code: "REVIEW_DEADLINE_EXCEEDED",
|
|
190337
|
+
message: "Review deadline was reached before the agent could start."
|
|
190338
|
+
}
|
|
190339
|
+
};
|
|
190340
|
+
}
|
|
190259
190341
|
return new Promise((resolveResult) => {
|
|
190260
190342
|
const child = spawn(agentConfig.command, agentConfig.args, {
|
|
190261
190343
|
cwd: input.workspaceDir,
|
|
@@ -190284,6 +190366,7 @@ async function runSubprocessAgent(agent, agentConfig, input, env) {
|
|
|
190284
190366
|
const timeout = setTimeout(() => {
|
|
190285
190367
|
abortController.abort(new Error("Kyoso agent timeout"));
|
|
190286
190368
|
terminateChild(child);
|
|
190369
|
+
const deadlineReached = input.deadlineAtEpochMs !== undefined && Date.now() >= input.deadlineAtEpochMs;
|
|
190287
190370
|
resolveOnce({
|
|
190288
190371
|
agent,
|
|
190289
190372
|
role: input.role,
|
|
@@ -190291,11 +190374,11 @@ async function runSubprocessAgent(agent, agentConfig, input, env) {
|
|
|
190291
190374
|
startedAt,
|
|
190292
190375
|
completedAt: new Date().toISOString(),
|
|
190293
190376
|
error: {
|
|
190294
|
-
code: "AGENT_TIMEOUT",
|
|
190295
|
-
message: `Agent timed out after ${
|
|
190377
|
+
code: deadlineReached ? "REVIEW_DEADLINE_EXCEEDED" : "AGENT_TIMEOUT",
|
|
190378
|
+
message: deadlineReached ? "Review deadline reached before the agent completed." : `Agent timed out after ${effectiveTimeoutMs}ms`
|
|
190296
190379
|
}
|
|
190297
190380
|
});
|
|
190298
|
-
},
|
|
190381
|
+
}, effectiveTimeoutMs);
|
|
190299
190382
|
child.stderr.on("data", (chunk) => {
|
|
190300
190383
|
stderr3 += chunk.toString("utf8");
|
|
190301
190384
|
});
|
|
@@ -190311,19 +190394,48 @@ async function runSubprocessAgent(agent, agentConfig, input, env) {
|
|
|
190311
190394
|
error: failure
|
|
190312
190395
|
});
|
|
190313
190396
|
});
|
|
190314
|
-
runAcpClientWorkflow(child, input, abortController
|
|
190397
|
+
runAcpClientWorkflow(child, input, abortController, resolveEffortConfigOption(agent, agentConfig.effort)).then(({ rawText, warnings, usage, outputBytes, stopReason }) => {
|
|
190315
190398
|
stdout = rawText;
|
|
190399
|
+
const completed = stopReason === "end_turn";
|
|
190316
190400
|
resolveOnce({
|
|
190317
190401
|
agent,
|
|
190318
190402
|
role: input.role,
|
|
190319
|
-
status: "completed",
|
|
190403
|
+
status: completed ? "completed" : "failed",
|
|
190320
190404
|
rawText,
|
|
190321
190405
|
normalized: normalizeAgentOutput(agent, input.role, rawText),
|
|
190322
190406
|
startedAt,
|
|
190323
190407
|
completedAt: new Date().toISOString(),
|
|
190324
|
-
|
|
190408
|
+
outputBytes,
|
|
190409
|
+
stopReason,
|
|
190410
|
+
...usage ? { usage } : {},
|
|
190411
|
+
...warnings.length > 0 ? { warnings } : {},
|
|
190412
|
+
...completed ? {} : {
|
|
190413
|
+
error: {
|
|
190414
|
+
code: "AGENT_STOPPED_EARLY",
|
|
190415
|
+
message: `Agent stopped before completing the review: ${stopReason}.`
|
|
190416
|
+
}
|
|
190417
|
+
}
|
|
190325
190418
|
});
|
|
190326
190419
|
}).catch((error51) => {
|
|
190420
|
+
const outputLimitError = findOutputLimitError(error51, abortController);
|
|
190421
|
+
if (outputLimitError) {
|
|
190422
|
+
stdout = outputLimitError.rawText;
|
|
190423
|
+
resolveOnce({
|
|
190424
|
+
agent,
|
|
190425
|
+
role: input.role,
|
|
190426
|
+
status: "failed",
|
|
190427
|
+
rawText: stdout,
|
|
190428
|
+
outputBytes: outputLimitError.outputBytes,
|
|
190429
|
+
stopReason: "cancelled",
|
|
190430
|
+
startedAt,
|
|
190431
|
+
completedAt: new Date().toISOString(),
|
|
190432
|
+
error: {
|
|
190433
|
+
code: "AGENT_OUTPUT_LIMIT",
|
|
190434
|
+
message: `Agent output exceeded ${outputLimitError.maxOutputBytes} bytes and was cancelled.`
|
|
190435
|
+
}
|
|
190436
|
+
});
|
|
190437
|
+
return;
|
|
190438
|
+
}
|
|
190327
190439
|
if (abortController.signal.aborted)
|
|
190328
190440
|
return;
|
|
190329
190441
|
const failureText = [stderr3, formatAgentErrorDetail(error51)].filter((part) => part.trim().length > 0).join(`
|
|
@@ -190356,7 +190468,7 @@ async function runSubprocessAgent(agent, agentConfig, input, env) {
|
|
|
190356
190468
|
});
|
|
190357
190469
|
});
|
|
190358
190470
|
}
|
|
190359
|
-
async function runAcpClientWorkflow(child, input,
|
|
190471
|
+
async function runAcpClientWorkflow(child, input, abortController, configOption) {
|
|
190360
190472
|
if (!child.stdin || !child.stdout) {
|
|
190361
190473
|
throw new Error("Agent process did not expose stdio streams.");
|
|
190362
190474
|
}
|
|
@@ -190406,8 +190518,8 @@ async function runAcpClientWorkflow(child, input, signal, configOption) {
|
|
|
190406
190518
|
}).withSession(async (session) => {
|
|
190407
190519
|
const warnings = [];
|
|
190408
190520
|
if (configOption) {
|
|
190409
|
-
await ctx.request(methods.agent.session.setConfigOption, { sessionId: session.sessionId, ...configOption }, { cancellationSignal: signal }).catch((error51) => {
|
|
190410
|
-
if (signal.aborted)
|
|
190521
|
+
await ctx.request(methods.agent.session.setConfigOption, { sessionId: session.sessionId, ...configOption }, { cancellationSignal: abortController.signal }).catch((error51) => {
|
|
190522
|
+
if (abortController.signal.aborted)
|
|
190411
190523
|
return;
|
|
190412
190524
|
const sanitizedValue = sanitizeTextForDisplay(configOption.value);
|
|
190413
190525
|
const detail = sanitizeTextForDisplay(formatAgentErrorDetail(error51));
|
|
@@ -190417,14 +190529,75 @@ async function runAcpClientWorkflow(child, input, signal, configOption) {
|
|
|
190417
190529
|
});
|
|
190418
190530
|
}
|
|
190419
190531
|
const promptResponse = session.prompt(input.prompt, {
|
|
190420
|
-
cancellationSignal: signal
|
|
190532
|
+
cancellationSignal: abortController.signal
|
|
190421
190533
|
});
|
|
190422
|
-
|
|
190423
|
-
|
|
190424
|
-
|
|
190534
|
+
promptResponse.catch(() => {
|
|
190535
|
+
return;
|
|
190536
|
+
});
|
|
190537
|
+
let rawText = "";
|
|
190538
|
+
let outputBytes = 0;
|
|
190539
|
+
for (;; ) {
|
|
190540
|
+
const message = await session.nextUpdate();
|
|
190541
|
+
if (message.kind === "stop") {
|
|
190542
|
+
const usage = normalizeUsage(message.response.usage);
|
|
190543
|
+
return {
|
|
190544
|
+
rawText,
|
|
190545
|
+
warnings,
|
|
190546
|
+
...usage ? { usage } : {},
|
|
190547
|
+
outputBytes,
|
|
190548
|
+
stopReason: message.stopReason
|
|
190549
|
+
};
|
|
190550
|
+
}
|
|
190551
|
+
const update = message.update;
|
|
190552
|
+
if (update.sessionUpdate !== "agent_message_chunk" && update.sessionUpdate !== "agent_thought_chunk" || update.content.type !== "text") {
|
|
190553
|
+
continue;
|
|
190554
|
+
}
|
|
190555
|
+
const chunkBytes = Buffer.byteLength(update.content.text, "utf8");
|
|
190556
|
+
const nextOutputBytes = outputBytes + chunkBytes;
|
|
190557
|
+
if (input.maxOutputBytes !== undefined && nextOutputBytes > input.maxOutputBytes) {
|
|
190558
|
+
await ctx.notify(methods.agent.session.cancel, {
|
|
190559
|
+
sessionId: session.sessionId
|
|
190560
|
+
}).catch(() => {
|
|
190561
|
+
return;
|
|
190562
|
+
});
|
|
190563
|
+
const error51 = new AgentOutputLimitError(rawText, nextOutputBytes, input.maxOutputBytes);
|
|
190564
|
+
abortController.abort(error51);
|
|
190565
|
+
throw error51;
|
|
190566
|
+
}
|
|
190567
|
+
if (update.sessionUpdate === "agent_message_chunk") {
|
|
190568
|
+
rawText += update.content.text;
|
|
190569
|
+
}
|
|
190570
|
+
outputBytes = nextOutputBytes;
|
|
190571
|
+
}
|
|
190425
190572
|
});
|
|
190426
190573
|
});
|
|
190427
190574
|
}
|
|
190575
|
+
|
|
190576
|
+
class AgentOutputLimitError extends Error {
|
|
190577
|
+
rawText;
|
|
190578
|
+
outputBytes;
|
|
190579
|
+
maxOutputBytes;
|
|
190580
|
+
constructor(rawText, outputBytes, maxOutputBytes) {
|
|
190581
|
+
super(`Agent output exceeded ${maxOutputBytes} bytes.`);
|
|
190582
|
+
this.rawText = rawText;
|
|
190583
|
+
this.outputBytes = outputBytes;
|
|
190584
|
+
this.maxOutputBytes = maxOutputBytes;
|
|
190585
|
+
this.name = "AgentOutputLimitError";
|
|
190586
|
+
}
|
|
190587
|
+
}
|
|
190588
|
+
function findOutputLimitError(error51, abortController) {
|
|
190589
|
+
if (error51 instanceof AgentOutputLimitError)
|
|
190590
|
+
return error51;
|
|
190591
|
+
const reason = abortController.signal.reason;
|
|
190592
|
+
return reason instanceof AgentOutputLimitError ? reason : undefined;
|
|
190593
|
+
}
|
|
190594
|
+
function resolveEffectiveTimeoutMs(input) {
|
|
190595
|
+
const deadlineRemaining = input.deadlineAtEpochMs === undefined ? Number.POSITIVE_INFINITY : input.deadlineAtEpochMs - Date.now();
|
|
190596
|
+
return Math.max(0, Math.min(input.timeoutMs, deadlineRemaining));
|
|
190597
|
+
}
|
|
190598
|
+
function normalizeUsage(usage) {
|
|
190599
|
+
return normalizeModelTokenUsage(usage);
|
|
190600
|
+
}
|
|
190428
190601
|
function resolveEffortConfigOption(agent, effort) {
|
|
190429
190602
|
if (!effort)
|
|
190430
190603
|
return;
|
|
@@ -190646,9 +190819,10 @@ ${JSON.stringify(opinion)}
|
|
|
190646
190819
|
role: input.role,
|
|
190647
190820
|
status: "completed",
|
|
190648
190821
|
rawText,
|
|
190649
|
-
normalized: scenario === "success" ? opinion : undefined,
|
|
190822
|
+
normalized: scenario === "success" || scenario === "unknown_usage" ? opinion : undefined,
|
|
190650
190823
|
startedAt,
|
|
190651
|
-
completedAt: new Date().toISOString()
|
|
190824
|
+
completedAt: new Date().toISOString(),
|
|
190825
|
+
...scenario === "unknown_usage" ? {} : { usage: fakeUsage() }
|
|
190652
190826
|
};
|
|
190653
190827
|
}
|
|
190654
190828
|
}
|
|
@@ -190682,9 +190856,13 @@ function verifierResult(input, startedAt, scenario) {
|
|
|
190682
190856
|
status: "completed",
|
|
190683
190857
|
rawText,
|
|
190684
190858
|
startedAt,
|
|
190685
|
-
completedAt: new Date().toISOString()
|
|
190859
|
+
completedAt: new Date().toISOString(),
|
|
190860
|
+
usage: fakeUsage()
|
|
190686
190861
|
};
|
|
190687
190862
|
}
|
|
190863
|
+
function fakeUsage() {
|
|
190864
|
+
return { totalTokens: 20, inputTokens: 12, outputTokens: 8 };
|
|
190865
|
+
}
|
|
190688
190866
|
function findingIdsFromPrompt(prompt) {
|
|
190689
190867
|
return Array.from(prompt.matchAll(/^Finding ID: (.+)$/gm)).map((match) => match[1] ?? "");
|
|
190690
190868
|
}
|
|
@@ -191607,6 +191785,16 @@ async function optionalLstat2(path) {
|
|
|
191607
191785
|
}
|
|
191608
191786
|
|
|
191609
191787
|
// src/audit/sanitize.ts
|
|
191788
|
+
var USAGE_METADATA_KEYS = new Set([
|
|
191789
|
+
"tokenUsage",
|
|
191790
|
+
"totalTokens",
|
|
191791
|
+
"inputTokens",
|
|
191792
|
+
"outputTokens",
|
|
191793
|
+
"thoughtTokens",
|
|
191794
|
+
"cachedReadTokens",
|
|
191795
|
+
"cachedWriteTokens",
|
|
191796
|
+
"skipOptionalPhasesWhenTokenUsageUnknown"
|
|
191797
|
+
]);
|
|
191610
191798
|
function sanitizeForAudit(value, options = {}) {
|
|
191611
191799
|
if (typeof value === "string")
|
|
191612
191800
|
return sanitizeText(value);
|
|
@@ -191615,7 +191803,7 @@ function sanitizeForAudit(value, options = {}) {
|
|
|
191615
191803
|
if (typeof value === "object" && value !== null) {
|
|
191616
191804
|
const result = {};
|
|
191617
191805
|
for (const [key, nested] of Object.entries(value)) {
|
|
191618
|
-
if (/raw|content|env|credential|token|secret|password/i.test(key) && !(options.includeRawAgentOutput && key === "rawText")) {
|
|
191806
|
+
if (/raw|content|env|credential|token|secret|password/i.test(key) && !(options.includeRawAgentOutput && key === "rawText") && !isUsageMetadata(key, nested)) {
|
|
191619
191807
|
continue;
|
|
191620
191808
|
}
|
|
191621
191809
|
result[key] = sanitizeForAudit(nested, options);
|
|
@@ -191624,6 +191812,17 @@ function sanitizeForAudit(value, options = {}) {
|
|
|
191624
191812
|
}
|
|
191625
191813
|
return value;
|
|
191626
191814
|
}
|
|
191815
|
+
function isUsageMetadata(key, value) {
|
|
191816
|
+
if (!USAGE_METADATA_KEYS.has(key))
|
|
191817
|
+
return false;
|
|
191818
|
+
if (key === "tokenUsage") {
|
|
191819
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
191820
|
+
}
|
|
191821
|
+
if (key === "skipOptionalPhasesWhenTokenUsageUnknown") {
|
|
191822
|
+
return typeof value === "boolean";
|
|
191823
|
+
}
|
|
191824
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
191825
|
+
}
|
|
191627
191826
|
|
|
191628
191827
|
// src/audit/trace.ts
|
|
191629
191828
|
var AUDIT_WARNING_WRITE_FAILED = "AUDIT_WRITE_FAILED: Audit trace writing failed; no further audit events will be written.";
|
|
@@ -191889,6 +192088,8 @@ function renderMarkdownResult(tool, result, options = {}) {
|
|
|
191889
192088
|
"",
|
|
191890
192089
|
`**Decision:** ${result.decision}`,
|
|
191891
192090
|
`**Mode:** ${tool}`,
|
|
192091
|
+
`**Completion:** ${formatCompletion(result)}`,
|
|
192092
|
+
`**Request fingerprint:** ${shortFingerprint(result.requestFingerprint)}`,
|
|
191892
192093
|
`**Agents:** ${result.agentOpinions.map((opinion) => `${title(opinion.agent)} ${opinion.status}`).join(", ")}`,
|
|
191893
192094
|
`**Review mode:** ${formatReviewMode(result)}`,
|
|
191894
192095
|
...result.verificationMode ? [
|
|
@@ -191900,6 +192101,7 @@ function renderMarkdownResult(tool, result, options = {}) {
|
|
|
191900
192101
|
"",
|
|
191901
192102
|
options.summaryText ?? defaultSummaryText(result)
|
|
191902
192103
|
];
|
|
192104
|
+
lines.push(...formatExecutionBudget(result));
|
|
191903
192105
|
if (result.cisaSecureByDesign) {
|
|
191904
192106
|
lines.push("", "## CISA Secure by Design Gate", "", "| Dimension | Status | Notes |", "|---|---|---|", `| Customer Security Outcomes | ${result.cisaSecureByDesign.customerSecurityOutcomes} | ${notes(result.cisaSecureByDesign.notes)} |`, `| Secure by Default | ${result.cisaSecureByDesign.secureByDefault} | ${notes(result.cisaSecureByDesign.notes)} |`, `| Transparency & Accountability | ${result.cisaSecureByDesign.transparencyAndAccountability} | ${notes(result.cisaSecureByDesign.notes)} |`, `| Governance | ${result.cisaSecureByDesign.governance} | ${notes(result.cisaSecureByDesign.notes)} |`);
|
|
191905
192107
|
}
|
|
@@ -191952,8 +192154,37 @@ function renderMarkdownResult(tool, result, options = {}) {
|
|
|
191952
192154
|
`);
|
|
191953
192155
|
}
|
|
191954
192156
|
function defaultSummaryText(result) {
|
|
192157
|
+
if (result.completion.status === "incomplete") {
|
|
192158
|
+
const reasons = result.completion.reasons.length > 0 ? result.completion.reasons.join(", ") : "unspecified coverage gap";
|
|
192159
|
+
return `Review incomplete (${reasons}). Decision is block because review coverage is incomplete, not because a code finding was established.`;
|
|
192160
|
+
}
|
|
191955
192161
|
return result.findings.length === 0 ? "No blocking findings were detected from the supplied context." : `${result.findings.length} finding(s) require attention.`;
|
|
191956
192162
|
}
|
|
192163
|
+
function formatExecutionBudget(result) {
|
|
192164
|
+
const budget = result.executionBudget;
|
|
192165
|
+
const agentOutputs = Object.entries(budget.agentOutputBytes);
|
|
192166
|
+
const outputLines = agentOutputs.length > 0 ? agentOutputs.map(([agent, bytes]) => `- ${title(agent)}: ${bytes} bytes`) : ["- None reported."];
|
|
192167
|
+
const totalTokens = budget.tokenUsage.totals.totalTokens;
|
|
192168
|
+
return [
|
|
192169
|
+
"",
|
|
192170
|
+
"## Execution Budget",
|
|
192171
|
+
"",
|
|
192172
|
+
`- Model calls: ${budget.modelCalls.planned} planned / ${budget.modelCalls.consumed} consumed / ${budget.modelCalls.skipped} skipped`,
|
|
192173
|
+
`- Wall time: ${budget.wallTime.consumedMs}ms consumed / ${budget.wallTime.limitMs}ms limit`,
|
|
192174
|
+
`- Token usage: ${budget.tokenUsage.status} (${budget.tokenUsage.reportedCalls} reported, ${budget.tokenUsage.unknownCalls} unknown${totalTokens === undefined ? "" : `, ${totalTokens} total`})`,
|
|
192175
|
+
"- Agent output:",
|
|
192176
|
+
...outputLines
|
|
192177
|
+
];
|
|
192178
|
+
}
|
|
192179
|
+
function formatCompletion(result) {
|
|
192180
|
+
if (result.completion.status === "complete")
|
|
192181
|
+
return "complete";
|
|
192182
|
+
const reasons = result.completion.reasons.join(", ") || "unspecified";
|
|
192183
|
+
return `incomplete (${reasons}; retryable=${String(result.completion.retryable)})`;
|
|
192184
|
+
}
|
|
192185
|
+
function shortFingerprint(value) {
|
|
192186
|
+
return value.length <= 20 ? value : `${value.slice(0, 20)}…`;
|
|
192187
|
+
}
|
|
191957
192188
|
function title(value) {
|
|
191958
192189
|
return value.slice(0, 1).toUpperCase() + value.slice(1);
|
|
191959
192190
|
}
|
|
@@ -192042,7 +192273,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
|
|
|
192042
192273
|
const parsed = JSON.parse(json2);
|
|
192043
192274
|
const summaryText = typeof parsed.summaryText === "string" && parsed.summaryText.trim().length > 0 ? sanitizeText(parsed.summaryText) : fallbackSummaryText;
|
|
192044
192275
|
const disagreementComments = Array.isArray(parsed.disagreementComments) ? parsed.disagreementComments.flatMap((item) => {
|
|
192045
|
-
if (!
|
|
192276
|
+
if (!isRecord8(item) || typeof item.topic !== "string" || typeof item.judgeComment !== "string") {
|
|
192046
192277
|
return [];
|
|
192047
192278
|
}
|
|
192048
192279
|
return [
|
|
@@ -192058,7 +192289,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
|
|
|
192058
192289
|
return { summaryText, disagreementComments, analysis };
|
|
192059
192290
|
}
|
|
192060
192291
|
function parseAnalysis(value) {
|
|
192061
|
-
if (!
|
|
192292
|
+
if (!isRecord8(value))
|
|
192062
192293
|
return;
|
|
192063
192294
|
if (!Array.isArray(value.blindSpots) || !Array.isArray(value.contradictions) || !Array.isArray(value.partialCoverage)) {
|
|
192064
192295
|
return;
|
|
@@ -192066,7 +192297,7 @@ function parseAnalysis(value) {
|
|
|
192066
192297
|
return {
|
|
192067
192298
|
blindSpots: value.blindSpots.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => typeof item === "string" ? [sanitizeAnalysisText(item)] : []),
|
|
192068
192299
|
contradictions: value.contradictions.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
|
|
192069
|
-
if (!
|
|
192300
|
+
if (!isRecord8(item) || typeof item.topic !== "string" || typeof item.detail !== "string") {
|
|
192070
192301
|
return [];
|
|
192071
192302
|
}
|
|
192072
192303
|
return [
|
|
@@ -192077,7 +192308,7 @@ function parseAnalysis(value) {
|
|
|
192077
192308
|
];
|
|
192078
192309
|
}),
|
|
192079
192310
|
partialCoverage: value.partialCoverage.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
|
|
192080
|
-
if (!
|
|
192311
|
+
if (!isRecord8(item) || typeof item.note !== "string")
|
|
192081
192312
|
return [];
|
|
192082
192313
|
const findingId = typeof item.findingId === "string" ? sanitizeAnalysisText(item.findingId) : undefined;
|
|
192083
192314
|
return [
|
|
@@ -192124,7 +192355,7 @@ function extractFirstJsonObject2(text) {
|
|
|
192124
192355
|
}
|
|
192125
192356
|
return;
|
|
192126
192357
|
}
|
|
192127
|
-
function
|
|
192358
|
+
function isRecord8(value) {
|
|
192128
192359
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
192129
192360
|
}
|
|
192130
192361
|
|
|
@@ -192142,7 +192373,7 @@ async function runAnthropicJudge(input, timeoutMs) {
|
|
|
192142
192373
|
},
|
|
192143
192374
|
body: JSON.stringify({
|
|
192144
192375
|
model: input.env.KYOSO_ANTHROPIC_JUDGE_MODEL ?? "claude-haiku-4-5",
|
|
192145
|
-
max_tokens:
|
|
192376
|
+
max_tokens: JUDGE_MAX_OUTPUT_TOKENS,
|
|
192146
192377
|
temperature: 0,
|
|
192147
192378
|
messages: [
|
|
192148
192379
|
{
|
|
@@ -192158,7 +192389,21 @@ async function runAnthropicJudge(input, timeoutMs) {
|
|
|
192158
192389
|
const content = payload.content?.find((item) => item.type === "text" && item.text)?.text;
|
|
192159
192390
|
if (!content)
|
|
192160
192391
|
throw new Error("Anthropic judge response did not include text content.");
|
|
192161
|
-
|
|
192392
|
+
const usage = normalizeUsage2(payload.usage);
|
|
192393
|
+
return {
|
|
192394
|
+
output: parseJudgeOutput(content, input.summaryText),
|
|
192395
|
+
...usage ? { usage } : {}
|
|
192396
|
+
};
|
|
192397
|
+
}
|
|
192398
|
+
function normalizeUsage2(usage) {
|
|
192399
|
+
if (!usage)
|
|
192400
|
+
return;
|
|
192401
|
+
return normalizeModelTokenUsage({
|
|
192402
|
+
inputTokens: usage.input_tokens,
|
|
192403
|
+
outputTokens: usage.output_tokens,
|
|
192404
|
+
cachedReadTokens: usage.cache_read_input_tokens,
|
|
192405
|
+
cachedWriteTokens: usage.cache_creation_input_tokens
|
|
192406
|
+
});
|
|
192162
192407
|
}
|
|
192163
192408
|
async function fetchWithTimeout(url2, init, timeoutMs) {
|
|
192164
192409
|
const controller = new AbortController;
|
|
@@ -192202,6 +192447,7 @@ async function runOpenAiJudge(input, timeoutMs) {
|
|
|
192202
192447
|
content: buildJudgePrompt(input.tool, input.result, input.summaryText, input.agentFindings)
|
|
192203
192448
|
}
|
|
192204
192449
|
],
|
|
192450
|
+
max_completion_tokens: JUDGE_MAX_OUTPUT_TOKENS,
|
|
192205
192451
|
temperature: 0
|
|
192206
192452
|
})
|
|
192207
192453
|
}, timeoutMs);
|
|
@@ -192211,7 +192457,22 @@ async function runOpenAiJudge(input, timeoutMs) {
|
|
|
192211
192457
|
const content = payload.choices?.[0]?.message?.content;
|
|
192212
192458
|
if (!content)
|
|
192213
192459
|
throw new Error("OpenAI judge response did not include content.");
|
|
192214
|
-
|
|
192460
|
+
const usage = normalizeUsage3(payload.usage);
|
|
192461
|
+
return {
|
|
192462
|
+
output: parseJudgeOutput(content, input.summaryText),
|
|
192463
|
+
...usage ? { usage } : {}
|
|
192464
|
+
};
|
|
192465
|
+
}
|
|
192466
|
+
function normalizeUsage3(usage) {
|
|
192467
|
+
if (!usage)
|
|
192468
|
+
return;
|
|
192469
|
+
return normalizeModelTokenUsage({
|
|
192470
|
+
totalTokens: usage.total_tokens,
|
|
192471
|
+
inputTokens: usage.prompt_tokens,
|
|
192472
|
+
outputTokens: usage.completion_tokens,
|
|
192473
|
+
cachedReadTokens: usage.prompt_tokens_details?.cached_tokens,
|
|
192474
|
+
thoughtTokens: usage.completion_tokens_details?.reasoning_tokens
|
|
192475
|
+
});
|
|
192215
192476
|
}
|
|
192216
192477
|
async function fetchWithTimeout2(url2, init, timeoutMs) {
|
|
192217
192478
|
const controller = new AbortController;
|
|
@@ -192253,8 +192514,13 @@ async function runJudge(input) {
|
|
|
192253
192514
|
return { provider, status: "deterministic_fallback", output: fallback };
|
|
192254
192515
|
}
|
|
192255
192516
|
try {
|
|
192256
|
-
const output = provider === "openai" ? await runOpenAiJudge(input, input.config.timeoutMs) : await runAnthropicJudge(input, input.config.timeoutMs);
|
|
192257
|
-
return {
|
|
192517
|
+
const output = provider === "openai" ? await runOpenAiJudge(input, input.timeoutMs ?? input.config.timeoutMs) : await runAnthropicJudge(input, input.timeoutMs ?? input.config.timeoutMs);
|
|
192518
|
+
return {
|
|
192519
|
+
provider,
|
|
192520
|
+
status: "completed",
|
|
192521
|
+
output: output.output,
|
|
192522
|
+
...output.usage ? { usage: output.usage } : {}
|
|
192523
|
+
};
|
|
192258
192524
|
} catch (error51) {
|
|
192259
192525
|
return {
|
|
192260
192526
|
provider,
|
|
@@ -192527,6 +192793,304 @@ function newTraceId() {
|
|
|
192527
192793
|
return `tr_${randomUUID()}`;
|
|
192528
192794
|
}
|
|
192529
192795
|
|
|
192796
|
+
// src/core/requestFingerprint.ts
|
|
192797
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
192798
|
+
var REVIEW_CONTRACT_VERSION = "2026-07-15-v1";
|
|
192799
|
+
function createRequestFingerprint(input) {
|
|
192800
|
+
const reviewers = ["codex", "claude"].filter((agent) => input.config.agents[agent].enabled).map((agent) => ({
|
|
192801
|
+
agent,
|
|
192802
|
+
role: input.roles[agent] ?? input.config.agents[agent].role,
|
|
192803
|
+
model: input.config.agents[agent].model ?? null,
|
|
192804
|
+
provider: agent === "codex" ? input.config.agents.codex.provider ?? "default" : "default"
|
|
192805
|
+
}));
|
|
192806
|
+
const request = structuredClone(input.request);
|
|
192807
|
+
if (request.options)
|
|
192808
|
+
delete request.options.includeAgentRawOutputs;
|
|
192809
|
+
const payload = {
|
|
192810
|
+
reviewContractVersion: REVIEW_CONTRACT_VERSION,
|
|
192811
|
+
tool: input.tool,
|
|
192812
|
+
request,
|
|
192813
|
+
reviewers,
|
|
192814
|
+
verification: input.config.verification,
|
|
192815
|
+
judge: {
|
|
192816
|
+
...input.config.judge,
|
|
192817
|
+
requestedProvider: input.request.options?.judgeProvider ?? null
|
|
192818
|
+
},
|
|
192819
|
+
executionBudget: input.budget
|
|
192820
|
+
};
|
|
192821
|
+
return `sha256:${createHash3("sha256").update(canonicalJson(payload), "utf8").digest("hex")}`;
|
|
192822
|
+
}
|
|
192823
|
+
function canonicalJson(value) {
|
|
192824
|
+
return JSON.stringify(canonicalize(value));
|
|
192825
|
+
}
|
|
192826
|
+
function canonicalize(value) {
|
|
192827
|
+
if (Array.isArray(value))
|
|
192828
|
+
return value.map(canonicalize);
|
|
192829
|
+
if (!isRecord9(value))
|
|
192830
|
+
return value;
|
|
192831
|
+
return Object.fromEntries(Object.entries(value).filter(([, child]) => child !== undefined).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, canonicalize(child)]));
|
|
192832
|
+
}
|
|
192833
|
+
function isRecord9(value) {
|
|
192834
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
192835
|
+
}
|
|
192836
|
+
|
|
192837
|
+
// src/core/reviewBudget.ts
|
|
192838
|
+
var REVIEW_BUDGET_KEYS = new Set([
|
|
192839
|
+
"maxModelCalls",
|
|
192840
|
+
"maxTotalWallTimeMs",
|
|
192841
|
+
"maxAgentOutputBytes",
|
|
192842
|
+
"maxFindingsPerAgent",
|
|
192843
|
+
"skipOptionalPhasesWhenTokenUsageUnknown"
|
|
192844
|
+
]);
|
|
192845
|
+
var MODEL_CALL_KINDS = ["primary", "verifier", "judge"];
|
|
192846
|
+
function resolveReviewBudget(ceiling, requested) {
|
|
192847
|
+
if (requested === undefined)
|
|
192848
|
+
return ceiling;
|
|
192849
|
+
if (!isRecord10(requested)) {
|
|
192850
|
+
throw new KyosoRequestError("options.reviewBudget must be an object.", "REVIEW_BUDGET_INVALID");
|
|
192851
|
+
}
|
|
192852
|
+
for (const [key, value] of Object.entries(requested)) {
|
|
192853
|
+
if (!REVIEW_BUDGET_KEYS.has(key)) {
|
|
192854
|
+
throw new KyosoRequestError(`options.reviewBudget.${key} is not supported.`, "REVIEW_BUDGET_INVALID");
|
|
192855
|
+
}
|
|
192856
|
+
if (key === "skipOptionalPhasesWhenTokenUsageUnknown") {
|
|
192857
|
+
if (typeof value !== "boolean") {
|
|
192858
|
+
throw new KyosoRequestError(`options.reviewBudget.${key} must be a boolean.`, "REVIEW_BUDGET_INVALID");
|
|
192859
|
+
}
|
|
192860
|
+
continue;
|
|
192861
|
+
}
|
|
192862
|
+
if (!isPositiveInteger(value)) {
|
|
192863
|
+
throw new KyosoRequestError(`options.reviewBudget.${key} must be a positive integer.`, "REVIEW_BUDGET_INVALID");
|
|
192864
|
+
}
|
|
192865
|
+
}
|
|
192866
|
+
const numericKeys = [
|
|
192867
|
+
"maxModelCalls",
|
|
192868
|
+
"maxTotalWallTimeMs",
|
|
192869
|
+
"maxAgentOutputBytes",
|
|
192870
|
+
"maxFindingsPerAgent"
|
|
192871
|
+
];
|
|
192872
|
+
for (const key of numericKeys) {
|
|
192873
|
+
const value = requested[key];
|
|
192874
|
+
if (value === undefined)
|
|
192875
|
+
continue;
|
|
192876
|
+
if (value > ceiling[key]) {
|
|
192877
|
+
throw new KyosoRequestError(`options.reviewBudget.${key} cannot exceed the user-global ceiling.`, "REVIEW_BUDGET_EXCEEDS_CEILING");
|
|
192878
|
+
}
|
|
192879
|
+
}
|
|
192880
|
+
if (ceiling.skipOptionalPhasesWhenTokenUsageUnknown && requested.skipOptionalPhasesWhenTokenUsageUnknown === false) {
|
|
192881
|
+
throw new KyosoRequestError("options.reviewBudget.skipOptionalPhasesWhenTokenUsageUnknown cannot relax the user-global ceiling.", "REVIEW_BUDGET_EXCEEDS_CEILING");
|
|
192882
|
+
}
|
|
192883
|
+
return {
|
|
192884
|
+
maxModelCalls: requested.maxModelCalls ?? ceiling.maxModelCalls,
|
|
192885
|
+
maxTotalWallTimeMs: requested.maxTotalWallTimeMs ?? ceiling.maxTotalWallTimeMs,
|
|
192886
|
+
maxAgentOutputBytes: requested.maxAgentOutputBytes ?? ceiling.maxAgentOutputBytes,
|
|
192887
|
+
maxFindingsPerAgent: requested.maxFindingsPerAgent ?? ceiling.maxFindingsPerAgent,
|
|
192888
|
+
skipOptionalPhasesWhenTokenUsageUnknown: ceiling.skipOptionalPhasesWhenTokenUsageUnknown || requested.skipOptionalPhasesWhenTokenUsageUnknown === true
|
|
192889
|
+
};
|
|
192890
|
+
}
|
|
192891
|
+
|
|
192892
|
+
class ReviewBudgetTracker {
|
|
192893
|
+
budget;
|
|
192894
|
+
startedAtEpochMs;
|
|
192895
|
+
deadlineAtEpochMs;
|
|
192896
|
+
reservations = new Map;
|
|
192897
|
+
skippedCalls = [];
|
|
192898
|
+
incompleteReasons = new Set;
|
|
192899
|
+
nextReservationId = 1;
|
|
192900
|
+
constructor(budget, startedAtEpochMs = Date.now()) {
|
|
192901
|
+
this.budget = budget;
|
|
192902
|
+
this.startedAtEpochMs = startedAtEpochMs;
|
|
192903
|
+
this.deadlineAtEpochMs = startedAtEpochMs + budget.maxTotalWallTimeMs;
|
|
192904
|
+
}
|
|
192905
|
+
remainingWallTimeMs(now = Date.now()) {
|
|
192906
|
+
return Math.max(0, this.deadlineAtEpochMs - now);
|
|
192907
|
+
}
|
|
192908
|
+
hasDeadlineExpired(now = Date.now()) {
|
|
192909
|
+
return this.remainingWallTimeMs(now) === 0;
|
|
192910
|
+
}
|
|
192911
|
+
reserveMany(inputs) {
|
|
192912
|
+
if (this.hasDeadlineExpired())
|
|
192913
|
+
return { failure: { reason: "deadline" } };
|
|
192914
|
+
if (this.usedCapacity() + inputs.length > this.budget.maxModelCalls) {
|
|
192915
|
+
return { failure: { reason: "model_call_budget" } };
|
|
192916
|
+
}
|
|
192917
|
+
const reservations = inputs.map((input) => {
|
|
192918
|
+
const reservation = {
|
|
192919
|
+
id: this.nextReservationId,
|
|
192920
|
+
kind: input.kind,
|
|
192921
|
+
...input.agent ? { agent: input.agent } : {},
|
|
192922
|
+
status: "reserved"
|
|
192923
|
+
};
|
|
192924
|
+
this.reservations.set(reservation.id, reservation);
|
|
192925
|
+
this.nextReservationId += 1;
|
|
192926
|
+
return reservation;
|
|
192927
|
+
});
|
|
192928
|
+
return {
|
|
192929
|
+
reservations: reservations.map(({ id, kind, agent }) => ({
|
|
192930
|
+
id,
|
|
192931
|
+
kind,
|
|
192932
|
+
...agent ? { agent } : {}
|
|
192933
|
+
}))
|
|
192934
|
+
};
|
|
192935
|
+
}
|
|
192936
|
+
reserve(input) {
|
|
192937
|
+
const result = this.reserveMany([input]);
|
|
192938
|
+
if ("failure" in result)
|
|
192939
|
+
return result;
|
|
192940
|
+
const reservation = result.reservations[0];
|
|
192941
|
+
if (!reservation) {
|
|
192942
|
+
return { failure: { reason: "model_call_budget" } };
|
|
192943
|
+
}
|
|
192944
|
+
return { reservation };
|
|
192945
|
+
}
|
|
192946
|
+
markStarted(reservation) {
|
|
192947
|
+
const current = this.reservations.get(reservation.id);
|
|
192948
|
+
if (!current || current.status !== "reserved")
|
|
192949
|
+
return;
|
|
192950
|
+
current.status = "started";
|
|
192951
|
+
}
|
|
192952
|
+
hasStarted(reservation) {
|
|
192953
|
+
const current = this.reservations.get(reservation.id);
|
|
192954
|
+
return current?.status === "started" || current?.status === "completed";
|
|
192955
|
+
}
|
|
192956
|
+
complete(reservation, values = {}) {
|
|
192957
|
+
const current = this.reservations.get(reservation.id);
|
|
192958
|
+
if (!current || current.status === "skipped" || current.status === "completed") {
|
|
192959
|
+
return;
|
|
192960
|
+
}
|
|
192961
|
+
current.status = "completed";
|
|
192962
|
+
current.outputBytes = values.outputBytes;
|
|
192963
|
+
current.usage = normalizeModelTokenUsage(values.usage);
|
|
192964
|
+
current.stopReason = values.stopReason;
|
|
192965
|
+
}
|
|
192966
|
+
skip(reservation, reason) {
|
|
192967
|
+
const current = this.reservations.get(reservation.id);
|
|
192968
|
+
if (!current || current.status !== "reserved")
|
|
192969
|
+
return;
|
|
192970
|
+
current.status = "skipped";
|
|
192971
|
+
current.reason = reason;
|
|
192972
|
+
}
|
|
192973
|
+
recordSkipped(input) {
|
|
192974
|
+
this.skippedCalls.push({
|
|
192975
|
+
kind: input.kind,
|
|
192976
|
+
...input.agent ? { agent: input.agent } : {},
|
|
192977
|
+
status: "skipped",
|
|
192978
|
+
reason: input.reason
|
|
192979
|
+
});
|
|
192980
|
+
}
|
|
192981
|
+
markIncomplete(reason) {
|
|
192982
|
+
this.incompleteReasons.add(reason);
|
|
192983
|
+
}
|
|
192984
|
+
isTokenUsageUnknown() {
|
|
192985
|
+
return Array.from(this.reservations.values()).some((reservation) => reservation.status === "completed" && reservation.usage === undefined);
|
|
192986
|
+
}
|
|
192987
|
+
snapshot(now = Date.now()) {
|
|
192988
|
+
const calls = this.modelCalls();
|
|
192989
|
+
const byKind = Object.fromEntries(MODEL_CALL_KINDS.map((kind) => [
|
|
192990
|
+
kind,
|
|
192991
|
+
{ planned: 0, consumed: 0, skipped: 0 }
|
|
192992
|
+
]));
|
|
192993
|
+
let planned = 0;
|
|
192994
|
+
let consumed = 0;
|
|
192995
|
+
let skipped = 0;
|
|
192996
|
+
const agentOutputBytes = {};
|
|
192997
|
+
const usageTotals = {};
|
|
192998
|
+
let reportedCalls = 0;
|
|
192999
|
+
let unknownCalls = 0;
|
|
193000
|
+
for (const reservation of this.reservations.values()) {
|
|
193001
|
+
planned += 1;
|
|
193002
|
+
byKind[reservation.kind].planned += 1;
|
|
193003
|
+
if (reservation.status === "completed") {
|
|
193004
|
+
consumed += 1;
|
|
193005
|
+
byKind[reservation.kind].consumed += 1;
|
|
193006
|
+
if (reservation.agent && reservation.outputBytes !== undefined) {
|
|
193007
|
+
agentOutputBytes[reservation.agent] = (agentOutputBytes[reservation.agent] ?? 0) + reservation.outputBytes;
|
|
193008
|
+
}
|
|
193009
|
+
if (reservation.usage) {
|
|
193010
|
+
reportedCalls += 1;
|
|
193011
|
+
addUsage(usageTotals, reservation.usage);
|
|
193012
|
+
} else {
|
|
193013
|
+
unknownCalls += 1;
|
|
193014
|
+
}
|
|
193015
|
+
}
|
|
193016
|
+
if (reservation.status === "skipped") {
|
|
193017
|
+
skipped += 1;
|
|
193018
|
+
byKind[reservation.kind].skipped += 1;
|
|
193019
|
+
}
|
|
193020
|
+
}
|
|
193021
|
+
for (const call of this.skippedCalls) {
|
|
193022
|
+
skipped += 1;
|
|
193023
|
+
byKind[call.kind].skipped += 1;
|
|
193024
|
+
}
|
|
193025
|
+
const tokenStatus = consumed === 0 || reportedCalls === 0 ? "unknown" : unknownCalls === 0 ? "reported" : "partial";
|
|
193026
|
+
const completionReasons = Array.from(this.incompleteReasons).sort();
|
|
193027
|
+
const consumedMs = Math.max(0, now - this.startedAtEpochMs);
|
|
193028
|
+
return {
|
|
193029
|
+
completion: {
|
|
193030
|
+
status: completionReasons.length > 0 ? "incomplete" : "complete",
|
|
193031
|
+
reasons: completionReasons,
|
|
193032
|
+
retryable: false
|
|
193033
|
+
},
|
|
193034
|
+
executionBudget: {
|
|
193035
|
+
maxModelCalls: this.budget.maxModelCalls,
|
|
193036
|
+
modelCalls: { planned, consumed, skipped, byKind },
|
|
193037
|
+
wallTime: {
|
|
193038
|
+
limitMs: this.budget.maxTotalWallTimeMs,
|
|
193039
|
+
consumedMs,
|
|
193040
|
+
remainingMs: this.remainingWallTimeMs(now)
|
|
193041
|
+
},
|
|
193042
|
+
maxAgentOutputBytes: this.budget.maxAgentOutputBytes,
|
|
193043
|
+
maxFindingsPerAgent: this.budget.maxFindingsPerAgent,
|
|
193044
|
+
skipOptionalPhasesWhenTokenUsageUnknown: this.budget.skipOptionalPhasesWhenTokenUsageUnknown,
|
|
193045
|
+
agentOutputBytes,
|
|
193046
|
+
tokenUsage: {
|
|
193047
|
+
status: tokenStatus,
|
|
193048
|
+
reportedCalls,
|
|
193049
|
+
unknownCalls,
|
|
193050
|
+
totals: usageTotals
|
|
193051
|
+
}
|
|
193052
|
+
},
|
|
193053
|
+
modelCalls: calls
|
|
193054
|
+
};
|
|
193055
|
+
}
|
|
193056
|
+
usedCapacity() {
|
|
193057
|
+
return Array.from(this.reservations.values()).filter((reservation) => reservation.status === "reserved" || reservation.status === "started" || reservation.status === "completed").length;
|
|
193058
|
+
}
|
|
193059
|
+
modelCalls() {
|
|
193060
|
+
const reservations = Array.from(this.reservations.values()).filter((reservation) => reservation.status === "completed" || reservation.status === "skipped").map((reservation) => ({
|
|
193061
|
+
kind: reservation.kind,
|
|
193062
|
+
...reservation.agent ? { agent: reservation.agent } : {},
|
|
193063
|
+
status: reservation.status === "completed" ? "completed" : "skipped",
|
|
193064
|
+
...reservation.reason ? { reason: reservation.reason } : {},
|
|
193065
|
+
...reservation.outputBytes !== undefined ? { outputBytes: reservation.outputBytes } : {},
|
|
193066
|
+
...reservation.usage ? { usage: reservation.usage } : {},
|
|
193067
|
+
...reservation.stopReason ? { stopReason: reservation.stopReason } : {}
|
|
193068
|
+
}));
|
|
193069
|
+
return [...reservations, ...this.skippedCalls];
|
|
193070
|
+
}
|
|
193071
|
+
}
|
|
193072
|
+
function addUsage(total, usage) {
|
|
193073
|
+
for (const key of [
|
|
193074
|
+
"totalTokens",
|
|
193075
|
+
"inputTokens",
|
|
193076
|
+
"outputTokens",
|
|
193077
|
+
"thoughtTokens",
|
|
193078
|
+
"cachedReadTokens",
|
|
193079
|
+
"cachedWriteTokens"
|
|
193080
|
+
]) {
|
|
193081
|
+
const value = usage[key];
|
|
193082
|
+
if (value === undefined)
|
|
193083
|
+
continue;
|
|
193084
|
+
total[key] = (total[key] ?? 0) + value;
|
|
193085
|
+
}
|
|
193086
|
+
}
|
|
193087
|
+
function isPositiveInteger(value) {
|
|
193088
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0;
|
|
193089
|
+
}
|
|
193090
|
+
function isRecord10(value) {
|
|
193091
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
193092
|
+
}
|
|
193093
|
+
|
|
192530
193094
|
// src/core/verification.ts
|
|
192531
193095
|
var REAL_AGENTS = ["codex", "claude"];
|
|
192532
193096
|
var VERIFIABLE_SEVERITIES = new Set(["critical", "high"]);
|
|
@@ -192568,9 +193132,12 @@ function groupVerificationTargetsByVerifier(targets) {
|
|
|
192568
193132
|
findings
|
|
192569
193133
|
}));
|
|
192570
193134
|
}
|
|
192571
|
-
function markVerificationOverflow(targets) {
|
|
193135
|
+
function markVerificationOverflow(targets, reason) {
|
|
192572
193136
|
for (const target of targets) {
|
|
192573
|
-
target.finding.verification = {
|
|
193137
|
+
target.finding.verification = {
|
|
193138
|
+
status: "not_verified",
|
|
193139
|
+
...reason ? { note: reason } : {}
|
|
193140
|
+
};
|
|
192574
193141
|
}
|
|
192575
193142
|
}
|
|
192576
193143
|
function parseVerificationVerdicts(rawText) {
|
|
@@ -192582,7 +193149,7 @@ function parseVerificationVerdicts(rawText) {
|
|
|
192582
193149
|
if (!Array.isArray(parsed.verdicts))
|
|
192583
193150
|
return;
|
|
192584
193151
|
return parsed.verdicts.flatMap((item) => {
|
|
192585
|
-
if (!
|
|
193152
|
+
if (!isRecord11(item))
|
|
192586
193153
|
return [];
|
|
192587
193154
|
if (typeof item.findingId !== "string")
|
|
192588
193155
|
return [];
|
|
@@ -192660,15 +193227,23 @@ function verificationNote(reasoning) {
|
|
|
192660
193227
|
function isVerdict(value) {
|
|
192661
193228
|
return value === "confirmed" || value === "refuted" || value === "uncertain";
|
|
192662
193229
|
}
|
|
192663
|
-
function
|
|
193230
|
+
function isRecord11(value) {
|
|
192664
193231
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
192665
193232
|
}
|
|
192666
193233
|
|
|
192667
193234
|
// src/core/runReview.ts
|
|
193235
|
+
function requestForRecursionFingerprint(request) {
|
|
193236
|
+
try {
|
|
193237
|
+
return scanAndRedactSecrets(request).redactedRequest;
|
|
193238
|
+
} catch {
|
|
193239
|
+
return { goal: "" };
|
|
193240
|
+
}
|
|
193241
|
+
}
|
|
192668
193242
|
async function runReview(tool, request, options = {}) {
|
|
192669
193243
|
const cwd = options.cwd ?? process.cwd();
|
|
192670
193244
|
const traceId = newTraceId();
|
|
192671
|
-
const
|
|
193245
|
+
const startedAtEpochMs = Date.now();
|
|
193246
|
+
const startedAt = new Date(startedAtEpochMs).toISOString();
|
|
192672
193247
|
const auditEnv = { ...process.env, ...options.env };
|
|
192673
193248
|
const traceWriterFactory = options.traceWriterFactory ?? createTraceWriter;
|
|
192674
193249
|
let snapshot;
|
|
@@ -192677,6 +193252,14 @@ async function runReview(tool, request, options = {}) {
|
|
|
192677
193252
|
} catch (error51) {
|
|
192678
193253
|
if (error51 instanceof KyosoRequestError) {
|
|
192679
193254
|
const config2 = kyosoConfigSchema.parse(defaultConfig);
|
|
193255
|
+
const budgetTracker = new ReviewBudgetTracker(config2.reviewBudget, startedAtEpochMs);
|
|
193256
|
+
const requestFingerprint = createRequestFingerprint({
|
|
193257
|
+
tool,
|
|
193258
|
+
request: requestForRecursionFingerprint(request),
|
|
193259
|
+
config: config2,
|
|
193260
|
+
roles: resolveAgentRoles(config2),
|
|
193261
|
+
budget: config2.reviewBudget
|
|
193262
|
+
});
|
|
192680
193263
|
const trace2 = traceWriterFactory({
|
|
192681
193264
|
enabled: config2.audit.enabled,
|
|
192682
193265
|
directory: config2.audit.directory,
|
|
@@ -192691,6 +193274,12 @@ async function runReview(tool, request, options = {}) {
|
|
|
192691
193274
|
tool,
|
|
192692
193275
|
timestamp: new Date().toISOString()
|
|
192693
193276
|
});
|
|
193277
|
+
await writeReviewBudgetPlanned({
|
|
193278
|
+
trace: trace2,
|
|
193279
|
+
traceId,
|
|
193280
|
+
budgetTracker,
|
|
193281
|
+
requestFingerprint
|
|
193282
|
+
});
|
|
192694
193283
|
return await buildPolicyBlockResult({
|
|
192695
193284
|
tool,
|
|
192696
193285
|
trace: trace2,
|
|
@@ -192698,6 +193287,8 @@ async function runReview(tool, request, options = {}) {
|
|
|
192698
193287
|
startedAt,
|
|
192699
193288
|
networkMode: config2.network.defaultMode,
|
|
192700
193289
|
warning: error51.message,
|
|
193290
|
+
budgetTracker,
|
|
193291
|
+
requestFingerprint,
|
|
192701
193292
|
finding: {
|
|
192702
193293
|
id: "KYOSO-1",
|
|
192703
193294
|
severity: "critical",
|
|
@@ -192763,6 +193354,8 @@ async function runReview(tool, request, options = {}) {
|
|
|
192763
193354
|
timestamp: new Date().toISOString()
|
|
192764
193355
|
});
|
|
192765
193356
|
validateReviewRequest(tool, request);
|
|
193357
|
+
const reviewBudget = resolveReviewBudget(loaded.config.reviewBudget, request.options?.reviewBudget);
|
|
193358
|
+
const budgetTracker = new ReviewBudgetTracker(reviewBudget, startedAtEpochMs);
|
|
192766
193359
|
assertTrustedWorkspaceRoot(request.workspace?.root, loaded.config.workspace.root, cwd);
|
|
192767
193360
|
const networkMode = resolveNetworkMode(request.options?.network, loaded.config.network.defaultMode, options.mcpNetworkMode);
|
|
192768
193361
|
if (networkMode === "unrestricted" && !loaded.config.network.allowUnrestricted) {
|
|
@@ -192781,6 +193374,19 @@ async function runReview(tool, request, options = {}) {
|
|
|
192781
193374
|
});
|
|
192782
193375
|
const allowSecretOverride = loaded.config.secrets.allowOverride && request.options?.allowSecretRedaction === true;
|
|
192783
193376
|
if (secretScan.detected && loaded.config.secrets.blockOnDetectedSecret && !allowSecretOverride) {
|
|
193377
|
+
const requestFingerprint2 = createRequestFingerprint({
|
|
193378
|
+
tool,
|
|
193379
|
+
request: secretScan.redactedRequest,
|
|
193380
|
+
config: loaded.config,
|
|
193381
|
+
roles: resolveAgentRoles(loaded.config),
|
|
193382
|
+
budget: reviewBudget
|
|
193383
|
+
});
|
|
193384
|
+
await writeReviewBudgetPlanned({
|
|
193385
|
+
trace,
|
|
193386
|
+
traceId,
|
|
193387
|
+
budgetTracker,
|
|
193388
|
+
requestFingerprint: requestFingerprint2
|
|
193389
|
+
});
|
|
192784
193390
|
return await buildSecretBlockResult({
|
|
192785
193391
|
tool,
|
|
192786
193392
|
trace,
|
|
@@ -192789,7 +193395,9 @@ async function runReview(tool, request, options = {}) {
|
|
|
192789
193395
|
configHash: loaded.configHash,
|
|
192790
193396
|
networkMode,
|
|
192791
193397
|
secretScan,
|
|
192792
|
-
warnings
|
|
193398
|
+
warnings,
|
|
193399
|
+
budgetTracker,
|
|
193400
|
+
requestFingerprint: requestFingerprint2
|
|
192793
193401
|
});
|
|
192794
193402
|
}
|
|
192795
193403
|
const denyPatterns = mergeDenyPatterns(loaded.config.workspace.deny, secretScan.redactedRequest.workspace?.denyRead);
|
|
@@ -192802,6 +193410,19 @@ async function runReview(tool, request, options = {}) {
|
|
|
192802
193410
|
});
|
|
192803
193411
|
warnings.push(...built.warnings);
|
|
192804
193412
|
const agentRoles = resolveAgentRoles(loaded.config);
|
|
193413
|
+
const requestFingerprint = createRequestFingerprint({
|
|
193414
|
+
tool,
|
|
193415
|
+
request: built.request,
|
|
193416
|
+
config: loaded.config,
|
|
193417
|
+
roles: agentRoles,
|
|
193418
|
+
budget: reviewBudget
|
|
193419
|
+
});
|
|
193420
|
+
await writeReviewBudgetPlanned({
|
|
193421
|
+
trace,
|
|
193422
|
+
traceId,
|
|
193423
|
+
budgetTracker,
|
|
193424
|
+
requestFingerprint
|
|
193425
|
+
});
|
|
192805
193426
|
snapshot = await createSnapshot(traceId, tool, built.request, {
|
|
192806
193427
|
denyPatterns,
|
|
192807
193428
|
allowPatterns,
|
|
@@ -192824,14 +193445,22 @@ async function runReview(tool, request, options = {}) {
|
|
|
192824
193445
|
networkMode,
|
|
192825
193446
|
manager,
|
|
192826
193447
|
trace,
|
|
192827
|
-
warnings
|
|
193448
|
+
warnings,
|
|
193449
|
+
budgetTracker
|
|
192828
193450
|
});
|
|
192829
193451
|
warnings.push(...agentResults.flatMap((result) => (result.warnings ?? []).map((warning) => `Agent ${result.agent} ${warning}`)));
|
|
192830
|
-
const
|
|
192831
|
-
const
|
|
192832
|
-
const
|
|
193452
|
+
const normalized = agentResults.map((result) => normalizeAgentRunResult(result, reviewBudget.maxFindingsPerAgent));
|
|
193453
|
+
const normalizedAgentResults = normalized.map((item) => item.result);
|
|
193454
|
+
for (const item of normalized.filter((item2) => item2.findingsCapped)) {
|
|
193455
|
+
budgetTracker.markIncomplete("coverage_incomplete");
|
|
193456
|
+
warnings.push(`Agent ${item.result.agent} findings were capped at ${reviewBudget.maxFindingsPerAgent}; review coverage is incomplete.`);
|
|
193457
|
+
}
|
|
193458
|
+
const enabledAgents = ["codex", "claude"].filter((agent) => loaded.config.agents[agent].enabled);
|
|
193459
|
+
const agentsUsed = normalizedAgentResults.filter((result) => result.status !== "skipped").map((result) => result.agent);
|
|
193460
|
+
const reviewMode = enabledAgents.length === 1 ? "single_agent" : "multi_agent";
|
|
192833
193461
|
const completed = normalizedAgentResults.filter((result) => result.status === "completed");
|
|
192834
|
-
const
|
|
193462
|
+
const attempted = normalizedAgentResults.filter((result) => result.status !== "skipped");
|
|
193463
|
+
const degraded = completed.length !== attempted.length || enabledAgents.length === 0;
|
|
192835
193464
|
let aggregate = aggregateAgentResults(normalizedAgentResults, {
|
|
192836
193465
|
reviewMode
|
|
192837
193466
|
});
|
|
@@ -192847,7 +193476,12 @@ async function runReview(tool, request, options = {}) {
|
|
|
192847
193476
|
])
|
|
192848
193477
|
};
|
|
192849
193478
|
}
|
|
192850
|
-
if (completed.length === 0) {
|
|
193479
|
+
if (completed.length === 0 && (attempted.length > 0 || enabledAgents.length === 0)) {
|
|
193480
|
+
const noPrimaryAgents = enabledAgents.length === 0;
|
|
193481
|
+
if (noPrimaryAgents) {
|
|
193482
|
+
budgetTracker.markIncomplete("coverage_incomplete");
|
|
193483
|
+
warnings.push("No primary review agents are enabled; review coverage is incomplete.");
|
|
193484
|
+
}
|
|
192851
193485
|
aggregate = {
|
|
192852
193486
|
...aggregate,
|
|
192853
193487
|
findings: [
|
|
@@ -192856,9 +193490,9 @@ async function runReview(tool, request, options = {}) {
|
|
|
192856
193490
|
id: `KYOSO-${aggregate.findings.length + 1}`,
|
|
192857
193491
|
severity: "critical",
|
|
192858
193492
|
category: "other",
|
|
192859
|
-
title: "All backend agents failed",
|
|
192860
|
-
evidence: normalizedAgentResults.map((result) => `${result.agent}: ${result.error?.code ?? result.status}`).join("; "),
|
|
192861
|
-
recommendation: "Run kyoso doctor and retry after agent authentication or adapter issues are fixed.",
|
|
193493
|
+
title: noPrimaryAgents ? "No primary review agents enabled" : "All backend agents failed",
|
|
193494
|
+
evidence: noPrimaryAgents ? "Both configured primary reviewers are disabled." : normalizedAgentResults.map((result) => `${result.agent}: ${result.error?.code ?? result.status}`).join("; "),
|
|
193495
|
+
recommendation: noPrimaryAgents ? "Enable at least one primary reviewer before running Kyoso." : "Run kyoso doctor and retry after agent authentication or adapter issues are fixed.",
|
|
192862
193496
|
sourceAgents: ["kyoso_policy"],
|
|
192863
193497
|
confidence: "high"
|
|
192864
193498
|
}
|
|
@@ -192871,7 +193505,7 @@ async function runReview(tool, request, options = {}) {
|
|
|
192871
193505
|
findingCount: aggregate.findings.length,
|
|
192872
193506
|
timestamp: new Date().toISOString()
|
|
192873
193507
|
});
|
|
192874
|
-
const verificationMode = loaded.config.verification.enabled && reviewMode === "single_agent" ? "skipped_single_agent" : loaded.config.verification.enabled ? "cross_agent" : undefined;
|
|
193508
|
+
const verificationMode = loaded.config.verification.enabled && reviewMode === "single_agent" ? "skipped_single_agent" : loaded.config.verification.enabled && enabledAgents.length > 1 ? "cross_agent" : undefined;
|
|
192875
193509
|
if (verificationMode === "cross_agent") {
|
|
192876
193510
|
warnings.push(...await runFindingVerification({
|
|
192877
193511
|
tool,
|
|
@@ -192882,11 +193516,16 @@ async function runReview(tool, request, options = {}) {
|
|
|
192882
193516
|
networkMode,
|
|
192883
193517
|
manager,
|
|
192884
193518
|
trace,
|
|
192885
|
-
findings: aggregate.findings
|
|
193519
|
+
findings: aggregate.findings,
|
|
193520
|
+
budgetTracker
|
|
192886
193521
|
}));
|
|
192887
193522
|
}
|
|
193523
|
+
if (aggregate.findings.some((finding) => finding.verification?.status === "refuted")) {
|
|
193524
|
+
budgetTracker.markIncomplete("disputed_finding");
|
|
193525
|
+
}
|
|
192888
193526
|
const cisa = tool === "security_review" ? computeCisaGate(aggregate.findings, normalizedAgentResults) : undefined;
|
|
192889
|
-
const
|
|
193527
|
+
const budgetBeforeJudge = budgetTracker.snapshot();
|
|
193528
|
+
const decision = budgetBeforeJudge.completion.status === "incomplete" ? "block" : decide({
|
|
192890
193529
|
tool,
|
|
192891
193530
|
findings: aggregate.findings,
|
|
192892
193531
|
cisa,
|
|
@@ -192896,6 +193535,9 @@ async function runReview(tool, request, options = {}) {
|
|
|
192896
193535
|
const completedAt = new Date().toISOString();
|
|
192897
193536
|
const resultWithoutMarkdown = {
|
|
192898
193537
|
decision,
|
|
193538
|
+
completion: budgetBeforeJudge.completion,
|
|
193539
|
+
executionBudget: budgetBeforeJudge.executionBudget,
|
|
193540
|
+
requestFingerprint,
|
|
192899
193541
|
degraded,
|
|
192900
193542
|
agentsUsed,
|
|
192901
193543
|
reviewMode,
|
|
@@ -192917,18 +193559,22 @@ async function runReview(tool, request, options = {}) {
|
|
|
192917
193559
|
networkMode,
|
|
192918
193560
|
workspaceMode: "temp_snapshot",
|
|
192919
193561
|
configHash: loaded.configHash,
|
|
192920
|
-
warnings: Array.from(new Set([...warnings, ...trace.warnings]))
|
|
193562
|
+
warnings: Array.from(new Set([...warnings, ...trace.warnings])),
|
|
193563
|
+
modelCalls: budgetBeforeJudge.modelCalls
|
|
192921
193564
|
}
|
|
192922
193565
|
};
|
|
192923
193566
|
const summaryText = defaultSummaryText(resultWithoutMarkdown);
|
|
192924
|
-
const judge = await
|
|
193567
|
+
const judge = await runBudgetedJudge({
|
|
192925
193568
|
tool,
|
|
192926
193569
|
result: resultWithoutMarkdown,
|
|
192927
193570
|
summaryText,
|
|
192928
193571
|
agentFindings: buildJudgeAgentFindings(normalizedAgentResults),
|
|
192929
193572
|
config: loaded.config.judge,
|
|
192930
193573
|
requestedProvider: request.options?.judgeProvider,
|
|
192931
|
-
env: options.env ?? process.env
|
|
193574
|
+
env: options.env ?? process.env,
|
|
193575
|
+
budgetTracker,
|
|
193576
|
+
trace,
|
|
193577
|
+
traceId
|
|
192932
193578
|
});
|
|
192933
193579
|
const judgeComments = new Map(judge.output.disagreementComments.map((comment) => [
|
|
192934
193580
|
comment.topic,
|
|
@@ -192939,10 +193585,20 @@ async function runReview(tool, request, options = {}) {
|
|
|
192939
193585
|
judgeComment: judgeComments.get(disagreement.topic) ?? disagreement.judgeComment
|
|
192940
193586
|
}));
|
|
192941
193587
|
const crossModelAnalysis = buildCrossModelAnalysis(judge, reviewMode);
|
|
193588
|
+
const budgetAfterJudge = budgetTracker.snapshot();
|
|
193589
|
+
const finalDecision = budgetAfterJudge.completion.status === "incomplete" ? "block" : decision;
|
|
192942
193590
|
const resultAfterJudge = {
|
|
192943
193591
|
...resultWithoutMarkdown,
|
|
193592
|
+
decision: finalDecision,
|
|
193593
|
+
completion: budgetAfterJudge.completion,
|
|
193594
|
+
executionBudget: budgetAfterJudge.executionBudget,
|
|
192944
193595
|
disagreements,
|
|
192945
|
-
...crossModelAnalysis ? { crossModelAnalysis } : {}
|
|
193596
|
+
...crossModelAnalysis ? { crossModelAnalysis } : {},
|
|
193597
|
+
audit: {
|
|
193598
|
+
...resultWithoutMarkdown.audit,
|
|
193599
|
+
completedAt: new Date().toISOString(),
|
|
193600
|
+
modelCalls: budgetAfterJudge.modelCalls
|
|
193601
|
+
}
|
|
192946
193602
|
};
|
|
192947
193603
|
const judgeEvent = {
|
|
192948
193604
|
type: "judge_completed",
|
|
@@ -192955,10 +193611,16 @@ async function runReview(tool, request, options = {}) {
|
|
|
192955
193611
|
judgeEvent.error = judge.error;
|
|
192956
193612
|
await trace.write(judgeEvent);
|
|
192957
193613
|
resultAfterJudge.audit.completedAt = new Date().toISOString();
|
|
193614
|
+
await writeReviewBudgetCompleted({
|
|
193615
|
+
trace,
|
|
193616
|
+
traceId,
|
|
193617
|
+
budgetTracker,
|
|
193618
|
+
requestFingerprint
|
|
193619
|
+
});
|
|
192958
193620
|
await trace.write({
|
|
192959
193621
|
type: "decision_completed",
|
|
192960
193622
|
traceId,
|
|
192961
|
-
decision,
|
|
193623
|
+
decision: finalDecision,
|
|
192962
193624
|
timestamp: new Date().toISOString()
|
|
192963
193625
|
});
|
|
192964
193626
|
await trace.write({
|
|
@@ -192970,7 +193632,7 @@ async function runReview(tool, request, options = {}) {
|
|
|
192970
193632
|
tool,
|
|
192971
193633
|
trace,
|
|
192972
193634
|
result: resultAfterJudge,
|
|
192973
|
-
summaryText: judge.output.summaryText
|
|
193635
|
+
summaryText: resultAfterJudge.completion.status === "incomplete" ? defaultSummaryText(resultAfterJudge) : judge.output.summaryText
|
|
192974
193636
|
});
|
|
192975
193637
|
} finally {
|
|
192976
193638
|
await trace.finalize();
|
|
@@ -192981,37 +193643,180 @@ async function runReview(tool, request, options = {}) {
|
|
|
192981
193643
|
async function runFindingVerification(input) {
|
|
192982
193644
|
const allowDemotionRequested = input.config.verification.allowDemotion;
|
|
192983
193645
|
const selection = selectVerificationTargets(input.findings, input.config.verification.maxFindings);
|
|
192984
|
-
markVerificationOverflow(selection.overflow);
|
|
192985
|
-
if (selection.selected.length === 0)
|
|
192986
|
-
return [];
|
|
192987
193646
|
const warnings = [];
|
|
192988
|
-
|
|
193647
|
+
if (selection.overflow.length > 0) {
|
|
193648
|
+
markVerificationOverflow(selection.overflow, "verification_max_findings");
|
|
193649
|
+
input.budgetTracker.markIncomplete("coverage_incomplete");
|
|
193650
|
+
}
|
|
193651
|
+
if (selection.selected.length === 0)
|
|
193652
|
+
return warnings;
|
|
193653
|
+
const potentialGroups = groupVerificationTargetsByVerifier(selection.selected);
|
|
192989
193654
|
await input.trace.write({
|
|
192990
193655
|
type: "verification_started",
|
|
192991
193656
|
traceId: input.traceId,
|
|
192992
193657
|
targetCount: selection.selected.length,
|
|
192993
193658
|
notVerifiedCount: selection.overflow.length,
|
|
192994
|
-
verifierCount:
|
|
193659
|
+
verifierCount: potentialGroups.length,
|
|
192995
193660
|
timeoutMs: input.config.verification.timeoutMs,
|
|
192996
193661
|
allowDemotionRequested,
|
|
192997
193662
|
timestamp: new Date().toISOString()
|
|
192998
193663
|
});
|
|
192999
|
-
|
|
193664
|
+
if (input.budgetTracker.budget.skipOptionalPhasesWhenTokenUsageUnknown && input.budgetTracker.isTokenUsageUnknown()) {
|
|
193665
|
+
markVerificationOverflow(selection.selected, "token_usage_unknown");
|
|
193666
|
+
input.budgetTracker.markIncomplete("token_usage_unknown");
|
|
193667
|
+
input.budgetTracker.markIncomplete("coverage_incomplete");
|
|
193668
|
+
warnings.push("Finding verification was skipped because primary-agent token usage was not reported.");
|
|
193669
|
+
for (const group of potentialGroups) {
|
|
193670
|
+
input.budgetTracker.recordSkipped({
|
|
193671
|
+
kind: "verifier",
|
|
193672
|
+
agent: group.verifier,
|
|
193673
|
+
reason: "token_usage_unknown"
|
|
193674
|
+
});
|
|
193675
|
+
await input.trace.write({
|
|
193676
|
+
type: "model_call_skipped",
|
|
193677
|
+
traceId: input.traceId,
|
|
193678
|
+
kind: "verifier",
|
|
193679
|
+
agent: group.verifier,
|
|
193680
|
+
reason: "token_usage_unknown",
|
|
193681
|
+
timestamp: new Date().toISOString()
|
|
193682
|
+
});
|
|
193683
|
+
}
|
|
193684
|
+
await input.trace.write({
|
|
193685
|
+
type: "verification_completed",
|
|
193686
|
+
traceId: input.traceId,
|
|
193687
|
+
counts: countVerificationStatuses(input.findings),
|
|
193688
|
+
timestamp: new Date().toISOString()
|
|
193689
|
+
});
|
|
193690
|
+
return warnings;
|
|
193691
|
+
}
|
|
193692
|
+
const groups = new Map;
|
|
193693
|
+
const unavailableVerifiers = new Map;
|
|
193694
|
+
for (const target of selection.selected) {
|
|
193695
|
+
const existing = groups.get(target.verifier);
|
|
193696
|
+
if (existing) {
|
|
193697
|
+
existing.targets.push(target);
|
|
193698
|
+
continue;
|
|
193699
|
+
}
|
|
193700
|
+
const unavailable = unavailableVerifiers.get(target.verifier);
|
|
193701
|
+
if (unavailable) {
|
|
193702
|
+
markVerificationOverflow([target], unavailable === "model_call_budget" ? "budget_exhausted" : "deadline");
|
|
193703
|
+
continue;
|
|
193704
|
+
}
|
|
193705
|
+
const reservationResult = input.budgetTracker.reserve({
|
|
193706
|
+
kind: "verifier",
|
|
193707
|
+
agent: target.verifier
|
|
193708
|
+
});
|
|
193709
|
+
if ("failure" in reservationResult) {
|
|
193710
|
+
unavailableVerifiers.set(target.verifier, reservationResult.failure.reason);
|
|
193711
|
+
markVerificationOverflow([target], reservationResult.failure.reason === "model_call_budget" ? "budget_exhausted" : "deadline");
|
|
193712
|
+
input.budgetTracker.recordSkipped({
|
|
193713
|
+
kind: "verifier",
|
|
193714
|
+
agent: target.verifier,
|
|
193715
|
+
reason: reservationResult.failure.reason
|
|
193716
|
+
});
|
|
193717
|
+
input.budgetTracker.markIncomplete(reservationResult.failure.reason);
|
|
193718
|
+
input.budgetTracker.markIncomplete("coverage_incomplete");
|
|
193719
|
+
await input.trace.write({
|
|
193720
|
+
type: "review_budget_exhausted",
|
|
193721
|
+
traceId: input.traceId,
|
|
193722
|
+
phase: "verification",
|
|
193723
|
+
kind: "verifier",
|
|
193724
|
+
agent: target.verifier,
|
|
193725
|
+
reason: reservationResult.failure.reason,
|
|
193726
|
+
timestamp: new Date().toISOString()
|
|
193727
|
+
});
|
|
193728
|
+
await input.trace.write({
|
|
193729
|
+
type: "model_call_skipped",
|
|
193730
|
+
traceId: input.traceId,
|
|
193731
|
+
kind: "verifier",
|
|
193732
|
+
agent: target.verifier,
|
|
193733
|
+
reason: reservationResult.failure.reason,
|
|
193734
|
+
timestamp: new Date().toISOString()
|
|
193735
|
+
});
|
|
193736
|
+
continue;
|
|
193737
|
+
}
|
|
193738
|
+
const group = {
|
|
193739
|
+
verifier: target.verifier,
|
|
193740
|
+
targets: [target],
|
|
193741
|
+
reservation: reservationResult.reservation
|
|
193742
|
+
};
|
|
193743
|
+
groups.set(target.verifier, group);
|
|
193744
|
+
await input.trace.write({
|
|
193745
|
+
type: "model_call_reserved",
|
|
193746
|
+
traceId: input.traceId,
|
|
193747
|
+
kind: "verifier",
|
|
193748
|
+
agent: target.verifier,
|
|
193749
|
+
timestamp: new Date().toISOString()
|
|
193750
|
+
});
|
|
193751
|
+
}
|
|
193752
|
+
const scheduledGroups = [];
|
|
193753
|
+
for (const group of groups.values()) {
|
|
193754
|
+
const timeoutMs = Math.min(input.config.verification.timeoutMs, input.budgetTracker.remainingWallTimeMs());
|
|
193755
|
+
if (timeoutMs > 0) {
|
|
193756
|
+
scheduledGroups.push(group);
|
|
193757
|
+
continue;
|
|
193758
|
+
}
|
|
193759
|
+
input.budgetTracker.skip(group.reservation, "deadline");
|
|
193760
|
+
input.budgetTracker.markIncomplete("deadline");
|
|
193761
|
+
input.budgetTracker.markIncomplete("coverage_incomplete");
|
|
193762
|
+
markVerificationOverflow(group.targets, "deadline");
|
|
193763
|
+
await input.trace.write({
|
|
193764
|
+
type: "review_budget_exhausted",
|
|
193765
|
+
traceId: input.traceId,
|
|
193766
|
+
phase: "verification",
|
|
193767
|
+
kind: "verifier",
|
|
193768
|
+
agent: group.verifier,
|
|
193769
|
+
reason: "deadline",
|
|
193770
|
+
timestamp: new Date().toISOString()
|
|
193771
|
+
});
|
|
193772
|
+
await input.trace.write({
|
|
193773
|
+
type: "model_call_skipped",
|
|
193774
|
+
traceId: input.traceId,
|
|
193775
|
+
kind: "verifier",
|
|
193776
|
+
agent: group.verifier,
|
|
193777
|
+
reason: "deadline",
|
|
193778
|
+
timestamp: new Date().toISOString()
|
|
193779
|
+
});
|
|
193780
|
+
}
|
|
193781
|
+
if (scheduledGroups.length === 0) {
|
|
193782
|
+
await input.trace.write({
|
|
193783
|
+
type: "verification_completed",
|
|
193784
|
+
traceId: input.traceId,
|
|
193785
|
+
counts: countVerificationStatuses(input.findings),
|
|
193786
|
+
timestamp: new Date().toISOString()
|
|
193787
|
+
});
|
|
193788
|
+
return warnings;
|
|
193789
|
+
}
|
|
193790
|
+
const agentInputs = scheduledGroups.map((group) => ({
|
|
193000
193791
|
traceId: input.traceId,
|
|
193001
|
-
agent: verifier,
|
|
193792
|
+
agent: group.verifier,
|
|
193002
193793
|
role: "finding_verifier",
|
|
193003
193794
|
tool: input.tool,
|
|
193004
|
-
prompt: buildFindingVerifierPrompt(input.tool, input.request, verifier,
|
|
193795
|
+
prompt: buildFindingVerifierPrompt(input.tool, input.request, group.verifier, group.targets.map((target) => target.finding)),
|
|
193005
193796
|
workspaceDir: input.workspaceDir,
|
|
193006
|
-
timeoutMs: input.config.verification.timeoutMs,
|
|
193007
|
-
|
|
193797
|
+
timeoutMs: Math.min(input.config.verification.timeoutMs, input.budgetTracker.remainingWallTimeMs()),
|
|
193798
|
+
deadlineAtEpochMs: input.budgetTracker.deadlineAtEpochMs,
|
|
193799
|
+
maxOutputBytes: input.budgetTracker.budget.maxAgentOutputBytes,
|
|
193800
|
+
networkMode: input.networkMode,
|
|
193801
|
+
onStarted: () => {
|
|
193802
|
+
input.budgetTracker.markStarted(group.reservation);
|
|
193803
|
+
return Promise.resolve();
|
|
193804
|
+
}
|
|
193008
193805
|
}));
|
|
193009
193806
|
let results;
|
|
193010
193807
|
try {
|
|
193011
193808
|
results = await input.manager.runAll(agentInputs);
|
|
193012
193809
|
} catch (error51) {
|
|
193013
|
-
for (const group of
|
|
193014
|
-
applyVerificationVerdicts(
|
|
193810
|
+
for (const group of scheduledGroups) {
|
|
193811
|
+
applyVerificationVerdicts(group.targets, group.verifier, undefined);
|
|
193812
|
+
await finalizeModelCallResult({
|
|
193813
|
+
budgetTracker: input.budgetTracker,
|
|
193814
|
+
reservation: group.reservation,
|
|
193815
|
+
result: failedVerifierResult(group.verifier, "AGENT_MANAGER_FAILED"),
|
|
193816
|
+
trace: input.trace,
|
|
193817
|
+
traceId: input.traceId
|
|
193818
|
+
});
|
|
193819
|
+
input.budgetTracker.markIncomplete("coverage_incomplete");
|
|
193015
193820
|
}
|
|
193016
193821
|
const message = `Finding verification failed: ${sanitizeTextForDisplay(error51 instanceof Error ? error51.message : String(error51))}`;
|
|
193017
193822
|
warnings.push(message);
|
|
@@ -193024,9 +193829,32 @@ async function runFindingVerification(input) {
|
|
|
193024
193829
|
});
|
|
193025
193830
|
return warnings;
|
|
193026
193831
|
}
|
|
193027
|
-
|
|
193832
|
+
const resultByAgent = new Map(results.map((result) => [result.agent, result]));
|
|
193833
|
+
for (const group of scheduledGroups) {
|
|
193834
|
+
const result = resultByAgent.get(group.verifier);
|
|
193835
|
+
if (!result) {
|
|
193836
|
+
applyVerificationVerdicts(group.targets, group.verifier, undefined);
|
|
193837
|
+
await finalizeModelCallResult({
|
|
193838
|
+
budgetTracker: input.budgetTracker,
|
|
193839
|
+
reservation: group.reservation,
|
|
193840
|
+
result: failedVerifierResult(group.verifier, "AGENT_RESULT_MISSING"),
|
|
193841
|
+
trace: input.trace,
|
|
193842
|
+
traceId: input.traceId
|
|
193843
|
+
});
|
|
193844
|
+
input.budgetTracker.markIncomplete("coverage_incomplete");
|
|
193845
|
+
warnings.push(`Finding verification by ${group.verifier} did not return a result.`);
|
|
193846
|
+
continue;
|
|
193847
|
+
}
|
|
193848
|
+
await finalizeModelCallResult({
|
|
193849
|
+
budgetTracker: input.budgetTracker,
|
|
193850
|
+
reservation: group.reservation,
|
|
193851
|
+
result,
|
|
193852
|
+
trace: input.trace,
|
|
193853
|
+
traceId: input.traceId
|
|
193854
|
+
});
|
|
193028
193855
|
if (result.status !== "completed") {
|
|
193029
|
-
applyVerificationVerdicts(
|
|
193856
|
+
applyVerificationVerdicts(group.targets, result.agent, undefined);
|
|
193857
|
+
input.budgetTracker.markIncomplete("coverage_incomplete");
|
|
193030
193858
|
const message = `Finding verification by ${result.agent} ${result.status}: ${result.error?.message ?? result.error?.code ?? "no detail"}`;
|
|
193031
193859
|
warnings.push(sanitizeTextForDisplay(message));
|
|
193032
193860
|
await input.trace.write({
|
|
@@ -193040,8 +193868,9 @@ async function runFindingVerification(input) {
|
|
|
193040
193868
|
continue;
|
|
193041
193869
|
}
|
|
193042
193870
|
const verdicts = parseVerificationVerdicts(result.rawText);
|
|
193043
|
-
applyVerificationVerdicts(
|
|
193871
|
+
applyVerificationVerdicts(group.targets, result.agent, verdicts);
|
|
193044
193872
|
if (!verdicts) {
|
|
193873
|
+
input.budgetTracker.markIncomplete("coverage_incomplete");
|
|
193045
193874
|
const message = `Finding verification by ${result.agent} returned malformed verdict JSON.`;
|
|
193046
193875
|
warnings.push(message);
|
|
193047
193876
|
await input.trace.write({
|
|
@@ -193061,6 +193890,20 @@ async function runFindingVerification(input) {
|
|
|
193061
193890
|
});
|
|
193062
193891
|
return warnings;
|
|
193063
193892
|
}
|
|
193893
|
+
function failedVerifierResult(agent, code) {
|
|
193894
|
+
const timestamp = new Date().toISOString();
|
|
193895
|
+
return {
|
|
193896
|
+
agent,
|
|
193897
|
+
role: "finding_verifier",
|
|
193898
|
+
status: "failed",
|
|
193899
|
+
startedAt: timestamp,
|
|
193900
|
+
completedAt: timestamp,
|
|
193901
|
+
error: {
|
|
193902
|
+
code,
|
|
193903
|
+
message: "The agent manager did not return a verification result."
|
|
193904
|
+
}
|
|
193905
|
+
};
|
|
193906
|
+
}
|
|
193064
193907
|
function buildJudgeAgentFindings(results) {
|
|
193065
193908
|
return results.flatMap((result) => {
|
|
193066
193909
|
if (!result.normalized)
|
|
@@ -193097,13 +193940,182 @@ function buildCrossModelAnalysis(judge, reviewMode) {
|
|
|
193097
193940
|
provider: judge.provider
|
|
193098
193941
|
};
|
|
193099
193942
|
}
|
|
193943
|
+
async function runBudgetedJudge(input) {
|
|
193944
|
+
const configuredProvider = input.requestedProvider ?? input.config.provider;
|
|
193945
|
+
const provider = resolveJudgeProvider(configuredProvider, input.env);
|
|
193946
|
+
if (input.config.mode === "deterministic_only" || provider === "deterministic_fallback") {
|
|
193947
|
+
return runJudge(input);
|
|
193948
|
+
}
|
|
193949
|
+
const fallback = () => runJudge({
|
|
193950
|
+
...input,
|
|
193951
|
+
config: { ...input.config, mode: "deterministic_only" }
|
|
193952
|
+
});
|
|
193953
|
+
if (input.budgetTracker.snapshot().completion.status === "incomplete") {
|
|
193954
|
+
await recordSkippedJudgeCall(input, "review_incomplete");
|
|
193955
|
+
return fallback();
|
|
193956
|
+
}
|
|
193957
|
+
if (input.budgetTracker.budget.skipOptionalPhasesWhenTokenUsageUnknown && input.budgetTracker.isTokenUsageUnknown()) {
|
|
193958
|
+
await recordSkippedJudgeCall(input, "token_usage_unknown");
|
|
193959
|
+
return fallback();
|
|
193960
|
+
}
|
|
193961
|
+
const reservationResult = input.budgetTracker.reserve({ kind: "judge" });
|
|
193962
|
+
if ("failure" in reservationResult) {
|
|
193963
|
+
await recordSkippedJudgeCall(input, reservationResult.failure.reason);
|
|
193964
|
+
await input.trace.write({
|
|
193965
|
+
type: "review_budget_exhausted",
|
|
193966
|
+
traceId: input.traceId,
|
|
193967
|
+
phase: "judge",
|
|
193968
|
+
kind: "judge",
|
|
193969
|
+
reason: reservationResult.failure.reason,
|
|
193970
|
+
timestamp: new Date().toISOString()
|
|
193971
|
+
});
|
|
193972
|
+
return fallback();
|
|
193973
|
+
}
|
|
193974
|
+
const reservation = reservationResult.reservation;
|
|
193975
|
+
await input.trace.write({
|
|
193976
|
+
type: "model_call_reserved",
|
|
193977
|
+
traceId: input.traceId,
|
|
193978
|
+
kind: "judge",
|
|
193979
|
+
timestamp: new Date().toISOString()
|
|
193980
|
+
});
|
|
193981
|
+
const timeoutMs = Math.min(input.config.timeoutMs, input.budgetTracker.remainingWallTimeMs());
|
|
193982
|
+
if (timeoutMs <= 0) {
|
|
193983
|
+
input.budgetTracker.skip(reservation, "deadline");
|
|
193984
|
+
await input.trace.write({
|
|
193985
|
+
type: "review_budget_exhausted",
|
|
193986
|
+
traceId: input.traceId,
|
|
193987
|
+
phase: "judge",
|
|
193988
|
+
kind: "judge",
|
|
193989
|
+
reason: "deadline",
|
|
193990
|
+
timestamp: new Date().toISOString()
|
|
193991
|
+
});
|
|
193992
|
+
await input.trace.write({
|
|
193993
|
+
type: "model_call_skipped",
|
|
193994
|
+
traceId: input.traceId,
|
|
193995
|
+
kind: "judge",
|
|
193996
|
+
reason: "deadline",
|
|
193997
|
+
timestamp: new Date().toISOString()
|
|
193998
|
+
});
|
|
193999
|
+
return fallback();
|
|
194000
|
+
}
|
|
194001
|
+
input.budgetTracker.markStarted(reservation);
|
|
194002
|
+
const judge = await runJudge({ ...input, timeoutMs });
|
|
194003
|
+
const usage = normalizeModelTokenUsage(judge.usage);
|
|
194004
|
+
input.budgetTracker.complete(reservation, {
|
|
194005
|
+
...usage ? { usage } : {}
|
|
194006
|
+
});
|
|
194007
|
+
await input.trace.write({
|
|
194008
|
+
type: "model_call_completed",
|
|
194009
|
+
traceId: input.traceId,
|
|
194010
|
+
kind: "judge",
|
|
194011
|
+
provider: judge.provider,
|
|
194012
|
+
resultStatus: judge.status,
|
|
194013
|
+
...usage ? { usage } : {},
|
|
194014
|
+
timestamp: new Date().toISOString()
|
|
194015
|
+
});
|
|
194016
|
+
return judge;
|
|
194017
|
+
}
|
|
194018
|
+
async function recordSkippedJudgeCall(input, reason) {
|
|
194019
|
+
input.budgetTracker.recordSkipped({ kind: "judge", reason });
|
|
194020
|
+
await input.trace.write({
|
|
194021
|
+
type: "model_call_skipped",
|
|
194022
|
+
traceId: input.traceId,
|
|
194023
|
+
kind: "judge",
|
|
194024
|
+
reason,
|
|
194025
|
+
timestamp: new Date().toISOString()
|
|
194026
|
+
});
|
|
194027
|
+
}
|
|
193100
194028
|
async function runAgents(input) {
|
|
193101
194029
|
const agentRoles = resolveAgentRoles(input.config);
|
|
194030
|
+
const enabledAgents = ["codex", "claude"].filter((agent) => input.config.agents[agent].enabled);
|
|
194031
|
+
if (enabledAgents.length === 0)
|
|
194032
|
+
return [];
|
|
194033
|
+
const reservationResult = input.budgetTracker.reserveMany(enabledAgents.map((agent) => ({ kind: "primary", agent })));
|
|
194034
|
+
if ("failure" in reservationResult) {
|
|
194035
|
+
input.budgetTracker.markIncomplete(reservationResult.failure.reason);
|
|
194036
|
+
input.budgetTracker.markIncomplete("coverage_incomplete");
|
|
194037
|
+
await input.trace.write({
|
|
194038
|
+
type: "review_budget_exhausted",
|
|
194039
|
+
traceId: input.traceId,
|
|
194040
|
+
phase: "primary",
|
|
194041
|
+
reason: reservationResult.failure.reason,
|
|
194042
|
+
requiredCalls: enabledAgents.length,
|
|
194043
|
+
timestamp: new Date().toISOString()
|
|
194044
|
+
});
|
|
194045
|
+
for (const agent of enabledAgents) {
|
|
194046
|
+
input.budgetTracker.recordSkipped({
|
|
194047
|
+
kind: "primary",
|
|
194048
|
+
agent,
|
|
194049
|
+
reason: reservationResult.failure.reason
|
|
194050
|
+
});
|
|
194051
|
+
await input.trace.write({
|
|
194052
|
+
type: "model_call_skipped",
|
|
194053
|
+
traceId: input.traceId,
|
|
194054
|
+
kind: "primary",
|
|
194055
|
+
agent,
|
|
194056
|
+
reason: reservationResult.failure.reason,
|
|
194057
|
+
timestamp: new Date().toISOString()
|
|
194058
|
+
});
|
|
194059
|
+
}
|
|
194060
|
+
return enabledAgents.map((agent) => {
|
|
194061
|
+
const role = agentRoles[agent] ?? input.config.agents[agent].role;
|
|
194062
|
+
const timestamp = new Date().toISOString();
|
|
194063
|
+
return {
|
|
194064
|
+
agent,
|
|
194065
|
+
role,
|
|
194066
|
+
status: "skipped",
|
|
194067
|
+
startedAt: timestamp,
|
|
194068
|
+
completedAt: timestamp,
|
|
194069
|
+
error: {
|
|
194070
|
+
code: reservationResult.failure.reason === "deadline" ? "REVIEW_DEADLINE_EXCEEDED" : "MODEL_CALL_BUDGET_EXHAUSTED",
|
|
194071
|
+
message: reservationResult.failure.reason === "deadline" ? "Review deadline was reached before primary agents could start." : "The review model-call budget cannot reserve all primary agents."
|
|
194072
|
+
}
|
|
194073
|
+
};
|
|
194074
|
+
});
|
|
194075
|
+
}
|
|
194076
|
+
const reservations = new Map(reservationResult.reservations.map((reservation) => [
|
|
194077
|
+
reservation.agent,
|
|
194078
|
+
reservation
|
|
194079
|
+
]));
|
|
194080
|
+
for (const reservation of reservationResult.reservations) {
|
|
194081
|
+
await input.trace.write({
|
|
194082
|
+
type: "model_call_reserved",
|
|
194083
|
+
traceId: input.traceId,
|
|
194084
|
+
kind: reservation.kind,
|
|
194085
|
+
agent: reservation.agent,
|
|
194086
|
+
timestamp: new Date().toISOString()
|
|
194087
|
+
});
|
|
194088
|
+
}
|
|
194089
|
+
if (input.budgetTracker.remainingWallTimeMs() <= 0) {
|
|
194090
|
+
input.budgetTracker.markIncomplete("deadline");
|
|
194091
|
+
input.budgetTracker.markIncomplete("coverage_incomplete");
|
|
194092
|
+
await input.trace.write({
|
|
194093
|
+
type: "review_budget_exhausted",
|
|
194094
|
+
traceId: input.traceId,
|
|
194095
|
+
phase: "primary",
|
|
194096
|
+
reason: "deadline",
|
|
194097
|
+
timestamp: new Date().toISOString()
|
|
194098
|
+
});
|
|
194099
|
+
return await skipReservedPrimaryAgents({
|
|
194100
|
+
trace: input.trace,
|
|
194101
|
+
traceId: input.traceId,
|
|
194102
|
+
budgetTracker: input.budgetTracker,
|
|
194103
|
+
config: input.config,
|
|
194104
|
+
agents: enabledAgents,
|
|
194105
|
+
agentRoles,
|
|
194106
|
+
reservations,
|
|
194107
|
+
reason: "deadline"
|
|
194108
|
+
});
|
|
194109
|
+
}
|
|
193102
194110
|
const startedWrites = [];
|
|
193103
194111
|
let acceptingStartedEvents = true;
|
|
193104
|
-
const agentInputs =
|
|
194112
|
+
const agentInputs = enabledAgents.map((agent) => {
|
|
193105
194113
|
const agentConfig = input.config.agents[agent];
|
|
193106
194114
|
const role = agentRoles[agent] ?? agentConfig.role;
|
|
194115
|
+
const reservation = reservations.get(agent);
|
|
194116
|
+
if (!reservation) {
|
|
194117
|
+
throw new Error(`Missing primary budget reservation for ${agent}.`);
|
|
194118
|
+
}
|
|
193107
194119
|
return {
|
|
193108
194120
|
traceId: input.traceId,
|
|
193109
194121
|
agent,
|
|
@@ -193111,9 +194123,12 @@ async function runAgents(input) {
|
|
|
193111
194123
|
tool: input.tool,
|
|
193112
194124
|
prompt: buildAgentPrompt(input.tool, input.request, agent, role),
|
|
193113
194125
|
workspaceDir: input.workspaceDir,
|
|
193114
|
-
timeoutMs: input.request.options?.maxAgentTimeoutMs ?? agentConfig.timeoutMs ?? DEFAULT_AGENT_TIMEOUT_MS,
|
|
194126
|
+
timeoutMs: Math.min(input.request.options?.maxAgentTimeoutMs ?? Number.POSITIVE_INFINITY, agentConfig.timeoutMs ?? DEFAULT_AGENT_TIMEOUT_MS, input.budgetTracker.remainingWallTimeMs()),
|
|
194127
|
+
deadlineAtEpochMs: input.budgetTracker.deadlineAtEpochMs,
|
|
194128
|
+
maxOutputBytes: input.budgetTracker.budget.maxAgentOutputBytes,
|
|
193115
194129
|
networkMode: input.networkMode,
|
|
193116
194130
|
onStarted: () => {
|
|
194131
|
+
input.budgetTracker.markStarted(reservation);
|
|
193117
194132
|
if (!acceptingStartedEvents)
|
|
193118
194133
|
return Promise.resolve();
|
|
193119
194134
|
const event = {
|
|
@@ -193141,10 +194156,61 @@ async function runAgents(input) {
|
|
|
193141
194156
|
}
|
|
193142
194157
|
};
|
|
193143
194158
|
});
|
|
193144
|
-
|
|
194159
|
+
let results;
|
|
194160
|
+
try {
|
|
194161
|
+
results = await input.manager.runAll(agentInputs);
|
|
194162
|
+
} catch (error51) {
|
|
194163
|
+
const detail = sanitizeTextForDisplay(error51 instanceof Error ? error51.message : String(error51));
|
|
194164
|
+
input.warnings.push(`Primary-agent execution failed: ${detail}`);
|
|
194165
|
+
results = agentInputs.map((agentInput) => ({
|
|
194166
|
+
agent: agentInput.agent,
|
|
194167
|
+
role: agentInput.role,
|
|
194168
|
+
status: "failed",
|
|
194169
|
+
startedAt: new Date().toISOString(),
|
|
194170
|
+
completedAt: new Date().toISOString(),
|
|
194171
|
+
error: {
|
|
194172
|
+
code: "AGENT_MANAGER_FAILED",
|
|
194173
|
+
message: "The agent manager did not return a review result."
|
|
194174
|
+
}
|
|
194175
|
+
}));
|
|
194176
|
+
}
|
|
193145
194177
|
acceptingStartedEvents = false;
|
|
193146
194178
|
await Promise.all(startedWrites);
|
|
193147
|
-
|
|
194179
|
+
const resultByAgent = new Map(results.map((result) => [result.agent, result]));
|
|
194180
|
+
const orderedResults = enabledAgents.map((agent) => {
|
|
194181
|
+
const existing = resultByAgent.get(agent);
|
|
194182
|
+
if (existing)
|
|
194183
|
+
return existing;
|
|
194184
|
+
const role = agentRoles[agent] ?? input.config.agents[agent].role;
|
|
194185
|
+
const timestamp = new Date().toISOString();
|
|
194186
|
+
return {
|
|
194187
|
+
agent,
|
|
194188
|
+
role,
|
|
194189
|
+
status: "failed",
|
|
194190
|
+
startedAt: timestamp,
|
|
194191
|
+
completedAt: timestamp,
|
|
194192
|
+
error: {
|
|
194193
|
+
code: "AGENT_RESULT_MISSING",
|
|
194194
|
+
message: "The agent manager did not return a review result."
|
|
194195
|
+
}
|
|
194196
|
+
};
|
|
194197
|
+
});
|
|
194198
|
+
for (const result of orderedResults) {
|
|
194199
|
+
const reservation = reservations.get(result.agent);
|
|
194200
|
+
if (!reservation)
|
|
194201
|
+
continue;
|
|
194202
|
+
await finalizeModelCallResult({
|
|
194203
|
+
budgetTracker: input.budgetTracker,
|
|
194204
|
+
reservation,
|
|
194205
|
+
result,
|
|
194206
|
+
trace: input.trace,
|
|
194207
|
+
traceId: input.traceId
|
|
194208
|
+
});
|
|
194209
|
+
if (result.status !== "completed") {
|
|
194210
|
+
input.budgetTracker.markIncomplete("coverage_incomplete");
|
|
194211
|
+
}
|
|
194212
|
+
}
|
|
194213
|
+
await Promise.all(orderedResults.map((result) => {
|
|
193148
194214
|
const event = {
|
|
193149
194215
|
type: "agent_completed",
|
|
193150
194216
|
traceId: input.traceId,
|
|
@@ -193164,8 +194230,92 @@ async function runAgents(input) {
|
|
|
193164
194230
|
}
|
|
193165
194231
|
return input.trace.write(event);
|
|
193166
194232
|
}));
|
|
194233
|
+
return orderedResults;
|
|
194234
|
+
}
|
|
194235
|
+
async function skipReservedPrimaryAgents(input) {
|
|
194236
|
+
const results = [];
|
|
194237
|
+
for (const agent of input.agents) {
|
|
194238
|
+
const reservation = input.reservations.get(agent);
|
|
194239
|
+
if (reservation)
|
|
194240
|
+
input.budgetTracker.skip(reservation, input.reason);
|
|
194241
|
+
await input.trace.write({
|
|
194242
|
+
type: "model_call_skipped",
|
|
194243
|
+
traceId: input.traceId,
|
|
194244
|
+
kind: "primary",
|
|
194245
|
+
agent,
|
|
194246
|
+
reason: input.reason,
|
|
194247
|
+
timestamp: new Date().toISOString()
|
|
194248
|
+
});
|
|
194249
|
+
const timestamp = new Date().toISOString();
|
|
194250
|
+
results.push({
|
|
194251
|
+
agent,
|
|
194252
|
+
role: input.agentRoles[agent] ?? input.config.agents[agent].role,
|
|
194253
|
+
status: "skipped",
|
|
194254
|
+
startedAt: timestamp,
|
|
194255
|
+
completedAt: timestamp,
|
|
194256
|
+
error: {
|
|
194257
|
+
code: "REVIEW_DEADLINE_EXCEEDED",
|
|
194258
|
+
message: "Review deadline was reached before the agent could start."
|
|
194259
|
+
}
|
|
194260
|
+
});
|
|
194261
|
+
}
|
|
193167
194262
|
return results;
|
|
193168
194263
|
}
|
|
194264
|
+
async function finalizeModelCallResult(input) {
|
|
194265
|
+
const reason = input.result.error?.code ?? input.result.status;
|
|
194266
|
+
const hasStarted = input.budgetTracker.hasStarted(input.reservation);
|
|
194267
|
+
const canSkip = input.result.status === "skipped" || isPreflightAgentFailure(input.result) || !hasStarted && input.result.error?.code === "REVIEW_DEADLINE_EXCEEDED";
|
|
194268
|
+
if (canSkip && !hasStarted) {
|
|
194269
|
+
input.budgetTracker.skip(input.reservation, reason);
|
|
194270
|
+
if (input.result.error?.code === "REVIEW_DEADLINE_EXCEEDED") {
|
|
194271
|
+
input.budgetTracker.markIncomplete("deadline");
|
|
194272
|
+
}
|
|
194273
|
+
await input.trace.write({
|
|
194274
|
+
type: "model_call_skipped",
|
|
194275
|
+
traceId: input.traceId,
|
|
194276
|
+
kind: input.reservation.kind,
|
|
194277
|
+
agent: input.reservation.agent,
|
|
194278
|
+
reason,
|
|
194279
|
+
timestamp: new Date().toISOString()
|
|
194280
|
+
});
|
|
194281
|
+
return;
|
|
194282
|
+
}
|
|
194283
|
+
input.budgetTracker.markStarted(input.reservation);
|
|
194284
|
+
const usage = normalizeModelTokenUsage(input.result.usage);
|
|
194285
|
+
const outputBytes = input.result.outputBytes ?? (input.result.rawText ? Buffer.byteLength(input.result.rawText, "utf8") : undefined);
|
|
194286
|
+
input.budgetTracker.complete(input.reservation, {
|
|
194287
|
+
...outputBytes === undefined ? {} : { outputBytes },
|
|
194288
|
+
...usage ? { usage } : {},
|
|
194289
|
+
...input.result.stopReason ? { stopReason: input.result.stopReason } : {}
|
|
194290
|
+
});
|
|
194291
|
+
if (input.result.error?.code === "AGENT_OUTPUT_LIMIT") {
|
|
194292
|
+
input.budgetTracker.markIncomplete("agent_output_limit");
|
|
194293
|
+
}
|
|
194294
|
+
if (input.result.error?.code === "REVIEW_DEADLINE_EXCEEDED") {
|
|
194295
|
+
input.budgetTracker.markIncomplete("deadline");
|
|
194296
|
+
}
|
|
194297
|
+
await input.trace.write({
|
|
194298
|
+
type: "model_call_completed",
|
|
194299
|
+
traceId: input.traceId,
|
|
194300
|
+
kind: input.reservation.kind,
|
|
194301
|
+
agent: input.reservation.agent,
|
|
194302
|
+
resultStatus: input.result.status,
|
|
194303
|
+
...input.result.error?.code ? { errorCode: input.result.error.code } : {},
|
|
194304
|
+
...outputBytes === undefined ? {} : { outputBytes },
|
|
194305
|
+
...usage ? { usage } : {},
|
|
194306
|
+
...input.result.stopReason ? { stopReason: input.result.stopReason } : {},
|
|
194307
|
+
timestamp: new Date().toISOString()
|
|
194308
|
+
});
|
|
194309
|
+
}
|
|
194310
|
+
function isPreflightAgentFailure(result) {
|
|
194311
|
+
return result.status === "failed" && [
|
|
194312
|
+
"AGENT_CONFIG_INVALID",
|
|
194313
|
+
"OPENROUTER_KEY_MISSING",
|
|
194314
|
+
"AGENT_SPAWN_FAILED",
|
|
194315
|
+
"AGENT_MANAGER_FAILED",
|
|
194316
|
+
"AGENT_RESULT_MISSING"
|
|
194317
|
+
].includes(result.error?.code ?? "");
|
|
194318
|
+
}
|
|
193169
194319
|
function resolveAgentRoles(config2) {
|
|
193170
194320
|
const enabledAgents = ["codex", "claude"].filter((agent) => config2.agents[agent].enabled);
|
|
193171
194321
|
const singleAgentMode = enabledAgents.length === 1;
|
|
@@ -193181,14 +194331,30 @@ function defaultAgentManager(config2, parentEnv) {
|
|
|
193181
194331
|
}
|
|
193182
194332
|
return new SubprocessAcpAgentManager(config2, parentEnv);
|
|
193183
194333
|
}
|
|
193184
|
-
function normalizeAgentRunResult(result) {
|
|
193185
|
-
|
|
193186
|
-
|
|
193187
|
-
|
|
193188
|
-
|
|
193189
|
-
|
|
193190
|
-
|
|
193191
|
-
|
|
194334
|
+
function normalizeAgentRunResult(result, maxFindingsPerAgent) {
|
|
194335
|
+
const normalizedResult = result.status === "completed" && result.rawText && !result.normalized ? {
|
|
194336
|
+
...result,
|
|
194337
|
+
normalized: normalizeAgentOutput(result.agent, result.role, result.rawText)
|
|
194338
|
+
} : result;
|
|
194339
|
+
const normalized = normalizedResult.normalized;
|
|
194340
|
+
const findings = normalized?.findings;
|
|
194341
|
+
if (!normalized || !findings || findings.length <= maxFindingsPerAgent) {
|
|
194342
|
+
return { result: normalizedResult, findingsCapped: false };
|
|
194343
|
+
}
|
|
194344
|
+
const limitedFindings = findings.map((finding, index) => ({ finding, index })).sort((left, right) => {
|
|
194345
|
+
const severity = compareSeverity(left.finding.severity, right.finding.severity);
|
|
194346
|
+
return severity === 0 ? left.index - right.index : severity;
|
|
194347
|
+
}).slice(0, maxFindingsPerAgent).map(({ finding }) => finding);
|
|
194348
|
+
return {
|
|
194349
|
+
result: {
|
|
194350
|
+
...normalizedResult,
|
|
194351
|
+
normalized: {
|
|
194352
|
+
...normalized,
|
|
194353
|
+
findings: limitedFindings
|
|
194354
|
+
}
|
|
194355
|
+
},
|
|
194356
|
+
findingsCapped: true
|
|
194357
|
+
};
|
|
193192
194358
|
}
|
|
193193
194359
|
function agentOpinionSummary(result, includeRawText = false) {
|
|
193194
194360
|
const opinion = {
|
|
@@ -193210,8 +194376,12 @@ async function buildSecretBlockResult(input) {
|
|
|
193210
194376
|
});
|
|
193211
194377
|
const cisa = input.tool === "security_review" ? computeCisaGate([finding], []) : undefined;
|
|
193212
194378
|
const completedAt = new Date().toISOString();
|
|
194379
|
+
const budget = input.budgetTracker.snapshot();
|
|
193213
194380
|
const resultWithoutMarkdown = {
|
|
193214
194381
|
decision: "block",
|
|
194382
|
+
completion: budget.completion,
|
|
194383
|
+
executionBudget: budget.executionBudget,
|
|
194384
|
+
requestFingerprint: input.requestFingerprint,
|
|
193215
194385
|
degraded: false,
|
|
193216
194386
|
agentsUsed: [],
|
|
193217
194387
|
reviewMode: "multi_agent",
|
|
@@ -193247,9 +194417,16 @@ async function buildSecretBlockResult(input) {
|
|
|
193247
194417
|
networkMode: input.networkMode,
|
|
193248
194418
|
workspaceMode: "temp_snapshot",
|
|
193249
194419
|
configHash: input.configHash,
|
|
193250
|
-
warnings: input.warnings
|
|
194420
|
+
warnings: input.warnings,
|
|
194421
|
+
modelCalls: budget.modelCalls
|
|
193251
194422
|
}
|
|
193252
194423
|
};
|
|
194424
|
+
await writeReviewBudgetCompleted({
|
|
194425
|
+
trace: input.trace,
|
|
194426
|
+
traceId: input.traceId,
|
|
194427
|
+
budgetTracker: input.budgetTracker,
|
|
194428
|
+
requestFingerprint: input.requestFingerprint
|
|
194429
|
+
});
|
|
193253
194430
|
await input.trace.write({
|
|
193254
194431
|
type: "decision_completed",
|
|
193255
194432
|
traceId: input.traceId,
|
|
@@ -193292,8 +194469,12 @@ function reindexFindings(findings) {
|
|
|
193292
194469
|
}
|
|
193293
194470
|
async function buildPolicyBlockResult(input) {
|
|
193294
194471
|
const completedAt = new Date().toISOString();
|
|
194472
|
+
const budget = input.budgetTracker.snapshot();
|
|
193295
194473
|
const resultWithoutMarkdown = {
|
|
193296
194474
|
decision: "block",
|
|
194475
|
+
completion: budget.completion,
|
|
194476
|
+
executionBudget: budget.executionBudget,
|
|
194477
|
+
requestFingerprint: input.requestFingerprint,
|
|
193297
194478
|
degraded: false,
|
|
193298
194479
|
agentsUsed: [],
|
|
193299
194480
|
reviewMode: "multi_agent",
|
|
@@ -193312,9 +194493,16 @@ async function buildPolicyBlockResult(input) {
|
|
|
193312
194493
|
networkMode: input.networkMode,
|
|
193313
194494
|
workspaceMode: "temp_snapshot",
|
|
193314
194495
|
configHash: input.configHash,
|
|
193315
|
-
warnings: [input.warning]
|
|
194496
|
+
warnings: [input.warning],
|
|
194497
|
+
modelCalls: budget.modelCalls
|
|
193316
194498
|
}
|
|
193317
194499
|
};
|
|
194500
|
+
await writeReviewBudgetCompleted({
|
|
194501
|
+
trace: input.trace,
|
|
194502
|
+
traceId: input.traceId,
|
|
194503
|
+
budgetTracker: input.budgetTracker,
|
|
194504
|
+
requestFingerprint: input.requestFingerprint
|
|
194505
|
+
});
|
|
193318
194506
|
await input.trace.write({
|
|
193319
194507
|
type: "decision_completed",
|
|
193320
194508
|
traceId: input.traceId,
|
|
@@ -193351,6 +194539,33 @@ async function finalizeReviewResult(input) {
|
|
|
193351
194539
|
})
|
|
193352
194540
|
};
|
|
193353
194541
|
}
|
|
194542
|
+
async function writeReviewBudgetPlanned(input) {
|
|
194543
|
+
const snapshot = input.budgetTracker.snapshot();
|
|
194544
|
+
await input.trace.write({
|
|
194545
|
+
type: "review_budget_planned",
|
|
194546
|
+
traceId: input.traceId,
|
|
194547
|
+
requestFingerprint: input.requestFingerprint,
|
|
194548
|
+
maxModelCalls: snapshot.executionBudget.maxModelCalls,
|
|
194549
|
+
maxTotalWallTimeMs: snapshot.executionBudget.wallTime.limitMs,
|
|
194550
|
+
maxAgentOutputBytes: snapshot.executionBudget.maxAgentOutputBytes,
|
|
194551
|
+
maxFindingsPerAgent: snapshot.executionBudget.maxFindingsPerAgent,
|
|
194552
|
+
skipOptionalPhasesWhenTokenUsageUnknown: snapshot.executionBudget.skipOptionalPhasesWhenTokenUsageUnknown,
|
|
194553
|
+
timestamp: new Date().toISOString()
|
|
194554
|
+
});
|
|
194555
|
+
}
|
|
194556
|
+
async function writeReviewBudgetCompleted(input) {
|
|
194557
|
+
const snapshot = input.budgetTracker.snapshot();
|
|
194558
|
+
await input.trace.write({
|
|
194559
|
+
type: "review_budget_completed",
|
|
194560
|
+
traceId: input.traceId,
|
|
194561
|
+
requestFingerprint: input.requestFingerprint,
|
|
194562
|
+
completion: snapshot.completion,
|
|
194563
|
+
modelCalls: snapshot.executionBudget.modelCalls,
|
|
194564
|
+
wallTime: snapshot.executionBudget.wallTime,
|
|
194565
|
+
tokenUsage: snapshot.executionBudget.tokenUsage,
|
|
194566
|
+
timestamp: new Date().toISOString()
|
|
194567
|
+
});
|
|
194568
|
+
}
|
|
193354
194569
|
function mergeDenyPatterns(configDeny, requestDeny) {
|
|
193355
194570
|
return Array.from(new Set([...configDeny, ...requestDeny ?? []]));
|
|
193356
194571
|
}
|