@bitkyc08/opencodex 2.68.0 → 2.69.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 (175) hide show
  1. package/bin/ocx.mjs +25 -17
  2. package/gui/dist/assets/App-DDbgOTh6.js +51 -0
  3. package/gui/dist/assets/{Tray-Bn8WhKBH.js → Tray-uqKRqfvw.js} +1 -1
  4. package/gui/dist/assets/index-BF9QnKO_.css +1 -0
  5. package/gui/dist/assets/index-CuaBJkG4.js +86 -0
  6. package/gui/dist/assets/{quota-summary-DL-bNuNL.js → quota-summary-COppQSVq.js} +1 -1
  7. package/gui/dist/index.html +2 -2
  8. package/gui/dist/provider-icons/factory-droid.svg +8 -0
  9. package/package.json +1 -1
  10. package/src/adapters/anthropic-output-schema.ts +109 -11
  11. package/src/adapters/coding-agent/protocol.ts +106 -11
  12. package/src/adapters/coding-agent/turn.ts +23 -18
  13. package/src/adapters/google-errors.ts +16 -1
  14. package/src/adapters/google-http.ts +14 -0
  15. package/src/adapters/openai-chat/passthrough.ts +2 -0
  16. package/src/adapters/openai-chat/summary-budget.ts +72 -0
  17. package/src/adapters/openai-chat/tool-call-id-remint.ts +11 -2
  18. package/src/adapters/openai-chat.ts +6 -9
  19. package/src/bridge/response-json.ts +3 -1
  20. package/src/bridge/sse.ts +3 -1
  21. package/src/claude/desktop-picker.ts +6 -2
  22. package/src/claude/desktop-system-proxy.ts +155 -0
  23. package/src/claude/inbound-model-options.ts +10 -2
  24. package/src/claude/inbound.ts +1 -1
  25. package/src/claude/intercept/picker-ca-cleanup.ts +31 -0
  26. package/src/claude/intercept/picker-ca.ts +283 -83
  27. package/src/claude/intercept/picker-models.ts +4 -1
  28. package/src/claude/intercept/picker-trust.ts +15 -2
  29. package/src/claude/intercept/runtime.ts +99 -32
  30. package/src/cli/account-api.ts +5 -2
  31. package/src/cli/account-extended.ts +47 -11
  32. package/src/cli/account-target.ts +12 -7
  33. package/src/cli/account.ts +47 -3
  34. package/src/cli/capabilities.ts +33 -7
  35. package/src/cli/claude-agent-startup-sync.ts +2 -0
  36. package/src/cli/claude-desktop.ts +37 -13
  37. package/src/cli/cross-home-owner.ts +138 -0
  38. package/src/cli/dispatch.ts +1 -1
  39. package/src/cli/doctor.ts +18 -8
  40. package/src/cli/ensure-desired-integrations.ts +5 -0
  41. package/src/cli/export-command.ts +6 -6
  42. package/src/cli/help.ts +1 -1
  43. package/src/cli/index.ts +15 -18
  44. package/src/cli/integrations.ts +8 -1
  45. package/src/cli/models-runtime-subcommands.ts +1 -0
  46. package/src/cli/models-runtime.ts +106 -4
  47. package/src/cli/models.ts +6 -1
  48. package/src/cli/opencode.ts +2 -83
  49. package/src/cli/registry.ts +3 -2
  50. package/src/cli/status.ts +86 -7
  51. package/src/client/connect.ts +24 -10
  52. package/src/client/link-ingress.ts +3 -1
  53. package/src/client/link-join.ts +76 -9
  54. package/src/client/link-relay-transport.ts +184 -0
  55. package/src/client/link-relay.ts +16 -4
  56. package/src/client/machine-listener.ts +2 -0
  57. package/src/clients/config-export/constants.ts +8 -0
  58. package/src/clients/config-export/contracts.ts +13 -1
  59. package/src/clients/config-export/droid.ts +71 -0
  60. package/src/clients/config-export/kilo.ts +116 -0
  61. package/src/clients/config-export.ts +33 -2
  62. package/src/codex/auth-api/routes.ts +1 -1
  63. package/src/codex/catalog/access-programs.ts +14 -2
  64. package/src/codex/catalog/provider-models.ts +1 -1
  65. package/src/codex/catalog-auto-refresh.ts +122 -0
  66. package/src/codex/config-drift-heal.ts +80 -0
  67. package/src/codex/desktop-switches.ts +7 -5
  68. package/src/codex/inject/bounded-config-reader.ts +69 -0
  69. package/src/codex/inject/config-toml.ts +31 -3
  70. package/src/codex/inject/routing-target.ts +6 -1
  71. package/src/codex/inject.ts +4 -0
  72. package/src/codex/model-entitlements.ts +32 -5
  73. package/src/codex/native-profile-startup.ts +17 -5
  74. package/src/codex/project-config-warnings.ts +72 -21
  75. package/src/codex/quota-auto-refresh-state.ts +8 -2
  76. package/src/codex/quota-auto-refresh.ts +80 -9
  77. package/src/codex/sync.ts +10 -0
  78. package/src/combos/failover.ts +4 -1
  79. package/src/combos/jev.ts +15 -4
  80. package/src/combos/types.ts +16 -0
  81. package/src/config/diagnostics.ts +19 -1
  82. package/src/config/load-degrade.ts +29 -0
  83. package/src/config/macos-system-proxy.ts +114 -0
  84. package/src/config/proxy-env.ts +67 -20
  85. package/src/config/schema/config-schema.ts +15 -0
  86. package/src/config/schema/leaf-validators.ts +23 -0
  87. package/src/config.ts +2 -0
  88. package/src/generated/compatibility-version.json +220 -144
  89. package/src/integrations/catalog-refresh.ts +1 -1
  90. package/src/integrations/config-io.ts +12 -3
  91. package/src/integrations/droid-settings.ts +65 -0
  92. package/src/integrations/kilo-candidates.ts +30 -0
  93. package/src/integrations/mutation-plan.ts +62 -7
  94. package/src/integrations/native/ownership-preflight.ts +46 -12
  95. package/src/integrations/registry.ts +136 -7
  96. package/src/integrations/state.ts +52 -4
  97. package/src/integrations/target.ts +3 -0
  98. package/src/integrations/writer.ts +55 -14
  99. package/src/lib/account-selection-events.ts +14 -0
  100. package/src/lib/jsonc.ts +110 -0
  101. package/src/lib/standalone.ts +9 -1
  102. package/src/lib/windows-system-proxy.ts +101 -1
  103. package/src/link/relay-auth.ts +29 -0
  104. package/src/link/ssh-argv.ts +6 -3
  105. package/src/link/ssh-runner.ts +5 -5
  106. package/src/oauth/generic-account-failover.ts +52 -8
  107. package/src/oauth/index.ts +37 -14
  108. package/src/oauth/store.ts +96 -11
  109. package/src/oauth/token-guardian.ts +7 -3
  110. package/src/oauth/types.ts +2 -0
  111. package/src/plugins/loader.ts +5 -3
  112. package/src/providers/fastwire.ts +3 -0
  113. package/src/providers/key-failover.ts +13 -5
  114. package/src/providers/kiro-model-catalog.ts +4 -3
  115. package/src/providers/model-rename-fields.ts +1 -0
  116. package/src/providers/openai-tiers-destination.ts +9 -0
  117. package/src/providers/quota/account-cache.ts +17 -3
  118. package/src/providers/quota/vendor-probes-oauth.ts +2 -0
  119. package/src/providers/quota.ts +2 -2
  120. package/src/responses/code-mode-helper-compat.ts +14 -1
  121. package/src/responses/custom-tool-compat.ts +1 -1
  122. package/src/server/auth-cors.ts +1 -0
  123. package/src/server/images.ts +70 -20
  124. package/src/server/index/link-listener.ts +20 -3
  125. package/src/server/index/link-relay-sessions.ts +138 -0
  126. package/src/server/index/optional-listeners.ts +11 -1
  127. package/src/server/index/serve-options.ts +12 -0
  128. package/src/server/inference/client-encoder-delivery.ts +1 -0
  129. package/src/server/local-management-read-client.ts +21 -2
  130. package/src/server/management/agent-settings-routes.ts +19 -5
  131. package/src/server/management/combo-routes.ts +5 -3
  132. package/src/server/management/config-routes.ts +32 -6
  133. package/src/server/management/model-routes.ts +232 -7
  134. package/src/server/management/model-rows.ts +199 -1
  135. package/src/server/management/oauth-account-routes.ts +63 -3
  136. package/src/server/management/route-registry.ts +2 -0
  137. package/src/server/port-reclaim.ts +170 -32
  138. package/src/server/responses/adapter-delivery.ts +6 -4
  139. package/src/server/responses/adapter-dispatch.ts +72 -2
  140. package/src/server/responses/core-combo.ts +6 -1
  141. package/src/server/responses/core-normalize.ts +19 -4
  142. package/src/server/responses/core-options.ts +2 -0
  143. package/src/server/responses/memory-models.ts +169 -0
  144. package/src/server/responses/passthrough-delivery.ts +4 -0
  145. package/src/server/responses/passthrough-dispatch.ts +10 -0
  146. package/src/server/responses/request-prepare.ts +86 -4
  147. package/src/server/responses/request-transport.ts +4 -0
  148. package/src/server/responses/run-turn-execution.ts +5 -2
  149. package/src/server/responses/shadow-target-availability.ts +17 -4
  150. package/src/server/responses/sidecar-execution.ts +18 -2
  151. package/src/server/responses/skills-snapshot.ts +239 -0
  152. package/src/server/responses-undeclared-tool-guard.ts +43 -6
  153. package/src/server/search.ts +1 -1
  154. package/src/service/guards.ts +4 -5
  155. package/src/service/state.ts +39 -2
  156. package/src/service.ts +2 -2
  157. package/src/types/config.ts +46 -4
  158. package/src/types/provider.ts +9 -0
  159. package/src/types/request.ts +6 -0
  160. package/src/types/tools.ts +38 -8
  161. package/src/types.ts +3 -0
  162. package/src/update/async-check.ts +5 -1
  163. package/src/update/index.ts +30 -12
  164. package/src/update/job.ts +3 -1
  165. package/src/update/pnpm-read-policy.d.mts +4 -0
  166. package/src/update/pnpm-read-policy.mjs +44 -0
  167. package/src/update/worker-launch.ts +152 -11
  168. package/src/usage/cost.ts +6 -3
  169. package/src/usage/log.ts +4 -0
  170. package/src/web-search/backends.ts +2 -2
  171. package/src/web-search/devin-executor.ts +1 -1
  172. package/src/web-search/sidecar-providers.ts +5 -5
  173. package/gui/dist/assets/App-mpsibsFj.js +0 -51
  174. package/gui/dist/assets/index-BDUBS8PW.css +0 -1
  175. package/gui/dist/assets/index-kUE8tuAK.js +0 -86
@@ -0,0 +1,44 @@
1
+ import { mkdtempSync, lstatSync, writeFileSync, unlinkSync, rmdirSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ /** Read probes never inherit the caller's project as their working directory. */
7
+ export const PNPM_READ_CWD = dirname(fileURLToPath(import.meta.url));
8
+ const MUTATIONS = new Set(["add", "install", "update", "remove", "uninstall"]);
9
+
10
+ /** Run a synchronous pnpm mutation outside the package, in a private workspace boundary. */
11
+ export function withPnpmCommandCwd(args, run) {
12
+ if (!MUTATIONS.has(args?.[0])) return run(PNPM_READ_CWD);
13
+ const cwd = mkdtempSync(join(tmpdir(), "ocx-pnpm-command-"));
14
+ const created = lstatSync(cwd);
15
+ const files = ["pnpm-workspace.yaml", ".npmrc"];
16
+ try {
17
+ // Stop discovery at our own workspace, rather than inheriting a shared /tmp workspace.
18
+ writeFileSync(join(cwd, files[0]), "packages: []\nignorePnpmfile: true\n", { flag: "wx", mode: 0o600 });
19
+ writeFileSync(join(cwd, files[1]), "ignore-pnpmfile=true\n", { flag: "wx", mode: 0o600 });
20
+ return run(cwd);
21
+ } finally {
22
+ try {
23
+ const now = lstatSync(cwd);
24
+ if (!now.isDirectory() || now.isSymbolicLink() || now.dev !== created.dev || now.ino !== created.ino) {
25
+ throw new Error("temporary directory identity changed");
26
+ }
27
+ // Never recursively remove pnpm-created or replacement contents.
28
+ for (const name of files) {
29
+ try { unlinkSync(join(cwd, name)); }
30
+ catch (error) { if (error?.code !== "ENOENT") throw error; }
31
+ }
32
+ rmdirSync(cwd);
33
+ } catch {
34
+ console.warn("[opencodex] Temporary pnpm workspace cleanup was incomplete; retained for inspection.");
35
+ }
36
+ }
37
+ }
38
+
39
+ /** pnpm 11 uses pnpm_config_ while earlier versions use npm_config_. */
40
+ export function pnpmReadEnvironment(env = process.env) {
41
+ const ignored = new Set(["npm_config_ignore_pnpmfile", "pnpm_config_ignore_pnpmfile"]);
42
+ const isolated = Object.fromEntries(Object.entries(env).filter(([key]) => !ignored.has(key.toLowerCase())));
43
+ return { ...isolated, npm_config_ignore_pnpmfile: "true", pnpm_config_ignore_pnpmfile: "true" };
44
+ }
@@ -1,4 +1,6 @@
1
- import { spawnSync } from "node:child_process";
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { accessSync, constants, realpathSync, statSync } from "node:fs";
3
+ import { dirname, isAbsolute } from "node:path";
2
4
 
3
5
  /**
4
6
  * How to launch the dashboard update worker on POSIX.
@@ -16,19 +18,157 @@ export const SYSTEMD_SCOPE_ARGS = ["--user", "--scope", "--quiet", "--collect",
16
18
  export interface WorkerLaunchContext {
17
19
  platform?: NodeJS.Platform;
18
20
  env?: NodeJS.ProcessEnv;
19
- hasSystemdRun?: () => boolean;
21
+ resolveSystemdRun?: () => string | undefined;
20
22
  }
21
23
 
22
- let systemdRunProbe: boolean | undefined;
24
+ // Absolute install paths only — PATH is never consulted, so a caller-controlled entry cannot
25
+ // redirect the launch. `/usr/local/bin` is where systemd lands when built or stowed outside the
26
+ // distro layout, and `/run/current-system/sw/bin` is the NixOS layout, where the binary lives
27
+ // nowhere else even though the user bus works. A candidate only counts when the binary and its
28
+ // directory are root-owned and not group/world-writable, so a lower-trust local actor cannot
29
+ // plant the launcher the scope probe execs.
30
+ const TRUSTED_SYSTEMD_RUN_PATHS = [
31
+ "/usr/bin/systemd-run", "/bin/systemd-run", "/usr/local/bin/systemd-run",
32
+ "/run/current-system/sw/bin/systemd-run",
33
+ ] as const;
23
34
 
24
- function probeSystemdRun(): boolean {
35
+ export interface SystemdRunHooks {
36
+ isExecutableFile: (path: string) => boolean;
37
+ probeScope: (path: string) => boolean;
38
+ /** Async variant of probeScope; resolveSystemdRunAsync prefers it when present. */
39
+ probeScopeAsync?: (path: string) => Promise<boolean>;
40
+ }
41
+
42
+ const GROUP_OR_WORLD_WRITE = 0o022;
43
+
44
+ // stat (follow) rather than lstat: a root-owned symlink to a user-writable directory must fail
45
+ // on the target's mode, not pass on the symlink's (mirrors isTrustedSystemPath in
46
+ // src/codex/desktop-app/linux.ts).
47
+ export interface SystemdRunTrustDeps {
48
+ /** Test seam: canonicalizes the candidate before its substitution chain is checked. */
49
+ realpathSync?: (path: string) => string;
50
+ /** Test seam: stats a resolved path for ownership and mode. */
51
+ statSync?: (path: string) => { isFile(): boolean; uid: number; mode: number };
52
+ /** Test seam: checks the candidate's executable bit. */
53
+ accessSync?: (path: string, mode: number) => void;
54
+ }
55
+
56
+ function rootOnlyWritable(path: string, stat: SystemdRunTrustDeps["statSync"] = statSync): boolean {
57
+ try {
58
+ const st = stat!(path);
59
+ return st.uid === 0 && (st.mode & GROUP_OR_WORLD_WRITE) === 0;
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ // "Executable" here includes trust: the binary and its directory must be root-owned and not
66
+ // group/world-writable. /usr/local/bin is group-writable on some systems, and a planted or
67
+ // replaced systemd-run there would be exec'd by the scope probe under the service account;
68
+ // the fallback is the plain detached spawn, so nothing breaks when it is skipped.
69
+ // Exported for unit tests.
70
+ export function isTrustedSystemdRunFile(path: string, deps: SystemdRunTrustDeps = {}): boolean {
71
+ try {
72
+ if (!isAbsolute(path)) return false;
73
+ (deps.accessSync ?? accessSync)(path, constants.X_OK);
74
+ // The lexical path may be a symlink. Checking the link's own parent only proves
75
+ // the *entry* is pinned; the file it resolves to — and every ancestor able to
76
+ // substitute that resolved file — is what the scope probe will actually exec.
77
+ const realpath = deps.realpathSync ?? realpathSync;
78
+ const resolved = realpath(path);
79
+ const stat = deps.statSync ?? statSync;
80
+ const st = stat(resolved);
81
+ if (!(st.isFile() && st.uid === 0 && (st.mode & GROUP_OR_WORLD_WRITE) === 0)) {
82
+ return false;
83
+ }
84
+ for (const start of [dirname(path), dirname(resolved)]) {
85
+ for (let dir = start, previous = ""; dir !== previous; previous = dir, dir = dirname(dir)) {
86
+ if (!rootOnlyWritable(dir, stat)) return false;
87
+ }
88
+ }
89
+ return true;
90
+ } catch {
91
+ return false;
92
+ }
93
+ }
94
+
95
+ /** Scope discovery gets only user-bus identity, never inherited management credentials. */
96
+ function scopeProbeEnvironment(): NodeJS.ProcessEnv {
97
+ const env: NodeJS.ProcessEnv = { PATH: "/usr/bin:/bin" };
98
+ for (const name of ["HOME", "USER", "LOGNAME", "XDG_RUNTIME_DIR", "DBUS_SESSION_BUS_ADDRESS"]) {
99
+ if (process.env[name] !== undefined) env[name] = process.env[name];
100
+ }
101
+ return env;
102
+ }
103
+
104
+ const systemdRunHooks: SystemdRunHooks = {
105
+ isExecutableFile: isTrustedSystemdRunFile,
106
+ // Run a real scope with the same absolute binary as its harmless version payload.
107
+ // Probing only the outer --version would not verify the user bus.
108
+ probeScope: path => {
109
+ const probe = spawnSync(path, [...SYSTEMD_SCOPE_ARGS, path, "--version"], { stdio: "ignore", timeout: 5_000, env: scopeProbeEnvironment() });
110
+ return !probe.error && probe.status === 0;
111
+ },
112
+ probeScopeAsync: path => new Promise<boolean>(resolve => {
113
+ const probe = spawn(path, [...SYSTEMD_SCOPE_ARGS, path, "--version"], { stdio: "ignore", env: scopeProbeEnvironment() });
114
+ probe.unref();
115
+ const timer = setTimeout(() => {
116
+ try { probe.kill("SIGKILL"); } catch { /* failed termination is not a successful probe */ }
117
+ resolve(false);
118
+ }, 5_000);
119
+ timer.unref();
120
+ probe.once("error", () => { clearTimeout(timer); resolve(false); });
121
+ probe.once("close", code => { clearTimeout(timer); resolve(code === 0); });
122
+ }),
123
+ };
124
+
125
+ let systemdRunProbe: string | null | undefined;
126
+ let systemdRunProbePending: Promise<string | null> | undefined;
127
+
128
+ export function resolveSystemdRun(hooks: SystemdRunHooks = systemdRunHooks): string | undefined {
25
129
  if (systemdRunProbe === undefined) {
26
- // Run a real no-op scope rather than `--version`: a present binary without a reachable user
27
- // bus would otherwise pass the probe and then fail to start the worker at all.
28
- const probe = spawnSync("systemd-run", [...SYSTEMD_SCOPE_ARGS, "true"], { stdio: "ignore", timeout: 5_000 });
29
- systemdRunProbe = !probe.error && probe.status === 0;
130
+ systemdRunProbe = null;
131
+ for (const command of TRUSTED_SYSTEMD_RUN_PATHS) {
132
+ if (!hooks.isExecutableFile(command)) continue;
133
+ if (hooks.probeScope(command)) {
134
+ systemdRunProbe = command;
135
+ break;
136
+ }
137
+ }
30
138
  }
31
- return systemdRunProbe;
139
+ return systemdRunProbe ?? undefined;
140
+ }
141
+
142
+ /**
143
+ * Management-request path variant. The synchronous resolver blocks the shared
144
+ * event loop for up to four sequential five-second scope probes on first use;
145
+ * the dashboard update route awaits this instead, so probing overlaps other
146
+ * requests. Concurrent first callers share one probe pass.
147
+ */
148
+ export async function resolveSystemdRunAsync(hooks: SystemdRunHooks = systemdRunHooks): Promise<string | undefined> {
149
+ if (systemdRunProbe !== undefined) return systemdRunProbe ?? undefined;
150
+ if (!systemdRunProbePending) {
151
+ systemdRunProbePending = (async () => {
152
+ const probeScope = hooks.probeScopeAsync ?? (async (path: string) => hooks.probeScope(path));
153
+ for (const command of TRUSTED_SYSTEMD_RUN_PATHS) {
154
+ if (!hooks.isExecutableFile(command)) continue;
155
+ if (await probeScope(command)) {
156
+ return command;
157
+ }
158
+ }
159
+ return null;
160
+ })();
161
+ }
162
+ const found = await systemdRunProbePending;
163
+ // Honor a cache the sync resolver may have filled while the probe ran — the
164
+ // older observation wins so every caller converges on one launcher.
165
+ if (systemdRunProbe === undefined) systemdRunProbe = found;
166
+ return systemdRunProbe ?? undefined;
167
+ }
168
+
169
+ export function resetSystemdRunProbeForTests(): void {
170
+ systemdRunProbe = undefined;
171
+ systemdRunProbePending = undefined;
32
172
  }
33
173
 
34
174
  export function guiUpdateWorkerCommand(
@@ -39,8 +179,9 @@ export function guiUpdateWorkerCommand(
39
179
  const platform = context.platform ?? process.platform;
40
180
  const env = context.env ?? process.env;
41
181
  const underSystemd = platform === "linux" && Boolean(env.INVOCATION_ID);
42
- if (underSystemd && (context.hasSystemdRun ?? probeSystemdRun)()) {
43
- return { command: "systemd-run", argv: [...SYSTEMD_SCOPE_ARGS, execPath, ...args] };
182
+ const systemdRun = underSystemd ? (context.resolveSystemdRun ?? resolveSystemdRun)() : undefined;
183
+ if (systemdRun) {
184
+ return { command: systemdRun, argv: [...SYSTEMD_SCOPE_ARGS, execPath, ...args] };
44
185
  }
45
186
  return { command: execPath, argv: [...args] };
46
187
  }
package/src/usage/cost.ts CHANGED
@@ -439,13 +439,16 @@ export function serviceTierContext(entry: ServiceTierContext): ServiceTierContex
439
439
 
440
440
  /** Convert one adapter-observed attempt outcome into the existing pricing provenance shape. */
441
441
  export function serviceTierContextFromOutcome(outcome: AttemptTierOutcome): ServiceTierContext {
442
- if (outcome.canonical === "priority" && outcome.confirmation === "confirmed") {
442
+ const responseAuthoritative = outcome.responseTierAuthoritative !== false;
443
+ if (responseAuthoritative && outcome.canonical === "priority" && outcome.confirmation === "confirmed") {
443
444
  return { responseServiceTier: "priority" };
444
445
  }
445
- if (outcome.responseServiceTier !== undefined) {
446
+ // Non-authoritative echoes remain in the log, but cannot become pricing confirmation either.
447
+ if (responseAuthoritative && outcome.responseServiceTier !== undefined) {
446
448
  return { responseServiceTier: outcome.responseServiceTier };
447
449
  }
448
- if (outcome.canonical === "priority" && outcome.confirmation === "assumed") {
450
+ if (outcome.canonical === "priority"
451
+ && (outcome.confirmation === "assumed" || (!responseAuthoritative && outcome.confirmation === "confirmed"))) {
449
452
  return { requestedServiceTier: "priority" };
450
453
  }
451
454
  // An unclassified route makes no canonical Fast claim, but its adapter can still prove that
package/src/usage/log.ts CHANGED
@@ -656,6 +656,7 @@ function normalizeAttemptTierOutcome(raw: unknown): AttemptTierOutcome | null {
656
656
  if ("callerFastSuppressedByConfig" in outcome
657
657
  && typeof outcome.callerFastSuppressedByConfig !== "boolean") return null;
658
658
  if ("responseServiceTier" in outcome && typeof outcome.responseServiceTier !== "string") return null;
659
+ if ("responseTierAuthoritative" in outcome && typeof outcome.responseTierAuthoritative !== "boolean") return null;
659
660
  const wireValue = sanitizeLogMetadataString(outcome.wireValue);
660
661
  const responseServiceTier = sanitizeLogMetadataString(outcome.responseServiceTier);
661
662
  return {
@@ -678,6 +679,9 @@ function normalizeAttemptTierOutcome(raw: unknown): AttemptTierOutcome | null {
678
679
  ? { callerFastSuppressedByConfig: outcome.callerFastSuppressedByConfig }
679
680
  : {}),
680
681
  confirmation: outcome.confirmation as AttemptTierOutcome["confirmation"],
682
+ ...(typeof outcome.responseTierAuthoritative === "boolean"
683
+ ? { responseTierAuthoritative: outcome.responseTierAuthoritative }
684
+ : {}),
681
685
  ...(responseServiceTier ? { responseServiceTier } : {}),
682
686
  };
683
687
  }
@@ -62,7 +62,7 @@ export const WEB_SEARCH_BACKENDS: readonly WebSearchBackendDescriptor[] = [
62
62
  if (!provider || provider.disabled === true || provider.authMode !== "oauth") return false;
63
63
  const set = getAccountSet("xai");
64
64
  const active = set?.accounts.find(account => account.id === set.activeAccountId);
65
- return !!active && active.needsReauth !== true;
65
+ return !!active && active.needsReauth !== true && active.paused !== true;
66
66
  },
67
67
  eligibleModel: candidate => candidate.provider === "xai",
68
68
  },
@@ -74,7 +74,7 @@ export const WEB_SEARCH_BACKENDS: readonly WebSearchBackendDescriptor[] = [
74
74
  if (!provider || provider.disabled === true || provider.authMode !== "oauth") return false;
75
75
  const set = getAccountSet("google-antigravity");
76
76
  const active = set?.accounts.find(account => account.id === set.activeAccountId);
77
- if (!active || active.needsReauth === true) return false;
77
+ if (!active || active.needsReauth === true || active.paused === true) return false;
78
78
  return !!(active.credential as { projectId?: string } | undefined)?.projectId;
79
79
  },
80
80
  eligibleModel: candidate => candidate.provider === "google-antigravity",
@@ -126,7 +126,7 @@ export async function resolveDevinWebSearchSnapshot(
126
126
  try {
127
127
  const selection = captureOAuthAccountSelection(credentialProvider);
128
128
  if (!selection) return { error: "devin web search auth failed: no signed-in account" };
129
- const snapshot = await getValidAccessTokenSnapshot(credentialProvider);
129
+ const snapshot = await getValidAccessTokenSnapshot(credentialProvider, { oauthProvider: "devin" });
130
130
  const committed = await commitOAuthAccountSelection(credentialProvider, snapshot.accountId, {
131
131
  expectedSelection: selection,
132
132
  expectedCredentialGeneration: snapshot.generation,
@@ -52,9 +52,9 @@ export function findAnthropicSidecarProvider(config: OcxConfig): AnthropicSideca
52
52
  }
53
53
 
54
54
  /**
55
- * First enabled provider whose stored Grok OAuth account is active and not marked for
56
- * reauth — the only credential the xai web-search executor may spend. Same account-set
57
- * predicate the shared sidecar auth module applies to Anthropic.
55
+ * First enabled provider whose stored Grok OAuth account is active, not paused by the
56
+ * operator and not marked for reauth — the only credential the xai web-search executor may
57
+ * spend. Same account-set predicate the shared sidecar auth module applies to Anthropic.
58
58
  */
59
59
  export function findXaiSidecarProvider(config: OcxConfig): { providerName: string; provider: OcxProviderConfig } | undefined {
60
60
  // The stored Grok credential lives under the provider named "xai" (registry id);
@@ -63,7 +63,7 @@ export function findXaiSidecarProvider(config: OcxConfig): { providerName: strin
63
63
  if (!provider || provider.disabled === true || provider.authMode !== "oauth") return undefined;
64
64
  const set = getAccountSet("xai");
65
65
  const active = set?.accounts.find(account => account.id === set.activeAccountId);
66
- if (active && active.needsReauth !== true) return { providerName: "xai", provider };
66
+ if (active && active.needsReauth !== true && active.paused !== true) return { providerName: "xai", provider };
67
67
  return undefined;
68
68
  }
69
69
 
@@ -78,7 +78,7 @@ export function findGeminiSidecarProvider(config: OcxConfig): { providerName: st
78
78
  if (!provider || provider.disabled === true || provider.authMode !== "oauth") return undefined;
79
79
  const set = getAccountSet("google-antigravity");
80
80
  const active = set?.accounts.find(account => account.id === set.activeAccountId);
81
- if (!active || active.needsReauth === true) return undefined;
81
+ if (!active || active.needsReauth === true || active.paused === true) return undefined;
82
82
  const projectId = (active.credential as { projectId?: string } | undefined)?.projectId;
83
83
  if (!projectId) return undefined;
84
84
  return { providerName: "google-antigravity", provider };