@yansigit/opencodex 2.36.1-dev.20260829.47 → 2.36.1-dev.20260829.50

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 (112) hide show
  1. package/gui/dist/assets/{ApiKeys-DAuYQsGJ.js → ApiKeys-BLPOvK4z.js} +1 -1
  2. package/gui/dist/assets/{Claude-bN_80RT8.js → Claude-BbHv3EU7.js} +1 -1
  3. package/gui/dist/assets/{CodexSet-D3dWX_ay.js → CodexSet-hgu5hES-.js} +1 -1
  4. package/gui/dist/assets/{FileIntegrationPage-BGyzkPs2.js → FileIntegrationPage-RCmDvvsx.js} +1 -1
  5. package/gui/dist/assets/{Grok-CuaKd1TT.js → Grok-DH-ggRqA.js} +1 -1
  6. package/gui/dist/assets/{Integrations-C7NGQoxR.js → Integrations-D6m5E2-T.js} +2 -2
  7. package/gui/dist/assets/{IntegrationsOverview-DjVorYO5.js → IntegrationsOverview-C1rbnXV7.js} +1 -1
  8. package/gui/dist/assets/{Logs-B21a-9xe.js → Logs-Bqb0vYRk.js} +1 -1
  9. package/gui/dist/assets/{Models-CN7Q3kuK.js → Models-gMnzHf0c.js} +1 -1
  10. package/gui/dist/assets/{NumberStepper-BWg90tEs.js → NumberStepper-awaoZNlj.js} +1 -1
  11. package/gui/dist/assets/{Providers-CP_i-7LS.js → Providers-Bo6wuQMa.js} +1 -1
  12. package/gui/dist/assets/{RestoreDialog-3M0NqCiD.js → RestoreDialog-DX7SGWIk.js} +1 -1
  13. package/gui/dist/assets/{Startup-BVPFoP2s.js → Startup-B1htiLU9.js} +1 -1
  14. package/gui/dist/assets/{Storage-BdsG9ypz.js → Storage-B3MfosmS.js} +1 -1
  15. package/gui/dist/assets/{Subagents-CguR_bGQ.js → Subagents-L6Q2V9PZ.js} +1 -1
  16. package/gui/dist/assets/{Usage-C0kKi9_H.js → Usage-xbZnkDoj.js} +1 -1
  17. package/gui/dist/assets/{codex-stale-banner-D4gvSWnO.js → codex-stale-banner-W4FhikN7.js} +1 -1
  18. package/gui/dist/assets/{data-surface-BtO3lwam.js → data-surface-B8PX8rjm.js} +1 -1
  19. package/gui/dist/assets/{data-surface-BCycbObp.js → data-surface-BbBMp0Wj.js} +1 -1
  20. package/gui/dist/assets/{index-CU1jE0st.js → index-JEC_6CUW.js} +3 -3
  21. package/gui/dist/assets/{model-display-D2elXkuE.js → model-display-BvLWy9D5.js} +1 -1
  22. package/gui/dist/assets/{provider-payload-DfkXWE7v.js → provider-payload-BRfSsonS.js} +1 -1
  23. package/gui/dist/assets/{section-tabs-BP7gEPK6.js → section-tabs-B8sLMiz6.js} +1 -1
  24. package/gui/dist/assets/shared-DI18-uF7.js +69 -0
  25. package/gui/dist/index.html +2 -2
  26. package/package.json +3 -1
  27. package/src/adapters/base.ts +26 -0
  28. package/src/adapters/cursor/catalog.ts +541 -0
  29. package/src/adapters/cursor/cursor-errors.ts +15 -0
  30. package/src/adapters/cursor/discovery.ts +34 -41
  31. package/src/adapters/cursor/envelope-echo.ts +128 -0
  32. package/src/adapters/cursor/request-builder.ts +19 -12
  33. package/src/adapters/cursor/tool-definitions.ts +2 -1
  34. package/src/adapters/cursor/tool-result-normalize.ts +23 -31
  35. package/src/adapters/cursor.ts +21 -2
  36. package/src/adapters/exec-tool-result-normalize.ts +99 -0
  37. package/src/adapters/google-antigravity-replay.ts +71 -2
  38. package/src/adapters/google.ts +19 -4
  39. package/src/adapters/kiro-constants.ts +12 -0
  40. package/src/adapters/kiro.ts +128 -11
  41. package/src/adapters/openai-chat.ts +16 -2
  42. package/src/adapters/openai-responses.ts +15 -2
  43. package/src/adapters/tool-catalog-nudge.ts +2 -1
  44. package/src/adapters/xai-web-search.ts +10 -14
  45. package/src/claude/outbound.ts +14 -3
  46. package/src/cli/access.ts +46 -3
  47. package/src/cli/account-api.ts +93 -16
  48. package/src/cli/account-extended.ts +262 -36
  49. package/src/cli/account-main.ts +12 -12
  50. package/src/cli/account.ts +40 -10
  51. package/src/cli/agent.ts +8 -1
  52. package/src/cli/capabilities-command.ts +94 -0
  53. package/src/cli/capabilities.ts +535 -0
  54. package/src/cli/claude-desktop.ts +31 -11
  55. package/src/cli/config-command.ts +7 -3
  56. package/src/cli/dispatch.ts +195 -27
  57. package/src/cli/doctor.ts +100 -1
  58. package/src/cli/help.ts +11 -2
  59. package/src/cli/index.ts +19 -3
  60. package/src/cli/init.ts +2 -2
  61. package/src/cli/inspect.ts +230 -0
  62. package/src/cli/observe.ts +11 -3
  63. package/src/cli/provider.ts +58 -74
  64. package/src/cli/registry.ts +34 -2
  65. package/src/cli/runtime-api.ts +51 -7
  66. package/src/cli/status.ts +16 -0
  67. package/src/cli/storage.ts +234 -0
  68. package/src/cli/system-command.ts +16 -0
  69. package/src/cli/usage-report.ts +52 -2
  70. package/src/cli/version-skew.ts +46 -0
  71. package/src/codex/account-label.ts +21 -0
  72. package/src/codex/catalog/provider-fetch.ts +4 -0
  73. package/src/codex/convergence.ts +110 -15
  74. package/src/codex/transition-state.ts +12 -3
  75. package/src/compatibility/openai-responses.ts +9 -1
  76. package/src/config.ts +209 -11
  77. package/src/generated/compatibility-version.json +115 -75
  78. package/src/integrations/ownership-policy.ts +24 -5
  79. package/src/integrations/ownership.ts +36 -2
  80. package/src/integrations/state.ts +40 -7
  81. package/src/integrations/writer.ts +21 -3
  82. package/src/lib/admin-secrets.ts +24 -0
  83. package/src/lib/errors.ts +25 -1
  84. package/src/lib/service-secrets.ts +15 -0
  85. package/src/oauth/index.ts +143 -82
  86. package/src/oauth/login-cli.ts +53 -11
  87. package/src/oauth/store.ts +16 -6
  88. package/src/providers/alibaba-region-startup.ts +31 -11
  89. package/src/providers/api-keys.ts +58 -34
  90. package/src/providers/key-failover.ts +59 -46
  91. package/src/providers/label.ts +34 -1
  92. package/src/providers/model-rename-startup.ts +25 -7
  93. package/src/providers/openai-tier-startup.ts +38 -14
  94. package/src/responses/turn-termination.ts +107 -0
  95. package/src/server/index.ts +10 -0
  96. package/src/server/management/context.ts +10 -3
  97. package/src/server/management/logs-usage-routes.ts +24 -43
  98. package/src/server/management/model-routes.ts +284 -165
  99. package/src/server/management/oauth-account-routes.ts +93 -15
  100. package/src/server/management/provider-routes.ts +200 -87
  101. package/src/server/management/route-registry.ts +317 -0
  102. package/src/server/proxy-liveness.ts +27 -4
  103. package/src/server/request-log.ts +37 -17
  104. package/src/server/responses/core.ts +80 -4
  105. package/src/server/responses/policy-fallback.ts +1 -1
  106. package/src/service.ts +34 -0
  107. package/src/storage/policy-input.ts +166 -0
  108. package/src/storage/policy-job.ts +14 -4
  109. package/src/storage/policy.ts +115 -221
  110. package/src/usage/log.ts +32 -6
  111. package/src/usage/summary.ts +22 -34
  112. package/gui/dist/assets/shared-BdYGV-yJ.js +0 -69
@@ -9,7 +9,7 @@ const FIXTURE_ID = "openai-codex-forward-gpt56-sol-v1";
9
9
  export const OPENAI_CODEX_FORWARD_GPT56_SOL_MANIFEST = defineCompatibilityManifest({
10
10
  schemaVersion: 1,
11
11
  id: "openai.codex-forward.gpt-5-6-sol.responses",
12
- version: "1.2.0",
12
+ version: "1.3.0",
13
13
  subject: {
14
14
  providerId: "openai",
15
15
  baseUrl: "https://chatgpt.com/backend-api/codex",
@@ -109,5 +109,13 @@ export const OPENAI_CODEX_FORWARD_GPT56_SOL_MANIFEST = defineCompatibilityManife
109
109
  limitation: "The field is removed without inventing a replacement prompt_cache_options value.",
110
110
  evidence: [{ kind: "fixture", id: FIXTURE_ID, assertionIds: ["prompt-cache-retention-removed"] }],
111
111
  },
112
+ {
113
+ id: "prompt-cache-options",
114
+ feature: "request.prompt_cache_options",
115
+ disposition: "unsupported",
116
+ summary: "The ChatGPT Codex forward route does not receive public prompt cache options.",
117
+ limitation: "The field is removed only for the canonical forward destination; public and custom Responses providers keep it.",
118
+ evidence: [{ kind: "fixture", id: FIXTURE_ID, assertionIds: ["prompt-cache-options-removed"] }],
119
+ },
112
120
  ],
113
121
  } as const);
package/src/config.ts CHANGED
@@ -96,7 +96,6 @@ import {
96
96
  isValidCost4Rate,
97
97
  refreshPreservedProviderOwner,
98
98
  refreshUserCostOverlays,
99
- withPreservedDiskOnlyProviders,
100
99
  } from "./usage/user-cost-overlays";
101
100
  import { MAX_COST4_RATE } from "./usage/expected-prices";
102
101
  import {
@@ -106,6 +105,8 @@ import {
106
105
  } from "./lib/app-owned-memory";
107
106
  import { isHostedToolUnsupportedForModel } from "./responses/hosted-tool-policy";
108
107
  import {
108
+ AtomicWriteResidualTempError,
109
+ AtomicWriteSecretResidualError,
109
110
  atomicWriteFile,
110
111
  isMissingPathError,
111
112
  nextAtomicTempSequence,
@@ -2732,23 +2733,34 @@ export const withExpectedConfigGenerationSync: WithExpectedConfigGenerationSync
2732
2733
  * cost-overlay registry from the persisted config so runtime estimates follow
2733
2734
  * every save path.
2734
2735
  */
2735
- function persistConfigUnlocked(config: OcxConfig): boolean {
2736
+ type PersistConfigAuthority = "ordinary" | "mutation" | "replacement";
2737
+
2738
+ function persistConfigUnlocked(config: OcxConfig, authority: PersistConfigAuthority = "ordinary"): boolean {
2736
2739
  const configPath = getConfigPath();
2737
2740
  // Check the resolved file target before reading it: a symlink can point from an
2738
2741
  // isolated test home into the protected real home, where another write guard
2739
2742
  // must not mask this refusal based on the target's current contents.
2740
2743
  assertNotRealHomeUnderTest(dirname(resolveWriteTarget(configPath)));
2741
2744
  const raw = readRawConfigJson();
2742
- if (raw && configNeedsProviderRepair(raw)) {
2745
+ if (authority !== "replacement" && raw && configNeedsProviderRepair(raw)) {
2743
2746
  throw new Error("refusing to overwrite a config repaired with defaults; fix the persisted config first");
2744
2747
  }
2745
- // External editors can add provider rows the live config deliberately does
2746
- // not route with yet; merge them at the serialization boundary so an
2747
- // unrelated in-process save cannot erase the provider or its overlay.
2748
- // Provider preservation reads symbol-keyed live-owner state, which structuredClone
2749
- // intentionally drops. Resolve that ownership before projecting JSON provenance.
2748
+ const snapshot = readConfigFileSnapshot();
2749
+ if (authority !== "replacement" && snapshot.diagnostics.source === "fallback") {
2750
+ throw new Error("refusing to overwrite an invalid persisted config; fix the persisted config first");
2751
+ }
2752
+ // Automatic whole-config writes own non-provider settings only. A valid disk
2753
+ // registry is authoritative; explicit locked mutations pass replacement
2754
+ // authority for intentional provider/default changes.
2755
+ const base = snapshot.diagnostics.source === "file" && authority === "ordinary"
2756
+ ? {
2757
+ ...config,
2758
+ providers: snapshot.diagnostics.config.providers,
2759
+ defaultProvider: snapshot.diagnostics.config.defaultProvider,
2760
+ }
2761
+ : config;
2750
2762
  const provenanceProjection = projectConfigRebaseProvenance(config);
2751
- const persisted = withPreservedDiskOnlyProviders(config);
2763
+ const persisted = base;
2752
2764
  if (provenanceProjection.configRebaseProvenance === undefined) delete persisted.configRebaseProvenance;
2753
2765
  else persisted.configRebaseProvenance = provenanceProjection.configRebaseProvenance;
2754
2766
  const bytes = JSON.stringify(persisted, null, 2) + "\n";
@@ -2790,6 +2802,183 @@ export function saveConfig(config: OcxConfig): void {
2790
2802
  });
2791
2803
  }
2792
2804
 
2805
+ /** Replace a validated config under the shared lock for confirmed import/init flows. */
2806
+ export function replacePersistedConfig(config: OcxConfig): void {
2807
+ assertNotRealHomeUnderTest(getConfigDir());
2808
+ withConfigMutationLockSync(() => {
2809
+ const projected = projectCustomModelCatalogMigration(
2810
+ readRawConfigJson(),
2811
+ projectConfigRebaseProvenance(config),
2812
+ );
2813
+ if (persistConfigUnlocked(projected, "replacement")) bumpGenerationForCooperatingConfigWrite();
2814
+ adoptCustomModelCatalogMigration(config, projected);
2815
+ if (projected.configRebaseProvenance === undefined) delete config.configRebaseProvenance;
2816
+ else config.configRebaseProvenance = structuredClone(projected.configRebaseProvenance);
2817
+ clearPendingConfigTopLevelDeletions(config);
2818
+ });
2819
+ }
2820
+
2821
+ export type PersistedConfigInitializationOutcome = "created" | "exists" | "invalid";
2822
+
2823
+ export class PersistedConfigInitializationCleanupError extends Error {
2824
+ constructor(options?: ErrorOptions) {
2825
+ super("Initial config publication cleanup failed after rollback", options);
2826
+ this.name = "PersistedConfigInitializationCleanupError";
2827
+ }
2828
+ }
2829
+
2830
+ export class PersistedConfigInitializationRollbackError extends Error {
2831
+ constructor(options?: ErrorOptions) {
2832
+ super("Initial config publication rollback failed", options);
2833
+ this.name = "PersistedConfigInitializationRollbackError";
2834
+ }
2835
+ }
2836
+
2837
+ export interface PersistedConfigInitializationIO {
2838
+ createExclusive(path: string): void;
2839
+ write(path: string, bytes: string): void;
2840
+ harden(path: string): void;
2841
+ publishNoReplace(temp: string, target: string): void;
2842
+ truncate(path: string): void;
2843
+ unlink(path: string): void;
2844
+ }
2845
+
2846
+ let persistedConfigInitializationBeforePublishForTests: (() => void) | null = null;
2847
+
2848
+ /** Test-only one-shot seam: create a competing config after staging, before no-replace publication. */
2849
+ export function setPersistedConfigInitializationBeforePublishForTests(hook: (() => void) | null): void {
2850
+ persistedConfigInitializationBeforePublishForTests = hook;
2851
+ }
2852
+
2853
+ function publishInitialConfigNoReplace(
2854
+ config: OcxConfig,
2855
+ io: PersistedConfigInitializationIO,
2856
+ ): boolean {
2857
+ const configPath = getConfigPath();
2858
+ const target = resolveWriteTarget(configPath);
2859
+ assertNotRealHomeUnderTest(dirname(target));
2860
+ recordOwnedConfigPath(getConfigDir(), configPath);
2861
+ const persisted = projectConfigRebaseProvenance(config);
2862
+ const bytes = JSON.stringify(persisted, null, 2) + "\n";
2863
+ const temp = `${target}.ocx.${process.pid}.${nextAtomicTempSequence()}.tmp`;
2864
+ let staged = false;
2865
+ let hardened = false;
2866
+ let published = false;
2867
+ let cleanupAttempted = false;
2868
+
2869
+ const scrubUnpublishedTemp = (cause?: unknown): void => {
2870
+ cleanupAttempted = true;
2871
+ let scrubbed = false;
2872
+ try {
2873
+ io.truncate(temp);
2874
+ scrubbed = true;
2875
+ } catch (error) {
2876
+ if (isMissingPathError(error)) scrubbed = true;
2877
+ else {
2878
+ try { io.write(temp, ""); scrubbed = true; } catch { /* removal may still succeed */ }
2879
+ }
2880
+ }
2881
+ let removed = false;
2882
+ try {
2883
+ io.unlink(temp);
2884
+ removed = true;
2885
+ } catch (error) {
2886
+ if (isMissingPathError(error)) removed = true;
2887
+ else {
2888
+ try { io.unlink(temp); removed = true; }
2889
+ catch (retryError) { if (isMissingPathError(retryError)) removed = true; }
2890
+ }
2891
+ }
2892
+ if (removed) forgetEphemeralSecretPath(temp);
2893
+ if (!removed && !scrubbed) throw new AtomicWriteSecretResidualError(temp, { cause });
2894
+ if (!removed) throw new AtomicWriteResidualTempError(temp, hardened, { cause });
2895
+ };
2896
+
2897
+ try {
2898
+ io.createExclusive(temp);
2899
+ staged = true;
2900
+ io.write(temp, bytes);
2901
+ io.harden(temp);
2902
+ hardened = true;
2903
+ const hook = persistedConfigInitializationBeforePublishForTests;
2904
+ persistedConfigInitializationBeforePublishForTests = null;
2905
+ hook?.();
2906
+ try {
2907
+ io.publishNoReplace(temp, target);
2908
+ } catch (cause) {
2909
+ if (!isAlreadyExistsError(cause)) throw cause;
2910
+ scrubUnpublishedTemp(cause);
2911
+ return false;
2912
+ }
2913
+ published = true;
2914
+ try {
2915
+ io.unlink(temp);
2916
+ forgetEphemeralSecretPath(temp);
2917
+ } catch (firstError) {
2918
+ if (isMissingPathError(firstError)) {
2919
+ forgetEphemeralSecretPath(temp);
2920
+ } else try {
2921
+ io.unlink(temp);
2922
+ forgetEphemeralSecretPath(temp);
2923
+ } catch (secondError) {
2924
+ if (isMissingPathError(secondError)) {
2925
+ forgetEphemeralSecretPath(temp);
2926
+ } else {
2927
+ // Both names point to one inode. Remove the published name before scrubbing.
2928
+ try { io.unlink(target); }
2929
+ catch (cause) { throw new PersistedConfigInitializationRollbackError({ cause }); }
2930
+ published = false;
2931
+ scrubUnpublishedTemp(secondError);
2932
+ throw new PersistedConfigInitializationCleanupError({ cause: secondError });
2933
+ }
2934
+ }
2935
+ }
2936
+ refreshUserCostOverlays(persisted);
2937
+ return true;
2938
+ } catch (cause) {
2939
+ if (staged && !published && !cleanupAttempted) scrubUnpublishedTemp(cause);
2940
+ throw cause;
2941
+ }
2942
+ }
2943
+
2944
+ function defaultPersistedConfigInitializationIO(configPath: string): PersistedConfigInitializationIO {
2945
+ return {
2946
+ createExclusive: target => { writeFileSync(target, "", { flag: "wx", mode: 0o600 }); },
2947
+ write: (target, bytes) => writeFileSync(target, bytes),
2948
+ harden: target => {
2949
+ try { chmodSync(target, 0o600); } catch { /* platform may ignore chmod */ }
2950
+ if (process.platform === "win32") hardenSecretPath(target, { required: true, timeoutMemoKey: configPath });
2951
+ },
2952
+ publishNoReplace: (temp, target) => linkSync(temp, target),
2953
+ truncate: target => truncateSync(target, 0),
2954
+ unlink: unlinkSync,
2955
+ };
2956
+ }
2957
+
2958
+ /** Create the initial config under the shared lock, but never replace existing bytes. */
2959
+ export function initializePersistedConfigIfMissing(
2960
+ config: OcxConfig,
2961
+ io = defaultPersistedConfigInitializationIO(getConfigPath()),
2962
+ ): PersistedConfigInitializationOutcome {
2963
+ assertNotRealHomeUnderTest(getConfigDir());
2964
+ return withConfigMutationLockSync(() => {
2965
+ const snapshot = readConfigFileSnapshot();
2966
+ if (snapshot.diagnostics.source === "file") return "exists";
2967
+ if (snapshot.diagnostics.source !== "default") return "invalid";
2968
+ const projected = projectCustomModelCatalogMigration(
2969
+ readRawConfigJson(),
2970
+ projectConfigRebaseProvenance(config),
2971
+ );
2972
+ if (!publishInitialConfigNoReplace(projected, io)) {
2973
+ const winner = readConfigFileSnapshot();
2974
+ return winner.diagnostics.source === "file" ? "exists" : "invalid";
2975
+ }
2976
+ bumpGenerationForCooperatingConfigWrite();
2977
+ adoptCustomModelCatalogMigration(config, projected);
2978
+ return "created";
2979
+ });
2980
+ }
2981
+
2793
2982
  export type PersistedConfigMutation<T> = {
2794
2983
  changed: boolean;
2795
2984
  value: T;
@@ -2799,6 +2988,13 @@ export type PersistedConfigMutationOutcome<T> =
2799
2988
  | { status: "committed" | "unchanged"; value: T }
2800
2989
  | { status: "unavailable"; reason: "missing" | "invalid" | "conflict" };
2801
2990
 
2991
+ export class ConfigMutationValidationError extends Error {
2992
+ constructor(readonly validationError: string) {
2993
+ super(`Config mutation rejected: ${validationError}`);
2994
+ this.name = "ConfigMutationValidationError";
2995
+ }
2996
+ }
2997
+
2802
2998
  const CONFIG_MUTATION_MAX_REBASE_ATTEMPTS = 3;
2803
2999
  let persistedConfigMutationBeforeCommitForTests: (() => void) | null = null;
2804
3000
 
@@ -2871,7 +3067,9 @@ export function mutatePersistedConfig<T>(
2871
3067
  commitBase.diagnostics.config,
2872
3068
  confirmedConfig,
2873
3069
  );
2874
- if (persistConfigUnlocked(projected)) bumpGenerationForCooperatingConfigWrite();
3070
+ const validation = validateConfigCandidate(projected);
3071
+ if (!validation.ok) throw new ConfigMutationValidationError(validation.error);
3072
+ if (persistConfigUnlocked(projected, "mutation")) bumpGenerationForCooperatingConfigWrite();
2875
3073
  return { status: "committed", value: confirmed.value };
2876
3074
  }
2877
3075
  return { status: "unavailable", reason: "conflict" };
@@ -3193,7 +3391,7 @@ function readPersistedServerBinding(
3193
3391
  * conflict keeps the live value;
3194
3392
  * - a provider or custom-model row deleted on disk stays deleted even if stale
3195
3393
  * live state edited that same row;
3196
- * - file missing/unreadable → save what we have, no throw.
3394
+ * - missing file → save what we have; invalid existing file → fail closed.
3197
3395
  *
3198
3396
  * Custom-model rows are merged by their stable `id`, preserving independent
3199
3397
  * edits and deletions across stale whole-config saves.