@kyo-so/cli 0.14.0 → 0.15.1

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
@@ -183918,6 +183918,7 @@ ${typeof file2.content === "string" ? file2.content.slice(0, 2000) : ""}`)
183918
183918
  var CODEX_OPENROUTER_PROVIDER = "openrouter";
183919
183919
  var CODEX_DEFAULT_PROVIDER = "default";
183920
183920
  var CODEX_OPENROUTER_MODEL_REQUIRED_ISSUE = "codex_openrouter_model_required";
183921
+ var CODEX_OPENROUTER_POLICY_REQUIRES_PROVIDER_ISSUE = "codex_openrouter_policy_requires_provider";
183921
183922
  var baseAgentSchema = exports_external.object({
183922
183923
  enabled: exports_external.boolean().default(true),
183923
183924
  type: exports_external.literal("acp").default("acp"),
@@ -183940,12 +183941,29 @@ var baseAgentSchema = exports_external.object({
183940
183941
  envWhitelist: exports_external.array(exports_external.string())
183941
183942
  })
183942
183943
  });
183944
+ var codexOpenRouterSchema = exports_external.object({
183945
+ streamIdleTimeoutMs: exports_external.number().int().min(1000).optional(),
183946
+ streamMaxRetries: exports_external.number().int().min(0).max(100).optional(),
183947
+ requestMaxRetries: exports_external.number().int().min(0).max(100).optional()
183948
+ });
183943
183949
  var codexAgentSchema = baseAgentSchema.extend({
183944
183950
  provider: exports_external.enum([CODEX_OPENROUTER_PROVIDER, CODEX_DEFAULT_PROVIDER]).optional(),
183945
183951
  allowProjectProvider: exports_external.array(exports_external.string().min(1).refine(isAbsolute, {
183946
183952
  message: "must contain only absolute project directory paths for exact matching"
183947
- })).default([])
183953
+ })).default([]),
183954
+ openRouter: codexOpenRouterSchema.default({})
183948
183955
  }).superRefine((agent, context) => {
183956
+ const hasOpenRouterPolicy = Object.values(agent.openRouter).some((value) => value !== undefined);
183957
+ if (hasOpenRouterPolicy && agent.provider !== CODEX_OPENROUTER_PROVIDER) {
183958
+ context.addIssue({
183959
+ code: exports_external.ZodIssueCode.custom,
183960
+ path: ["openRouter"],
183961
+ message: 'agents.codex.openRouter.* requires provider = "openrouter".',
183962
+ params: {
183963
+ kyosoIssue: CODEX_OPENROUTER_POLICY_REQUIRES_PROVIDER_ISSUE
183964
+ }
183965
+ });
183966
+ }
183949
183967
  if (agent.provider !== CODEX_OPENROUTER_PROVIDER || (agent.model?.trim().length ?? 0) > 0) {
183950
183968
  return;
183951
183969
  }
@@ -184071,7 +184089,7 @@ function agentConfigLeafPaths(agent) {
184071
184089
  `agents.${agent}.auth.envWhitelist`
184072
184090
  ];
184073
184091
  if (agent === "codex") {
184074
- paths.push("agents.codex.provider", "agents.codex.allowProjectProvider");
184092
+ paths.push("agents.codex.provider", "agents.codex.allowProjectProvider", "agents.codex.openRouter.streamIdleTimeoutMs", "agents.codex.openRouter.streamMaxRetries", "agents.codex.openRouter.requestMaxRetries");
184075
184093
  }
184076
184094
  return paths;
184077
184095
  }
@@ -184160,7 +184178,7 @@ var defaultConfig = {
184160
184178
  enabled: true,
184161
184179
  type: "acp",
184162
184180
  command: "npx",
184163
- args: ["-y", "@agentclientprotocol/codex-acp@1.1.4"],
184181
+ args: ["-y", "@agentclientprotocol/codex-acp@1.1.5"],
184164
184182
  role: "implementation_reviewer",
184165
184183
  timeoutMs: DEFAULT_AGENT_TIMEOUT_MS,
184166
184184
  allowProjectProvider: [],
@@ -184185,7 +184203,7 @@ var defaultConfig = {
184185
184203
  enabled: true,
184186
184204
  type: "acp",
184187
184205
  command: "npx",
184188
- args: ["-y", "@agentclientprotocol/claude-agent-acp@0.59.0"],
184206
+ args: ["-y", "@agentclientprotocol/claude-agent-acp@0.60.0"],
184189
184207
  role: "architecture_security_reviewer",
184190
184208
  timeoutMs: DEFAULT_AGENT_TIMEOUT_MS,
184191
184209
  env: {
@@ -184302,6 +184320,9 @@ var kyosoConfigOverridePaths = [
184302
184320
  "agents.codex.enabled",
184303
184321
  "agents.codex.model",
184304
184322
  "agents.codex.provider",
184323
+ "agents.codex.openRouter.streamIdleTimeoutMs",
184324
+ "agents.codex.openRouter.streamMaxRetries",
184325
+ "agents.codex.openRouter.requestMaxRetries",
184305
184326
  "agents.codex.effort",
184306
184327
  "agents.codex.role",
184307
184328
  "agents.codex.timeoutMs",
@@ -185715,7 +185736,7 @@ async function loadProjectConfig(input) {
185715
185736
  const extension = extname2(requestedPath);
185716
185737
  if (extension === ".toml") {
185717
185738
  const projectTomlConfig = await loadTomlConfigFile(canonicalPath);
185718
- const projectChangesOpenRouterRoute = projectConfigChangesOpenRouterRoute(projectTomlConfig, input.baseConfig);
185739
+ const projectChangesOpenRouterPolicy = projectConfigChangesOpenRouterPolicy(projectTomlConfig, input.baseConfig);
185719
185740
  await assertProjectOpenRouterAuthorization({
185720
185741
  projectConfig: projectTomlConfig,
185721
185742
  projectPath: requestedPath,
@@ -185733,7 +185754,7 @@ async function loadProjectConfig(input) {
185733
185754
  configPath: requestedPath,
185734
185755
  configTrustStatus: "not_found",
185735
185756
  source: { path: requestedPath, layer: "project_toml" },
185736
- warnings: projectChangesOpenRouterRoute ? [openRouterProjectConfigWarning(requestedPath)] : []
185757
+ warnings: projectChangesOpenRouterPolicy ? [openRouterProjectConfigWarning(requestedPath)] : []
185737
185758
  };
185738
185759
  }
185739
185760
  if (extension === ".ts") {
@@ -185750,30 +185771,35 @@ function projectConfigSelectsDefaultProvider(config2) {
185750
185771
  function projectConfigSuppliesCodexModel(config2) {
185751
185772
  return flattenLeaves(config2).some((leaf) => leaf.path.join(".") === "agents.codex.model");
185752
185773
  }
185753
- function projectConfigChangesOpenRouterRoute(projectConfig, baseConfig) {
185754
- return projectConfigSelectsOpenRouter(projectConfig) || configSelectsOpenRouter(baseConfig) && projectConfigSuppliesCodexModel(projectConfig) && !projectConfigSelectsDefaultProvider(projectConfig);
185774
+ function projectConfigSuppliesOpenRouterPolicy(config2) {
185775
+ return flattenLeaves(config2).some((leaf) => leaf.path.join(".").startsWith("agents.codex.openRouter."));
185776
+ }
185777
+ function projectConfigChangesOpenRouterPolicy(projectConfig, baseConfig) {
185778
+ return projectConfigSelectsOpenRouter(projectConfig) || configSelectsOpenRouter(baseConfig) && !projectConfigSelectsDefaultProvider(projectConfig) && (projectConfigSuppliesCodexModel(projectConfig) || projectConfigSuppliesOpenRouterPolicy(projectConfig));
185755
185779
  }
185756
185780
  function configSelectsOpenRouter(config2) {
185757
185781
  return flattenLeaves(config2).some((leaf) => leaf.path.join(".") === "agents.codex.provider" && leaf.value === CODEX_OPENROUTER_PROVIDER);
185758
185782
  }
185759
185783
  function applyProjectCodexProviderReset(baseConfig, projectConfig, mergedConfig) {
185760
- if (!projectConfigSelectsDefaultProvider(projectConfig) || projectConfigSuppliesCodexModel(projectConfig) || !configSelectsOpenRouter(baseConfig) || !isRecord3(mergedConfig) || !isRecord3(mergedConfig.agents) || !isRecord3(mergedConfig.agents.codex)) {
185784
+ if (!projectConfigSelectsDefaultProvider(projectConfig) || !configSelectsOpenRouter(baseConfig) || !isRecord3(mergedConfig) || !isRecord3(mergedConfig.agents) || !isRecord3(mergedConfig.agents.codex)) {
185761
185785
  return mergedConfig;
185762
185786
  }
185763
- const codexWithoutInheritedModel = Object.fromEntries(Object.entries(mergedConfig.agents.codex).filter(([key]) => key !== "model"));
185787
+ const shouldClearInheritedModel = !projectConfigSuppliesCodexModel(projectConfig);
185788
+ const shouldClearInheritedOpenRouterPolicy = !projectConfigSuppliesOpenRouterPolicy(projectConfig);
185789
+ const codexWithoutInheritedOpenRouterConfig = Object.fromEntries(Object.entries(mergedConfig.agents.codex).filter(([key]) => (!shouldClearInheritedModel || key !== "model") && (!shouldClearInheritedOpenRouterPolicy || key !== "openRouter")));
185764
185790
  return {
185765
185791
  ...mergedConfig,
185766
185792
  agents: {
185767
185793
  ...mergedConfig.agents,
185768
- codex: codexWithoutInheritedModel
185794
+ codex: codexWithoutInheritedOpenRouterConfig
185769
185795
  }
185770
185796
  };
185771
185797
  }
185772
185798
  function openRouterProjectConfigWarning(configPath) {
185773
- return `Project config ${sanitizeWarningText(configPath)} changes Codex OpenRouter routing under user-global authorization; it can route Codex review content through OpenRouter.`;
185799
+ return `Project config ${sanitizeWarningText(configPath)} changes Codex OpenRouter routing or transport retry policy under user-global authorization; it can route Codex review content through OpenRouter.`;
185774
185800
  }
185775
185801
  async function assertProjectOpenRouterAuthorization(input) {
185776
- if (!projectConfigChangesOpenRouterRoute(input.projectConfig, input.baseConfig)) {
185802
+ if (!projectConfigChangesOpenRouterPolicy(input.projectConfig, input.baseConfig)) {
185777
185803
  return;
185778
185804
  }
185779
185805
  if (await projectProviderIsAuthorized(input.baseConfig, input.projectDirectory)) {
@@ -185838,7 +185864,7 @@ async function loadProjectTsConfig(input) {
185838
185864
  if (trustDecision.execute) {
185839
185865
  const userConfig = await loadUserConfig(canonicalPath, source);
185840
185866
  validateExplicitReviewBudgetThresholds(userConfig, input.baseConfig);
185841
- const projectChangesOpenRouterRoute = projectConfigChangesOpenRouterRoute(userConfig, input.baseConfig);
185867
+ const projectChangesOpenRouterPolicy = projectConfigChangesOpenRouterPolicy(userConfig, input.baseConfig);
185842
185868
  await assertProjectOpenRouterAuthorization({
185843
185869
  projectConfig: userConfig,
185844
185870
  projectPath: requestedPath,
@@ -185851,7 +185877,7 @@ async function loadProjectTsConfig(input) {
185851
185877
  await trustConfig(trustStorePath, canonicalPath, configHash);
185852
185878
  }
185853
185879
  const projectMergedConfig = deepMerge2(input.baseConfig, userConfig);
185854
- if (projectChangesOpenRouterRoute) {
185880
+ if (projectChangesOpenRouterPolicy) {
185855
185881
  warnings.push(openRouterProjectConfigWarning(requestedPath));
185856
185882
  }
185857
185883
  return {
@@ -185962,6 +185988,11 @@ async function exists(path) {
185962
185988
 
185963
185989
  // src/config/configOverrides.ts
185964
185990
  var NUMBER_VALUE = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
185991
+ var UNSET_NUMBER_OVERRIDE_PATHS = new Set([
185992
+ "agents.codex.openRouter.streamIdleTimeoutMs",
185993
+ "agents.codex.openRouter.streamMaxRetries",
185994
+ "agents.codex.openRouter.requestMaxRetries"
185995
+ ]);
185965
185996
  function applyConfigOverrides(config2, assignments) {
185966
185997
  if (assignments.length === 0)
185967
185998
  return config2;
@@ -185969,9 +186000,9 @@ function applyConfigOverrides(config2, assignments) {
185969
186000
  const baseConfig = config2;
185970
186001
  const overridden = structuredClone(config2);
185971
186002
  for (const override of overrides) {
185972
- writePath2(overridden, override.path, parseConfigOverrideValue(override.value, readPath2(baseConfig, override.path)));
186003
+ writePath2(overridden, override.path, parseConfigOverrideValue(override.value, readPath2(baseConfig, override.path), override.path));
185973
186004
  }
185974
- clearInheritedOpenRouterModelForProviderReset(baseConfig, overridden, overrides);
186005
+ clearInheritedOpenRouterConfigForProviderReset(baseConfig, overridden, overrides);
185975
186006
  assertOpenRouterProviderOverrideIncludesModel(baseConfig, overridden, overrides);
185976
186007
  const parsed = kyosoConfigSchema.safeParse(overridden);
185977
186008
  if (parsed.success)
@@ -185992,17 +186023,23 @@ function assertOpenRouterProviderOverrideIncludesModel(baseConfig, overridden, o
185992
186023
  const assignment = findAssignmentForPath(overrides, providerPath.join(".")) ?? "agents.codex.provider=openrouter";
185993
186024
  throw new Error(`Invalid --set value ${JSON.stringify(assignment)}: selecting agents.codex.provider=openrouter requires agents.codex.model in the same --set invocation.`);
185994
186025
  }
185995
- function clearInheritedOpenRouterModelForProviderReset(baseConfig, overridden, overrides) {
186026
+ function clearInheritedOpenRouterConfigForProviderReset(baseConfig, overridden, overrides) {
185996
186027
  const providerPath = ["agents", "codex", "provider"];
185997
186028
  const modelPath = ["agents", "codex", "model"];
186029
+ const openRouterPath = ["agents", "codex", "openRouter"];
185998
186030
  const selectsDefaultProvider = overrides.some((override) => override.path.join(".") === providerPath.join(".") && override.value === CODEX_DEFAULT_PROVIDER);
185999
186031
  const suppliesModel = overrides.some((override) => override.path.join(".") === modelPath.join("."));
186000
- if (!selectsDefaultProvider || suppliesModel || readPath2(baseConfig, providerPath) !== CODEX_OPENROUTER_PROVIDER || readPath2(overridden, providerPath) !== CODEX_DEFAULT_PROVIDER) {
186032
+ const suppliesOpenRouterPolicy = overrides.some((override) => override.path.join(".").startsWith(`${openRouterPath.join(".")}.`));
186033
+ if (!selectsDefaultProvider || readPath2(baseConfig, providerPath) !== CODEX_OPENROUTER_PROVIDER || readPath2(overridden, providerPath) !== CODEX_DEFAULT_PROVIDER) {
186001
186034
  return;
186002
186035
  }
186003
186036
  const codex = readPath2(overridden, ["agents", "codex"]);
186004
- if (isRecord4(codex))
186037
+ if (!isRecord4(codex))
186038
+ return;
186039
+ if (!suppliesModel)
186005
186040
  delete codex.model;
186041
+ if (!suppliesOpenRouterPolicy)
186042
+ delete codex.openRouter;
186006
186043
  }
186007
186044
  function findAssignmentForPath(overrides, path) {
186008
186045
  for (let index = overrides.length - 1;index >= 0; index -= 1) {
@@ -186028,7 +186065,7 @@ function parseConfigOverride(assignment) {
186028
186065
  value: assignment.slice(separator + 1)
186029
186066
  };
186030
186067
  }
186031
- function parseConfigOverrideValue(value, currentValue) {
186068
+ function parseConfigOverrideValue(value, currentValue, path) {
186032
186069
  if (typeof currentValue === "boolean") {
186033
186070
  if (value === "true")
186034
186071
  return true;
@@ -186036,7 +186073,7 @@ function parseConfigOverrideValue(value, currentValue) {
186036
186073
  return false;
186037
186074
  return value;
186038
186075
  }
186039
- if (typeof currentValue === "number" && NUMBER_VALUE.test(value)) {
186076
+ if ((typeof currentValue === "number" || currentValue === undefined && UNSET_NUMBER_OVERRIDE_PATHS.has(path.join("."))) && NUMBER_VALUE.test(value)) {
186040
186077
  const parsed = Number(value);
186041
186078
  if (Number.isFinite(parsed))
186042
186079
  return parsed;
@@ -190003,6 +190040,29 @@ var legacyClientNotificationMethods = new Set([
190003
190040
  CLIENT_METHODS.elicitation_complete
190004
190041
  ]);
190005
190042
 
190043
+ // src/core/errors.ts
190044
+ class KyosoRequestError extends Error {
190045
+ code;
190046
+ constructor(message, code) {
190047
+ super(message);
190048
+ this.code = code;
190049
+ this.name = "KyosoRequestError";
190050
+ }
190051
+ }
190052
+
190053
+ class KyosoCancellationError extends Error {
190054
+ code = "REQUEST_CANCELLED";
190055
+ constructor(message = "Kyoso review was cancelled.") {
190056
+ super(message);
190057
+ this.name = "KyosoCancellationError";
190058
+ }
190059
+ }
190060
+ function throwIfAborted(signal) {
190061
+ if (!signal?.aborted)
190062
+ return;
190063
+ throw new KyosoCancellationError(typeof signal.reason === "string" ? signal.reason : undefined);
190064
+ }
190065
+
190006
190066
  // src/core/modelExecutionIdentity.ts
190007
190067
  var MODEL_EXECUTION_IDENTITY_MAX_CHARS = 160;
190008
190068
  var MODEL_PROVIDER_ROUTES = new Set([
@@ -190112,13 +190172,18 @@ var OPENROUTER_EXCLUDED_CREDENTIAL_ENV_KEYS = [
190112
190172
  var OPENROUTER_API_KEY_ENV = "OPENROUTER_API_KEY";
190113
190173
  var KYOSO_OPENROUTER_PROVIDER_ID = "kyoso-openrouter";
190114
190174
  var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
190115
- var OPENROUTER_PROVIDER_PRESET = {
190116
- name: "OpenRouter",
190117
- base_url: OPENROUTER_BASE_URL,
190118
- env_key: OPENROUTER_API_KEY_ENV,
190119
- wire_api: "responses",
190120
- requires_openai_auth: false
190121
- };
190175
+ function buildOpenRouterProviderPreset(options, baseUrl = OPENROUTER_BASE_URL) {
190176
+ return {
190177
+ name: "OpenRouter",
190178
+ base_url: baseUrl,
190179
+ env_key: OPENROUTER_API_KEY_ENV,
190180
+ wire_api: "responses",
190181
+ requires_openai_auth: false,
190182
+ ...options.streamIdleTimeoutMs === undefined ? {} : { stream_idle_timeout_ms: options.streamIdleTimeoutMs },
190183
+ ...options.streamMaxRetries === undefined ? {} : { stream_max_retries: options.streamMaxRetries },
190184
+ ...options.requestMaxRetries === undefined ? {} : { request_max_retries: options.requestMaxRetries }
190185
+ };
190186
+ }
190122
190187
 
190123
190188
  class ChildEnvPreflightError extends Error {
190124
190189
  code;
@@ -190177,7 +190242,7 @@ function buildChildEnvironment(parentEnv, whitelist, explicit, options = {}) {
190177
190242
  }
190178
190243
  if (openRouterSelected) {
190179
190244
  discardOpenRouterExcludedCredentials(env);
190180
- applyOpenRouterConfig(env, parentEnv, options.model, onCredentialPlaceholderDiscarded, options.onOpenRouterProvidersDiscarded ?? warnOpenRouterProvidersDiscarded);
190245
+ applyOpenRouterConfig(env, parentEnv, options.model, options.openRouter, options.openRouterBaseUrlForTest, onCredentialPlaceholderDiscarded, options.onOpenRouterProvidersDiscarded ?? warnOpenRouterProvidersDiscarded);
190181
190246
  } else {
190182
190247
  applyModelConfig(env, options.agent, options.model);
190183
190248
  }
@@ -190234,7 +190299,7 @@ function applyModelConfig(env, agent, model) {
190234
190299
  env.CODEX_CONFIG = JSON.stringify({ model });
190235
190300
  }
190236
190301
  }
190237
- function applyOpenRouterConfig(env, parentEnv, model, onCredentialPlaceholderDiscarded, onOpenRouterProvidersDiscarded) {
190302
+ function applyOpenRouterConfig(env, parentEnv, model, openRouter = {}, baseUrl = OPENROUTER_BASE_URL, onCredentialPlaceholderDiscarded, onOpenRouterProvidersDiscarded) {
190238
190303
  const configuredModel = model?.trim();
190239
190304
  if (!configuredModel) {
190240
190305
  throw new ChildEnvPreflightError("AGENT_CONFIG_INVALID", 'agents.codex.provider="openrouter" requires a non-empty agents.codex.model.');
@@ -190262,7 +190327,7 @@ function applyOpenRouterConfig(env, parentEnv, model, onCredentialPlaceholderDis
190262
190327
  model: configuredModel,
190263
190328
  model_provider: KYOSO_OPENROUTER_PROVIDER_ID,
190264
190329
  model_providers: {
190265
- [KYOSO_OPENROUTER_PROVIDER_ID]: OPENROUTER_PROVIDER_PRESET
190330
+ [KYOSO_OPENROUTER_PROVIDER_ID]: buildOpenRouterProviderPreset(openRouter, baseUrl)
190266
190331
  }
190267
190332
  });
190268
190333
  }
@@ -190379,6 +190444,117 @@ function limitAcpNdJsonLineBytes(input, maxLineBytes = MAX_ACP_NDJSON_LINE_BYTES
190379
190444
  }));
190380
190445
  }
190381
190446
 
190447
+ // src/acp/AgentOutputAccumulator.ts
190448
+ class AgentOutputAccumulator {
190449
+ segments = new Map;
190450
+ messageChunks = [];
190451
+ retryEpoch = 0;
190452
+ observedStreamRetries = 0;
190453
+ discardedRetryMessageBytes = 0;
190454
+ firstOutputAt;
190455
+ lastAcpUpdateAt;
190456
+ nextChunkSequence = 0;
190457
+ addMessageChunk(text, meta3) {
190458
+ this.noteOutput();
190459
+ const id = meta3.messageId ?? `epoch-${this.retryEpoch}`;
190460
+ const key = `${this.retryEpoch}\x00${id}`;
190461
+ const phase = meta3.phase ?? "unknown";
190462
+ let segment = this.segments.get(key);
190463
+ if (!segment) {
190464
+ segment = {
190465
+ id,
190466
+ phase,
190467
+ retryEpoch: this.retryEpoch,
190468
+ text: "",
190469
+ abandoned: false,
190470
+ lastChunkSequence: 0
190471
+ };
190472
+ this.segments.set(key, segment);
190473
+ } else if (segment.phase === "unknown" && phase !== "unknown") {
190474
+ segment.phase = phase;
190475
+ }
190476
+ const sequence = this.nextChunkSequence;
190477
+ this.nextChunkSequence += 1;
190478
+ segment.text += text;
190479
+ segment.lastChunkSequence = sequence;
190480
+ this.messageChunks.push({ segment, text, sequence });
190481
+ }
190482
+ addThoughtChunk(_text) {
190483
+ this.noteOutput();
190484
+ }
190485
+ noteUpdate() {
190486
+ this.lastAcpUpdateAt = new Date().toISOString();
190487
+ }
190488
+ markRetryBoundary() {
190489
+ let discardedMessageBytes = 0;
190490
+ for (const segment of this.segments.values()) {
190491
+ if (segment.retryEpoch !== this.retryEpoch || segment.abandoned)
190492
+ continue;
190493
+ segment.abandoned = true;
190494
+ discardedMessageBytes += Buffer.byteLength(segment.text, "utf8");
190495
+ }
190496
+ this.observedStreamRetries += 1;
190497
+ this.discardedRetryMessageBytes += discardedMessageBytes;
190498
+ this.retryEpoch += 1;
190499
+ return { discardedMessageBytes };
190500
+ }
190501
+ finalRawText() {
190502
+ if (this.observedStreamRetries === 0) {
190503
+ return this.messageChunks.map((chunk) => chunk.text).join("");
190504
+ }
190505
+ const finalAnswer = [...this.segments.values()].filter((segment) => !segment.abandoned && segment.phase === "final_answer").sort((left, right) => left.lastChunkSequence - right.lastChunkSequence).at(-1);
190506
+ if (finalAnswer)
190507
+ return finalAnswer.text;
190508
+ return this.messageChunks.filter((chunk) => !chunk.segment.abandoned && chunk.segment.retryEpoch === this.retryEpoch && chunk.segment.phase === "unknown").sort((left, right) => left.sequence - right.sequence).map((chunk) => chunk.text).join("");
190509
+ }
190510
+ metrics() {
190511
+ return {
190512
+ observedStreamRetries: this.observedStreamRetries,
190513
+ discardedRetryMessageBytes: this.discardedRetryMessageBytes,
190514
+ ...this.firstOutputAt === undefined ? {} : { firstOutputAt: this.firstOutputAt },
190515
+ ...this.lastAcpUpdateAt === undefined ? {} : { lastAcpUpdateAt: this.lastAcpUpdateAt }
190516
+ };
190517
+ }
190518
+ noteOutput() {
190519
+ const timestamp = new Date().toISOString();
190520
+ this.firstOutputAt ??= timestamp;
190521
+ this.lastAcpUpdateAt = timestamp;
190522
+ }
190523
+ }
190524
+
190525
+ // src/acp/codexRetryUpdate.ts
190526
+ var RETRY_ATTEMPT_PATTERN = /Reconnecting\.\.\.\s*(\d+)\/(\d+)/;
190527
+ var DISPLAY_MESSAGE_FIELDS = ["title", "text", "message", "description"];
190528
+ function parseCodexRetryUpdate(update) {
190529
+ if (!isRecord8(update) || update.sessionUpdate !== "session_info_update") {
190530
+ return;
190531
+ }
190532
+ const meta3 = isRecord8(update._meta) ? update._meta : undefined;
190533
+ const codex = meta3 && isRecord8(meta3.codex) ? meta3.codex : undefined;
190534
+ const error51 = codex && isRecord8(codex.error) ? codex.error : undefined;
190535
+ if (error51?.willRetry !== true)
190536
+ return;
190537
+ const rawMessage = typeof error51.message === "string" ? error51.message : findDisplayMessage(update) ?? "model stream retry";
190538
+ const message = sanitizeTextForDisplay(rawMessage) || "model stream retry";
190539
+ const attemptMatch = RETRY_ATTEMPT_PATTERN.exec(message);
190540
+ return {
190541
+ message,
190542
+ ...attemptMatch?.[1] === undefined ? {} : { attempt: Number.parseInt(attemptMatch[1], 10) },
190543
+ ...attemptMatch?.[2] === undefined ? {} : { maxRetries: Number.parseInt(attemptMatch[2], 10) }
190544
+ };
190545
+ }
190546
+ function findDisplayMessage(update) {
190547
+ for (const field of DISPLAY_MESSAGE_FIELDS) {
190548
+ const value = update[field];
190549
+ if (typeof value === "string")
190550
+ return value;
190551
+ }
190552
+ return;
190553
+ }
190554
+ function isRecord8(value) {
190555
+ return value !== null && typeof value === "object" && !Array.isArray(value);
190556
+ }
190557
+
190382
190558
  // src/core/findingAdmission.ts
190383
190559
  import { createHash as createHash2 } from "node:crypto";
190384
190560
  var SAFETY_CATEGORIES = new Set([
@@ -190860,7 +191036,7 @@ function isSeverity(value) {
190860
191036
  return typeof value === "string" && severities.includes(value);
190861
191037
  }
190862
191038
  function normalizeCisaSecureByDesign(value) {
190863
- if (!isRecord8(value))
191039
+ if (!isRecord9(value))
190864
191040
  return;
190865
191041
  const normalized = {};
190866
191042
  const customerSecurityOutcomes = normalizeGateStatus(value.customerSecurityOutcomes);
@@ -190901,7 +191077,7 @@ function isEvidenceQuality(value) {
190901
191077
  return typeof value === "string" && evidenceQualities.includes(value);
190902
191078
  }
190903
191079
  function isStrictAgentOpinion(value) {
190904
- if (!isRecord8(value) || !hasOnlyKeys(value, STRICT_ROOT_KEYS))
191080
+ if (!isRecord9(value) || !hasOnlyKeys(value, STRICT_ROOT_KEYS))
190905
191081
  return false;
190906
191082
  if (typeof value.summary !== "string")
190907
191083
  return false;
@@ -190911,7 +191087,7 @@ function isStrictAgentOpinion(value) {
190911
191087
  return value.cisaSecureByDesign === undefined || isStrictCisaSecureByDesign(value.cisaSecureByDesign);
190912
191088
  }
190913
191089
  function isStrictFinding(value) {
190914
- if (!isRecord8(value) || !hasOnlyKeys(value, STRICT_FINDING_KEYS)) {
191090
+ if (!isRecord9(value) || !hasOnlyKeys(value, STRICT_FINDING_KEYS)) {
190915
191091
  return false;
190916
191092
  }
190917
191093
  if (!isSeverity(value.severity) || !isCategory(value.category) || !isNonEmptyString(value.title) || !isNonEmptyString(value.evidence) || !isNonEmptyString(value.recommendation) || !isConfidence(value.confidence)) {
@@ -190923,11 +191099,11 @@ function isStrictFinding(value) {
190923
191099
  return true;
190924
191100
  }
190925
191101
  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));
191102
+ return Array.isArray(value) && value.every((item) => isRecord9(item) && hasOnlyKeys(item, STRICT_FILE_KEYS) && isNonEmptyString(item.path) && isOptionalLineNumber(item.lineStart) && isOptionalLineNumber(item.lineEnd));
190927
191103
  }
190928
191104
  function isStrictEvidenceRefs(value) {
190929
191105
  return Array.isArray(value) && value.length <= MAX_EVIDENCE_REFS2 && value.every((item) => {
190930
- if (!isRecord8(item) || !hasOnlyKeys(item, STRICT_EVIDENCE_REF_KEYS)) {
191106
+ if (!isRecord9(item) || !hasOnlyKeys(item, STRICT_EVIDENCE_REF_KEYS)) {
190931
191107
  return false;
190932
191108
  }
190933
191109
  if (item.kind !== "file" && item.kind !== "diff_hunk" && item.kind !== "plan_clause") {
@@ -190943,7 +191119,7 @@ function isStrictEvidenceRefs(value) {
190943
191119
  });
190944
191120
  }
190945
191121
  function isStrictCisaSecureByDesign(value) {
190946
- if (!isRecord8(value) || !hasOnlyKeys(value, STRICT_CISA_KEYS))
191122
+ if (!isRecord9(value) || !hasOnlyKeys(value, STRICT_CISA_KEYS))
190947
191123
  return false;
190948
191124
  for (const key of [
190949
191125
  "customerSecurityOutcomes",
@@ -190982,7 +191158,7 @@ function normalizeFindingFiles(value) {
190982
191158
  if (!Array.isArray(value))
190983
191159
  return;
190984
191160
  const files = value.flatMap((item) => {
190985
- if (!isRecord8(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
191161
+ if (!isRecord9(item) || typeof item.path !== "string" || item.path.trim().length === 0) {
190986
191162
  return [];
190987
191163
  }
190988
191164
  const file2 = {
@@ -191002,7 +191178,7 @@ function normalizeEvidenceRefs2(value) {
191002
191178
  if (!Array.isArray(value))
191003
191179
  return;
191004
191180
  const references = value.slice(0, MAX_EVIDENCE_REFS2).flatMap((item) => {
191005
- if (!isRecord8(item) || item.kind !== "file" && item.kind !== "diff_hunk" && item.kind !== "plan_clause") {
191181
+ if (!isRecord9(item) || item.kind !== "file" && item.kind !== "diff_hunk" && item.kind !== "plan_clause") {
191006
191182
  return [];
191007
191183
  }
191008
191184
  const reference = { kind: item.kind };
@@ -191025,18 +191201,23 @@ function normalizeEvidenceRefs2(value) {
191025
191201
  function normalizeLineNumber(value) {
191026
191202
  return typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= MAX_EVIDENCE_LINE2 ? value : undefined;
191027
191203
  }
191028
- function isRecord8(value) {
191204
+ function isRecord9(value) {
191029
191205
  return typeof value === "object" && value !== null && !Array.isArray(value);
191030
191206
  }
191031
191207
 
191032
191208
  // src/acp/AcpAgentProcess.ts
191209
+ var DEFAULT_PROGRESS_HEARTBEAT_MS = 15000;
191210
+ var ACTIVITY_PROGRESS_THROTTLE_MS = 3000;
191211
+
191033
191212
  class SubprocessAcpAgentManager extends BaseAcpAgentManager {
191034
191213
  config;
191035
191214
  parentEnv;
191036
- constructor(config2, parentEnv = process.env) {
191215
+ internalOptions;
191216
+ constructor(config2, parentEnv = process.env, internalOptions = {}) {
191037
191217
  super();
191038
191218
  this.config = config2;
191039
191219
  this.parentEnv = parentEnv;
191220
+ this.internalOptions = internalOptions;
191040
191221
  }
191041
191222
  async runAgent(input) {
191042
191223
  const agentConfig = this.config.agents[input.agent];
@@ -191057,7 +191238,9 @@ class SubprocessAcpAgentManager extends BaseAcpAgentManager {
191057
191238
  agent: input.agent,
191058
191239
  model: agentConfig.model,
191059
191240
  provider,
191060
- preferApiKey: agentConfig.auth.preferApiKey
191241
+ preferApiKey: agentConfig.auth.preferApiKey,
191242
+ openRouter: input.agent === "codex" ? this.config.agents.codex.openRouter : undefined,
191243
+ openRouterBaseUrlForTest: input.agent === "codex" ? this.internalOptions.openRouterBaseUrlForTest : undefined
191061
191244
  });
191062
191245
  } catch (error51) {
191063
191246
  return {
@@ -191072,6 +191255,8 @@ class SubprocessAcpAgentManager extends BaseAcpAgentManager {
191072
191255
  try {
191073
191256
  return await runSubprocessAgent(input.agent, agentConfig, input, launchContext.env, launchContext.executionIdentity);
191074
191257
  } catch (error51) {
191258
+ if (error51 instanceof KyosoCancellationError)
191259
+ throw error51;
191075
191260
  return {
191076
191261
  agent: input.agent,
191077
191262
  role: input.role,
@@ -191084,6 +191269,7 @@ class SubprocessAcpAgentManager extends BaseAcpAgentManager {
191084
191269
  }
191085
191270
  }
191086
191271
  async function runSubprocessAgent(agent, agentConfig, input, env, launchExecutionIdentity) {
191272
+ throwIfAborted(input.signal);
191087
191273
  const startedAt = new Date().toISOString();
191088
191274
  const effectiveTimeoutMs = resolveEffectiveTimeoutMs(input);
191089
191275
  if (effectiveTimeoutMs <= 0) {
@@ -191099,12 +191285,20 @@ async function runSubprocessAgent(agent, agentConfig, input, env, launchExecutio
191099
191285
  }
191100
191286
  };
191101
191287
  }
191102
- return new Promise((resolveResult) => {
191288
+ return new Promise((resolveResult, rejectResult) => {
191289
+ const abortController = new AbortController;
191290
+ let cancelSession;
191291
+ let timeout;
191103
191292
  const child = spawn(agentConfig.command, agentConfig.args, {
191104
191293
  cwd: input.workspaceDir,
191105
191294
  env,
191106
191295
  stdio: ["pipe", "pipe", "pipe"]
191107
191296
  });
191297
+ let termination;
191298
+ const terminate = () => {
191299
+ termination ??= terminateChild(child);
191300
+ return termination;
191301
+ };
191108
191302
  let stdout = "";
191109
191303
  let stderr3 = "";
191110
191304
  let settled = false;
@@ -191120,18 +191314,44 @@ async function runSubprocessAgent(agent, agentConfig, input, env, launchExecutio
191120
191314
  return;
191121
191315
  });
191122
191316
  });
191123
- const abortController = new AbortController;
191317
+ let onAbort;
191318
+ const cleanup = () => {
191319
+ if (timeout)
191320
+ clearTimeout(timeout);
191321
+ if (onAbort)
191322
+ input.signal?.removeEventListener("abort", onAbort);
191323
+ };
191124
191324
  const resolveOnce = (result) => {
191125
191325
  if (settled)
191126
191326
  return;
191127
191327
  settled = true;
191128
- clearTimeout(timeout);
191328
+ cleanup();
191129
191329
  const finalResult = spawned && result.executionIdentity === undefined ? { ...result, executionIdentity: launchExecutionIdentity } : result;
191130
191330
  (startedWrite ?? Promise.resolve()).then(() => resolveResult(finalResult));
191131
191331
  };
191132
- const timeout = setTimeout(() => {
191332
+ const rejectOnce = (error51) => {
191333
+ if (settled)
191334
+ return;
191335
+ settled = true;
191336
+ cleanup();
191337
+ rejectResult(error51);
191338
+ };
191339
+ onAbort = () => {
191340
+ const cancellation = cancellationFromSignal(input.signal);
191341
+ if (timeout)
191342
+ clearTimeout(timeout);
191343
+ abortController.abort(cancellation);
191344
+ cancelSession?.();
191345
+ terminate().then(() => rejectOnce(cancellation));
191346
+ };
191347
+ input.signal?.addEventListener("abort", onAbort, { once: true });
191348
+ if (input.signal?.aborted) {
191349
+ onAbort();
191350
+ return;
191351
+ }
191352
+ timeout = setTimeout(() => {
191133
191353
  abortController.abort(new Error("Kyoso agent timeout"));
191134
- terminateChild(child);
191354
+ terminate();
191135
191355
  const deadlineReached = input.deadlineAtEpochMs !== undefined && Date.now() >= input.deadlineAtEpochMs;
191136
191356
  resolveOnce({
191137
191357
  agent,
@@ -191160,7 +191380,9 @@ async function runSubprocessAgent(agent, agentConfig, input, env, launchExecutio
191160
191380
  error: failure
191161
191381
  });
191162
191382
  });
191163
- runAcpClientWorkflow(child, input, abortController, resolveEffortConfigOption(agent, agentConfig.effort), launchExecutionIdentity).then(({
191383
+ runAcpClientWorkflow(child, input, abortController, resolveEffortConfigOption(agent, agentConfig.effort), launchExecutionIdentity, (cancel) => {
191384
+ cancelSession = cancel;
191385
+ }).then(({
191164
191386
  rawText,
191165
191387
  warnings,
191166
191388
  usage,
@@ -191168,6 +191390,10 @@ async function runSubprocessAgent(agent, agentConfig, input, env, launchExecutio
191168
191390
  thoughtBytes,
191169
191391
  outputBytes,
191170
191392
  outputWarningTriggered,
191393
+ observedStreamRetries,
191394
+ discardedRetryMessageBytes,
191395
+ firstOutputAt,
191396
+ lastAcpUpdateAt,
191171
191397
  stopReason,
191172
191398
  executionIdentity
191173
191399
  }) => {
@@ -191185,6 +191411,10 @@ async function runSubprocessAgent(agent, agentConfig, input, env, launchExecutio
191185
191411
  thoughtBytes,
191186
191412
  outputBytes,
191187
191413
  outputWarningTriggered,
191414
+ observedStreamRetries,
191415
+ ...discardedRetryMessageBytes === 0 ? {} : { discardedRetryMessageBytes },
191416
+ ...firstOutputAt === undefined ? {} : { firstOutputAt },
191417
+ ...lastAcpUpdateAt === undefined ? {} : { lastAcpUpdateAt },
191188
191418
  stopReason,
191189
191419
  executionIdentity,
191190
191420
  ...usage ? { usage } : {},
@@ -191197,6 +191427,10 @@ async function runSubprocessAgent(agent, agentConfig, input, env, launchExecutio
191197
191427
  }
191198
191428
  });
191199
191429
  }).catch((error51) => {
191430
+ if (error51 instanceof KyosoCancellationError) {
191431
+ terminate().then(() => rejectOnce(error51));
191432
+ return;
191433
+ }
191200
191434
  const outputLimitError = findOutputLimitError(error51, abortController);
191201
191435
  if (outputLimitError) {
191202
191436
  stdout = outputLimitError.rawText;
@@ -191211,6 +191445,12 @@ async function runSubprocessAgent(agent, agentConfig, input, env, launchExecutio
191211
191445
  thoughtBytes: outputLimitError.thoughtBytes,
191212
191446
  outputBytes: outputLimitError.outputBytes,
191213
191447
  outputWarningTriggered: outputLimitError.outputWarningTriggered,
191448
+ observedStreamRetries: outputLimitError.metrics.observedStreamRetries,
191449
+ ...outputLimitError.metrics.discardedRetryMessageBytes === 0 ? {} : {
191450
+ discardedRetryMessageBytes: outputLimitError.metrics.discardedRetryMessageBytes
191451
+ },
191452
+ ...outputLimitError.metrics.firstOutputAt === undefined ? {} : { firstOutputAt: outputLimitError.metrics.firstOutputAt },
191453
+ ...outputLimitError.metrics.lastAcpUpdateAt === undefined ? {} : { lastAcpUpdateAt: outputLimitError.metrics.lastAcpUpdateAt },
191214
191454
  stopReason: "cancelled",
191215
191455
  startedAt,
191216
191456
  completedAt: new Date().toISOString(),
@@ -191221,6 +191461,31 @@ async function runSubprocessAgent(agent, agentConfig, input, env, launchExecutio
191221
191461
  });
191222
191462
  return;
191223
191463
  }
191464
+ if (error51 instanceof CodexTerminalSystemError) {
191465
+ stdout = error51.rawText;
191466
+ const failureText2 = [stderr3, formatAgentErrorDetail(error51)].filter((part) => part.trim().length > 0).join(`
191467
+ `);
191468
+ resolveOnce({
191469
+ agent,
191470
+ role: input.role,
191471
+ status: "failed",
191472
+ rawText: stdout,
191473
+ messageBytes: error51.messageBytes,
191474
+ thoughtBytes: error51.thoughtBytes,
191475
+ outputBytes: error51.outputBytes,
191476
+ outputWarningTriggered: error51.outputWarningTriggered,
191477
+ observedStreamRetries: error51.metrics.observedStreamRetries,
191478
+ ...error51.metrics.discardedRetryMessageBytes === 0 ? {} : {
191479
+ discardedRetryMessageBytes: error51.metrics.discardedRetryMessageBytes
191480
+ },
191481
+ ...error51.metrics.firstOutputAt === undefined ? {} : { firstOutputAt: error51.metrics.firstOutputAt },
191482
+ ...error51.metrics.lastAcpUpdateAt === undefined ? {} : { lastAcpUpdateAt: error51.metrics.lastAcpUpdateAt },
191483
+ startedAt,
191484
+ completedAt: new Date().toISOString(),
191485
+ error: buildAgentFailure(failureText2, "Agent process failed.")
191486
+ });
191487
+ return;
191488
+ }
191224
191489
  if (error51 instanceof AcpNdJsonLineLimitError) {
191225
191490
  abortController.abort(error51);
191226
191491
  resolveOnce({
@@ -191252,7 +191517,7 @@ async function runSubprocessAgent(agent, agentConfig, input, env, launchExecutio
191252
191517
  error: buildAgentFailure(failureText, "Agent process failed.")
191253
191518
  });
191254
191519
  }).finally(() => {
191255
- terminateChild(child);
191520
+ terminate();
191256
191521
  });
191257
191522
  child.on("close", (code) => {
191258
191523
  if (settled || code === 0 || abortController.signal.aborted)
@@ -191270,7 +191535,7 @@ async function runSubprocessAgent(agent, agentConfig, input, env, launchExecutio
191270
191535
  });
191271
191536
  });
191272
191537
  }
191273
- async function runAcpClientWorkflow(child, input, abortController, configOption, launchExecutionIdentity) {
191538
+ async function runAcpClientWorkflow(child, input, abortController, configOption, launchExecutionIdentity, onSessionReady) {
191274
191539
  if (!child.stdin || !child.stdout) {
191275
191540
  throw new Error("Agent process did not expose stdio streams.");
191276
191541
  }
@@ -191318,6 +191583,15 @@ async function runAcpClientWorkflow(child, input, abortController, configOption,
191318
191583
  kyosoReadOnly: true
191319
191584
  }
191320
191585
  }).withSession(async (session) => {
191586
+ const cancelSession = () => {
191587
+ ctx.notify(methods.agent.session.cancel, {
191588
+ sessionId: session.sessionId
191589
+ }).catch(() => {
191590
+ return;
191591
+ });
191592
+ };
191593
+ onSessionReady(cancelSession);
191594
+ throwIfAborted(input.signal);
191321
191595
  const warnings = [];
191322
191596
  if (configOption) {
191323
191597
  await ctx.request(methods.agent.session.setConfigOption, { sessionId: session.sessionId, ...configOption }, { cancellationSignal: abortController.signal }).catch((error51) => {
@@ -191330,61 +191604,154 @@ async function runAcpClientWorkflow(child, input, abortController, configOption,
191330
191604
  console.error(`kyoso: ${warning}`);
191331
191605
  });
191332
191606
  }
191333
- const promptResponse = session.prompt(input.prompt, {
191334
- cancellationSignal: abortController.signal
191335
- });
191336
- promptResponse.catch(() => {
191337
- return;
191338
- });
191339
- let rawText = "";
191607
+ const accumulator = new AgentOutputAccumulator;
191340
191608
  let messageBytes = 0;
191341
191609
  let thoughtBytes = 0;
191342
191610
  let outputBytes = 0;
191343
191611
  let outputWarningTriggered = false;
191344
- for (;; ) {
191345
- const message = await session.nextUpdate();
191346
- if (message.kind === "stop") {
191347
- const usage = normalizeUsage(message.response.usage);
191348
- return {
191349
- rawText,
191350
- warnings,
191351
- ...usage ? { usage } : {},
191352
- messageBytes,
191353
- thoughtBytes,
191354
- outputBytes,
191355
- outputWarningTriggered,
191356
- stopReason: message.stopReason,
191357
- executionIdentity: withReportedExecutionIdentity(launchExecutionIdentity, message.response._meta)
191358
- };
191359
- }
191360
- const update = message.update;
191361
- if (update.sessionUpdate !== "agent_message_chunk" && update.sessionUpdate !== "agent_thought_chunk" || update.content.type !== "text") {
191362
- continue;
191363
- }
191364
- const chunkBytes = Buffer.byteLength(update.content.text, "utf8");
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;
191370
- if (input.maxOutputBytes !== undefined && nextOutputBytes > input.maxOutputBytes) {
191371
- const retainedRawText = isMessage ? `${rawText}${utf8Prefix(update.content.text, input.maxOutputBytes - outputBytes)}` : rawText;
191372
- await ctx.notify(methods.agent.session.cancel, {
191373
- sessionId: session.sessionId
191374
- }).catch(() => {
191612
+ let codexReportedSystemError = false;
191613
+ const sessionStartedAtEpochMs = Date.now();
191614
+ let lastActivityAtEpochMs = 0;
191615
+ const emitProgress = (event) => {
191616
+ try {
191617
+ const progress = input.onProgress?.(event);
191618
+ Promise.resolve(progress).catch(() => {
191375
191619
  return;
191376
191620
  });
191377
- const error51 = new AgentOutputLimitError(retainedRawText, nextMessageBytes, nextThoughtBytes, nextOutputBytes, input.maxOutputBytes, nextOutputWarningTriggered);
191378
- abortController.abort(error51);
191621
+ } catch {}
191622
+ };
191623
+ const heartbeatMs = input.heartbeatMs ?? DEFAULT_PROGRESS_HEARTBEAT_MS;
191624
+ const heartbeat = input.onProgress && heartbeatMs > 0 ? setInterval(() => {
191625
+ const now = Date.now();
191626
+ const lastAcpUpdateAt = accumulator.metrics().lastAcpUpdateAt;
191627
+ const lastUpdateEpochMs = lastAcpUpdateAt ? Date.parse(lastAcpUpdateAt) : sessionStartedAtEpochMs;
191628
+ emitProgress({
191629
+ type: "agent_waiting",
191630
+ agent: input.agent,
191631
+ elapsedMs: Math.max(0, now - sessionStartedAtEpochMs),
191632
+ sinceLastAcpUpdateMs: Math.max(0, now - (Number.isFinite(lastUpdateEpochMs) ? lastUpdateEpochMs : sessionStartedAtEpochMs)),
191633
+ ...input.streamIdleTimeoutMs === undefined ? {} : { streamIdleTimeoutMs: input.streamIdleTimeoutMs },
191634
+ timestamp: new Date(now).toISOString()
191635
+ });
191636
+ }, heartbeatMs) : undefined;
191637
+ heartbeat?.unref?.();
191638
+ const stopHeartbeat = () => {
191639
+ if (heartbeat)
191640
+ clearInterval(heartbeat);
191641
+ };
191642
+ input.signal?.addEventListener("abort", stopHeartbeat, { once: true });
191643
+ try {
191644
+ const promptResponse = session.prompt(input.prompt, {
191645
+ cancellationSignal: abortController.signal
191646
+ });
191647
+ const promptCompletion = promptResponse.catch((error51) => {
191648
+ if (input.signal?.aborted) {
191649
+ throw cancellationFromSignal(input.signal);
191650
+ }
191651
+ if (abortController.signal.aborted) {
191652
+ const reason = abortController.signal.reason;
191653
+ if (reason !== undefined)
191654
+ throw reason;
191655
+ }
191379
191656
  throw error51;
191657
+ });
191658
+ const promptFailure = promptCompletion.then(() => new Promise(() => {
191659
+ return;
191660
+ }));
191661
+ for (;; ) {
191662
+ const message = await Promise.race([
191663
+ session.nextUpdate(),
191664
+ promptFailure
191665
+ ]);
191666
+ if (message.kind === "stop") {
191667
+ await promptCompletion;
191668
+ if (codexReportedSystemError) {
191669
+ throw new CodexTerminalSystemError(accumulator.finalRawText(), messageBytes, thoughtBytes, outputBytes, outputWarningTriggered, accumulator.metrics());
191670
+ }
191671
+ const usage = normalizeUsage(message.response.usage);
191672
+ return {
191673
+ rawText: accumulator.finalRawText(),
191674
+ warnings,
191675
+ ...usage ? { usage } : {},
191676
+ messageBytes,
191677
+ thoughtBytes,
191678
+ outputBytes,
191679
+ outputWarningTriggered,
191680
+ ...accumulator.metrics(),
191681
+ stopReason: message.stopReason,
191682
+ executionIdentity: withReportedExecutionIdentity(launchExecutionIdentity, message.response._meta)
191683
+ };
191684
+ }
191685
+ const update = message.update;
191686
+ accumulator.noteUpdate();
191687
+ const retry = parseCodexRetryUpdate(update);
191688
+ if (retry) {
191689
+ codexReportedSystemError = false;
191690
+ const boundary = accumulator.markRetryBoundary();
191691
+ emitProgress({
191692
+ type: "agent_retrying",
191693
+ agent: input.agent,
191694
+ observedRetry: accumulator.metrics().observedStreamRetries,
191695
+ ...retry.attempt === undefined ? {} : { attempt: retry.attempt },
191696
+ ...retry.maxRetries === undefined ? {} : { maxRetries: retry.maxRetries },
191697
+ reason: retry.message,
191698
+ discardedMessageBytes: boundary.discardedMessageBytes,
191699
+ timestamp: new Date().toISOString()
191700
+ });
191701
+ continue;
191702
+ }
191703
+ const codexThreadStatus = readCodexThreadStatus(update);
191704
+ if (codexThreadStatus !== undefined) {
191705
+ codexReportedSystemError = codexThreadStatus === "systemError";
191706
+ continue;
191707
+ }
191708
+ if (update.sessionUpdate !== "agent_message_chunk" && update.sessionUpdate !== "agent_thought_chunk" || update.content.type !== "text") {
191709
+ continue;
191710
+ }
191711
+ const chunkBytes = Buffer.byteLength(update.content.text, "utf8");
191712
+ const isMessage = update.sessionUpdate === "agent_message_chunk";
191713
+ const nextMessageBytes = messageBytes + (isMessage ? chunkBytes : 0);
191714
+ const nextThoughtBytes = thoughtBytes + (isMessage ? 0 : chunkBytes);
191715
+ const nextOutputBytes = nextMessageBytes + nextThoughtBytes;
191716
+ const nextOutputWarningTriggered = outputWarningTriggered || input.warnOutputBytes !== undefined && nextOutputBytes >= input.warnOutputBytes;
191717
+ if (input.maxOutputBytes !== undefined && nextOutputBytes > input.maxOutputBytes) {
191718
+ if (isMessage) {
191719
+ accumulator.addMessageChunk(utf8Prefix(update.content.text, input.maxOutputBytes - outputBytes), readChunkMeta(update));
191720
+ }
191721
+ const retainedRawText = accumulator.finalRawText();
191722
+ await ctx.notify(methods.agent.session.cancel, {
191723
+ sessionId: session.sessionId
191724
+ }).catch(() => {
191725
+ return;
191726
+ });
191727
+ const error51 = new AgentOutputLimitError(retainedRawText, nextMessageBytes, nextThoughtBytes, nextOutputBytes, input.maxOutputBytes, nextOutputWarningTriggered, accumulator.metrics());
191728
+ abortController.abort(error51);
191729
+ throw error51;
191730
+ }
191731
+ if (isMessage) {
191732
+ accumulator.addMessageChunk(update.content.text, readChunkMeta(update));
191733
+ } else {
191734
+ accumulator.addThoughtChunk(update.content.text);
191735
+ }
191736
+ messageBytes = nextMessageBytes;
191737
+ thoughtBytes = nextThoughtBytes;
191738
+ outputBytes = nextOutputBytes;
191739
+ outputWarningTriggered = nextOutputWarningTriggered;
191740
+ const now = Date.now();
191741
+ if (now - lastActivityAtEpochMs >= ACTIVITY_PROGRESS_THROTTLE_MS) {
191742
+ lastActivityAtEpochMs = now;
191743
+ emitProgress({
191744
+ type: "agent_activity",
191745
+ agent: input.agent,
191746
+ activity: isMessage ? "message" : "thought",
191747
+ totalOutputBytes: outputBytes,
191748
+ timestamp: new Date(now).toISOString()
191749
+ });
191750
+ }
191380
191751
  }
191381
- if (isMessage) {
191382
- rawText += update.content.text;
191383
- }
191384
- messageBytes = nextMessageBytes;
191385
- thoughtBytes = nextThoughtBytes;
191386
- outputBytes = nextOutputBytes;
191387
- outputWarningTriggered = nextOutputWarningTriggered;
191752
+ } finally {
191753
+ stopHeartbeat();
191754
+ input.signal?.removeEventListener("abort", stopHeartbeat);
191388
191755
  }
191389
191756
  });
191390
191757
  });
@@ -191408,7 +191775,8 @@ class AgentOutputLimitError extends Error {
191408
191775
  outputBytes;
191409
191776
  maxOutputBytes;
191410
191777
  outputWarningTriggered;
191411
- constructor(rawText, messageBytes, thoughtBytes, outputBytes, maxOutputBytes, outputWarningTriggered) {
191778
+ metrics;
191779
+ constructor(rawText, messageBytes, thoughtBytes, outputBytes, maxOutputBytes, outputWarningTriggered, metrics) {
191412
191780
  super(`Agent output exceeded ${maxOutputBytes} bytes.`);
191413
191781
  this.rawText = rawText;
191414
191782
  this.messageBytes = messageBytes;
@@ -191416,15 +191784,40 @@ class AgentOutputLimitError extends Error {
191416
191784
  this.outputBytes = outputBytes;
191417
191785
  this.maxOutputBytes = maxOutputBytes;
191418
191786
  this.outputWarningTriggered = outputWarningTriggered;
191787
+ this.metrics = metrics;
191419
191788
  this.name = "AgentOutputLimitError";
191420
191789
  }
191421
191790
  }
191791
+
191792
+ class CodexTerminalSystemError extends Error {
191793
+ rawText;
191794
+ messageBytes;
191795
+ thoughtBytes;
191796
+ outputBytes;
191797
+ outputWarningTriggered;
191798
+ metrics;
191799
+ constructor(rawText, messageBytes, thoughtBytes, outputBytes, outputWarningTriggered, metrics) {
191800
+ super(sanitizeTextForDisplay(rawText) || "Codex ACP reported a terminal system error.");
191801
+ this.rawText = rawText;
191802
+ this.messageBytes = messageBytes;
191803
+ this.thoughtBytes = thoughtBytes;
191804
+ this.outputBytes = outputBytes;
191805
+ this.outputWarningTriggered = outputWarningTriggered;
191806
+ this.metrics = metrics;
191807
+ this.name = "CodexTerminalSystemError";
191808
+ }
191809
+ }
191422
191810
  function findOutputLimitError(error51, abortController) {
191423
191811
  if (error51 instanceof AgentOutputLimitError)
191424
191812
  return error51;
191425
191813
  const reason = abortController.signal.reason;
191426
191814
  return reason instanceof AgentOutputLimitError ? reason : undefined;
191427
191815
  }
191816
+ function cancellationFromSignal(signal) {
191817
+ if (signal?.reason instanceof KyosoCancellationError)
191818
+ return signal.reason;
191819
+ return new KyosoCancellationError(typeof signal?.reason === "string" ? signal.reason : undefined);
191820
+ }
191428
191821
  function resolveEffectiveTimeoutMs(input) {
191429
191822
  const deadlineRemaining = input.deadlineAtEpochMs === undefined ? Number.POSITIVE_INFINITY : input.deadlineAtEpochMs - Date.now();
191430
191823
  return Math.max(0, Math.min(input.timeoutMs, deadlineRemaining));
@@ -191433,7 +191826,7 @@ function normalizeUsage(usage) {
191433
191826
  return normalizeModelTokenUsage(usage);
191434
191827
  }
191435
191828
  function withReportedExecutionIdentity(identity, metadata) {
191436
- const record2 = isRecord9(metadata) ? metadata : {};
191829
+ const record2 = isRecord10(metadata) ? metadata : {};
191437
191830
  return createModelExecutionIdentity({
191438
191831
  providerRoute: identity.providerRoute,
191439
191832
  requestedModel: identity.requestedModel,
@@ -191441,7 +191834,26 @@ function withReportedExecutionIdentity(identity, metadata) {
191441
191834
  reportedModel: record2.model
191442
191835
  });
191443
191836
  }
191444
- function isRecord9(value) {
191837
+ function readChunkMeta(update) {
191838
+ const record2 = isRecord10(update) ? update : {};
191839
+ const metadata = isRecord10(record2._meta) ? record2._meta : {};
191840
+ const codex = isRecord10(metadata.codex) ? metadata.codex : {};
191841
+ const phase = codex.phase === "commentary" || codex.phase === "final_answer" ? codex.phase : "unknown";
191842
+ return {
191843
+ ...typeof record2.messageId === "string" ? { messageId: record2.messageId } : {},
191844
+ phase
191845
+ };
191846
+ }
191847
+ function readCodexThreadStatus(update) {
191848
+ const record2 = isRecord10(update) ? update : {};
191849
+ if (record2.sessionUpdate !== "session_info_update")
191850
+ return;
191851
+ const metadata = isRecord10(record2._meta) ? record2._meta : {};
191852
+ const codex = isRecord10(metadata.codex) ? metadata.codex : {};
191853
+ const threadStatus = isRecord10(codex.threadStatus) ? codex.threadStatus : {};
191854
+ return typeof threadStatus.type === "string" ? threadStatus.type : undefined;
191855
+ }
191856
+ function isRecord10(value) {
191445
191857
  return value !== null && typeof value === "object" && !Array.isArray(value);
191446
191858
  }
191447
191859
  function resolveEffortConfigOption(agent, effort) {
@@ -191507,14 +191919,40 @@ function assertWithinWorkspace(workspaceRoot, absolute) {
191507
191919
  }
191508
191920
  }
191509
191921
  function terminateChild(child) {
191510
- if (child.exitCode !== null || child.signalCode !== null)
191511
- return;
191512
- child.kill("SIGTERM");
191513
- const killTimer = setTimeout(() => {
191514
- if (child.exitCode === null && child.signalCode === null)
191922
+ if (child.exitCode !== null || child.signalCode !== null) {
191923
+ return Promise.resolve();
191924
+ }
191925
+ return new Promise((resolve4) => {
191926
+ let settled = false;
191927
+ let killTimer;
191928
+ let escalationTimer;
191929
+ let onClose;
191930
+ const settle = () => {
191931
+ if (settled)
191932
+ return;
191933
+ settled = true;
191934
+ if (killTimer)
191935
+ clearTimeout(killTimer);
191936
+ if (escalationTimer)
191937
+ clearTimeout(escalationTimer);
191938
+ if (onClose)
191939
+ child.off("close", onClose);
191940
+ resolve4();
191941
+ };
191942
+ onClose = () => settle();
191943
+ child.once("close", onClose);
191944
+ child.kill("SIGTERM");
191945
+ killTimer = setTimeout(() => {
191946
+ if (child.exitCode !== null || child.signalCode !== null) {
191947
+ settle();
191948
+ return;
191949
+ }
191515
191950
  child.kill("SIGKILL");
191516
- }, 2000);
191517
- killTimer.unref();
191951
+ escalationTimer = setTimeout(settle, 500);
191952
+ escalationTimer.unref();
191953
+ }, 2000);
191954
+ killTimer.unref();
191955
+ });
191518
191956
  }
191519
191957
  function isMissingPathError2(error51) {
191520
191958
  return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "ENOENT";
@@ -191599,6 +192037,7 @@ class FakeAgentManager extends BaseAcpAgentManager {
191599
192037
  this.verifierScenarios = verifierScenarios;
191600
192038
  }
191601
192039
  async runAgent(input) {
192040
+ throwIfAborted(input.signal);
191602
192041
  this.calls.push(input);
191603
192042
  const startedAt = new Date().toISOString();
191604
192043
  if (input.role === "finding_verifier") {
@@ -192294,18 +192733,6 @@ import { basename, dirname as dirname4, isAbsolute as isAbsolute4, join as join3
192294
192733
 
192295
192734
  // src/context/pathPolicy.ts
192296
192735
  import { normalize, sep } from "node:path";
192297
-
192298
- // src/core/errors.ts
192299
- class KyosoRequestError extends Error {
192300
- code;
192301
- constructor(message, code) {
192302
- super(message);
192303
- this.code = code;
192304
- this.name = "KyosoRequestError";
192305
- }
192306
- }
192307
-
192308
- // src/context/pathPolicy.ts
192309
192736
  function normalizeRelativePath(path) {
192310
192737
  const normalized = normalize(path).replaceAll("\\", "/");
192311
192738
  if (normalized === "." || normalized.startsWith("..") || normalized.includes(`${sep}..${sep}`) || normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized)) {
@@ -192961,6 +193388,161 @@ function buildContext(request, options) {
192961
193388
  }
192962
193389
  }
192963
193390
 
193391
+ // src/core/progressDispatcher.ts
193392
+ var DEFAULT_SINK_TIMEOUT_MS = 2000;
193393
+ var DEFAULT_MAX_QUEUE = 128;
193394
+ function isTransientEvent(event) {
193395
+ return event.type === "agent_activity" || event.type === "agent_waiting";
193396
+ }
193397
+ function isSameCoalescibleEvent(previous, next) {
193398
+ if (!previous || !isTransientEvent(previous) || !isTransientEvent(next)) {
193399
+ return false;
193400
+ }
193401
+ return previous.type === next.type && previous.agent === next.agent;
193402
+ }
193403
+ function withoutThrowing(callback) {
193404
+ try {
193405
+ callback?.();
193406
+ } catch {}
193407
+ }
193408
+ function createProgressDispatcher(sink, options = {}) {
193409
+ if (!sink) {
193410
+ return {
193411
+ emit: () => {
193412
+ return;
193413
+ },
193414
+ flush: async () => {
193415
+ return;
193416
+ }
193417
+ };
193418
+ }
193419
+ const sinkTimeoutMs = options.sinkTimeoutMs ?? DEFAULT_SINK_TIMEOUT_MS;
193420
+ const maxQueue = options.maxQueue ?? DEFAULT_MAX_QUEUE;
193421
+ const queue = [];
193422
+ const idleResolvers = new Set;
193423
+ let disabled = false;
193424
+ let processing = false;
193425
+ const notifyIdle = () => {
193426
+ if (processing || queue.length > 0)
193427
+ return;
193428
+ for (const resolve6 of idleResolvers)
193429
+ resolve6();
193430
+ idleResolvers.clear();
193431
+ };
193432
+ const waitForIdle = () => {
193433
+ if (!processing && queue.length === 0)
193434
+ return Promise.resolve();
193435
+ return new Promise((resolve6) => idleResolvers.add(resolve6));
193436
+ };
193437
+ const disable = (reason) => {
193438
+ if (disabled)
193439
+ return;
193440
+ disabled = true;
193441
+ queue.length = 0;
193442
+ withoutThrowing(() => options.onSinkDisabled?.(reason));
193443
+ notifyIdle();
193444
+ };
193445
+ const deliver = async (event) => {
193446
+ let timeout;
193447
+ try {
193448
+ const delivery = Promise.resolve().then(() => sink(event));
193449
+ const timeoutResult = new Promise((resolve6) => {
193450
+ timeout = setTimeout(() => resolve6("timeout"), sinkTimeoutMs);
193451
+ timeout.unref?.();
193452
+ });
193453
+ const result = await Promise.race([
193454
+ delivery.then(() => "delivered"),
193455
+ timeoutResult
193456
+ ]);
193457
+ if (result === "timeout") {
193458
+ disable(`Progress sink timed out after ${sinkTimeoutMs}ms.`);
193459
+ return false;
193460
+ }
193461
+ return true;
193462
+ } catch {
193463
+ disable("Progress sink threw while handling an event.");
193464
+ return false;
193465
+ } finally {
193466
+ if (timeout)
193467
+ clearTimeout(timeout);
193468
+ }
193469
+ };
193470
+ const drain = async () => {
193471
+ try {
193472
+ while (!disabled && queue.length > 0) {
193473
+ const event = queue.shift();
193474
+ if (!event)
193475
+ continue;
193476
+ if (!await deliver(event))
193477
+ break;
193478
+ }
193479
+ } finally {
193480
+ processing = false;
193481
+ if (!disabled && queue.length > 0) {
193482
+ startDrain();
193483
+ } else {
193484
+ notifyIdle();
193485
+ }
193486
+ }
193487
+ };
193488
+ const startDrain = () => {
193489
+ if (processing || disabled || queue.length === 0)
193490
+ return;
193491
+ processing = true;
193492
+ drain();
193493
+ };
193494
+ const makeRoomForMilestone = () => {
193495
+ const transientIndex = queue.findIndex(isTransientEvent);
193496
+ if (transientIndex >= 0) {
193497
+ queue.splice(transientIndex, 1);
193498
+ return true;
193499
+ }
193500
+ if (queue.length < maxQueue)
193501
+ return true;
193502
+ queue.shift();
193503
+ return true;
193504
+ };
193505
+ return {
193506
+ emit(event) {
193507
+ try {
193508
+ if (disabled)
193509
+ return;
193510
+ const previous = queue.at(-1);
193511
+ if (isSameCoalescibleEvent(previous, event)) {
193512
+ queue[queue.length - 1] = event;
193513
+ } else if (queue.length < maxQueue) {
193514
+ queue.push(event);
193515
+ } else if (isTransientEvent(event)) {
193516
+ return;
193517
+ } else if (makeRoomForMilestone()) {
193518
+ queue.push(event);
193519
+ }
193520
+ startDrain();
193521
+ } catch {}
193522
+ },
193523
+ async flush(maxWaitMs = DEFAULT_SINK_TIMEOUT_MS) {
193524
+ try {
193525
+ const idle = waitForIdle();
193526
+ if (maxWaitMs <= 0)
193527
+ return;
193528
+ let timeout;
193529
+ try {
193530
+ await Promise.race([
193531
+ idle,
193532
+ new Promise((resolve6) => {
193533
+ timeout = setTimeout(resolve6, maxWaitMs);
193534
+ timeout.unref?.();
193535
+ })
193536
+ ]);
193537
+ } finally {
193538
+ if (timeout)
193539
+ clearTimeout(timeout);
193540
+ }
193541
+ } catch {}
193542
+ }
193543
+ };
193544
+ }
193545
+
192964
193546
  // src/core/validateRequest.ts
192965
193547
  function validateReviewRequest(tool, request) {
192966
193548
  if (typeof request.goal !== "string" || request.goal.trim().length === 0) {
@@ -192976,7 +193558,7 @@ function validateReviewContract(request) {
192976
193558
  const contract = request.reviewContract;
192977
193559
  if (contract === undefined)
192978
193560
  return;
192979
- if (!isRecord10(contract)) {
193561
+ if (!isRecord11(contract)) {
192980
193562
  throw new KyosoRequestError("reviewContract must be an object", "VALIDATION_ERROR");
192981
193563
  }
192982
193564
  const allowedKeys = new Set(["focus", "nonGoals", "acceptedRisks"]);
@@ -192993,7 +193575,7 @@ function validateReviewContract(request) {
192993
193575
  throw new KyosoRequestError("reviewContract.nonGoals must contain at most 20 non-empty strings of 500 characters or fewer", "VALIDATION_ERROR");
192994
193576
  }
192995
193577
  const acceptedRisks = contract.acceptedRisks;
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))) {
193578
+ if (acceptedRisks !== undefined && (!Array.isArray(acceptedRisks) || acceptedRisks.length > 20 || acceptedRisks.some((risk) => !isRecord11(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))) {
192997
193579
  throw new KyosoRequestError("reviewContract.acceptedRisks must contain valid finding fingerprints and bounded rationales", "VALIDATION_ERROR");
192998
193580
  }
192999
193581
  }
@@ -193005,13 +193587,13 @@ function validateSelectedFiles(request) {
193005
193587
  throw new KyosoRequestError("selectedFiles must be an array", "VALIDATION_ERROR");
193006
193588
  }
193007
193589
  for (const file2 of selectedFiles) {
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") {
193590
+ if (!isRecord11(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") {
193009
193591
  throw new KyosoRequestError("selectedFiles entries require string path/content and valid optional metadata", "VALIDATION_ERROR");
193010
193592
  }
193011
193593
  normalizeRelativePath(file2.path);
193012
193594
  }
193013
193595
  }
193014
- function isRecord10(value) {
193596
+ function isRecord11(value) {
193015
193597
  return typeof value === "object" && value !== null && !Array.isArray(value);
193016
193598
  }
193017
193599
 
@@ -193272,7 +193854,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
193272
193854
  const parsed = JSON.parse(json2);
193273
193855
  const summaryText = typeof parsed.summaryText === "string" && parsed.summaryText.trim().length > 0 ? sanitizeText(parsed.summaryText) : fallbackSummaryText;
193274
193856
  const disagreementComments = Array.isArray(parsed.disagreementComments) ? parsed.disagreementComments.flatMap((item) => {
193275
- if (!isRecord11(item) || typeof item.topic !== "string" || typeof item.judgeComment !== "string") {
193857
+ if (!isRecord12(item) || typeof item.topic !== "string" || typeof item.judgeComment !== "string") {
193276
193858
  return [];
193277
193859
  }
193278
193860
  return [
@@ -193288,7 +193870,7 @@ function parseJudgeOutput(text, fallbackSummaryText) {
193288
193870
  return { summaryText, disagreementComments, analysis };
193289
193871
  }
193290
193872
  function parseAnalysis(value) {
193291
- if (!isRecord11(value))
193873
+ if (!isRecord12(value))
193292
193874
  return;
193293
193875
  if (!Array.isArray(value.blindSpots) || !Array.isArray(value.contradictions) || !Array.isArray(value.partialCoverage)) {
193294
193876
  return;
@@ -193296,7 +193878,7 @@ function parseAnalysis(value) {
193296
193878
  return {
193297
193879
  blindSpots: value.blindSpots.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => typeof item === "string" ? [sanitizeAnalysisText(item)] : []),
193298
193880
  contradictions: value.contradictions.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
193299
- if (!isRecord11(item) || typeof item.topic !== "string" || typeof item.detail !== "string") {
193881
+ if (!isRecord12(item) || typeof item.topic !== "string" || typeof item.detail !== "string") {
193300
193882
  return [];
193301
193883
  }
193302
193884
  return [
@@ -193307,7 +193889,7 @@ function parseAnalysis(value) {
193307
193889
  ];
193308
193890
  }),
193309
193891
  partialCoverage: value.partialCoverage.slice(0, ANALYSIS_MAX_ITEMS).flatMap((item) => {
193310
- if (!isRecord11(item) || typeof item.note !== "string")
193892
+ if (!isRecord12(item) || typeof item.note !== "string")
193311
193893
  return [];
193312
193894
  const findingId = typeof item.findingId === "string" ? sanitizeAnalysisText(item.findingId) : undefined;
193313
193895
  return [
@@ -193354,10 +193936,31 @@ function extractFirstJsonObject2(text) {
193354
193936
  }
193355
193937
  return;
193356
193938
  }
193357
- function isRecord11(value) {
193939
+ function isRecord12(value) {
193358
193940
  return typeof value === "object" && value !== null && !Array.isArray(value);
193359
193941
  }
193360
193942
 
193943
+ // src/judge/signals.ts
193944
+ function linkSignals(timeoutMs, external3) {
193945
+ const controller = new AbortController;
193946
+ const timeout = setTimeout(() => controller.abort(new Error("judge timeout")), timeoutMs);
193947
+ timeout.unref?.();
193948
+ const onExternalAbort = () => controller.abort(external3?.reason);
193949
+ if (external3) {
193950
+ if (external3.aborted)
193951
+ onExternalAbort();
193952
+ else
193953
+ external3.addEventListener("abort", onExternalAbort, { once: true });
193954
+ }
193955
+ return {
193956
+ signal: controller.signal,
193957
+ cleanup() {
193958
+ clearTimeout(timeout);
193959
+ external3?.removeEventListener("abort", onExternalAbort);
193960
+ }
193961
+ };
193962
+ }
193963
+
193361
193964
  // src/judge/anthropic.ts
193362
193965
  var DEFAULT_ANTHROPIC_JUDGE_MODEL = "claude-haiku-4-5";
193363
193966
  function resolveAnthropicJudgeModel(env) {
@@ -193368,39 +193971,45 @@ async function runAnthropicJudge(input, timeoutMs) {
193368
193971
  if (!apiKey)
193369
193972
  throw new Error("ANTHROPIC_API_KEY is not configured.");
193370
193973
  const requestedModel = resolveAnthropicJudgeModel(input.env);
193371
- const response = await fetchWithTimeout(`${input.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com"}/v1/messages`, {
193372
- method: "POST",
193373
- headers: {
193374
- "x-api-key": apiKey,
193375
- "anthropic-version": "2023-06-01",
193376
- "content-type": "application/json"
193377
- },
193378
- body: JSON.stringify({
193379
- model: requestedModel,
193380
- max_tokens: JUDGE_MAX_OUTPUT_TOKENS,
193381
- temperature: 0,
193382
- messages: [
193383
- {
193384
- role: "user",
193385
- content: buildJudgePrompt(input.tool, input.result, input.summaryText, input.agentFindings)
193386
- }
193387
- ]
193388
- })
193389
- }, timeoutMs);
193390
- if (!response.ok)
193391
- throw new Error(`Anthropic judge failed with HTTP ${response.status}.`);
193392
- const payload = await response.json();
193393
- const content = payload.content?.find((item) => item.type === "text" && item.text)?.text;
193394
- if (!content)
193395
- throw new Error("Anthropic judge response did not include text content.");
193396
- const usage = normalizeUsage2(payload.usage);
193397
- const reportedModel = typeof payload.model === "string" ? payload.model : undefined;
193398
- return {
193399
- output: parseJudgeOutput(content, input.summaryText),
193400
- requestedModel,
193401
- ...reportedModel ? { reportedModel } : {},
193402
- ...usage ? { usage } : {}
193403
- };
193974
+ const { signal, cleanup } = linkSignals(timeoutMs, input.signal);
193975
+ try {
193976
+ const response = await fetch(`${input.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com"}/v1/messages`, {
193977
+ method: "POST",
193978
+ headers: {
193979
+ "x-api-key": apiKey,
193980
+ "anthropic-version": "2023-06-01",
193981
+ "content-type": "application/json"
193982
+ },
193983
+ body: JSON.stringify({
193984
+ model: requestedModel,
193985
+ max_tokens: JUDGE_MAX_OUTPUT_TOKENS,
193986
+ temperature: 0,
193987
+ messages: [
193988
+ {
193989
+ role: "user",
193990
+ content: buildJudgePrompt(input.tool, input.result, input.summaryText, input.agentFindings)
193991
+ }
193992
+ ]
193993
+ }),
193994
+ signal
193995
+ });
193996
+ if (!response.ok)
193997
+ throw new Error(`Anthropic judge failed with HTTP ${response.status}.`);
193998
+ const payload = await response.json();
193999
+ const content = payload.content?.find((item) => item.type === "text" && item.text)?.text;
194000
+ if (!content)
194001
+ throw new Error("Anthropic judge response did not include text content.");
194002
+ const usage = normalizeUsage2(payload.usage);
194003
+ const reportedModel = typeof payload.model === "string" ? payload.model : undefined;
194004
+ return {
194005
+ output: parseJudgeOutput(content, input.summaryText),
194006
+ requestedModel,
194007
+ ...reportedModel ? { reportedModel } : {},
194008
+ ...usage ? { usage } : {}
194009
+ };
194010
+ } finally {
194011
+ cleanup();
194012
+ }
193404
194013
  }
193405
194014
  function normalizeUsage2(usage) {
193406
194015
  if (!usage)
@@ -193412,16 +194021,6 @@ function normalizeUsage2(usage) {
193412
194021
  cachedWriteTokens: usage.cache_creation_input_tokens
193413
194022
  });
193414
194023
  }
193415
- async function fetchWithTimeout(url2, init, timeoutMs) {
193416
- const controller = new AbortController;
193417
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
193418
- timeout.unref?.();
193419
- try {
193420
- return await fetch(url2, { ...init, signal: controller.signal });
193421
- } finally {
193422
- clearTimeout(timeout);
193423
- }
193424
- }
193425
194024
 
193426
194025
  // src/judge/deterministicFallback.ts
193427
194026
  function runDeterministicJudge(result, summaryText) {
@@ -193444,39 +194043,45 @@ async function runOpenAiJudge(input, timeoutMs) {
193444
194043
  if (!apiKey)
193445
194044
  throw new Error("OPENAI_API_KEY is not configured.");
193446
194045
  const requestedModel = resolveOpenAiJudgeModel(input.env);
193447
- const response = await fetchWithTimeout2(`${input.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1"}/chat/completions`, {
193448
- method: "POST",
193449
- headers: {
193450
- authorization: `Bearer ${apiKey}`,
193451
- "content-type": "application/json"
193452
- },
193453
- body: JSON.stringify({
193454
- model: requestedModel,
193455
- response_format: { type: "json_object" },
193456
- messages: [
193457
- {
193458
- role: "user",
193459
- content: buildJudgePrompt(input.tool, input.result, input.summaryText, input.agentFindings)
193460
- }
193461
- ],
193462
- max_completion_tokens: JUDGE_MAX_OUTPUT_TOKENS,
193463
- temperature: 0
193464
- })
193465
- }, timeoutMs);
193466
- if (!response.ok)
193467
- throw new Error(`OpenAI judge failed with HTTP ${response.status}.`);
193468
- const payload = await response.json();
193469
- const content = payload.choices?.[0]?.message?.content;
193470
- if (!content)
193471
- throw new Error("OpenAI judge response did not include content.");
193472
- const usage = normalizeUsage3(payload.usage);
193473
- const reportedModel = typeof payload.model === "string" ? payload.model : undefined;
193474
- return {
193475
- output: parseJudgeOutput(content, input.summaryText),
193476
- requestedModel,
193477
- ...reportedModel ? { reportedModel } : {},
193478
- ...usage ? { usage } : {}
193479
- };
194046
+ const { signal, cleanup } = linkSignals(timeoutMs, input.signal);
194047
+ try {
194048
+ const response = await fetch(`${input.env.OPENAI_BASE_URL ?? "https://api.openai.com/v1"}/chat/completions`, {
194049
+ method: "POST",
194050
+ headers: {
194051
+ authorization: `Bearer ${apiKey}`,
194052
+ "content-type": "application/json"
194053
+ },
194054
+ body: JSON.stringify({
194055
+ model: requestedModel,
194056
+ response_format: { type: "json_object" },
194057
+ messages: [
194058
+ {
194059
+ role: "user",
194060
+ content: buildJudgePrompt(input.tool, input.result, input.summaryText, input.agentFindings)
194061
+ }
194062
+ ],
194063
+ max_completion_tokens: JUDGE_MAX_OUTPUT_TOKENS,
194064
+ temperature: 0
194065
+ }),
194066
+ signal
194067
+ });
194068
+ if (!response.ok)
194069
+ throw new Error(`OpenAI judge failed with HTTP ${response.status}.`);
194070
+ const payload = await response.json();
194071
+ const content = payload.choices?.[0]?.message?.content;
194072
+ if (!content)
194073
+ throw new Error("OpenAI judge response did not include content.");
194074
+ const usage = normalizeUsage3(payload.usage);
194075
+ const reportedModel = typeof payload.model === "string" ? payload.model : undefined;
194076
+ return {
194077
+ output: parseJudgeOutput(content, input.summaryText),
194078
+ requestedModel,
194079
+ ...reportedModel ? { reportedModel } : {},
194080
+ ...usage ? { usage } : {}
194081
+ };
194082
+ } finally {
194083
+ cleanup();
194084
+ }
193480
194085
  }
193481
194086
  function normalizeUsage3(usage) {
193482
194087
  if (!usage)
@@ -193489,16 +194094,6 @@ function normalizeUsage3(usage) {
193489
194094
  thoughtTokens: usage.completion_tokens_details?.reasoning_tokens
193490
194095
  });
193491
194096
  }
193492
- async function fetchWithTimeout2(url2, init, timeoutMs) {
193493
- const controller = new AbortController;
193494
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
193495
- timeout.unref?.();
193496
- try {
193497
- return await fetch(url2, { ...init, signal: controller.signal });
193498
- } finally {
193499
- clearTimeout(timeout);
193500
- }
193501
- }
193502
194097
 
193503
194098
  // src/judge/provider.ts
193504
194099
  function resolveJudgeProvider(provider, env) {
@@ -193552,6 +194147,9 @@ async function runJudge(input) {
193552
194147
  ...output.usage ? { usage: output.usage } : {}
193553
194148
  };
193554
194149
  } catch (error51) {
194150
+ if (input.signal?.aborted) {
194151
+ throw new KyosoCancellationError("Kyoso review was cancelled during judge execution.");
194152
+ }
193555
194153
  return {
193556
194154
  provider,
193557
194155
  status: "failed_fallback",
@@ -193883,11 +194481,11 @@ function canonicalJson(value) {
193883
194481
  function canonicalize(value) {
193884
194482
  if (Array.isArray(value))
193885
194483
  return value.map(canonicalize);
193886
- if (!isRecord12(value))
194484
+ if (!isRecord13(value))
193887
194485
  return value;
193888
194486
  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)]));
193889
194487
  }
193890
- function isRecord12(value) {
194488
+ function isRecord13(value) {
193891
194489
  return typeof value === "object" && value !== null && !Array.isArray(value);
193892
194490
  }
193893
194491
 
@@ -193901,7 +194499,7 @@ var REVIEW_BUDGET_KEYS = new Set([
193901
194499
  ]);
193902
194500
  var MODEL_CALL_KINDS = ["primary", "verifier", "judge"];
193903
194501
  function resolveReviewBudget(ceiling, requested) {
193904
- if (requested !== undefined && !isRecord13(requested)) {
194502
+ if (requested !== undefined && !isRecord14(requested)) {
193905
194503
  throw new KyosoRequestError("options.reviewBudget must be an object.", "REVIEW_BUDGET_INVALID");
193906
194504
  }
193907
194505
  for (const [key, value] of Object.entries(requested ?? {})) {
@@ -194091,6 +194689,10 @@ class ReviewBudgetTracker {
194091
194689
  current.thoughtBytes = values.thoughtBytes;
194092
194690
  current.outputBytes = values.outputBytes;
194093
194691
  current.outputWarningTriggered = values.outputWarningTriggered;
194692
+ current.observedStreamRetries = values.observedStreamRetries;
194693
+ current.discardedRetryMessageBytes = values.discardedRetryMessageBytes;
194694
+ current.firstOutputAt = values.firstOutputAt;
194695
+ current.lastAcpUpdateAt = values.lastAcpUpdateAt;
194094
194696
  current.salvaged = values.salvaged;
194095
194697
  current.reportedFindings = values.reportedFindings;
194096
194698
  current.findingsTargetExceeded = values.findingsTargetExceeded;
@@ -194205,6 +194807,12 @@ class ReviewBudgetTracker {
194205
194807
  ...reservation.thoughtBytes !== undefined ? { thoughtBytes: reservation.thoughtBytes } : {},
194206
194808
  ...reservation.outputBytes !== undefined ? { outputBytes: reservation.outputBytes } : {},
194207
194809
  ...reservation.outputWarningTriggered !== undefined ? { outputWarningTriggered: reservation.outputWarningTriggered } : {},
194810
+ ...reservation.observedStreamRetries !== undefined ? { observedStreamRetries: reservation.observedStreamRetries } : {},
194811
+ ...reservation.discardedRetryMessageBytes !== undefined ? {
194812
+ discardedRetryMessageBytes: reservation.discardedRetryMessageBytes
194813
+ } : {},
194814
+ ...reservation.firstOutputAt !== undefined ? { firstOutputAt: reservation.firstOutputAt } : {},
194815
+ ...reservation.lastAcpUpdateAt !== undefined ? { lastAcpUpdateAt: reservation.lastAcpUpdateAt } : {},
194208
194816
  ...reservation.salvaged !== undefined ? { salvaged: reservation.salvaged } : {},
194209
194817
  ...reservation.reportedFindings !== undefined ? { reportedFindings: reservation.reportedFindings } : {},
194210
194818
  ...reservation.findingsTargetExceeded !== undefined ? { findingsTargetExceeded: reservation.findingsTargetExceeded } : {},
@@ -194245,7 +194853,7 @@ function emptyReviewModelCallPlan() {
194245
194853
  ceilingEffects: []
194246
194854
  };
194247
194855
  }
194248
- function isRecord13(value) {
194856
+ function isRecord14(value) {
194249
194857
  return typeof value === "object" && value !== null && !Array.isArray(value);
194250
194858
  }
194251
194859
 
@@ -194307,7 +194915,7 @@ function parseVerificationVerdicts(rawText) {
194307
194915
  if (!Array.isArray(parsed.verdicts))
194308
194916
  return;
194309
194917
  return parsed.verdicts.flatMap((item) => {
194310
- if (!isRecord14(item))
194918
+ if (!isRecord15(item))
194311
194919
  return [];
194312
194920
  if (typeof item.findingId !== "string")
194313
194921
  return [];
@@ -194385,11 +194993,12 @@ function verificationNote(reasoning) {
194385
194993
  function isVerdict(value) {
194386
194994
  return value === "confirmed" || value === "refuted" || value === "uncertain";
194387
194995
  }
194388
- function isRecord14(value) {
194996
+ function isRecord15(value) {
194389
194997
  return typeof value === "object" && value !== null && !Array.isArray(value);
194390
194998
  }
194391
194999
 
194392
195000
  // src/core/runReview.ts
195001
+ var MAX_AGENT_RETRY_PROGRESS_EVENTS = 100;
194393
195002
  function requestForRecursionFingerprint(request) {
194394
195003
  try {
194395
195004
  return scanAndRedactSecrets(request).redactedRequest;
@@ -194405,138 +195014,368 @@ async function runReview(tool, request, options = {}) {
194405
195014
  const auditEnv = { ...process.env, ...options.env };
194406
195015
  const traceWriterFactory = options.traceWriterFactory ?? createTraceWriter;
194407
195016
  let snapshot;
195017
+ let activeTrace;
195018
+ let progressDeliveryFailure;
195019
+ let writeProgressDeliveryFailure;
195020
+ let reviewFailureReported = false;
195021
+ const dispatcher = createProgressDispatcher(options.onProgress, {
195022
+ onSinkDisabled: (reason) => {
195023
+ if (progressDeliveryFailure !== undefined)
195024
+ return;
195025
+ progressDeliveryFailure = reason;
195026
+ writeProgressDeliveryFailure?.(reason);
195027
+ }
195028
+ });
195029
+ const phaseStartedAt = new Map;
195030
+ const startPhase = (phase) => {
195031
+ throwIfAborted(options.signal);
195032
+ phaseStartedAt.set(phase, Date.now());
195033
+ dispatcher.emit({
195034
+ type: "phase_started",
195035
+ traceId,
195036
+ phase,
195037
+ timestamp: new Date().toISOString()
195038
+ });
195039
+ };
195040
+ const completePhase = (phase) => {
195041
+ dispatcher.emit({
195042
+ type: "phase_completed",
195043
+ traceId,
195044
+ phase,
195045
+ durationMs: Math.max(0, Date.now() - (phaseStartedAt.get(phase) ?? Date.now())),
195046
+ timestamp: new Date().toISOString()
195047
+ });
195048
+ };
195049
+ const skipPhase = (phase, reason) => {
195050
+ dispatcher.emit({
195051
+ type: "phase_skipped",
195052
+ traceId,
195053
+ phase,
195054
+ reason,
195055
+ timestamp: new Date().toISOString()
195056
+ });
195057
+ };
195058
+ const reportReviewCompleted = async (result) => {
195059
+ dispatcher.emit({
195060
+ type: "review_completed",
195061
+ traceId,
195062
+ decision: result.decision,
195063
+ completionStatus: result.completion.status,
195064
+ durationMs: Math.max(0, Date.now() - startedAtEpochMs),
195065
+ timestamp: new Date().toISOString()
195066
+ });
195067
+ await dispatcher.flush();
195068
+ if (progressDeliveryFailure !== undefined) {
195069
+ result.audit.warnings = Array.from(new Set([
195070
+ ...result.audit.warnings ?? [],
195071
+ `PROGRESS_DELIVERY_FAILED: ${progressDeliveryFailure}`
195072
+ ]));
195073
+ }
195074
+ };
195075
+ const completeReview = async (result) => {
195076
+ await reportReviewCompleted(result);
195077
+ return result;
195078
+ };
195079
+ const reportReviewFailure = async (error51, trace) => {
195080
+ if (reviewFailureReported)
195081
+ return;
195082
+ reviewFailureReported = true;
195083
+ const timestamp = new Date().toISOString();
195084
+ if (error51 instanceof KyosoCancellationError) {
195085
+ dispatcher.emit({ type: "review_cancelled", traceId, timestamp });
195086
+ await trace?.write({ type: "review_cancelled", traceId, timestamp }).catch(() => {
195087
+ return;
195088
+ });
195089
+ } else {
195090
+ dispatcher.emit({
195091
+ type: "review_failed",
195092
+ traceId,
195093
+ ...error51 instanceof KyosoRequestError ? { errorCode: error51.code } : {},
195094
+ timestamp
195095
+ });
195096
+ await trace?.write({
195097
+ type: "review_failed",
195098
+ traceId,
195099
+ ...error51 instanceof KyosoRequestError ? { errorCode: error51.code } : {},
195100
+ timestamp
195101
+ }).catch(() => {
195102
+ return;
195103
+ });
195104
+ }
195105
+ await dispatcher.flush();
195106
+ };
195107
+ dispatcher.emit({
195108
+ type: "review_started",
195109
+ traceId,
195110
+ tool,
195111
+ timestamp: new Date().toISOString()
195112
+ });
194408
195113
  try {
194409
- assertNotChildAgent(options.env ?? process.env);
194410
- } catch (error51) {
194411
- if (error51 instanceof KyosoRequestError) {
194412
- const config2 = kyosoConfigSchema.parse(defaultConfig);
194413
- const reviewBudget = resolveReviewBudget(config2.reviewBudget, undefined);
194414
- const budgetTracker = new ReviewBudgetTracker(reviewBudget, startedAtEpochMs, configuredReviewModelCallPlan(config2, reviewBudget, options.env ?? process.env));
194415
- const requestFingerprint = createRequestFingerprint({
195114
+ try {
195115
+ throwIfAborted(options.signal);
195116
+ assertNotChildAgent(options.env ?? process.env);
195117
+ } catch (error51) {
195118
+ if (error51 instanceof KyosoRequestError) {
195119
+ const config2 = kyosoConfigSchema.parse(defaultConfig);
195120
+ const reviewBudget = resolveReviewBudget(config2.reviewBudget, undefined);
195121
+ const budgetTracker = new ReviewBudgetTracker(reviewBudget, startedAtEpochMs, configuredReviewModelCallPlan(config2, reviewBudget, options.env ?? process.env));
195122
+ const requestFingerprint = createRequestFingerprint({
195123
+ tool,
195124
+ request: requestForRecursionFingerprint(request),
195125
+ config: config2,
195126
+ roles: resolveAgentRoles(config2),
195127
+ budget: reviewBudget,
195128
+ entrypoint: options.entrypoint
195129
+ });
195130
+ const trace2 = traceWriterFactory({
195131
+ enabled: config2.audit.enabled,
195132
+ directory: config2.audit.directory,
195133
+ traceId,
195134
+ cwd,
195135
+ env: auditEnv
195136
+ });
195137
+ activeTrace = trace2;
195138
+ writeProgressDeliveryFailure = (reason) => {
195139
+ trace2.write({
195140
+ type: "progress_delivery_failed",
195141
+ traceId,
195142
+ reason,
195143
+ timestamp: new Date().toISOString()
195144
+ }).catch(() => {
195145
+ return;
195146
+ });
195147
+ };
195148
+ if (progressDeliveryFailure !== undefined) {
195149
+ writeProgressDeliveryFailure(progressDeliveryFailure);
195150
+ }
195151
+ try {
195152
+ await trace2.write({
195153
+ type: "request_received",
195154
+ traceId,
195155
+ tool,
195156
+ timestamp: new Date().toISOString()
195157
+ });
195158
+ await writeReviewBudgetPlanned({
195159
+ trace: trace2,
195160
+ traceId,
195161
+ budgetTracker,
195162
+ requestFingerprint
195163
+ });
195164
+ return await completeReview(await buildPolicyBlockResult({
195165
+ tool,
195166
+ trace: trace2,
195167
+ traceId,
195168
+ startedAt,
195169
+ networkMode: config2.network.defaultMode,
195170
+ cisaPolicy: config2.securityReview.cisaSecureByDesign,
195171
+ warning: error51.message,
195172
+ budgetTracker,
195173
+ requestFingerprint,
195174
+ coverage: unavailableReviewCoverage(requestForRecursionFingerprint(request), "recursive invocation blocked before agent execution", config2.reviewPolicy.additionalLenses),
195175
+ finding: {
195176
+ id: "KYOSO-1",
195177
+ severity: "critical",
195178
+ category: "other",
195179
+ title: "Recursive Kyoso invocation blocked",
195180
+ evidence: error51.message,
195181
+ recommendation: "Do not expose Kyoso MCP tools to Kyoso child agents.",
195182
+ disposition: "gate",
195183
+ changeRelation: "unknown",
195184
+ evidenceQuality: "concrete",
195185
+ evidenceRefs: [],
195186
+ policyReasons: ["kyoso_policy", "recursive_invocation"],
195187
+ fingerprint: "",
195188
+ sourceAgents: ["kyoso_policy"],
195189
+ confidence: "high"
195190
+ },
195191
+ redactionsApplied: 0
195192
+ }));
195193
+ } finally {
195194
+ await trace2.finalize();
195195
+ }
195196
+ }
195197
+ throw error51;
195198
+ }
195199
+ startPhase("preflight");
195200
+ const baseLoaded = options.config !== undefined ? {
195201
+ config: options.config,
195202
+ configHash: options.configHash,
195203
+ configTrustStatus: "trusted",
195204
+ sources: [],
195205
+ warnings: []
195206
+ } : await loadConfig({
195207
+ cwd,
195208
+ configPath: options.configPath,
195209
+ ignoreConfig: options.ignoreConfig,
195210
+ trustConfig: options.trustConfig,
195211
+ allowUnknownConfig: options.allowUnknownConfig,
195212
+ promptForTrust: options.promptForTrust,
195213
+ trustStorePath: options.trustStorePath,
195214
+ env: options.env,
195215
+ trustPrompt: options.trustPrompt
195216
+ });
195217
+ const loaded = options.configOverrides && options.configOverrides.length > 0 ? {
195218
+ ...baseLoaded,
195219
+ config: applyConfigOverrides(baseLoaded.config, options.configOverrides)
195220
+ } : baseLoaded;
195221
+ completePhase("preflight");
195222
+ const trace = traceWriterFactory({
195223
+ enabled: loaded.config.audit.enabled,
195224
+ directory: loaded.config.audit.directory,
195225
+ traceId,
195226
+ cwd,
195227
+ includeRawAgentOutput: loaded.config.audit.includeRawAgentOutput,
195228
+ env: auditEnv
195229
+ });
195230
+ activeTrace = trace;
195231
+ const warnings = [...loaded.warnings, ...trace.warnings];
195232
+ writeProgressDeliveryFailure = (reason) => {
195233
+ warnings.push(`PROGRESS_DELIVERY_FAILED: ${reason}`);
195234
+ trace.write({
195235
+ type: "progress_delivery_failed",
195236
+ traceId,
195237
+ reason,
195238
+ timestamp: new Date().toISOString()
195239
+ }).catch(() => {
195240
+ return;
195241
+ });
195242
+ };
195243
+ if (progressDeliveryFailure !== undefined) {
195244
+ writeProgressDeliveryFailure(progressDeliveryFailure);
195245
+ }
195246
+ try {
195247
+ await trace.write({
195248
+ type: "request_received",
195249
+ traceId,
194416
195250
  tool,
194417
- request: requestForRecursionFingerprint(request),
194418
- config: config2,
194419
- roles: resolveAgentRoles(config2),
194420
- budget: reviewBudget,
194421
- entrypoint: options.entrypoint
195251
+ timestamp: new Date().toISOString()
194422
195252
  });
194423
- const trace2 = traceWriterFactory({
194424
- enabled: config2.audit.enabled,
194425
- directory: config2.audit.directory,
195253
+ await trace.write({
195254
+ type: "config_loaded",
194426
195255
  traceId,
194427
- cwd,
194428
- env: auditEnv
195256
+ configHash: loaded.configHash,
195257
+ configPath: loaded.configPath,
195258
+ configSources: loaded.sources,
195259
+ configTrustStatus: loaded.configTrustStatus,
195260
+ timestamp: new Date().toISOString()
194429
195261
  });
194430
- try {
194431
- await trace2.write({
194432
- type: "request_received",
194433
- traceId,
195262
+ validateReviewRequest(tool, request);
195263
+ const reviewBudget = resolveReviewBudget(loaded.config.reviewBudget, request.options?.reviewBudget);
195264
+ const budgetTracker = new ReviewBudgetTracker(reviewBudget, startedAtEpochMs, configuredReviewModelCallPlan(loaded.config, reviewBudget, options.env ?? process.env, request.options?.judgeProvider));
195265
+ assertTrustedWorkspaceRoot(request.workspace?.root, loaded.config.workspace.root, cwd);
195266
+ const networkMode = resolveNetworkMode(request.options?.network, loaded.config.network.defaultMode, options.mcpNetworkMode);
195267
+ if (networkMode === "unrestricted" && !loaded.config.network.allowUnrestricted) {
195268
+ throw new KyosoRequestError("unrestricted network mode is disabled by config", "NETWORK_MODE_DISABLED");
195269
+ }
195270
+ const disabledPolicy = disabledReviewPolicy(tool, loaded.config, options.entrypoint);
195271
+ if (disabledPolicy) {
195272
+ const redactedRequest = requestForRecursionFingerprint(request);
195273
+ const requestFingerprint2 = createRequestFingerprint({
194434
195274
  tool,
194435
- timestamp: new Date().toISOString()
195275
+ request: redactedRequest,
195276
+ config: loaded.config,
195277
+ roles: resolveAgentRoles(loaded.config),
195278
+ budget: reviewBudget,
195279
+ entrypoint: options.entrypoint
194436
195280
  });
194437
195281
  await writeReviewBudgetPlanned({
194438
- trace: trace2,
195282
+ trace,
194439
195283
  traceId,
194440
195284
  budgetTracker,
194441
- requestFingerprint
195285
+ requestFingerprint: requestFingerprint2
194442
195286
  });
194443
- return await buildPolicyBlockResult({
195287
+ const warning = disabledPolicy.warning;
195288
+ return await completeReview(await buildPolicyBlockResult({
194444
195289
  tool,
194445
- trace: trace2,
195290
+ trace,
194446
195291
  traceId,
194447
195292
  startedAt,
194448
- networkMode: config2.network.defaultMode,
194449
- cisaPolicy: config2.securityReview.cisaSecureByDesign,
194450
- warning: error51.message,
195293
+ configHash: loaded.configHash,
195294
+ networkMode,
195295
+ cisaPolicy: loaded.config.securityReview.cisaSecureByDesign,
195296
+ warning,
194451
195297
  budgetTracker,
194452
- requestFingerprint,
194453
- coverage: unavailableReviewCoverage(requestForRecursionFingerprint(request), "recursive invocation blocked before agent execution", config2.reviewPolicy.additionalLenses),
195298
+ requestFingerprint: requestFingerprint2,
195299
+ coverage: unavailableReviewCoverage(redactedRequest, disabledPolicy.coverageReason, loaded.config.reviewPolicy.additionalLenses),
194454
195300
  finding: {
194455
195301
  id: "KYOSO-1",
194456
195302
  severity: "critical",
194457
195303
  category: "other",
194458
- title: "Recursive Kyoso invocation blocked",
194459
- evidence: error51.message,
194460
- recommendation: "Do not expose Kyoso MCP tools to Kyoso child agents.",
195304
+ title: disabledPolicy.title,
195305
+ evidence: warning,
195306
+ recommendation: disabledPolicy.recommendation,
194461
195307
  disposition: "gate",
194462
195308
  changeRelation: "unknown",
194463
195309
  evidenceQuality: "concrete",
194464
195310
  evidenceRefs: [],
194465
- policyReasons: ["kyoso_policy", "recursive_invocation"],
195311
+ policyReasons: ["kyoso_policy", disabledPolicy.policyReason],
194466
195312
  fingerprint: "",
194467
195313
  sourceAgents: ["kyoso_policy"],
194468
195314
  confidence: "high"
194469
195315
  },
194470
195316
  redactionsApplied: 0
195317
+ }));
195318
+ }
195319
+ if (networkMode === "unrestricted" && loaded.config.network.warnOnUnrestricted) {
195320
+ warnings.push("Network mode is unrestricted; write policy remains denied.");
195321
+ }
195322
+ startPhase("context");
195323
+ const secretScan = scanAndRedactSecrets(request);
195324
+ await trace.write({
195325
+ type: "secret_scan_completed",
195326
+ traceId,
195327
+ detected: secretScan.detected,
195328
+ redactions: secretScan.redactions,
195329
+ timestamp: new Date().toISOString()
195330
+ });
195331
+ const allowSecretOverride = loaded.config.secrets.allowOverride && request.options?.allowSecretRedaction === true;
195332
+ if (secretScan.detected && loaded.config.secrets.blockOnDetectedSecret && !allowSecretOverride) {
195333
+ const requestFingerprint2 = createRequestFingerprint({
195334
+ tool,
195335
+ request: secretScan.redactedRequest,
195336
+ config: loaded.config,
195337
+ roles: resolveAgentRoles(loaded.config),
195338
+ budget: reviewBudget,
195339
+ entrypoint: options.entrypoint
194471
195340
  });
194472
- } finally {
194473
- await trace2.finalize();
195341
+ await writeReviewBudgetPlanned({
195342
+ trace,
195343
+ traceId,
195344
+ budgetTracker,
195345
+ requestFingerprint: requestFingerprint2
195346
+ });
195347
+ skipPhase("context", "secret_detected");
195348
+ return await completeReview(await buildSecretBlockResult({
195349
+ tool,
195350
+ trace,
195351
+ traceId,
195352
+ startedAt,
195353
+ configHash: loaded.configHash,
195354
+ networkMode,
195355
+ cisaPolicy: loaded.config.securityReview.cisaSecureByDesign,
195356
+ additionalLenses: loaded.config.reviewPolicy.additionalLenses,
195357
+ secretScan,
195358
+ warnings,
195359
+ budgetTracker,
195360
+ requestFingerprint: requestFingerprint2
195361
+ }));
194474
195362
  }
194475
- }
194476
- throw error51;
194477
- }
194478
- const baseLoaded = options.config !== undefined ? {
194479
- config: options.config,
194480
- configHash: options.configHash,
194481
- configTrustStatus: "trusted",
194482
- sources: [],
194483
- warnings: []
194484
- } : await loadConfig({
194485
- cwd,
194486
- configPath: options.configPath,
194487
- ignoreConfig: options.ignoreConfig,
194488
- trustConfig: options.trustConfig,
194489
- allowUnknownConfig: options.allowUnknownConfig,
194490
- promptForTrust: options.promptForTrust,
194491
- trustStorePath: options.trustStorePath,
194492
- env: options.env,
194493
- trustPrompt: options.trustPrompt
194494
- });
194495
- const loaded = options.configOverrides && options.configOverrides.length > 0 ? {
194496
- ...baseLoaded,
194497
- config: applyConfigOverrides(baseLoaded.config, options.configOverrides)
194498
- } : baseLoaded;
194499
- const trace = traceWriterFactory({
194500
- enabled: loaded.config.audit.enabled,
194501
- directory: loaded.config.audit.directory,
194502
- traceId,
194503
- cwd,
194504
- includeRawAgentOutput: loaded.config.audit.includeRawAgentOutput,
194505
- env: auditEnv
194506
- });
194507
- const warnings = [...loaded.warnings, ...trace.warnings];
194508
- try {
194509
- await trace.write({
194510
- type: "request_received",
194511
- traceId,
194512
- tool,
194513
- timestamp: new Date().toISOString()
194514
- });
194515
- await trace.write({
194516
- type: "config_loaded",
194517
- traceId,
194518
- configHash: loaded.configHash,
194519
- configPath: loaded.configPath,
194520
- configSources: loaded.sources,
194521
- configTrustStatus: loaded.configTrustStatus,
194522
- timestamp: new Date().toISOString()
194523
- });
194524
- validateReviewRequest(tool, request);
194525
- const reviewBudget = resolveReviewBudget(loaded.config.reviewBudget, request.options?.reviewBudget);
194526
- const budgetTracker = new ReviewBudgetTracker(reviewBudget, startedAtEpochMs, configuredReviewModelCallPlan(loaded.config, reviewBudget, options.env ?? process.env, request.options?.judgeProvider));
194527
- assertTrustedWorkspaceRoot(request.workspace?.root, loaded.config.workspace.root, cwd);
194528
- const networkMode = resolveNetworkMode(request.options?.network, loaded.config.network.defaultMode, options.mcpNetworkMode);
194529
- if (networkMode === "unrestricted" && !loaded.config.network.allowUnrestricted) {
194530
- throw new KyosoRequestError("unrestricted network mode is disabled by config", "NETWORK_MODE_DISABLED");
194531
- }
194532
- const disabledPolicy = disabledReviewPolicy(tool, loaded.config, options.entrypoint);
194533
- if (disabledPolicy) {
194534
- const redactedRequest = requestForRecursionFingerprint(request);
194535
- const requestFingerprint2 = createRequestFingerprint({
195363
+ const denyPatterns = mergeDenyPatterns(loaded.config.workspace.deny, secretScan.redactedRequest.workspace?.denyRead);
195364
+ const allowPatterns = secretScan.redactedRequest.workspace?.allowRead ?? [];
195365
+ const built = buildContext(secretScan.redactedRequest, {
195366
+ maxContextBytes: loaded.config.workspace.maxContextBytes,
195367
+ maxDiffBytes: loaded.config.workspace.maxDiffBytes,
195368
+ denyPatterns,
195369
+ allowPatterns
195370
+ });
195371
+ warnings.push(...built.warnings);
195372
+ completePhase("context");
195373
+ const agentRoles = resolveAgentRoles(loaded.config);
195374
+ const requestFingerprint = createRequestFingerprint({
194536
195375
  tool,
194537
- request: redactedRequest,
195376
+ request: built.request,
194538
195377
  config: loaded.config,
194539
- roles: resolveAgentRoles(loaded.config),
195378
+ roles: agentRoles,
194540
195379
  budget: reviewBudget,
194541
195380
  entrypoint: options.entrypoint
194542
195381
  });
@@ -194544,370 +195383,308 @@ async function runReview(tool, request, options = {}) {
194544
195383
  trace,
194545
195384
  traceId,
194546
195385
  budgetTracker,
194547
- requestFingerprint: requestFingerprint2
195386
+ requestFingerprint
194548
195387
  });
194549
- const warning = disabledPolicy.warning;
194550
- return await buildPolicyBlockResult({
194551
- tool,
194552
- trace,
194553
- traceId,
194554
- startedAt,
194555
- configHash: loaded.configHash,
194556
- networkMode,
194557
- cisaPolicy: loaded.config.securityReview.cisaSecureByDesign,
194558
- warning,
194559
- budgetTracker,
194560
- requestFingerprint: requestFingerprint2,
194561
- coverage: unavailableReviewCoverage(redactedRequest, disabledPolicy.coverageReason, loaded.config.reviewPolicy.additionalLenses),
194562
- finding: {
194563
- id: "KYOSO-1",
194564
- severity: "critical",
194565
- category: "other",
194566
- title: disabledPolicy.title,
194567
- evidence: warning,
194568
- recommendation: disabledPolicy.recommendation,
194569
- disposition: "gate",
194570
- changeRelation: "unknown",
194571
- evidenceQuality: "concrete",
194572
- evidenceRefs: [],
194573
- policyReasons: ["kyoso_policy", disabledPolicy.policyReason],
194574
- fingerprint: "",
194575
- sourceAgents: ["kyoso_policy"],
194576
- confidence: "high"
194577
- },
194578
- redactionsApplied: 0
194579
- });
194580
- }
194581
- if (networkMode === "unrestricted" && loaded.config.network.warnOnUnrestricted) {
194582
- warnings.push("Network mode is unrestricted; write policy remains denied.");
194583
- }
194584
- const secretScan = scanAndRedactSecrets(request);
194585
- await trace.write({
194586
- type: "secret_scan_completed",
194587
- traceId,
194588
- detected: secretScan.detected,
194589
- redactions: secretScan.redactions,
194590
- timestamp: new Date().toISOString()
194591
- });
194592
- const allowSecretOverride = loaded.config.secrets.allowOverride && request.options?.allowSecretRedaction === true;
194593
- if (secretScan.detected && loaded.config.secrets.blockOnDetectedSecret && !allowSecretOverride) {
194594
- const requestFingerprint2 = createRequestFingerprint({
194595
- tool,
194596
- request: secretScan.redactedRequest,
194597
- config: loaded.config,
194598
- roles: resolveAgentRoles(loaded.config),
194599
- budget: reviewBudget,
194600
- entrypoint: options.entrypoint
195388
+ warnings.push(...plannedBudgetWarnings(budgetTracker));
195389
+ startPhase("snapshot");
195390
+ snapshot = await createSnapshot(traceId, tool, built.request, {
195391
+ denyPatterns,
195392
+ allowPatterns,
195393
+ agentRoles
194601
195394
  });
194602
- await writeReviewBudgetPlanned({
194603
- trace,
195395
+ await trace.write({
195396
+ type: "snapshot_created",
194604
195397
  traceId,
194605
- budgetTracker,
194606
- requestFingerprint: requestFingerprint2
195398
+ path: snapshot.root,
195399
+ fileCount: snapshot.fileCount,
195400
+ timestamp: new Date().toISOString()
194607
195401
  });
194608
- return await buildSecretBlockResult({
195402
+ completePhase("snapshot");
195403
+ const manager = options.agentManager ?? defaultAgentManager(loaded.config, options.env ?? process.env);
195404
+ startPhase("primary");
195405
+ const agentResults = await runAgents({
194609
195406
  tool,
194610
- trace,
195407
+ request: built.request,
195408
+ config: loaded.config,
194611
195409
  traceId,
194612
- startedAt,
194613
- configHash: loaded.configHash,
195410
+ workspaceDir: snapshot.root,
194614
195411
  networkMode,
194615
- cisaPolicy: loaded.config.securityReview.cisaSecureByDesign,
194616
- additionalLenses: loaded.config.reviewPolicy.additionalLenses,
194617
- secretScan,
195412
+ manager,
195413
+ trace,
194618
195414
  warnings,
194619
195415
  budgetTracker,
194620
- requestFingerprint: requestFingerprint2
195416
+ progressDispatcher: dispatcher,
195417
+ signal: options.signal,
195418
+ progressHeartbeatMs: options.progressHeartbeatMs
194621
195419
  });
194622
- }
194623
- const denyPatterns = mergeDenyPatterns(loaded.config.workspace.deny, secretScan.redactedRequest.workspace?.denyRead);
194624
- const allowPatterns = secretScan.redactedRequest.workspace?.allowRead ?? [];
194625
- const built = buildContext(secretScan.redactedRequest, {
194626
- maxContextBytes: loaded.config.workspace.maxContextBytes,
194627
- maxDiffBytes: loaded.config.workspace.maxDiffBytes,
194628
- denyPatterns,
194629
- allowPatterns
194630
- });
194631
- warnings.push(...built.warnings);
194632
- const agentRoles = resolveAgentRoles(loaded.config);
194633
- const requestFingerprint = createRequestFingerprint({
194634
- tool,
194635
- request: built.request,
194636
- config: loaded.config,
194637
- roles: agentRoles,
194638
- budget: reviewBudget,
194639
- entrypoint: options.entrypoint
194640
- });
194641
- await writeReviewBudgetPlanned({
194642
- trace,
194643
- traceId,
194644
- budgetTracker,
194645
- requestFingerprint
194646
- });
194647
- warnings.push(...plannedBudgetWarnings(budgetTracker));
194648
- snapshot = await createSnapshot(traceId, tool, built.request, {
194649
- denyPatterns,
194650
- allowPatterns,
194651
- agentRoles
194652
- });
194653
- await trace.write({
194654
- type: "snapshot_created",
194655
- traceId,
194656
- path: snapshot.root,
194657
- fileCount: snapshot.fileCount,
194658
- timestamp: new Date().toISOString()
194659
- });
194660
- const manager = options.agentManager ?? defaultAgentManager(loaded.config, options.env ?? process.env);
194661
- const agentResults = await runAgents({
194662
- tool,
194663
- request: built.request,
194664
- config: loaded.config,
194665
- traceId,
194666
- workspaceDir: snapshot.root,
194667
- networkMode,
194668
- manager,
194669
- trace,
194670
- warnings,
194671
- budgetTracker
194672
- });
194673
- warnings.push(...agentResults.flatMap((result) => (result.warnings ?? []).map((warning) => `Agent ${result.agent} ${warning}`)));
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.`);
194677
- }
194678
- const enabledAgents = ["codex", "claude"].filter((agent) => loaded.config.agents[agent].enabled);
194679
- const agentsUsed = normalizedAgentResults.filter((result) => result.status !== "skipped").map((result) => result.agent);
194680
- const reviewMode = enabledAgents.length === 1 ? "single_agent" : "multi_agent";
194681
- const completed = normalizedAgentResults.filter((result) => result.status === "completed");
194682
- const attempted = normalizedAgentResults.filter((result) => result.status !== "skipped");
194683
- const degraded = completed.length !== attempted.length || enabledAgents.length === 0;
194684
- const coverage = buildReviewCoverage({
194685
- request: built.request,
194686
- additionalLenses: loaded.config.reviewPolicy.additionalLenses,
194687
- agentResults: normalizedAgentResults
194688
- });
194689
- if (isCoverageIncomplete(coverage, {
194690
- multiAgentRequired: loaded.config.reviewPolicy.multiAgentRequired
194691
- })) {
194692
- budgetTracker.markIncomplete("coverage_incomplete");
194693
- warnings.push(formatCoverageWarning(coverage, loaded.config));
194694
- }
194695
- let aggregate = aggregateAgentResults(normalizedAgentResults, {
194696
- reviewMode
194697
- });
194698
- if (secretScan.detected && allowSecretOverride) {
195420
+ completePhase("primary");
195421
+ warnings.push(...agentResults.flatMap((result) => (result.warnings ?? []).map((warning) => `Agent ${result.agent} ${warning}`)));
195422
+ startPhase("aggregation");
195423
+ const normalizedAgentResults = agentResults.map((result) => normalizeAgentRunResult(result, reviewBudget.maxFindingsPerAgent));
195424
+ for (const result of normalizedAgentResults.filter((item) => item.findingsTargetExceeded)) {
195425
+ warnings.push(`Agent ${result.agent} reported ${result.reportedFindings} findings, above the soft target of ${reviewBudget.maxFindingsPerAgent}; all findings were retained.`);
195426
+ }
195427
+ const enabledAgents = ["codex", "claude"].filter((agent) => loaded.config.agents[agent].enabled);
195428
+ const agentsUsed = normalizedAgentResults.filter((result) => result.status !== "skipped").map((result) => result.agent);
195429
+ const reviewMode = enabledAgents.length === 1 ? "single_agent" : "multi_agent";
195430
+ const completed = normalizedAgentResults.filter((result) => result.status === "completed");
195431
+ const attempted = normalizedAgentResults.filter((result) => result.status !== "skipped");
195432
+ const degraded = completed.length !== attempted.length || enabledAgents.length === 0;
195433
+ const coverage = buildReviewCoverage({
195434
+ request: built.request,
195435
+ additionalLenses: loaded.config.reviewPolicy.additionalLenses,
195436
+ agentResults: normalizedAgentResults
195437
+ });
195438
+ if (isCoverageIncomplete(coverage, {
195439
+ multiAgentRequired: loaded.config.reviewPolicy.multiAgentRequired
195440
+ })) {
195441
+ budgetTracker.markIncomplete("coverage_incomplete");
195442
+ warnings.push(formatCoverageWarning(coverage, loaded.config));
195443
+ }
195444
+ let aggregate = aggregateAgentResults(normalizedAgentResults, {
195445
+ reviewMode
195446
+ });
195447
+ if (secretScan.detected && allowSecretOverride) {
195448
+ aggregate = {
195449
+ ...aggregate,
195450
+ findings: reindexFindings([
195451
+ buildSecretFinding(secretScan, {
195452
+ id: "KYOSO-1",
195453
+ blocked: false
195454
+ }),
195455
+ ...aggregate.findings
195456
+ ])
195457
+ };
195458
+ }
195459
+ if (completed.length === 0 && (attempted.length > 0 || enabledAgents.length === 0)) {
195460
+ const noPrimaryAgents = enabledAgents.length === 0;
195461
+ if (noPrimaryAgents) {
195462
+ budgetTracker.markIncomplete("coverage_incomplete");
195463
+ warnings.push("No primary review agents are enabled; review coverage is incomplete.");
195464
+ }
195465
+ aggregate = {
195466
+ ...aggregate,
195467
+ findings: [
195468
+ ...aggregate.findings,
195469
+ {
195470
+ id: `KYOSO-${aggregate.findings.length + 1}`,
195471
+ severity: "critical",
195472
+ category: "other",
195473
+ title: noPrimaryAgents ? "No primary review agents enabled" : "All backend agents failed",
195474
+ evidence: noPrimaryAgents ? "Both configured primary reviewers are disabled." : normalizedAgentResults.map((result) => `${result.agent}: ${result.error?.code ?? result.status}`).join("; "),
195475
+ recommendation: noPrimaryAgents ? "Enable at least one primary reviewer before running Kyoso." : "Run kyoso doctor and retry after agent authentication or adapter issues are fixed.",
195476
+ disposition: "gate",
195477
+ changeRelation: "unknown",
195478
+ evidenceQuality: "concrete",
195479
+ evidenceRefs: [],
195480
+ policyReasons: ["kyoso_policy", "coverage_incomplete"],
195481
+ fingerprint: "",
195482
+ sourceAgents: ["kyoso_policy"],
195483
+ confidence: "high"
195484
+ }
195485
+ ]
195486
+ };
195487
+ }
194699
195488
  aggregate = {
194700
195489
  ...aggregate,
194701
- findings: reindexFindings([
194702
- buildSecretFinding(secretScan, {
194703
- id: "KYOSO-1",
194704
- blocked: false
194705
- }),
194706
- ...aggregate.findings
194707
- ])
195490
+ findings: admitFindings({
195491
+ tool,
195492
+ request: built.request,
195493
+ findings: aggregate.findings,
195494
+ reviewMode
195495
+ })
194708
195496
  };
194709
- }
194710
- if (completed.length === 0 && (attempted.length > 0 || enabledAgents.length === 0)) {
194711
- const noPrimaryAgents = enabledAgents.length === 0;
194712
- if (noPrimaryAgents) {
194713
- budgetTracker.markIncomplete("coverage_incomplete");
194714
- warnings.push("No primary review agents are enabled; review coverage is incomplete.");
195497
+ await trace.write({
195498
+ type: "aggregation_completed",
195499
+ traceId,
195500
+ findingCount: aggregate.findings.length,
195501
+ timestamp: new Date().toISOString()
195502
+ });
195503
+ completePhase("aggregation");
195504
+ const verificationMode = loaded.config.verification.enabled && reviewMode === "single_agent" ? "skipped_single_agent" : loaded.config.verification.enabled && enabledAgents.length > 1 ? "cross_agent" : undefined;
195505
+ if (verificationMode === "cross_agent") {
195506
+ startPhase("verification");
195507
+ warnings.push(...await runFindingVerification({
195508
+ tool,
195509
+ request: built.request,
195510
+ config: loaded.config,
195511
+ traceId,
195512
+ workspaceDir: snapshot.root,
195513
+ networkMode,
195514
+ manager,
195515
+ trace,
195516
+ findings: aggregate.findings,
195517
+ budgetTracker,
195518
+ signal: options.signal
195519
+ }));
195520
+ completePhase("verification");
195521
+ } else {
195522
+ skipPhase("verification", verificationMode === "skipped_single_agent" ? "single_agent_review" : "verification_disabled");
194715
195523
  }
194716
195524
  aggregate = {
194717
195525
  ...aggregate,
194718
- findings: [
194719
- ...aggregate.findings,
194720
- {
194721
- id: `KYOSO-${aggregate.findings.length + 1}`,
194722
- severity: "critical",
194723
- category: "other",
194724
- title: noPrimaryAgents ? "No primary review agents enabled" : "All backend agents failed",
194725
- evidence: noPrimaryAgents ? "Both configured primary reviewers are disabled." : normalizedAgentResults.map((result) => `${result.agent}: ${result.error?.code ?? result.status}`).join("; "),
194726
- recommendation: noPrimaryAgents ? "Enable at least one primary reviewer before running Kyoso." : "Run kyoso doctor and retry after agent authentication or adapter issues are fixed.",
194727
- disposition: "gate",
194728
- changeRelation: "unknown",
194729
- evidenceQuality: "concrete",
194730
- evidenceRefs: [],
194731
- policyReasons: ["kyoso_policy", "coverage_incomplete"],
194732
- fingerprint: "",
194733
- sourceAgents: ["kyoso_policy"],
194734
- confidence: "high"
194735
- }
194736
- ]
195526
+ findings: admitFindings({
195527
+ tool,
195528
+ request: built.request,
195529
+ findings: aggregate.findings,
195530
+ reviewMode
195531
+ })
194737
195532
  };
194738
- }
194739
- aggregate = {
194740
- ...aggregate,
194741
- findings: admitFindings({
195533
+ if (aggregate.findings.some((finding) => finding.disposition === "disputed")) {
195534
+ budgetTracker.markIncomplete("disputed_finding");
195535
+ }
195536
+ const cisaPolicy = loaded.config.securityReview.cisaSecureByDesign;
195537
+ const cisa = tool === "security_review" && cisaPolicy.enabled ? computeCisaGate(aggregate.findings, normalizedAgentResults, cisaPolicy) : undefined;
195538
+ const budgetBeforeJudge = budgetTracker.snapshot();
195539
+ const decision = budgetBeforeJudge.completion.status === "incomplete" ? "block" : decide({
194742
195540
  tool,
194743
- request: built.request,
194744
195541
  findings: aggregate.findings,
194745
- reviewMode
194746
- })
194747
- };
194748
- await trace.write({
194749
- type: "aggregation_completed",
194750
- traceId,
194751
- findingCount: aggregate.findings.length,
194752
- timestamp: new Date().toISOString()
194753
- });
194754
- const verificationMode = loaded.config.verification.enabled && reviewMode === "single_agent" ? "skipped_single_agent" : loaded.config.verification.enabled && enabledAgents.length > 1 ? "cross_agent" : undefined;
194755
- if (verificationMode === "cross_agent") {
194756
- warnings.push(...await runFindingVerification({
195542
+ cisa: cisaPolicy.gate ? cisa : undefined,
195543
+ degraded,
195544
+ secretScan: { detected: secretScan.detected, blocked: false }
195545
+ });
195546
+ const completedAt = new Date().toISOString();
195547
+ const resultWithoutMarkdown = {
195548
+ decision,
195549
+ completion: budgetBeforeJudge.completion,
195550
+ executionBudget: budgetBeforeJudge.executionBudget,
195551
+ requestFingerprint,
195552
+ degraded,
195553
+ agentsUsed,
195554
+ reviewMode,
195555
+ coverage,
195556
+ ...verificationMode ? { verificationMode } : {},
195557
+ findings: aggregate.findings,
195558
+ cisaSecureByDesign: cisa,
195559
+ disagreements: aggregate.disagreements,
195560
+ testsToAdd: selectRegressionTests(aggregate.testsToAdd),
195561
+ residualRisks: tool === "security_review" && aggregate.residualRisks.length === 0 ? [
195562
+ "No residual risks were reported by completed agents; verify security assumptions before release."
195563
+ ] : aggregate.residualRisks,
195564
+ openQuestions: Array.from(new Set([
195565
+ ...aggregate.openQuestions,
195566
+ ...buildAdmissionOpenQuestions(aggregate.findings)
195567
+ ])),
195568
+ agentOpinions: normalizedAgentResults.map((result) => agentOpinionSummary(result, request.options?.includeAgentRawOutputs === true)),
195569
+ audit: {
195570
+ traceId,
195571
+ startedAt,
195572
+ completedAt,
195573
+ agentsUsed,
195574
+ redactionsApplied: secretScan.redactions,
195575
+ networkMode,
195576
+ workspaceMode: "temp_snapshot",
195577
+ configHash: loaded.configHash,
195578
+ warnings: Array.from(new Set([...warnings, ...trace.warnings])),
195579
+ modelCalls: budgetBeforeJudge.modelCalls
195580
+ }
195581
+ };
195582
+ const summaryText = defaultSummaryText(resultWithoutMarkdown);
195583
+ startPhase("judge");
195584
+ const judge = await runBudgetedJudge({
194757
195585
  tool,
194758
- request: built.request,
194759
- config: loaded.config,
194760
- traceId,
194761
- workspaceDir: snapshot.root,
194762
- networkMode,
194763
- manager,
195586
+ result: resultWithoutMarkdown,
195587
+ summaryText,
195588
+ agentFindings: buildJudgeAgentFindings(normalizedAgentResults),
195589
+ config: loaded.config.judge,
195590
+ requestedProvider: request.options?.judgeProvider,
195591
+ env: options.env ?? process.env,
195592
+ budgetTracker,
194764
195593
  trace,
194765
- findings: aggregate.findings,
194766
- budgetTracker
195594
+ traceId,
195595
+ signal: options.signal
195596
+ });
195597
+ completePhase("judge");
195598
+ startPhase("finalize");
195599
+ const judgeComments = new Map(judge.output.disagreementComments.map((comment) => [
195600
+ comment.topic,
195601
+ comment.judgeComment
195602
+ ]));
195603
+ const disagreements = resultWithoutMarkdown.disagreements.map((disagreement) => ({
195604
+ ...disagreement,
195605
+ judgeComment: judgeComments.get(disagreement.topic) ?? disagreement.judgeComment
194767
195606
  }));
194768
- }
194769
- aggregate = {
194770
- ...aggregate,
194771
- findings: admitFindings({
195607
+ const crossModelAnalysis = buildCrossModelAnalysis(judge, reviewMode);
195608
+ const budgetAfterJudge = budgetTracker.snapshot();
195609
+ const finalWarnings = Array.from(new Set([
195610
+ ...resultWithoutMarkdown.audit.warnings ?? [],
195611
+ ...outputWarningMessages(budgetAfterJudge),
195612
+ ...tokenUsageWarningMessages(budgetTracker, budgetAfterJudge)
195613
+ ]));
195614
+ const finalDecision = budgetAfterJudge.completion.status === "incomplete" ? "block" : decision;
195615
+ const resultAfterJudge = {
195616
+ ...resultWithoutMarkdown,
195617
+ decision: finalDecision,
195618
+ completion: budgetAfterJudge.completion,
195619
+ executionBudget: budgetAfterJudge.executionBudget,
195620
+ disagreements,
195621
+ ...crossModelAnalysis ? { crossModelAnalysis } : {},
195622
+ audit: {
195623
+ ...resultWithoutMarkdown.audit,
195624
+ completedAt: new Date().toISOString(),
195625
+ warnings: finalWarnings,
195626
+ modelCalls: budgetAfterJudge.modelCalls
195627
+ }
195628
+ };
195629
+ const judgeEvent = {
195630
+ type: "judge_completed",
195631
+ traceId,
195632
+ provider: judge.provider,
195633
+ status: judge.status,
195634
+ ...judge.executionIdentity ? { executionIdentity: judge.executionIdentity } : {},
195635
+ timestamp: new Date().toISOString()
195636
+ };
195637
+ if (judge.error)
195638
+ judgeEvent.error = judge.error;
195639
+ await trace.write(judgeEvent);
195640
+ resultAfterJudge.audit.completedAt = new Date().toISOString();
195641
+ await writeReviewBudgetCompleted({
195642
+ trace,
195643
+ traceId,
195644
+ budgetTracker,
195645
+ requestFingerprint
195646
+ });
195647
+ await trace.write({
195648
+ type: "decision_completed",
195649
+ traceId,
195650
+ decision: finalDecision,
195651
+ timestamp: new Date().toISOString()
195652
+ });
195653
+ await trace.write({
195654
+ type: "response_sent",
195655
+ traceId,
195656
+ timestamp: new Date().toISOString()
195657
+ });
195658
+ completePhase("finalize");
195659
+ await reportReviewCompleted(resultAfterJudge);
195660
+ return await finalizeReviewResult({
194772
195661
  tool,
194773
- request: built.request,
194774
- findings: aggregate.findings,
194775
- reviewMode
194776
- })
194777
- };
194778
- if (aggregate.findings.some((finding) => finding.disposition === "disputed")) {
194779
- budgetTracker.markIncomplete("disputed_finding");
195662
+ trace,
195663
+ result: resultAfterJudge,
195664
+ summaryText: resultAfterJudge.completion.status === "incomplete" ? defaultSummaryText(resultAfterJudge) : judge.output.summaryText
195665
+ });
195666
+ } catch (error51) {
195667
+ await reportReviewFailure(error51, trace);
195668
+ throw error51;
195669
+ } finally {
195670
+ await trace.finalize();
195671
+ if (snapshot)
195672
+ await cleanupSnapshot(snapshot.root);
194780
195673
  }
194781
- const cisaPolicy = loaded.config.securityReview.cisaSecureByDesign;
194782
- const cisa = tool === "security_review" && cisaPolicy.enabled ? computeCisaGate(aggregate.findings, normalizedAgentResults, cisaPolicy) : undefined;
194783
- const budgetBeforeJudge = budgetTracker.snapshot();
194784
- const decision = budgetBeforeJudge.completion.status === "incomplete" ? "block" : decide({
194785
- tool,
194786
- findings: aggregate.findings,
194787
- cisa: cisaPolicy.gate ? cisa : undefined,
194788
- degraded,
194789
- secretScan: { detected: secretScan.detected, blocked: false }
194790
- });
194791
- const completedAt = new Date().toISOString();
194792
- const resultWithoutMarkdown = {
194793
- decision,
194794
- completion: budgetBeforeJudge.completion,
194795
- executionBudget: budgetBeforeJudge.executionBudget,
194796
- requestFingerprint,
194797
- degraded,
194798
- agentsUsed,
194799
- reviewMode,
194800
- coverage,
194801
- ...verificationMode ? { verificationMode } : {},
194802
- findings: aggregate.findings,
194803
- cisaSecureByDesign: cisa,
194804
- disagreements: aggregate.disagreements,
194805
- testsToAdd: selectRegressionTests(aggregate.testsToAdd),
194806
- residualRisks: tool === "security_review" && aggregate.residualRisks.length === 0 ? [
194807
- "No residual risks were reported by completed agents; verify security assumptions before release."
194808
- ] : aggregate.residualRisks,
194809
- openQuestions: Array.from(new Set([
194810
- ...aggregate.openQuestions,
194811
- ...buildAdmissionOpenQuestions(aggregate.findings)
194812
- ])),
194813
- agentOpinions: normalizedAgentResults.map((result) => agentOpinionSummary(result, request.options?.includeAgentRawOutputs === true)),
194814
- audit: {
194815
- traceId,
194816
- startedAt,
194817
- completedAt,
194818
- agentsUsed,
194819
- redactionsApplied: secretScan.redactions,
194820
- networkMode,
194821
- workspaceMode: "temp_snapshot",
194822
- configHash: loaded.configHash,
194823
- warnings: Array.from(new Set([...warnings, ...trace.warnings])),
194824
- modelCalls: budgetBeforeJudge.modelCalls
194825
- }
194826
- };
194827
- const summaryText = defaultSummaryText(resultWithoutMarkdown);
194828
- const judge = await runBudgetedJudge({
194829
- tool,
194830
- result: resultWithoutMarkdown,
194831
- summaryText,
194832
- agentFindings: buildJudgeAgentFindings(normalizedAgentResults),
194833
- config: loaded.config.judge,
194834
- requestedProvider: request.options?.judgeProvider,
194835
- env: options.env ?? process.env,
194836
- budgetTracker,
194837
- trace,
194838
- traceId
194839
- });
194840
- const judgeComments = new Map(judge.output.disagreementComments.map((comment) => [
194841
- comment.topic,
194842
- comment.judgeComment
194843
- ]));
194844
- const disagreements = resultWithoutMarkdown.disagreements.map((disagreement) => ({
194845
- ...disagreement,
194846
- judgeComment: judgeComments.get(disagreement.topic) ?? disagreement.judgeComment
194847
- }));
194848
- const crossModelAnalysis = buildCrossModelAnalysis(judge, reviewMode);
194849
- const budgetAfterJudge = budgetTracker.snapshot();
194850
- const finalWarnings = Array.from(new Set([
194851
- ...resultWithoutMarkdown.audit.warnings ?? [],
194852
- ...outputWarningMessages(budgetAfterJudge),
194853
- ...tokenUsageWarningMessages(budgetTracker, budgetAfterJudge)
194854
- ]));
194855
- const finalDecision = budgetAfterJudge.completion.status === "incomplete" ? "block" : decision;
194856
- const resultAfterJudge = {
194857
- ...resultWithoutMarkdown,
194858
- decision: finalDecision,
194859
- completion: budgetAfterJudge.completion,
194860
- executionBudget: budgetAfterJudge.executionBudget,
194861
- disagreements,
194862
- ...crossModelAnalysis ? { crossModelAnalysis } : {},
194863
- audit: {
194864
- ...resultWithoutMarkdown.audit,
194865
- completedAt: new Date().toISOString(),
194866
- warnings: finalWarnings,
194867
- modelCalls: budgetAfterJudge.modelCalls
194868
- }
194869
- };
194870
- const judgeEvent = {
194871
- type: "judge_completed",
194872
- traceId,
194873
- provider: judge.provider,
194874
- status: judge.status,
194875
- ...judge.executionIdentity ? { executionIdentity: judge.executionIdentity } : {},
194876
- timestamp: new Date().toISOString()
194877
- };
194878
- if (judge.error)
194879
- judgeEvent.error = judge.error;
194880
- await trace.write(judgeEvent);
194881
- resultAfterJudge.audit.completedAt = new Date().toISOString();
194882
- await writeReviewBudgetCompleted({
194883
- trace,
194884
- traceId,
194885
- budgetTracker,
194886
- requestFingerprint
194887
- });
194888
- await trace.write({
194889
- type: "decision_completed",
194890
- traceId,
194891
- decision: finalDecision,
194892
- timestamp: new Date().toISOString()
194893
- });
194894
- await trace.write({
194895
- type: "response_sent",
194896
- traceId,
194897
- timestamp: new Date().toISOString()
194898
- });
194899
- return await finalizeReviewResult({
194900
- tool,
194901
- trace,
194902
- result: resultAfterJudge,
194903
- summaryText: resultAfterJudge.completion.status === "incomplete" ? defaultSummaryText(resultAfterJudge) : judge.output.summaryText
194904
- });
194905
- } finally {
194906
- await trace.finalize();
194907
- if (snapshot)
194908
- await cleanupSnapshot(snapshot.root);
195674
+ } catch (error51) {
195675
+ await reportReviewFailure(error51, activeTrace);
195676
+ throw error51;
194909
195677
  }
194910
195678
  }
195679
+ function durationMsBetween(startedAt, completedAt) {
195680
+ if (!startedAt || !completedAt)
195681
+ return 0;
195682
+ const start = Date.parse(startedAt);
195683
+ const end = Date.parse(completedAt);
195684
+ if (!Number.isFinite(start) || !Number.isFinite(end))
195685
+ return 0;
195686
+ return Math.max(0, end - start);
195687
+ }
194911
195688
  async function runFindingVerification(input) {
194912
195689
  const allowDemotionRequested = input.config.verification.allowDemotion;
194913
195690
  const selection = selectVerificationTargets(input.findings, input.config.verification.maxFindings);
@@ -195069,6 +195846,7 @@ async function runFindingVerification(input) {
195069
195846
  warnOutputBytes: input.budgetTracker.budget.effectiveWarnAgentOutputBytes,
195070
195847
  maxOutputBytes: input.budgetTracker.budget.maxAgentOutputBytes,
195071
195848
  networkMode: input.networkMode,
195849
+ signal: input.signal,
195072
195850
  onStarted: (executionIdentity) => {
195073
195851
  input.budgetTracker.markStarted(group.reservation, executionIdentity);
195074
195852
  const event = buildAgentStartedEvent({
@@ -195086,6 +195864,8 @@ async function runFindingVerification(input) {
195086
195864
  try {
195087
195865
  results = await input.manager.runAll(agentInputs);
195088
195866
  } catch (error51) {
195867
+ if (error51 instanceof KyosoCancellationError)
195868
+ throw error51;
195089
195869
  for (const group of scheduledGroups) {
195090
195870
  applyVerificationVerdicts(group.targets, group.verifier, undefined);
195091
195871
  await finalizeModelCallResult({
@@ -195310,6 +196090,19 @@ async function recordSkippedJudgeCall(input, reason) {
195310
196090
  async function runAgents(input) {
195311
196091
  const agentRoles = resolveAgentRoles(input.config);
195312
196092
  const enabledAgents = ["codex", "claude"].filter((agent) => input.config.agents[agent].enabled);
196093
+ const openRouter = input.config.agents.codex.openRouter;
196094
+ const hasOpenRouterRetryPolicy = Object.values(openRouter).some((value) => value !== undefined);
196095
+ if (enabledAgents.includes("codex") && input.config.agents.codex.provider === CODEX_OPENROUTER_PROVIDER && hasOpenRouterRetryPolicy) {
196096
+ await input.trace.write({
196097
+ type: "openrouter_retry_policy_resolved",
196098
+ traceId: input.traceId,
196099
+ streamIdleTimeoutMs: openRouter.streamIdleTimeoutMs,
196100
+ streamMaxRetries: openRouter.streamMaxRetries,
196101
+ requestMaxRetries: openRouter.requestMaxRetries,
196102
+ source: "kyoso_config",
196103
+ timestamp: new Date().toISOString()
196104
+ });
196105
+ }
195313
196106
  if (enabledAgents.length === 0)
195314
196107
  return [];
195315
196108
  const reservationResult = input.budgetTracker.reserveMany(enabledAgents.map((agent) => ({ kind: "primary", agent })));
@@ -195395,6 +196188,8 @@ async function runAgents(input) {
195395
196188
  const agentInputs = enabledAgents.map((agent) => {
195396
196189
  const agentConfig = input.config.agents[agent];
195397
196190
  const role = agentRoles[agent] ?? agentConfig.role;
196191
+ let emittedRetryProgressEvents = 0;
196192
+ let retryProgressLimitWarned = false;
195398
196193
  const reservation = reservations.get(agent);
195399
196194
  if (!reservation) {
195400
196195
  throw new Error(`Missing primary budget reservation for ${agent}.`);
@@ -195415,15 +196210,27 @@ async function runAgents(input) {
195415
196210
  warnOutputBytes: input.budgetTracker.budget.effectiveWarnAgentOutputBytes,
195416
196211
  maxOutputBytes: input.budgetTracker.budget.maxAgentOutputBytes,
195417
196212
  networkMode: input.networkMode,
196213
+ signal: input.signal,
196214
+ heartbeatMs: input.progressHeartbeatMs,
196215
+ ...agent === "codex" && input.config.agents.codex.provider === CODEX_OPENROUTER_PROVIDER && openRouter.streamIdleTimeoutMs !== undefined ? { streamIdleTimeoutMs: openRouter.streamIdleTimeoutMs } : {},
195418
196216
  onStarted: (executionIdentity) => {
195419
196217
  input.budgetTracker.markStarted(reservation, executionIdentity);
195420
196218
  if (!acceptingStartedEvents)
195421
196219
  return Promise.resolve();
196220
+ const progressExecutionIdentity = input.budgetTracker.executionIdentity(reservation);
196221
+ input.progressDispatcher.emit({
196222
+ type: "agent_started",
196223
+ traceId: input.traceId,
196224
+ agent,
196225
+ role,
196226
+ ...progressExecutionIdentity ? { executionIdentity: progressExecutionIdentity } : {},
196227
+ timestamp: new Date().toISOString()
196228
+ });
195422
196229
  const event = buildAgentStartedEvent({
195423
196230
  traceId: input.traceId,
195424
196231
  agent,
195425
196232
  role,
195426
- executionIdentity: input.budgetTracker.executionIdentity(reservation)
196233
+ executionIdentity: progressExecutionIdentity
195427
196234
  });
195428
196235
  const write = (async () => {
195429
196236
  try {
@@ -195434,6 +196241,33 @@ async function runAgents(input) {
195434
196241
  })();
195435
196242
  startedWrites.push(write);
195436
196243
  return write;
196244
+ },
196245
+ onProgress: (event) => {
196246
+ if (!acceptingStartedEvents)
196247
+ return;
196248
+ input.progressDispatcher.emit({ ...event, traceId: input.traceId });
196249
+ if (event.type !== "agent_retrying")
196250
+ return;
196251
+ if (emittedRetryProgressEvents >= MAX_AGENT_RETRY_PROGRESS_EVENTS) {
196252
+ if (!retryProgressLimitWarned) {
196253
+ retryProgressLimitWarned = true;
196254
+ input.warnings.push(`AGENT_RETRY_PROGRESS_LIMIT: ${agent} emitted more than ${MAX_AGENT_RETRY_PROGRESS_EVENTS} retry progress events; later events were omitted from the audit trace.`);
196255
+ }
196256
+ return;
196257
+ }
196258
+ emittedRetryProgressEvents += 1;
196259
+ const write = (async () => {
196260
+ try {
196261
+ await input.trace.write({
196262
+ ...event,
196263
+ traceId: input.traceId,
196264
+ type: "agent_retrying"
196265
+ });
196266
+ } catch {
196267
+ input.warnings.push("AUDIT_WRITE_FAILED: agent_retrying event could not be recorded.");
196268
+ }
196269
+ })();
196270
+ startedWrites.push(write);
195437
196271
  }
195438
196272
  };
195439
196273
  });
@@ -195441,6 +196275,8 @@ async function runAgents(input) {
195441
196275
  try {
195442
196276
  results = await input.manager.runAll(agentInputs);
195443
196277
  } catch (error51) {
196278
+ if (error51 instanceof KyosoCancellationError)
196279
+ throw error51;
195444
196280
  const detail = sanitizeTextForDisplay(error51 instanceof Error ? error51.message : String(error51));
195445
196281
  input.warnings.push(`Primary-agent execution failed: ${detail}`);
195446
196282
  results = agentInputs.map((agentInput) => ({
@@ -195491,6 +196327,16 @@ async function runAgents(input) {
195491
196327
  if (result.status !== "completed") {
195492
196328
  input.budgetTracker.markIncomplete("coverage_incomplete");
195493
196329
  }
196330
+ input.progressDispatcher.emit({
196331
+ type: "agent_completed",
196332
+ traceId: input.traceId,
196333
+ agent: result.agent,
196334
+ status: result.status,
196335
+ durationMs: durationMsBetween(result.startedAt, result.completedAt),
196336
+ ...result.outputBytes === undefined ? {} : { outputBytes: result.outputBytes },
196337
+ ...result.observedStreamRetries === undefined ? {} : { observedStreamRetries: result.observedStreamRetries },
196338
+ timestamp: new Date().toISOString()
196339
+ });
195494
196340
  }
195495
196341
  await Promise.all(normalizedResults.map((result) => {
195496
196342
  const event = {
@@ -195581,6 +196427,12 @@ async function finalizeModelCallResult(input) {
195581
196427
  ...thoughtBytes === undefined ? {} : { thoughtBytes },
195582
196428
  ...outputBytes === undefined ? {} : { outputBytes },
195583
196429
  ...outputWarningTriggered === undefined ? {} : { outputWarningTriggered },
196430
+ ...input.result.observedStreamRetries === undefined ? {} : { observedStreamRetries: input.result.observedStreamRetries },
196431
+ ...input.result.discardedRetryMessageBytes === undefined ? {} : {
196432
+ discardedRetryMessageBytes: input.result.discardedRetryMessageBytes
196433
+ },
196434
+ ...input.result.firstOutputAt === undefined ? {} : { firstOutputAt: input.result.firstOutputAt },
196435
+ ...input.result.lastAcpUpdateAt === undefined ? {} : { lastAcpUpdateAt: input.result.lastAcpUpdateAt },
195584
196436
  ...input.result.salvaged === undefined ? {} : { salvaged: input.result.salvaged },
195585
196437
  ...input.result.reportedFindings === undefined ? {} : { reportedFindings: input.result.reportedFindings },
195586
196438
  ...input.result.findingsTargetExceeded === undefined ? {} : { findingsTargetExceeded: input.result.findingsTargetExceeded },
@@ -195619,6 +196471,12 @@ async function finalizeModelCallResult(input) {
195619
196471
  ...thoughtBytes === undefined ? {} : { thoughtBytes },
195620
196472
  ...outputBytes === undefined ? {} : { outputBytes },
195621
196473
  ...outputWarningTriggered === undefined ? {} : { outputWarningTriggered },
196474
+ ...input.result.observedStreamRetries === undefined ? {} : { observedStreamRetries: input.result.observedStreamRetries },
196475
+ ...input.result.discardedRetryMessageBytes === undefined ? {} : {
196476
+ discardedRetryMessageBytes: input.result.discardedRetryMessageBytes
196477
+ },
196478
+ ...input.result.firstOutputAt === undefined ? {} : { firstOutputAt: input.result.firstOutputAt },
196479
+ ...input.result.lastAcpUpdateAt === undefined ? {} : { lastAcpUpdateAt: input.result.lastAcpUpdateAt },
195622
196480
  ...input.result.salvaged === undefined ? {} : { salvaged: input.result.salvaged },
195623
196481
  ...input.result.reportedFindings === undefined ? {} : { reportedFindings: input.result.reportedFindings },
195624
196482
  ...input.result.findingsTargetExceeded === undefined ? {} : { findingsTargetExceeded: input.result.findingsTargetExceeded },
@@ -196042,5 +196900,6 @@ function resolveNetworkMode(requested, configDefault, mcpNetworkMode) {
196042
196900
  }
196043
196901
  export {
196044
196902
  runReview,
196045
- defineConfig
196903
+ defineConfig,
196904
+ KyosoCancellationError
196046
196905
  };