@kyo-so/cli 0.9.1 → 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 +63 -0
- package/README.ja.md +214 -132
- package/README.md +214 -135
- package/README.zh-CN.md +214 -132
- package/dist/acp/AcpAgentProcess.d.ts +2 -1
- package/dist/acp/FakeAgentManager.d.ts +1 -1
- package/dist/bin/kyoso.js +27044 -25063
- package/dist/cli/knownSkillDigests.d.ts +1 -1
- package/dist/cli/openRouterAcpSmoke.d.ts +25 -0
- package/dist/cli/pluginRuntimeContract.d.ts +10 -8
- package/dist/cli/setup.d.ts +3 -2
- package/dist/config/loadConfig.d.ts +19 -0
- package/dist/config/projectScope.d.ts +1 -1
- package/dist/config/schema.d.ts +16 -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 +71 -0
- package/dist/core/verification.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1879 -195
- 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/dist/utils/env.d.ts +13 -0
- package/examples/codex-config.toml +4 -1
- package/examples/kyoso.toml +14 -0
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -169500,6 +169500,9 @@ function defineConfig(config) {
|
|
|
169500
169500
|
// src/core/runReview.ts
|
|
169501
169501
|
import { resolve as resolve6 } from "node:path";
|
|
169502
169502
|
|
|
169503
|
+
// src/config/schema.ts
|
|
169504
|
+
import { isAbsolute } from "node:path";
|
|
169505
|
+
|
|
169503
169506
|
// node_modules/zod/v4/classic/external.js
|
|
169504
169507
|
var exports_external = {};
|
|
169505
169508
|
__export(exports_external, {
|
|
@@ -183776,8 +183779,19 @@ function date4(params) {
|
|
|
183776
183779
|
|
|
183777
183780
|
// node_modules/zod/v4/classic/external.js
|
|
183778
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
|
+
|
|
183779
183790
|
// src/config/schema.ts
|
|
183780
|
-
var
|
|
183791
|
+
var CODEX_OPENROUTER_PROVIDER = "openrouter";
|
|
183792
|
+
var CODEX_DEFAULT_PROVIDER = "default";
|
|
183793
|
+
var CODEX_OPENROUTER_MODEL_REQUIRED_ISSUE = "codex_openrouter_model_required";
|
|
183794
|
+
var baseAgentSchema = exports_external.object({
|
|
183781
183795
|
enabled: exports_external.boolean().default(true),
|
|
183782
183796
|
type: exports_external.literal("acp").default("acp"),
|
|
183783
183797
|
command: exports_external.string(),
|
|
@@ -183799,6 +183813,31 @@ var agentSchema = exports_external.object({
|
|
|
183799
183813
|
envWhitelist: exports_external.array(exports_external.string())
|
|
183800
183814
|
})
|
|
183801
183815
|
});
|
|
183816
|
+
var codexAgentSchema = baseAgentSchema.extend({
|
|
183817
|
+
provider: exports_external.enum([CODEX_OPENROUTER_PROVIDER, CODEX_DEFAULT_PROVIDER]).optional(),
|
|
183818
|
+
allowProjectProvider: exports_external.array(exports_external.string().min(1).refine(isAbsolute, {
|
|
183819
|
+
message: "must contain only absolute project directory paths for exact matching"
|
|
183820
|
+
})).default([])
|
|
183821
|
+
}).superRefine((agent, context) => {
|
|
183822
|
+
if (agent.provider !== CODEX_OPENROUTER_PROVIDER || (agent.model?.trim().length ?? 0) > 0) {
|
|
183823
|
+
return;
|
|
183824
|
+
}
|
|
183825
|
+
context.addIssue({
|
|
183826
|
+
code: exports_external.ZodIssueCode.custom,
|
|
183827
|
+
path: ["model"],
|
|
183828
|
+
message: 'model must be a non-empty string when provider is "openrouter".',
|
|
183829
|
+
params: {
|
|
183830
|
+
kyosoIssue: CODEX_OPENROUTER_MODEL_REQUIRED_ISSUE
|
|
183831
|
+
}
|
|
183832
|
+
});
|
|
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
|
+
});
|
|
183802
183841
|
var kyosoConfigSchema = exports_external.object({
|
|
183803
183842
|
entrypoints: exports_external.object({
|
|
183804
183843
|
mcp: exports_external.boolean(),
|
|
@@ -183811,8 +183850,8 @@ var kyosoConfigSchema = exports_external.object({
|
|
|
183811
183850
|
diffReview: exports_external.boolean()
|
|
183812
183851
|
}),
|
|
183813
183852
|
agents: exports_external.object({
|
|
183814
|
-
codex:
|
|
183815
|
-
claude:
|
|
183853
|
+
codex: codexAgentSchema,
|
|
183854
|
+
claude: baseAgentSchema
|
|
183816
183855
|
}),
|
|
183817
183856
|
workspace: exports_external.object({
|
|
183818
183857
|
mode: exports_external.literal("temp_snapshot"),
|
|
@@ -183856,6 +183895,7 @@ var kyosoConfigSchema = exports_external.object({
|
|
|
183856
183895
|
timeoutMs: exports_external.number().int().positive().default(90000),
|
|
183857
183896
|
allowDemotion: exports_external.boolean().default(false)
|
|
183858
183897
|
}),
|
|
183898
|
+
reviewBudget: reviewBudgetSchema,
|
|
183859
183899
|
audit: exports_external.object({
|
|
183860
183900
|
enabled: exports_external.boolean(),
|
|
183861
183901
|
format: exports_external.literal("jsonl"),
|
|
@@ -183863,9 +183903,18 @@ var kyosoConfigSchema = exports_external.object({
|
|
|
183863
183903
|
includeRawAgentOutput: exports_external.boolean(),
|
|
183864
183904
|
includeFileContents: exports_external.boolean()
|
|
183865
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
|
+
});
|
|
183866
183915
|
});
|
|
183867
183916
|
function agentConfigLeafPaths(agent) {
|
|
183868
|
-
|
|
183917
|
+
const paths = [
|
|
183869
183918
|
`agents.${agent}.enabled`,
|
|
183870
183919
|
`agents.${agent}.type`,
|
|
183871
183920
|
`agents.${agent}.command`,
|
|
@@ -183881,6 +183930,10 @@ function agentConfigLeafPaths(agent) {
|
|
|
183881
183930
|
`agents.${agent}.auth.recommendedEnv`,
|
|
183882
183931
|
`agents.${agent}.auth.envWhitelist`
|
|
183883
183932
|
];
|
|
183933
|
+
if (agent === "codex") {
|
|
183934
|
+
paths.push("agents.codex.provider", "agents.codex.allowProjectProvider");
|
|
183935
|
+
}
|
|
183936
|
+
return paths;
|
|
183884
183937
|
}
|
|
183885
183938
|
var kyosoConfigKnownLeafPaths = [
|
|
183886
183939
|
"entrypoints.mcp",
|
|
@@ -183917,6 +183970,11 @@ var kyosoConfigKnownLeafPaths = [
|
|
|
183917
183970
|
"verification.maxFindings",
|
|
183918
183971
|
"verification.timeoutMs",
|
|
183919
183972
|
"verification.allowDemotion",
|
|
183973
|
+
"reviewBudget.maxModelCalls",
|
|
183974
|
+
"reviewBudget.maxTotalWallTimeMs",
|
|
183975
|
+
"reviewBudget.maxAgentOutputBytes",
|
|
183976
|
+
"reviewBudget.maxFindingsPerAgent",
|
|
183977
|
+
"reviewBudget.skipOptionalPhasesWhenTokenUsageUnknown",
|
|
183920
183978
|
"audit.enabled",
|
|
183921
183979
|
"audit.format",
|
|
183922
183980
|
"audit.directory",
|
|
@@ -183936,6 +183994,7 @@ var kyosoConfigSecuritySensitivePrefixes = [
|
|
|
183936
183994
|
"secrets",
|
|
183937
183995
|
"securityReview",
|
|
183938
183996
|
"verification",
|
|
183997
|
+
"reviewBudget",
|
|
183939
183998
|
"workspace"
|
|
183940
183999
|
];
|
|
183941
184000
|
|
|
@@ -183956,6 +184015,7 @@ var defaultConfig = {
|
|
|
183956
184015
|
args: ["-y", "@agentclientprotocol/codex-acp@1.1.2"],
|
|
183957
184016
|
role: "implementation_reviewer",
|
|
183958
184017
|
timeoutMs: 120000,
|
|
184018
|
+
allowProjectProvider: [],
|
|
183959
184019
|
env: {
|
|
183960
184020
|
INITIAL_AGENT_MODE: "read-only",
|
|
183961
184021
|
KYOSO_CHILD_AGENT: "1"
|
|
@@ -184048,7 +184108,7 @@ var defaultConfig = {
|
|
|
184048
184108
|
}
|
|
184049
184109
|
},
|
|
184050
184110
|
judge: {
|
|
184051
|
-
mode: "
|
|
184111
|
+
mode: "deterministic_only",
|
|
184052
184112
|
provider: "auto",
|
|
184053
184113
|
timeoutMs: 60000
|
|
184054
184114
|
},
|
|
@@ -184058,6 +184118,13 @@ var defaultConfig = {
|
|
|
184058
184118
|
timeoutMs: 90000,
|
|
184059
184119
|
allowDemotion: false
|
|
184060
184120
|
},
|
|
184121
|
+
reviewBudget: {
|
|
184122
|
+
maxModelCalls: 4,
|
|
184123
|
+
maxTotalWallTimeMs: 480000,
|
|
184124
|
+
maxAgentOutputBytes: 65536,
|
|
184125
|
+
maxFindingsPerAgent: 10,
|
|
184126
|
+
skipOptionalPhasesWhenTokenUsageUnknown: true
|
|
184127
|
+
},
|
|
184061
184128
|
audit: {
|
|
184062
184129
|
enabled: true,
|
|
184063
184130
|
format: "jsonl",
|
|
@@ -184068,17 +184135,21 @@ var defaultConfig = {
|
|
|
184068
184135
|
};
|
|
184069
184136
|
|
|
184070
184137
|
// src/config/loadConfig.ts
|
|
184071
|
-
import { access, readFile as readFile3 } from "node:fs/promises";
|
|
184138
|
+
import { access, readFile as readFile3, realpath } from "node:fs/promises";
|
|
184072
184139
|
import { homedir as homedir2 } from "node:os";
|
|
184073
184140
|
import { stderr, stdin } from "node:process";
|
|
184074
|
-
import { extname as extname2, join as join2, resolve as resolve2 } from "node:path";
|
|
184141
|
+
import { dirname as dirname3, extname as extname2, isAbsolute as isAbsolute2, join as join2, resolve as resolve2 } from "node:path";
|
|
184075
184142
|
import { createInterface } from "node:readline/promises";
|
|
184076
184143
|
|
|
184077
184144
|
// src/config/projectScope.ts
|
|
184078
184145
|
var PROJECT_GLOBAL_ONLY_MESSAGE = "Move global-only settings to the user global config:";
|
|
184146
|
+
var PROJECT_GLOBAL_ONLY_REASONS = {
|
|
184147
|
+
"agents.codex.allowProjectProvider": "must be a user-global exact project-directory allowlist"
|
|
184148
|
+
};
|
|
184079
184149
|
var kyosoConfigOverridePaths = [
|
|
184080
184150
|
"agents.codex.enabled",
|
|
184081
184151
|
"agents.codex.model",
|
|
184152
|
+
"agents.codex.provider",
|
|
184082
184153
|
"agents.codex.effort",
|
|
184083
184154
|
"agents.codex.role",
|
|
184084
184155
|
"agents.codex.timeoutMs",
|
|
@@ -184118,6 +184189,11 @@ function collectProjectScopeViolations(config2) {
|
|
|
184118
184189
|
const violations = [];
|
|
184119
184190
|
for (const leaf of leaves) {
|
|
184120
184191
|
const path = leaf.path.join(".");
|
|
184192
|
+
const globalOnlyReason = projectGlobalOnlyReason(leaf.path);
|
|
184193
|
+
if (globalOnlyReason) {
|
|
184194
|
+
violations.push({ path, reason: globalOnlyReason });
|
|
184195
|
+
continue;
|
|
184196
|
+
}
|
|
184121
184197
|
if (!isAllowedProjectPath(leaf.path)) {
|
|
184122
184198
|
violations.push({ path });
|
|
184123
184199
|
continue;
|
|
@@ -184128,6 +184204,15 @@ function collectProjectScopeViolations(config2) {
|
|
|
184128
184204
|
}
|
|
184129
184205
|
return violations.sort((left, right) => left.path.localeCompare(right.path));
|
|
184130
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
|
+
}
|
|
184131
184216
|
function isAllowedProjectPath(path) {
|
|
184132
184217
|
const [top, second, third, fourth] = path;
|
|
184133
184218
|
if (isAllowedConfigOverridePath(path))
|
|
@@ -184258,12 +184343,6 @@ function isRecord(value) {
|
|
|
184258
184343
|
// src/security/redact.ts
|
|
184259
184344
|
var REDACTION = "[KYOSO_REDACTED]";
|
|
184260
184345
|
|
|
184261
|
-
// src/core/constants.ts
|
|
184262
|
-
var DEFAULT_AGENT_TIMEOUT_MS = 120000;
|
|
184263
|
-
var RAW_OUTPUT_MAX_CHARS = 16384;
|
|
184264
|
-
var TRACE_DIR = ".kyoso/traces";
|
|
184265
|
-
var KYOSO_CHILD_AGENT = "KYOSO_CHILD_AGENT";
|
|
184266
|
-
|
|
184267
184346
|
// src/security/sanitizeText.ts
|
|
184268
184347
|
var SENSITIVE_TEXT_PATTERNS = [
|
|
184269
184348
|
/\bsk-(?:proj-)?[A-Za-z0-9_-]{8,}\b/g,
|
|
@@ -184278,7 +184357,7 @@ function sanitizeText(value) {
|
|
|
184278
184357
|
return SENSITIVE_TEXT_PATTERNS.reduce((text, pattern) => text.replace(pattern, REDACTION), value);
|
|
184279
184358
|
}
|
|
184280
184359
|
function sanitizeTextForDisplay(value, maxChars = 240) {
|
|
184281
|
-
const compact = sanitizeText(value).replace(/\s+/g, " ").trim();
|
|
184360
|
+
const compact = sanitizeText(value).replace(/\u001b\[[0-?]*[ -/]*[@-~]/g, "").replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, "").replace(/\s+/g, " ").trim();
|
|
184282
184361
|
if (compact.length <= maxChars)
|
|
184283
184362
|
return compact;
|
|
184284
184363
|
return `${compact.slice(0, Math.max(0, maxChars - 3))}...`;
|
|
@@ -185322,6 +185401,25 @@ function isMissingPathError(error51) {
|
|
|
185322
185401
|
}
|
|
185323
185402
|
|
|
185324
185403
|
// src/config/loadConfig.ts
|
|
185404
|
+
var configValidationContexts = new WeakMap;
|
|
185405
|
+
class ProjectOpenRouterAuthorizationError extends Error {
|
|
185406
|
+
code = "PROJECT_OPENROUTER_AUTHORIZATION_REQUIRED";
|
|
185407
|
+
projectPath;
|
|
185408
|
+
projectDirectory;
|
|
185409
|
+
layer;
|
|
185410
|
+
globalConfigPath;
|
|
185411
|
+
constructor(input) {
|
|
185412
|
+
const projectPath = sanitizeWarningText(input.projectPath);
|
|
185413
|
+
const projectDirectory = sanitizeWarningText(input.projectDirectory);
|
|
185414
|
+
const globalConfigPath = sanitizeWarningText(input.globalConfigPath);
|
|
185415
|
+
super(`Project config ${projectPath} changes Codex OpenRouter routing, but its directory ${projectDirectory} is not in the user-global allowlist. Add ${JSON.stringify(projectDirectory)} to agents.codex.allowProjectProvider in ${globalConfigPath} to permit project-level external provider routing.`);
|
|
185416
|
+
this.name = "ProjectOpenRouterAuthorizationError";
|
|
185417
|
+
this.projectPath = projectPath;
|
|
185418
|
+
this.projectDirectory = projectDirectory;
|
|
185419
|
+
this.layer = input.layer;
|
|
185420
|
+
this.globalConfigPath = globalConfigPath;
|
|
185421
|
+
}
|
|
185422
|
+
}
|
|
185325
185423
|
var KNOWN_GLOBAL_CONFIG_LEAF_PATHS = new Set(kyosoConfigKnownLeafPaths);
|
|
185326
185424
|
var GLOBAL_CONFIG_RECORD_PREFIXES = kyosoConfigRecordPrefixes.map((path) => path.split("."));
|
|
185327
185425
|
var SECURITY_SENSITIVE_GLOBAL_PREFIXES = kyosoConfigSecuritySensitivePrefixes.map((path) => path.split("."));
|
|
@@ -185349,11 +185447,12 @@ async function loadConfig(options = {}) {
|
|
|
185349
185447
|
}
|
|
185350
185448
|
if (options.configPath) {
|
|
185351
185449
|
const explicitConfigPath = resolve2(cwd, options.configPath);
|
|
185352
|
-
|
|
185450
|
+
const projectConfig = await resolveProjectConfigIdentity(explicitConfigPath);
|
|
185451
|
+
if (!projectConfig) {
|
|
185353
185452
|
throw new Error(`Config file not found: ${explicitConfigPath} (from --config)`);
|
|
185354
185453
|
}
|
|
185355
185454
|
const loaded = await loadProjectConfig({
|
|
185356
|
-
|
|
185455
|
+
projectConfig,
|
|
185357
185456
|
baseConfig: mergedConfig,
|
|
185358
185457
|
globalConfigPath,
|
|
185359
185458
|
options
|
|
@@ -185367,19 +185466,29 @@ async function loadConfig(options = {}) {
|
|
|
185367
185466
|
} else {
|
|
185368
185467
|
const projectTomlPath = resolve2(cwd, "kyoso.toml");
|
|
185369
185468
|
const projectTsPath = resolve2(cwd, "kyoso.config.ts");
|
|
185370
|
-
const
|
|
185371
|
-
const
|
|
185372
|
-
if (
|
|
185373
|
-
|
|
185374
|
-
|
|
185375
|
-
|
|
185376
|
-
|
|
185469
|
+
const projectToml = await resolveProjectConfigIdentity(projectTomlPath);
|
|
185470
|
+
const projectTs = await resolveProjectConfigIdentity(projectTsPath);
|
|
185471
|
+
if (projectToml) {
|
|
185472
|
+
const loaded = await loadProjectConfig({
|
|
185473
|
+
projectConfig: projectToml,
|
|
185474
|
+
baseConfig: mergedConfig,
|
|
185475
|
+
globalConfigPath,
|
|
185476
|
+
options
|
|
185477
|
+
});
|
|
185478
|
+
mergedConfig = loaded.mergedConfig;
|
|
185479
|
+
configPath = loaded.configPath;
|
|
185480
|
+
configHash = loaded.configHash;
|
|
185481
|
+
configTrustStatus = loaded.configTrustStatus;
|
|
185482
|
+
sources.push(loaded.source);
|
|
185483
|
+
warnings.push(...loaded.warnings);
|
|
185484
|
+
if (projectTs) {
|
|
185377
185485
|
warnings.push(`kyoso.config.ts was ignored because kyoso.toml takes precedence: ${projectTsPath}`);
|
|
185378
185486
|
}
|
|
185379
|
-
} else if (
|
|
185487
|
+
} else if (projectTs) {
|
|
185380
185488
|
const loaded = await loadProjectTsConfig({
|
|
185381
|
-
|
|
185489
|
+
projectConfig: projectTs,
|
|
185382
185490
|
baseConfig: mergedConfig,
|
|
185491
|
+
globalConfigPath,
|
|
185383
185492
|
options
|
|
185384
185493
|
});
|
|
185385
185494
|
mergedConfig = loaded.mergedConfig;
|
|
@@ -185391,9 +185500,17 @@ async function loadConfig(options = {}) {
|
|
|
185391
185500
|
}
|
|
185392
185501
|
}
|
|
185393
185502
|
}
|
|
185394
|
-
const parsed = kyosoConfigSchema.
|
|
185503
|
+
const parsed = kyosoConfigSchema.safeParse(mergedConfig);
|
|
185504
|
+
if (!parsed.success) {
|
|
185505
|
+
const source = sources.at(-1);
|
|
185506
|
+
configValidationContexts.set(parsed.error, {
|
|
185507
|
+
source,
|
|
185508
|
+
projectTsExecuted: source?.layer === "project_ts" && isTrustedProjectConfigExecution(configTrustStatus)
|
|
185509
|
+
});
|
|
185510
|
+
throw parsed.error;
|
|
185511
|
+
}
|
|
185395
185512
|
return {
|
|
185396
|
-
config: parsed,
|
|
185513
|
+
config: parsed.data,
|
|
185397
185514
|
configPath,
|
|
185398
185515
|
configHash,
|
|
185399
185516
|
configTrustStatus,
|
|
@@ -185441,62 +185558,170 @@ function pathStartsWith(path, prefix) {
|
|
|
185441
185558
|
return path.length >= prefix.length && prefix.every((part, index) => path[index] === part);
|
|
185442
185559
|
}
|
|
185443
185560
|
async function loadProjectConfig(input) {
|
|
185444
|
-
const
|
|
185561
|
+
const { canonicalDirectory, canonicalPath, requestedPath } = input.projectConfig;
|
|
185562
|
+
const extension = extname2(requestedPath);
|
|
185445
185563
|
if (extension === ".toml") {
|
|
185564
|
+
const projectTomlConfig = await loadTomlConfigFile(canonicalPath);
|
|
185565
|
+
const projectChangesOpenRouterRoute = projectConfigChangesOpenRouterRoute(projectTomlConfig, input.baseConfig);
|
|
185566
|
+
await assertProjectOpenRouterAuthorization({
|
|
185567
|
+
projectConfig: projectTomlConfig,
|
|
185568
|
+
projectPath: requestedPath,
|
|
185569
|
+
projectDirectory: canonicalDirectory,
|
|
185570
|
+
layer: "project_toml",
|
|
185571
|
+
baseConfig: input.baseConfig,
|
|
185572
|
+
globalConfigPath: input.globalConfigPath
|
|
185573
|
+
});
|
|
185574
|
+
const projectMergedConfig = mergeProjectTomlConfig(input.baseConfig, projectTomlConfig, {
|
|
185575
|
+
projectPath: requestedPath,
|
|
185576
|
+
globalConfigPath: input.globalConfigPath
|
|
185577
|
+
});
|
|
185446
185578
|
return {
|
|
185447
|
-
mergedConfig:
|
|
185448
|
-
|
|
185449
|
-
globalConfigPath: input.globalConfigPath
|
|
185450
|
-
}),
|
|
185451
|
-
configPath: input.configPath,
|
|
185579
|
+
mergedConfig: applyProjectCodexProviderReset(input.baseConfig, projectTomlConfig, projectMergedConfig),
|
|
185580
|
+
configPath: requestedPath,
|
|
185452
185581
|
configTrustStatus: "not_found",
|
|
185453
|
-
source: { path:
|
|
185454
|
-
warnings: []
|
|
185582
|
+
source: { path: requestedPath, layer: "project_toml" },
|
|
185583
|
+
warnings: projectChangesOpenRouterRoute ? [openRouterProjectConfigWarning(requestedPath)] : []
|
|
185455
185584
|
};
|
|
185456
185585
|
}
|
|
185457
185586
|
if (extension === ".ts") {
|
|
185458
185587
|
return await loadProjectTsConfig(input);
|
|
185459
185588
|
}
|
|
185460
|
-
throw new Error(`Unsupported config file extension for ${
|
|
185589
|
+
throw new Error(`Unsupported config file extension for ${requestedPath}. Expected .toml or .ts.`);
|
|
185590
|
+
}
|
|
185591
|
+
function projectConfigSelectsOpenRouter(config2) {
|
|
185592
|
+
return flattenLeaves(config2).some((leaf) => leaf.path.join(".") === "agents.codex.provider" && leaf.value === CODEX_OPENROUTER_PROVIDER);
|
|
185593
|
+
}
|
|
185594
|
+
function projectConfigSelectsDefaultProvider(config2) {
|
|
185595
|
+
return flattenLeaves(config2).some((leaf) => leaf.path.join(".") === "agents.codex.provider" && leaf.value === CODEX_DEFAULT_PROVIDER);
|
|
185596
|
+
}
|
|
185597
|
+
function projectConfigSuppliesCodexModel(config2) {
|
|
185598
|
+
return flattenLeaves(config2).some((leaf) => leaf.path.join(".") === "agents.codex.model");
|
|
185599
|
+
}
|
|
185600
|
+
function projectConfigChangesOpenRouterRoute(projectConfig, baseConfig) {
|
|
185601
|
+
return projectConfigSelectsOpenRouter(projectConfig) || configSelectsOpenRouter(baseConfig) && projectConfigSuppliesCodexModel(projectConfig) && !projectConfigSelectsDefaultProvider(projectConfig);
|
|
185602
|
+
}
|
|
185603
|
+
function configSelectsOpenRouter(config2) {
|
|
185604
|
+
return flattenLeaves(config2).some((leaf) => leaf.path.join(".") === "agents.codex.provider" && leaf.value === CODEX_OPENROUTER_PROVIDER);
|
|
185605
|
+
}
|
|
185606
|
+
function applyProjectCodexProviderReset(baseConfig, projectConfig, mergedConfig) {
|
|
185607
|
+
if (!projectConfigSelectsDefaultProvider(projectConfig) || projectConfigSuppliesCodexModel(projectConfig) || !configSelectsOpenRouter(baseConfig) || !isRecord3(mergedConfig) || !isRecord3(mergedConfig.agents) || !isRecord3(mergedConfig.agents.codex)) {
|
|
185608
|
+
return mergedConfig;
|
|
185609
|
+
}
|
|
185610
|
+
const codexWithoutInheritedModel = Object.fromEntries(Object.entries(mergedConfig.agents.codex).filter(([key]) => key !== "model"));
|
|
185611
|
+
return {
|
|
185612
|
+
...mergedConfig,
|
|
185613
|
+
agents: {
|
|
185614
|
+
...mergedConfig.agents,
|
|
185615
|
+
codex: codexWithoutInheritedModel
|
|
185616
|
+
}
|
|
185617
|
+
};
|
|
185618
|
+
}
|
|
185619
|
+
function openRouterProjectConfigWarning(configPath) {
|
|
185620
|
+
return `Project config ${sanitizeWarningText(configPath)} changes Codex OpenRouter routing under user-global authorization; it can route Codex review content through OpenRouter.`;
|
|
185621
|
+
}
|
|
185622
|
+
async function assertProjectOpenRouterAuthorization(input) {
|
|
185623
|
+
if (!projectConfigChangesOpenRouterRoute(input.projectConfig, input.baseConfig)) {
|
|
185624
|
+
return;
|
|
185625
|
+
}
|
|
185626
|
+
if (await projectProviderIsAuthorized(input.baseConfig, input.projectDirectory)) {
|
|
185627
|
+
return;
|
|
185628
|
+
}
|
|
185629
|
+
throw new ProjectOpenRouterAuthorizationError({
|
|
185630
|
+
projectPath: input.projectPath,
|
|
185631
|
+
projectDirectory: input.projectDirectory,
|
|
185632
|
+
layer: input.layer,
|
|
185633
|
+
globalConfigPath: input.globalConfigPath
|
|
185634
|
+
});
|
|
185635
|
+
}
|
|
185636
|
+
async function projectProviderIsAuthorized(config2, projectDirectory) {
|
|
185637
|
+
if (!isRecord3(config2))
|
|
185638
|
+
return false;
|
|
185639
|
+
const agents = config2.agents;
|
|
185640
|
+
if (!isRecord3(agents))
|
|
185641
|
+
return false;
|
|
185642
|
+
const codex = agents.codex;
|
|
185643
|
+
if (!isRecord3(codex) || !Array.isArray(codex.allowProjectProvider)) {
|
|
185644
|
+
return false;
|
|
185645
|
+
}
|
|
185646
|
+
for (const directory of codex.allowProjectProvider) {
|
|
185647
|
+
if (typeof directory !== "string" || !isAbsolute2(directory))
|
|
185648
|
+
continue;
|
|
185649
|
+
const allowedDirectory = await existingRealpath(directory);
|
|
185650
|
+
if (allowedDirectory === projectDirectory)
|
|
185651
|
+
return true;
|
|
185652
|
+
}
|
|
185653
|
+
return false;
|
|
185654
|
+
}
|
|
185655
|
+
async function resolveProjectConfigIdentity(requestedPath) {
|
|
185656
|
+
const canonicalPath = await existingRealpath(requestedPath);
|
|
185657
|
+
return canonicalPath === undefined ? undefined : {
|
|
185658
|
+
requestedPath,
|
|
185659
|
+
canonicalPath,
|
|
185660
|
+
canonicalDirectory: dirname3(canonicalPath)
|
|
185661
|
+
};
|
|
185662
|
+
}
|
|
185663
|
+
async function existingRealpath(path) {
|
|
185664
|
+
try {
|
|
185665
|
+
return await realpath(path);
|
|
185666
|
+
} catch {
|
|
185667
|
+
return;
|
|
185668
|
+
}
|
|
185461
185669
|
}
|
|
185462
185670
|
async function loadProjectTsConfig(input) {
|
|
185671
|
+
const { canonicalDirectory, canonicalPath, requestedPath } = input.projectConfig;
|
|
185463
185672
|
const warnings = [
|
|
185464
185673
|
'kyoso.config.ts is deprecated; migrate to kyoso.toml (see README "Configuration")'
|
|
185465
185674
|
];
|
|
185466
|
-
const source = await readFile3(
|
|
185675
|
+
const source = await readFile3(canonicalPath, "utf8");
|
|
185467
185676
|
const configHash = hashConfigSource(source);
|
|
185468
185677
|
const trustStorePath = input.options.trustStorePath ?? defaultTrustedConfigStorePath(input.options.env);
|
|
185469
|
-
const trusted = await isTrustedConfig(trustStorePath,
|
|
185678
|
+
const trusted = await isTrustedConfig(trustStorePath, canonicalPath, configHash);
|
|
185470
185679
|
const trustDecision = await resolveTrustDecision({
|
|
185471
|
-
configPath:
|
|
185680
|
+
configPath: requestedPath,
|
|
185472
185681
|
configHash,
|
|
185473
185682
|
trusted,
|
|
185474
185683
|
options: input.options
|
|
185475
185684
|
});
|
|
185476
185685
|
if (trustDecision.execute) {
|
|
185477
|
-
const userConfig = await loadUserConfig(
|
|
185686
|
+
const userConfig = await loadUserConfig(canonicalPath, source);
|
|
185687
|
+
const projectChangesOpenRouterRoute = projectConfigChangesOpenRouterRoute(userConfig, input.baseConfig);
|
|
185688
|
+
await assertProjectOpenRouterAuthorization({
|
|
185689
|
+
projectConfig: userConfig,
|
|
185690
|
+
projectPath: requestedPath,
|
|
185691
|
+
projectDirectory: canonicalDirectory,
|
|
185692
|
+
layer: "project_ts",
|
|
185693
|
+
baseConfig: input.baseConfig,
|
|
185694
|
+
globalConfigPath: input.globalConfigPath
|
|
185695
|
+
});
|
|
185478
185696
|
if (trustDecision.shouldPersist) {
|
|
185479
|
-
await trustConfig(trustStorePath,
|
|
185697
|
+
await trustConfig(trustStorePath, canonicalPath, configHash);
|
|
185698
|
+
}
|
|
185699
|
+
const projectMergedConfig = deepMerge2(input.baseConfig, userConfig);
|
|
185700
|
+
if (projectChangesOpenRouterRoute) {
|
|
185701
|
+
warnings.push(openRouterProjectConfigWarning(requestedPath));
|
|
185480
185702
|
}
|
|
185481
185703
|
return {
|
|
185482
|
-
mergedConfig:
|
|
185483
|
-
configPath:
|
|
185704
|
+
mergedConfig: applyProjectCodexProviderReset(input.baseConfig, userConfig, projectMergedConfig),
|
|
185705
|
+
configPath: requestedPath,
|
|
185484
185706
|
configHash,
|
|
185485
185707
|
configTrustStatus: trustDecision.status,
|
|
185486
|
-
source: { path:
|
|
185708
|
+
source: { path: requestedPath, layer: "project_ts" },
|
|
185487
185709
|
warnings
|
|
185488
185710
|
};
|
|
185489
185711
|
}
|
|
185490
|
-
warnings.push(`untrusted config was not executed: ${
|
|
185712
|
+
warnings.push(`untrusted config was not executed: ${requestedPath}; run \`kyoso doctor --trust-config\` or pass \`--trust-config\` once to trust it`);
|
|
185491
185713
|
return {
|
|
185492
185714
|
mergedConfig: input.baseConfig,
|
|
185493
|
-
configPath:
|
|
185715
|
+
configPath: requestedPath,
|
|
185494
185716
|
configHash,
|
|
185495
185717
|
configTrustStatus: trustDecision.status,
|
|
185496
|
-
source: { path:
|
|
185718
|
+
source: { path: requestedPath, layer: "project_ts" },
|
|
185497
185719
|
warnings
|
|
185498
185720
|
};
|
|
185499
185721
|
}
|
|
185722
|
+
function isTrustedProjectConfigExecution(status) {
|
|
185723
|
+
return status === "trusted" || status === "trusted_by_flag" || status === "trusted_interactively";
|
|
185724
|
+
}
|
|
185500
185725
|
async function loadUserConfig(configPath, source) {
|
|
185501
185726
|
try {
|
|
185502
185727
|
const loaded = await loadConfigModule(configPath, source);
|
|
@@ -185575,6 +185800,8 @@ function applyConfigOverrides(config2, assignments) {
|
|
|
185575
185800
|
for (const override of overrides) {
|
|
185576
185801
|
writePath2(overridden, override.path, parseConfigOverrideValue(override.value, readPath2(baseConfig, override.path)));
|
|
185577
185802
|
}
|
|
185803
|
+
clearInheritedOpenRouterModelForProviderReset(baseConfig, overridden, overrides);
|
|
185804
|
+
assertOpenRouterProviderOverrideIncludesModel(baseConfig, overridden, overrides);
|
|
185578
185805
|
const parsed = kyosoConfigSchema.safeParse(overridden);
|
|
185579
185806
|
if (parsed.success)
|
|
185580
185807
|
return parsed.data;
|
|
@@ -185583,6 +185810,29 @@ function applyConfigOverrides(config2, assignments) {
|
|
|
185583
185810
|
const assignment = findAssignmentForPath(overrides, issuePath) ?? assignments.at(-1) ?? "";
|
|
185584
185811
|
throw new Error(`Invalid --set value ${JSON.stringify(assignment)}: ${issuePath}: ${issue2?.message ?? "config validation failed"}.`);
|
|
185585
185812
|
}
|
|
185813
|
+
function assertOpenRouterProviderOverrideIncludesModel(baseConfig, overridden, overrides) {
|
|
185814
|
+
const providerPath = ["agents", "codex", "provider"];
|
|
185815
|
+
const modelPath = ["agents", "codex", "model"];
|
|
185816
|
+
const selectsOpenRouter = readPath2(overridden, providerPath) === CODEX_OPENROUTER_PROVIDER;
|
|
185817
|
+
const alreadySelected = readPath2(baseConfig, providerPath) === CODEX_OPENROUTER_PROVIDER;
|
|
185818
|
+
const suppliesModel = overrides.some((override) => override.path.join(".") === modelPath.join("."));
|
|
185819
|
+
if (!selectsOpenRouter || alreadySelected || suppliesModel)
|
|
185820
|
+
return;
|
|
185821
|
+
const assignment = findAssignmentForPath(overrides, providerPath.join(".")) ?? "agents.codex.provider=openrouter";
|
|
185822
|
+
throw new Error(`Invalid --set value ${JSON.stringify(assignment)}: selecting agents.codex.provider=openrouter requires agents.codex.model in the same --set invocation.`);
|
|
185823
|
+
}
|
|
185824
|
+
function clearInheritedOpenRouterModelForProviderReset(baseConfig, overridden, overrides) {
|
|
185825
|
+
const providerPath = ["agents", "codex", "provider"];
|
|
185826
|
+
const modelPath = ["agents", "codex", "model"];
|
|
185827
|
+
const selectsDefaultProvider = overrides.some((override) => override.path.join(".") === providerPath.join(".") && override.value === CODEX_DEFAULT_PROVIDER);
|
|
185828
|
+
const suppliesModel = overrides.some((override) => override.path.join(".") === modelPath.join("."));
|
|
185829
|
+
if (!selectsDefaultProvider || suppliesModel || readPath2(baseConfig, providerPath) !== CODEX_OPENROUTER_PROVIDER || readPath2(overridden, providerPath) !== CODEX_DEFAULT_PROVIDER) {
|
|
185830
|
+
return;
|
|
185831
|
+
}
|
|
185832
|
+
const codex = readPath2(overridden, ["agents", "codex"]);
|
|
185833
|
+
if (isRecord4(codex))
|
|
185834
|
+
delete codex.model;
|
|
185835
|
+
}
|
|
185586
185836
|
function findAssignmentForPath(overrides, path) {
|
|
185587
185837
|
for (let index = overrides.length - 1;index >= 0; index -= 1) {
|
|
185588
185838
|
const override = overrides[index];
|
|
@@ -185650,8 +185900,8 @@ function isRecord4(value) {
|
|
|
185650
185900
|
|
|
185651
185901
|
// src/acp/AcpAgentProcess.ts
|
|
185652
185902
|
import { spawn } from "node:child_process";
|
|
185653
|
-
import { readFile as readFile4, realpath } from "node:fs/promises";
|
|
185654
|
-
import { isAbsolute, relative, resolve as resolve3 } from "node:path";
|
|
185903
|
+
import { readFile as readFile4, realpath as realpath2 } from "node:fs/promises";
|
|
185904
|
+
import { isAbsolute as isAbsolute3, relative, resolve as resolve3 } from "node:path";
|
|
185655
185905
|
import { Readable, Writable } from "node:stream";
|
|
185656
185906
|
|
|
185657
185907
|
// node_modules/@agentclientprotocol/sdk/dist/schema/index.js
|
|
@@ -189582,7 +189832,35 @@ var legacyClientNotificationMethods = new Set([
|
|
|
189582
189832
|
CLIENT_METHODS.elicitation_complete
|
|
189583
189833
|
]);
|
|
189584
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
|
+
|
|
189585
189862
|
// src/utils/env.ts
|
|
189863
|
+
import { stderr as stderr2 } from "node:process";
|
|
189586
189864
|
var MINIMAL_ENV_KEYS = [
|
|
189587
189865
|
"PATH",
|
|
189588
189866
|
"HOME",
|
|
@@ -189596,29 +189874,110 @@ var MINIMAL_ENV_KEYS = [
|
|
|
189596
189874
|
"USERNAME",
|
|
189597
189875
|
"SystemRoot"
|
|
189598
189876
|
];
|
|
189877
|
+
var CREDENTIAL_ENV_KEYS = new Set([
|
|
189878
|
+
"ANTHROPIC_API_KEY",
|
|
189879
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
189880
|
+
"CODEX_ACCESS_TOKEN",
|
|
189881
|
+
"CODEX_API_KEY",
|
|
189882
|
+
"OPENAI_API_KEY",
|
|
189883
|
+
"OPENROUTER_API_KEY"
|
|
189884
|
+
]);
|
|
189885
|
+
var CREDENTIAL_LIKE_ENV_KEY_PATTERN = /(?:^|_)(?:KEY|TOKEN|SECRET|PASSWORD)$/i;
|
|
189886
|
+
var UNEXPANDED_ENV_PLACEHOLDER_PATTERN = /^\s*(?:\$\{[A-Za-z_][A-Za-z0-9_]*\}|\$[A-Za-z_][A-Za-z0-9_]*|%[A-Za-z_][A-Za-z0-9_]*%)\s*$/;
|
|
189887
|
+
var OPENROUTER_EXCLUDED_CREDENTIAL_ENV_KEYS = [
|
|
189888
|
+
"OPENAI_API_KEY",
|
|
189889
|
+
"CODEX_API_KEY",
|
|
189890
|
+
"CODEX_ACCESS_TOKEN"
|
|
189891
|
+
];
|
|
189892
|
+
var OPENROUTER_API_KEY_ENV = "OPENROUTER_API_KEY";
|
|
189893
|
+
var KYOSO_OPENROUTER_PROVIDER_ID = "kyoso-openrouter";
|
|
189894
|
+
var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
189895
|
+
var OPENROUTER_PROVIDER_PRESET = {
|
|
189896
|
+
name: "OpenRouter",
|
|
189897
|
+
base_url: OPENROUTER_BASE_URL,
|
|
189898
|
+
env_key: OPENROUTER_API_KEY_ENV,
|
|
189899
|
+
wire_api: "responses",
|
|
189900
|
+
requires_openai_auth: false
|
|
189901
|
+
};
|
|
189902
|
+
|
|
189903
|
+
class ChildEnvPreflightError extends Error {
|
|
189904
|
+
code;
|
|
189905
|
+
constructor(code, message) {
|
|
189906
|
+
super(message);
|
|
189907
|
+
this.code = code;
|
|
189908
|
+
this.name = "ChildEnvPreflightError";
|
|
189909
|
+
}
|
|
189910
|
+
}
|
|
189599
189911
|
function buildChildEnv(parentEnv, whitelist, explicit, options = {}) {
|
|
189600
189912
|
if (!parentEnv.PATH) {
|
|
189601
189913
|
throw new Error("PATH is required to launch ACP child agents.");
|
|
189602
189914
|
}
|
|
189603
189915
|
const env = {};
|
|
189916
|
+
const warnedCredentialPlaceholderKeys = new Set;
|
|
189917
|
+
const onCredentialPlaceholderDiscarded = (key) => {
|
|
189918
|
+
if (warnedCredentialPlaceholderKeys.has(key))
|
|
189919
|
+
return;
|
|
189920
|
+
warnedCredentialPlaceholderKeys.add(key);
|
|
189921
|
+
(options.onCredentialPlaceholderDiscarded ?? warnCredentialPlaceholderDiscarded)(key);
|
|
189922
|
+
};
|
|
189923
|
+
const openRouterSelected = options.agent === "codex" && options.provider === CODEX_OPENROUTER_PROVIDER;
|
|
189604
189924
|
for (const key of MINIMAL_ENV_KEYS) {
|
|
189605
189925
|
if (parentEnv[key])
|
|
189606
189926
|
env[key] = parentEnv[key];
|
|
189607
189927
|
}
|
|
189608
189928
|
for (const key of whitelist) {
|
|
189609
|
-
if (
|
|
189929
|
+
if (key === OPENROUTER_API_KEY_ENV && !openRouterSelected)
|
|
189930
|
+
continue;
|
|
189931
|
+
if (parentEnv[key] && canCopyEnvValue(key, parentEnv[key], onCredentialPlaceholderDiscarded)) {
|
|
189610
189932
|
env[key] = parentEnv[key];
|
|
189933
|
+
}
|
|
189611
189934
|
}
|
|
189612
189935
|
for (const [key, value] of Object.entries(explicit)) {
|
|
189613
|
-
|
|
189936
|
+
if (key === OPENROUTER_API_KEY_ENV && !openRouterSelected) {
|
|
189937
|
+
if (value.trim().length > 0) {
|
|
189938
|
+
(options.onOpenRouterCredentialWithheld ?? warnOpenRouterCredentialWithheld)(key);
|
|
189939
|
+
}
|
|
189940
|
+
continue;
|
|
189941
|
+
}
|
|
189942
|
+
if (canCopyEnvValue(key, value, onCredentialPlaceholderDiscarded)) {
|
|
189943
|
+
env[key] = value;
|
|
189944
|
+
}
|
|
189945
|
+
}
|
|
189946
|
+
if (openRouterSelected) {
|
|
189947
|
+
discardOpenRouterExcludedCredentials(env);
|
|
189948
|
+
applyOpenRouterConfig(env, parentEnv, options.model, onCredentialPlaceholderDiscarded, options.onOpenRouterProvidersDiscarded ?? warnOpenRouterProvidersDiscarded);
|
|
189949
|
+
} else {
|
|
189950
|
+
applyModelConfig(env, options.agent, options.model);
|
|
189614
189951
|
}
|
|
189615
|
-
applyModelConfig(env, options.agent, options.model);
|
|
189616
189952
|
env.KYOSO_CHILD_AGENT = "1";
|
|
189617
189953
|
if (options.agent === "claude") {
|
|
189618
189954
|
applyClaudeAuthPreference(env, options.preferApiKey === true);
|
|
189619
189955
|
}
|
|
189620
189956
|
return env;
|
|
189621
189957
|
}
|
|
189958
|
+
function canCopyEnvValue(key, value, onCredentialPlaceholderDiscarded) {
|
|
189959
|
+
if (!isUnexpandedCredentialEnvValue(key, value))
|
|
189960
|
+
return true;
|
|
189961
|
+
(onCredentialPlaceholderDiscarded ?? warnCredentialPlaceholderDiscarded)(key);
|
|
189962
|
+
return false;
|
|
189963
|
+
}
|
|
189964
|
+
function warnCredentialPlaceholderDiscarded(key) {
|
|
189965
|
+
stderr2.write(`kyoso: ignored an unexpanded credential placeholder for ${key}; ensure the client expands it before starting Kyoso.
|
|
189966
|
+
`);
|
|
189967
|
+
}
|
|
189968
|
+
function warnOpenRouterCredentialWithheld(key) {
|
|
189969
|
+
stderr2.write(`kyoso: ignored explicit ${key} because the OpenRouter provider is not selected for this child.
|
|
189970
|
+
`);
|
|
189971
|
+
}
|
|
189972
|
+
function warnOpenRouterProvidersDiscarded(count) {
|
|
189973
|
+
stderr2.write(`kyoso: discarded foreign CODEX_CONFIG.model_providers entries: ${count}; provider IDs and configuration values were not displayed.
|
|
189974
|
+
`);
|
|
189975
|
+
}
|
|
189976
|
+
function discardOpenRouterExcludedCredentials(env) {
|
|
189977
|
+
for (const key of OPENROUTER_EXCLUDED_CREDENTIAL_ENV_KEYS) {
|
|
189978
|
+
delete env[key];
|
|
189979
|
+
}
|
|
189980
|
+
}
|
|
189622
189981
|
function applyModelConfig(env, agent, model) {
|
|
189623
189982
|
if (!model)
|
|
189624
189983
|
return;
|
|
@@ -189630,7 +189989,67 @@ function applyModelConfig(env, agent, model) {
|
|
|
189630
189989
|
env.CODEX_CONFIG = JSON.stringify({ model });
|
|
189631
189990
|
}
|
|
189632
189991
|
}
|
|
189992
|
+
function applyOpenRouterConfig(env, parentEnv, model, onCredentialPlaceholderDiscarded, onOpenRouterProvidersDiscarded) {
|
|
189993
|
+
const configuredModel = model?.trim();
|
|
189994
|
+
if (!configuredModel) {
|
|
189995
|
+
throw new ChildEnvPreflightError("AGENT_CONFIG_INVALID", 'agents.codex.provider="openrouter" requires a non-empty agents.codex.model.');
|
|
189996
|
+
}
|
|
189997
|
+
if (!hasEnv(env, OPENROUTER_API_KEY_ENV)) {
|
|
189998
|
+
const parentKey = nonEmptyEnv(parentEnv, OPENROUTER_API_KEY_ENV);
|
|
189999
|
+
if (parentKey)
|
|
190000
|
+
env[OPENROUTER_API_KEY_ENV] = parentKey;
|
|
190001
|
+
else if (isUnexpandedCredentialEnvValue(OPENROUTER_API_KEY_ENV, parentEnv[OPENROUTER_API_KEY_ENV] ?? "")) {
|
|
190002
|
+
onCredentialPlaceholderDiscarded(OPENROUTER_API_KEY_ENV);
|
|
190003
|
+
}
|
|
190004
|
+
}
|
|
190005
|
+
if (!hasEnv(env, OPENROUTER_API_KEY_ENV)) {
|
|
190006
|
+
throw new ChildEnvPreflightError("OPENROUTER_KEY_MISSING", 'agents.codex.provider="openrouter" requires OPENROUTER_API_KEY, but it is not visible to the Kyoso process. Add OPENROUTER_API_KEY to the MCP registration, restart the client, then run `kyoso doctor`.');
|
|
190007
|
+
}
|
|
190008
|
+
const config2 = parseCodexConfig(env.CODEX_CONFIG);
|
|
190009
|
+
assertOpenRouterConfigDoesNotSelectProfile(config2);
|
|
190010
|
+
if (config2.model_providers !== undefined && !isPlainObject2(config2.model_providers)) {
|
|
190011
|
+
throw new ChildEnvPreflightError("AGENT_CONFIG_INVALID", "CODEX_CONFIG.model_providers must be a JSON object for the OpenRouter provider.");
|
|
190012
|
+
}
|
|
190013
|
+
reportDiscardedOpenRouterProviders(config2.model_providers, onOpenRouterProvidersDiscarded);
|
|
190014
|
+
env.MODEL_PROVIDER = KYOSO_OPENROUTER_PROVIDER_ID;
|
|
190015
|
+
env.CODEX_CONFIG = JSON.stringify({
|
|
190016
|
+
...config2,
|
|
190017
|
+
model: configuredModel,
|
|
190018
|
+
model_provider: KYOSO_OPENROUTER_PROVIDER_ID,
|
|
190019
|
+
model_providers: {
|
|
190020
|
+
[KYOSO_OPENROUTER_PROVIDER_ID]: OPENROUTER_PROVIDER_PRESET
|
|
190021
|
+
}
|
|
190022
|
+
});
|
|
190023
|
+
}
|
|
190024
|
+
function reportDiscardedOpenRouterProviders(modelProviders, onDiscarded) {
|
|
190025
|
+
if (!isPlainObject2(modelProviders))
|
|
190026
|
+
return;
|
|
190027
|
+
const foreignProviderCount = Object.keys(modelProviders).filter((providerId) => providerId !== KYOSO_OPENROUTER_PROVIDER_ID).length;
|
|
190028
|
+
if (foreignProviderCount > 0)
|
|
190029
|
+
onDiscarded(foreignProviderCount);
|
|
190030
|
+
}
|
|
190031
|
+
function parseCodexConfig(value) {
|
|
190032
|
+
if (!value)
|
|
190033
|
+
return {};
|
|
190034
|
+
let parsed;
|
|
190035
|
+
try {
|
|
190036
|
+
parsed = JSON.parse(value);
|
|
190037
|
+
} catch {
|
|
190038
|
+
throw new ChildEnvPreflightError("AGENT_CONFIG_INVALID", "CODEX_CONFIG must contain a JSON object for the OpenRouter provider.");
|
|
190039
|
+
}
|
|
190040
|
+
if (!isPlainObject2(parsed)) {
|
|
190041
|
+
throw new ChildEnvPreflightError("AGENT_CONFIG_INVALID", "CODEX_CONFIG must contain a JSON object for the OpenRouter provider.");
|
|
190042
|
+
}
|
|
190043
|
+
return parsed;
|
|
190044
|
+
}
|
|
190045
|
+
function assertOpenRouterConfigDoesNotSelectProfile(config2) {
|
|
190046
|
+
if (Object.hasOwn(config2, "profile") || Object.hasOwn(config2, "profiles")) {
|
|
190047
|
+
throw new ChildEnvPreflightError("AGENT_CONFIG_INVALID", "CODEX_CONFIG.profile and CODEX_CONFIG.profiles are not supported for the OpenRouter provider.");
|
|
190048
|
+
}
|
|
190049
|
+
}
|
|
189633
190050
|
function applyClaudeAuthPreference(env, preferApiKey) {
|
|
190051
|
+
discardUnusableEnvValue(env, "ANTHROPIC_API_KEY");
|
|
190052
|
+
discardUnusableEnvValue(env, "CLAUDE_CODE_OAUTH_TOKEN");
|
|
189634
190053
|
const hasApiKey = hasEnv(env, "ANTHROPIC_API_KEY");
|
|
189635
190054
|
const hasOAuthToken = hasEnv(env, "CLAUDE_CODE_OAUTH_TOKEN");
|
|
189636
190055
|
if (!hasApiKey || !hasOAuthToken)
|
|
@@ -189641,8 +190060,32 @@ function applyClaudeAuthPreference(env, preferApiKey) {
|
|
|
189641
190060
|
delete env.ANTHROPIC_API_KEY;
|
|
189642
190061
|
}
|
|
189643
190062
|
}
|
|
190063
|
+
function discardUnusableEnvValue(env, key) {
|
|
190064
|
+
if (env[key] !== undefined && !hasEnv(env, key)) {
|
|
190065
|
+
delete env[key];
|
|
190066
|
+
}
|
|
190067
|
+
}
|
|
190068
|
+
function hasUsableEnvValue(env, key) {
|
|
190069
|
+
return nonEmptyEnv(env, key) !== undefined;
|
|
190070
|
+
}
|
|
190071
|
+
function isUnexpandedEnvPlaceholder(value) {
|
|
190072
|
+
return typeof value === "string" && UNEXPANDED_ENV_PLACEHOLDER_PATTERN.test(value);
|
|
190073
|
+
}
|
|
189644
190074
|
function hasEnv(env, key) {
|
|
189645
|
-
return
|
|
190075
|
+
return hasUsableEnvValue(env, key);
|
|
190076
|
+
}
|
|
190077
|
+
function nonEmptyEnv(env, key) {
|
|
190078
|
+
const value = env[key];
|
|
190079
|
+
return typeof value === "string" && value.trim().length > 0 && !isUnexpandedCredentialEnvValue(key, value) ? value : undefined;
|
|
190080
|
+
}
|
|
190081
|
+
function isUnexpandedCredentialEnvValue(key, value) {
|
|
190082
|
+
return (CREDENTIAL_ENV_KEYS.has(key) || CREDENTIAL_LIKE_ENV_KEY_PATTERN.test(key)) && isUnexpandedEnvPlaceholder(value);
|
|
190083
|
+
}
|
|
190084
|
+
function isPlainObject2(value) {
|
|
190085
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
190086
|
+
return false;
|
|
190087
|
+
}
|
|
190088
|
+
return Object.getPrototypeOf(value) === Object.prototype;
|
|
189646
190089
|
}
|
|
189647
190090
|
|
|
189648
190091
|
// src/acp/AcpAgentManager.ts
|
|
@@ -189761,7 +190204,7 @@ function isSeverity(value) {
|
|
|
189761
190204
|
return typeof value === "string" && severities.includes(value);
|
|
189762
190205
|
}
|
|
189763
190206
|
function normalizeCisaSecureByDesign(value) {
|
|
189764
|
-
if (!
|
|
190207
|
+
if (!isRecord7(value))
|
|
189765
190208
|
return;
|
|
189766
190209
|
const normalized = {};
|
|
189767
190210
|
const customerSecurityOutcomes = normalizeGateStatus(value.customerSecurityOutcomes);
|
|
@@ -189802,7 +190245,7 @@ function normalizeFindingFiles(value) {
|
|
|
189802
190245
|
if (!Array.isArray(value))
|
|
189803
190246
|
return;
|
|
189804
190247
|
const files = value.flatMap((item) => {
|
|
189805
|
-
if (!
|
|
190248
|
+
if (!isRecord7(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
|
|
189806
190249
|
return [];
|
|
189807
190250
|
}
|
|
189808
190251
|
const file2 = {
|
|
@@ -189821,38 +190264,80 @@ function normalizeFindingFiles(value) {
|
|
|
189821
190264
|
function normalizeLineNumber(value) {
|
|
189822
190265
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
189823
190266
|
}
|
|
189824
|
-
function
|
|
190267
|
+
function isRecord7(value) {
|
|
189825
190268
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
189826
190269
|
}
|
|
189827
190270
|
|
|
189828
190271
|
// src/acp/AcpAgentProcess.ts
|
|
189829
190272
|
class SubprocessAcpAgentManager extends BaseAcpAgentManager {
|
|
189830
190273
|
config;
|
|
189831
|
-
|
|
190274
|
+
parentEnv;
|
|
190275
|
+
constructor(config2, parentEnv = process.env) {
|
|
189832
190276
|
super();
|
|
189833
190277
|
this.config = config2;
|
|
190278
|
+
this.parentEnv = parentEnv;
|
|
189834
190279
|
}
|
|
189835
190280
|
async runAgent(input) {
|
|
189836
190281
|
const agentConfig = this.config.agents[input.agent];
|
|
190282
|
+
const startedAt = new Date().toISOString();
|
|
189837
190283
|
if (!agentConfig.enabled) {
|
|
189838
190284
|
return {
|
|
189839
190285
|
agent: input.agent,
|
|
189840
190286
|
role: input.role,
|
|
189841
190287
|
status: "skipped",
|
|
189842
|
-
startedAt
|
|
189843
|
-
completedAt:
|
|
190288
|
+
startedAt,
|
|
190289
|
+
completedAt: startedAt
|
|
190290
|
+
};
|
|
190291
|
+
}
|
|
190292
|
+
const provider = input.agent === "codex" ? this.config.agents.codex.provider : undefined;
|
|
190293
|
+
let env;
|
|
190294
|
+
try {
|
|
190295
|
+
env = buildChildEnv(this.parentEnv, agentConfig.auth.envWhitelist, agentConfig.env, {
|
|
190296
|
+
agent: input.agent,
|
|
190297
|
+
model: agentConfig.model,
|
|
190298
|
+
provider,
|
|
190299
|
+
preferApiKey: agentConfig.auth.preferApiKey
|
|
190300
|
+
});
|
|
190301
|
+
} catch (error51) {
|
|
190302
|
+
return {
|
|
190303
|
+
agent: input.agent,
|
|
190304
|
+
role: input.role,
|
|
190305
|
+
status: "failed",
|
|
190306
|
+
startedAt,
|
|
190307
|
+
completedAt: new Date().toISOString(),
|
|
190308
|
+
error: buildPreflightFailure(error51)
|
|
190309
|
+
};
|
|
190310
|
+
}
|
|
190311
|
+
try {
|
|
190312
|
+
return await runSubprocessAgent(input.agent, agentConfig, input, env);
|
|
190313
|
+
} catch (error51) {
|
|
190314
|
+
return {
|
|
190315
|
+
agent: input.agent,
|
|
190316
|
+
role: input.role,
|
|
190317
|
+
status: "failed",
|
|
190318
|
+
startedAt,
|
|
190319
|
+
completedAt: new Date().toISOString(),
|
|
190320
|
+
error: buildAgentFailure(formatAgentErrorDetail(error51), "Agent process failed.")
|
|
189844
190321
|
};
|
|
189845
190322
|
}
|
|
189846
|
-
return runSubprocessAgent(input.agent, agentConfig, input);
|
|
189847
190323
|
}
|
|
189848
190324
|
}
|
|
189849
|
-
async function runSubprocessAgent(agent, agentConfig, input) {
|
|
190325
|
+
async function runSubprocessAgent(agent, agentConfig, input, env) {
|
|
189850
190326
|
const startedAt = new Date().toISOString();
|
|
189851
|
-
const
|
|
189852
|
-
|
|
189853
|
-
|
|
189854
|
-
|
|
189855
|
-
|
|
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
|
+
}
|
|
189856
190341
|
return new Promise((resolveResult) => {
|
|
189857
190342
|
const child = spawn(agentConfig.command, agentConfig.args, {
|
|
189858
190343
|
cwd: input.workspaceDir,
|
|
@@ -189860,19 +190345,28 @@ async function runSubprocessAgent(agent, agentConfig, input) {
|
|
|
189860
190345
|
stdio: ["pipe", "pipe", "pipe"]
|
|
189861
190346
|
});
|
|
189862
190347
|
let stdout = "";
|
|
189863
|
-
let
|
|
190348
|
+
let stderr3 = "";
|
|
189864
190349
|
let settled = false;
|
|
190350
|
+
let startedWrite;
|
|
190351
|
+
child.once("spawn", () => {
|
|
190352
|
+
if (settled)
|
|
190353
|
+
return;
|
|
190354
|
+
startedWrite = Promise.resolve().then(() => input.onStarted?.()).catch(() => {
|
|
190355
|
+
return;
|
|
190356
|
+
});
|
|
190357
|
+
});
|
|
189865
190358
|
const abortController = new AbortController;
|
|
189866
190359
|
const resolveOnce = (result) => {
|
|
189867
190360
|
if (settled)
|
|
189868
190361
|
return;
|
|
189869
190362
|
settled = true;
|
|
189870
190363
|
clearTimeout(timeout);
|
|
189871
|
-
resolveResult(result);
|
|
190364
|
+
(startedWrite ?? Promise.resolve()).then(() => resolveResult(result));
|
|
189872
190365
|
};
|
|
189873
190366
|
const timeout = setTimeout(() => {
|
|
189874
190367
|
abortController.abort(new Error("Kyoso agent timeout"));
|
|
189875
190368
|
terminateChild(child);
|
|
190369
|
+
const deadlineReached = input.deadlineAtEpochMs !== undefined && Date.now() >= input.deadlineAtEpochMs;
|
|
189876
190370
|
resolveOnce({
|
|
189877
190371
|
agent,
|
|
189878
190372
|
role: input.role,
|
|
@@ -189880,13 +190374,13 @@ async function runSubprocessAgent(agent, agentConfig, input) {
|
|
|
189880
190374
|
startedAt,
|
|
189881
190375
|
completedAt: new Date().toISOString(),
|
|
189882
190376
|
error: {
|
|
189883
|
-
code: "AGENT_TIMEOUT",
|
|
189884
|
-
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`
|
|
189885
190379
|
}
|
|
189886
190380
|
});
|
|
189887
|
-
},
|
|
190381
|
+
}, effectiveTimeoutMs);
|
|
189888
190382
|
child.stderr.on("data", (chunk) => {
|
|
189889
|
-
|
|
190383
|
+
stderr3 += chunk.toString("utf8");
|
|
189890
190384
|
});
|
|
189891
190385
|
child.on("error", (error51) => {
|
|
189892
190386
|
const failure = buildAgentFailure(error51.message, "Agent process could not be started.");
|
|
@@ -189900,22 +190394,51 @@ async function runSubprocessAgent(agent, agentConfig, input) {
|
|
|
189900
190394
|
error: failure
|
|
189901
190395
|
});
|
|
189902
190396
|
});
|
|
189903
|
-
runAcpClientWorkflow(child, input, abortController
|
|
190397
|
+
runAcpClientWorkflow(child, input, abortController, resolveEffortConfigOption(agent, agentConfig.effort)).then(({ rawText, warnings, usage, outputBytes, stopReason }) => {
|
|
189904
190398
|
stdout = rawText;
|
|
190399
|
+
const completed = stopReason === "end_turn";
|
|
189905
190400
|
resolveOnce({
|
|
189906
190401
|
agent,
|
|
189907
190402
|
role: input.role,
|
|
189908
|
-
status: "completed",
|
|
190403
|
+
status: completed ? "completed" : "failed",
|
|
189909
190404
|
rawText,
|
|
189910
190405
|
normalized: normalizeAgentOutput(agent, input.role, rawText),
|
|
189911
190406
|
startedAt,
|
|
189912
190407
|
completedAt: new Date().toISOString(),
|
|
189913
|
-
|
|
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
|
+
}
|
|
189914
190418
|
});
|
|
189915
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
|
+
}
|
|
189916
190439
|
if (abortController.signal.aborted)
|
|
189917
190440
|
return;
|
|
189918
|
-
const failureText = [
|
|
190441
|
+
const failureText = [stderr3, formatAgentErrorDetail(error51)].filter((part) => part.trim().length > 0).join(`
|
|
189919
190442
|
`);
|
|
189920
190443
|
resolveOnce({
|
|
189921
190444
|
agent,
|
|
@@ -189940,12 +190463,12 @@ async function runSubprocessAgent(agent, agentConfig, input) {
|
|
|
189940
190463
|
rawText: stdout,
|
|
189941
190464
|
startedAt,
|
|
189942
190465
|
completedAt: new Date().toISOString(),
|
|
189943
|
-
error: buildAgentFailure(
|
|
190466
|
+
error: buildAgentFailure(stderr3, fallback)
|
|
189944
190467
|
});
|
|
189945
190468
|
});
|
|
189946
190469
|
});
|
|
189947
190470
|
}
|
|
189948
|
-
async function runAcpClientWorkflow(child, input,
|
|
190471
|
+
async function runAcpClientWorkflow(child, input, abortController, configOption) {
|
|
189949
190472
|
if (!child.stdin || !child.stdout) {
|
|
189950
190473
|
throw new Error("Agent process did not expose stdio streams.");
|
|
189951
190474
|
}
|
|
@@ -189995,8 +190518,8 @@ async function runAcpClientWorkflow(child, input, signal, configOption) {
|
|
|
189995
190518
|
}).withSession(async (session) => {
|
|
189996
190519
|
const warnings = [];
|
|
189997
190520
|
if (configOption) {
|
|
189998
|
-
await ctx.request(methods.agent.session.setConfigOption, { sessionId: session.sessionId, ...configOption }, { cancellationSignal: signal }).catch((error51) => {
|
|
189999
|
-
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)
|
|
190000
190523
|
return;
|
|
190001
190524
|
const sanitizedValue = sanitizeTextForDisplay(configOption.value);
|
|
190002
190525
|
const detail = sanitizeTextForDisplay(formatAgentErrorDetail(error51));
|
|
@@ -190006,14 +190529,75 @@ async function runAcpClientWorkflow(child, input, signal, configOption) {
|
|
|
190006
190529
|
});
|
|
190007
190530
|
}
|
|
190008
190531
|
const promptResponse = session.prompt(input.prompt, {
|
|
190009
|
-
cancellationSignal: signal
|
|
190532
|
+
cancellationSignal: abortController.signal
|
|
190533
|
+
});
|
|
190534
|
+
promptResponse.catch(() => {
|
|
190535
|
+
return;
|
|
190010
190536
|
});
|
|
190011
|
-
|
|
190012
|
-
|
|
190013
|
-
|
|
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
|
+
}
|
|
190014
190572
|
});
|
|
190015
190573
|
});
|
|
190016
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
|
+
}
|
|
190017
190601
|
function resolveEffortConfigOption(agent, effort) {
|
|
190018
190602
|
if (!effort)
|
|
190019
190603
|
return;
|
|
@@ -190024,7 +190608,7 @@ function resolveEffortConfigOption(agent, effort) {
|
|
|
190024
190608
|
return;
|
|
190025
190609
|
}
|
|
190026
190610
|
async function readWorkspaceFile(workspaceDir, requestedPath, line, limit) {
|
|
190027
|
-
const workspaceRoot = await
|
|
190611
|
+
const workspaceRoot = await realpath2(workspaceDir);
|
|
190028
190612
|
const candidates = resolveReadablePaths(workspaceRoot, requestedPath);
|
|
190029
190613
|
let content;
|
|
190030
190614
|
for (const absolute of candidates) {
|
|
@@ -190047,7 +190631,7 @@ async function readWorkspaceFile(workspaceDir, requestedPath, line, limit) {
|
|
|
190047
190631
|
`);
|
|
190048
190632
|
}
|
|
190049
190633
|
async function resolveReadableFile(workspaceRoot, absolute) {
|
|
190050
|
-
const realPath = await
|
|
190634
|
+
const realPath = await realpath2(absolute).catch((error51) => {
|
|
190051
190635
|
if (isMissingPathError2(error51))
|
|
190052
190636
|
return;
|
|
190053
190637
|
throw error51;
|
|
@@ -190066,7 +190650,7 @@ function resolveReadablePaths(workspaceRoot, requestedPath) {
|
|
|
190066
190650
|
}
|
|
190067
190651
|
const repoPath = resolve3(workspaceRoot, "repo", relativePath);
|
|
190068
190652
|
assertWithinWorkspace(workspaceRoot, repoPath);
|
|
190069
|
-
return
|
|
190653
|
+
return isAbsolute3(requestedPath) ? [primary, repoPath] : [repoPath, primary];
|
|
190070
190654
|
}
|
|
190071
190655
|
function assertWithinWorkspace(workspaceRoot, absolute) {
|
|
190072
190656
|
const relativePath = relative(workspaceRoot, absolute);
|
|
@@ -190098,6 +190682,20 @@ function buildAgentFailure(rawDetail, fallbackMessage) {
|
|
|
190098
190682
|
...detail ? { detail } : {}
|
|
190099
190683
|
};
|
|
190100
190684
|
}
|
|
190685
|
+
function buildPreflightFailure(error51) {
|
|
190686
|
+
if (error51 instanceof ChildEnvPreflightError) {
|
|
190687
|
+
return {
|
|
190688
|
+
code: error51.code,
|
|
190689
|
+
message: error51.message
|
|
190690
|
+
};
|
|
190691
|
+
}
|
|
190692
|
+
const detail = sanitizeTextForDisplay(formatAgentErrorDetail(error51));
|
|
190693
|
+
return {
|
|
190694
|
+
code: "AGENT_CONFIG_INVALID",
|
|
190695
|
+
message: "Agent configuration is invalid. Run kyoso doctor and check agent configuration.",
|
|
190696
|
+
...detail ? { detail } : {}
|
|
190697
|
+
};
|
|
190698
|
+
}
|
|
190101
190699
|
function safeAgentFailureMessage(code, fallbackMessage) {
|
|
190102
190700
|
if (code === "AUTH_FAILED") {
|
|
190103
190701
|
return "Agent authentication failed. Run kyoso doctor and check configured credentials.";
|
|
@@ -190113,15 +190711,15 @@ function safeAgentFailureMessage(code, fallbackMessage) {
|
|
|
190113
190711
|
}
|
|
190114
190712
|
return sanitizeTextForDisplay(fallbackMessage);
|
|
190115
190713
|
}
|
|
190116
|
-
function classifyAgentFailure(
|
|
190117
|
-
if (/spawn/i.test(
|
|
190714
|
+
function classifyAgentFailure(stderr3) {
|
|
190715
|
+
if (/spawn/i.test(stderr3))
|
|
190118
190716
|
return "AGENT_SPAWN_FAILED";
|
|
190119
|
-
if (/ENOTFOUND|ENOTCACHED|ECONNREFUSED|ETIMEDOUT|registry\.npmjs\.org|network request|cache mode/i.test(
|
|
190717
|
+
if (/ENOTFOUND|ENOTCACHED|ECONNREFUSED|ETIMEDOUT|registry\.npmjs\.org|network request|cache mode/i.test(stderr3)) {
|
|
190120
190718
|
return "AGENT_NETWORK_FAILED";
|
|
190121
190719
|
}
|
|
190122
|
-
if (/auth|api key|login|credential/i.test(
|
|
190720
|
+
if (/auth|api key|login|credential/i.test(stderr3))
|
|
190123
190721
|
return "AUTH_FAILED";
|
|
190124
|
-
if (/permission|policy|write|terminal/i.test(
|
|
190722
|
+
if (/permission|policy|write|terminal/i.test(stderr3))
|
|
190125
190723
|
return "PERMISSION_DENIED";
|
|
190126
190724
|
return "AGENT_FAILED";
|
|
190127
190725
|
}
|
|
@@ -190158,9 +190756,24 @@ class FakeAgentManager extends BaseAcpAgentManager {
|
|
|
190158
190756
|
this.calls.push(input);
|
|
190159
190757
|
const startedAt = new Date().toISOString();
|
|
190160
190758
|
if (input.role === "finding_verifier") {
|
|
190759
|
+
await input.onStarted?.();
|
|
190161
190760
|
return verifierResult(input, startedAt, this.verifierScenarios[input.agent] ?? "confirmed");
|
|
190162
190761
|
}
|
|
190163
190762
|
const scenario = this.scenarios[input.agent] ?? "success";
|
|
190763
|
+
if (scenario === "preflight_failure" || scenario === "openrouter_key_missing") {
|
|
190764
|
+
return {
|
|
190765
|
+
agent: input.agent,
|
|
190766
|
+
role: input.role,
|
|
190767
|
+
status: "failed",
|
|
190768
|
+
startedAt,
|
|
190769
|
+
completedAt: new Date().toISOString(),
|
|
190770
|
+
error: {
|
|
190771
|
+
code: scenario === "openrouter_key_missing" ? "OPENROUTER_KEY_MISSING" : "AGENT_CONFIG_INVALID",
|
|
190772
|
+
message: scenario === "openrouter_key_missing" ? "Fake OpenRouter key is missing." : "Fake agent configuration is invalid."
|
|
190773
|
+
}
|
|
190774
|
+
};
|
|
190775
|
+
}
|
|
190776
|
+
await input.onStarted?.();
|
|
190164
190777
|
if (scenario === "timeout") {
|
|
190165
190778
|
return {
|
|
190166
190779
|
agent: input.agent,
|
|
@@ -190206,9 +190819,10 @@ ${JSON.stringify(opinion)}
|
|
|
190206
190819
|
role: input.role,
|
|
190207
190820
|
status: "completed",
|
|
190208
190821
|
rawText,
|
|
190209
|
-
normalized: scenario === "success" ? opinion : undefined,
|
|
190822
|
+
normalized: scenario === "success" || scenario === "unknown_usage" ? opinion : undefined,
|
|
190210
190823
|
startedAt,
|
|
190211
|
-
completedAt: new Date().toISOString()
|
|
190824
|
+
completedAt: new Date().toISOString(),
|
|
190825
|
+
...scenario === "unknown_usage" ? {} : { usage: fakeUsage() }
|
|
190212
190826
|
};
|
|
190213
190827
|
}
|
|
190214
190828
|
}
|
|
@@ -190242,9 +190856,13 @@ function verifierResult(input, startedAt, scenario) {
|
|
|
190242
190856
|
status: "completed",
|
|
190243
190857
|
rawText,
|
|
190244
190858
|
startedAt,
|
|
190245
|
-
completedAt: new Date().toISOString()
|
|
190859
|
+
completedAt: new Date().toISOString(),
|
|
190860
|
+
usage: fakeUsage()
|
|
190246
190861
|
};
|
|
190247
190862
|
}
|
|
190863
|
+
function fakeUsage() {
|
|
190864
|
+
return { totalTokens: 20, inputTokens: 12, outputTokens: 8 };
|
|
190865
|
+
}
|
|
190248
190866
|
function findingIdsFromPrompt(prompt) {
|
|
190249
190867
|
return Array.from(prompt.matchAll(/^Finding ID: (.+)$/gm)).map((match) => match[1] ?? "");
|
|
190250
190868
|
}
|
|
@@ -190784,8 +191402,8 @@ function normalizeTitle(value) {
|
|
|
190784
191402
|
|
|
190785
191403
|
// src/audit/stateRoot.ts
|
|
190786
191404
|
import { createHash as createHash2 } from "node:crypto";
|
|
190787
|
-
import { lstat, mkdir as mkdir2, realpath as
|
|
190788
|
-
import { basename, dirname as
|
|
191405
|
+
import { lstat, mkdir as mkdir2, realpath as realpath3 } from "node:fs/promises";
|
|
191406
|
+
import { basename, dirname as dirname4, isAbsolute as isAbsolute4, join as join3, resolve as resolve5 } from "node:path";
|
|
190789
191407
|
|
|
190790
191408
|
// src/context/pathPolicy.ts
|
|
190791
191409
|
import { normalize, sep } from "node:path";
|
|
@@ -190876,7 +191494,7 @@ async function resolveAuditStateRoot(options) {
|
|
|
190876
191494
|
return { warnings: [...warnings, AUDIT_WARNING_UNSUPPORTED_CAPABILITY] };
|
|
190877
191495
|
}
|
|
190878
191496
|
try {
|
|
190879
|
-
const workspaceRoot = await
|
|
191497
|
+
const workspaceRoot = await realpath3(resolve5(options.cwd));
|
|
190880
191498
|
const candidate = resolveStateBaseCandidate(options.env ?? process.env);
|
|
190881
191499
|
if (!candidate)
|
|
190882
191500
|
throw new Error("missing trusted state base");
|
|
@@ -190905,7 +191523,7 @@ async function resolveAuditStateRoot(options) {
|
|
|
190905
191523
|
}
|
|
190906
191524
|
}
|
|
190907
191525
|
async function ensureTrustedDirectory(options) {
|
|
190908
|
-
const realRoot = await
|
|
191526
|
+
const realRoot = await realpath3(options.root);
|
|
190909
191527
|
assertSafeDirectory(realRoot, options.uid, await lstat(realRoot));
|
|
190910
191528
|
if (options.workspaceRoot && isPathWithin(realRoot, options.workspaceRoot)) {
|
|
190911
191529
|
throw new Error("trusted directory resolves inside workspace");
|
|
@@ -190922,7 +191540,7 @@ async function ensureTrustedDirectory(options) {
|
|
|
190922
191540
|
entry = await lstat(next);
|
|
190923
191541
|
}
|
|
190924
191542
|
assertSafeDirectory(next, options.uid, entry);
|
|
190925
|
-
const realNext = await
|
|
191543
|
+
const realNext = await realpath3(next);
|
|
190926
191544
|
if (!isPathWithin(realNext, realRoot)) {
|
|
190927
191545
|
throw new Error("managed directory escaped trusted root");
|
|
190928
191546
|
}
|
|
@@ -190938,7 +191556,7 @@ function isResolvedAuditStateRoot(resolution) {
|
|
|
190938
191556
|
}
|
|
190939
191557
|
function validateAuditDirectory(directory, warnings) {
|
|
190940
191558
|
try {
|
|
190941
|
-
if (directory.trim().length === 0 ||
|
|
191559
|
+
if (directory.trim().length === 0 || isAbsolute4(directory) || directory.split(/[\\/]+/).includes("..")) {
|
|
190942
191560
|
throw new Error("unsafe logical directory");
|
|
190943
191561
|
}
|
|
190944
191562
|
const normalized = normalizeRelativePath(directory);
|
|
@@ -190953,11 +191571,11 @@ function validateAuditDirectory(directory, warnings) {
|
|
|
190953
191571
|
}
|
|
190954
191572
|
function resolveStateBaseCandidate(env) {
|
|
190955
191573
|
const xdgStateHome = env.XDG_STATE_HOME?.trim();
|
|
190956
|
-
if (xdgStateHome &&
|
|
191574
|
+
if (xdgStateHome && isAbsolute4(xdgStateHome)) {
|
|
190957
191575
|
return resolve5(xdgStateHome);
|
|
190958
191576
|
}
|
|
190959
191577
|
const home = env.HOME?.trim();
|
|
190960
|
-
if (!home || !
|
|
191578
|
+
if (!home || !isAbsolute4(home))
|
|
190961
191579
|
return;
|
|
190962
191580
|
return join3(resolve5(home), ".local", "state");
|
|
190963
191581
|
}
|
|
@@ -190969,7 +191587,7 @@ async function ensureTrustedStateBase(options) {
|
|
|
190969
191587
|
assertSafeDirectory(existing.path, options.uid, await lstat(existing.path), {
|
|
190970
191588
|
allowFilesystemRoot: true
|
|
190971
191589
|
});
|
|
190972
|
-
const realExisting = await
|
|
191590
|
+
const realExisting = await realpath3(existing.path);
|
|
190973
191591
|
if (isPathWithin(realExisting, options.workspaceRoot)) {
|
|
190974
191592
|
throw new Error("state base resolves inside workspace");
|
|
190975
191593
|
}
|
|
@@ -190980,7 +191598,7 @@ async function ensureTrustedStateBase(options) {
|
|
|
190980
191598
|
await createDirectory(current);
|
|
190981
191599
|
const entry = await lstat(current);
|
|
190982
191600
|
assertSafeDirectory(current, options.uid, entry);
|
|
190983
|
-
const realCurrent = await
|
|
191601
|
+
const realCurrent = await realpath3(current);
|
|
190984
191602
|
if (!isPathWithin(realCurrent, realExisting)) {
|
|
190985
191603
|
throw new Error("state base changed while being created");
|
|
190986
191604
|
}
|
|
@@ -190989,7 +191607,7 @@ async function ensureTrustedStateBase(options) {
|
|
|
190989
191607
|
}
|
|
190990
191608
|
current = realCurrent;
|
|
190991
191609
|
}
|
|
190992
|
-
const stateBase = await
|
|
191610
|
+
const stateBase = await realpath3(current);
|
|
190993
191611
|
assertSafeDirectory(stateBase, options.uid, await lstat(stateBase));
|
|
190994
191612
|
if (isPathWithin(stateBase, options.workspaceRoot)) {
|
|
190995
191613
|
throw new Error("state base resolves inside workspace");
|
|
@@ -191008,7 +191626,7 @@ async function findExistingAncestor(candidate) {
|
|
|
191008
191626
|
}
|
|
191009
191627
|
return { path: current, missingSegments };
|
|
191010
191628
|
}
|
|
191011
|
-
const parent =
|
|
191629
|
+
const parent = dirname4(current);
|
|
191012
191630
|
if (parent === current)
|
|
191013
191631
|
throw new Error("state base has no existing ancestor");
|
|
191014
191632
|
missingSegments.unshift(basename(current));
|
|
@@ -191041,12 +191659,12 @@ function getCurrentUid(getuid) {
|
|
|
191041
191659
|
}
|
|
191042
191660
|
}
|
|
191043
191661
|
function isFilesystemRoot(path) {
|
|
191044
|
-
return
|
|
191662
|
+
return dirname4(path) === path;
|
|
191045
191663
|
}
|
|
191046
191664
|
async function assertTrustedAncestorChain(path, uid) {
|
|
191047
191665
|
let child = path;
|
|
191048
191666
|
while (!isFilesystemRoot(child)) {
|
|
191049
|
-
const parent =
|
|
191667
|
+
const parent = dirname4(child);
|
|
191050
191668
|
const [parentStat, childStat] = await Promise.all([
|
|
191051
191669
|
lstat(parent),
|
|
191052
191670
|
lstat(child)
|
|
@@ -191094,7 +191712,7 @@ function isAlreadyExistsError(error51) {
|
|
|
191094
191712
|
|
|
191095
191713
|
// src/audit/safeTraceFile.ts
|
|
191096
191714
|
import { constants } from "node:fs";
|
|
191097
|
-
import { lstat as lstat2, open, realpath as
|
|
191715
|
+
import { lstat as lstat2, open, realpath as realpath4, stat } from "node:fs/promises";
|
|
191098
191716
|
import { join as join4 } from "node:path";
|
|
191099
191717
|
var AUDIT_WARNING_UNSUPPORTED_OPEN_CAPABILITY = "AUDIT_DISABLED_UNSUPPORTED_CAPABILITY: Audit trace writing requires unavailable filesystem capabilities.";
|
|
191100
191718
|
async function openVerifiedTraceFile(options) {
|
|
@@ -191124,7 +191742,7 @@ async function openVerifiedTraceFile(options) {
|
|
|
191124
191742
|
const [handleStat, pathStat, realTracePath] = await Promise.all([
|
|
191125
191743
|
handle.stat({ bigint: true }),
|
|
191126
191744
|
stat(tracePath, { bigint: true }),
|
|
191127
|
-
|
|
191745
|
+
realpath4(tracePath)
|
|
191128
191746
|
]);
|
|
191129
191747
|
if (!handleStat.isFile() || !pathStat.isFile() || handleStat.dev !== pathStat.dev || handleStat.ino !== pathStat.ino || !isPathWithin(realTracePath, options.kyosoRoot)) {
|
|
191130
191748
|
throw new Error("trace file identity could not be verified");
|
|
@@ -191167,6 +191785,16 @@ async function optionalLstat2(path) {
|
|
|
191167
191785
|
}
|
|
191168
191786
|
|
|
191169
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
|
+
]);
|
|
191170
191798
|
function sanitizeForAudit(value, options = {}) {
|
|
191171
191799
|
if (typeof value === "string")
|
|
191172
191800
|
return sanitizeText(value);
|
|
@@ -191175,7 +191803,7 @@ function sanitizeForAudit(value, options = {}) {
|
|
|
191175
191803
|
if (typeof value === "object" && value !== null) {
|
|
191176
191804
|
const result = {};
|
|
191177
191805
|
for (const [key, nested] of Object.entries(value)) {
|
|
191178
|
-
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)) {
|
|
191179
191807
|
continue;
|
|
191180
191808
|
}
|
|
191181
191809
|
result[key] = sanitizeForAudit(nested, options);
|
|
@@ -191184,6 +191812,17 @@ function sanitizeForAudit(value, options = {}) {
|
|
|
191184
191812
|
}
|
|
191185
191813
|
return value;
|
|
191186
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
|
+
}
|
|
191187
191826
|
|
|
191188
191827
|
// src/audit/trace.ts
|
|
191189
191828
|
var AUDIT_WARNING_WRITE_FAILED = "AUDIT_WRITE_FAILED: Audit trace writing failed; no further audit events will be written.";
|
|
@@ -191449,6 +192088,8 @@ function renderMarkdownResult(tool, result, options = {}) {
|
|
|
191449
192088
|
"",
|
|
191450
192089
|
`**Decision:** ${result.decision}`,
|
|
191451
192090
|
`**Mode:** ${tool}`,
|
|
192091
|
+
`**Completion:** ${formatCompletion(result)}`,
|
|
192092
|
+
`**Request fingerprint:** ${shortFingerprint(result.requestFingerprint)}`,
|
|
191452
192093
|
`**Agents:** ${result.agentOpinions.map((opinion) => `${title(opinion.agent)} ${opinion.status}`).join(", ")}`,
|
|
191453
192094
|
`**Review mode:** ${formatReviewMode(result)}`,
|
|
191454
192095
|
...result.verificationMode ? [
|
|
@@ -191460,6 +192101,7 @@ function renderMarkdownResult(tool, result, options = {}) {
|
|
|
191460
192101
|
"",
|
|
191461
192102
|
options.summaryText ?? defaultSummaryText(result)
|
|
191462
192103
|
];
|
|
192104
|
+
lines.push(...formatExecutionBudget(result));
|
|
191463
192105
|
if (result.cisaSecureByDesign) {
|
|
191464
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)} |`);
|
|
191465
192107
|
}
|
|
@@ -191512,8 +192154,37 @@ function renderMarkdownResult(tool, result, options = {}) {
|
|
|
191512
192154
|
`);
|
|
191513
192155
|
}
|
|
191514
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
|
+
}
|
|
191515
192161
|
return result.findings.length === 0 ? "No blocking findings were detected from the supplied context." : `${result.findings.length} finding(s) require attention.`;
|
|
191516
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
|
+
}
|
|
191517
192188
|
function title(value) {
|
|
191518
192189
|
return value.slice(0, 1).toUpperCase() + value.slice(1);
|
|
191519
192190
|
}
|
|
@@ -191602,7 +192273,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
|
|
|
191602
192273
|
const parsed = JSON.parse(json2);
|
|
191603
192274
|
const summaryText = typeof parsed.summaryText === "string" && parsed.summaryText.trim().length > 0 ? sanitizeText(parsed.summaryText) : fallbackSummaryText;
|
|
191604
192275
|
const disagreementComments = Array.isArray(parsed.disagreementComments) ? parsed.disagreementComments.flatMap((item) => {
|
|
191605
|
-
if (!
|
|
192276
|
+
if (!isRecord8(item) || typeof item.topic !== "string" || typeof item.judgeComment !== "string") {
|
|
191606
192277
|
return [];
|
|
191607
192278
|
}
|
|
191608
192279
|
return [
|
|
@@ -191618,7 +192289,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
|
|
|
191618
192289
|
return { summaryText, disagreementComments, analysis };
|
|
191619
192290
|
}
|
|
191620
192291
|
function parseAnalysis(value) {
|
|
191621
|
-
if (!
|
|
192292
|
+
if (!isRecord8(value))
|
|
191622
192293
|
return;
|
|
191623
192294
|
if (!Array.isArray(value.blindSpots) || !Array.isArray(value.contradictions) || !Array.isArray(value.partialCoverage)) {
|
|
191624
192295
|
return;
|
|
@@ -191626,7 +192297,7 @@ function parseAnalysis(value) {
|
|
|
191626
192297
|
return {
|
|
191627
192298
|
blindSpots: value.blindSpots.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => typeof item === "string" ? [sanitizeAnalysisText(item)] : []),
|
|
191628
192299
|
contradictions: value.contradictions.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
|
|
191629
|
-
if (!
|
|
192300
|
+
if (!isRecord8(item) || typeof item.topic !== "string" || typeof item.detail !== "string") {
|
|
191630
192301
|
return [];
|
|
191631
192302
|
}
|
|
191632
192303
|
return [
|
|
@@ -191637,7 +192308,7 @@ function parseAnalysis(value) {
|
|
|
191637
192308
|
];
|
|
191638
192309
|
}),
|
|
191639
192310
|
partialCoverage: value.partialCoverage.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
|
|
191640
|
-
if (!
|
|
192311
|
+
if (!isRecord8(item) || typeof item.note !== "string")
|
|
191641
192312
|
return [];
|
|
191642
192313
|
const findingId = typeof item.findingId === "string" ? sanitizeAnalysisText(item.findingId) : undefined;
|
|
191643
192314
|
return [
|
|
@@ -191684,7 +192355,7 @@ function extractFirstJsonObject2(text) {
|
|
|
191684
192355
|
}
|
|
191685
192356
|
return;
|
|
191686
192357
|
}
|
|
191687
|
-
function
|
|
192358
|
+
function isRecord8(value) {
|
|
191688
192359
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
191689
192360
|
}
|
|
191690
192361
|
|
|
@@ -191702,7 +192373,7 @@ async function runAnthropicJudge(input, timeoutMs) {
|
|
|
191702
192373
|
},
|
|
191703
192374
|
body: JSON.stringify({
|
|
191704
192375
|
model: input.env.KYOSO_ANTHROPIC_JUDGE_MODEL ?? "claude-haiku-4-5",
|
|
191705
|
-
max_tokens:
|
|
192376
|
+
max_tokens: JUDGE_MAX_OUTPUT_TOKENS,
|
|
191706
192377
|
temperature: 0,
|
|
191707
192378
|
messages: [
|
|
191708
192379
|
{
|
|
@@ -191718,7 +192389,21 @@ async function runAnthropicJudge(input, timeoutMs) {
|
|
|
191718
192389
|
const content = payload.content?.find((item) => item.type === "text" && item.text)?.text;
|
|
191719
192390
|
if (!content)
|
|
191720
192391
|
throw new Error("Anthropic judge response did not include text content.");
|
|
191721
|
-
|
|
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
|
+
});
|
|
191722
192407
|
}
|
|
191723
192408
|
async function fetchWithTimeout(url2, init, timeoutMs) {
|
|
191724
192409
|
const controller = new AbortController;
|
|
@@ -191762,6 +192447,7 @@ async function runOpenAiJudge(input, timeoutMs) {
|
|
|
191762
192447
|
content: buildJudgePrompt(input.tool, input.result, input.summaryText, input.agentFindings)
|
|
191763
192448
|
}
|
|
191764
192449
|
],
|
|
192450
|
+
max_completion_tokens: JUDGE_MAX_OUTPUT_TOKENS,
|
|
191765
192451
|
temperature: 0
|
|
191766
192452
|
})
|
|
191767
192453
|
}, timeoutMs);
|
|
@@ -191771,7 +192457,22 @@ async function runOpenAiJudge(input, timeoutMs) {
|
|
|
191771
192457
|
const content = payload.choices?.[0]?.message?.content;
|
|
191772
192458
|
if (!content)
|
|
191773
192459
|
throw new Error("OpenAI judge response did not include content.");
|
|
191774
|
-
|
|
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
|
+
});
|
|
191775
192476
|
}
|
|
191776
192477
|
async function fetchWithTimeout2(url2, init, timeoutMs) {
|
|
191777
192478
|
const controller = new AbortController;
|
|
@@ -191813,8 +192514,13 @@ async function runJudge(input) {
|
|
|
191813
192514
|
return { provider, status: "deterministic_fallback", output: fallback };
|
|
191814
192515
|
}
|
|
191815
192516
|
try {
|
|
191816
|
-
const output = provider === "openai" ? await runOpenAiJudge(input, input.config.timeoutMs) : await runAnthropicJudge(input, input.config.timeoutMs);
|
|
191817
|
-
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
|
+
};
|
|
191818
192524
|
} catch (error51) {
|
|
191819
192525
|
return {
|
|
191820
192526
|
provider,
|
|
@@ -192020,7 +192726,7 @@ function decide(input) {
|
|
|
192020
192726
|
|
|
192021
192727
|
// src/workspace/createSnapshot.ts
|
|
192022
192728
|
import { chmod, mkdir as mkdir3, mkdtemp, writeFile as writeFile2 } from "node:fs/promises";
|
|
192023
|
-
import { dirname as
|
|
192729
|
+
import { dirname as dirname5, join as join5 } from "node:path";
|
|
192024
192730
|
import { tmpdir } from "node:os";
|
|
192025
192731
|
async function createSnapshot(traceId, tool, request, options = {}) {
|
|
192026
192732
|
const root = await mkdtemp(join5(tmpdir(), `kyoso-${traceId}-`));
|
|
@@ -192036,7 +192742,7 @@ async function createSnapshot(traceId, tool, request, options = {}) {
|
|
|
192036
192742
|
if (!isAllowedPath(relative2, options.allowPatterns ?? []))
|
|
192037
192743
|
continue;
|
|
192038
192744
|
const dest = join5(repoDir, relative2);
|
|
192039
|
-
await mkdir3(
|
|
192745
|
+
await mkdir3(dirname5(dest), { recursive: true });
|
|
192040
192746
|
await writeFile2(dest, file2.content, "utf8");
|
|
192041
192747
|
await chmod(dest, 292).catch(() => {
|
|
192042
192748
|
return;
|
|
@@ -192087,6 +192793,304 @@ function newTraceId() {
|
|
|
192087
192793
|
return `tr_${randomUUID()}`;
|
|
192088
192794
|
}
|
|
192089
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
|
+
|
|
192090
193094
|
// src/core/verification.ts
|
|
192091
193095
|
var REAL_AGENTS = ["codex", "claude"];
|
|
192092
193096
|
var VERIFIABLE_SEVERITIES = new Set(["critical", "high"]);
|
|
@@ -192128,9 +193132,12 @@ function groupVerificationTargetsByVerifier(targets) {
|
|
|
192128
193132
|
findings
|
|
192129
193133
|
}));
|
|
192130
193134
|
}
|
|
192131
|
-
function markVerificationOverflow(targets) {
|
|
193135
|
+
function markVerificationOverflow(targets, reason) {
|
|
192132
193136
|
for (const target of targets) {
|
|
192133
|
-
target.finding.verification = {
|
|
193137
|
+
target.finding.verification = {
|
|
193138
|
+
status: "not_verified",
|
|
193139
|
+
...reason ? { note: reason } : {}
|
|
193140
|
+
};
|
|
192134
193141
|
}
|
|
192135
193142
|
}
|
|
192136
193143
|
function parseVerificationVerdicts(rawText) {
|
|
@@ -192142,7 +193149,7 @@ function parseVerificationVerdicts(rawText) {
|
|
|
192142
193149
|
if (!Array.isArray(parsed.verdicts))
|
|
192143
193150
|
return;
|
|
192144
193151
|
return parsed.verdicts.flatMap((item) => {
|
|
192145
|
-
if (!
|
|
193152
|
+
if (!isRecord11(item))
|
|
192146
193153
|
return [];
|
|
192147
193154
|
if (typeof item.findingId !== "string")
|
|
192148
193155
|
return [];
|
|
@@ -192220,15 +193227,23 @@ function verificationNote(reasoning) {
|
|
|
192220
193227
|
function isVerdict(value) {
|
|
192221
193228
|
return value === "confirmed" || value === "refuted" || value === "uncertain";
|
|
192222
193229
|
}
|
|
192223
|
-
function
|
|
193230
|
+
function isRecord11(value) {
|
|
192224
193231
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
192225
193232
|
}
|
|
192226
193233
|
|
|
192227
193234
|
// src/core/runReview.ts
|
|
193235
|
+
function requestForRecursionFingerprint(request) {
|
|
193236
|
+
try {
|
|
193237
|
+
return scanAndRedactSecrets(request).redactedRequest;
|
|
193238
|
+
} catch {
|
|
193239
|
+
return { goal: "" };
|
|
193240
|
+
}
|
|
193241
|
+
}
|
|
192228
193242
|
async function runReview(tool, request, options = {}) {
|
|
192229
193243
|
const cwd = options.cwd ?? process.cwd();
|
|
192230
193244
|
const traceId = newTraceId();
|
|
192231
|
-
const
|
|
193245
|
+
const startedAtEpochMs = Date.now();
|
|
193246
|
+
const startedAt = new Date(startedAtEpochMs).toISOString();
|
|
192232
193247
|
const auditEnv = { ...process.env, ...options.env };
|
|
192233
193248
|
const traceWriterFactory = options.traceWriterFactory ?? createTraceWriter;
|
|
192234
193249
|
let snapshot;
|
|
@@ -192237,6 +193252,14 @@ async function runReview(tool, request, options = {}) {
|
|
|
192237
193252
|
} catch (error51) {
|
|
192238
193253
|
if (error51 instanceof KyosoRequestError) {
|
|
192239
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
|
+
});
|
|
192240
193263
|
const trace2 = traceWriterFactory({
|
|
192241
193264
|
enabled: config2.audit.enabled,
|
|
192242
193265
|
directory: config2.audit.directory,
|
|
@@ -192251,6 +193274,12 @@ async function runReview(tool, request, options = {}) {
|
|
|
192251
193274
|
tool,
|
|
192252
193275
|
timestamp: new Date().toISOString()
|
|
192253
193276
|
});
|
|
193277
|
+
await writeReviewBudgetPlanned({
|
|
193278
|
+
trace: trace2,
|
|
193279
|
+
traceId,
|
|
193280
|
+
budgetTracker,
|
|
193281
|
+
requestFingerprint
|
|
193282
|
+
});
|
|
192254
193283
|
return await buildPolicyBlockResult({
|
|
192255
193284
|
tool,
|
|
192256
193285
|
trace: trace2,
|
|
@@ -192258,6 +193287,8 @@ async function runReview(tool, request, options = {}) {
|
|
|
192258
193287
|
startedAt,
|
|
192259
193288
|
networkMode: config2.network.defaultMode,
|
|
192260
193289
|
warning: error51.message,
|
|
193290
|
+
budgetTracker,
|
|
193291
|
+
requestFingerprint,
|
|
192261
193292
|
finding: {
|
|
192262
193293
|
id: "KYOSO-1",
|
|
192263
193294
|
severity: "critical",
|
|
@@ -192323,6 +193354,8 @@ async function runReview(tool, request, options = {}) {
|
|
|
192323
193354
|
timestamp: new Date().toISOString()
|
|
192324
193355
|
});
|
|
192325
193356
|
validateReviewRequest(tool, request);
|
|
193357
|
+
const reviewBudget = resolveReviewBudget(loaded.config.reviewBudget, request.options?.reviewBudget);
|
|
193358
|
+
const budgetTracker = new ReviewBudgetTracker(reviewBudget, startedAtEpochMs);
|
|
192326
193359
|
assertTrustedWorkspaceRoot(request.workspace?.root, loaded.config.workspace.root, cwd);
|
|
192327
193360
|
const networkMode = resolveNetworkMode(request.options?.network, loaded.config.network.defaultMode, options.mcpNetworkMode);
|
|
192328
193361
|
if (networkMode === "unrestricted" && !loaded.config.network.allowUnrestricted) {
|
|
@@ -192341,6 +193374,19 @@ async function runReview(tool, request, options = {}) {
|
|
|
192341
193374
|
});
|
|
192342
193375
|
const allowSecretOverride = loaded.config.secrets.allowOverride && request.options?.allowSecretRedaction === true;
|
|
192343
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
|
+
});
|
|
192344
193390
|
return await buildSecretBlockResult({
|
|
192345
193391
|
tool,
|
|
192346
193392
|
trace,
|
|
@@ -192349,7 +193395,9 @@ async function runReview(tool, request, options = {}) {
|
|
|
192349
193395
|
configHash: loaded.configHash,
|
|
192350
193396
|
networkMode,
|
|
192351
193397
|
secretScan,
|
|
192352
|
-
warnings
|
|
193398
|
+
warnings,
|
|
193399
|
+
budgetTracker,
|
|
193400
|
+
requestFingerprint: requestFingerprint2
|
|
192353
193401
|
});
|
|
192354
193402
|
}
|
|
192355
193403
|
const denyPatterns = mergeDenyPatterns(loaded.config.workspace.deny, secretScan.redactedRequest.workspace?.denyRead);
|
|
@@ -192362,6 +193410,19 @@ async function runReview(tool, request, options = {}) {
|
|
|
192362
193410
|
});
|
|
192363
193411
|
warnings.push(...built.warnings);
|
|
192364
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
|
+
});
|
|
192365
193426
|
snapshot = await createSnapshot(traceId, tool, built.request, {
|
|
192366
193427
|
denyPatterns,
|
|
192367
193428
|
allowPatterns,
|
|
@@ -192374,7 +193435,7 @@ async function runReview(tool, request, options = {}) {
|
|
|
192374
193435
|
fileCount: snapshot.fileCount,
|
|
192375
193436
|
timestamp: new Date().toISOString()
|
|
192376
193437
|
});
|
|
192377
|
-
const manager = options.agentManager ?? defaultAgentManager(loaded.config);
|
|
193438
|
+
const manager = options.agentManager ?? defaultAgentManager(loaded.config, options.env ?? process.env);
|
|
192378
193439
|
const agentResults = await runAgents({
|
|
192379
193440
|
tool,
|
|
192380
193441
|
request: built.request,
|
|
@@ -192383,14 +193444,23 @@ async function runReview(tool, request, options = {}) {
|
|
|
192383
193444
|
workspaceDir: snapshot.root,
|
|
192384
193445
|
networkMode,
|
|
192385
193446
|
manager,
|
|
192386
|
-
trace
|
|
193447
|
+
trace,
|
|
193448
|
+
warnings,
|
|
193449
|
+
budgetTracker
|
|
192387
193450
|
});
|
|
192388
193451
|
warnings.push(...agentResults.flatMap((result) => (result.warnings ?? []).map((warning) => `Agent ${result.agent} ${warning}`)));
|
|
192389
|
-
const
|
|
192390
|
-
const
|
|
192391
|
-
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";
|
|
192392
193461
|
const completed = normalizedAgentResults.filter((result) => result.status === "completed");
|
|
192393
|
-
const
|
|
193462
|
+
const attempted = normalizedAgentResults.filter((result) => result.status !== "skipped");
|
|
193463
|
+
const degraded = completed.length !== attempted.length || enabledAgents.length === 0;
|
|
192394
193464
|
let aggregate = aggregateAgentResults(normalizedAgentResults, {
|
|
192395
193465
|
reviewMode
|
|
192396
193466
|
});
|
|
@@ -192406,7 +193476,12 @@ async function runReview(tool, request, options = {}) {
|
|
|
192406
193476
|
])
|
|
192407
193477
|
};
|
|
192408
193478
|
}
|
|
192409
|
-
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
|
+
}
|
|
192410
193485
|
aggregate = {
|
|
192411
193486
|
...aggregate,
|
|
192412
193487
|
findings: [
|
|
@@ -192415,9 +193490,9 @@ async function runReview(tool, request, options = {}) {
|
|
|
192415
193490
|
id: `KYOSO-${aggregate.findings.length + 1}`,
|
|
192416
193491
|
severity: "critical",
|
|
192417
193492
|
category: "other",
|
|
192418
|
-
title: "All backend agents failed",
|
|
192419
|
-
evidence: normalizedAgentResults.map((result) => `${result.agent}: ${result.error?.code ?? result.status}`).join("; "),
|
|
192420
|
-
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.",
|
|
192421
193496
|
sourceAgents: ["kyoso_policy"],
|
|
192422
193497
|
confidence: "high"
|
|
192423
193498
|
}
|
|
@@ -192430,7 +193505,7 @@ async function runReview(tool, request, options = {}) {
|
|
|
192430
193505
|
findingCount: aggregate.findings.length,
|
|
192431
193506
|
timestamp: new Date().toISOString()
|
|
192432
193507
|
});
|
|
192433
|
-
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;
|
|
192434
193509
|
if (verificationMode === "cross_agent") {
|
|
192435
193510
|
warnings.push(...await runFindingVerification({
|
|
192436
193511
|
tool,
|
|
@@ -192441,11 +193516,16 @@ async function runReview(tool, request, options = {}) {
|
|
|
192441
193516
|
networkMode,
|
|
192442
193517
|
manager,
|
|
192443
193518
|
trace,
|
|
192444
|
-
findings: aggregate.findings
|
|
193519
|
+
findings: aggregate.findings,
|
|
193520
|
+
budgetTracker
|
|
192445
193521
|
}));
|
|
192446
193522
|
}
|
|
193523
|
+
if (aggregate.findings.some((finding) => finding.verification?.status === "refuted")) {
|
|
193524
|
+
budgetTracker.markIncomplete("disputed_finding");
|
|
193525
|
+
}
|
|
192447
193526
|
const cisa = tool === "security_review" ? computeCisaGate(aggregate.findings, normalizedAgentResults) : undefined;
|
|
192448
|
-
const
|
|
193527
|
+
const budgetBeforeJudge = budgetTracker.snapshot();
|
|
193528
|
+
const decision = budgetBeforeJudge.completion.status === "incomplete" ? "block" : decide({
|
|
192449
193529
|
tool,
|
|
192450
193530
|
findings: aggregate.findings,
|
|
192451
193531
|
cisa,
|
|
@@ -192455,6 +193535,9 @@ async function runReview(tool, request, options = {}) {
|
|
|
192455
193535
|
const completedAt = new Date().toISOString();
|
|
192456
193536
|
const resultWithoutMarkdown = {
|
|
192457
193537
|
decision,
|
|
193538
|
+
completion: budgetBeforeJudge.completion,
|
|
193539
|
+
executionBudget: budgetBeforeJudge.executionBudget,
|
|
193540
|
+
requestFingerprint,
|
|
192458
193541
|
degraded,
|
|
192459
193542
|
agentsUsed,
|
|
192460
193543
|
reviewMode,
|
|
@@ -192476,18 +193559,22 @@ async function runReview(tool, request, options = {}) {
|
|
|
192476
193559
|
networkMode,
|
|
192477
193560
|
workspaceMode: "temp_snapshot",
|
|
192478
193561
|
configHash: loaded.configHash,
|
|
192479
|
-
warnings: Array.from(new Set([...warnings, ...trace.warnings]))
|
|
193562
|
+
warnings: Array.from(new Set([...warnings, ...trace.warnings])),
|
|
193563
|
+
modelCalls: budgetBeforeJudge.modelCalls
|
|
192480
193564
|
}
|
|
192481
193565
|
};
|
|
192482
193566
|
const summaryText = defaultSummaryText(resultWithoutMarkdown);
|
|
192483
|
-
const judge = await
|
|
193567
|
+
const judge = await runBudgetedJudge({
|
|
192484
193568
|
tool,
|
|
192485
193569
|
result: resultWithoutMarkdown,
|
|
192486
193570
|
summaryText,
|
|
192487
193571
|
agentFindings: buildJudgeAgentFindings(normalizedAgentResults),
|
|
192488
193572
|
config: loaded.config.judge,
|
|
192489
193573
|
requestedProvider: request.options?.judgeProvider,
|
|
192490
|
-
env: options.env ?? process.env
|
|
193574
|
+
env: options.env ?? process.env,
|
|
193575
|
+
budgetTracker,
|
|
193576
|
+
trace,
|
|
193577
|
+
traceId
|
|
192491
193578
|
});
|
|
192492
193579
|
const judgeComments = new Map(judge.output.disagreementComments.map((comment) => [
|
|
192493
193580
|
comment.topic,
|
|
@@ -192498,10 +193585,20 @@ async function runReview(tool, request, options = {}) {
|
|
|
192498
193585
|
judgeComment: judgeComments.get(disagreement.topic) ?? disagreement.judgeComment
|
|
192499
193586
|
}));
|
|
192500
193587
|
const crossModelAnalysis = buildCrossModelAnalysis(judge, reviewMode);
|
|
193588
|
+
const budgetAfterJudge = budgetTracker.snapshot();
|
|
193589
|
+
const finalDecision = budgetAfterJudge.completion.status === "incomplete" ? "block" : decision;
|
|
192501
193590
|
const resultAfterJudge = {
|
|
192502
193591
|
...resultWithoutMarkdown,
|
|
193592
|
+
decision: finalDecision,
|
|
193593
|
+
completion: budgetAfterJudge.completion,
|
|
193594
|
+
executionBudget: budgetAfterJudge.executionBudget,
|
|
192503
193595
|
disagreements,
|
|
192504
|
-
...crossModelAnalysis ? { crossModelAnalysis } : {}
|
|
193596
|
+
...crossModelAnalysis ? { crossModelAnalysis } : {},
|
|
193597
|
+
audit: {
|
|
193598
|
+
...resultWithoutMarkdown.audit,
|
|
193599
|
+
completedAt: new Date().toISOString(),
|
|
193600
|
+
modelCalls: budgetAfterJudge.modelCalls
|
|
193601
|
+
}
|
|
192505
193602
|
};
|
|
192506
193603
|
const judgeEvent = {
|
|
192507
193604
|
type: "judge_completed",
|
|
@@ -192514,10 +193611,16 @@ async function runReview(tool, request, options = {}) {
|
|
|
192514
193611
|
judgeEvent.error = judge.error;
|
|
192515
193612
|
await trace.write(judgeEvent);
|
|
192516
193613
|
resultAfterJudge.audit.completedAt = new Date().toISOString();
|
|
193614
|
+
await writeReviewBudgetCompleted({
|
|
193615
|
+
trace,
|
|
193616
|
+
traceId,
|
|
193617
|
+
budgetTracker,
|
|
193618
|
+
requestFingerprint
|
|
193619
|
+
});
|
|
192517
193620
|
await trace.write({
|
|
192518
193621
|
type: "decision_completed",
|
|
192519
193622
|
traceId,
|
|
192520
|
-
decision,
|
|
193623
|
+
decision: finalDecision,
|
|
192521
193624
|
timestamp: new Date().toISOString()
|
|
192522
193625
|
});
|
|
192523
193626
|
await trace.write({
|
|
@@ -192529,7 +193632,7 @@ async function runReview(tool, request, options = {}) {
|
|
|
192529
193632
|
tool,
|
|
192530
193633
|
trace,
|
|
192531
193634
|
result: resultAfterJudge,
|
|
192532
|
-
summaryText: judge.output.summaryText
|
|
193635
|
+
summaryText: resultAfterJudge.completion.status === "incomplete" ? defaultSummaryText(resultAfterJudge) : judge.output.summaryText
|
|
192533
193636
|
});
|
|
192534
193637
|
} finally {
|
|
192535
193638
|
await trace.finalize();
|
|
@@ -192540,37 +193643,180 @@ async function runReview(tool, request, options = {}) {
|
|
|
192540
193643
|
async function runFindingVerification(input) {
|
|
192541
193644
|
const allowDemotionRequested = input.config.verification.allowDemotion;
|
|
192542
193645
|
const selection = selectVerificationTargets(input.findings, input.config.verification.maxFindings);
|
|
192543
|
-
markVerificationOverflow(selection.overflow);
|
|
192544
|
-
if (selection.selected.length === 0)
|
|
192545
|
-
return [];
|
|
192546
193646
|
const warnings = [];
|
|
192547
|
-
|
|
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);
|
|
192548
193654
|
await input.trace.write({
|
|
192549
193655
|
type: "verification_started",
|
|
192550
193656
|
traceId: input.traceId,
|
|
192551
193657
|
targetCount: selection.selected.length,
|
|
192552
193658
|
notVerifiedCount: selection.overflow.length,
|
|
192553
|
-
verifierCount:
|
|
193659
|
+
verifierCount: potentialGroups.length,
|
|
192554
193660
|
timeoutMs: input.config.verification.timeoutMs,
|
|
192555
193661
|
allowDemotionRequested,
|
|
192556
193662
|
timestamp: new Date().toISOString()
|
|
192557
193663
|
});
|
|
192558
|
-
|
|
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) => ({
|
|
192559
193791
|
traceId: input.traceId,
|
|
192560
|
-
agent: verifier,
|
|
193792
|
+
agent: group.verifier,
|
|
192561
193793
|
role: "finding_verifier",
|
|
192562
193794
|
tool: input.tool,
|
|
192563
|
-
prompt: buildFindingVerifierPrompt(input.tool, input.request, verifier,
|
|
193795
|
+
prompt: buildFindingVerifierPrompt(input.tool, input.request, group.verifier, group.targets.map((target) => target.finding)),
|
|
192564
193796
|
workspaceDir: input.workspaceDir,
|
|
192565
|
-
timeoutMs: input.config.verification.timeoutMs,
|
|
192566
|
-
|
|
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
|
+
}
|
|
192567
193805
|
}));
|
|
192568
193806
|
let results;
|
|
192569
193807
|
try {
|
|
192570
193808
|
results = await input.manager.runAll(agentInputs);
|
|
192571
193809
|
} catch (error51) {
|
|
192572
|
-
for (const group of
|
|
192573
|
-
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");
|
|
192574
193820
|
}
|
|
192575
193821
|
const message = `Finding verification failed: ${sanitizeTextForDisplay(error51 instanceof Error ? error51.message : String(error51))}`;
|
|
192576
193822
|
warnings.push(message);
|
|
@@ -192583,9 +193829,32 @@ async function runFindingVerification(input) {
|
|
|
192583
193829
|
});
|
|
192584
193830
|
return warnings;
|
|
192585
193831
|
}
|
|
192586
|
-
|
|
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
|
+
});
|
|
192587
193855
|
if (result.status !== "completed") {
|
|
192588
|
-
applyVerificationVerdicts(
|
|
193856
|
+
applyVerificationVerdicts(group.targets, result.agent, undefined);
|
|
193857
|
+
input.budgetTracker.markIncomplete("coverage_incomplete");
|
|
192589
193858
|
const message = `Finding verification by ${result.agent} ${result.status}: ${result.error?.message ?? result.error?.code ?? "no detail"}`;
|
|
192590
193859
|
warnings.push(sanitizeTextForDisplay(message));
|
|
192591
193860
|
await input.trace.write({
|
|
@@ -192599,8 +193868,9 @@ async function runFindingVerification(input) {
|
|
|
192599
193868
|
continue;
|
|
192600
193869
|
}
|
|
192601
193870
|
const verdicts = parseVerificationVerdicts(result.rawText);
|
|
192602
|
-
applyVerificationVerdicts(
|
|
193871
|
+
applyVerificationVerdicts(group.targets, result.agent, verdicts);
|
|
192603
193872
|
if (!verdicts) {
|
|
193873
|
+
input.budgetTracker.markIncomplete("coverage_incomplete");
|
|
192604
193874
|
const message = `Finding verification by ${result.agent} returned malformed verdict JSON.`;
|
|
192605
193875
|
warnings.push(message);
|
|
192606
193876
|
await input.trace.write({
|
|
@@ -192620,6 +193890,20 @@ async function runFindingVerification(input) {
|
|
|
192620
193890
|
});
|
|
192621
193891
|
return warnings;
|
|
192622
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
|
+
}
|
|
192623
193907
|
function buildJudgeAgentFindings(results) {
|
|
192624
193908
|
return results.flatMap((result) => {
|
|
192625
193909
|
if (!result.normalized)
|
|
@@ -192656,27 +193940,277 @@ function buildCrossModelAnalysis(judge, reviewMode) {
|
|
|
192656
193940
|
provider: judge.provider
|
|
192657
193941
|
};
|
|
192658
193942
|
}
|
|
192659
|
-
async function
|
|
192660
|
-
const
|
|
192661
|
-
const
|
|
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",
|
|
192662
193977
|
traceId: input.traceId,
|
|
192663
|
-
|
|
192664
|
-
|
|
192665
|
-
|
|
192666
|
-
|
|
192667
|
-
|
|
192668
|
-
|
|
192669
|
-
|
|
192670
|
-
|
|
192671
|
-
|
|
192672
|
-
|
|
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",
|
|
192673
194009
|
traceId: input.traceId,
|
|
192674
|
-
|
|
192675
|
-
|
|
194010
|
+
kind: "judge",
|
|
194011
|
+
provider: judge.provider,
|
|
194012
|
+
resultStatus: judge.status,
|
|
194013
|
+
...usage ? { usage } : {},
|
|
192676
194014
|
timestamp: new Date().toISOString()
|
|
192677
|
-
})
|
|
192678
|
-
|
|
192679
|
-
|
|
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
|
+
}
|
|
194028
|
+
async function runAgents(input) {
|
|
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
|
+
}
|
|
194110
|
+
const startedWrites = [];
|
|
194111
|
+
let acceptingStartedEvents = true;
|
|
194112
|
+
const agentInputs = enabledAgents.map((agent) => {
|
|
194113
|
+
const agentConfig = input.config.agents[agent];
|
|
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
|
+
}
|
|
194119
|
+
return {
|
|
194120
|
+
traceId: input.traceId,
|
|
194121
|
+
agent,
|
|
194122
|
+
role,
|
|
194123
|
+
tool: input.tool,
|
|
194124
|
+
prompt: buildAgentPrompt(input.tool, input.request, agent, role),
|
|
194125
|
+
workspaceDir: input.workspaceDir,
|
|
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,
|
|
194129
|
+
networkMode: input.networkMode,
|
|
194130
|
+
onStarted: () => {
|
|
194131
|
+
input.budgetTracker.markStarted(reservation);
|
|
194132
|
+
if (!acceptingStartedEvents)
|
|
194133
|
+
return Promise.resolve();
|
|
194134
|
+
const event = {
|
|
194135
|
+
type: "agent_started",
|
|
194136
|
+
traceId: input.traceId,
|
|
194137
|
+
agent,
|
|
194138
|
+
role,
|
|
194139
|
+
timestamp: new Date().toISOString()
|
|
194140
|
+
};
|
|
194141
|
+
if (agentConfig.model) {
|
|
194142
|
+
event.model = sanitizeTextForDisplay(agentConfig.model);
|
|
194143
|
+
}
|
|
194144
|
+
if (agent === "codex" && input.config.agents.codex.provider === CODEX_OPENROUTER_PROVIDER) {
|
|
194145
|
+
event.provider = CODEX_OPENROUTER_PROVIDER;
|
|
194146
|
+
}
|
|
194147
|
+
const write = (async () => {
|
|
194148
|
+
try {
|
|
194149
|
+
await input.trace.write(event);
|
|
194150
|
+
} catch {
|
|
194151
|
+
input.warnings.push("AUDIT_WRITE_FAILED: agent_started event could not be recorded.");
|
|
194152
|
+
}
|
|
194153
|
+
})();
|
|
194154
|
+
startedWrites.push(write);
|
|
194155
|
+
return write;
|
|
194156
|
+
}
|
|
194157
|
+
};
|
|
194158
|
+
});
|
|
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
|
+
}
|
|
194177
|
+
acceptingStartedEvents = false;
|
|
194178
|
+
await Promise.all(startedWrites);
|
|
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) => {
|
|
192680
194214
|
const event = {
|
|
192681
194215
|
type: "agent_completed",
|
|
192682
194216
|
traceId: input.traceId,
|
|
@@ -192696,8 +194230,92 @@ async function runAgents(input) {
|
|
|
192696
194230
|
}
|
|
192697
194231
|
return input.trace.write(event);
|
|
192698
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
|
+
}
|
|
192699
194262
|
return results;
|
|
192700
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
|
+
}
|
|
192701
194319
|
function resolveAgentRoles(config2) {
|
|
192702
194320
|
const enabledAgents = ["codex", "claude"].filter((agent) => config2.agents[agent].enabled);
|
|
192703
194321
|
const singleAgentMode = enabledAgents.length === 1;
|
|
@@ -192707,19 +194325,36 @@ function resolveAgentRoles(config2) {
|
|
|
192707
194325
|
}
|
|
192708
194326
|
return roles;
|
|
192709
194327
|
}
|
|
192710
|
-
function defaultAgentManager(config2) {
|
|
192711
|
-
if (
|
|
194328
|
+
function defaultAgentManager(config2, parentEnv) {
|
|
194329
|
+
if (parentEnv.KYOSO_TEST_FAKE_AGENTS === "1") {
|
|
192712
194330
|
return new FakeAgentManager;
|
|
192713
|
-
return new SubprocessAcpAgentManager(config2);
|
|
192714
|
-
}
|
|
192715
|
-
function normalizeAgentRunResult(result) {
|
|
192716
|
-
if (result.status === "completed" && result.rawText && !result.normalized) {
|
|
192717
|
-
return {
|
|
192718
|
-
...result,
|
|
192719
|
-
normalized: normalizeAgentOutput(result.agent, result.role, result.rawText)
|
|
192720
|
-
};
|
|
192721
194331
|
}
|
|
192722
|
-
return
|
|
194332
|
+
return new SubprocessAcpAgentManager(config2, parentEnv);
|
|
194333
|
+
}
|
|
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
|
+
};
|
|
192723
194358
|
}
|
|
192724
194359
|
function agentOpinionSummary(result, includeRawText = false) {
|
|
192725
194360
|
const opinion = {
|
|
@@ -192741,8 +194376,12 @@ async function buildSecretBlockResult(input) {
|
|
|
192741
194376
|
});
|
|
192742
194377
|
const cisa = input.tool === "security_review" ? computeCisaGate([finding], []) : undefined;
|
|
192743
194378
|
const completedAt = new Date().toISOString();
|
|
194379
|
+
const budget = input.budgetTracker.snapshot();
|
|
192744
194380
|
const resultWithoutMarkdown = {
|
|
192745
194381
|
decision: "block",
|
|
194382
|
+
completion: budget.completion,
|
|
194383
|
+
executionBudget: budget.executionBudget,
|
|
194384
|
+
requestFingerprint: input.requestFingerprint,
|
|
192746
194385
|
degraded: false,
|
|
192747
194386
|
agentsUsed: [],
|
|
192748
194387
|
reviewMode: "multi_agent",
|
|
@@ -192778,9 +194417,16 @@ async function buildSecretBlockResult(input) {
|
|
|
192778
194417
|
networkMode: input.networkMode,
|
|
192779
194418
|
workspaceMode: "temp_snapshot",
|
|
192780
194419
|
configHash: input.configHash,
|
|
192781
|
-
warnings: input.warnings
|
|
194420
|
+
warnings: input.warnings,
|
|
194421
|
+
modelCalls: budget.modelCalls
|
|
192782
194422
|
}
|
|
192783
194423
|
};
|
|
194424
|
+
await writeReviewBudgetCompleted({
|
|
194425
|
+
trace: input.trace,
|
|
194426
|
+
traceId: input.traceId,
|
|
194427
|
+
budgetTracker: input.budgetTracker,
|
|
194428
|
+
requestFingerprint: input.requestFingerprint
|
|
194429
|
+
});
|
|
192784
194430
|
await input.trace.write({
|
|
192785
194431
|
type: "decision_completed",
|
|
192786
194432
|
traceId: input.traceId,
|
|
@@ -192823,8 +194469,12 @@ function reindexFindings(findings) {
|
|
|
192823
194469
|
}
|
|
192824
194470
|
async function buildPolicyBlockResult(input) {
|
|
192825
194471
|
const completedAt = new Date().toISOString();
|
|
194472
|
+
const budget = input.budgetTracker.snapshot();
|
|
192826
194473
|
const resultWithoutMarkdown = {
|
|
192827
194474
|
decision: "block",
|
|
194475
|
+
completion: budget.completion,
|
|
194476
|
+
executionBudget: budget.executionBudget,
|
|
194477
|
+
requestFingerprint: input.requestFingerprint,
|
|
192828
194478
|
degraded: false,
|
|
192829
194479
|
agentsUsed: [],
|
|
192830
194480
|
reviewMode: "multi_agent",
|
|
@@ -192843,9 +194493,16 @@ async function buildPolicyBlockResult(input) {
|
|
|
192843
194493
|
networkMode: input.networkMode,
|
|
192844
194494
|
workspaceMode: "temp_snapshot",
|
|
192845
194495
|
configHash: input.configHash,
|
|
192846
|
-
warnings: [input.warning]
|
|
194496
|
+
warnings: [input.warning],
|
|
194497
|
+
modelCalls: budget.modelCalls
|
|
192847
194498
|
}
|
|
192848
194499
|
};
|
|
194500
|
+
await writeReviewBudgetCompleted({
|
|
194501
|
+
trace: input.trace,
|
|
194502
|
+
traceId: input.traceId,
|
|
194503
|
+
budgetTracker: input.budgetTracker,
|
|
194504
|
+
requestFingerprint: input.requestFingerprint
|
|
194505
|
+
});
|
|
192849
194506
|
await input.trace.write({
|
|
192850
194507
|
type: "decision_completed",
|
|
192851
194508
|
traceId: input.traceId,
|
|
@@ -192882,6 +194539,33 @@ async function finalizeReviewResult(input) {
|
|
|
192882
194539
|
})
|
|
192883
194540
|
};
|
|
192884
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
|
+
}
|
|
192885
194569
|
function mergeDenyPatterns(configDeny, requestDeny) {
|
|
192886
194570
|
return Array.from(new Set([...configDeny, ...requestDeny ?? []]));
|
|
192887
194571
|
}
|