@yansigit/opencodex 2.33.0 → 2.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/README.md +3 -3
  2. package/gui/dist/assets/index-BjCaHxdz.js +112 -0
  3. package/gui/dist/assets/index-DLkXOXLC.css +1 -0
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +1 -1
  6. package/src/adapters/anthropic.ts +79 -2
  7. package/src/adapters/command-code.ts +141 -23
  8. package/src/adapters/cursor/call-id.ts +44 -0
  9. package/src/adapters/cursor/checkpoint-store.ts +15 -10
  10. package/src/adapters/cursor/discovery.ts +60 -2
  11. package/src/adapters/cursor/effort-map.ts +79 -1
  12. package/src/adapters/cursor/envelope-echo.ts +162 -0
  13. package/src/adapters/cursor/live-models.ts +7 -2
  14. package/src/adapters/cursor/live-transport.ts +17 -1
  15. package/src/adapters/cursor/message-mapper.ts +4 -1
  16. package/src/adapters/cursor/native-exec-fs.ts +13 -12
  17. package/src/adapters/cursor/native-exec-network.ts +3 -5
  18. package/src/adapters/cursor/native-exec-policy.ts +47 -0
  19. package/src/adapters/cursor/native-exec-shell.ts +116 -31
  20. package/src/adapters/cursor/native-exec.ts +38 -10
  21. package/src/adapters/cursor/protobuf-events.ts +28 -2
  22. package/src/adapters/cursor/protobuf-request.ts +93 -41
  23. package/src/adapters/cursor/request-builder.ts +39 -10
  24. package/src/adapters/cursor/tool-definitions.ts +27 -3
  25. package/src/adapters/cursor/tool-result-normalize.ts +51 -6
  26. package/src/adapters/cursor/types.ts +23 -4
  27. package/src/adapters/cursor.ts +170 -29
  28. package/src/adapters/google-aistudio-parser.ts +49 -0
  29. package/src/adapters/google-antigravity-replay.ts +105 -25
  30. package/src/adapters/google-antigravity-wire.ts +5 -0
  31. package/src/adapters/google-errors.ts +41 -12
  32. package/src/adapters/google-http.ts +12 -11
  33. package/src/adapters/google.ts +219 -36
  34. package/src/adapters/image.ts +1 -1
  35. package/src/adapters/kiro-constants.ts +15 -0
  36. package/src/adapters/kiro-tools.ts +43 -15
  37. package/src/adapters/kiro.ts +54 -9
  38. package/src/adapters/openai-chat.ts +286 -242
  39. package/src/adapters/openai-responses.ts +335 -24
  40. package/src/adapters/run-turn-queue.ts +36 -1
  41. package/src/adapters/tool-catalog-nudge.ts +2 -2
  42. package/src/adapters/xai-tool-schema.ts +436 -0
  43. package/src/bridge.ts +67 -26
  44. package/src/chat/inbound.ts +29 -1
  45. package/src/chat/outbound.ts +15 -7
  46. package/src/claude/agents-inject.ts +8 -1
  47. package/src/claude/outbound.ts +10 -8
  48. package/src/cli/account-api.ts +27 -7
  49. package/src/cli/account-extended.ts +10 -3
  50. package/src/cli/account.ts +29 -5
  51. package/src/cli/alias.ts +66 -0
  52. package/src/cli/claude.ts +26 -1
  53. package/src/cli/dispatch.ts +13 -1
  54. package/src/cli/help.ts +1 -0
  55. package/src/cli/index.ts +6 -1
  56. package/src/cli/init.ts +1 -0
  57. package/src/cli/models-runtime.ts +95 -0
  58. package/src/cli/models.ts +13 -7
  59. package/src/cli/provider-runtime.ts +16 -2
  60. package/src/cli/registry.ts +6 -1
  61. package/src/cli/telemetry-commands.ts +25 -0
  62. package/src/cli/v2.ts +34 -10
  63. package/src/codex/account-pause.ts +2 -1
  64. package/src/codex/account-priority.ts +3 -2
  65. package/src/codex/app-server-processes.ts +80 -6
  66. package/src/codex/auth-api.ts +48 -8
  67. package/src/codex/auth-context.ts +21 -18
  68. package/src/codex/catalog/aggregation.ts +6 -0
  69. package/src/codex/catalog/model-metadata.ts +13 -1
  70. package/src/codex/catalog/native-models.ts +5 -2
  71. package/src/codex/catalog/parsing.ts +16 -0
  72. package/src/codex/catalog/provider-fetch.ts +20 -3
  73. package/src/codex/catalog/sync.ts +127 -2
  74. package/src/codex/catalog.ts +1 -1
  75. package/src/codex/codex-write-lock.ts +3 -1
  76. package/src/codex/convergence-types.ts +1 -1
  77. package/src/codex/convergence.ts +22 -2
  78. package/src/codex/desired-state.ts +2 -2
  79. package/src/codex/desktop-app-restart.ts +18 -5
  80. package/src/codex/inject-coordination.ts +83 -0
  81. package/src/codex/inject.ts +14 -1
  82. package/src/codex/log-guard/inspect.ts +22 -4
  83. package/src/codex/model-entitlements.ts +9 -2
  84. package/src/codex/prompt-layers.ts +371 -25
  85. package/src/codex/prompt-text-probe.ts +238 -0
  86. package/src/codex/quota.ts +123 -18
  87. package/src/codex/routing.ts +9 -0
  88. package/src/codex/subagent-model-fallback.ts +198 -27
  89. package/src/codex/transition-state.ts +107 -8
  90. package/src/combos/types.ts +10 -0
  91. package/src/compatibility/openai-responses.ts +33 -1
  92. package/src/config/autonomous-remediation.ts +21 -0
  93. package/src/config/provider-validation.ts +14 -0
  94. package/src/config/rebase-provenance.ts +68 -0
  95. package/src/config.ts +191 -17
  96. package/src/generated/compatibility-version.json +279 -159
  97. package/src/generated/model-metadata.ts +3 -0
  98. package/src/images/loop.ts +5 -4
  99. package/src/lab/conformance/fixtures/protocol-v1-cases.json +1 -1
  100. package/src/lab/fabric/producer-child.ts +1 -1
  101. package/src/lib/config-ownership.ts +20 -0
  102. package/src/lib/errors.ts +11 -2
  103. package/src/lib/package-tree-integrity.ts +101 -0
  104. package/src/oauth/aistudio-credentials.ts +65 -0
  105. package/src/oauth/aistudio-native-daemon.ts +116 -0
  106. package/src/oauth/aistudio-session-sync.ts +95 -0
  107. package/src/oauth/generic-account-failover.ts +231 -0
  108. package/src/oauth/google-aistudio-auth.ts +98 -0
  109. package/src/oauth/index.ts +57 -5
  110. package/src/oauth/key-providers.ts +18 -1
  111. package/src/oauth/kiro.ts +45 -0
  112. package/src/oauth/login-cli.ts +65 -1
  113. package/src/oauth/types.ts +15 -0
  114. package/src/providers/codex-capacity.ts +5 -2
  115. package/src/providers/command-code-efforts.ts +38 -6
  116. package/src/providers/context-cap.ts +4 -3
  117. package/src/providers/default-aliases.ts +65 -0
  118. package/src/providers/derive.ts +29 -1
  119. package/src/providers/fastwire.ts +7 -1
  120. package/src/providers/model-presets.ts +119 -0
  121. package/src/providers/new-model-policy.ts +146 -0
  122. package/src/providers/provider-id-rewrite.ts +2 -1
  123. package/src/providers/quota.ts +157 -46
  124. package/src/providers/registry.ts +184 -71
  125. package/src/providers/slug-codec.ts +52 -0
  126. package/src/responses/code-mode-helper-compat.ts +50 -0
  127. package/src/responses/custom-tool-compat.ts +34 -10
  128. package/src/responses/parser.ts +4 -0
  129. package/src/responses/schema.ts +5 -1
  130. package/src/responses/thought-signature-replay.ts +17 -0
  131. package/src/router.ts +43 -2
  132. package/src/routing/account-pool/cooldown.ts +8 -0
  133. package/src/routing/account-pool/index.ts +1 -0
  134. package/src/routing/analytics.ts +1 -0
  135. package/src/routing/quota.ts +10 -0
  136. package/src/server/auth-cors.ts +24 -0
  137. package/src/server/chat-completions.ts +26 -16
  138. package/src/server/chat-native-sse.ts +3 -3
  139. package/src/server/chat-native.ts +30 -11
  140. package/src/server/claude-messages.ts +1 -1
  141. package/src/server/effort-policy.ts +16 -0
  142. package/src/server/index.ts +180 -14
  143. package/src/server/lifecycle.ts +52 -1
  144. package/src/server/management/agent-settings-routes.ts +31 -15
  145. package/src/server/management/codex-prompt-routes.ts +570 -0
  146. package/src/server/management/combo-routes.ts +2 -1
  147. package/src/server/management/config-routes.ts +27 -9
  148. package/src/server/management/context.ts +9 -0
  149. package/src/server/management/logs-usage-routes.ts +11 -5
  150. package/src/server/management/model-routes.ts +266 -0
  151. package/src/server/management/oauth-account-routes.ts +13 -3
  152. package/src/server/management/provider-routes.ts +137 -3
  153. package/src/server/management/routing-profile-routes.ts +2 -2
  154. package/src/server/management-api.ts +2 -0
  155. package/src/server/port-reclaim.ts +19 -1
  156. package/src/server/relay-eager.ts +147 -20
  157. package/src/server/relay.ts +251 -19
  158. package/src/server/request-log-conversation.ts +33 -0
  159. package/src/server/request-log.ts +48 -21
  160. package/src/server/responses/collaboration.ts +42 -5
  161. package/src/server/responses/combo-stream-preflight.ts +10 -3
  162. package/src/server/responses/core.ts +575 -140
  163. package/src/server/responses/empty-completion-guard.ts +35 -0
  164. package/src/server/responses/fetch-helpers.ts +14 -6
  165. package/src/server/responses/input-admission.ts +3 -1
  166. package/src/server/responses/passthrough-error.ts +33 -9
  167. package/src/server/responses/policy-fallback.ts +1 -1
  168. package/src/server/responses/responses-field-backfill.ts +105 -13
  169. package/src/server/responses/ws-upstream.ts +35 -5
  170. package/src/server/responses-custom-tool-repair.ts +52 -7
  171. package/src/server/responses-terminal-repair.ts +25 -4
  172. package/src/server/sse-frame-buffer.ts +31 -4
  173. package/src/server/ws-bridge.ts +14 -2
  174. package/src/smoke/fingerprint-cache.ts +133 -0
  175. package/src/smoke/live-scenarios.ts +33 -0
  176. package/src/smoke/runner.ts +119 -0
  177. package/src/telemetry/dispatcher.ts +44 -0
  178. package/src/telemetry/fingerprint.ts +24 -0
  179. package/src/telemetry/hook.ts +43 -0
  180. package/src/telemetry/ledger.ts +54 -0
  181. package/src/telemetry/types.ts +23 -0
  182. package/src/types/config.ts +66 -14
  183. package/src/types/provider.ts +79 -1
  184. package/src/types/request.ts +18 -10
  185. package/src/types/tools.ts +30 -11
  186. package/src/types.ts +1 -0
  187. package/src/usage/command-code-manifest.ts +116 -0
  188. package/src/usage/cost.ts +2 -2
  189. package/src/usage/expected-prices.ts +126 -24
  190. package/src/usage/log.ts +18 -8
  191. package/src/usage/summary.ts +34 -12
  192. package/src/web-search/exa-executor.ts +40 -9
  193. package/src/web-search/index.ts +16 -8
  194. package/src/web-search/loop.ts +5 -4
  195. package/gui/dist/assets/index-DKLr4LTE.js +0 -102
  196. package/gui/dist/assets/index-DrSQdTRd.css +0 -1
@@ -193,6 +193,12 @@ export function deriveComboCatalogModel(
193
193
  ? { supportsServiceTier: false }
194
194
  : {}),
195
195
  ...(members.some(member => member.supportsReasoningSummaries === false) ? { supportsReasoningSummaries: false } : {}),
196
+ // A combo is only as capable as its least capable member. One member that cannot honour
197
+ // text.verbosity is enough to make the control a no-op for the whole combo, so the
198
+ // conservative false propagates — the same rule supportsReasoningSummaries uses above.
199
+ // Without this, routing a combo through an xAI or Kiro member re-advertised a control the
200
+ // upstream accepts and ignores.
201
+ ...(members.some(member => member.supportsVerbosity === false) ? { supportsVerbosity: false } : {}),
196
202
  ...(members.every(member => member.codexToolMode === "shell")
197
203
  ? { codexToolMode: "shell" as const }
198
204
  : {}),
@@ -5,7 +5,11 @@ import { isModelCacheGenerationCurrent } from "../model-cache";
5
5
  import type { GenerationContext } from "../../lib/state-store-sweeper";
6
6
  import { captureConfigGeneration } from "../../lib/state-store-sweeper";
7
7
  import { assertNotRealHomeUnderTest } from "../../lib/test-home-guard";
8
- import { CURSOR_STATIC_MODELS } from "../../adapters/cursor/discovery";
8
+ import {
9
+ CURSOR_KNOWN_UNCALLABLE_MODEL_IDS,
10
+ CURSOR_STATIC_MODELS,
11
+ inferCursorContextWindow,
12
+ } from "../../adapters/cursor/discovery";
9
13
  import { cursorModelEffortLadder } from "../../adapters/cursor/effort-map";
10
14
  import { generatedModelMetadata, type CatalogModel } from "./parsing";
11
15
 
@@ -494,6 +498,14 @@ export function registryLayerForModel(provider: string, modelId: string): ModelM
494
498
  : {}),
495
499
  };
496
500
  }
501
+ // Quarantined models stay out of the routed catalog, but retain metadata for
502
+ // existing configurations and diagnostics.
503
+ if (CURSOR_KNOWN_UNCALLABLE_MODEL_IDS.has(modelId)) {
504
+ return {
505
+ contextWindow: inferCursorContextWindow(modelId),
506
+ reasoningEfforts: cursorModelEffortLadder(modelId) ?? [],
507
+ };
508
+ }
497
509
  }
498
510
  return undefined;
499
511
  }
@@ -3,6 +3,9 @@ export const NATIVE_DAYBREAK_BLUE_MODEL = "gpt-daybreak-blue-latest";
3
3
 
4
4
  /** Native ChatGPT/Codex ids whose availability is proven per authenticated account. */
5
5
  export const ACCOUNT_GATED_NATIVE_OPENAI_MODELS: ReadonlySet<string> = new Set([
6
+ "gpt-5.6-sol",
7
+ "gpt-5.6-terra",
8
+ "gpt-5.6-luna",
6
9
  NATIVE_DAYBREAK_BLUE_MODEL,
7
10
  ]);
8
11
 
@@ -58,8 +61,8 @@ export function nativeOpenAiCapabilitySourceSlug(slug: string): string {
58
61
  * discover it on a clean install.
59
62
  *
60
63
  * Availability is not static: catalog sync and Pool routing require the account's authenticated
61
- * `/models` roster to contain the slug. An unconfirmed or unentitled account never receives the
62
- * request. `disabledModels` remains the independent user visibility control.
64
+ * `/models` roster to contain account-gated slugs. An unconfirmed or unentitled account never
65
+ * receives the request. `disabledModels` remains the independent user visibility control.
63
66
  *
64
67
  * Devlog: 260816_codexrs_multiagent_v2_and_history_perf/011 §4-bis.
65
68
  */
@@ -238,6 +238,22 @@ export function readCodexCatalogPathForHome(codexHome: string): string {
238
238
  return join(codexHome, "opencodex-catalog.json");
239
239
  }
240
240
 
241
+ /**
242
+ * Read the configured auto-review model from the root of Codex's config.toml (issue #1225).
243
+ * Stamped onto catalog entries as `auto_review_model_override` during sync so the auto-review
244
+ * subagent uses the operator's chosen model across catalog regenerations.
245
+ */
246
+ export function readConfiguredAutoReviewModel(): string | null {
247
+ try {
248
+ const configPath = activeCodexConfigPath();
249
+ if (existsSync(configPath)) {
250
+ const toml = readFileSync(configPath, "utf-8");
251
+ return readRootTomlString(toml, "auto_review_model");
252
+ }
253
+ } catch { /* ignore */ }
254
+ return null;
255
+ }
256
+
241
257
  export function parseCatalogJson(raw: string): RawCatalog | null {
242
258
  try {
243
259
  const cat = JSON.parse(raw);
@@ -38,7 +38,7 @@ import {
38
38
  serviceTierSupportFromPolicy,
39
39
  } from "../../providers/service-tier";
40
40
  import type { FastPolicyAuthority } from "../../providers/fastwire";
41
- import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport } from "../../providers/registry";
41
+ import { effectiveGoogleMode, getProviderRegistryEntry, providerMatchesRegistryTransport, registryEntryForProviderDestination } from "../../providers/registry";
42
42
  import { parseAntigravityAvailableModels, registerAntigravityDiscoveredWireModels } from "../../providers/antigravity-models";
43
43
  import { applyProviderContextCap, providerContextCap, resolveUnknownRoutedContextWindow } from "../../providers/context-cap";
44
44
  import { clampAutoCompactTokenLimit } from "../../providers/auto-compact-budget";
@@ -578,6 +578,7 @@ function providerCatalogFingerprint(name: string, prov: OcxProviderConfig): Reco
578
578
  re: prov.modelReasoningEfforts ?? null,
579
579
  defRe: prov.modelDefaultReasoningEfforts ?? null,
580
580
  rsSum: prov.modelSupportsReasoningSummaries ?? null,
581
+ verbosity: prov.modelSupportsVerbosity ?? null,
581
582
  rsDel: prov.modelReasoningSummaryDelivery ?? null,
582
583
  serviceTier: prov.modelSupportsServiceTier ?? null,
583
584
  noVis: [...(prov.noVisionModels ?? [])].sort(),
@@ -646,8 +647,23 @@ function configuredReasoningSummarySupport(prov: OcxProviderConfig | undefined,
646
647
  return modelRecordValue(prov.modelReasoningSummaryDelivery, id) !== undefined ? true : undefined;
647
648
  }
648
649
 
649
- export function applyProviderConfigHints(name: string, prov: OcxProviderConfig, model: CatalogModel, providerCap?: number): CatalogModel {
650
+ function configuredVerbositySupport(name: string, prov: OcxProviderConfig | undefined, id: string): boolean | undefined {
651
+ const explicit = prov ? modelRecordValue(prov.modelSupportsVerbosity, id) : undefined;
652
+ if (explicit !== undefined) return explicit;
653
+ if (!prov) return undefined;
650
654
  void name;
655
+ // Provider-wide fallback for ids the per-model map does not enumerate — a live-discovered
656
+ // model would otherwise re-advertise a control the upstream accepts and ignores.
657
+ //
658
+ // Read from the PROVIDER CONFIG, never from PROVIDER_REGISTRY. A gather flight captures its
659
+ // registry authority up front and forbids any later registry read, so consulting the registry
660
+ // here made a custom-destination flight fall back to "configured" instead of serving its own
661
+ // discovery result (tests/codex-gather-authority.test.ts). `applyVerbosityDefaults` in
662
+ // providers/derive.ts materializes the registry default into the config at seed/enrich time.
663
+ return prov.supportsVerbosity;
664
+ }
665
+
666
+ export function applyProviderConfigHints(name: string, prov: OcxProviderConfig, model: CatalogModel, providerCap?: number): CatalogModel {
651
667
  const configuredCap = configuredContextWindow(prov, model.id);
652
668
  const configuredMaxInput = configuredMaxInputTokens(prov, model.id);
653
669
  const configuredAutoCompact = configuredAutoCompactTokenLimit(prov, model.id);
@@ -663,6 +679,7 @@ export function applyProviderConfigHints(name: string, prov: OcxProviderConfig,
663
679
  const reasoningEfforts = configuredReasoningEfforts(prov, model.id);
664
680
  const defaultReasoningEffort = modelRecordValue(prov.modelDefaultReasoningEfforts, model.id) ?? model.defaultReasoningEffort;
665
681
  const supportsReasoningSummaries = configuredReasoningSummarySupport(prov, model.id);
682
+ const supportsVerbosity = configuredVerbositySupport(name, prov, model.id);
666
683
  const fastPolicy = fastPolicyForModel(prov, model.id, name);
667
684
  const supportsServiceTier = serviceTierSupportFromPolicy(fastPolicy);
668
685
  const {
@@ -691,11 +708,11 @@ export function applyProviderConfigHints(name: string, prov: OcxProviderConfig,
691
708
  : {}),
692
709
  ...(defaultReasoningEffort ? { defaultReasoningEffort } : {}),
693
710
  ...(typeof supportsReasoningSummaries === "boolean" ? { supportsReasoningSummaries } : {}),
711
+ ...(typeof supportsVerbosity === "boolean" ? { supportsVerbosity } : {}),
694
712
  ...(typeof supportsServiceTier === "boolean" ? { supportsServiceTier } : {}),
695
713
  ...(supportsServiceTier === true && fastPolicy.fastTierDescription !== undefined
696
714
  ? { fastTierDescription: fastPolicy.fastTierDescription }
697
715
  : {}),
698
- ...(prov.adapter === "kiro" ? { supportsVerbosity: false } : {}),
699
716
  // Default-on for openai-chat providers (explicit false opts out); other adapters
700
717
  // advertise only on explicit opt-in.
701
718
  ...(prov.parallelToolCalls === true || (prov.adapter === "openai-chat" && prov.parallelToolCalls !== false)
@@ -41,7 +41,7 @@ import {
41
41
  } from "../model-entitlements";
42
42
 
43
43
 
44
- import { CODEX_CUSTOM_MODEL_CATALOG_KIND, CODEX_PROVIDER_MODEL_CATALOG_KIND, activeCodexModelsCachePath, applyCatalogMetadata, applyMultiAgentMode, applyNativeOpenAiContextOverride, applyRoutedCodexToolMode, catalogBackupPathFor, catalogHasRoutedEntries, catalogModelSlug, ensureStrictCatalogFields, findNativeTemplate, isDefaultCatalogPath, isRoutedModelCompatibilityExcluded, legacyCatalogBackupPath, normalizeRoutedCatalogEntry, normalizeServiceTiers, readCatalog, readCatalogBackup, readCodexCatalogPath, readCodexCatalogPathForHome, readNativeBaseline } from "./parsing";
44
+ import { CODEX_CUSTOM_MODEL_CATALOG_KIND, CODEX_PROVIDER_MODEL_CATALOG_KIND, activeCodexModelsCachePath, applyCatalogMetadata, applyMultiAgentMode, applyNativeOpenAiContextOverride, applyRoutedCodexToolMode, catalogBackupPathFor, catalogHasRoutedEntries, catalogModelSlug, ensureStrictCatalogFields, findNativeTemplate, isDefaultCatalogPath, isRoutedModelCompatibilityExcluded, legacyCatalogBackupPath, normalizeRoutedCatalogEntry, normalizeServiceTiers, readCatalog, readCatalogBackup, readCodexCatalogPath, readCodexCatalogPathForHome, readConfiguredAutoReviewModel, readNativeBaseline } from "./parsing";
45
45
  import type { CatalogModel, MultiAgentMode, RawCatalog, RawEntry } from "./parsing";
46
46
  import { accountBoundNativeOpenAiSlugs, accountBoundNativeOpenAiSlugsBySelector, applyNativeVisibility, CODEX_NATIVE_ALIAS_CATALOG_KIND, desktopAllowlistSuppressedNativeSlugs, disabledNativeSlugs, isNativeAliasCatalogEntry, isUnsupportedOpenAiNativeSlug, NATIVE_OPENAI_MODELS, nativeContextLimits, observedAccountBoundNativeEntries, shouldIncludeAccountBoundNativeOpenAi, shouldIncludeNativeOpenAi, shouldUpgradeToUpstreamEntry, SUPPORTED_NATIVE_OPENAI_SLUGS, upstreamNativeEntry, type NativeContextLimitsInput } from "./metadata";
47
47
  import {
@@ -1403,6 +1403,130 @@ function catalogModelsForMergeWithNativeRecovery(
1403
1403
  ]);
1404
1404
  }
1405
1405
 
1406
+ const AUTO_REVIEW_MODEL_CONTROL_CHARS = /[\u0000-\u001f\u007f-\u009f\u2028\u2029\s]/;
1407
+
1408
+ export function isValidAutoReviewModel(value: unknown): value is string {
1409
+ if (typeof value !== "string") return false;
1410
+ const trimmed = value.trim();
1411
+ return Boolean(trimmed)
1412
+ && trimmed.length <= 1024
1413
+ && !AUTO_REVIEW_MODEL_CONTROL_CHARS.test(trimmed);
1414
+ }
1415
+
1416
+ export type AutoReviewModelOverrideResult = "absent" | "applied" | "invalid" | "unresolved";
1417
+
1418
+ function isRoutedCatalogEntry(entry: RawEntry): boolean {
1419
+ const slug = typeof entry.slug === "string" ? entry.slug : "";
1420
+ return slug.includes("/")
1421
+ || (typeof entry.description === "string" && entry.description.startsWith("Routed via opencodex → "));
1422
+ }
1423
+
1424
+ function clearAutoReviewModelOverride(
1425
+ models: readonly RawEntry[],
1426
+ sourceModels: readonly RawEntry[] = [],
1427
+ ): void {
1428
+ const observedModels = [...models, ...sourceModels];
1429
+ const configuredValues = new Set(observedModels.flatMap(entry => {
1430
+ const value = entry?.auto_review_model_override;
1431
+ return typeof value === "string" && value.trim() ? [value] : [];
1432
+ }));
1433
+ const globalStamp = configuredValues.size === 1
1434
+ && observedModels.some(entry => {
1435
+ const value = entry.auto_review_model_override;
1436
+ return isRoutedCatalogEntry(entry)
1437
+ && typeof value === "string"
1438
+ && value.trim().length > 0
1439
+ && configuredValues.has(value);
1440
+ })
1441
+ && observedModels.every(entry => {
1442
+ const value = entry?.auto_review_model_override;
1443
+ return value === null
1444
+ || value === undefined
1445
+ || (typeof value === "string" && configuredValues.has(value));
1446
+ });
1447
+ for (const entry of models) {
1448
+ if (!entry || typeof entry !== "object") continue;
1449
+ const current = entry.auto_review_model_override;
1450
+ if (isRoutedCatalogEntry(entry)
1451
+ || (globalStamp && typeof current === "string" && configuredValues.has(current))) {
1452
+ entry.auto_review_model_override = null;
1453
+ }
1454
+ }
1455
+ }
1456
+
1457
+ function warnAutoReviewModelDiagnostic(
1458
+ reason: "invalid" | "unresolved",
1459
+ configured: string,
1460
+ ): void {
1461
+ const safeConfigured = JSON.stringify(redactSecretString(configured));
1462
+ const detail = reason === "unresolved"
1463
+ ? "the selector was not found in the final catalog"
1464
+ : "the selector format is invalid";
1465
+ console.warn(
1466
+ `[opencodex] auto_review_model ${detail} (${safeConfigured}); preserving normal upstream auto-review behavior.`,
1467
+ );
1468
+ }
1469
+
1470
+ function preserveNativeAutoReviewModelOverrides(
1471
+ models: readonly RawEntry[],
1472
+ sourceModels: readonly RawEntry[],
1473
+ ): void {
1474
+ const existing = new Map<string, string | null>();
1475
+ for (const entry of sourceModels) {
1476
+ const slug = typeof entry.slug === "string" ? entry.slug : undefined;
1477
+ const value = entry.auto_review_model_override;
1478
+ if (!slug || isRoutedCatalogEntry(entry)) continue;
1479
+ if (typeof value === "string" || value === null) existing.set(slug, value);
1480
+ }
1481
+ for (const entry of models) {
1482
+ const slug = typeof entry.slug === "string" ? entry.slug : undefined;
1483
+ if (!slug || isRoutedCatalogEntry(entry) || !existing.has(slug)) continue;
1484
+ entry.auto_review_model_override = existing.get(slug) ?? null;
1485
+ }
1486
+ }
1487
+
1488
+ export function applyAutoReviewModelOverride(
1489
+ models: RawEntry[] | undefined,
1490
+ autoReviewModel: string | null | undefined,
1491
+ sourceModels: readonly RawEntry[] = [],
1492
+ ): AutoReviewModelOverrideResult {
1493
+ if (!models || !Array.isArray(models)) return "absent";
1494
+ if (autoReviewModel === null || autoReviewModel === undefined) {
1495
+ clearAutoReviewModelOverride(models, sourceModels);
1496
+ return "absent";
1497
+ }
1498
+ const trimmed = autoReviewModel.trim();
1499
+ if (!trimmed) {
1500
+ clearAutoReviewModelOverride(models, sourceModels);
1501
+ return "absent";
1502
+ }
1503
+ if (!isValidAutoReviewModel(trimmed)) {
1504
+ clearAutoReviewModelOverride(models, sourceModels);
1505
+ warnAutoReviewModelDiagnostic("invalid", trimmed);
1506
+ return "invalid";
1507
+ }
1508
+ if (!configuredCatalogEntry(models, trimmed)) {
1509
+ clearAutoReviewModelOverride(models, sourceModels);
1510
+ warnAutoReviewModelDiagnostic("unresolved", trimmed);
1511
+ return "unresolved";
1512
+ }
1513
+ for (const entry of models) {
1514
+ if (entry && typeof entry === "object") {
1515
+ entry.auto_review_model_override = trimmed;
1516
+ }
1517
+ }
1518
+ return "applied";
1519
+ }
1520
+
1521
+ /** Apply the root Codex auto-review selector after the final catalog merge. */
1522
+ export function finalizeAutoReviewModelOverride(
1523
+ models: RawEntry[] | undefined,
1524
+ sourceModels: readonly RawEntry[] = [],
1525
+ ): AutoReviewModelOverrideResult {
1526
+ if (models && sourceModels.length > 0) preserveNativeAutoReviewModelOverrides(models, sourceModels);
1527
+ return applyAutoReviewModelOverride(models, readConfiguredAutoReviewModel(), sourceModels);
1528
+ }
1529
+
1406
1530
  function writeRetainedCatalogSync({
1407
1531
  config,
1408
1532
  goModels,
@@ -1596,6 +1720,7 @@ function writeRetainedCatalogSync({
1596
1720
  },
1597
1721
  });
1598
1722
  clampCatalogModelsToCodexSupport(catalog.models);
1723
+ finalizeAutoReviewModelOverride(catalog.models, catalogModelsForMerge);
1599
1724
 
1600
1725
  const added = goEntries.length + accountBoundEntries.length;
1601
1726
  const content = `${JSON.stringify(catalog, null, 2)}\n`;
@@ -1833,10 +1958,10 @@ export function invalidateCodexModelsCacheWithPermit(
1833
1958
  // keeps the cache consistent with the catalog it just wrote.
1834
1959
  if (!shouldSyncCodexOnStart(loadConfig()) && options?.allowWhenDesiredDisabled !== true) return false;
1835
1960
  const catalogPath = readCodexCatalogPathForHome(owningCodexHome);
1836
- const cachePath = join(owningCodexHome, "models_cache.json");
1837
1961
  if (!existsSync(catalogPath)) return false;
1838
1962
  const catalog = JSON.parse(readFileSync(catalogPath, "utf8"));
1839
1963
  const models = catalog.models ?? catalog;
1964
+ const cachePath = join(owningCodexHome, "models_cache.json");
1840
1965
  const currentCache = readCatalog(cachePath);
1841
1966
  const existingSlugs = new Set(models.flatMap((entry: RawEntry) =>
1842
1967
  typeof entry.slug === "string" ? [entry.slug] : []));
@@ -8,7 +8,7 @@ export { nativeEffortClamp, shouldApplyNativeEffortClamp, catalogModelEfforts, c
8
8
  export { applyProviderConfigHints, isDatedVariantId, filterCatalogVisibleModels, gatherRoutedModels, clearGatherRoutedModelsInflight, augmentRoutedModelsWithRegistryOpenAiApiRows, augmentRoutedModelsWithMetadata, resolveComboCatalogMember, configuredComboTargetModelsByProvider } from "./catalog/provider-fetch";
9
9
  export { deriveComboCatalogModel, exactComboCatalogSlugs, getLastComboCatalogOmissions, resetOpenAiApiCatalogWarningStateForTests, uniqueCatalogModelsForPublicList, uniqueCatalogModelsForRawPublicList, buildComboCatalogOmission, comboCatalogOmissionReason, summarizeComboCatalogOmissions } from "./catalog/aggregation";
10
10
  export type { ComboCatalogOmission, ComboCatalogOmissionReason } from "./catalog/aggregation";
11
- export { MAX_SPAWN_AGENT_MODEL_OVERRIDES, CANONICAL_NATIVE_CATALOG_CONTENT_POLICY, effectiveSubagentRoster, buildCatalogEntries, mergeCatalogEntriesFromObservedState, resetCatalogRuntimeStateForTests, orderForSubagents, mergeCatalogEntriesForSync, syncCatalogModels, restoreCodexCatalog, invalidateCodexModelsCache } from "./catalog/sync";
11
+ export { MAX_SPAWN_AGENT_MODEL_OVERRIDES, CANONICAL_NATIVE_CATALOG_CONTENT_POLICY, effectiveSubagentRoster, buildCatalogEntries, mergeCatalogEntriesFromObservedState, resetCatalogRuntimeStateForTests, orderForSubagents, mergeCatalogEntriesForSync, syncCatalogModels, restoreCodexCatalog, invalidateCodexModelsCache, finalizeAutoReviewModelOverride } from "./catalog/sync";
12
12
  export type { ObservedCatalogMergeInput } from "./catalog/sync";
13
13
  export type { SpawnAgentSurface, SubagentRosterExclusionReason, EffectiveSubagentModel, SubagentRosterExclusion, EffectiveSubagentRoster } from "./catalog/sync";
14
14
  export { accountBoundNativeDisplayName, accountBoundNativeModelSlugs, CODEX_ACCOUNT_BOUND_CATALOG_KIND, trustedAccountBoundNativeCatalogSlug, visibleCodexAccountSelectors } from "./catalog/account-models";
@@ -93,6 +93,8 @@ export interface CodexWriteLockOptions {
93
93
  admitted: CodexWriteWitness;
94
94
  /** Authoritative synchronous re-read while N and C are both held. */
95
95
  readAdmissionUnderLock(): CodexWriteWitness;
96
+ /** Positively authorized migration of an already-routed pre-substrate home. */
97
+ adoption?: { readonly direction: "apply" | "remove" };
96
98
  }
97
99
 
98
100
  /**
@@ -304,7 +306,7 @@ export async function withCodexWriteLock<T>(
304
306
 
305
307
  let transaction: ReturnType<typeof openCodexCoordinatorTransaction> | undefined;
306
308
  try {
307
- transaction = openCodexCoordinatorTransaction(databasePath);
309
+ transaction = openCodexCoordinatorTransaction(databasePath, options.adoption);
308
310
  } catch (error) {
309
311
  // Only contention retries. A malformed database, an unsafe path, or an
310
312
  // identity failure will fail identically forever; telling a caller to retry
@@ -34,7 +34,7 @@ export interface CodexIntegrationRecord {
34
34
  }
35
35
 
36
36
  export interface CodexHistoryState {
37
- status: "converged" | "pending" | "running" | "blocked" | "unknown" | "not-evaluated";
37
+ status: "adoption-pending" | "converged" | "pending" | "running" | "blocked" | "unknown" | "not-evaluated";
38
38
  /**
39
39
  * Why it is not converged, when it is not. These are terminal observations
40
40
  * for one attempt, not reasons to collapse the durable retry schedule.
@@ -1,6 +1,7 @@
1
1
  import { join } from "node:path";
2
2
 
3
- import { getConfigDir, websocketsEnabled, withExpectedConfigGenerationSync } from "../config";
3
+ import { getConfigDir, saveConfigPreservingClaudeCode, websocketsEnabled, withExpectedConfigGenerationSync } from "../config";
4
+ import { reconcileSuccessfulModelDiscoveries } from "../providers/new-model-policy";
4
5
  import { COMBO_NAMESPACE } from "../combos";
5
6
  import { getAuthStorePath } from "../oauth/store";
6
7
  import type { OcxConfig } from "../types";
@@ -41,6 +42,7 @@ import {
41
42
  import {
42
43
  buildCatalogEntriesFromObservedState,
43
44
  CANONICAL_NATIVE_CATALOG_CONTENT_POLICY,
45
+ finalizeAutoReviewModelOverride,
44
46
  mergeCatalogEntriesFromObservedState,
45
47
  mergeCatalogModelsWithNativeRecovery,
46
48
  orderForSubagents,
@@ -135,6 +137,7 @@ interface CandidateState {
135
137
  readonly changed: boolean;
136
138
  readonly notices: readonly CatalogNotice[];
137
139
  readonly modelEntitlements: CodexModelEntitlementSnapshot;
140
+ readonly discoveryConfig?: OcxConfig;
138
141
  }
139
142
 
140
143
  const candidateStates = new WeakMap<object, CandidateState>();
@@ -369,6 +372,7 @@ function prepareCatalog(
369
372
  ? supportedCodexReasoningEffortsFromObservedCatalog(source.runtimeSupport.catalog)
370
373
  : null,
371
374
  );
375
+ finalizeAutoReviewModelOverride(mergedModels, catalogModels);
372
376
  catalog.models = mergedModels;
373
377
  return catalog;
374
378
  }
@@ -436,8 +440,17 @@ export async function gatherCodexCatalogCandidate(
436
440
  ? active
437
441
  : !hasRoutedEntries(source.catalog) ? source.catalog : null)
438
442
  : null);
443
+ const discoveryConfig = structuredClone(snapshot.config) as OcxConfig;
444
+ const discoveryChanged = reconcileSuccessfulModelDiscoveries({
445
+ config: discoveryConfig,
446
+ models: routedModels,
447
+ authoritativeProviders: providerModelOutcomes
448
+ .filter(outcome => outcome.state === "authoritative")
449
+ .map(outcome => outcome.provider),
450
+ now: new Date().toISOString(),
451
+ });
439
452
  const preparedCatalog = prepareCatalog(
440
- snapshot.config,
453
+ discoveryConfig,
441
454
  source,
442
455
  active,
443
456
  routedModels,
@@ -502,6 +515,7 @@ export async function gatherCodexCatalogCandidate(
502
515
  || Buffer.from(cacheBytes ?? []).toString("utf8") !== preparedCacheBytes,
503
516
  notices: Object.freeze([...notices]),
504
517
  modelEntitlements,
518
+ ...(discoveryChanged ? { discoveryConfig } : {}),
505
519
  });
506
520
  return { kind: "candidate", candidate };
507
521
  } catch (error) {
@@ -649,6 +663,12 @@ export async function convergeCodexCatalog(
649
663
  const state = candidateStates.get(gathered.candidate as object)!;
650
664
  lifecycle.onCommitBegin?.();
651
665
  const committed = await commitCodexCatalogCandidate(gathered.candidate, request.deadlineMs);
666
+ if (committed.kind === "committed" && state.discoveryConfig) {
667
+ const mutable = snapshot.config as OcxConfig;
668
+ mutable.modelDiscovery = state.discoveryConfig.modelDiscovery;
669
+ mutable.disabledModels = state.discoveryConfig.disabledModels;
670
+ saveConfigPreservingClaudeCode(mutable);
671
+ }
652
672
  return {
653
673
  changed: committed.kind === "committed" ? committed.changed : false,
654
674
  catalogRefresh: projectCommit(committed, state.notices),
@@ -20,7 +20,7 @@
20
20
  *
21
21
  * Design record: devlog/_fin/260803_codex_desktop_toggle/030_desired_state.md.
22
22
  */
23
- import { loadConfig, mutatePersistedConfig } from "../config";
23
+ import { deleteConfigTopLevelKey, loadConfig, mutatePersistedConfig } from "../config";
24
24
  import type { OcxClientIntegrationsConfig, OcxConfig } from "../types";
25
25
  import { runStartupReadinessSync, type ReadinessGate, type SyncOutcomeLike } from "../server/readiness";
26
26
 
@@ -114,7 +114,7 @@ export function setIntegrationEnabled(
114
114
  }
115
115
  // Drop the key entirely once nothing is left in it, so enabling twice does
116
116
  // not leave `"clientIntegrations": {}` behind in the user's file.
117
- if (Object.keys(integrations).length === 0) delete config.clientIntegrations;
117
+ if (Object.keys(integrations).length === 0) deleteConfigTopLevelKey(config, "clientIntegrations");
118
118
  else config.clientIntegrations = integrations;
119
119
  return { changed: true, value: enabled };
120
120
  });
@@ -43,6 +43,7 @@ export interface DesktopAppRestartIo {
43
43
  export type DesktopAppRestartReason =
44
44
  | "windows_only"
45
45
  | "package_discovery_failed"
46
+ | "process_probe_failed"
46
47
  | "no_targets"
47
48
  | "self_ancestry"
48
49
  | "targets_survived";
@@ -119,7 +120,7 @@ interface DesktopProcess {
119
120
  function listPackageProcesses(
120
121
  exec: NonNullable<DesktopAppRestartIo["execFile"]>,
121
122
  installLocation: string,
122
- ): DesktopProcess[] {
123
+ ): DesktopProcess[] | null {
123
124
  const literal = installLocation.replace(/'/g, "''");
124
125
  const script = [
125
126
  "$ErrorActionPreference='SilentlyContinue'",
@@ -136,7 +137,10 @@ function listPackageProcesses(
136
137
  " }",
137
138
  " }",
138
139
  " }",
139
- ].join(" ");
140
+ // Statements must be newline-separated. Joining with a space concatenates
141
+ // `$ErrorActionPreference='SilentlyContinue' $root = '...'` into one malformed statement,
142
+ // which PowerShell rejects — so the probe threw and every caller read "not running" (#2557).
143
+ ].join("\n");
140
144
  let stdout: string;
141
145
  try {
142
146
  stdout = exec(resolveTrustedWindowsPowerShellExe(), ["-NoProfile", "-NonInteractive", "-Command", script], {
@@ -144,7 +148,10 @@ function listPackageProcesses(
144
148
  windowsHide: true,
145
149
  });
146
150
  } catch {
147
- return [];
151
+ // A probe that could not run is NOT proof the app is absent. Returning [] here made a
152
+ // failed enumeration indistinguishable from "no targets", so the CLI reported the app as
153
+ // not running and skipped a restart the user had explicitly asked for.
154
+ return null;
148
155
  }
149
156
  const processes: DesktopProcess[] = [];
150
157
  for (const line of stdout.split(/\r?\n/)) {
@@ -170,8 +177,11 @@ function stillSameProcess(
170
177
  installLocation: string,
171
178
  target: DesktopProcess,
172
179
  ): boolean {
173
- const current = listPackageProcesses(exec, installLocation)
174
- .find(p => p.pid === target.pid);
180
+ const processes = listPackageProcesses(exec, installLocation);
181
+ // Fail CLOSED on a failed re-probe: this guards a kill, and "we could not look" must not be
182
+ // read as "the pid was recycled and is now someone else's process".
183
+ if (processes === null) return false;
184
+ const current = processes.find(p => p.pid === target.pid);
175
185
  return current !== undefined && current.createdAt === target.createdAt;
176
186
  }
177
187
 
@@ -266,6 +276,9 @@ export function restartCodexDesktopApp(io: DesktopAppRestartIo = {}): DesktopApp
266
276
  if (!pkg) return skipped("package_discovery_failed");
267
277
 
268
278
  const processes = listPackageProcesses(exec, pkg.installLocation);
279
+ // A probe that could not run is not evidence of absence. Reporting it as `no_targets` told
280
+ // the user the app was not running and silently skipped the restart they asked for (#2557).
281
+ if (processes === null) return skipped("process_probe_failed");
269
282
  const roots = rootProcesses(processes);
270
283
  if (roots.length === 0) return skipped("no_targets");
271
284
 
@@ -11,7 +11,12 @@ import { atomicWriteFile } from "../config";
11
11
  import type { CodexWriteLockResult } from "./codex-write-lock";
12
12
  import { inspectCodexCoordinatorPath } from "./coordinator-doctor";
13
13
  import { JOURNAL_PATH } from "./journal";
14
+ import { updateIntegrationRecord } from "./integration-record";
14
15
  import { CODEX_CONFIG_PATH, CODEX_PROFILE_PATH } from "./paths";
16
+ import type {
17
+ CodexArtifactId,
18
+ CodexProvenanceEntry,
19
+ } from "./convergence-types";
15
20
  import {
16
21
  codexWriteCoordination,
17
22
  type CodexWriteCandidate,
@@ -41,6 +46,7 @@ export const DEFAULT_INJECT_LOCK_TIMEOUT_MS = 5_000;
41
46
  */
42
47
  export type CodexWriteCoordinationEligibility =
43
48
  | { kind: "coordinated" }
49
+ | { kind: "adopt" }
44
50
  | { kind: "legacy-uncoordinated"; reason: string }
45
51
  | { kind: "refused"; reason: string };
46
52
 
@@ -97,6 +103,7 @@ export function codexWriteCoordinationEligibility(deps: {
97
103
 
98
104
  const residue = deps.residue();
99
105
  if (residue.kind === "clean") return { kind: "coordinated" };
106
+ if (residue.kind === "residue" && !coordinatorIsStableZeroByte) return { kind: "adopt" };
100
107
  /*
101
108
  * Everything else keeps the path it has always had.
102
109
  *
@@ -176,6 +183,82 @@ export function captureCodexPreImages(): CodexPreImages {
176
183
  };
177
184
  }
178
185
 
186
+ /**
187
+ * How many transactions of evidence the ledger keeps.
188
+ *
189
+ * Each transaction appends three entries, and a `present` baseline embeds the artifact's exact
190
+ * bytes as base64 — a 25 KB `config.toml` is ~34 KB per entry, so roughly 100 KB per transaction.
191
+ * A machine that syncs on every start would grow this file without limit, and it is re-read and
192
+ * re-serialized on every append, so the cost is quadratic rather than merely large.
193
+ *
194
+ * A ledger is evidence, not an archive. The most recent transactions are the ones anyone
195
+ * diagnoses against, so the window keeps those and drops the oldest.
196
+ */
197
+ export const CODEX_PROVENANCE_MAX_TRANSACTIONS = 16;
198
+
199
+ function provenanceBaseline(bytes: string | null): CodexProvenanceEntry["baseline"] {
200
+ if (bytes === null) return { kind: "absent" };
201
+ return {
202
+ kind: "present",
203
+ sha256: createHash("sha256").update(bytes).digest("hex"),
204
+ bytesBase64: Buffer.from(bytes).toString("base64"),
205
+ };
206
+ }
207
+
208
+ function provenancePostImage(path: string): string | null {
209
+ try {
210
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
211
+ } catch {
212
+ return null;
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Keep the newest `CODEX_PROVENANCE_MAX_TRANSACTIONS` transactions, whole.
218
+ *
219
+ * Trimming by ENTRY count would cut a transaction in half and leave evidence that says a
220
+ * transaction touched two artifacts when it touched three — worse than dropping it outright,
221
+ * because a partial record still reads as complete. Order is preserved; only whole leading
222
+ * transactions are removed.
223
+ */
224
+ export function boundProvenanceEntries(
225
+ entries: readonly CodexProvenanceEntry[],
226
+ maxTransactions = CODEX_PROVENANCE_MAX_TRANSACTIONS,
227
+ ): readonly CodexProvenanceEntry[] {
228
+ const order: string[] = [];
229
+ for (const entry of entries) if (!order.includes(entry.txId)) order.push(entry.txId);
230
+ if (order.length <= maxTransactions) return entries;
231
+ const keep = new Set(order.slice(order.length - maxTransactions));
232
+ return entries.filter(entry => keep.has(entry.txId));
233
+ }
234
+
235
+ /** Append evidence for an already-committed native transaction, best-effort. */
236
+ export function recordCodexNativeTransactionProvenance(
237
+ preImages: CodexPreImages,
238
+ txId: string,
239
+ ) {
240
+ const at = new Date().toISOString();
241
+ const surfaces: readonly [CodexArtifactId, string, string | null][] = [
242
+ [{ kind: "config" }, CODEX_CONFIG_PATH, preImages.config],
243
+ [{ kind: "generated-profile" }, CODEX_PROFILE_PATH, preImages.profile],
244
+ [{ kind: "injection-journal" }, JOURNAL_PATH, preImages.journal],
245
+ ];
246
+ const entries: readonly CodexProvenanceEntry[] = surfaces.map(([artifact, path, baseline]) => ({
247
+ artifact,
248
+ baseline: provenanceBaseline(baseline),
249
+ postImage: provenancePostImage(path),
250
+ txId,
251
+ at,
252
+ }));
253
+ return updateIntegrationRecord(record => ({
254
+ ...record,
255
+ provenance: {
256
+ ...record.provenance,
257
+ entries: boundProvenanceEntries([...(record.provenance?.entries ?? []), ...entries]),
258
+ },
259
+ }));
260
+ }
261
+
179
262
  /**
180
263
  * Put back exactly what was there, and report honestly when that fails.
181
264
  *