@kyo-so/cli 0.12.0 → 0.13.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/dist/bin/kyoso.js CHANGED
@@ -183944,13 +183944,14 @@ function matchesPathPattern(path, patterns, mode) {
183944
183944
  }
183945
183945
 
183946
183946
  // src/core/constants.ts
183947
- var DEFAULT_AGENT_TIMEOUT_MS = 120000;
183947
+ var DEFAULT_AGENT_TIMEOUT_MS = 600000;
183948
+ var DEFAULT_WARN_AGENT_OUTPUT_BYTES = 524288;
183948
183949
  var MAX_AGENT_OUTPUT_BYTES = 1048576;
183949
183950
  var JUDGE_MAX_OUTPUT_TOKENS = 4096;
183950
183951
  var RAW_OUTPUT_MAX_CHARS = 16384;
183951
183952
  var TRACE_DIR = ".kyoso/traces";
183952
183953
  var KYOSO_CHILD_AGENT = "KYOSO_CHILD_AGENT";
183953
- var KYOSO_VERSION = "0.12.0";
183954
+ var KYOSO_VERSION = "0.13.0";
183954
183955
 
183955
183956
  // src/utils/pathContainment.ts
183956
183957
  import { resolve, sep as sep2 } from "node:path";
@@ -184255,9 +184256,9 @@ var defaultConfig = {
184255
184256
  enabled: true,
184256
184257
  type: "acp",
184257
184258
  command: "npx",
184258
- args: ["-y", "@agentclientprotocol/codex-acp@1.1.2"],
184259
+ args: ["-y", "@agentclientprotocol/codex-acp@1.1.4"],
184259
184260
  role: "implementation_reviewer",
184260
- timeoutMs: 120000,
184261
+ timeoutMs: DEFAULT_AGENT_TIMEOUT_MS,
184261
184262
  allowProjectProvider: [],
184262
184263
  env: {
184263
184264
  INITIAL_AGENT_MODE: "read-only",
@@ -184282,7 +184283,7 @@ var defaultConfig = {
184282
184283
  command: "npx",
184283
184284
  args: ["-y", "@agentclientprotocol/claude-agent-acp@0.58.1"],
184284
184285
  role: "architecture_security_reviewer",
184285
- timeoutMs: 300000,
184286
+ timeoutMs: DEFAULT_AGENT_TIMEOUT_MS,
184286
184287
  env: {
184287
184288
  KYOSO_CHILD_AGENT: "1"
184288
184289
  },
@@ -184363,10 +184364,11 @@ var defaultConfig = {
184363
184364
  },
184364
184365
  reviewBudget: {
184365
184366
  maxModelCalls: 4,
184366
- maxTotalWallTimeMs: 480000,
184367
- maxAgentOutputBytes: 65536,
184367
+ maxTotalWallTimeMs: 660000,
184368
+ warnAgentOutputBytes: DEFAULT_WARN_AGENT_OUTPUT_BYTES,
184369
+ maxAgentOutputBytes: 1048576,
184368
184370
  maxFindingsPerAgent: 10,
184369
- skipOptionalPhasesWhenTokenUsageUnknown: true
184371
+ skipOptionalPhasesWhenTokenUsageUnknown: false
184370
184372
  },
184371
184373
  audit: {
184372
184374
  enabled: true,
@@ -184724,7 +184726,7 @@ var baseAgentSchema = exports_external.object({
184724
184726
  "architecture_security_reviewer",
184725
184727
  "combined_reviewer"
184726
184728
  ]),
184727
- timeoutMs: exports_external.number().int().positive().default(120000),
184729
+ timeoutMs: exports_external.number().int().positive().default(DEFAULT_AGENT_TIMEOUT_MS),
184728
184730
  env: exports_external.record(exports_external.string(), exports_external.string()).default({}),
184729
184731
  auth: exports_external.object({
184730
184732
  mode: exports_external.literal("passthrough").default("passthrough"),
@@ -184755,6 +184757,7 @@ var codexAgentSchema = baseAgentSchema.extend({
184755
184757
  var reviewBudgetSchema = exports_external.object({
184756
184758
  maxModelCalls: exports_external.number().int().positive(),
184757
184759
  maxTotalWallTimeMs: exports_external.number().int().positive(),
184760
+ warnAgentOutputBytes: exports_external.number().int().positive().max(MAX_AGENT_OUTPUT_BYTES),
184758
184761
  maxAgentOutputBytes: exports_external.number().int().positive().max(MAX_AGENT_OUTPUT_BYTES),
184759
184762
  maxFindingsPerAgent: exports_external.number().int().positive(),
184760
184763
  skipOptionalPhasesWhenTokenUsageUnknown: exports_external.boolean()
@@ -184830,13 +184833,21 @@ var kyosoConfigSchema = exports_external.object({
184830
184833
  })
184831
184834
  }).superRefine((config2, context) => {
184832
184835
  const enabledPrimaryReviewers = Object.values(config2.agents).filter((agent) => agent.enabled).length;
184833
- if (config2.reviewBudget.maxModelCalls >= enabledPrimaryReviewers)
184834
- return;
184835
- context.addIssue({
184836
- code: exports_external.ZodIssueCode.custom,
184837
- path: ["reviewBudget", "maxModelCalls"],
184838
- message: "must be greater than or equal to the number of enabled primary reviewers."
184839
- });
184836
+ if (config2.reviewBudget.maxModelCalls < enabledPrimaryReviewers) {
184837
+ context.addIssue({
184838
+ code: exports_external.ZodIssueCode.custom,
184839
+ path: ["reviewBudget", "maxModelCalls"],
184840
+ message: "must be greater than or equal to the number of enabled primary reviewers."
184841
+ });
184842
+ }
184843
+ const inheritedLegacyHardLimit = config2.reviewBudget.warnAgentOutputBytes === DEFAULT_WARN_AGENT_OUTPUT_BYTES && config2.reviewBudget.maxAgentOutputBytes <= DEFAULT_WARN_AGENT_OUTPUT_BYTES;
184844
+ if (config2.reviewBudget.warnAgentOutputBytes >= config2.reviewBudget.maxAgentOutputBytes && !inheritedLegacyHardLimit) {
184845
+ context.addIssue({
184846
+ code: exports_external.ZodIssueCode.custom,
184847
+ path: ["reviewBudget", "warnAgentOutputBytes"],
184848
+ message: "must be less than reviewBudget.maxAgentOutputBytes."
184849
+ });
184850
+ }
184840
184851
  });
184841
184852
  function agentConfigLeafPaths(agent) {
184842
184853
  const paths = [
@@ -184899,6 +184910,7 @@ var kyosoConfigKnownLeafPaths = [
184899
184910
  "verification.allowDemotion",
184900
184911
  "reviewBudget.maxModelCalls",
184901
184912
  "reviewBudget.maxTotalWallTimeMs",
184913
+ "reviewBudget.warnAgentOutputBytes",
184902
184914
  "reviewBudget.maxAgentOutputBytes",
184903
184915
  "reviewBudget.maxFindingsPerAgent",
184904
184916
  "reviewBudget.skipOptionalPhasesWhenTokenUsageUnknown",
@@ -186033,6 +186045,7 @@ async function loadConfig(options = {}) {
186033
186045
  if (!options.ignoreConfig) {
186034
186046
  if (await exists2(globalConfigPath)) {
186035
186047
  const globalConfig2 = await loadTomlConfigFile(globalConfigPath);
186048
+ validateExplicitReviewBudgetThresholds(globalConfig2, defaultConfig);
186036
186049
  const globalConfigWarnings = collectGlobalConfigWarnings(globalConfigPath, globalConfig2);
186037
186050
  const securitySensitiveWarnings = globalConfigWarnings.filter((warning) => warning.startsWith("security-sensitive unknown settings "));
186038
186051
  if (securitySensitiveWarnings.length > 0 && !options.allowUnknownConfig) {
@@ -186281,6 +186294,7 @@ async function loadProjectTsConfig(input2) {
186281
186294
  });
186282
186295
  if (trustDecision.execute) {
186283
186296
  const userConfig = await loadUserConfig(canonicalPath, source);
186297
+ validateExplicitReviewBudgetThresholds(userConfig, input2.baseConfig);
186284
186298
  const projectChangesOpenRouterRoute = projectConfigChangesOpenRouterRoute(userConfig, input2.baseConfig);
186285
186299
  await assertProjectOpenRouterAuthorization({
186286
186300
  projectConfig: userConfig,
@@ -186374,6 +186388,23 @@ function deepMerge2(base, override) {
186374
186388
  }
186375
186389
  return result;
186376
186390
  }
186391
+ function validateExplicitReviewBudgetThresholds(config2, baseConfig) {
186392
+ const reviewBudget = readRecord(config2, "reviewBudget");
186393
+ if (!reviewBudget || !Object.prototype.hasOwnProperty.call(reviewBudget, "warnAgentOutputBytes")) {
186394
+ return;
186395
+ }
186396
+ const warnAgentOutputBytes = reviewBudget.warnAgentOutputBytes;
186397
+ const maxAgentOutputBytes = Object.prototype.hasOwnProperty.call(reviewBudget, "maxAgentOutputBytes") ? reviewBudget.maxAgentOutputBytes : readRecord(baseConfig, "reviewBudget")?.maxAgentOutputBytes;
186398
+ if (typeof warnAgentOutputBytes === "number" && typeof maxAgentOutputBytes === "number" && warnAgentOutputBytes >= maxAgentOutputBytes) {
186399
+ throw new Error("reviewBudget.warnAgentOutputBytes must be less than reviewBudget.maxAgentOutputBytes.");
186400
+ }
186401
+ }
186402
+ function readRecord(value, key) {
186403
+ if (!isRecord3(value))
186404
+ return;
186405
+ const nested = value[key];
186406
+ return isRecord3(nested) ? nested : undefined;
186407
+ }
186377
186408
  function isRecord3(value) {
186378
186409
  return typeof value === "object" && value !== null && !Array.isArray(value);
186379
186410
  }
@@ -186386,6 +186417,55 @@ async function exists2(path) {
186386
186417
  }
186387
186418
  }
186388
186419
 
186420
+ // src/core/modelExecutionIdentity.ts
186421
+ var MODEL_EXECUTION_IDENTITY_MAX_CHARS = 160;
186422
+ var MODEL_PROVIDER_ROUTES = new Set([
186423
+ "codex_default",
186424
+ "claude_default",
186425
+ "openrouter",
186426
+ "openai",
186427
+ "anthropic"
186428
+ ]);
186429
+ function createModelExecutionIdentity(input2) {
186430
+ const requestedModel = sanitizeIdentityValue(input2.requestedModel);
186431
+ const reportedProvider = sanitizeIdentityValue(input2.reportedProvider);
186432
+ const reportedModel = sanitizeIdentityValue(input2.reportedModel);
186433
+ const reportingStatus = reportedProvider !== undefined || reportedModel !== undefined ? "reported" : requestedModel !== undefined ? "requested_only" : "unknown";
186434
+ return {
186435
+ providerRoute: input2.providerRoute,
186436
+ ...requestedModel ? { requestedModel } : {},
186437
+ ...reportedProvider ? { reportedProvider } : {},
186438
+ ...reportedModel ? { reportedModel } : {},
186439
+ reportingStatus
186440
+ };
186441
+ }
186442
+ function normalizeModelExecutionIdentity(value) {
186443
+ if (!isRecord4(value) || !isModelProviderRoute(value.providerRoute)) {
186444
+ return;
186445
+ }
186446
+ return createModelExecutionIdentity({
186447
+ providerRoute: value.providerRoute,
186448
+ requestedModel: value.requestedModel,
186449
+ reportedProvider: value.reportedProvider,
186450
+ reportedModel: value.reportedModel
186451
+ });
186452
+ }
186453
+ function sanitizeIdentityValue(value) {
186454
+ if (typeof value !== "string")
186455
+ return;
186456
+ const sanitized = sanitizeTextForDisplay(value, MODEL_EXECUTION_IDENTITY_MAX_CHARS);
186457
+ if (sanitized.includes(REDACTION) || /(?:https?|wss?):\/\//i.test(sanitized) || /\b(?:api[_-]?key|base[_-]?url|credential|secret|token|password)\b/i.test(sanitized) || /[{}=]/.test(sanitized)) {
186458
+ return;
186459
+ }
186460
+ return sanitized.length > 0 ? sanitized : undefined;
186461
+ }
186462
+ function isModelProviderRoute(value) {
186463
+ return typeof value === "string" && MODEL_PROVIDER_ROUTES.has(value);
186464
+ }
186465
+ function isRecord4(value) {
186466
+ return value !== null && typeof value === "object" && !Array.isArray(value);
186467
+ }
186468
+
186389
186469
  // src/core/tokenUsage.ts
186390
186470
  var TOKEN_USAGE_KEYS = [
186391
186471
  "totalTokens",
@@ -186396,7 +186476,7 @@ var TOKEN_USAGE_KEYS = [
186396
186476
  "cachedWriteTokens"
186397
186477
  ];
186398
186478
  function normalizeModelTokenUsage(usage) {
186399
- if (!isRecord4(usage))
186479
+ if (!isRecord5(usage))
186400
186480
  return;
186401
186481
  const normalized = {};
186402
186482
  for (const key of TOKEN_USAGE_KEYS) {
@@ -186409,7 +186489,7 @@ function normalizeModelTokenUsage(usage) {
186409
186489
  function isTokenCount(value) {
186410
186490
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
186411
186491
  }
186412
- function isRecord4(value) {
186492
+ function isRecord5(value) {
186413
186493
  return typeof value === "object" && value !== null && !Array.isArray(value);
186414
186494
  }
186415
186495
 
@@ -186461,7 +186541,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
186461
186541
  const parsed = JSON.parse(json2);
186462
186542
  const summaryText = typeof parsed.summaryText === "string" && parsed.summaryText.trim().length > 0 ? sanitizeText(parsed.summaryText) : fallbackSummaryText;
186463
186543
  const disagreementComments = Array.isArray(parsed.disagreementComments) ? parsed.disagreementComments.flatMap((item) => {
186464
- if (!isRecord5(item) || typeof item.topic !== "string" || typeof item.judgeComment !== "string") {
186544
+ if (!isRecord6(item) || typeof item.topic !== "string" || typeof item.judgeComment !== "string") {
186465
186545
  return [];
186466
186546
  }
186467
186547
  return [
@@ -186477,7 +186557,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
186477
186557
  return { summaryText, disagreementComments, analysis };
186478
186558
  }
186479
186559
  function parseAnalysis(value) {
186480
- if (!isRecord5(value))
186560
+ if (!isRecord6(value))
186481
186561
  return;
186482
186562
  if (!Array.isArray(value.blindSpots) || !Array.isArray(value.contradictions) || !Array.isArray(value.partialCoverage)) {
186483
186563
  return;
@@ -186485,7 +186565,7 @@ function parseAnalysis(value) {
186485
186565
  return {
186486
186566
  blindSpots: value.blindSpots.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => typeof item === "string" ? [sanitizeAnalysisText(item)] : []),
186487
186567
  contradictions: value.contradictions.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
186488
- if (!isRecord5(item) || typeof item.topic !== "string" || typeof item.detail !== "string") {
186568
+ if (!isRecord6(item) || typeof item.topic !== "string" || typeof item.detail !== "string") {
186489
186569
  return [];
186490
186570
  }
186491
186571
  return [
@@ -186496,7 +186576,7 @@ function parseAnalysis(value) {
186496
186576
  ];
186497
186577
  }),
186498
186578
  partialCoverage: value.partialCoverage.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
186499
- if (!isRecord5(item) || typeof item.note !== "string")
186579
+ if (!isRecord6(item) || typeof item.note !== "string")
186500
186580
  return [];
186501
186581
  const findingId = typeof item.findingId === "string" ? sanitizeAnalysisText(item.findingId) : undefined;
186502
186582
  return [
@@ -186543,15 +186623,20 @@ function extractFirstJsonObject(text) {
186543
186623
  }
186544
186624
  return;
186545
186625
  }
186546
- function isRecord5(value) {
186626
+ function isRecord6(value) {
186547
186627
  return typeof value === "object" && value !== null && !Array.isArray(value);
186548
186628
  }
186549
186629
 
186550
186630
  // src/judge/anthropic.ts
186631
+ var DEFAULT_ANTHROPIC_JUDGE_MODEL = "claude-haiku-4-5";
186632
+ function resolveAnthropicJudgeModel(env) {
186633
+ return env.KYOSO_ANTHROPIC_JUDGE_MODEL ?? DEFAULT_ANTHROPIC_JUDGE_MODEL;
186634
+ }
186551
186635
  async function runAnthropicJudge(input2, timeoutMs) {
186552
186636
  const apiKey = input2.env.ANTHROPIC_API_KEY;
186553
186637
  if (!apiKey)
186554
186638
  throw new Error("ANTHROPIC_API_KEY is not configured.");
186639
+ const requestedModel = resolveAnthropicJudgeModel(input2.env);
186555
186640
  const response = await fetchWithTimeout(`${input2.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com"}/v1/messages`, {
186556
186641
  method: "POST",
186557
186642
  headers: {
@@ -186560,7 +186645,7 @@ async function runAnthropicJudge(input2, timeoutMs) {
186560
186645
  "content-type": "application/json"
186561
186646
  },
186562
186647
  body: JSON.stringify({
186563
- model: input2.env.KYOSO_ANTHROPIC_JUDGE_MODEL ?? "claude-haiku-4-5",
186648
+ model: requestedModel,
186564
186649
  max_tokens: JUDGE_MAX_OUTPUT_TOKENS,
186565
186650
  temperature: 0,
186566
186651
  messages: [
@@ -186578,8 +186663,11 @@ async function runAnthropicJudge(input2, timeoutMs) {
186578
186663
  if (!content)
186579
186664
  throw new Error("Anthropic judge response did not include text content.");
186580
186665
  const usage = normalizeUsage(payload.usage);
186666
+ const reportedModel = typeof payload.model === "string" ? payload.model : undefined;
186581
186667
  return {
186582
186668
  output: parseJudgeOutput(content, input2.summaryText),
186669
+ requestedModel,
186670
+ ...reportedModel ? { reportedModel } : {},
186583
186671
  ...usage ? { usage } : {}
186584
186672
  };
186585
186673
  }
@@ -186616,10 +186704,15 @@ function runDeterministicJudge(result, summaryText) {
186616
186704
  }
186617
186705
 
186618
186706
  // src/judge/openai.ts
186707
+ var DEFAULT_OPENAI_JUDGE_MODEL = "gpt-5.4-mini";
186708
+ function resolveOpenAiJudgeModel(env) {
186709
+ return env.KYOSO_OPENAI_JUDGE_MODEL ?? DEFAULT_OPENAI_JUDGE_MODEL;
186710
+ }
186619
186711
  async function runOpenAiJudge(input2, timeoutMs) {
186620
186712
  const apiKey = input2.env.OPENAI_API_KEY ?? input2.env.CODEX_API_KEY;
186621
186713
  if (!apiKey)
186622
186714
  throw new Error("OPENAI_API_KEY is not configured.");
186715
+ const requestedModel = resolveOpenAiJudgeModel(input2.env);
186623
186716
  const response = await fetchWithTimeout2(`${input2.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1"}/chat/completions`, {
186624
186717
  method: "POST",
186625
186718
  headers: {
@@ -186627,7 +186720,7 @@ async function runOpenAiJudge(input2, timeoutMs) {
186627
186720
  "content-type": "application/json"
186628
186721
  },
186629
186722
  body: JSON.stringify({
186630
- model: input2.env.KYOSO_OPENAI_JUDGE_MODEL ?? "gpt-5.4-mini",
186723
+ model: requestedModel,
186631
186724
  response_format: { type: "json_object" },
186632
186725
  messages: [
186633
186726
  {
@@ -186646,8 +186739,11 @@ async function runOpenAiJudge(input2, timeoutMs) {
186646
186739
  if (!content)
186647
186740
  throw new Error("OpenAI judge response did not include content.");
186648
186741
  const usage = normalizeUsage2(payload.usage);
186742
+ const reportedModel = typeof payload.model === "string" ? payload.model : undefined;
186649
186743
  return {
186650
186744
  output: parseJudgeOutput(content, input2.summaryText),
186745
+ requestedModel,
186746
+ ...reportedModel ? { reportedModel } : {},
186651
186747
  ...usage ? { usage } : {}
186652
186748
  };
186653
186749
  }
@@ -186687,26 +186783,41 @@ function resolveJudgeProvider(provider, env) {
186687
186783
  return "anthropic";
186688
186784
  return "deterministic_fallback";
186689
186785
  }
186786
+ function resolveJudgeCallRoute(mode, provider, env) {
186787
+ const resolvedProvider = resolveJudgeProvider(provider, env);
186788
+ const credentialAvailable = resolvedProvider === "openai" && (hasEnv(env, "OPENAI_API_KEY") || hasEnv(env, "CODEX_API_KEY")) || resolvedProvider === "anthropic" && hasEnv(env, "ANTHROPIC_API_KEY");
186789
+ return {
186790
+ provider: resolvedProvider,
186791
+ llmAvailable: mode === "deterministic_plus_llm" && credentialAvailable
186792
+ };
186793
+ }
186690
186794
  async function runJudge(input2) {
186691
186795
  const fallback = runDeterministicJudge(input2.result, input2.summaryText);
186692
- if (input2.config.mode === "deterministic_only") {
186796
+ const configuredProvider = input2.requestedProvider ?? input2.config.provider;
186797
+ const route = resolveJudgeCallRoute(input2.config.mode, configuredProvider, input2.env);
186798
+ if (!route.llmAvailable) {
186693
186799
  return {
186694
186800
  provider: "deterministic_fallback",
186695
186801
  status: "deterministic_fallback",
186696
186802
  output: fallback
186697
186803
  };
186698
186804
  }
186699
- const configuredProvider = input2.requestedProvider ?? input2.config.provider;
186700
- const provider = resolveJudgeProvider(configuredProvider, input2.env);
186701
- if (provider === "deterministic_fallback") {
186702
- return { provider, status: "deterministic_fallback", output: fallback };
186703
- }
186805
+ const provider = route.provider;
186806
+ const requestExecutionIdentity = createModelExecutionIdentity({
186807
+ providerRoute: provider === "openai" ? "openai" : "anthropic",
186808
+ requestedModel: provider === "openai" ? resolveOpenAiJudgeModel(input2.env) : resolveAnthropicJudgeModel(input2.env)
186809
+ });
186704
186810
  try {
186705
186811
  const output2 = provider === "openai" ? await runOpenAiJudge(input2, input2.timeoutMs ?? input2.config.timeoutMs) : await runAnthropicJudge(input2, input2.timeoutMs ?? input2.config.timeoutMs);
186706
186812
  return {
186707
186813
  provider,
186708
186814
  status: "completed",
186709
186815
  output: output2.output,
186816
+ executionIdentity: createModelExecutionIdentity({
186817
+ providerRoute: requestExecutionIdentity.providerRoute,
186818
+ requestedModel: output2.requestedModel,
186819
+ reportedModel: output2.reportedModel
186820
+ }),
186710
186821
  ...output2.usage ? { usage: output2.usage } : {}
186711
186822
  };
186712
186823
  } catch (error51) {
@@ -186714,6 +186825,7 @@ async function runJudge(input2) {
186714
186825
  provider,
186715
186826
  status: "failed_fallback",
186716
186827
  output: fallback,
186828
+ executionIdentity: requestExecutionIdentity,
186717
186829
  error: error51 instanceof Error ? error51.message : String(error51)
186718
186830
  };
186719
186831
  }
@@ -186771,7 +186883,19 @@ class ChildEnvPreflightError extends Error {
186771
186883
  this.name = "ChildEnvPreflightError";
186772
186884
  }
186773
186885
  }
186774
- function buildChildEnv(parentEnv, whitelist, explicit, options = {}) {
186886
+ function buildChildLaunchContext(parentEnv, whitelist, explicit, options) {
186887
+ const env = buildChildEnvironment(parentEnv, whitelist, explicit, options);
186888
+ const openRouterSelected = options.agent === "codex" && options.provider === CODEX_OPENROUTER_PROVIDER;
186889
+ const requestedModel = options.agent === "claude" ? env.ANTHROPIC_MODEL : readCodexRequestedModel(env.CODEX_CONFIG);
186890
+ return {
186891
+ env,
186892
+ executionIdentity: createModelExecutionIdentity({
186893
+ providerRoute: openRouterSelected ? "openrouter" : options.agent === "codex" ? "codex_default" : "claude_default",
186894
+ requestedModel
186895
+ })
186896
+ };
186897
+ }
186898
+ function buildChildEnvironment(parentEnv, whitelist, explicit, options = {}) {
186775
186899
  if (!parentEnv.PATH) {
186776
186900
  throw new Error("PATH is required to launch ACP child agents.");
186777
186901
  }
@@ -186818,6 +186942,19 @@ function buildChildEnv(parentEnv, whitelist, explicit, options = {}) {
186818
186942
  }
186819
186943
  return env;
186820
186944
  }
186945
+ function readCodexRequestedModel(value) {
186946
+ if (!value)
186947
+ return;
186948
+ try {
186949
+ const parsed = JSON.parse(value);
186950
+ if (!isPlainObject2(parsed) || typeof parsed.model !== "string") {
186951
+ return;
186952
+ }
186953
+ return parsed.model;
186954
+ } catch {
186955
+ return;
186956
+ }
186957
+ }
186821
186958
  function canCopyEnvValue(key, value, onCredentialPlaceholderDiscarded) {
186822
186959
  if (!isUnexpandedCredentialEnvValue(key, value))
186823
186960
  return true;
@@ -186959,9 +187096,9 @@ var PLUGIN_RUNTIME_COMPATIBILITY_SCHEMA_VERSION = 1;
186959
187096
  var MINIMUM_SUPPORTED_CODEX_VERSION = "0.144.0-alpha.4";
186960
187097
  var PLUGIN_RUNTIME_EXPECTED_CONTRACT = {
186961
187098
  distribution: {
186962
- pluginVersion: "0.5.0",
187099
+ pluginVersion: "0.6.0",
186963
187100
  mcpCommand: "npx",
186964
- mcpPackagePin: "@kyo-so/cli@0.11.0"
187101
+ mcpPackagePin: "@kyo-so/cli@0.12.0"
186965
187102
  },
186966
187103
  marketplace: {
186967
187104
  name: "kyoso",
@@ -186995,7 +187132,7 @@ var PLUGIN_RUNTIME_EXPECTED_CONTRACT = {
186995
187132
  ],
186996
187133
  cwd: null,
186997
187134
  startupTimeoutSec: 20,
186998
- toolTimeoutSec: 360
187135
+ toolTimeoutSec: 2160
186999
187136
  },
187000
187137
  pluginOverride: {
187001
187138
  enabled: false,
@@ -187012,7 +187149,7 @@ var PLUGIN_RUNTIME_EXPECTED_CONTRACT = {
187012
187149
  ],
187013
187150
  cwd: null,
187014
187151
  startupTimeoutSec: 20,
187015
- toolTimeoutSec: 360
187152
+ toolTimeoutSec: 2160
187016
187153
  },
187017
187154
  manualOverride: {
187018
187155
  enabled: true,
@@ -187206,7 +187343,7 @@ function parseJson(value) {
187206
187343
  }
187207
187344
  }
187208
187345
  function parsePluginList(value) {
187209
- if (!isRecord6(value))
187346
+ if (!isRecord7(value))
187210
187347
  return;
187211
187348
  const allowedKeys = new Set(PLUGIN_LIST_JSON_SCHEMA.collections);
187212
187349
  if (Object.keys(value).some((key) => !allowedKeys.has(key))) {
@@ -187234,7 +187371,7 @@ function parsePluginEntries(value) {
187234
187371
  return;
187235
187372
  const entries = [];
187236
187373
  for (const item of value) {
187237
- if (!isRecord6(item))
187374
+ if (!isRecord7(item))
187238
187375
  return;
187239
187376
  if (typeof item.pluginId !== "string" || typeof item.installed !== "boolean" || typeof item.enabled !== "boolean") {
187240
187377
  return;
@@ -187260,7 +187397,7 @@ function parseMcpList(value) {
187260
187397
  return;
187261
187398
  const matches = [];
187262
187399
  for (const item of value) {
187263
- if (!isRecord6(item) || typeof item.name !== "string")
187400
+ if (!isRecord7(item) || typeof item.name !== "string")
187264
187401
  return "unknown";
187265
187402
  if (item.name !== "kyoso")
187266
187403
  continue;
@@ -187342,7 +187479,7 @@ function comparePrerelease(left, right) {
187342
187479
  }
187343
187480
  return 0;
187344
187481
  }
187345
- function isRecord6(value) {
187482
+ function isRecord7(value) {
187346
187483
  return typeof value === "object" && value !== null && !Array.isArray(value);
187347
187484
  }
187348
187485
 
@@ -187448,7 +187585,7 @@ function findKyosoPackage(executable) {
187448
187585
  if (existsSync(packagePath)) {
187449
187586
  try {
187450
187587
  const parsed = JSON.parse(readFileSync(packagePath, "utf8"));
187451
- if (isRecord7(parsed) && parsed.name === "@kyo-so/cli" && typeof parsed.version === "string" && parsed.version.trim().length > 0) {
187588
+ if (isRecord8(parsed) && parsed.name === "@kyo-so/cli" && typeof parsed.version === "string" && parsed.version.trim().length > 0) {
187452
187589
  return { directory, version: parsed.version };
187453
187590
  }
187454
187591
  } catch {}
@@ -187534,7 +187671,7 @@ function isWithin(path, parent) {
187534
187671
  const relativePath = relative(resolve5(parent), resolve5(path));
187535
187672
  return relativePath === "" || !relativePath.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) && relativePath !== ".." && !isAbsolute4(relativePath);
187536
187673
  }
187537
- function isRecord7(value) {
187674
+ function isRecord8(value) {
187538
187675
  return typeof value === "object" && value !== null && !Array.isArray(value);
187539
187676
  }
187540
187677
 
@@ -188165,7 +188302,7 @@ function buildCodexMcpToml(command, withOpenRouter = false) {
188165
188302
  `args = ${JSON.stringify(command.args)}`,
188166
188303
  `env_vars = [${envVars.map((value) => JSON.stringify(value)).join(", ")}]`,
188167
188304
  "startup_timeout_sec = 20",
188168
- "tool_timeout_sec = 360",
188305
+ "tool_timeout_sec = 2160",
188169
188306
  "enabled = true",
188170
188307
  ""
188171
188308
  ].join(`
@@ -188331,7 +188468,7 @@ async function ensureClaudeMcp(context) {
188331
188468
  const configPath = join6(context.cwd, ".mcp.json");
188332
188469
  const current = await readJsonObject(configPath);
188333
188470
  const mcpServers = recordValue(current.mcpServers);
188334
- if (isRecord8(mcpServers.kyoso)) {
188471
+ if (isRecord9(mcpServers.kyoso)) {
188335
188472
  return {
188336
188473
  kind: "mcp",
188337
188474
  registration: "preserved",
@@ -188571,7 +188708,7 @@ async function readJsonObject(path) {
188571
188708
  if (content.trim().length === 0)
188572
188709
  return {};
188573
188710
  const parsed = JSON.parse(content);
188574
- if (!isRecord8(parsed))
188711
+ if (!isRecord9(parsed))
188575
188712
  throw new Error(`${path} must contain a JSON object`);
188576
188713
  return parsed;
188577
188714
  }
@@ -188581,9 +188718,9 @@ function hasCodexMcpContent(content) {
188581
188718
  function codexMcpStatusFromContent(content) {
188582
188719
  try {
188583
188720
  const parsed = parse5(content);
188584
- if (!isRecord8(parsed))
188721
+ if (!isRecord9(parsed))
188585
188722
  return "unknown";
188586
- if (!isRecord8(parsed.mcp_servers))
188723
+ if (!isRecord9(parsed.mcp_servers))
188587
188724
  return "missing";
188588
188725
  if (!("kyoso" in parsed.mcp_servers))
188589
188726
  return "missing";
@@ -188612,11 +188749,11 @@ function detectCodexMcp(path, cwd, home) {
188612
188749
  if (hasUnprobedProjectIntegrationOverride(parsed, cwd, home)) {
188613
188750
  return { status: "unknown", paths: [path] };
188614
188751
  }
188615
- if (!isRecord8(parsed))
188752
+ if (!isRecord9(parsed))
188616
188753
  return { status: "unknown", paths: [path] };
188617
188754
  if (!("mcp_servers" in parsed))
188618
188755
  return { status: "missing", paths: [] };
188619
- if (!isRecord8(parsed.mcp_servers)) {
188756
+ if (!isRecord9(parsed.mcp_servers)) {
188620
188757
  return { status: "unknown", paths: [path] };
188621
188758
  }
188622
188759
  if (!("kyoso" in parsed.mcp_servers)) {
@@ -188641,18 +188778,18 @@ function detectClaudeMcp(path, cwd, home) {
188641
188778
  }
188642
188779
  }
188643
188780
  function jsonMcpStatuses(value, cwd, home) {
188644
- if (!isRecord8(value))
188781
+ if (!isRecord9(value))
188645
188782
  return ["unknown"];
188646
188783
  const statuses = directMcpStatuses(value);
188647
188784
  if (!("projects" in value))
188648
188785
  return statuses;
188649
- if (!isRecord8(value.projects))
188786
+ if (!isRecord9(value.projects))
188650
188787
  return [...statuses, "unknown"];
188651
188788
  const currentProject = normalizeProjectPath(cwd, home);
188652
188789
  for (const [projectPath, projectConfig] of Object.entries(value.projects)) {
188653
188790
  if (normalizeProjectPath(projectPath, home) !== currentProject)
188654
188791
  continue;
188655
- if (!isRecord8(projectConfig)) {
188792
+ if (!isRecord9(projectConfig)) {
188656
188793
  statuses.push("unknown");
188657
188794
  continue;
188658
188795
  }
@@ -188663,7 +188800,7 @@ function jsonMcpStatuses(value, cwd, home) {
188663
188800
  function directMcpStatuses(value) {
188664
188801
  const statuses = [];
188665
188802
  if ("mcpServers" in value) {
188666
- if (!isRecord8(value.mcpServers)) {
188803
+ if (!isRecord9(value.mcpServers)) {
188667
188804
  statuses.push("unknown");
188668
188805
  } else if ("kyoso" in value.mcpServers) {
188669
188806
  statuses.push(mcpEntryStatus(value.mcpServers.kyoso));
@@ -188674,7 +188811,7 @@ function directMcpStatuses(value) {
188674
188811
  function nestedMcpEntryStatus(value, path) {
188675
188812
  let current = value;
188676
188813
  for (const key of path) {
188677
- if (!isRecord8(current))
188814
+ if (!isRecord9(current))
188678
188815
  return "unknown";
188679
188816
  if (!(key in current))
188680
188817
  return "missing";
@@ -188683,11 +188820,11 @@ function nestedMcpEntryStatus(value, path) {
188683
188820
  return mcpEntryStatus(current);
188684
188821
  }
188685
188822
  function hasUnprobedProjectIntegrationOverride(value, cwd, home) {
188686
- if (!isRecord8(value) || !isRecord8(value.projects))
188823
+ if (!isRecord9(value) || !isRecord9(value.projects))
188687
188824
  return false;
188688
188825
  const currentProject = normalizeProjectPath(cwd, home);
188689
188826
  for (const [projectPath, projectConfig] of Object.entries(value.projects)) {
188690
- if (normalizeProjectPath(projectPath, home) !== currentProject || !isRecord8(projectConfig)) {
188827
+ if (normalizeProjectPath(projectPath, home) !== currentProject || !isRecord9(projectConfig)) {
188691
188828
  continue;
188692
188829
  }
188693
188830
  if ("mcp_servers" in projectConfig || "plugins" in projectConfig) {
@@ -188710,7 +188847,7 @@ function normalizeProjectPath(path, home) {
188710
188847
  return process.platform === "win32" ? normalized.toLowerCase() : normalized;
188711
188848
  }
188712
188849
  function mcpEntryStatus(value) {
188713
- if (!isRecord8(value))
188850
+ if (!isRecord9(value))
188714
188851
  return "unknown";
188715
188852
  if (!("enabled" in value))
188716
188853
  return "enabled";
@@ -188744,7 +188881,7 @@ function readTextSync(path) {
188744
188881
  function recordValue(value) {
188745
188882
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
188746
188883
  }
188747
- function isRecord8(value) {
188884
+ function isRecord9(value) {
188748
188885
  return typeof value === "object" && value !== null && !Array.isArray(value);
188749
188886
  }
188750
188887
  function diffForAppend(path, snippet) {
@@ -188934,7 +189071,16 @@ async function runDoctor(options) {
188934
189071
  const remaining = agentCommandExists.codex ? "codex" : "claude";
188935
189072
  lines.push(` single-agent mode: set agents.${missing}.enabled: false to use ${remaining} only; the remaining agent will cover both review roles.`);
188936
189073
  }
188937
- const judgeProvider = loaded.config.judge.mode === "deterministic_only" ? "deterministic_fallback" : resolveJudgeProvider(loaded.config.judge.provider, env);
189074
+ const judgeRoute = resolveJudgeCallRoute(loaded.config.judge.mode, loaded.config.judge.provider, env);
189075
+ const reviewTiming = calculateReviewTiming(loaded.config, judgeRoute.llmAvailable);
189076
+ lines.push("", "Review timing");
189077
+ lines.push(` review-wide deadline: ${loaded.config.reviewBudget.maxTotalWallTimeMs} ms`, ` sequential phases: primary ${reviewTiming.primaryPhaseMs} + verification ${reviewTiming.verificationPhaseMs} + LLM judge ${reviewTiming.judgePhaseMs} = ${reviewTiming.sequentialPhaseMs} ms`, ` recommended review-wide deadline: ${reviewTiming.recommendedReviewWallTimeMs} ms`);
189078
+ if (loaded.config.reviewBudget.maxTotalWallTimeMs < reviewTiming.sequentialPhaseMs) {
189079
+ lines.push(` warning: review-wide deadline is insufficient: ${loaded.config.reviewBudget.maxTotalWallTimeMs} ms is below the configured sequential phase time of ${reviewTiming.sequentialPhaseMs} ms; later phases cannot receive their configured timeout.`, ` hint: set user-global reviewBudget.maxTotalWallTimeMs to at least ${reviewTiming.recommendedReviewWallTimeMs}.`);
189080
+ } else if (loaded.config.reviewBudget.maxTotalWallTimeMs < reviewTiming.recommendedReviewWallTimeMs) {
189081
+ lines.push(` warning: review-wide deadline has low margin: ${loaded.config.reviewBudget.maxTotalWallTimeMs} ms is below the recommended ${reviewTiming.recommendedReviewWallTimeMs} ms; scheduling and finalization margin is reduced.`, ` hint: set user-global reviewBudget.maxTotalWallTimeMs to at least ${reviewTiming.recommendedReviewWallTimeMs}.`);
189082
+ }
189083
+ const judgeProvider = judgeRoute.llmAvailable ? judgeRoute.provider : "deterministic_fallback";
188938
189084
  lines.push("", "Judge");
188939
189085
  lines.push(` provider: ${judgeProvider}`);
188940
189086
  lines.push(judgeProvider === "deterministic_fallback" ? " billing: none (deterministic fallback)" : " billing: direct provider API calls (pay-per-token billing)");
@@ -188955,6 +189101,20 @@ async function runDoctor(options) {
188955
189101
  return lines.join(`
188956
189102
  `);
188957
189103
  }
189104
+ function calculateReviewTiming(config2, llmJudgeAvailable) {
189105
+ const primaryPhaseMs = Math.max(0, ...Object.values(config2.agents).filter((agent) => agent.enabled).map((agent) => agent.timeoutMs));
189106
+ const verificationPhaseMs = config2.verification.enabled ? config2.verification.timeoutMs : 0;
189107
+ const judgePhaseMs = llmJudgeAvailable ? config2.judge.timeoutMs : 0;
189108
+ const sequentialPhaseMs = primaryPhaseMs + verificationPhaseMs + judgePhaseMs;
189109
+ const recommendedReviewWallTimeMs = sequentialPhaseMs + Math.max(60000, Math.ceil(sequentialPhaseMs * 0.1));
189110
+ return {
189111
+ primaryPhaseMs,
189112
+ verificationPhaseMs,
189113
+ judgePhaseMs,
189114
+ sequentialPhaseMs,
189115
+ recommendedReviewWallTimeMs
189116
+ };
189117
+ }
188958
189118
  async function loadDoctorConfig(options) {
188959
189119
  try {
188960
189120
  return { loaded: await loadConfig(options) };
@@ -191755,14 +191915,14 @@ var zGuardCreateElicitationResponseCancel = object({
191755
191915
  });
191756
191916
  // node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js
191757
191917
  var CANCEL_REQUEST_METHOD = "$/cancel_request";
191758
- function isRecord9(value) {
191918
+ function isRecord10(value) {
191759
191919
  return typeof value === "object" && value !== null;
191760
191920
  }
191761
191921
  function isJsonRpcId(value) {
191762
191922
  return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
191763
191923
  }
191764
191924
  function cancelRequestId(params) {
191765
- if (!isRecord9(params) || !isJsonRpcId(params["requestId"])) {
191925
+ if (!isRecord10(params) || !isJsonRpcId(params["requestId"])) {
191766
191926
  return;
191767
191927
  }
191768
191928
  return params["requestId"];
@@ -192109,7 +192269,7 @@ class Connection {
192109
192269
  if (this.abortController.signal.aborted) {
192110
192270
  return;
192111
192271
  }
192112
- if (!isRecord9(message)) {
192272
+ if (!isRecord10(message)) {
192113
192273
  console.error("Invalid message", { message });
192114
192274
  return;
192115
192275
  }
@@ -192202,7 +192362,7 @@ class Connection {
192202
192362
  pendingResponse.cleanup?.();
192203
192363
  if ("result" in response) {
192204
192364
  pendingResponse.resolve(response.result);
192205
- } else if ("error" in response && isRecord9(response.error)) {
192365
+ } else if ("error" in response && isRecord10(response.error)) {
192206
192366
  const { code, message, data } = response.error;
192207
192367
  pendingResponse.reject(new RequestError(code, message, data));
192208
192368
  } else {
@@ -192412,7 +192572,7 @@ function ndJsonStream(output2, input2) {
192412
192572
  if (trimmedLine) {
192413
192573
  try {
192414
192574
  const message = JSON.parse(trimmedLine);
192415
- if (isRecord9(message)) {
192575
+ if (isRecord10(message)) {
192416
192576
  controller.enqueue(message);
192417
192577
  } else {
192418
192578
  console.warn("Skipping JSON line that is not an object:", trimmedLine);
@@ -193301,6 +193461,45 @@ class BaseAcpAgentManager {
193301
193461
  }
193302
193462
  }
193303
193463
 
193464
+ // src/acp/ndJsonLineLimit.ts
193465
+ var JSON_STRING_MAX_ESCAPE_EXPANSION = 6;
193466
+ var ACP_NDJSON_ENVELOPE_BYTES = 2 * 1048576;
193467
+ var NEWLINE_BYTE = 10;
193468
+ var MAX_ACP_NDJSON_LINE_BYTES = MAX_AGENT_OUTPUT_BYTES * JSON_STRING_MAX_ESCAPE_EXPANSION + ACP_NDJSON_ENVELOPE_BYTES;
193469
+
193470
+ class AcpNdJsonLineLimitError extends Error {
193471
+ maxLineBytes;
193472
+ constructor(maxLineBytes) {
193473
+ super(`ACP NDJSON line exceeded ${maxLineBytes} bytes.`);
193474
+ this.maxLineBytes = maxLineBytes;
193475
+ this.name = "AcpNdJsonLineLimitError";
193476
+ }
193477
+ }
193478
+ function limitAcpNdJsonLineBytes(input2, maxLineBytes = MAX_ACP_NDJSON_LINE_BYTES) {
193479
+ if (!Number.isSafeInteger(maxLineBytes) || maxLineBytes <= 0) {
193480
+ throw new RangeError("ACP NDJSON line limit must be a positive integer.");
193481
+ }
193482
+ let pendingLineBytes = 0;
193483
+ return input2.pipeThrough(new TransformStream({
193484
+ transform(chunk, controller) {
193485
+ let start = 0;
193486
+ for (;; ) {
193487
+ const newlineIndex = chunk.indexOf(NEWLINE_BYTE, start);
193488
+ const end = newlineIndex === -1 ? chunk.byteLength : newlineIndex;
193489
+ pendingLineBytes += end - start;
193490
+ if (pendingLineBytes > maxLineBytes) {
193491
+ throw new AcpNdJsonLineLimitError(maxLineBytes);
193492
+ }
193493
+ if (newlineIndex === -1)
193494
+ break;
193495
+ pendingLineBytes = 0;
193496
+ start = newlineIndex + 1;
193497
+ }
193498
+ controller.enqueue(chunk);
193499
+ }
193500
+ }));
193501
+ }
193502
+
193304
193503
  // src/core/findingAdmission.ts
193305
193504
  import { createHash as createHash4 } from "node:crypto";
193306
193505
  var SAFETY_CATEGORIES = new Set([
@@ -193642,6 +193841,43 @@ var evidenceQualities = [
193642
193841
  ];
193643
193842
  var MAX_EVIDENCE_REFS2 = 20;
193644
193843
  var MAX_EVIDENCE_LINE2 = 1e6;
193844
+ var STRICT_ROOT_KEYS = new Set([
193845
+ "summary",
193846
+ "findings",
193847
+ "testsToAdd",
193848
+ "residualRisks",
193849
+ "openQuestions",
193850
+ "cisaSecureByDesign"
193851
+ ]);
193852
+ var STRICT_FINDING_KEYS = new Set([
193853
+ "severity",
193854
+ "category",
193855
+ "title",
193856
+ "evidence",
193857
+ "recommendation",
193858
+ "disposition",
193859
+ "changeRelation",
193860
+ "evidenceQuality",
193861
+ "evidenceRefs",
193862
+ "files",
193863
+ "confidence",
193864
+ "cisaMapping"
193865
+ ]);
193866
+ var STRICT_FILE_KEYS = new Set(["path", "lineStart", "lineEnd"]);
193867
+ var STRICT_EVIDENCE_REF_KEYS = new Set([
193868
+ "kind",
193869
+ "path",
193870
+ "lineStart",
193871
+ "lineEnd",
193872
+ "label"
193873
+ ]);
193874
+ var STRICT_CISA_KEYS = new Set([
193875
+ "customerSecurityOutcomes",
193876
+ "secureByDefault",
193877
+ "transparencyAndAccountability",
193878
+ "governance",
193879
+ "notes"
193880
+ ]);
193645
193881
  function normalizeAgentOutput(agent, role, rawText) {
193646
193882
  const json2 = extractFirstJsonObject2(rawText);
193647
193883
  if (!json2)
@@ -193675,6 +193911,19 @@ function normalizeAgentOutput(agent, role, rawText) {
193675
193911
  return parseFailureOpinion(agent, role, `Structured parse failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
193676
193912
  }
193677
193913
  }
193914
+ function parseAgentOutputStrict(agent, role, rawText) {
193915
+ const json2 = extractFirstJsonObject2(rawText);
193916
+ if (!json2)
193917
+ return;
193918
+ try {
193919
+ const parsed = JSON.parse(json2);
193920
+ if (!isStrictAgentOpinion(parsed))
193921
+ return;
193922
+ return normalizeAgentOutput(agent, role, json2);
193923
+ } catch {
193924
+ return;
193925
+ }
193926
+ }
193678
193927
  function extractFirstJsonObject2(text) {
193679
193928
  const start = text.indexOf("{");
193680
193929
  if (start === -1)
@@ -193732,7 +193981,7 @@ function isSeverity(value) {
193732
193981
  return typeof value === "string" && severities.includes(value);
193733
193982
  }
193734
193983
  function normalizeCisaSecureByDesign(value) {
193735
- if (!isRecord10(value))
193984
+ if (!isRecord11(value))
193736
193985
  return;
193737
193986
  const normalized = {};
193738
193987
  const customerSecurityOutcomes = normalizeGateStatus(value.customerSecurityOutcomes);
@@ -193772,6 +194021,78 @@ function isChangeRelation(value) {
193772
194021
  function isEvidenceQuality(value) {
193773
194022
  return typeof value === "string" && evidenceQualities.includes(value);
193774
194023
  }
194024
+ function isStrictAgentOpinion(value) {
194025
+ if (!isRecord11(value) || !hasOnlyKeys(value, STRICT_ROOT_KEYS))
194026
+ return false;
194027
+ if (typeof value.summary !== "string")
194028
+ return false;
194029
+ if (!Array.isArray(value.findings) || !value.findings.every(isStrictFinding) || !isStringArray(value.testsToAdd) || !isStringArray(value.residualRisks) || !isStringArray(value.openQuestions)) {
194030
+ return false;
194031
+ }
194032
+ return value.cisaSecureByDesign === undefined || isStrictCisaSecureByDesign(value.cisaSecureByDesign);
194033
+ }
194034
+ function isStrictFinding(value) {
194035
+ if (!isRecord11(value) || !hasOnlyKeys(value, STRICT_FINDING_KEYS)) {
194036
+ return false;
194037
+ }
194038
+ if (!isSeverity(value.severity) || !isCategory(value.category) || !isNonEmptyString(value.title) || !isNonEmptyString(value.evidence) || !isNonEmptyString(value.recommendation) || !isConfidence(value.confidence)) {
194039
+ return false;
194040
+ }
194041
+ if (value.disposition !== undefined && !isDisposition(value.disposition) || value.changeRelation !== undefined && !isChangeRelation(value.changeRelation) || value.evidenceQuality !== undefined && !isEvidenceQuality(value.evidenceQuality) || value.files !== undefined && !isStrictFindingFiles(value.files) || value.evidenceRefs !== undefined && !isStrictEvidenceRefs(value.evidenceRefs) || value.cisaMapping !== undefined && !isStringArray(value.cisaMapping)) {
194042
+ return false;
194043
+ }
194044
+ return true;
194045
+ }
194046
+ function isStrictFindingFiles(value) {
194047
+ return Array.isArray(value) && value.every((item) => isRecord11(item) && hasOnlyKeys(item, STRICT_FILE_KEYS) && isNonEmptyString(item.path) && isOptionalLineNumber(item.lineStart) && isOptionalLineNumber(item.lineEnd));
194048
+ }
194049
+ function isStrictEvidenceRefs(value) {
194050
+ return Array.isArray(value) && value.length <= MAX_EVIDENCE_REFS2 && value.every((item) => {
194051
+ if (!isRecord11(item) || !hasOnlyKeys(item, STRICT_EVIDENCE_REF_KEYS)) {
194052
+ return false;
194053
+ }
194054
+ if (item.kind !== "file" && item.kind !== "diff_hunk" && item.kind !== "plan_clause") {
194055
+ return false;
194056
+ }
194057
+ if (!isOptionalNonEmptyString(item.path) || !isOptionalNonEmptyString(item.label) || !isOptionalLineNumber(item.lineStart) || !isOptionalLineNumber(item.lineEnd)) {
194058
+ return false;
194059
+ }
194060
+ if (item.kind === "file" || item.kind === "diff_hunk") {
194061
+ return item.path !== undefined && item.lineStart !== undefined;
194062
+ }
194063
+ return item.label !== undefined || item.lineStart !== undefined;
194064
+ });
194065
+ }
194066
+ function isStrictCisaSecureByDesign(value) {
194067
+ if (!isRecord11(value) || !hasOnlyKeys(value, STRICT_CISA_KEYS))
194068
+ return false;
194069
+ for (const key of [
194070
+ "customerSecurityOutcomes",
194071
+ "secureByDefault",
194072
+ "transparencyAndAccountability",
194073
+ "governance"
194074
+ ]) {
194075
+ if (value[key] !== undefined && !normalizeGateStatus(value[key])) {
194076
+ return false;
194077
+ }
194078
+ }
194079
+ return value.notes === undefined || isStringArray(value.notes);
194080
+ }
194081
+ function hasOnlyKeys(value, allowed) {
194082
+ return Object.keys(value).every((key) => allowed.has(key));
194083
+ }
194084
+ function isStringArray(value) {
194085
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
194086
+ }
194087
+ function isNonEmptyString(value) {
194088
+ return typeof value === "string" && value.trim().length > 0;
194089
+ }
194090
+ function isOptionalNonEmptyString(value) {
194091
+ return value === undefined || isNonEmptyString(value);
194092
+ }
194093
+ function isOptionalLineNumber(value) {
194094
+ return value === undefined || normalizeLineNumber(value) !== undefined;
194095
+ }
193775
194096
  function normalizeStringList(value) {
193776
194097
  return Array.isArray(value) ? value.filter((item) => typeof item === "string").map((item) => sanitizeText(item)) : [];
193777
194098
  }
@@ -193782,7 +194103,7 @@ function normalizeFindingFiles(value) {
193782
194103
  if (!Array.isArray(value))
193783
194104
  return;
193784
194105
  const files = value.flatMap((item) => {
193785
- if (!isRecord10(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
194106
+ if (!isRecord11(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
193786
194107
  return [];
193787
194108
  }
193788
194109
  const file2 = {
@@ -193802,7 +194123,7 @@ function normalizeEvidenceRefs2(value) {
193802
194123
  if (!Array.isArray(value))
193803
194124
  return;
193804
194125
  const references = value.slice(0, MAX_EVIDENCE_REFS2).flatMap((item) => {
193805
- if (!isRecord10(item) || item.kind !== "file" && item.kind !== "diff_hunk" && item.kind !== "plan_clause") {
194126
+ if (!isRecord11(item) || item.kind !== "file" && item.kind !== "diff_hunk" && item.kind !== "plan_clause") {
193806
194127
  return [];
193807
194128
  }
193808
194129
  const reference = { kind: item.kind };
@@ -193825,7 +194146,7 @@ function normalizeEvidenceRefs2(value) {
193825
194146
  function normalizeLineNumber(value) {
193826
194147
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= MAX_EVIDENCE_LINE2 ? value : undefined;
193827
194148
  }
193828
- function isRecord10(value) {
194149
+ function isRecord11(value) {
193829
194150
  return typeof value === "object" && value !== null && !Array.isArray(value);
193830
194151
  }
193831
194152
 
@@ -193851,9 +194172,9 @@ class SubprocessAcpAgentManager extends BaseAcpAgentManager {
193851
194172
  };
193852
194173
  }
193853
194174
  const provider = input2.agent === "codex" ? this.config.agents.codex.provider : undefined;
193854
- let env;
194175
+ let launchContext;
193855
194176
  try {
193856
- env = buildChildEnv(this.parentEnv, agentConfig.auth.envWhitelist, agentConfig.env, {
194177
+ launchContext = buildChildLaunchContext(this.parentEnv, agentConfig.auth.envWhitelist, agentConfig.env, {
193857
194178
  agent: input2.agent,
193858
194179
  model: agentConfig.model,
193859
194180
  provider,
@@ -193870,7 +194191,7 @@ class SubprocessAcpAgentManager extends BaseAcpAgentManager {
193870
194191
  };
193871
194192
  }
193872
194193
  try {
193873
- return await runSubprocessAgent(input2.agent, agentConfig, input2, env);
194194
+ return await runSubprocessAgent(input2.agent, agentConfig, input2, launchContext.env, launchContext.executionIdentity);
193874
194195
  } catch (error51) {
193875
194196
  return {
193876
194197
  agent: input2.agent,
@@ -193883,7 +194204,7 @@ class SubprocessAcpAgentManager extends BaseAcpAgentManager {
193883
194204
  }
193884
194205
  }
193885
194206
  }
193886
- async function runSubprocessAgent(agent, agentConfig, input2, env) {
194207
+ async function runSubprocessAgent(agent, agentConfig, input2, env, launchExecutionIdentity) {
193887
194208
  const startedAt = new Date().toISOString();
193888
194209
  const effectiveTimeoutMs = resolveEffectiveTimeoutMs(input2);
193889
194210
  if (effectiveTimeoutMs <= 0) {
@@ -193908,11 +194229,15 @@ async function runSubprocessAgent(agent, agentConfig, input2, env) {
193908
194229
  let stdout = "";
193909
194230
  let stderr3 = "";
193910
194231
  let settled = false;
194232
+ let spawned = false;
193911
194233
  let startedWrite;
193912
194234
  child.once("spawn", () => {
193913
194235
  if (settled)
193914
194236
  return;
193915
- startedWrite = Promise.resolve().then(() => input2.onStarted?.()).catch(() => {
194237
+ spawned = true;
194238
+ startedWrite = Promise.resolve().then(async () => {
194239
+ await input2.onStarted?.(launchExecutionIdentity);
194240
+ }).catch(() => {
193916
194241
  return;
193917
194242
  });
193918
194243
  });
@@ -193922,7 +194247,8 @@ async function runSubprocessAgent(agent, agentConfig, input2, env) {
193922
194247
  return;
193923
194248
  settled = true;
193924
194249
  clearTimeout(timeout);
193925
- (startedWrite ?? Promise.resolve()).then(() => resolveResult(result));
194250
+ const finalResult = spawned && result.executionIdentity === undefined ? { ...result, executionIdentity: launchExecutionIdentity } : result;
194251
+ (startedWrite ?? Promise.resolve()).then(() => resolveResult(finalResult));
193926
194252
  };
193927
194253
  const timeout = setTimeout(() => {
193928
194254
  abortController.abort(new Error("Kyoso agent timeout"));
@@ -193955,7 +194281,17 @@ async function runSubprocessAgent(agent, agentConfig, input2, env) {
193955
194281
  error: failure
193956
194282
  });
193957
194283
  });
193958
- runAcpClientWorkflow(child, input2, abortController, resolveEffortConfigOption(agent, agentConfig.effort)).then(({ rawText, warnings, usage, outputBytes, stopReason }) => {
194284
+ runAcpClientWorkflow(child, input2, abortController, resolveEffortConfigOption(agent, agentConfig.effort), launchExecutionIdentity).then(({
194285
+ rawText,
194286
+ warnings,
194287
+ usage,
194288
+ messageBytes,
194289
+ thoughtBytes,
194290
+ outputBytes,
194291
+ outputWarningTriggered,
194292
+ stopReason,
194293
+ executionIdentity
194294
+ }) => {
193959
194295
  stdout = rawText;
193960
194296
  const completed = stopReason === "end_turn";
193961
194297
  resolveOnce({
@@ -193966,8 +194302,12 @@ async function runSubprocessAgent(agent, agentConfig, input2, env) {
193966
194302
  normalized: normalizeAgentOutput(agent, input2.role, rawText),
193967
194303
  startedAt,
193968
194304
  completedAt: new Date().toISOString(),
194305
+ messageBytes,
194306
+ thoughtBytes,
193969
194307
  outputBytes,
194308
+ outputWarningTriggered,
193970
194309
  stopReason,
194310
+ executionIdentity,
193971
194311
  ...usage ? { usage } : {},
193972
194312
  ...warnings.length > 0 ? { warnings } : {},
193973
194313
  ...completed ? {} : {
@@ -193981,18 +194321,40 @@ async function runSubprocessAgent(agent, agentConfig, input2, env) {
193981
194321
  const outputLimitError = findOutputLimitError(error51, abortController);
193982
194322
  if (outputLimitError) {
193983
194323
  stdout = outputLimitError.rawText;
194324
+ const normalized = parseAgentOutputStrict(agent, input2.role, stdout);
193984
194325
  resolveOnce({
193985
194326
  agent,
193986
194327
  role: input2.role,
193987
194328
  status: "failed",
193988
194329
  rawText: stdout,
194330
+ ...normalized ? { normalized, salvaged: true } : {},
194331
+ messageBytes: outputLimitError.messageBytes,
194332
+ thoughtBytes: outputLimitError.thoughtBytes,
193989
194333
  outputBytes: outputLimitError.outputBytes,
194334
+ outputWarningTriggered: outputLimitError.outputWarningTriggered,
193990
194335
  stopReason: "cancelled",
193991
194336
  startedAt,
193992
194337
  completedAt: new Date().toISOString(),
193993
194338
  error: {
193994
194339
  code: "AGENT_OUTPUT_LIMIT",
193995
- message: `Agent output exceeded ${outputLimitError.maxOutputBytes} bytes and was cancelled.`
194340
+ message: `Agent output exceeded the ${outputLimitError.maxOutputBytes}-byte hard limit (message: ${outputLimitError.messageBytes}, thought: ${outputLimitError.thoughtBytes}, total: ${outputLimitError.outputBytes}) and was cancelled. Adjust user-global reviewBudget.maxAgentOutputBytes to change this ceiling.`
194341
+ }
194342
+ });
194343
+ return;
194344
+ }
194345
+ if (error51 instanceof AcpNdJsonLineLimitError) {
194346
+ abortController.abort(error51);
194347
+ resolveOnce({
194348
+ agent,
194349
+ role: input2.role,
194350
+ status: "failed",
194351
+ rawText: stdout,
194352
+ stopReason: "cancelled",
194353
+ startedAt,
194354
+ completedAt: new Date().toISOString(),
194355
+ error: {
194356
+ code: "AGENT_PROTOCOL_LIMIT",
194357
+ message: `Agent emitted an ACP NDJSON line above the ${error51.maxLineBytes}-byte transport limit and was cancelled.`
193996
194358
  }
193997
194359
  });
193998
194360
  return;
@@ -194029,13 +194391,13 @@ async function runSubprocessAgent(agent, agentConfig, input2, env) {
194029
194391
  });
194030
194392
  });
194031
194393
  }
194032
- async function runAcpClientWorkflow(child, input2, abortController, configOption) {
194394
+ async function runAcpClientWorkflow(child, input2, abortController, configOption, launchExecutionIdentity) {
194033
194395
  if (!child.stdin || !child.stdout) {
194034
194396
  throw new Error("Agent process did not expose stdio streams.");
194035
194397
  }
194036
194398
  const output2 = Writable.toWeb(child.stdin);
194037
194399
  const inputStream = Readable.toWeb(child.stdout);
194038
- const stream2 = ndJsonStream(output2, inputStream);
194400
+ const stream2 = ndJsonStream(output2, limitAcpNdJsonLineBytes(inputStream));
194039
194401
  const app = client({ name: "kyoso" }).onRequest(methods.client.session.requestPermission, () => ({
194040
194402
  outcome: { outcome: "cancelled" }
194041
194403
  })).onRequest(methods.client.fs.readTextFile, async (ctx) => ({
@@ -194096,7 +194458,10 @@ async function runAcpClientWorkflow(child, input2, abortController, configOption
194096
194458
  return;
194097
194459
  });
194098
194460
  let rawText = "";
194461
+ let messageBytes = 0;
194462
+ let thoughtBytes = 0;
194099
194463
  let outputBytes = 0;
194464
+ let outputWarningTriggered = false;
194100
194465
  for (;; ) {
194101
194466
  const message = await session.nextUpdate();
194102
194467
  if (message.kind === "stop") {
@@ -194105,8 +194470,12 @@ async function runAcpClientWorkflow(child, input2, abortController, configOption
194105
194470
  rawText,
194106
194471
  warnings,
194107
194472
  ...usage ? { usage } : {},
194473
+ messageBytes,
194474
+ thoughtBytes,
194108
194475
  outputBytes,
194109
- stopReason: message.stopReason
194476
+ outputWarningTriggered,
194477
+ stopReason: message.stopReason,
194478
+ executionIdentity: withReportedExecutionIdentity(launchExecutionIdentity, message.response._meta)
194110
194479
  };
194111
194480
  }
194112
194481
  const update = message.update;
@@ -194114,35 +194483,60 @@ async function runAcpClientWorkflow(child, input2, abortController, configOption
194114
194483
  continue;
194115
194484
  }
194116
194485
  const chunkBytes = Buffer.byteLength(update.content.text, "utf8");
194117
- const nextOutputBytes = outputBytes + chunkBytes;
194486
+ const isMessage = update.sessionUpdate === "agent_message_chunk";
194487
+ const nextMessageBytes = messageBytes + (isMessage ? chunkBytes : 0);
194488
+ const nextThoughtBytes = thoughtBytes + (isMessage ? 0 : chunkBytes);
194489
+ const nextOutputBytes = nextMessageBytes + nextThoughtBytes;
194490
+ const nextOutputWarningTriggered = outputWarningTriggered || input2.warnOutputBytes !== undefined && nextOutputBytes >= input2.warnOutputBytes;
194118
194491
  if (input2.maxOutputBytes !== undefined && nextOutputBytes > input2.maxOutputBytes) {
194492
+ const retainedRawText = isMessage ? `${rawText}${utf8Prefix(update.content.text, input2.maxOutputBytes - outputBytes)}` : rawText;
194119
194493
  await ctx.notify(methods.agent.session.cancel, {
194120
194494
  sessionId: session.sessionId
194121
194495
  }).catch(() => {
194122
194496
  return;
194123
194497
  });
194124
- const error51 = new AgentOutputLimitError(rawText, nextOutputBytes, input2.maxOutputBytes);
194498
+ const error51 = new AgentOutputLimitError(retainedRawText, nextMessageBytes, nextThoughtBytes, nextOutputBytes, input2.maxOutputBytes, nextOutputWarningTriggered);
194125
194499
  abortController.abort(error51);
194126
194500
  throw error51;
194127
194501
  }
194128
- if (update.sessionUpdate === "agent_message_chunk") {
194502
+ if (isMessage) {
194129
194503
  rawText += update.content.text;
194130
194504
  }
194505
+ messageBytes = nextMessageBytes;
194506
+ thoughtBytes = nextThoughtBytes;
194131
194507
  outputBytes = nextOutputBytes;
194508
+ outputWarningTriggered = nextOutputWarningTriggered;
194132
194509
  }
194133
194510
  });
194134
194511
  });
194135
194512
  }
194513
+ function utf8Prefix(input2, maxBytes) {
194514
+ const encoded = new TextEncoder().encode(input2);
194515
+ const budget = Math.max(0, Math.min(maxBytes, encoded.byteLength));
194516
+ const decoder = new TextDecoder("utf-8", { fatal: true });
194517
+ for (let end = budget;end > 0; end -= 1) {
194518
+ try {
194519
+ return decoder.decode(encoded.subarray(0, end));
194520
+ } catch {}
194521
+ }
194522
+ return "";
194523
+ }
194136
194524
 
194137
194525
  class AgentOutputLimitError extends Error {
194138
194526
  rawText;
194527
+ messageBytes;
194528
+ thoughtBytes;
194139
194529
  outputBytes;
194140
194530
  maxOutputBytes;
194141
- constructor(rawText, outputBytes, maxOutputBytes) {
194531
+ outputWarningTriggered;
194532
+ constructor(rawText, messageBytes, thoughtBytes, outputBytes, maxOutputBytes, outputWarningTriggered) {
194142
194533
  super(`Agent output exceeded ${maxOutputBytes} bytes.`);
194143
194534
  this.rawText = rawText;
194535
+ this.messageBytes = messageBytes;
194536
+ this.thoughtBytes = thoughtBytes;
194144
194537
  this.outputBytes = outputBytes;
194145
194538
  this.maxOutputBytes = maxOutputBytes;
194539
+ this.outputWarningTriggered = outputWarningTriggered;
194146
194540
  this.name = "AgentOutputLimitError";
194147
194541
  }
194148
194542
  }
@@ -194159,6 +194553,18 @@ function resolveEffectiveTimeoutMs(input2) {
194159
194553
  function normalizeUsage3(usage) {
194160
194554
  return normalizeModelTokenUsage(usage);
194161
194555
  }
194556
+ function withReportedExecutionIdentity(identity, metadata) {
194557
+ const record2 = isRecord12(metadata) ? metadata : {};
194558
+ return createModelExecutionIdentity({
194559
+ providerRoute: identity.providerRoute,
194560
+ requestedModel: identity.requestedModel,
194561
+ reportedProvider: record2.provider,
194562
+ reportedModel: record2.model
194563
+ });
194564
+ }
194565
+ function isRecord12(value) {
194566
+ return value !== null && typeof value === "object" && !Array.isArray(value);
194567
+ }
194162
194568
  function resolveEffortConfigOption(agent, effort) {
194163
194569
  if (!effort)
194164
194570
  return;
@@ -208028,7 +208434,7 @@ function clearInheritedOpenRouterModelForProviderReset(baseConfig, overridden, o
208028
208434
  return;
208029
208435
  }
208030
208436
  const codex = readPath2(overridden, ["agents", "codex"]);
208031
- if (isRecord11(codex))
208437
+ if (isRecord13(codex))
208032
208438
  delete codex.model;
208033
208439
  }
208034
208440
  function findAssignmentForPath(overrides, path) {
@@ -208073,7 +208479,7 @@ function parseConfigOverrideValue(value, currentValue) {
208073
208479
  function readPath2(target, path) {
208074
208480
  let current = target;
208075
208481
  for (const key of path) {
208076
- if (!isRecord11(current))
208482
+ if (!isRecord13(current))
208077
208483
  return;
208078
208484
  current = current[key];
208079
208485
  }
@@ -208083,7 +208489,7 @@ function writePath2(target, path, value) {
208083
208489
  let current = target;
208084
208490
  for (const key of path.slice(0, -1)) {
208085
208491
  const child = current[key];
208086
- if (!isRecord11(child)) {
208492
+ if (!isRecord13(child)) {
208087
208493
  throw new Error(`Cannot apply --set key ${JSON.stringify(path.join("."))}.`);
208088
208494
  }
208089
208495
  current = child;
@@ -208092,7 +208498,7 @@ function writePath2(target, path, value) {
208092
208498
  if (leaf)
208093
208499
  current[leaf] = value;
208094
208500
  }
208095
- function isRecord11(value) {
208501
+ function isRecord13(value) {
208096
208502
  return typeof value === "object" && value !== null && !Array.isArray(value);
208097
208503
  }
208098
208504
 
@@ -208261,6 +208667,9 @@ function buildAgentPrompt(tool, request, agent, role, policy = {}) {
208261
208667
  "A formal finding requires a concrete file/line, diff hunk, or plan clause; an actual failure or exploit path; a change relation; and an executable recommendation.",
208262
208668
  "Put insufficiently supported hypotheses in openQuestions instead of findings.",
208263
208669
  "Do not create formal findings for style, formatting, generic hardening, unrelated pre-existing issues, duplicate tests, implementation-detail tests, or exhaustive boundary matrices.",
208670
+ ...policy.maxFindingsTarget === undefined ? [] : [
208671
+ `Avoid duplicates and aim for at most ${policy.maxFindingsTarget} findings in severity order, but do not hide a material finding solely to meet this target.`
208672
+ ],
208264
208673
  "Recommend only specific regression scenarios tied to changed behavior, with at most three testsToAdd entries.",
208265
208674
  "Critical and High safety issues must still be reported when they match a non-goal.",
208266
208675
  "Write each finding title in concise English, regardless of the language used elsewhere. Titles are compared across agents for deduplication.",
@@ -208885,6 +209294,12 @@ function sanitizeForAudit(value, options = {}) {
208885
209294
  if (typeof value === "object" && value !== null) {
208886
209295
  const result = {};
208887
209296
  for (const [key, nested] of Object.entries(value)) {
209297
+ if (key === "executionIdentity") {
209298
+ const identity = normalizeModelExecutionIdentity(nested);
209299
+ if (identity)
209300
+ result[key] = identity;
209301
+ continue;
209302
+ }
208888
209303
  if (/raw|content|env|credential|token|secret|password/i.test(key) && !(options.includeRawAgentOutput && key === "rawText") && !isUsageMetadata(key, nested)) {
208889
209304
  continue;
208890
209305
  }
@@ -209165,7 +209580,7 @@ function validateReviewContract(request) {
209165
209580
  const contract = request.reviewContract;
209166
209581
  if (contract === undefined)
209167
209582
  return;
209168
- if (!isRecord12(contract)) {
209583
+ if (!isRecord14(contract)) {
209169
209584
  throw new KyosoRequestError("reviewContract must be an object", "VALIDATION_ERROR");
209170
209585
  }
209171
209586
  const allowedKeys = new Set(["focus", "nonGoals", "acceptedRisks"]);
@@ -209182,7 +209597,7 @@ function validateReviewContract(request) {
209182
209597
  throw new KyosoRequestError("reviewContract.nonGoals must contain at most 20 non-empty strings of 500 characters or fewer", "VALIDATION_ERROR");
209183
209598
  }
209184
209599
  const acceptedRisks = contract.acceptedRisks;
209185
- if (acceptedRisks !== undefined && (!Array.isArray(acceptedRisks) || acceptedRisks.length > 20 || acceptedRisks.some((risk) => !isRecord12(risk) || typeof risk.findingFingerprint !== "string" || !/^sha256:[0-9a-f]{64}$/.test(risk.findingFingerprint) || typeof risk.rationale !== "string" || risk.rationale.trim().length === 0 || risk.rationale.length > 500))) {
209600
+ if (acceptedRisks !== undefined && (!Array.isArray(acceptedRisks) || acceptedRisks.length > 20 || acceptedRisks.some((risk) => !isRecord14(risk) || typeof risk.findingFingerprint !== "string" || !/^sha256:[0-9a-f]{64}$/.test(risk.findingFingerprint) || typeof risk.rationale !== "string" || risk.rationale.trim().length === 0 || risk.rationale.length > 500))) {
209186
209601
  throw new KyosoRequestError("reviewContract.acceptedRisks must contain valid finding fingerprints and bounded rationales", "VALIDATION_ERROR");
209187
209602
  }
209188
209603
  }
@@ -209194,13 +209609,13 @@ function validateSelectedFiles(request) {
209194
209609
  throw new KyosoRequestError("selectedFiles must be an array", "VALIDATION_ERROR");
209195
209610
  }
209196
209611
  for (const file2 of selectedFiles) {
209197
- if (!isRecord12(file2) || typeof file2.path !== "string" || file2.path.trim().length === 0 || typeof file2.content !== "string" || file2.language !== undefined && typeof file2.language !== "string" || file2.truncated !== undefined && typeof file2.truncated !== "boolean") {
209612
+ if (!isRecord14(file2) || typeof file2.path !== "string" || file2.path.trim().length === 0 || typeof file2.content !== "string" || file2.language !== undefined && typeof file2.language !== "string" || file2.truncated !== undefined && typeof file2.truncated !== "boolean") {
209198
209613
  throw new KyosoRequestError("selectedFiles entries require string path/content and valid optional metadata", "VALIDATION_ERROR");
209199
209614
  }
209200
209615
  normalizeRelativePath(file2.path);
209201
209616
  }
209202
209617
  }
209203
- function isRecord12(value) {
209618
+ function isRecord14(value) {
209204
209619
  return typeof value === "object" && value !== null && !Array.isArray(value);
209205
209620
  }
209206
209621
 
@@ -209213,7 +209628,7 @@ function renderMarkdownResult(tool, result, options = {}) {
209213
209628
  `**Mode:** ${tool}`,
209214
209629
  `**Completion:** ${formatCompletion(result)}`,
209215
209630
  `**Request fingerprint:** ${shortFingerprint(result.requestFingerprint)}`,
209216
- `**Agents:** ${result.agentOpinions.map((opinion) => `${title(opinion.agent)} ${opinion.status}`).join(", ")}`,
209631
+ `**Agents:** ${result.agentOpinions.map((opinion) => `${title(opinion.agent)} ${opinion.status}${opinion.salvaged ? " (salvaged)" : ""}`).join(", ")}`,
209217
209632
  `**Review mode:** ${formatReviewMode(result)}`,
209218
209633
  ...result.verificationMode ? [
209219
209634
  `**Verification mode:** ${formatVerificationMode(result.verificationMode)}`
@@ -209264,7 +209679,7 @@ function renderMarkdownResult(tool, result, options = {}) {
209264
209679
  }
209265
209680
  lines.push("", "## Agent Opinions", "");
209266
209681
  for (const opinion of result.agentOpinions) {
209267
- lines.push(`### ${title(opinion.agent)}`, "", `${opinion.summary} (${opinion.status})`, "");
209682
+ lines.push(`### ${title(opinion.agent)}`, "", `${opinion.summary} (${opinion.status}${opinion.salvaged ? ", salvaged" : ""})`, "");
209268
209683
  }
209269
209684
  lines.push("", "## Disagreements", "");
209270
209685
  if (result.reviewMode === "single_agent") {
@@ -209295,17 +209710,50 @@ function defaultSummaryText(result) {
209295
209710
  function formatExecutionBudget(result) {
209296
209711
  const budget = result.executionBudget;
209297
209712
  const agentOutputs = Object.entries(budget.agentOutputBytes);
209298
- const outputLines = agentOutputs.length > 0 ? agentOutputs.map(([agent, bytes]) => `- ${title(agent)}: ${bytes} bytes`) : ["- None reported."];
209713
+ const byteBreakdowns = new Map;
209714
+ for (const call of result.audit.modelCalls) {
209715
+ if (call.status !== "completed" || !call.agent)
209716
+ continue;
209717
+ const current = byteBreakdowns.get(call.agent) ?? {
209718
+ messageBytes: 0,
209719
+ thoughtBytes: 0
209720
+ };
209721
+ current.messageBytes += call.messageBytes ?? 0;
209722
+ current.thoughtBytes += call.thoughtBytes ?? 0;
209723
+ byteBreakdowns.set(call.agent, current);
209724
+ }
209725
+ const outputLines = agentOutputs.length > 0 ? agentOutputs.map(([agent, bytes]) => {
209726
+ const breakdown = byteBreakdowns.get(agent);
209727
+ return `- ${title(agent)}: ${bytes} bytes${breakdown ? ` (message: ${breakdown.messageBytes}, thought: ${breakdown.thoughtBytes})` : ""}`;
209728
+ }) : ["- None reported."];
209729
+ const identityLines = result.audit.modelCalls.filter((call) => call.status === "completed").map((call) => {
209730
+ const label = [call.kind, call.agent].filter(Boolean).join("/");
209731
+ const identity = call.executionIdentity;
209732
+ if (!identity)
209733
+ return `- ${label}: identity=unknown`;
209734
+ const reportedIdentity = [
209735
+ identity.reportedProvider ? `reportedProvider=${escapeMarkdownText(identity.reportedProvider)}` : undefined,
209736
+ identity.reportedModel ? `reportedModel=${escapeMarkdownText(identity.reportedModel)}` : undefined
209737
+ ].filter((value) => value !== undefined);
209738
+ return `- ${label}: route=${identity.providerRoute}, requested=${escapeMarkdownText(identity.requestedModel ?? "unknown")}${reportedIdentity.length > 0 ? `, ${reportedIdentity.join(", ")}` : ""}, reporting=${identity.reportingStatus}`;
209739
+ });
209299
209740
  const totalTokens = budget.tokenUsage.totals.totalTokens;
209741
+ const plan = budget.modelCallPlan;
209742
+ const outputLimits = budget.effectiveWarnAgentOutputBytes === undefined ? `${budget.maxAgentOutputBytes} bytes hard (soft warning disabled)` : `${budget.effectiveWarnAgentOutputBytes} bytes soft / ${budget.maxAgentOutputBytes} bytes hard`;
209300
209743
  return [
209301
209744
  "",
209302
209745
  "## Execution Budget",
209303
209746
  "",
209304
- `- Model calls: ${budget.modelCalls.planned} planned / ${budget.modelCalls.consumed} consumed / ${budget.modelCalls.skipped} skipped`,
209747
+ `- Model calls: ${budget.modelCalls.planned} planned / ${budget.modelCalls.consumed} consumed / ${budget.modelCalls.skipped} skipped / ${budget.maxModelCalls} ceiling`,
209748
+ `- Potential calls: ${plan.potentialTotalCalls} total (${plan.requiredPrimaryCalls} primary, ${plan.potentialVerifierCalls} verifier, ${plan.potentialJudgeCalls} judge)`,
209305
209749
  `- Wall time: ${budget.wallTime.consumedMs}ms consumed / ${budget.wallTime.limitMs}ms limit`,
209306
209750
  `- Token usage: ${budget.tokenUsage.status} (${budget.tokenUsage.reportedCalls} reported, ${budget.tokenUsage.unknownCalls} unknown${totalTokens === undefined ? "" : `, ${totalTokens} total`})`,
209751
+ `- Agent output limits: ${outputLimits}`,
209752
+ `- Findings target: ${budget.maxFindingsPerAgent} per primary agent (soft)`,
209307
209753
  "- Agent output:",
209308
- ...outputLines
209754
+ ...outputLines,
209755
+ "- Model identities:",
209756
+ ...identityLines.length > 0 ? identityLines : ["- None completed."]
209309
209757
  ];
209310
209758
  }
209311
209759
  function formatCompletion(result) {
@@ -209698,11 +210146,11 @@ function canonicalJson(value) {
209698
210146
  function canonicalize(value) {
209699
210147
  if (Array.isArray(value))
209700
210148
  return value.map(canonicalize);
209701
- if (!isRecord13(value))
210149
+ if (!isRecord15(value))
209702
210150
  return value;
209703
210151
  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)]));
209704
210152
  }
209705
- function isRecord13(value) {
210153
+ function isRecord15(value) {
209706
210154
  return typeof value === "object" && value !== null && !Array.isArray(value);
209707
210155
  }
209708
210156
 
@@ -209716,12 +210164,10 @@ var REVIEW_BUDGET_KEYS = new Set([
209716
210164
  ]);
209717
210165
  var MODEL_CALL_KINDS = ["primary", "verifier", "judge"];
209718
210166
  function resolveReviewBudget(ceiling, requested) {
209719
- if (requested === undefined)
209720
- return ceiling;
209721
- if (!isRecord14(requested)) {
210167
+ if (requested !== undefined && !isRecord16(requested)) {
209722
210168
  throw new KyosoRequestError("options.reviewBudget must be an object.", "REVIEW_BUDGET_INVALID");
209723
210169
  }
209724
- for (const [key, value] of Object.entries(requested)) {
210170
+ for (const [key, value] of Object.entries(requested ?? {})) {
209725
210171
  if (!REVIEW_BUDGET_KEYS.has(key)) {
209726
210172
  throw new KyosoRequestError(`options.reviewBudget.${key} is not supported.`, "REVIEW_BUDGET_INVALID");
209727
210173
  }
@@ -209742,36 +210188,105 @@ function resolveReviewBudget(ceiling, requested) {
209742
210188
  "maxFindingsPerAgent"
209743
210189
  ];
209744
210190
  for (const key of numericKeys) {
209745
- const value = requested[key];
210191
+ const value = requested?.[key];
209746
210192
  if (value === undefined)
209747
210193
  continue;
209748
210194
  if (value > ceiling[key]) {
209749
210195
  throw new KyosoRequestError(`options.reviewBudget.${key} cannot exceed the user-global ceiling.`, "REVIEW_BUDGET_EXCEEDS_CEILING");
209750
210196
  }
209751
210197
  }
209752
- if (ceiling.skipOptionalPhasesWhenTokenUsageUnknown && requested.skipOptionalPhasesWhenTokenUsageUnknown === false) {
210198
+ if (ceiling.skipOptionalPhasesWhenTokenUsageUnknown && requested?.skipOptionalPhasesWhenTokenUsageUnknown === false) {
209753
210199
  throw new KyosoRequestError("options.reviewBudget.skipOptionalPhasesWhenTokenUsageUnknown cannot relax the user-global ceiling.", "REVIEW_BUDGET_EXCEEDS_CEILING");
209754
210200
  }
210201
+ const maxAgentOutputBytes = requested?.maxAgentOutputBytes ?? ceiling.maxAgentOutputBytes;
210202
+ return {
210203
+ maxModelCalls: requested?.maxModelCalls ?? ceiling.maxModelCalls,
210204
+ maxTotalWallTimeMs: requested?.maxTotalWallTimeMs ?? ceiling.maxTotalWallTimeMs,
210205
+ warnAgentOutputBytes: ceiling.warnAgentOutputBytes,
210206
+ maxAgentOutputBytes,
210207
+ maxFindingsPerAgent: requested?.maxFindingsPerAgent ?? ceiling.maxFindingsPerAgent,
210208
+ skipOptionalPhasesWhenTokenUsageUnknown: ceiling.skipOptionalPhasesWhenTokenUsageUnknown || requested?.skipOptionalPhasesWhenTokenUsageUnknown === true,
210209
+ ...ceiling.warnAgentOutputBytes < maxAgentOutputBytes ? { effectiveWarnAgentOutputBytes: ceiling.warnAgentOutputBytes } : {}
210210
+ };
210211
+ }
210212
+ function buildReviewModelCallPlan(input2) {
210213
+ const requiredPrimaryCalls = nonNegativeInteger(input2.requiredPrimaryCalls);
210214
+ const potentialVerifierCalls = input2.verificationEnabled && requiredPrimaryCalls === 2 ? Math.min(2, nonNegativeInteger(input2.verificationMaxFindings)) : 0;
210215
+ const potentialJudgeCalls = input2.llmJudgeAvailable ? 1 : 0;
210216
+ const potentialTotalCalls = requiredPrimaryCalls + potentialVerifierCalls + potentialJudgeCalls;
210217
+ const ceilingEffects = [];
210218
+ const availableCalls = nonNegativeInteger(input2.maxModelCalls);
210219
+ if (availableCalls < requiredPrimaryCalls) {
210220
+ if (requiredPrimaryCalls > 0) {
210221
+ ceilingEffects.push({
210222
+ kind: "primary",
210223
+ action: "skip",
210224
+ calls: requiredPrimaryCalls,
210225
+ reason: "model_call_budget"
210226
+ });
210227
+ }
210228
+ if (potentialVerifierCalls > 0) {
210229
+ ceilingEffects.push({
210230
+ kind: "verifier",
210231
+ action: "skip",
210232
+ calls: potentialVerifierCalls,
210233
+ reason: "model_call_budget"
210234
+ });
210235
+ }
210236
+ if (potentialJudgeCalls > 0) {
210237
+ ceilingEffects.push({
210238
+ kind: "judge",
210239
+ action: "deterministic_fallback",
210240
+ calls: potentialJudgeCalls,
210241
+ reason: "model_call_budget"
210242
+ });
210243
+ }
210244
+ } else {
210245
+ let remainingCalls = availableCalls - requiredPrimaryCalls;
210246
+ const verifierCalls = Math.min(potentialVerifierCalls, remainingCalls);
210247
+ remainingCalls -= verifierCalls;
210248
+ const skippedVerifierCalls = potentialVerifierCalls - verifierCalls;
210249
+ if (skippedVerifierCalls > 0) {
210250
+ ceilingEffects.push({
210251
+ kind: "verifier",
210252
+ action: "skip",
210253
+ calls: skippedVerifierCalls,
210254
+ reason: "model_call_budget"
210255
+ });
210256
+ }
210257
+ const judgeCalls = Math.min(potentialJudgeCalls, remainingCalls);
210258
+ const fallbackJudgeCalls = potentialJudgeCalls - judgeCalls;
210259
+ if (fallbackJudgeCalls > 0) {
210260
+ ceilingEffects.push({
210261
+ kind: "judge",
210262
+ action: "deterministic_fallback",
210263
+ calls: fallbackJudgeCalls,
210264
+ reason: "model_call_budget"
210265
+ });
210266
+ }
210267
+ }
209755
210268
  return {
209756
- maxModelCalls: requested.maxModelCalls ?? ceiling.maxModelCalls,
209757
- maxTotalWallTimeMs: requested.maxTotalWallTimeMs ?? ceiling.maxTotalWallTimeMs,
209758
- maxAgentOutputBytes: requested.maxAgentOutputBytes ?? ceiling.maxAgentOutputBytes,
209759
- maxFindingsPerAgent: requested.maxFindingsPerAgent ?? ceiling.maxFindingsPerAgent,
209760
- skipOptionalPhasesWhenTokenUsageUnknown: ceiling.skipOptionalPhasesWhenTokenUsageUnknown || requested.skipOptionalPhasesWhenTokenUsageUnknown === true
210269
+ requiredPrimaryCalls,
210270
+ potentialVerifierCalls,
210271
+ potentialJudgeCalls,
210272
+ potentialTotalCalls,
210273
+ ceilingEffects
209761
210274
  };
209762
210275
  }
209763
210276
 
209764
210277
  class ReviewBudgetTracker {
209765
210278
  budget;
209766
210279
  startedAtEpochMs;
210280
+ modelCallPlan;
209767
210281
  deadlineAtEpochMs;
209768
210282
  reservations = new Map;
209769
210283
  skippedCalls = [];
209770
210284
  incompleteReasons = new Set;
209771
210285
  nextReservationId = 1;
209772
- constructor(budget, startedAtEpochMs = Date.now()) {
210286
+ constructor(budget, startedAtEpochMs = Date.now(), modelCallPlan = emptyReviewModelCallPlan()) {
209773
210287
  this.budget = budget;
209774
210288
  this.startedAtEpochMs = startedAtEpochMs;
210289
+ this.modelCallPlan = modelCallPlan;
209775
210290
  this.deadlineAtEpochMs = startedAtEpochMs + budget.maxTotalWallTimeMs;
209776
210291
  }
209777
210292
  remainingWallTimeMs(now = Date.now()) {
@@ -209815,24 +210330,35 @@ class ReviewBudgetTracker {
209815
210330
  }
209816
210331
  return { reservation };
209817
210332
  }
209818
- markStarted(reservation) {
210333
+ markStarted(reservation, executionIdentity) {
209819
210334
  const current = this.reservations.get(reservation.id);
209820
210335
  if (!current || current.status !== "reserved")
209821
210336
  return;
209822
210337
  current.status = "started";
210338
+ current.executionIdentity = normalizeModelExecutionIdentity(executionIdentity);
209823
210339
  }
209824
210340
  hasStarted(reservation) {
209825
210341
  const current = this.reservations.get(reservation.id);
209826
210342
  return current?.status === "started" || current?.status === "completed";
209827
210343
  }
210344
+ executionIdentity(reservation) {
210345
+ return this.reservations.get(reservation.id)?.executionIdentity;
210346
+ }
209828
210347
  complete(reservation, values = {}) {
209829
210348
  const current = this.reservations.get(reservation.id);
209830
210349
  if (!current || current.status === "skipped" || current.status === "completed") {
209831
210350
  return;
209832
210351
  }
209833
210352
  current.status = "completed";
210353
+ current.messageBytes = values.messageBytes;
210354
+ current.thoughtBytes = values.thoughtBytes;
209834
210355
  current.outputBytes = values.outputBytes;
210356
+ current.outputWarningTriggered = values.outputWarningTriggered;
210357
+ current.salvaged = values.salvaged;
210358
+ current.reportedFindings = values.reportedFindings;
210359
+ current.findingsTargetExceeded = values.findingsTargetExceeded;
209835
210360
  current.usage = normalizeModelTokenUsage(values.usage);
210361
+ current.executionIdentity = normalizeModelExecutionIdentity(values.executionIdentity) ?? current.executionIdentity;
209836
210362
  current.stopReason = values.stopReason;
209837
210363
  }
209838
210364
  skip(reservation, reason) {
@@ -209905,12 +210431,16 @@ class ReviewBudgetTracker {
209905
210431
  },
209906
210432
  executionBudget: {
209907
210433
  maxModelCalls: this.budget.maxModelCalls,
210434
+ modelCallPlan: this.modelCallPlan,
209908
210435
  modelCalls: { planned, consumed, skipped, byKind },
209909
210436
  wallTime: {
209910
210437
  limitMs: this.budget.maxTotalWallTimeMs,
209911
210438
  consumedMs,
209912
210439
  remainingMs: this.remainingWallTimeMs(now)
209913
210440
  },
210441
+ ...this.budget.effectiveWarnAgentOutputBytes !== undefined ? {
210442
+ effectiveWarnAgentOutputBytes: this.budget.effectiveWarnAgentOutputBytes
210443
+ } : {},
209914
210444
  maxAgentOutputBytes: this.budget.maxAgentOutputBytes,
209915
210445
  maxFindingsPerAgent: this.budget.maxFindingsPerAgent,
209916
210446
  skipOptionalPhasesWhenTokenUsageUnknown: this.budget.skipOptionalPhasesWhenTokenUsageUnknown,
@@ -209934,8 +210464,15 @@ class ReviewBudgetTracker {
209934
210464
  ...reservation.agent ? { agent: reservation.agent } : {},
209935
210465
  status: reservation.status === "completed" ? "completed" : "skipped",
209936
210466
  ...reservation.reason ? { reason: reservation.reason } : {},
210467
+ ...reservation.messageBytes !== undefined ? { messageBytes: reservation.messageBytes } : {},
210468
+ ...reservation.thoughtBytes !== undefined ? { thoughtBytes: reservation.thoughtBytes } : {},
209937
210469
  ...reservation.outputBytes !== undefined ? { outputBytes: reservation.outputBytes } : {},
210470
+ ...reservation.outputWarningTriggered !== undefined ? { outputWarningTriggered: reservation.outputWarningTriggered } : {},
210471
+ ...reservation.salvaged !== undefined ? { salvaged: reservation.salvaged } : {},
210472
+ ...reservation.reportedFindings !== undefined ? { reportedFindings: reservation.reportedFindings } : {},
210473
+ ...reservation.findingsTargetExceeded !== undefined ? { findingsTargetExceeded: reservation.findingsTargetExceeded } : {},
209938
210474
  ...reservation.usage ? { usage: reservation.usage } : {},
210475
+ ...reservation.executionIdentity ? { executionIdentity: reservation.executionIdentity } : {},
209939
210476
  ...reservation.stopReason ? { stopReason: reservation.stopReason } : {}
209940
210477
  }));
209941
210478
  return [...reservations, ...this.skippedCalls];
@@ -209959,7 +210496,19 @@ function addUsage(total, usage) {
209959
210496
  function isPositiveInteger(value) {
209960
210497
  return typeof value === "number" && Number.isInteger(value) && value > 0;
209961
210498
  }
209962
- function isRecord14(value) {
210499
+ function nonNegativeInteger(value) {
210500
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
210501
+ }
210502
+ function emptyReviewModelCallPlan() {
210503
+ return {
210504
+ requiredPrimaryCalls: 0,
210505
+ potentialVerifierCalls: 0,
210506
+ potentialJudgeCalls: 0,
210507
+ potentialTotalCalls: 0,
210508
+ ceilingEffects: []
210509
+ };
210510
+ }
210511
+ function isRecord16(value) {
209963
210512
  return typeof value === "object" && value !== null && !Array.isArray(value);
209964
210513
  }
209965
210514
 
@@ -210021,7 +210570,7 @@ function parseVerificationVerdicts(rawText) {
210021
210570
  if (!Array.isArray(parsed.verdicts))
210022
210571
  return;
210023
210572
  return parsed.verdicts.flatMap((item) => {
210024
- if (!isRecord15(item))
210573
+ if (!isRecord17(item))
210025
210574
  return [];
210026
210575
  if (typeof item.findingId !== "string")
210027
210576
  return [];
@@ -210099,7 +210648,7 @@ function verificationNote(reasoning) {
210099
210648
  function isVerdict(value) {
210100
210649
  return value === "confirmed" || value === "refuted" || value === "uncertain";
210101
210650
  }
210102
- function isRecord15(value) {
210651
+ function isRecord17(value) {
210103
210652
  return typeof value === "object" && value !== null && !Array.isArray(value);
210104
210653
  }
210105
210654
 
@@ -210124,13 +210673,14 @@ async function runReview(tool, request, options = {}) {
210124
210673
  } catch (error51) {
210125
210674
  if (error51 instanceof KyosoRequestError) {
210126
210675
  const config2 = kyosoConfigSchema.parse(defaultConfig);
210127
- const budgetTracker = new ReviewBudgetTracker(config2.reviewBudget, startedAtEpochMs);
210676
+ const reviewBudget = resolveReviewBudget(config2.reviewBudget, undefined);
210677
+ const budgetTracker = new ReviewBudgetTracker(reviewBudget, startedAtEpochMs, configuredReviewModelCallPlan(config2, reviewBudget, options.env ?? process.env));
210128
210678
  const requestFingerprint = createRequestFingerprint({
210129
210679
  tool,
210130
210680
  request: requestForRecursionFingerprint(request),
210131
210681
  config: config2,
210132
210682
  roles: resolveAgentRoles(config2),
210133
- budget: config2.reviewBudget,
210683
+ budget: reviewBudget,
210134
210684
  entrypoint: options.entrypoint
210135
210685
  });
210136
210686
  const trace2 = traceWriterFactory({
@@ -210236,7 +210786,7 @@ async function runReview(tool, request, options = {}) {
210236
210786
  });
210237
210787
  validateReviewRequest(tool, request);
210238
210788
  const reviewBudget = resolveReviewBudget(loaded.config.reviewBudget, request.options?.reviewBudget);
210239
- const budgetTracker = new ReviewBudgetTracker(reviewBudget, startedAtEpochMs);
210789
+ const budgetTracker = new ReviewBudgetTracker(reviewBudget, startedAtEpochMs, configuredReviewModelCallPlan(loaded.config, reviewBudget, options.env ?? process.env, request.options?.judgeProvider));
210240
210790
  assertTrustedWorkspaceRoot(request.workspace?.root, loaded.config.workspace.root, cwd);
210241
210791
  const networkMode = resolveNetworkMode(request.options?.network, loaded.config.network.defaultMode, options.mcpNetworkMode);
210242
210792
  if (networkMode === "unrestricted" && !loaded.config.network.allowUnrestricted) {
@@ -210357,6 +210907,7 @@ async function runReview(tool, request, options = {}) {
210357
210907
  budgetTracker,
210358
210908
  requestFingerprint
210359
210909
  });
210910
+ warnings.push(...plannedBudgetWarnings(budgetTracker));
210360
210911
  snapshot = await createSnapshot(traceId, tool, built.request, {
210361
210912
  denyPatterns,
210362
210913
  allowPatterns,
@@ -210383,11 +210934,9 @@ async function runReview(tool, request, options = {}) {
210383
210934
  budgetTracker
210384
210935
  });
210385
210936
  warnings.push(...agentResults.flatMap((result) => (result.warnings ?? []).map((warning) => `Agent ${result.agent} ${warning}`)));
210386
- const normalized = agentResults.map((result) => normalizeAgentRunResult(result, reviewBudget.maxFindingsPerAgent));
210387
- const normalizedAgentResults = normalized.map((item) => item.result);
210388
- for (const item of normalized.filter((item2) => item2.findingsCapped)) {
210389
- budgetTracker.markIncomplete("coverage_incomplete");
210390
- warnings.push(`Agent ${item.result.agent} findings were capped at ${reviewBudget.maxFindingsPerAgent}; review coverage is incomplete.`);
210937
+ const normalizedAgentResults = agentResults.map((result) => normalizeAgentRunResult(result, reviewBudget.maxFindingsPerAgent));
210938
+ for (const result of normalizedAgentResults.filter((item) => item.findingsTargetExceeded)) {
210939
+ warnings.push(`Agent ${result.agent} reported ${result.reportedFindings} findings, above the soft target of ${reviewBudget.maxFindingsPerAgent}; all findings were retained.`);
210391
210940
  }
210392
210941
  const enabledAgents = ["codex", "claude"].filter((agent) => loaded.config.agents[agent].enabled);
210393
210942
  const agentsUsed = normalizedAgentResults.filter((result) => result.status !== "skipped").map((result) => result.agent);
@@ -210561,6 +211110,11 @@ async function runReview(tool, request, options = {}) {
210561
211110
  }));
210562
211111
  const crossModelAnalysis = buildCrossModelAnalysis(judge, reviewMode);
210563
211112
  const budgetAfterJudge = budgetTracker.snapshot();
211113
+ const finalWarnings = Array.from(new Set([
211114
+ ...resultWithoutMarkdown.audit.warnings ?? [],
211115
+ ...outputWarningMessages(budgetAfterJudge),
211116
+ ...tokenUsageWarningMessages(budgetTracker, budgetAfterJudge)
211117
+ ]));
210564
211118
  const finalDecision = budgetAfterJudge.completion.status === "incomplete" ? "block" : decision;
210565
211119
  const resultAfterJudge = {
210566
211120
  ...resultWithoutMarkdown,
@@ -210572,6 +211126,7 @@ async function runReview(tool, request, options = {}) {
210572
211126
  audit: {
210573
211127
  ...resultWithoutMarkdown.audit,
210574
211128
  completedAt: new Date().toISOString(),
211129
+ warnings: finalWarnings,
210575
211130
  modelCalls: budgetAfterJudge.modelCalls
210576
211131
  }
210577
211132
  };
@@ -210580,6 +211135,7 @@ async function runReview(tool, request, options = {}) {
210580
211135
  traceId,
210581
211136
  provider: judge.provider,
210582
211137
  status: judge.status,
211138
+ ...judge.executionIdentity ? { executionIdentity: judge.executionIdentity } : {},
210583
211139
  timestamp: new Date().toISOString()
210584
211140
  };
210585
211141
  if (judge.error)
@@ -210773,11 +211329,20 @@ async function runFindingVerification(input2) {
210773
211329
  workspaceDir: input2.workspaceDir,
210774
211330
  timeoutMs: Math.min(input2.config.verification.timeoutMs, input2.budgetTracker.remainingWallTimeMs()),
210775
211331
  deadlineAtEpochMs: input2.budgetTracker.deadlineAtEpochMs,
211332
+ warnOutputBytes: input2.budgetTracker.budget.effectiveWarnAgentOutputBytes,
210776
211333
  maxOutputBytes: input2.budgetTracker.budget.maxAgentOutputBytes,
210777
211334
  networkMode: input2.networkMode,
210778
- onStarted: () => {
210779
- input2.budgetTracker.markStarted(group.reservation);
210780
- return Promise.resolve();
211335
+ onStarted: (executionIdentity) => {
211336
+ input2.budgetTracker.markStarted(group.reservation, executionIdentity);
211337
+ const event = buildAgentStartedEvent({
211338
+ traceId: input2.traceId,
211339
+ agent: group.verifier,
211340
+ role: "finding_verifier",
211341
+ executionIdentity: input2.budgetTracker.executionIdentity(group.reservation)
211342
+ });
211343
+ return input2.trace.write(event).catch(() => {
211344
+ warnings.push("AUDIT_WRITE_FAILED: agent_started event could not be recorded.");
211345
+ });
210781
211346
  }
210782
211347
  }));
210783
211348
  let results;
@@ -210919,8 +211484,8 @@ function buildCrossModelAnalysis(judge, reviewMode) {
210919
211484
  }
210920
211485
  async function runBudgetedJudge(input2) {
210921
211486
  const configuredProvider = input2.requestedProvider ?? input2.config.provider;
210922
- const provider = resolveJudgeProvider(configuredProvider, input2.env);
210923
- if (input2.config.mode === "deterministic_only" || provider === "deterministic_fallback") {
211487
+ const judgeRoute = resolveJudgeCallRoute(input2.config.mode, configuredProvider, input2.env);
211488
+ if (!judgeRoute.llmAvailable) {
210924
211489
  return runJudge(input2);
210925
211490
  }
210926
211491
  const fallback = () => runJudge({
@@ -210979,8 +211544,10 @@ async function runBudgetedJudge(input2) {
210979
211544
  const judge = await runJudge({ ...input2, timeoutMs });
210980
211545
  const usage = normalizeModelTokenUsage(judge.usage);
210981
211546
  input2.budgetTracker.complete(reservation, {
210982
- ...usage ? { usage } : {}
211547
+ ...usage ? { usage } : {},
211548
+ ...judge.executionIdentity ? { executionIdentity: judge.executionIdentity } : {}
210983
211549
  });
211550
+ const executionIdentity = input2.budgetTracker.executionIdentity(reservation);
210984
211551
  await input2.trace.write({
210985
211552
  type: "model_call_completed",
210986
211553
  traceId: input2.traceId,
@@ -210988,6 +211555,7 @@ async function runBudgetedJudge(input2) {
210988
211555
  provider: judge.provider,
210989
211556
  resultStatus: judge.status,
210990
211557
  ...usage ? { usage } : {},
211558
+ ...executionIdentity ? { executionIdentity } : {},
210991
211559
  timestamp: new Date().toISOString()
210992
211560
  });
210993
211561
  return judge;
@@ -211101,30 +211669,25 @@ async function runAgents(input2) {
211101
211669
  tool: input2.tool,
211102
211670
  prompt: buildAgentPrompt(input2.tool, input2.request, agent, role, {
211103
211671
  requiredLenses,
211104
- cisaEnabled: input2.config.securityReview.cisaSecureByDesign.enabled
211672
+ cisaEnabled: input2.config.securityReview.cisaSecureByDesign.enabled,
211673
+ maxFindingsTarget: input2.budgetTracker.budget.maxFindingsPerAgent
211105
211674
  }),
211106
211675
  workspaceDir: input2.workspaceDir,
211107
211676
  timeoutMs: Math.min(input2.request.options?.maxAgentTimeoutMs ?? Number.POSITIVE_INFINITY, agentConfig.timeoutMs ?? DEFAULT_AGENT_TIMEOUT_MS, input2.budgetTracker.remainingWallTimeMs()),
211108
211677
  deadlineAtEpochMs: input2.budgetTracker.deadlineAtEpochMs,
211678
+ warnOutputBytes: input2.budgetTracker.budget.effectiveWarnAgentOutputBytes,
211109
211679
  maxOutputBytes: input2.budgetTracker.budget.maxAgentOutputBytes,
211110
211680
  networkMode: input2.networkMode,
211111
- onStarted: () => {
211112
- input2.budgetTracker.markStarted(reservation);
211681
+ onStarted: (executionIdentity) => {
211682
+ input2.budgetTracker.markStarted(reservation, executionIdentity);
211113
211683
  if (!acceptingStartedEvents)
211114
211684
  return Promise.resolve();
211115
- const event = {
211116
- type: "agent_started",
211685
+ const event = buildAgentStartedEvent({
211117
211686
  traceId: input2.traceId,
211118
211687
  agent,
211119
211688
  role,
211120
- timestamp: new Date().toISOString()
211121
- };
211122
- if (agentConfig.model) {
211123
- event.model = sanitizeTextForDisplay(agentConfig.model);
211124
- }
211125
- if (agent === "codex" && input2.config.agents.codex.provider === CODEX_OPENROUTER_PROVIDER) {
211126
- event.provider = CODEX_OPENROUTER_PROVIDER;
211127
- }
211689
+ executionIdentity: input2.budgetTracker.executionIdentity(reservation)
211690
+ });
211128
211691
  const write = (async () => {
211129
211692
  try {
211130
211693
  await input2.trace.write(event);
@@ -211176,7 +211739,8 @@ async function runAgents(input2) {
211176
211739
  }
211177
211740
  };
211178
211741
  });
211179
- for (const result of orderedResults) {
211742
+ const normalizedResults = orderedResults.map((result) => normalizeAgentRunResult(result, input2.budgetTracker.budget.maxFindingsPerAgent));
211743
+ for (const result of normalizedResults) {
211180
211744
  const reservation = reservations.get(result.agent);
211181
211745
  if (!reservation)
211182
211746
  continue;
@@ -211191,7 +211755,7 @@ async function runAgents(input2) {
211191
211755
  input2.budgetTracker.markIncomplete("coverage_incomplete");
211192
211756
  }
211193
211757
  }
211194
- await Promise.all(orderedResults.map((result) => {
211758
+ await Promise.all(normalizedResults.map((result) => {
211195
211759
  const event = {
211196
211760
  type: "agent_completed",
211197
211761
  traceId: input2.traceId,
@@ -211206,12 +211770,20 @@ async function runAgents(input2) {
211206
211770
  event.errorCode = result.error.code;
211207
211771
  event.errorDetail = result.error.detail;
211208
211772
  }
211773
+ if (result.salvaged !== undefined)
211774
+ event.salvaged = result.salvaged;
211775
+ if (result.reportedFindings !== undefined) {
211776
+ event.reportedFindings = result.reportedFindings;
211777
+ }
211778
+ if (result.findingsTargetExceeded !== undefined) {
211779
+ event.findingsTargetExceeded = result.findingsTargetExceeded;
211780
+ }
211209
211781
  if (input2.config.audit.includeRawAgentOutput && result.rawText) {
211210
211782
  event.rawText = sanitizeTextForRawOutput(result.rawText);
211211
211783
  }
211212
211784
  return input2.trace.write(event);
211213
211785
  }));
211214
- return orderedResults;
211786
+ return normalizedResults;
211215
211787
  }
211216
211788
  async function skipReservedPrimaryAgents(input2) {
211217
211789
  const results = [];
@@ -211261,20 +211833,44 @@ async function finalizeModelCallResult(input2) {
211261
211833
  });
211262
211834
  return;
211263
211835
  }
211264
- input2.budgetTracker.markStarted(input2.reservation);
211836
+ input2.budgetTracker.markStarted(input2.reservation, input2.result.executionIdentity);
211265
211837
  const usage = normalizeModelTokenUsage(input2.result.usage);
211266
- const outputBytes = input2.result.outputBytes ?? (input2.result.rawText ? Buffer.byteLength(input2.result.rawText, "utf8") : undefined);
211838
+ const { messageBytes, thoughtBytes, outputBytes } = resolveOutputByteMetrics(input2.result);
211839
+ const warningThreshold = input2.budgetTracker.budget.effectiveWarnAgentOutputBytes;
211840
+ const warningTriggered = warningThreshold !== undefined && outputBytes !== undefined && (input2.result.outputWarningTriggered === true || outputBytes >= warningThreshold);
211841
+ const outputWarningTriggered = warningTriggered || input2.result.outputWarningTriggered !== undefined ? warningTriggered : undefined;
211267
211842
  input2.budgetTracker.complete(input2.reservation, {
211843
+ ...messageBytes === undefined ? {} : { messageBytes },
211844
+ ...thoughtBytes === undefined ? {} : { thoughtBytes },
211268
211845
  ...outputBytes === undefined ? {} : { outputBytes },
211846
+ ...outputWarningTriggered === undefined ? {} : { outputWarningTriggered },
211847
+ ...input2.result.salvaged === undefined ? {} : { salvaged: input2.result.salvaged },
211848
+ ...input2.result.reportedFindings === undefined ? {} : { reportedFindings: input2.result.reportedFindings },
211849
+ ...input2.result.findingsTargetExceeded === undefined ? {} : { findingsTargetExceeded: input2.result.findingsTargetExceeded },
211269
211850
  ...usage ? { usage } : {},
211851
+ ...input2.result.executionIdentity ? { executionIdentity: input2.result.executionIdentity } : {},
211270
211852
  ...input2.result.stopReason ? { stopReason: input2.result.stopReason } : {}
211271
211853
  });
211854
+ const executionIdentity = input2.budgetTracker.executionIdentity(input2.reservation);
211272
211855
  if (input2.result.error?.code === "AGENT_OUTPUT_LIMIT") {
211273
211856
  input2.budgetTracker.markIncomplete("agent_output_limit");
211274
211857
  }
211275
211858
  if (input2.result.error?.code === "REVIEW_DEADLINE_EXCEEDED") {
211276
211859
  input2.budgetTracker.markIncomplete("deadline");
211277
211860
  }
211861
+ if (outputWarningTriggered && warningThreshold !== undefined && messageBytes !== undefined && thoughtBytes !== undefined && outputBytes !== undefined) {
211862
+ await input2.trace.write({
211863
+ type: "agent_output_warning",
211864
+ traceId: input2.traceId,
211865
+ kind: input2.reservation.kind,
211866
+ agent: input2.reservation.agent,
211867
+ thresholdBytes: warningThreshold,
211868
+ messageBytes,
211869
+ thoughtBytes,
211870
+ outputBytes,
211871
+ timestamp: new Date().toISOString()
211872
+ });
211873
+ }
211278
211874
  await input2.trace.write({
211279
211875
  type: "model_call_completed",
211280
211876
  traceId: input2.traceId,
@@ -211282,12 +211878,55 @@ async function finalizeModelCallResult(input2) {
211282
211878
  agent: input2.reservation.agent,
211283
211879
  resultStatus: input2.result.status,
211284
211880
  ...input2.result.error?.code ? { errorCode: input2.result.error.code } : {},
211881
+ ...messageBytes === undefined ? {} : { messageBytes },
211882
+ ...thoughtBytes === undefined ? {} : { thoughtBytes },
211285
211883
  ...outputBytes === undefined ? {} : { outputBytes },
211884
+ ...outputWarningTriggered === undefined ? {} : { outputWarningTriggered },
211885
+ ...input2.result.salvaged === undefined ? {} : { salvaged: input2.result.salvaged },
211886
+ ...input2.result.reportedFindings === undefined ? {} : { reportedFindings: input2.result.reportedFindings },
211887
+ ...input2.result.findingsTargetExceeded === undefined ? {} : { findingsTargetExceeded: input2.result.findingsTargetExceeded },
211286
211888
  ...usage ? { usage } : {},
211889
+ ...executionIdentity ? { executionIdentity } : {},
211287
211890
  ...input2.result.stopReason ? { stopReason: input2.result.stopReason } : {},
211288
211891
  timestamp: new Date().toISOString()
211289
211892
  });
211290
211893
  }
211894
+ function buildAgentStartedEvent(input2) {
211895
+ const executionIdentity = normalizeModelExecutionIdentity(input2.executionIdentity);
211896
+ return {
211897
+ type: "agent_started",
211898
+ traceId: input2.traceId,
211899
+ agent: input2.agent,
211900
+ role: input2.role,
211901
+ ...executionIdentity ? { executionIdentity } : {},
211902
+ ...executionIdentity?.requestedModel ? { model: executionIdentity.requestedModel } : {},
211903
+ ...executionIdentity?.providerRoute === "openrouter" ? { provider: "openrouter" } : {},
211904
+ timestamp: new Date().toISOString()
211905
+ };
211906
+ }
211907
+ function resolveOutputByteMetrics(result) {
211908
+ const rawTextBytes = result.rawText ? Buffer.byteLength(result.rawText, "utf8") : undefined;
211909
+ let messageBytes = result.messageBytes;
211910
+ let thoughtBytes = result.thoughtBytes;
211911
+ if (messageBytes === undefined && thoughtBytes === undefined) {
211912
+ if (rawTextBytes !== undefined) {
211913
+ messageBytes = rawTextBytes;
211914
+ thoughtBytes = result.outputBytes === undefined ? 0 : Math.max(0, result.outputBytes - rawTextBytes);
211915
+ } else if (result.outputBytes !== undefined) {
211916
+ messageBytes = result.outputBytes;
211917
+ thoughtBytes = 0;
211918
+ }
211919
+ } else if (messageBytes === undefined) {
211920
+ messageBytes = result.outputBytes === undefined ? rawTextBytes ?? 0 : Math.max(0, result.outputBytes - (thoughtBytes ?? 0));
211921
+ } else if (thoughtBytes === undefined) {
211922
+ thoughtBytes = result.outputBytes === undefined ? 0 : Math.max(0, result.outputBytes - messageBytes);
211923
+ }
211924
+ return {
211925
+ ...messageBytes === undefined ? {} : { messageBytes },
211926
+ ...thoughtBytes === undefined ? {} : { thoughtBytes },
211927
+ ...messageBytes === undefined || thoughtBytes === undefined ? {} : { outputBytes: messageBytes + thoughtBytes }
211928
+ };
211929
+ }
211291
211930
  function isPreflightAgentFailure(result) {
211292
211931
  return result.status === "failed" && [
211293
211932
  "AGENT_CONFIG_INVALID",
@@ -211365,24 +212004,11 @@ function normalizeAgentRunResult(result, maxFindingsPerAgent) {
211365
212004
  ...result,
211366
212005
  normalized: normalizeAgentOutput(result.agent, result.role, result.rawText)
211367
212006
  } : result;
211368
- const normalized = normalizedResult.normalized;
211369
- const findings = normalized?.findings;
211370
- if (!normalized || !findings || findings.length <= maxFindingsPerAgent) {
211371
- return { result: normalizedResult, findingsCapped: false };
211372
- }
211373
- const limitedFindings = findings.map((finding, index) => ({ finding, index })).sort((left, right) => {
211374
- const severity = compareSeverity(left.finding.severity, right.finding.severity);
211375
- return severity === 0 ? left.index - right.index : severity;
211376
- }).slice(0, maxFindingsPerAgent).map(({ finding }) => finding);
211377
- return {
211378
- result: {
211379
- ...normalizedResult,
211380
- normalized: {
211381
- ...normalized,
211382
- findings: limitedFindings
211383
- }
211384
- },
211385
- findingsCapped: true
212007
+ const reportedFindings = normalizedResult.normalized?.findings.length;
212008
+ return reportedFindings === undefined ? normalizedResult : {
212009
+ ...normalizedResult,
212010
+ reportedFindings,
212011
+ findingsTargetExceeded: reportedFindings > maxFindingsPerAgent
211386
212012
  };
211387
212013
  }
211388
212014
  function agentOpinionSummary(result, includeRawText = false) {
@@ -211391,7 +212017,8 @@ function agentOpinionSummary(result, includeRawText = false) {
211391
212017
  role: result.role,
211392
212018
  summary: result.normalized?.summary ?? sanitizeTextForDisplay(result.error?.message ?? result.status),
211393
212019
  status: result.status,
211394
- errorCode: result.error?.code
212020
+ errorCode: result.error?.code,
212021
+ ...result.salvaged === undefined ? {} : { salvaged: result.salvaged }
211395
212022
  };
211396
212023
  if (includeRawText && result.rawText) {
211397
212024
  opinion.rawText = sanitizeTextForRawOutput(result.rawText);
@@ -211593,12 +212220,60 @@ async function writeReviewBudgetPlanned(input2) {
211593
212220
  requestFingerprint: input2.requestFingerprint,
211594
212221
  maxModelCalls: snapshot.executionBudget.maxModelCalls,
211595
212222
  maxTotalWallTimeMs: snapshot.executionBudget.wallTime.limitMs,
212223
+ ...snapshot.executionBudget.effectiveWarnAgentOutputBytes !== undefined ? {
212224
+ effectiveWarnAgentOutputBytes: snapshot.executionBudget.effectiveWarnAgentOutputBytes
212225
+ } : {},
211596
212226
  maxAgentOutputBytes: snapshot.executionBudget.maxAgentOutputBytes,
211597
212227
  maxFindingsPerAgent: snapshot.executionBudget.maxFindingsPerAgent,
211598
212228
  skipOptionalPhasesWhenTokenUsageUnknown: snapshot.executionBudget.skipOptionalPhasesWhenTokenUsageUnknown,
212229
+ ...snapshot.executionBudget.modelCallPlan,
211599
212230
  timestamp: new Date().toISOString()
211600
212231
  });
211601
212232
  }
212233
+ function plannedBudgetWarnings(budgetTracker) {
212234
+ const fallbackCalls = budgetTracker.modelCallPlan.ceilingEffects.filter((effect) => effect.kind === "judge" && effect.action === "deterministic_fallback" && effect.reason === "model_call_budget").reduce((total, effect) => total + effect.calls, 0);
212235
+ if (fallbackCalls === 0)
212236
+ return [];
212237
+ return [
212238
+ `The potential model-call plan requires ${budgetTracker.modelCallPlan.potentialTotalCalls} calls, above maxModelCalls=${budgetTracker.budget.maxModelCalls}; ${fallbackCalls} LLM judge call(s) will use deterministic fallback if higher-priority calls consume the available capacity.`
212239
+ ];
212240
+ }
212241
+ function outputWarningMessages(snapshot) {
212242
+ const threshold = snapshot.executionBudget.effectiveWarnAgentOutputBytes;
212243
+ if (threshold === undefined)
212244
+ return [];
212245
+ return snapshot.modelCalls.flatMap((call) => {
212246
+ if (call.status !== "completed" || !call.outputWarningTriggered || !call.agent) {
212247
+ return [];
212248
+ }
212249
+ const messageBytes = call.messageBytes ?? 0;
212250
+ const thoughtBytes = call.thoughtBytes ?? 0;
212251
+ const outputBytes = call.outputBytes ?? messageBytes + thoughtBytes;
212252
+ const outcome = call.stopReason === "cancelled" ? "the hard breaker subsequently stopped execution." : "execution continued.";
212253
+ return [
212254
+ `Agent ${call.agent} ${call.kind} output reached the ${threshold}-byte soft threshold (message: ${messageBytes}, thought: ${thoughtBytes}, total: ${outputBytes}); ${outcome}`
212255
+ ];
212256
+ });
212257
+ }
212258
+ function tokenUsageWarningMessages(budgetTracker, snapshot) {
212259
+ const unknownCalls = snapshot.executionBudget.tokenUsage.unknownCalls;
212260
+ if (budgetTracker.budget.skipOptionalPhasesWhenTokenUsageUnknown || unknownCalls === 0) {
212261
+ return [];
212262
+ }
212263
+ return [
212264
+ `Token usage was not reported for ${unknownCalls} completed call(s); budget enforcement continued using calls, wall time, and bytes.`
212265
+ ];
212266
+ }
212267
+ function configuredReviewModelCallPlan(config2, budget, env, requestedJudgeProvider) {
212268
+ const judgeRoute = resolveJudgeCallRoute(config2.judge.mode, requestedJudgeProvider ?? config2.judge.provider, env);
212269
+ return buildReviewModelCallPlan({
212270
+ maxModelCalls: budget.maxModelCalls,
212271
+ requiredPrimaryCalls: Object.values(config2.agents).filter((agent) => agent.enabled).length,
212272
+ verificationEnabled: config2.verification.enabled,
212273
+ verificationMaxFindings: config2.verification.maxFindings,
212274
+ llmJudgeAvailable: judgeRoute.llmAvailable
212275
+ });
212276
+ }
211602
212277
  async function writeReviewBudgetCompleted(input2) {
211603
212278
  const snapshot = input2.budgetTracker.snapshot();
211604
212279
  await input2.trace.write({