@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/index.js CHANGED
@@ -183780,7 +183780,8 @@ function date4(params) {
183780
183780
  // node_modules/zod/v4/classic/external.js
183781
183781
  config(en_default());
183782
183782
  // src/core/constants.ts
183783
- var DEFAULT_AGENT_TIMEOUT_MS = 120000;
183783
+ var DEFAULT_AGENT_TIMEOUT_MS = 600000;
183784
+ var DEFAULT_WARN_AGENT_OUTPUT_BYTES = 524288;
183784
183785
  var MAX_AGENT_OUTPUT_BYTES = 1048576;
183785
183786
  var JUDGE_MAX_OUTPUT_TOKENS = 4096;
183786
183787
  var RAW_OUTPUT_MAX_CHARS = 16384;
@@ -183929,7 +183930,7 @@ var baseAgentSchema = exports_external.object({
183929
183930
  "architecture_security_reviewer",
183930
183931
  "combined_reviewer"
183931
183932
  ]),
183932
- timeoutMs: exports_external.number().int().positive().default(120000),
183933
+ timeoutMs: exports_external.number().int().positive().default(DEFAULT_AGENT_TIMEOUT_MS),
183933
183934
  env: exports_external.record(exports_external.string(), exports_external.string()).default({}),
183934
183935
  auth: exports_external.object({
183935
183936
  mode: exports_external.literal("passthrough").default("passthrough"),
@@ -183960,6 +183961,7 @@ var codexAgentSchema = baseAgentSchema.extend({
183960
183961
  var reviewBudgetSchema = exports_external.object({
183961
183962
  maxModelCalls: exports_external.number().int().positive(),
183962
183963
  maxTotalWallTimeMs: exports_external.number().int().positive(),
183964
+ warnAgentOutputBytes: exports_external.number().int().positive().max(MAX_AGENT_OUTPUT_BYTES),
183963
183965
  maxAgentOutputBytes: exports_external.number().int().positive().max(MAX_AGENT_OUTPUT_BYTES),
183964
183966
  maxFindingsPerAgent: exports_external.number().int().positive(),
183965
183967
  skipOptionalPhasesWhenTokenUsageUnknown: exports_external.boolean()
@@ -184035,13 +184037,21 @@ var kyosoConfigSchema = exports_external.object({
184035
184037
  })
184036
184038
  }).superRefine((config2, context) => {
184037
184039
  const enabledPrimaryReviewers = Object.values(config2.agents).filter((agent) => agent.enabled).length;
184038
- if (config2.reviewBudget.maxModelCalls >= enabledPrimaryReviewers)
184039
- return;
184040
- context.addIssue({
184041
- code: exports_external.ZodIssueCode.custom,
184042
- path: ["reviewBudget", "maxModelCalls"],
184043
- message: "must be greater than or equal to the number of enabled primary reviewers."
184044
- });
184040
+ if (config2.reviewBudget.maxModelCalls < enabledPrimaryReviewers) {
184041
+ context.addIssue({
184042
+ code: exports_external.ZodIssueCode.custom,
184043
+ path: ["reviewBudget", "maxModelCalls"],
184044
+ message: "must be greater than or equal to the number of enabled primary reviewers."
184045
+ });
184046
+ }
184047
+ const inheritedLegacyHardLimit = config2.reviewBudget.warnAgentOutputBytes === DEFAULT_WARN_AGENT_OUTPUT_BYTES && config2.reviewBudget.maxAgentOutputBytes <= DEFAULT_WARN_AGENT_OUTPUT_BYTES;
184048
+ if (config2.reviewBudget.warnAgentOutputBytes >= config2.reviewBudget.maxAgentOutputBytes && !inheritedLegacyHardLimit) {
184049
+ context.addIssue({
184050
+ code: exports_external.ZodIssueCode.custom,
184051
+ path: ["reviewBudget", "warnAgentOutputBytes"],
184052
+ message: "must be less than reviewBudget.maxAgentOutputBytes."
184053
+ });
184054
+ }
184045
184055
  });
184046
184056
  function agentConfigLeafPaths(agent) {
184047
184057
  const paths = [
@@ -184104,6 +184114,7 @@ var kyosoConfigKnownLeafPaths = [
184104
184114
  "verification.allowDemotion",
184105
184115
  "reviewBudget.maxModelCalls",
184106
184116
  "reviewBudget.maxTotalWallTimeMs",
184117
+ "reviewBudget.warnAgentOutputBytes",
184107
184118
  "reviewBudget.maxAgentOutputBytes",
184108
184119
  "reviewBudget.maxFindingsPerAgent",
184109
184120
  "reviewBudget.skipOptionalPhasesWhenTokenUsageUnknown",
@@ -184149,9 +184160,9 @@ var defaultConfig = {
184149
184160
  enabled: true,
184150
184161
  type: "acp",
184151
184162
  command: "npx",
184152
- args: ["-y", "@agentclientprotocol/codex-acp@1.1.2"],
184163
+ args: ["-y", "@agentclientprotocol/codex-acp@1.1.4"],
184153
184164
  role: "implementation_reviewer",
184154
- timeoutMs: 120000,
184165
+ timeoutMs: DEFAULT_AGENT_TIMEOUT_MS,
184155
184166
  allowProjectProvider: [],
184156
184167
  env: {
184157
184168
  INITIAL_AGENT_MODE: "read-only",
@@ -184176,7 +184187,7 @@ var defaultConfig = {
184176
184187
  command: "npx",
184177
184188
  args: ["-y", "@agentclientprotocol/claude-agent-acp@0.58.1"],
184178
184189
  role: "architecture_security_reviewer",
184179
- timeoutMs: 300000,
184190
+ timeoutMs: DEFAULT_AGENT_TIMEOUT_MS,
184180
184191
  env: {
184181
184192
  KYOSO_CHILD_AGENT: "1"
184182
184193
  },
@@ -184257,10 +184268,11 @@ var defaultConfig = {
184257
184268
  },
184258
184269
  reviewBudget: {
184259
184270
  maxModelCalls: 4,
184260
- maxTotalWallTimeMs: 480000,
184261
- maxAgentOutputBytes: 65536,
184271
+ maxTotalWallTimeMs: 660000,
184272
+ warnAgentOutputBytes: DEFAULT_WARN_AGENT_OUTPUT_BYTES,
184273
+ maxAgentOutputBytes: 1048576,
184262
184274
  maxFindingsPerAgent: 10,
184263
- skipOptionalPhasesWhenTokenUsageUnknown: true
184275
+ skipOptionalPhasesWhenTokenUsageUnknown: false
184264
184276
  },
184265
184277
  audit: {
184266
184278
  enabled: true,
@@ -185576,6 +185588,7 @@ async function loadConfig(options = {}) {
185576
185588
  if (!options.ignoreConfig) {
185577
185589
  if (await exists(globalConfigPath)) {
185578
185590
  const globalConfig2 = await loadTomlConfigFile(globalConfigPath);
185591
+ validateExplicitReviewBudgetThresholds(globalConfig2, defaultConfig);
185579
185592
  const globalConfigWarnings = collectGlobalConfigWarnings(globalConfigPath, globalConfig2);
185580
185593
  const securitySensitiveWarnings = globalConfigWarnings.filter((warning) => warning.startsWith("security-sensitive unknown settings "));
185581
185594
  if (securitySensitiveWarnings.length > 0 && !options.allowUnknownConfig) {
@@ -185824,6 +185837,7 @@ async function loadProjectTsConfig(input) {
185824
185837
  });
185825
185838
  if (trustDecision.execute) {
185826
185839
  const userConfig = await loadUserConfig(canonicalPath, source);
185840
+ validateExplicitReviewBudgetThresholds(userConfig, input.baseConfig);
185827
185841
  const projectChangesOpenRouterRoute = projectConfigChangesOpenRouterRoute(userConfig, input.baseConfig);
185828
185842
  await assertProjectOpenRouterAuthorization({
185829
185843
  projectConfig: userConfig,
@@ -185917,6 +185931,23 @@ function deepMerge2(base, override) {
185917
185931
  }
185918
185932
  return result;
185919
185933
  }
185934
+ function validateExplicitReviewBudgetThresholds(config2, baseConfig) {
185935
+ const reviewBudget = readRecord(config2, "reviewBudget");
185936
+ if (!reviewBudget || !Object.prototype.hasOwnProperty.call(reviewBudget, "warnAgentOutputBytes")) {
185937
+ return;
185938
+ }
185939
+ const warnAgentOutputBytes = reviewBudget.warnAgentOutputBytes;
185940
+ const maxAgentOutputBytes = Object.prototype.hasOwnProperty.call(reviewBudget, "maxAgentOutputBytes") ? reviewBudget.maxAgentOutputBytes : readRecord(baseConfig, "reviewBudget")?.maxAgentOutputBytes;
185941
+ if (typeof warnAgentOutputBytes === "number" && typeof maxAgentOutputBytes === "number" && warnAgentOutputBytes >= maxAgentOutputBytes) {
185942
+ throw new Error("reviewBudget.warnAgentOutputBytes must be less than reviewBudget.maxAgentOutputBytes.");
185943
+ }
185944
+ }
185945
+ function readRecord(value, key) {
185946
+ if (!isRecord3(value))
185947
+ return;
185948
+ const nested = value[key];
185949
+ return isRecord3(nested) ? nested : undefined;
185950
+ }
185920
185951
  function isRecord3(value) {
185921
185952
  return typeof value === "object" && value !== null && !Array.isArray(value);
185922
185953
  }
@@ -189972,6 +190003,55 @@ var legacyClientNotificationMethods = new Set([
189972
190003
  CLIENT_METHODS.elicitation_complete
189973
190004
  ]);
189974
190005
 
190006
+ // src/core/modelExecutionIdentity.ts
190007
+ var MODEL_EXECUTION_IDENTITY_MAX_CHARS = 160;
190008
+ var MODEL_PROVIDER_ROUTES = new Set([
190009
+ "codex_default",
190010
+ "claude_default",
190011
+ "openrouter",
190012
+ "openai",
190013
+ "anthropic"
190014
+ ]);
190015
+ function createModelExecutionIdentity(input) {
190016
+ const requestedModel = sanitizeIdentityValue(input.requestedModel);
190017
+ const reportedProvider = sanitizeIdentityValue(input.reportedProvider);
190018
+ const reportedModel = sanitizeIdentityValue(input.reportedModel);
190019
+ const reportingStatus = reportedProvider !== undefined || reportedModel !== undefined ? "reported" : requestedModel !== undefined ? "requested_only" : "unknown";
190020
+ return {
190021
+ providerRoute: input.providerRoute,
190022
+ ...requestedModel ? { requestedModel } : {},
190023
+ ...reportedProvider ? { reportedProvider } : {},
190024
+ ...reportedModel ? { reportedModel } : {},
190025
+ reportingStatus
190026
+ };
190027
+ }
190028
+ function normalizeModelExecutionIdentity(value) {
190029
+ if (!isRecord6(value) || !isModelProviderRoute(value.providerRoute)) {
190030
+ return;
190031
+ }
190032
+ return createModelExecutionIdentity({
190033
+ providerRoute: value.providerRoute,
190034
+ requestedModel: value.requestedModel,
190035
+ reportedProvider: value.reportedProvider,
190036
+ reportedModel: value.reportedModel
190037
+ });
190038
+ }
190039
+ function sanitizeIdentityValue(value) {
190040
+ if (typeof value !== "string")
190041
+ return;
190042
+ const sanitized = sanitizeTextForDisplay(value, MODEL_EXECUTION_IDENTITY_MAX_CHARS);
190043
+ if (sanitized.includes(REDACTION) || /(?:https?|wss?):\/\//i.test(sanitized) || /\b(?:api[_-]?key|base[_-]?url|credential|secret|token|password)\b/i.test(sanitized) || /[{}=]/.test(sanitized)) {
190044
+ return;
190045
+ }
190046
+ return sanitized.length > 0 ? sanitized : undefined;
190047
+ }
190048
+ function isModelProviderRoute(value) {
190049
+ return typeof value === "string" && MODEL_PROVIDER_ROUTES.has(value);
190050
+ }
190051
+ function isRecord6(value) {
190052
+ return value !== null && typeof value === "object" && !Array.isArray(value);
190053
+ }
190054
+
189975
190055
  // src/core/tokenUsage.ts
189976
190056
  var TOKEN_USAGE_KEYS = [
189977
190057
  "totalTokens",
@@ -189982,7 +190062,7 @@ var TOKEN_USAGE_KEYS = [
189982
190062
  "cachedWriteTokens"
189983
190063
  ];
189984
190064
  function normalizeModelTokenUsage(usage) {
189985
- if (!isRecord6(usage))
190065
+ if (!isRecord7(usage))
189986
190066
  return;
189987
190067
  const normalized = {};
189988
190068
  for (const key of TOKEN_USAGE_KEYS) {
@@ -189995,7 +190075,7 @@ function normalizeModelTokenUsage(usage) {
189995
190075
  function isTokenCount(value) {
189996
190076
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
189997
190077
  }
189998
- function isRecord6(value) {
190078
+ function isRecord7(value) {
189999
190079
  return typeof value === "object" && value !== null && !Array.isArray(value);
190000
190080
  }
190001
190081
 
@@ -190048,7 +190128,19 @@ class ChildEnvPreflightError extends Error {
190048
190128
  this.name = "ChildEnvPreflightError";
190049
190129
  }
190050
190130
  }
190051
- function buildChildEnv(parentEnv, whitelist, explicit, options = {}) {
190131
+ function buildChildLaunchContext(parentEnv, whitelist, explicit, options) {
190132
+ const env = buildChildEnvironment(parentEnv, whitelist, explicit, options);
190133
+ const openRouterSelected = options.agent === "codex" && options.provider === CODEX_OPENROUTER_PROVIDER;
190134
+ const requestedModel = options.agent === "claude" ? env.ANTHROPIC_MODEL : readCodexRequestedModel(env.CODEX_CONFIG);
190135
+ return {
190136
+ env,
190137
+ executionIdentity: createModelExecutionIdentity({
190138
+ providerRoute: openRouterSelected ? "openrouter" : options.agent === "codex" ? "codex_default" : "claude_default",
190139
+ requestedModel
190140
+ })
190141
+ };
190142
+ }
190143
+ function buildChildEnvironment(parentEnv, whitelist, explicit, options = {}) {
190052
190144
  if (!parentEnv.PATH) {
190053
190145
  throw new Error("PATH is required to launch ACP child agents.");
190054
190146
  }
@@ -190095,6 +190187,19 @@ function buildChildEnv(parentEnv, whitelist, explicit, options = {}) {
190095
190187
  }
190096
190188
  return env;
190097
190189
  }
190190
+ function readCodexRequestedModel(value) {
190191
+ if (!value)
190192
+ return;
190193
+ try {
190194
+ const parsed = JSON.parse(value);
190195
+ if (!isPlainObject2(parsed) || typeof parsed.model !== "string") {
190196
+ return;
190197
+ }
190198
+ return parsed.model;
190199
+ } catch {
190200
+ return;
190201
+ }
190202
+ }
190098
190203
  function canCopyEnvValue(key, value, onCredentialPlaceholderDiscarded) {
190099
190204
  if (!isUnexpandedCredentialEnvValue(key, value))
190100
190205
  return true;
@@ -190235,6 +190340,45 @@ class BaseAcpAgentManager {
190235
190340
  }
190236
190341
  }
190237
190342
 
190343
+ // src/acp/ndJsonLineLimit.ts
190344
+ var JSON_STRING_MAX_ESCAPE_EXPANSION = 6;
190345
+ var ACP_NDJSON_ENVELOPE_BYTES = 2 * 1048576;
190346
+ var NEWLINE_BYTE = 10;
190347
+ var MAX_ACP_NDJSON_LINE_BYTES = MAX_AGENT_OUTPUT_BYTES * JSON_STRING_MAX_ESCAPE_EXPANSION + ACP_NDJSON_ENVELOPE_BYTES;
190348
+
190349
+ class AcpNdJsonLineLimitError extends Error {
190350
+ maxLineBytes;
190351
+ constructor(maxLineBytes) {
190352
+ super(`ACP NDJSON line exceeded ${maxLineBytes} bytes.`);
190353
+ this.maxLineBytes = maxLineBytes;
190354
+ this.name = "AcpNdJsonLineLimitError";
190355
+ }
190356
+ }
190357
+ function limitAcpNdJsonLineBytes(input, maxLineBytes = MAX_ACP_NDJSON_LINE_BYTES) {
190358
+ if (!Number.isSafeInteger(maxLineBytes) || maxLineBytes <= 0) {
190359
+ throw new RangeError("ACP NDJSON line limit must be a positive integer.");
190360
+ }
190361
+ let pendingLineBytes = 0;
190362
+ return input.pipeThrough(new TransformStream({
190363
+ transform(chunk, controller) {
190364
+ let start = 0;
190365
+ for (;; ) {
190366
+ const newlineIndex = chunk.indexOf(NEWLINE_BYTE, start);
190367
+ const end = newlineIndex === -1 ? chunk.byteLength : newlineIndex;
190368
+ pendingLineBytes += end - start;
190369
+ if (pendingLineBytes > maxLineBytes) {
190370
+ throw new AcpNdJsonLineLimitError(maxLineBytes);
190371
+ }
190372
+ if (newlineIndex === -1)
190373
+ break;
190374
+ pendingLineBytes = 0;
190375
+ start = newlineIndex + 1;
190376
+ }
190377
+ controller.enqueue(chunk);
190378
+ }
190379
+ }));
190380
+ }
190381
+
190238
190382
  // src/core/findingAdmission.ts
190239
190383
  import { createHash as createHash2 } from "node:crypto";
190240
190384
  var SAFETY_CATEGORIES = new Set([
@@ -190576,6 +190720,43 @@ var evidenceQualities = [
190576
190720
  ];
190577
190721
  var MAX_EVIDENCE_REFS2 = 20;
190578
190722
  var MAX_EVIDENCE_LINE2 = 1e6;
190723
+ var STRICT_ROOT_KEYS = new Set([
190724
+ "summary",
190725
+ "findings",
190726
+ "testsToAdd",
190727
+ "residualRisks",
190728
+ "openQuestions",
190729
+ "cisaSecureByDesign"
190730
+ ]);
190731
+ var STRICT_FINDING_KEYS = new Set([
190732
+ "severity",
190733
+ "category",
190734
+ "title",
190735
+ "evidence",
190736
+ "recommendation",
190737
+ "disposition",
190738
+ "changeRelation",
190739
+ "evidenceQuality",
190740
+ "evidenceRefs",
190741
+ "files",
190742
+ "confidence",
190743
+ "cisaMapping"
190744
+ ]);
190745
+ var STRICT_FILE_KEYS = new Set(["path", "lineStart", "lineEnd"]);
190746
+ var STRICT_EVIDENCE_REF_KEYS = new Set([
190747
+ "kind",
190748
+ "path",
190749
+ "lineStart",
190750
+ "lineEnd",
190751
+ "label"
190752
+ ]);
190753
+ var STRICT_CISA_KEYS = new Set([
190754
+ "customerSecurityOutcomes",
190755
+ "secureByDefault",
190756
+ "transparencyAndAccountability",
190757
+ "governance",
190758
+ "notes"
190759
+ ]);
190579
190760
  function normalizeAgentOutput(agent, role, rawText) {
190580
190761
  const json2 = extractFirstJsonObject(rawText);
190581
190762
  if (!json2)
@@ -190609,6 +190790,19 @@ function normalizeAgentOutput(agent, role, rawText) {
190609
190790
  return parseFailureOpinion(agent, role, `Structured parse failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
190610
190791
  }
190611
190792
  }
190793
+ function parseAgentOutputStrict(agent, role, rawText) {
190794
+ const json2 = extractFirstJsonObject(rawText);
190795
+ if (!json2)
190796
+ return;
190797
+ try {
190798
+ const parsed = JSON.parse(json2);
190799
+ if (!isStrictAgentOpinion(parsed))
190800
+ return;
190801
+ return normalizeAgentOutput(agent, role, json2);
190802
+ } catch {
190803
+ return;
190804
+ }
190805
+ }
190612
190806
  function extractFirstJsonObject(text) {
190613
190807
  const start = text.indexOf("{");
190614
190808
  if (start === -1)
@@ -190666,7 +190860,7 @@ function isSeverity(value) {
190666
190860
  return typeof value === "string" && severities.includes(value);
190667
190861
  }
190668
190862
  function normalizeCisaSecureByDesign(value) {
190669
- if (!isRecord7(value))
190863
+ if (!isRecord8(value))
190670
190864
  return;
190671
190865
  const normalized = {};
190672
190866
  const customerSecurityOutcomes = normalizeGateStatus(value.customerSecurityOutcomes);
@@ -190706,6 +190900,78 @@ function isChangeRelation(value) {
190706
190900
  function isEvidenceQuality(value) {
190707
190901
  return typeof value === "string" && evidenceQualities.includes(value);
190708
190902
  }
190903
+ function isStrictAgentOpinion(value) {
190904
+ if (!isRecord8(value) || !hasOnlyKeys(value, STRICT_ROOT_KEYS))
190905
+ return false;
190906
+ if (typeof value.summary !== "string")
190907
+ return false;
190908
+ if (!Array.isArray(value.findings) || !value.findings.every(isStrictFinding) || !isStringArray(value.testsToAdd) || !isStringArray(value.residualRisks) || !isStringArray(value.openQuestions)) {
190909
+ return false;
190910
+ }
190911
+ return value.cisaSecureByDesign === undefined || isStrictCisaSecureByDesign(value.cisaSecureByDesign);
190912
+ }
190913
+ function isStrictFinding(value) {
190914
+ if (!isRecord8(value) || !hasOnlyKeys(value, STRICT_FINDING_KEYS)) {
190915
+ return false;
190916
+ }
190917
+ if (!isSeverity(value.severity) || !isCategory(value.category) || !isNonEmptyString(value.title) || !isNonEmptyString(value.evidence) || !isNonEmptyString(value.recommendation) || !isConfidence(value.confidence)) {
190918
+ return false;
190919
+ }
190920
+ 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)) {
190921
+ return false;
190922
+ }
190923
+ return true;
190924
+ }
190925
+ function isStrictFindingFiles(value) {
190926
+ return Array.isArray(value) && value.every((item) => isRecord8(item) && hasOnlyKeys(item, STRICT_FILE_KEYS) && isNonEmptyString(item.path) && isOptionalLineNumber(item.lineStart) && isOptionalLineNumber(item.lineEnd));
190927
+ }
190928
+ function isStrictEvidenceRefs(value) {
190929
+ return Array.isArray(value) && value.length <= MAX_EVIDENCE_REFS2 && value.every((item) => {
190930
+ if (!isRecord8(item) || !hasOnlyKeys(item, STRICT_EVIDENCE_REF_KEYS)) {
190931
+ return false;
190932
+ }
190933
+ if (item.kind !== "file" && item.kind !== "diff_hunk" && item.kind !== "plan_clause") {
190934
+ return false;
190935
+ }
190936
+ if (!isOptionalNonEmptyString(item.path) || !isOptionalNonEmptyString(item.label) || !isOptionalLineNumber(item.lineStart) || !isOptionalLineNumber(item.lineEnd)) {
190937
+ return false;
190938
+ }
190939
+ if (item.kind === "file" || item.kind === "diff_hunk") {
190940
+ return item.path !== undefined && item.lineStart !== undefined;
190941
+ }
190942
+ return item.label !== undefined || item.lineStart !== undefined;
190943
+ });
190944
+ }
190945
+ function isStrictCisaSecureByDesign(value) {
190946
+ if (!isRecord8(value) || !hasOnlyKeys(value, STRICT_CISA_KEYS))
190947
+ return false;
190948
+ for (const key of [
190949
+ "customerSecurityOutcomes",
190950
+ "secureByDefault",
190951
+ "transparencyAndAccountability",
190952
+ "governance"
190953
+ ]) {
190954
+ if (value[key] !== undefined && !normalizeGateStatus(value[key])) {
190955
+ return false;
190956
+ }
190957
+ }
190958
+ return value.notes === undefined || isStringArray(value.notes);
190959
+ }
190960
+ function hasOnlyKeys(value, allowed) {
190961
+ return Object.keys(value).every((key) => allowed.has(key));
190962
+ }
190963
+ function isStringArray(value) {
190964
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
190965
+ }
190966
+ function isNonEmptyString(value) {
190967
+ return typeof value === "string" && value.trim().length > 0;
190968
+ }
190969
+ function isOptionalNonEmptyString(value) {
190970
+ return value === undefined || isNonEmptyString(value);
190971
+ }
190972
+ function isOptionalLineNumber(value) {
190973
+ return value === undefined || normalizeLineNumber(value) !== undefined;
190974
+ }
190709
190975
  function normalizeStringList(value) {
190710
190976
  return Array.isArray(value) ? value.filter((item) => typeof item === "string").map((item) => sanitizeText(item)) : [];
190711
190977
  }
@@ -190716,7 +190982,7 @@ function normalizeFindingFiles(value) {
190716
190982
  if (!Array.isArray(value))
190717
190983
  return;
190718
190984
  const files = value.flatMap((item) => {
190719
- if (!isRecord7(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
190985
+ if (!isRecord8(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
190720
190986
  return [];
190721
190987
  }
190722
190988
  const file2 = {
@@ -190736,7 +191002,7 @@ function normalizeEvidenceRefs2(value) {
190736
191002
  if (!Array.isArray(value))
190737
191003
  return;
190738
191004
  const references = value.slice(0, MAX_EVIDENCE_REFS2).flatMap((item) => {
190739
- if (!isRecord7(item) || item.kind !== "file" && item.kind !== "diff_hunk" && item.kind !== "plan_clause") {
191005
+ if (!isRecord8(item) || item.kind !== "file" && item.kind !== "diff_hunk" && item.kind !== "plan_clause") {
190740
191006
  return [];
190741
191007
  }
190742
191008
  const reference = { kind: item.kind };
@@ -190759,7 +191025,7 @@ function normalizeEvidenceRefs2(value) {
190759
191025
  function normalizeLineNumber(value) {
190760
191026
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= MAX_EVIDENCE_LINE2 ? value : undefined;
190761
191027
  }
190762
- function isRecord7(value) {
191028
+ function isRecord8(value) {
190763
191029
  return typeof value === "object" && value !== null && !Array.isArray(value);
190764
191030
  }
190765
191031
 
@@ -190785,9 +191051,9 @@ class SubprocessAcpAgentManager extends BaseAcpAgentManager {
190785
191051
  };
190786
191052
  }
190787
191053
  const provider = input.agent === "codex" ? this.config.agents.codex.provider : undefined;
190788
- let env;
191054
+ let launchContext;
190789
191055
  try {
190790
- env = buildChildEnv(this.parentEnv, agentConfig.auth.envWhitelist, agentConfig.env, {
191056
+ launchContext = buildChildLaunchContext(this.parentEnv, agentConfig.auth.envWhitelist, agentConfig.env, {
190791
191057
  agent: input.agent,
190792
191058
  model: agentConfig.model,
190793
191059
  provider,
@@ -190804,7 +191070,7 @@ class SubprocessAcpAgentManager extends BaseAcpAgentManager {
190804
191070
  };
190805
191071
  }
190806
191072
  try {
190807
- return await runSubprocessAgent(input.agent, agentConfig, input, env);
191073
+ return await runSubprocessAgent(input.agent, agentConfig, input, launchContext.env, launchContext.executionIdentity);
190808
191074
  } catch (error51) {
190809
191075
  return {
190810
191076
  agent: input.agent,
@@ -190817,7 +191083,7 @@ class SubprocessAcpAgentManager extends BaseAcpAgentManager {
190817
191083
  }
190818
191084
  }
190819
191085
  }
190820
- async function runSubprocessAgent(agent, agentConfig, input, env) {
191086
+ async function runSubprocessAgent(agent, agentConfig, input, env, launchExecutionIdentity) {
190821
191087
  const startedAt = new Date().toISOString();
190822
191088
  const effectiveTimeoutMs = resolveEffectiveTimeoutMs(input);
190823
191089
  if (effectiveTimeoutMs <= 0) {
@@ -190842,11 +191108,15 @@ async function runSubprocessAgent(agent, agentConfig, input, env) {
190842
191108
  let stdout = "";
190843
191109
  let stderr3 = "";
190844
191110
  let settled = false;
191111
+ let spawned = false;
190845
191112
  let startedWrite;
190846
191113
  child.once("spawn", () => {
190847
191114
  if (settled)
190848
191115
  return;
190849
- startedWrite = Promise.resolve().then(() => input.onStarted?.()).catch(() => {
191116
+ spawned = true;
191117
+ startedWrite = Promise.resolve().then(async () => {
191118
+ await input.onStarted?.(launchExecutionIdentity);
191119
+ }).catch(() => {
190850
191120
  return;
190851
191121
  });
190852
191122
  });
@@ -190856,7 +191126,8 @@ async function runSubprocessAgent(agent, agentConfig, input, env) {
190856
191126
  return;
190857
191127
  settled = true;
190858
191128
  clearTimeout(timeout);
190859
- (startedWrite ?? Promise.resolve()).then(() => resolveResult(result));
191129
+ const finalResult = spawned && result.executionIdentity === undefined ? { ...result, executionIdentity: launchExecutionIdentity } : result;
191130
+ (startedWrite ?? Promise.resolve()).then(() => resolveResult(finalResult));
190860
191131
  };
190861
191132
  const timeout = setTimeout(() => {
190862
191133
  abortController.abort(new Error("Kyoso agent timeout"));
@@ -190889,7 +191160,17 @@ async function runSubprocessAgent(agent, agentConfig, input, env) {
190889
191160
  error: failure
190890
191161
  });
190891
191162
  });
190892
- runAcpClientWorkflow(child, input, abortController, resolveEffortConfigOption(agent, agentConfig.effort)).then(({ rawText, warnings, usage, outputBytes, stopReason }) => {
191163
+ runAcpClientWorkflow(child, input, abortController, resolveEffortConfigOption(agent, agentConfig.effort), launchExecutionIdentity).then(({
191164
+ rawText,
191165
+ warnings,
191166
+ usage,
191167
+ messageBytes,
191168
+ thoughtBytes,
191169
+ outputBytes,
191170
+ outputWarningTriggered,
191171
+ stopReason,
191172
+ executionIdentity
191173
+ }) => {
190893
191174
  stdout = rawText;
190894
191175
  const completed = stopReason === "end_turn";
190895
191176
  resolveOnce({
@@ -190900,8 +191181,12 @@ async function runSubprocessAgent(agent, agentConfig, input, env) {
190900
191181
  normalized: normalizeAgentOutput(agent, input.role, rawText),
190901
191182
  startedAt,
190902
191183
  completedAt: new Date().toISOString(),
191184
+ messageBytes,
191185
+ thoughtBytes,
190903
191186
  outputBytes,
191187
+ outputWarningTriggered,
190904
191188
  stopReason,
191189
+ executionIdentity,
190905
191190
  ...usage ? { usage } : {},
190906
191191
  ...warnings.length > 0 ? { warnings } : {},
190907
191192
  ...completed ? {} : {
@@ -190915,18 +191200,40 @@ async function runSubprocessAgent(agent, agentConfig, input, env) {
190915
191200
  const outputLimitError = findOutputLimitError(error51, abortController);
190916
191201
  if (outputLimitError) {
190917
191202
  stdout = outputLimitError.rawText;
191203
+ const normalized = parseAgentOutputStrict(agent, input.role, stdout);
190918
191204
  resolveOnce({
190919
191205
  agent,
190920
191206
  role: input.role,
190921
191207
  status: "failed",
190922
191208
  rawText: stdout,
191209
+ ...normalized ? { normalized, salvaged: true } : {},
191210
+ messageBytes: outputLimitError.messageBytes,
191211
+ thoughtBytes: outputLimitError.thoughtBytes,
190923
191212
  outputBytes: outputLimitError.outputBytes,
191213
+ outputWarningTriggered: outputLimitError.outputWarningTriggered,
190924
191214
  stopReason: "cancelled",
190925
191215
  startedAt,
190926
191216
  completedAt: new Date().toISOString(),
190927
191217
  error: {
190928
191218
  code: "AGENT_OUTPUT_LIMIT",
190929
- message: `Agent output exceeded ${outputLimitError.maxOutputBytes} bytes and was cancelled.`
191219
+ 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.`
191220
+ }
191221
+ });
191222
+ return;
191223
+ }
191224
+ if (error51 instanceof AcpNdJsonLineLimitError) {
191225
+ abortController.abort(error51);
191226
+ resolveOnce({
191227
+ agent,
191228
+ role: input.role,
191229
+ status: "failed",
191230
+ rawText: stdout,
191231
+ stopReason: "cancelled",
191232
+ startedAt,
191233
+ completedAt: new Date().toISOString(),
191234
+ error: {
191235
+ code: "AGENT_PROTOCOL_LIMIT",
191236
+ message: `Agent emitted an ACP NDJSON line above the ${error51.maxLineBytes}-byte transport limit and was cancelled.`
190930
191237
  }
190931
191238
  });
190932
191239
  return;
@@ -190963,13 +191270,13 @@ async function runSubprocessAgent(agent, agentConfig, input, env) {
190963
191270
  });
190964
191271
  });
190965
191272
  }
190966
- async function runAcpClientWorkflow(child, input, abortController, configOption) {
191273
+ async function runAcpClientWorkflow(child, input, abortController, configOption, launchExecutionIdentity) {
190967
191274
  if (!child.stdin || !child.stdout) {
190968
191275
  throw new Error("Agent process did not expose stdio streams.");
190969
191276
  }
190970
191277
  const output = Writable.toWeb(child.stdin);
190971
191278
  const inputStream = Readable.toWeb(child.stdout);
190972
- const stream2 = ndJsonStream(output, inputStream);
191279
+ const stream2 = ndJsonStream(output, limitAcpNdJsonLineBytes(inputStream));
190973
191280
  const app = client({ name: "kyoso" }).onRequest(methods.client.session.requestPermission, () => ({
190974
191281
  outcome: { outcome: "cancelled" }
190975
191282
  })).onRequest(methods.client.fs.readTextFile, async (ctx) => ({
@@ -191030,7 +191337,10 @@ async function runAcpClientWorkflow(child, input, abortController, configOption)
191030
191337
  return;
191031
191338
  });
191032
191339
  let rawText = "";
191340
+ let messageBytes = 0;
191341
+ let thoughtBytes = 0;
191033
191342
  let outputBytes = 0;
191343
+ let outputWarningTriggered = false;
191034
191344
  for (;; ) {
191035
191345
  const message = await session.nextUpdate();
191036
191346
  if (message.kind === "stop") {
@@ -191039,8 +191349,12 @@ async function runAcpClientWorkflow(child, input, abortController, configOption)
191039
191349
  rawText,
191040
191350
  warnings,
191041
191351
  ...usage ? { usage } : {},
191352
+ messageBytes,
191353
+ thoughtBytes,
191042
191354
  outputBytes,
191043
- stopReason: message.stopReason
191355
+ outputWarningTriggered,
191356
+ stopReason: message.stopReason,
191357
+ executionIdentity: withReportedExecutionIdentity(launchExecutionIdentity, message.response._meta)
191044
191358
  };
191045
191359
  }
191046
191360
  const update = message.update;
@@ -191048,35 +191362,60 @@ async function runAcpClientWorkflow(child, input, abortController, configOption)
191048
191362
  continue;
191049
191363
  }
191050
191364
  const chunkBytes = Buffer.byteLength(update.content.text, "utf8");
191051
- const nextOutputBytes = outputBytes + chunkBytes;
191365
+ const isMessage = update.sessionUpdate === "agent_message_chunk";
191366
+ const nextMessageBytes = messageBytes + (isMessage ? chunkBytes : 0);
191367
+ const nextThoughtBytes = thoughtBytes + (isMessage ? 0 : chunkBytes);
191368
+ const nextOutputBytes = nextMessageBytes + nextThoughtBytes;
191369
+ const nextOutputWarningTriggered = outputWarningTriggered || input.warnOutputBytes !== undefined && nextOutputBytes >= input.warnOutputBytes;
191052
191370
  if (input.maxOutputBytes !== undefined && nextOutputBytes > input.maxOutputBytes) {
191371
+ const retainedRawText = isMessage ? `${rawText}${utf8Prefix(update.content.text, input.maxOutputBytes - outputBytes)}` : rawText;
191053
191372
  await ctx.notify(methods.agent.session.cancel, {
191054
191373
  sessionId: session.sessionId
191055
191374
  }).catch(() => {
191056
191375
  return;
191057
191376
  });
191058
- const error51 = new AgentOutputLimitError(rawText, nextOutputBytes, input.maxOutputBytes);
191377
+ const error51 = new AgentOutputLimitError(retainedRawText, nextMessageBytes, nextThoughtBytes, nextOutputBytes, input.maxOutputBytes, nextOutputWarningTriggered);
191059
191378
  abortController.abort(error51);
191060
191379
  throw error51;
191061
191380
  }
191062
- if (update.sessionUpdate === "agent_message_chunk") {
191381
+ if (isMessage) {
191063
191382
  rawText += update.content.text;
191064
191383
  }
191384
+ messageBytes = nextMessageBytes;
191385
+ thoughtBytes = nextThoughtBytes;
191065
191386
  outputBytes = nextOutputBytes;
191387
+ outputWarningTriggered = nextOutputWarningTriggered;
191066
191388
  }
191067
191389
  });
191068
191390
  });
191069
191391
  }
191392
+ function utf8Prefix(input, maxBytes) {
191393
+ const encoded = new TextEncoder().encode(input);
191394
+ const budget = Math.max(0, Math.min(maxBytes, encoded.byteLength));
191395
+ const decoder = new TextDecoder("utf-8", { fatal: true });
191396
+ for (let end = budget;end > 0; end -= 1) {
191397
+ try {
191398
+ return decoder.decode(encoded.subarray(0, end));
191399
+ } catch {}
191400
+ }
191401
+ return "";
191402
+ }
191070
191403
 
191071
191404
  class AgentOutputLimitError extends Error {
191072
191405
  rawText;
191406
+ messageBytes;
191407
+ thoughtBytes;
191073
191408
  outputBytes;
191074
191409
  maxOutputBytes;
191075
- constructor(rawText, outputBytes, maxOutputBytes) {
191410
+ outputWarningTriggered;
191411
+ constructor(rawText, messageBytes, thoughtBytes, outputBytes, maxOutputBytes, outputWarningTriggered) {
191076
191412
  super(`Agent output exceeded ${maxOutputBytes} bytes.`);
191077
191413
  this.rawText = rawText;
191414
+ this.messageBytes = messageBytes;
191415
+ this.thoughtBytes = thoughtBytes;
191078
191416
  this.outputBytes = outputBytes;
191079
191417
  this.maxOutputBytes = maxOutputBytes;
191418
+ this.outputWarningTriggered = outputWarningTriggered;
191080
191419
  this.name = "AgentOutputLimitError";
191081
191420
  }
191082
191421
  }
@@ -191093,6 +191432,18 @@ function resolveEffectiveTimeoutMs(input) {
191093
191432
  function normalizeUsage(usage) {
191094
191433
  return normalizeModelTokenUsage(usage);
191095
191434
  }
191435
+ function withReportedExecutionIdentity(identity, metadata) {
191436
+ const record2 = isRecord9(metadata) ? metadata : {};
191437
+ return createModelExecutionIdentity({
191438
+ providerRoute: identity.providerRoute,
191439
+ requestedModel: identity.requestedModel,
191440
+ reportedProvider: record2.provider,
191441
+ reportedModel: record2.model
191442
+ });
191443
+ }
191444
+ function isRecord9(value) {
191445
+ return value !== null && typeof value === "object" && !Array.isArray(value);
191446
+ }
191096
191447
  function resolveEffortConfigOption(agent, effort) {
191097
191448
  if (!effort)
191098
191449
  return;
@@ -191402,6 +191753,9 @@ function buildAgentPrompt(tool, request, agent, role, policy = {}) {
191402
191753
  "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.",
191403
191754
  "Put insufficiently supported hypotheses in openQuestions instead of findings.",
191404
191755
  "Do not create formal findings for style, formatting, generic hardening, unrelated pre-existing issues, duplicate tests, implementation-detail tests, or exhaustive boundary matrices.",
191756
+ ...policy.maxFindingsTarget === undefined ? [] : [
191757
+ `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.`
191758
+ ],
191405
191759
  "Recommend only specific regression scenarios tied to changed behavior, with at most three testsToAdd entries.",
191406
191760
  "Critical and High safety issues must still be reported when they match a non-goal.",
191407
191761
  "Write each finding title in concise English, regardless of the language used elsewhere. Titles are compared across agents for deduplication.",
@@ -192336,6 +192690,12 @@ function sanitizeForAudit(value, options = {}) {
192336
192690
  if (typeof value === "object" && value !== null) {
192337
192691
  const result = {};
192338
192692
  for (const [key, nested] of Object.entries(value)) {
192693
+ if (key === "executionIdentity") {
192694
+ const identity = normalizeModelExecutionIdentity(nested);
192695
+ if (identity)
192696
+ result[key] = identity;
192697
+ continue;
192698
+ }
192339
192699
  if (/raw|content|env|credential|token|secret|password/i.test(key) && !(options.includeRawAgentOutput && key === "rawText") && !isUsageMetadata(key, nested)) {
192340
192700
  continue;
192341
192701
  }
@@ -192616,7 +192976,7 @@ function validateReviewContract(request) {
192616
192976
  const contract = request.reviewContract;
192617
192977
  if (contract === undefined)
192618
192978
  return;
192619
- if (!isRecord8(contract)) {
192979
+ if (!isRecord10(contract)) {
192620
192980
  throw new KyosoRequestError("reviewContract must be an object", "VALIDATION_ERROR");
192621
192981
  }
192622
192982
  const allowedKeys = new Set(["focus", "nonGoals", "acceptedRisks"]);
@@ -192633,7 +192993,7 @@ function validateReviewContract(request) {
192633
192993
  throw new KyosoRequestError("reviewContract.nonGoals must contain at most 20 non-empty strings of 500 characters or fewer", "VALIDATION_ERROR");
192634
192994
  }
192635
192995
  const acceptedRisks = contract.acceptedRisks;
192636
- if (acceptedRisks !== undefined && (!Array.isArray(acceptedRisks) || acceptedRisks.length > 20 || acceptedRisks.some((risk) => !isRecord8(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))) {
192996
+ if (acceptedRisks !== undefined && (!Array.isArray(acceptedRisks) || acceptedRisks.length > 20 || acceptedRisks.some((risk) => !isRecord10(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))) {
192637
192997
  throw new KyosoRequestError("reviewContract.acceptedRisks must contain valid finding fingerprints and bounded rationales", "VALIDATION_ERROR");
192638
192998
  }
192639
192999
  }
@@ -192645,13 +193005,13 @@ function validateSelectedFiles(request) {
192645
193005
  throw new KyosoRequestError("selectedFiles must be an array", "VALIDATION_ERROR");
192646
193006
  }
192647
193007
  for (const file2 of selectedFiles) {
192648
- if (!isRecord8(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") {
193008
+ if (!isRecord10(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") {
192649
193009
  throw new KyosoRequestError("selectedFiles entries require string path/content and valid optional metadata", "VALIDATION_ERROR");
192650
193010
  }
192651
193011
  normalizeRelativePath(file2.path);
192652
193012
  }
192653
193013
  }
192654
- function isRecord8(value) {
193014
+ function isRecord10(value) {
192655
193015
  return typeof value === "object" && value !== null && !Array.isArray(value);
192656
193016
  }
192657
193017
 
@@ -192664,7 +193024,7 @@ function renderMarkdownResult(tool, result, options = {}) {
192664
193024
  `**Mode:** ${tool}`,
192665
193025
  `**Completion:** ${formatCompletion(result)}`,
192666
193026
  `**Request fingerprint:** ${shortFingerprint(result.requestFingerprint)}`,
192667
- `**Agents:** ${result.agentOpinions.map((opinion) => `${title(opinion.agent)} ${opinion.status}`).join(", ")}`,
193027
+ `**Agents:** ${result.agentOpinions.map((opinion) => `${title(opinion.agent)} ${opinion.status}${opinion.salvaged ? " (salvaged)" : ""}`).join(", ")}`,
192668
193028
  `**Review mode:** ${formatReviewMode(result)}`,
192669
193029
  ...result.verificationMode ? [
192670
193030
  `**Verification mode:** ${formatVerificationMode(result.verificationMode)}`
@@ -192715,7 +193075,7 @@ function renderMarkdownResult(tool, result, options = {}) {
192715
193075
  }
192716
193076
  lines.push("", "## Agent Opinions", "");
192717
193077
  for (const opinion of result.agentOpinions) {
192718
- lines.push(`### ${title(opinion.agent)}`, "", `${opinion.summary} (${opinion.status})`, "");
193078
+ lines.push(`### ${title(opinion.agent)}`, "", `${opinion.summary} (${opinion.status}${opinion.salvaged ? ", salvaged" : ""})`, "");
192719
193079
  }
192720
193080
  lines.push("", "## Disagreements", "");
192721
193081
  if (result.reviewMode === "single_agent") {
@@ -192746,17 +193106,50 @@ function defaultSummaryText(result) {
192746
193106
  function formatExecutionBudget(result) {
192747
193107
  const budget = result.executionBudget;
192748
193108
  const agentOutputs = Object.entries(budget.agentOutputBytes);
192749
- const outputLines = agentOutputs.length > 0 ? agentOutputs.map(([agent, bytes]) => `- ${title(agent)}: ${bytes} bytes`) : ["- None reported."];
193109
+ const byteBreakdowns = new Map;
193110
+ for (const call of result.audit.modelCalls) {
193111
+ if (call.status !== "completed" || !call.agent)
193112
+ continue;
193113
+ const current = byteBreakdowns.get(call.agent) ?? {
193114
+ messageBytes: 0,
193115
+ thoughtBytes: 0
193116
+ };
193117
+ current.messageBytes += call.messageBytes ?? 0;
193118
+ current.thoughtBytes += call.thoughtBytes ?? 0;
193119
+ byteBreakdowns.set(call.agent, current);
193120
+ }
193121
+ const outputLines = agentOutputs.length > 0 ? agentOutputs.map(([agent, bytes]) => {
193122
+ const breakdown = byteBreakdowns.get(agent);
193123
+ return `- ${title(agent)}: ${bytes} bytes${breakdown ? ` (message: ${breakdown.messageBytes}, thought: ${breakdown.thoughtBytes})` : ""}`;
193124
+ }) : ["- None reported."];
193125
+ const identityLines = result.audit.modelCalls.filter((call) => call.status === "completed").map((call) => {
193126
+ const label = [call.kind, call.agent].filter(Boolean).join("/");
193127
+ const identity = call.executionIdentity;
193128
+ if (!identity)
193129
+ return `- ${label}: identity=unknown`;
193130
+ const reportedIdentity = [
193131
+ identity.reportedProvider ? `reportedProvider=${escapeMarkdownText(identity.reportedProvider)}` : undefined,
193132
+ identity.reportedModel ? `reportedModel=${escapeMarkdownText(identity.reportedModel)}` : undefined
193133
+ ].filter((value) => value !== undefined);
193134
+ return `- ${label}: route=${identity.providerRoute}, requested=${escapeMarkdownText(identity.requestedModel ?? "unknown")}${reportedIdentity.length > 0 ? `, ${reportedIdentity.join(", ")}` : ""}, reporting=${identity.reportingStatus}`;
193135
+ });
192750
193136
  const totalTokens = budget.tokenUsage.totals.totalTokens;
193137
+ const plan = budget.modelCallPlan;
193138
+ const outputLimits = budget.effectiveWarnAgentOutputBytes === undefined ? `${budget.maxAgentOutputBytes} bytes hard (soft warning disabled)` : `${budget.effectiveWarnAgentOutputBytes} bytes soft / ${budget.maxAgentOutputBytes} bytes hard`;
192751
193139
  return [
192752
193140
  "",
192753
193141
  "## Execution Budget",
192754
193142
  "",
192755
- `- Model calls: ${budget.modelCalls.planned} planned / ${budget.modelCalls.consumed} consumed / ${budget.modelCalls.skipped} skipped`,
193143
+ `- Model calls: ${budget.modelCalls.planned} planned / ${budget.modelCalls.consumed} consumed / ${budget.modelCalls.skipped} skipped / ${budget.maxModelCalls} ceiling`,
193144
+ `- Potential calls: ${plan.potentialTotalCalls} total (${plan.requiredPrimaryCalls} primary, ${plan.potentialVerifierCalls} verifier, ${plan.potentialJudgeCalls} judge)`,
192756
193145
  `- Wall time: ${budget.wallTime.consumedMs}ms consumed / ${budget.wallTime.limitMs}ms limit`,
192757
193146
  `- Token usage: ${budget.tokenUsage.status} (${budget.tokenUsage.reportedCalls} reported, ${budget.tokenUsage.unknownCalls} unknown${totalTokens === undefined ? "" : `, ${totalTokens} total`})`,
193147
+ `- Agent output limits: ${outputLimits}`,
193148
+ `- Findings target: ${budget.maxFindingsPerAgent} per primary agent (soft)`,
192758
193149
  "- Agent output:",
192759
- ...outputLines
193150
+ ...outputLines,
193151
+ "- Model identities:",
193152
+ ...identityLines.length > 0 ? identityLines : ["- None completed."]
192760
193153
  ];
192761
193154
  }
192762
193155
  function formatCompletion(result) {
@@ -192879,7 +193272,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
192879
193272
  const parsed = JSON.parse(json2);
192880
193273
  const summaryText = typeof parsed.summaryText === "string" && parsed.summaryText.trim().length > 0 ? sanitizeText(parsed.summaryText) : fallbackSummaryText;
192881
193274
  const disagreementComments = Array.isArray(parsed.disagreementComments) ? parsed.disagreementComments.flatMap((item) => {
192882
- if (!isRecord9(item) || typeof item.topic !== "string" || typeof item.judgeComment !== "string") {
193275
+ if (!isRecord11(item) || typeof item.topic !== "string" || typeof item.judgeComment !== "string") {
192883
193276
  return [];
192884
193277
  }
192885
193278
  return [
@@ -192895,7 +193288,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
192895
193288
  return { summaryText, disagreementComments, analysis };
192896
193289
  }
192897
193290
  function parseAnalysis(value) {
192898
- if (!isRecord9(value))
193291
+ if (!isRecord11(value))
192899
193292
  return;
192900
193293
  if (!Array.isArray(value.blindSpots) || !Array.isArray(value.contradictions) || !Array.isArray(value.partialCoverage)) {
192901
193294
  return;
@@ -192903,7 +193296,7 @@ function parseAnalysis(value) {
192903
193296
  return {
192904
193297
  blindSpots: value.blindSpots.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => typeof item === "string" ? [sanitizeAnalysisText(item)] : []),
192905
193298
  contradictions: value.contradictions.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
192906
- if (!isRecord9(item) || typeof item.topic !== "string" || typeof item.detail !== "string") {
193299
+ if (!isRecord11(item) || typeof item.topic !== "string" || typeof item.detail !== "string") {
192907
193300
  return [];
192908
193301
  }
192909
193302
  return [
@@ -192914,7 +193307,7 @@ function parseAnalysis(value) {
192914
193307
  ];
192915
193308
  }),
192916
193309
  partialCoverage: value.partialCoverage.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
192917
- if (!isRecord9(item) || typeof item.note !== "string")
193310
+ if (!isRecord11(item) || typeof item.note !== "string")
192918
193311
  return [];
192919
193312
  const findingId = typeof item.findingId === "string" ? sanitizeAnalysisText(item.findingId) : undefined;
192920
193313
  return [
@@ -192961,15 +193354,20 @@ function extractFirstJsonObject2(text) {
192961
193354
  }
192962
193355
  return;
192963
193356
  }
192964
- function isRecord9(value) {
193357
+ function isRecord11(value) {
192965
193358
  return typeof value === "object" && value !== null && !Array.isArray(value);
192966
193359
  }
192967
193360
 
192968
193361
  // src/judge/anthropic.ts
193362
+ var DEFAULT_ANTHROPIC_JUDGE_MODEL = "claude-haiku-4-5";
193363
+ function resolveAnthropicJudgeModel(env) {
193364
+ return env.KYOSO_ANTHROPIC_JUDGE_MODEL ?? DEFAULT_ANTHROPIC_JUDGE_MODEL;
193365
+ }
192969
193366
  async function runAnthropicJudge(input, timeoutMs) {
192970
193367
  const apiKey = input.env.ANTHROPIC_API_KEY;
192971
193368
  if (!apiKey)
192972
193369
  throw new Error("ANTHROPIC_API_KEY is not configured.");
193370
+ const requestedModel = resolveAnthropicJudgeModel(input.env);
192973
193371
  const response = await fetchWithTimeout(`${input.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com"}/v1/messages`, {
192974
193372
  method: "POST",
192975
193373
  headers: {
@@ -192978,7 +193376,7 @@ async function runAnthropicJudge(input, timeoutMs) {
192978
193376
  "content-type": "application/json"
192979
193377
  },
192980
193378
  body: JSON.stringify({
192981
- model: input.env.KYOSO_ANTHROPIC_JUDGE_MODEL ?? "claude-haiku-4-5",
193379
+ model: requestedModel,
192982
193380
  max_tokens: JUDGE_MAX_OUTPUT_TOKENS,
192983
193381
  temperature: 0,
192984
193382
  messages: [
@@ -192996,8 +193394,11 @@ async function runAnthropicJudge(input, timeoutMs) {
192996
193394
  if (!content)
192997
193395
  throw new Error("Anthropic judge response did not include text content.");
192998
193396
  const usage = normalizeUsage2(payload.usage);
193397
+ const reportedModel = typeof payload.model === "string" ? payload.model : undefined;
192999
193398
  return {
193000
193399
  output: parseJudgeOutput(content, input.summaryText),
193400
+ requestedModel,
193401
+ ...reportedModel ? { reportedModel } : {},
193001
193402
  ...usage ? { usage } : {}
193002
193403
  };
193003
193404
  }
@@ -193034,10 +193435,15 @@ function runDeterministicJudge(result, summaryText) {
193034
193435
  }
193035
193436
 
193036
193437
  // src/judge/openai.ts
193438
+ var DEFAULT_OPENAI_JUDGE_MODEL = "gpt-5.4-mini";
193439
+ function resolveOpenAiJudgeModel(env) {
193440
+ return env.KYOSO_OPENAI_JUDGE_MODEL ?? DEFAULT_OPENAI_JUDGE_MODEL;
193441
+ }
193037
193442
  async function runOpenAiJudge(input, timeoutMs) {
193038
193443
  const apiKey = input.env.OPENAI_API_KEY ?? input.env.CODEX_API_KEY;
193039
193444
  if (!apiKey)
193040
193445
  throw new Error("OPENAI_API_KEY is not configured.");
193446
+ const requestedModel = resolveOpenAiJudgeModel(input.env);
193041
193447
  const response = await fetchWithTimeout2(`${input.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1"}/chat/completions`, {
193042
193448
  method: "POST",
193043
193449
  headers: {
@@ -193045,7 +193451,7 @@ async function runOpenAiJudge(input, timeoutMs) {
193045
193451
  "content-type": "application/json"
193046
193452
  },
193047
193453
  body: JSON.stringify({
193048
- model: input.env.KYOSO_OPENAI_JUDGE_MODEL ?? "gpt-5.4-mini",
193454
+ model: requestedModel,
193049
193455
  response_format: { type: "json_object" },
193050
193456
  messages: [
193051
193457
  {
@@ -193064,8 +193470,11 @@ async function runOpenAiJudge(input, timeoutMs) {
193064
193470
  if (!content)
193065
193471
  throw new Error("OpenAI judge response did not include content.");
193066
193472
  const usage = normalizeUsage3(payload.usage);
193473
+ const reportedModel = typeof payload.model === "string" ? payload.model : undefined;
193067
193474
  return {
193068
193475
  output: parseJudgeOutput(content, input.summaryText),
193476
+ requestedModel,
193477
+ ...reportedModel ? { reportedModel } : {},
193069
193478
  ...usage ? { usage } : {}
193070
193479
  };
193071
193480
  }
@@ -193105,26 +193514,41 @@ function resolveJudgeProvider(provider, env) {
193105
193514
  return "anthropic";
193106
193515
  return "deterministic_fallback";
193107
193516
  }
193517
+ function resolveJudgeCallRoute(mode, provider, env) {
193518
+ const resolvedProvider = resolveJudgeProvider(provider, env);
193519
+ const credentialAvailable = resolvedProvider === "openai" && (hasEnv2(env, "OPENAI_API_KEY") || hasEnv2(env, "CODEX_API_KEY")) || resolvedProvider === "anthropic" && hasEnv2(env, "ANTHROPIC_API_KEY");
193520
+ return {
193521
+ provider: resolvedProvider,
193522
+ llmAvailable: mode === "deterministic_plus_llm" && credentialAvailable
193523
+ };
193524
+ }
193108
193525
  async function runJudge(input) {
193109
193526
  const fallback = runDeterministicJudge(input.result, input.summaryText);
193110
- if (input.config.mode === "deterministic_only") {
193527
+ const configuredProvider = input.requestedProvider ?? input.config.provider;
193528
+ const route = resolveJudgeCallRoute(input.config.mode, configuredProvider, input.env);
193529
+ if (!route.llmAvailable) {
193111
193530
  return {
193112
193531
  provider: "deterministic_fallback",
193113
193532
  status: "deterministic_fallback",
193114
193533
  output: fallback
193115
193534
  };
193116
193535
  }
193117
- const configuredProvider = input.requestedProvider ?? input.config.provider;
193118
- const provider = resolveJudgeProvider(configuredProvider, input.env);
193119
- if (provider === "deterministic_fallback") {
193120
- return { provider, status: "deterministic_fallback", output: fallback };
193121
- }
193536
+ const provider = route.provider;
193537
+ const requestExecutionIdentity = createModelExecutionIdentity({
193538
+ providerRoute: provider === "openai" ? "openai" : "anthropic",
193539
+ requestedModel: provider === "openai" ? resolveOpenAiJudgeModel(input.env) : resolveAnthropicJudgeModel(input.env)
193540
+ });
193122
193541
  try {
193123
193542
  const output = provider === "openai" ? await runOpenAiJudge(input, input.timeoutMs ?? input.config.timeoutMs) : await runAnthropicJudge(input, input.timeoutMs ?? input.config.timeoutMs);
193124
193543
  return {
193125
193544
  provider,
193126
193545
  status: "completed",
193127
193546
  output: output.output,
193547
+ executionIdentity: createModelExecutionIdentity({
193548
+ providerRoute: requestExecutionIdentity.providerRoute,
193549
+ requestedModel: output.requestedModel,
193550
+ reportedModel: output.reportedModel
193551
+ }),
193128
193552
  ...output.usage ? { usage: output.usage } : {}
193129
193553
  };
193130
193554
  } catch (error51) {
@@ -193132,6 +193556,7 @@ async function runJudge(input) {
193132
193556
  provider,
193133
193557
  status: "failed_fallback",
193134
193558
  output: fallback,
193559
+ executionIdentity: requestExecutionIdentity,
193135
193560
  error: error51 instanceof Error ? error51.message : String(error51)
193136
193561
  };
193137
193562
  }
@@ -193458,11 +193883,11 @@ function canonicalJson(value) {
193458
193883
  function canonicalize(value) {
193459
193884
  if (Array.isArray(value))
193460
193885
  return value.map(canonicalize);
193461
- if (!isRecord10(value))
193886
+ if (!isRecord12(value))
193462
193887
  return value;
193463
193888
  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)]));
193464
193889
  }
193465
- function isRecord10(value) {
193890
+ function isRecord12(value) {
193466
193891
  return typeof value === "object" && value !== null && !Array.isArray(value);
193467
193892
  }
193468
193893
 
@@ -193476,12 +193901,10 @@ var REVIEW_BUDGET_KEYS = new Set([
193476
193901
  ]);
193477
193902
  var MODEL_CALL_KINDS = ["primary", "verifier", "judge"];
193478
193903
  function resolveReviewBudget(ceiling, requested) {
193479
- if (requested === undefined)
193480
- return ceiling;
193481
- if (!isRecord11(requested)) {
193904
+ if (requested !== undefined && !isRecord13(requested)) {
193482
193905
  throw new KyosoRequestError("options.reviewBudget must be an object.", "REVIEW_BUDGET_INVALID");
193483
193906
  }
193484
- for (const [key, value] of Object.entries(requested)) {
193907
+ for (const [key, value] of Object.entries(requested ?? {})) {
193485
193908
  if (!REVIEW_BUDGET_KEYS.has(key)) {
193486
193909
  throw new KyosoRequestError(`options.reviewBudget.${key} is not supported.`, "REVIEW_BUDGET_INVALID");
193487
193910
  }
@@ -193502,36 +193925,105 @@ function resolveReviewBudget(ceiling, requested) {
193502
193925
  "maxFindingsPerAgent"
193503
193926
  ];
193504
193927
  for (const key of numericKeys) {
193505
- const value = requested[key];
193928
+ const value = requested?.[key];
193506
193929
  if (value === undefined)
193507
193930
  continue;
193508
193931
  if (value > ceiling[key]) {
193509
193932
  throw new KyosoRequestError(`options.reviewBudget.${key} cannot exceed the user-global ceiling.`, "REVIEW_BUDGET_EXCEEDS_CEILING");
193510
193933
  }
193511
193934
  }
193512
- if (ceiling.skipOptionalPhasesWhenTokenUsageUnknown && requested.skipOptionalPhasesWhenTokenUsageUnknown === false) {
193935
+ if (ceiling.skipOptionalPhasesWhenTokenUsageUnknown && requested?.skipOptionalPhasesWhenTokenUsageUnknown === false) {
193513
193936
  throw new KyosoRequestError("options.reviewBudget.skipOptionalPhasesWhenTokenUsageUnknown cannot relax the user-global ceiling.", "REVIEW_BUDGET_EXCEEDS_CEILING");
193514
193937
  }
193938
+ const maxAgentOutputBytes = requested?.maxAgentOutputBytes ?? ceiling.maxAgentOutputBytes;
193939
+ return {
193940
+ maxModelCalls: requested?.maxModelCalls ?? ceiling.maxModelCalls,
193941
+ maxTotalWallTimeMs: requested?.maxTotalWallTimeMs ?? ceiling.maxTotalWallTimeMs,
193942
+ warnAgentOutputBytes: ceiling.warnAgentOutputBytes,
193943
+ maxAgentOutputBytes,
193944
+ maxFindingsPerAgent: requested?.maxFindingsPerAgent ?? ceiling.maxFindingsPerAgent,
193945
+ skipOptionalPhasesWhenTokenUsageUnknown: ceiling.skipOptionalPhasesWhenTokenUsageUnknown || requested?.skipOptionalPhasesWhenTokenUsageUnknown === true,
193946
+ ...ceiling.warnAgentOutputBytes < maxAgentOutputBytes ? { effectiveWarnAgentOutputBytes: ceiling.warnAgentOutputBytes } : {}
193947
+ };
193948
+ }
193949
+ function buildReviewModelCallPlan(input) {
193950
+ const requiredPrimaryCalls = nonNegativeInteger(input.requiredPrimaryCalls);
193951
+ const potentialVerifierCalls = input.verificationEnabled && requiredPrimaryCalls === 2 ? Math.min(2, nonNegativeInteger(input.verificationMaxFindings)) : 0;
193952
+ const potentialJudgeCalls = input.llmJudgeAvailable ? 1 : 0;
193953
+ const potentialTotalCalls = requiredPrimaryCalls + potentialVerifierCalls + potentialJudgeCalls;
193954
+ const ceilingEffects = [];
193955
+ const availableCalls = nonNegativeInteger(input.maxModelCalls);
193956
+ if (availableCalls < requiredPrimaryCalls) {
193957
+ if (requiredPrimaryCalls > 0) {
193958
+ ceilingEffects.push({
193959
+ kind: "primary",
193960
+ action: "skip",
193961
+ calls: requiredPrimaryCalls,
193962
+ reason: "model_call_budget"
193963
+ });
193964
+ }
193965
+ if (potentialVerifierCalls > 0) {
193966
+ ceilingEffects.push({
193967
+ kind: "verifier",
193968
+ action: "skip",
193969
+ calls: potentialVerifierCalls,
193970
+ reason: "model_call_budget"
193971
+ });
193972
+ }
193973
+ if (potentialJudgeCalls > 0) {
193974
+ ceilingEffects.push({
193975
+ kind: "judge",
193976
+ action: "deterministic_fallback",
193977
+ calls: potentialJudgeCalls,
193978
+ reason: "model_call_budget"
193979
+ });
193980
+ }
193981
+ } else {
193982
+ let remainingCalls = availableCalls - requiredPrimaryCalls;
193983
+ const verifierCalls = Math.min(potentialVerifierCalls, remainingCalls);
193984
+ remainingCalls -= verifierCalls;
193985
+ const skippedVerifierCalls = potentialVerifierCalls - verifierCalls;
193986
+ if (skippedVerifierCalls > 0) {
193987
+ ceilingEffects.push({
193988
+ kind: "verifier",
193989
+ action: "skip",
193990
+ calls: skippedVerifierCalls,
193991
+ reason: "model_call_budget"
193992
+ });
193993
+ }
193994
+ const judgeCalls = Math.min(potentialJudgeCalls, remainingCalls);
193995
+ const fallbackJudgeCalls = potentialJudgeCalls - judgeCalls;
193996
+ if (fallbackJudgeCalls > 0) {
193997
+ ceilingEffects.push({
193998
+ kind: "judge",
193999
+ action: "deterministic_fallback",
194000
+ calls: fallbackJudgeCalls,
194001
+ reason: "model_call_budget"
194002
+ });
194003
+ }
194004
+ }
193515
194005
  return {
193516
- maxModelCalls: requested.maxModelCalls ?? ceiling.maxModelCalls,
193517
- maxTotalWallTimeMs: requested.maxTotalWallTimeMs ?? ceiling.maxTotalWallTimeMs,
193518
- maxAgentOutputBytes: requested.maxAgentOutputBytes ?? ceiling.maxAgentOutputBytes,
193519
- maxFindingsPerAgent: requested.maxFindingsPerAgent ?? ceiling.maxFindingsPerAgent,
193520
- skipOptionalPhasesWhenTokenUsageUnknown: ceiling.skipOptionalPhasesWhenTokenUsageUnknown || requested.skipOptionalPhasesWhenTokenUsageUnknown === true
194006
+ requiredPrimaryCalls,
194007
+ potentialVerifierCalls,
194008
+ potentialJudgeCalls,
194009
+ potentialTotalCalls,
194010
+ ceilingEffects
193521
194011
  };
193522
194012
  }
193523
194013
 
193524
194014
  class ReviewBudgetTracker {
193525
194015
  budget;
193526
194016
  startedAtEpochMs;
194017
+ modelCallPlan;
193527
194018
  deadlineAtEpochMs;
193528
194019
  reservations = new Map;
193529
194020
  skippedCalls = [];
193530
194021
  incompleteReasons = new Set;
193531
194022
  nextReservationId = 1;
193532
- constructor(budget, startedAtEpochMs = Date.now()) {
194023
+ constructor(budget, startedAtEpochMs = Date.now(), modelCallPlan = emptyReviewModelCallPlan()) {
193533
194024
  this.budget = budget;
193534
194025
  this.startedAtEpochMs = startedAtEpochMs;
194026
+ this.modelCallPlan = modelCallPlan;
193535
194027
  this.deadlineAtEpochMs = startedAtEpochMs + budget.maxTotalWallTimeMs;
193536
194028
  }
193537
194029
  remainingWallTimeMs(now = Date.now()) {
@@ -193575,24 +194067,35 @@ class ReviewBudgetTracker {
193575
194067
  }
193576
194068
  return { reservation };
193577
194069
  }
193578
- markStarted(reservation) {
194070
+ markStarted(reservation, executionIdentity) {
193579
194071
  const current = this.reservations.get(reservation.id);
193580
194072
  if (!current || current.status !== "reserved")
193581
194073
  return;
193582
194074
  current.status = "started";
194075
+ current.executionIdentity = normalizeModelExecutionIdentity(executionIdentity);
193583
194076
  }
193584
194077
  hasStarted(reservation) {
193585
194078
  const current = this.reservations.get(reservation.id);
193586
194079
  return current?.status === "started" || current?.status === "completed";
193587
194080
  }
194081
+ executionIdentity(reservation) {
194082
+ return this.reservations.get(reservation.id)?.executionIdentity;
194083
+ }
193588
194084
  complete(reservation, values = {}) {
193589
194085
  const current = this.reservations.get(reservation.id);
193590
194086
  if (!current || current.status === "skipped" || current.status === "completed") {
193591
194087
  return;
193592
194088
  }
193593
194089
  current.status = "completed";
194090
+ current.messageBytes = values.messageBytes;
194091
+ current.thoughtBytes = values.thoughtBytes;
193594
194092
  current.outputBytes = values.outputBytes;
194093
+ current.outputWarningTriggered = values.outputWarningTriggered;
194094
+ current.salvaged = values.salvaged;
194095
+ current.reportedFindings = values.reportedFindings;
194096
+ current.findingsTargetExceeded = values.findingsTargetExceeded;
193595
194097
  current.usage = normalizeModelTokenUsage(values.usage);
194098
+ current.executionIdentity = normalizeModelExecutionIdentity(values.executionIdentity) ?? current.executionIdentity;
193596
194099
  current.stopReason = values.stopReason;
193597
194100
  }
193598
194101
  skip(reservation, reason) {
@@ -193665,12 +194168,16 @@ class ReviewBudgetTracker {
193665
194168
  },
193666
194169
  executionBudget: {
193667
194170
  maxModelCalls: this.budget.maxModelCalls,
194171
+ modelCallPlan: this.modelCallPlan,
193668
194172
  modelCalls: { planned, consumed, skipped, byKind },
193669
194173
  wallTime: {
193670
194174
  limitMs: this.budget.maxTotalWallTimeMs,
193671
194175
  consumedMs,
193672
194176
  remainingMs: this.remainingWallTimeMs(now)
193673
194177
  },
194178
+ ...this.budget.effectiveWarnAgentOutputBytes !== undefined ? {
194179
+ effectiveWarnAgentOutputBytes: this.budget.effectiveWarnAgentOutputBytes
194180
+ } : {},
193674
194181
  maxAgentOutputBytes: this.budget.maxAgentOutputBytes,
193675
194182
  maxFindingsPerAgent: this.budget.maxFindingsPerAgent,
193676
194183
  skipOptionalPhasesWhenTokenUsageUnknown: this.budget.skipOptionalPhasesWhenTokenUsageUnknown,
@@ -193694,8 +194201,15 @@ class ReviewBudgetTracker {
193694
194201
  ...reservation.agent ? { agent: reservation.agent } : {},
193695
194202
  status: reservation.status === "completed" ? "completed" : "skipped",
193696
194203
  ...reservation.reason ? { reason: reservation.reason } : {},
194204
+ ...reservation.messageBytes !== undefined ? { messageBytes: reservation.messageBytes } : {},
194205
+ ...reservation.thoughtBytes !== undefined ? { thoughtBytes: reservation.thoughtBytes } : {},
193697
194206
  ...reservation.outputBytes !== undefined ? { outputBytes: reservation.outputBytes } : {},
194207
+ ...reservation.outputWarningTriggered !== undefined ? { outputWarningTriggered: reservation.outputWarningTriggered } : {},
194208
+ ...reservation.salvaged !== undefined ? { salvaged: reservation.salvaged } : {},
194209
+ ...reservation.reportedFindings !== undefined ? { reportedFindings: reservation.reportedFindings } : {},
194210
+ ...reservation.findingsTargetExceeded !== undefined ? { findingsTargetExceeded: reservation.findingsTargetExceeded } : {},
193698
194211
  ...reservation.usage ? { usage: reservation.usage } : {},
194212
+ ...reservation.executionIdentity ? { executionIdentity: reservation.executionIdentity } : {},
193699
194213
  ...reservation.stopReason ? { stopReason: reservation.stopReason } : {}
193700
194214
  }));
193701
194215
  return [...reservations, ...this.skippedCalls];
@@ -193719,7 +194233,19 @@ function addUsage(total, usage) {
193719
194233
  function isPositiveInteger(value) {
193720
194234
  return typeof value === "number" && Number.isInteger(value) && value > 0;
193721
194235
  }
193722
- function isRecord11(value) {
194236
+ function nonNegativeInteger(value) {
194237
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
194238
+ }
194239
+ function emptyReviewModelCallPlan() {
194240
+ return {
194241
+ requiredPrimaryCalls: 0,
194242
+ potentialVerifierCalls: 0,
194243
+ potentialJudgeCalls: 0,
194244
+ potentialTotalCalls: 0,
194245
+ ceilingEffects: []
194246
+ };
194247
+ }
194248
+ function isRecord13(value) {
193723
194249
  return typeof value === "object" && value !== null && !Array.isArray(value);
193724
194250
  }
193725
194251
 
@@ -193781,7 +194307,7 @@ function parseVerificationVerdicts(rawText) {
193781
194307
  if (!Array.isArray(parsed.verdicts))
193782
194308
  return;
193783
194309
  return parsed.verdicts.flatMap((item) => {
193784
- if (!isRecord12(item))
194310
+ if (!isRecord14(item))
193785
194311
  return [];
193786
194312
  if (typeof item.findingId !== "string")
193787
194313
  return [];
@@ -193859,7 +194385,7 @@ function verificationNote(reasoning) {
193859
194385
  function isVerdict(value) {
193860
194386
  return value === "confirmed" || value === "refuted" || value === "uncertain";
193861
194387
  }
193862
- function isRecord12(value) {
194388
+ function isRecord14(value) {
193863
194389
  return typeof value === "object" && value !== null && !Array.isArray(value);
193864
194390
  }
193865
194391
 
@@ -193884,13 +194410,14 @@ async function runReview(tool, request, options = {}) {
193884
194410
  } catch (error51) {
193885
194411
  if (error51 instanceof KyosoRequestError) {
193886
194412
  const config2 = kyosoConfigSchema.parse(defaultConfig);
193887
- const budgetTracker = new ReviewBudgetTracker(config2.reviewBudget, startedAtEpochMs);
194413
+ const reviewBudget = resolveReviewBudget(config2.reviewBudget, undefined);
194414
+ const budgetTracker = new ReviewBudgetTracker(reviewBudget, startedAtEpochMs, configuredReviewModelCallPlan(config2, reviewBudget, options.env ?? process.env));
193888
194415
  const requestFingerprint = createRequestFingerprint({
193889
194416
  tool,
193890
194417
  request: requestForRecursionFingerprint(request),
193891
194418
  config: config2,
193892
194419
  roles: resolveAgentRoles(config2),
193893
- budget: config2.reviewBudget,
194420
+ budget: reviewBudget,
193894
194421
  entrypoint: options.entrypoint
193895
194422
  });
193896
194423
  const trace2 = traceWriterFactory({
@@ -193996,7 +194523,7 @@ async function runReview(tool, request, options = {}) {
193996
194523
  });
193997
194524
  validateReviewRequest(tool, request);
193998
194525
  const reviewBudget = resolveReviewBudget(loaded.config.reviewBudget, request.options?.reviewBudget);
193999
- const budgetTracker = new ReviewBudgetTracker(reviewBudget, startedAtEpochMs);
194526
+ const budgetTracker = new ReviewBudgetTracker(reviewBudget, startedAtEpochMs, configuredReviewModelCallPlan(loaded.config, reviewBudget, options.env ?? process.env, request.options?.judgeProvider));
194000
194527
  assertTrustedWorkspaceRoot(request.workspace?.root, loaded.config.workspace.root, cwd);
194001
194528
  const networkMode = resolveNetworkMode(request.options?.network, loaded.config.network.defaultMode, options.mcpNetworkMode);
194002
194529
  if (networkMode === "unrestricted" && !loaded.config.network.allowUnrestricted) {
@@ -194117,6 +194644,7 @@ async function runReview(tool, request, options = {}) {
194117
194644
  budgetTracker,
194118
194645
  requestFingerprint
194119
194646
  });
194647
+ warnings.push(...plannedBudgetWarnings(budgetTracker));
194120
194648
  snapshot = await createSnapshot(traceId, tool, built.request, {
194121
194649
  denyPatterns,
194122
194650
  allowPatterns,
@@ -194143,11 +194671,9 @@ async function runReview(tool, request, options = {}) {
194143
194671
  budgetTracker
194144
194672
  });
194145
194673
  warnings.push(...agentResults.flatMap((result) => (result.warnings ?? []).map((warning) => `Agent ${result.agent} ${warning}`)));
194146
- const normalized = agentResults.map((result) => normalizeAgentRunResult(result, reviewBudget.maxFindingsPerAgent));
194147
- const normalizedAgentResults = normalized.map((item) => item.result);
194148
- for (const item of normalized.filter((item2) => item2.findingsCapped)) {
194149
- budgetTracker.markIncomplete("coverage_incomplete");
194150
- warnings.push(`Agent ${item.result.agent} findings were capped at ${reviewBudget.maxFindingsPerAgent}; review coverage is incomplete.`);
194674
+ const normalizedAgentResults = agentResults.map((result) => normalizeAgentRunResult(result, reviewBudget.maxFindingsPerAgent));
194675
+ for (const result of normalizedAgentResults.filter((item) => item.findingsTargetExceeded)) {
194676
+ warnings.push(`Agent ${result.agent} reported ${result.reportedFindings} findings, above the soft target of ${reviewBudget.maxFindingsPerAgent}; all findings were retained.`);
194151
194677
  }
194152
194678
  const enabledAgents = ["codex", "claude"].filter((agent) => loaded.config.agents[agent].enabled);
194153
194679
  const agentsUsed = normalizedAgentResults.filter((result) => result.status !== "skipped").map((result) => result.agent);
@@ -194321,6 +194847,11 @@ async function runReview(tool, request, options = {}) {
194321
194847
  }));
194322
194848
  const crossModelAnalysis = buildCrossModelAnalysis(judge, reviewMode);
194323
194849
  const budgetAfterJudge = budgetTracker.snapshot();
194850
+ const finalWarnings = Array.from(new Set([
194851
+ ...resultWithoutMarkdown.audit.warnings ?? [],
194852
+ ...outputWarningMessages(budgetAfterJudge),
194853
+ ...tokenUsageWarningMessages(budgetTracker, budgetAfterJudge)
194854
+ ]));
194324
194855
  const finalDecision = budgetAfterJudge.completion.status === "incomplete" ? "block" : decision;
194325
194856
  const resultAfterJudge = {
194326
194857
  ...resultWithoutMarkdown,
@@ -194332,6 +194863,7 @@ async function runReview(tool, request, options = {}) {
194332
194863
  audit: {
194333
194864
  ...resultWithoutMarkdown.audit,
194334
194865
  completedAt: new Date().toISOString(),
194866
+ warnings: finalWarnings,
194335
194867
  modelCalls: budgetAfterJudge.modelCalls
194336
194868
  }
194337
194869
  };
@@ -194340,6 +194872,7 @@ async function runReview(tool, request, options = {}) {
194340
194872
  traceId,
194341
194873
  provider: judge.provider,
194342
194874
  status: judge.status,
194875
+ ...judge.executionIdentity ? { executionIdentity: judge.executionIdentity } : {},
194343
194876
  timestamp: new Date().toISOString()
194344
194877
  };
194345
194878
  if (judge.error)
@@ -194533,11 +195066,20 @@ async function runFindingVerification(input) {
194533
195066
  workspaceDir: input.workspaceDir,
194534
195067
  timeoutMs: Math.min(input.config.verification.timeoutMs, input.budgetTracker.remainingWallTimeMs()),
194535
195068
  deadlineAtEpochMs: input.budgetTracker.deadlineAtEpochMs,
195069
+ warnOutputBytes: input.budgetTracker.budget.effectiveWarnAgentOutputBytes,
194536
195070
  maxOutputBytes: input.budgetTracker.budget.maxAgentOutputBytes,
194537
195071
  networkMode: input.networkMode,
194538
- onStarted: () => {
194539
- input.budgetTracker.markStarted(group.reservation);
194540
- return Promise.resolve();
195072
+ onStarted: (executionIdentity) => {
195073
+ input.budgetTracker.markStarted(group.reservation, executionIdentity);
195074
+ const event = buildAgentStartedEvent({
195075
+ traceId: input.traceId,
195076
+ agent: group.verifier,
195077
+ role: "finding_verifier",
195078
+ executionIdentity: input.budgetTracker.executionIdentity(group.reservation)
195079
+ });
195080
+ return input.trace.write(event).catch(() => {
195081
+ warnings.push("AUDIT_WRITE_FAILED: agent_started event could not be recorded.");
195082
+ });
194541
195083
  }
194542
195084
  }));
194543
195085
  let results;
@@ -194679,8 +195221,8 @@ function buildCrossModelAnalysis(judge, reviewMode) {
194679
195221
  }
194680
195222
  async function runBudgetedJudge(input) {
194681
195223
  const configuredProvider = input.requestedProvider ?? input.config.provider;
194682
- const provider = resolveJudgeProvider(configuredProvider, input.env);
194683
- if (input.config.mode === "deterministic_only" || provider === "deterministic_fallback") {
195224
+ const judgeRoute = resolveJudgeCallRoute(input.config.mode, configuredProvider, input.env);
195225
+ if (!judgeRoute.llmAvailable) {
194684
195226
  return runJudge(input);
194685
195227
  }
194686
195228
  const fallback = () => runJudge({
@@ -194739,8 +195281,10 @@ async function runBudgetedJudge(input) {
194739
195281
  const judge = await runJudge({ ...input, timeoutMs });
194740
195282
  const usage = normalizeModelTokenUsage(judge.usage);
194741
195283
  input.budgetTracker.complete(reservation, {
194742
- ...usage ? { usage } : {}
195284
+ ...usage ? { usage } : {},
195285
+ ...judge.executionIdentity ? { executionIdentity: judge.executionIdentity } : {}
194743
195286
  });
195287
+ const executionIdentity = input.budgetTracker.executionIdentity(reservation);
194744
195288
  await input.trace.write({
194745
195289
  type: "model_call_completed",
194746
195290
  traceId: input.traceId,
@@ -194748,6 +195292,7 @@ async function runBudgetedJudge(input) {
194748
195292
  provider: judge.provider,
194749
195293
  resultStatus: judge.status,
194750
195294
  ...usage ? { usage } : {},
195295
+ ...executionIdentity ? { executionIdentity } : {},
194751
195296
  timestamp: new Date().toISOString()
194752
195297
  });
194753
195298
  return judge;
@@ -194861,30 +195406,25 @@ async function runAgents(input) {
194861
195406
  tool: input.tool,
194862
195407
  prompt: buildAgentPrompt(input.tool, input.request, agent, role, {
194863
195408
  requiredLenses,
194864
- cisaEnabled: input.config.securityReview.cisaSecureByDesign.enabled
195409
+ cisaEnabled: input.config.securityReview.cisaSecureByDesign.enabled,
195410
+ maxFindingsTarget: input.budgetTracker.budget.maxFindingsPerAgent
194865
195411
  }),
194866
195412
  workspaceDir: input.workspaceDir,
194867
195413
  timeoutMs: Math.min(input.request.options?.maxAgentTimeoutMs ?? Number.POSITIVE_INFINITY, agentConfig.timeoutMs ?? DEFAULT_AGENT_TIMEOUT_MS, input.budgetTracker.remainingWallTimeMs()),
194868
195414
  deadlineAtEpochMs: input.budgetTracker.deadlineAtEpochMs,
195415
+ warnOutputBytes: input.budgetTracker.budget.effectiveWarnAgentOutputBytes,
194869
195416
  maxOutputBytes: input.budgetTracker.budget.maxAgentOutputBytes,
194870
195417
  networkMode: input.networkMode,
194871
- onStarted: () => {
194872
- input.budgetTracker.markStarted(reservation);
195418
+ onStarted: (executionIdentity) => {
195419
+ input.budgetTracker.markStarted(reservation, executionIdentity);
194873
195420
  if (!acceptingStartedEvents)
194874
195421
  return Promise.resolve();
194875
- const event = {
194876
- type: "agent_started",
195422
+ const event = buildAgentStartedEvent({
194877
195423
  traceId: input.traceId,
194878
195424
  agent,
194879
195425
  role,
194880
- timestamp: new Date().toISOString()
194881
- };
194882
- if (agentConfig.model) {
194883
- event.model = sanitizeTextForDisplay(agentConfig.model);
194884
- }
194885
- if (agent === "codex" && input.config.agents.codex.provider === CODEX_OPENROUTER_PROVIDER) {
194886
- event.provider = CODEX_OPENROUTER_PROVIDER;
194887
- }
195426
+ executionIdentity: input.budgetTracker.executionIdentity(reservation)
195427
+ });
194888
195428
  const write = (async () => {
194889
195429
  try {
194890
195430
  await input.trace.write(event);
@@ -194936,7 +195476,8 @@ async function runAgents(input) {
194936
195476
  }
194937
195477
  };
194938
195478
  });
194939
- for (const result of orderedResults) {
195479
+ const normalizedResults = orderedResults.map((result) => normalizeAgentRunResult(result, input.budgetTracker.budget.maxFindingsPerAgent));
195480
+ for (const result of normalizedResults) {
194940
195481
  const reservation = reservations.get(result.agent);
194941
195482
  if (!reservation)
194942
195483
  continue;
@@ -194951,7 +195492,7 @@ async function runAgents(input) {
194951
195492
  input.budgetTracker.markIncomplete("coverage_incomplete");
194952
195493
  }
194953
195494
  }
194954
- await Promise.all(orderedResults.map((result) => {
195495
+ await Promise.all(normalizedResults.map((result) => {
194955
195496
  const event = {
194956
195497
  type: "agent_completed",
194957
195498
  traceId: input.traceId,
@@ -194966,12 +195507,20 @@ async function runAgents(input) {
194966
195507
  event.errorCode = result.error.code;
194967
195508
  event.errorDetail = result.error.detail;
194968
195509
  }
195510
+ if (result.salvaged !== undefined)
195511
+ event.salvaged = result.salvaged;
195512
+ if (result.reportedFindings !== undefined) {
195513
+ event.reportedFindings = result.reportedFindings;
195514
+ }
195515
+ if (result.findingsTargetExceeded !== undefined) {
195516
+ event.findingsTargetExceeded = result.findingsTargetExceeded;
195517
+ }
194969
195518
  if (input.config.audit.includeRawAgentOutput && result.rawText) {
194970
195519
  event.rawText = sanitizeTextForRawOutput(result.rawText);
194971
195520
  }
194972
195521
  return input.trace.write(event);
194973
195522
  }));
194974
- return orderedResults;
195523
+ return normalizedResults;
194975
195524
  }
194976
195525
  async function skipReservedPrimaryAgents(input) {
194977
195526
  const results = [];
@@ -195021,20 +195570,44 @@ async function finalizeModelCallResult(input) {
195021
195570
  });
195022
195571
  return;
195023
195572
  }
195024
- input.budgetTracker.markStarted(input.reservation);
195573
+ input.budgetTracker.markStarted(input.reservation, input.result.executionIdentity);
195025
195574
  const usage = normalizeModelTokenUsage(input.result.usage);
195026
- const outputBytes = input.result.outputBytes ?? (input.result.rawText ? Buffer.byteLength(input.result.rawText, "utf8") : undefined);
195575
+ const { messageBytes, thoughtBytes, outputBytes } = resolveOutputByteMetrics(input.result);
195576
+ const warningThreshold = input.budgetTracker.budget.effectiveWarnAgentOutputBytes;
195577
+ const warningTriggered = warningThreshold !== undefined && outputBytes !== undefined && (input.result.outputWarningTriggered === true || outputBytes >= warningThreshold);
195578
+ const outputWarningTriggered = warningTriggered || input.result.outputWarningTriggered !== undefined ? warningTriggered : undefined;
195027
195579
  input.budgetTracker.complete(input.reservation, {
195580
+ ...messageBytes === undefined ? {} : { messageBytes },
195581
+ ...thoughtBytes === undefined ? {} : { thoughtBytes },
195028
195582
  ...outputBytes === undefined ? {} : { outputBytes },
195583
+ ...outputWarningTriggered === undefined ? {} : { outputWarningTriggered },
195584
+ ...input.result.salvaged === undefined ? {} : { salvaged: input.result.salvaged },
195585
+ ...input.result.reportedFindings === undefined ? {} : { reportedFindings: input.result.reportedFindings },
195586
+ ...input.result.findingsTargetExceeded === undefined ? {} : { findingsTargetExceeded: input.result.findingsTargetExceeded },
195029
195587
  ...usage ? { usage } : {},
195588
+ ...input.result.executionIdentity ? { executionIdentity: input.result.executionIdentity } : {},
195030
195589
  ...input.result.stopReason ? { stopReason: input.result.stopReason } : {}
195031
195590
  });
195591
+ const executionIdentity = input.budgetTracker.executionIdentity(input.reservation);
195032
195592
  if (input.result.error?.code === "AGENT_OUTPUT_LIMIT") {
195033
195593
  input.budgetTracker.markIncomplete("agent_output_limit");
195034
195594
  }
195035
195595
  if (input.result.error?.code === "REVIEW_DEADLINE_EXCEEDED") {
195036
195596
  input.budgetTracker.markIncomplete("deadline");
195037
195597
  }
195598
+ if (outputWarningTriggered && warningThreshold !== undefined && messageBytes !== undefined && thoughtBytes !== undefined && outputBytes !== undefined) {
195599
+ await input.trace.write({
195600
+ type: "agent_output_warning",
195601
+ traceId: input.traceId,
195602
+ kind: input.reservation.kind,
195603
+ agent: input.reservation.agent,
195604
+ thresholdBytes: warningThreshold,
195605
+ messageBytes,
195606
+ thoughtBytes,
195607
+ outputBytes,
195608
+ timestamp: new Date().toISOString()
195609
+ });
195610
+ }
195038
195611
  await input.trace.write({
195039
195612
  type: "model_call_completed",
195040
195613
  traceId: input.traceId,
@@ -195042,12 +195615,55 @@ async function finalizeModelCallResult(input) {
195042
195615
  agent: input.reservation.agent,
195043
195616
  resultStatus: input.result.status,
195044
195617
  ...input.result.error?.code ? { errorCode: input.result.error.code } : {},
195618
+ ...messageBytes === undefined ? {} : { messageBytes },
195619
+ ...thoughtBytes === undefined ? {} : { thoughtBytes },
195045
195620
  ...outputBytes === undefined ? {} : { outputBytes },
195621
+ ...outputWarningTriggered === undefined ? {} : { outputWarningTriggered },
195622
+ ...input.result.salvaged === undefined ? {} : { salvaged: input.result.salvaged },
195623
+ ...input.result.reportedFindings === undefined ? {} : { reportedFindings: input.result.reportedFindings },
195624
+ ...input.result.findingsTargetExceeded === undefined ? {} : { findingsTargetExceeded: input.result.findingsTargetExceeded },
195046
195625
  ...usage ? { usage } : {},
195626
+ ...executionIdentity ? { executionIdentity } : {},
195047
195627
  ...input.result.stopReason ? { stopReason: input.result.stopReason } : {},
195048
195628
  timestamp: new Date().toISOString()
195049
195629
  });
195050
195630
  }
195631
+ function buildAgentStartedEvent(input) {
195632
+ const executionIdentity = normalizeModelExecutionIdentity(input.executionIdentity);
195633
+ return {
195634
+ type: "agent_started",
195635
+ traceId: input.traceId,
195636
+ agent: input.agent,
195637
+ role: input.role,
195638
+ ...executionIdentity ? { executionIdentity } : {},
195639
+ ...executionIdentity?.requestedModel ? { model: executionIdentity.requestedModel } : {},
195640
+ ...executionIdentity?.providerRoute === "openrouter" ? { provider: "openrouter" } : {},
195641
+ timestamp: new Date().toISOString()
195642
+ };
195643
+ }
195644
+ function resolveOutputByteMetrics(result) {
195645
+ const rawTextBytes = result.rawText ? Buffer.byteLength(result.rawText, "utf8") : undefined;
195646
+ let messageBytes = result.messageBytes;
195647
+ let thoughtBytes = result.thoughtBytes;
195648
+ if (messageBytes === undefined && thoughtBytes === undefined) {
195649
+ if (rawTextBytes !== undefined) {
195650
+ messageBytes = rawTextBytes;
195651
+ thoughtBytes = result.outputBytes === undefined ? 0 : Math.max(0, result.outputBytes - rawTextBytes);
195652
+ } else if (result.outputBytes !== undefined) {
195653
+ messageBytes = result.outputBytes;
195654
+ thoughtBytes = 0;
195655
+ }
195656
+ } else if (messageBytes === undefined) {
195657
+ messageBytes = result.outputBytes === undefined ? rawTextBytes ?? 0 : Math.max(0, result.outputBytes - (thoughtBytes ?? 0));
195658
+ } else if (thoughtBytes === undefined) {
195659
+ thoughtBytes = result.outputBytes === undefined ? 0 : Math.max(0, result.outputBytes - messageBytes);
195660
+ }
195661
+ return {
195662
+ ...messageBytes === undefined ? {} : { messageBytes },
195663
+ ...thoughtBytes === undefined ? {} : { thoughtBytes },
195664
+ ...messageBytes === undefined || thoughtBytes === undefined ? {} : { outputBytes: messageBytes + thoughtBytes }
195665
+ };
195666
+ }
195051
195667
  function isPreflightAgentFailure(result) {
195052
195668
  return result.status === "failed" && [
195053
195669
  "AGENT_CONFIG_INVALID",
@@ -195125,24 +195741,11 @@ function normalizeAgentRunResult(result, maxFindingsPerAgent) {
195125
195741
  ...result,
195126
195742
  normalized: normalizeAgentOutput(result.agent, result.role, result.rawText)
195127
195743
  } : result;
195128
- const normalized = normalizedResult.normalized;
195129
- const findings = normalized?.findings;
195130
- if (!normalized || !findings || findings.length <= maxFindingsPerAgent) {
195131
- return { result: normalizedResult, findingsCapped: false };
195132
- }
195133
- const limitedFindings = findings.map((finding, index) => ({ finding, index })).sort((left, right) => {
195134
- const severity = compareSeverity(left.finding.severity, right.finding.severity);
195135
- return severity === 0 ? left.index - right.index : severity;
195136
- }).slice(0, maxFindingsPerAgent).map(({ finding }) => finding);
195137
- return {
195138
- result: {
195139
- ...normalizedResult,
195140
- normalized: {
195141
- ...normalized,
195142
- findings: limitedFindings
195143
- }
195144
- },
195145
- findingsCapped: true
195744
+ const reportedFindings = normalizedResult.normalized?.findings.length;
195745
+ return reportedFindings === undefined ? normalizedResult : {
195746
+ ...normalizedResult,
195747
+ reportedFindings,
195748
+ findingsTargetExceeded: reportedFindings > maxFindingsPerAgent
195146
195749
  };
195147
195750
  }
195148
195751
  function agentOpinionSummary(result, includeRawText = false) {
@@ -195151,7 +195754,8 @@ function agentOpinionSummary(result, includeRawText = false) {
195151
195754
  role: result.role,
195152
195755
  summary: result.normalized?.summary ?? sanitizeTextForDisplay(result.error?.message ?? result.status),
195153
195756
  status: result.status,
195154
- errorCode: result.error?.code
195757
+ errorCode: result.error?.code,
195758
+ ...result.salvaged === undefined ? {} : { salvaged: result.salvaged }
195155
195759
  };
195156
195760
  if (includeRawText && result.rawText) {
195157
195761
  opinion.rawText = sanitizeTextForRawOutput(result.rawText);
@@ -195353,12 +195957,60 @@ async function writeReviewBudgetPlanned(input) {
195353
195957
  requestFingerprint: input.requestFingerprint,
195354
195958
  maxModelCalls: snapshot.executionBudget.maxModelCalls,
195355
195959
  maxTotalWallTimeMs: snapshot.executionBudget.wallTime.limitMs,
195960
+ ...snapshot.executionBudget.effectiveWarnAgentOutputBytes !== undefined ? {
195961
+ effectiveWarnAgentOutputBytes: snapshot.executionBudget.effectiveWarnAgentOutputBytes
195962
+ } : {},
195356
195963
  maxAgentOutputBytes: snapshot.executionBudget.maxAgentOutputBytes,
195357
195964
  maxFindingsPerAgent: snapshot.executionBudget.maxFindingsPerAgent,
195358
195965
  skipOptionalPhasesWhenTokenUsageUnknown: snapshot.executionBudget.skipOptionalPhasesWhenTokenUsageUnknown,
195966
+ ...snapshot.executionBudget.modelCallPlan,
195359
195967
  timestamp: new Date().toISOString()
195360
195968
  });
195361
195969
  }
195970
+ function plannedBudgetWarnings(budgetTracker) {
195971
+ 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);
195972
+ if (fallbackCalls === 0)
195973
+ return [];
195974
+ return [
195975
+ `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.`
195976
+ ];
195977
+ }
195978
+ function outputWarningMessages(snapshot) {
195979
+ const threshold = snapshot.executionBudget.effectiveWarnAgentOutputBytes;
195980
+ if (threshold === undefined)
195981
+ return [];
195982
+ return snapshot.modelCalls.flatMap((call) => {
195983
+ if (call.status !== "completed" || !call.outputWarningTriggered || !call.agent) {
195984
+ return [];
195985
+ }
195986
+ const messageBytes = call.messageBytes ?? 0;
195987
+ const thoughtBytes = call.thoughtBytes ?? 0;
195988
+ const outputBytes = call.outputBytes ?? messageBytes + thoughtBytes;
195989
+ const outcome = call.stopReason === "cancelled" ? "the hard breaker subsequently stopped execution." : "execution continued.";
195990
+ return [
195991
+ `Agent ${call.agent} ${call.kind} output reached the ${threshold}-byte soft threshold (message: ${messageBytes}, thought: ${thoughtBytes}, total: ${outputBytes}); ${outcome}`
195992
+ ];
195993
+ });
195994
+ }
195995
+ function tokenUsageWarningMessages(budgetTracker, snapshot) {
195996
+ const unknownCalls = snapshot.executionBudget.tokenUsage.unknownCalls;
195997
+ if (budgetTracker.budget.skipOptionalPhasesWhenTokenUsageUnknown || unknownCalls === 0) {
195998
+ return [];
195999
+ }
196000
+ return [
196001
+ `Token usage was not reported for ${unknownCalls} completed call(s); budget enforcement continued using calls, wall time, and bytes.`
196002
+ ];
196003
+ }
196004
+ function configuredReviewModelCallPlan(config2, budget, env, requestedJudgeProvider) {
196005
+ const judgeRoute = resolveJudgeCallRoute(config2.judge.mode, requestedJudgeProvider ?? config2.judge.provider, env);
196006
+ return buildReviewModelCallPlan({
196007
+ maxModelCalls: budget.maxModelCalls,
196008
+ requiredPrimaryCalls: Object.values(config2.agents).filter((agent) => agent.enabled).length,
196009
+ verificationEnabled: config2.verification.enabled,
196010
+ verificationMaxFindings: config2.verification.maxFindings,
196011
+ llmJudgeAvailable: judgeRoute.llmAvailable
196012
+ });
196013
+ }
195362
196014
  async function writeReviewBudgetCompleted(input) {
195363
196015
  const snapshot = input.budgetTracker.snapshot();
195364
196016
  await input.trace.write({