@bitkyc08/opencodex 2.12.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. package/README.md +1 -1
  2. package/gui/dist/assets/index-BHldBl6_.js +76 -0
  3. package/gui/dist/index.html +1 -1
  4. package/package.json +1 -1
  5. package/src/adapters/anthropic-output-schema.ts +137 -0
  6. package/src/adapters/anthropic.ts +15 -0
  7. package/src/adapters/google-antigravity-replay.ts +269 -5
  8. package/src/adapters/openai-chat.ts +25 -18
  9. package/src/adapters/openai-responses.ts +63 -17
  10. package/src/bridge.ts +11 -5
  11. package/src/claude/inbound.ts +24 -3
  12. package/src/cli/account-api.ts +2 -0
  13. package/src/cli/account-extended.ts +262 -1
  14. package/src/cli/account.ts +3 -1
  15. package/src/cli/dispatch.ts +532 -0
  16. package/src/cli/doctor.ts +26 -21
  17. package/src/cli/help.ts +9 -278
  18. package/src/cli/index.ts +45 -506
  19. package/src/cli/lab.ts +171 -65
  20. package/src/cli/registry.ts +384 -0
  21. package/src/cli/root.ts +86 -0
  22. package/src/cli/status.ts +9 -2
  23. package/src/cli/v2.ts +37 -2
  24. package/src/codex/account-label.ts +14 -1
  25. package/src/codex/account-store.ts +12 -1
  26. package/src/codex/admission.ts +2 -1
  27. package/src/codex/auth-api.ts +3 -2
  28. package/src/codex/catalog/metadata.ts +6 -4
  29. package/src/codex/catalog/parsing.ts +31 -7
  30. package/src/codex/catalog/provider-fetch.ts +2 -1
  31. package/src/codex/catalog/sync.ts +65 -11
  32. package/src/codex/features.ts +437 -31
  33. package/src/codex/history-job.ts +6 -11
  34. package/src/codex/history-provider.ts +10 -9
  35. package/src/codex/inject.ts +14 -1
  36. package/src/codex/native-profile-startup.ts +49 -3
  37. package/src/codex/native-residue.ts +14 -2
  38. package/src/codex/paths.ts +75 -1
  39. package/src/codex/reset-credit-recovery.ts +1044 -0
  40. package/src/codex/shim.ts +876 -16
  41. package/src/codex/sync.ts +27 -2
  42. package/src/config.ts +148 -10
  43. package/src/generated/compatibility-version.json +2572 -0
  44. package/src/images/loop.ts +1 -1
  45. package/src/lab/artifacts/sanitize.ts +3 -3
  46. package/src/lab/automation/budgets.ts +78 -0
  47. package/src/lab/automation/config-persistence.ts +256 -0
  48. package/src/lab/automation/constants.ts +39 -0
  49. package/src/lab/automation/cooldown.ts +103 -0
  50. package/src/lab/automation/dispatch.ts +211 -0
  51. package/src/lab/automation/index.ts +13 -0
  52. package/src/lab/automation/orchestrator.ts +480 -0
  53. package/src/lab/automation/persistence.ts +512 -0
  54. package/src/lab/automation/planner.ts +371 -0
  55. package/src/lab/automation/policy.ts +136 -0
  56. package/src/lab/automation/queue.ts +191 -0
  57. package/src/lab/automation/recovery.ts +24 -0
  58. package/src/lab/automation/route-context.ts +21 -0
  59. package/src/lab/automation/run-key.ts +44 -0
  60. package/src/lab/automation/runs-query.ts +34 -0
  61. package/src/lab/automation/types.ts +160 -0
  62. package/src/lab/conformance/types.ts +23 -0
  63. package/src/lab/events/types.ts +2 -1
  64. package/src/lab/events/validate.ts +2 -1
  65. package/src/lab/fabric/constants.ts +40 -0
  66. package/src/lab/fabric/executor.ts +492 -0
  67. package/src/lab/fabric/index.ts +80 -0
  68. package/src/lab/fabric/manifest.ts +222 -0
  69. package/src/lab/fabric/observe.ts +454 -0
  70. package/src/lab/fabric/patch.ts +79 -0
  71. package/src/lab/fabric/producer-child.ts +139 -0
  72. package/src/lab/fabric/producer-isolate.ts +276 -0
  73. package/src/lab/fabric/producer-protocol.ts +61 -0
  74. package/src/lab/fabric/scratch.ts +439 -0
  75. package/src/lab/fabric/subject.ts +106 -0
  76. package/src/lab/fabric/types.ts +134 -0
  77. package/src/lab/fabric/verifier.ts +98 -0
  78. package/src/lab/index.ts +2 -0
  79. package/src/lab/ledger/store.ts +173 -0
  80. package/src/lab/live/executor.ts +29 -4
  81. package/src/lab/live/transport.ts +3 -1
  82. package/src/lab/observe/from-conformance.ts +12 -57
  83. package/src/lab/observe/from-live.ts +9 -1
  84. package/src/lab/paths.ts +74 -9
  85. package/src/lab/projection/schema.ts +14 -1
  86. package/src/lab/projection/verdicts.ts +89 -53
  87. package/src/lab/projection/verification.ts +83 -3
  88. package/src/lab/query/catalog.ts +34 -5
  89. package/src/lab/query/freshness.ts +53 -0
  90. package/src/lab/query/index.ts +11 -0
  91. package/src/lab/query/latest-observation.ts +59 -0
  92. package/src/lab/query/passive-production.ts +159 -0
  93. package/src/lab/subject/installation-salt.ts +24 -4
  94. package/src/lab/subject/protocol-subject.ts +80 -0
  95. package/src/lab/subject/route-subject.ts +2 -1
  96. package/src/lib/fabric-task-execution-authority.ts +7 -0
  97. package/src/lib/fabric-task-host.ts +29 -0
  98. package/src/lib/lab-live-route-production.ts +130 -0
  99. package/src/lib/local-management-capability.ts +100 -0
  100. package/src/lib/server-resource-ownership.ts +71 -0
  101. package/src/lib/windows-elevation.ts +8 -1
  102. package/src/lib/windows-user-principal.ts +62 -2
  103. package/src/lib/winsw.ts +8 -2
  104. package/src/oauth/account-import/google-antigravity-adapter.ts +74 -0
  105. package/src/oauth/account-import/index.ts +15 -0
  106. package/src/oauth/account-import/parser.ts +83 -0
  107. package/src/oauth/account-import/registry.ts +18 -0
  108. package/src/oauth/account-import/service.ts +75 -0
  109. package/src/oauth/account-import/types.ts +91 -0
  110. package/src/oauth/anthropic.ts +12 -1
  111. package/src/oauth/callback-server.ts +8 -2
  112. package/src/oauth/chatgpt.ts +12 -1
  113. package/src/oauth/google-antigravity.ts +27 -0
  114. package/src/oauth/health.ts +12 -48
  115. package/src/oauth/index.ts +122 -0
  116. package/src/oauth/kimi.ts +16 -2
  117. package/src/oauth/nous.ts +749 -0
  118. package/src/oauth/store.ts +56 -3
  119. package/src/providers/codex-capacity.ts +1 -0
  120. package/src/providers/registry.ts +161 -16
  121. package/src/responses/custom-tool-compat.ts +182 -0
  122. package/src/responses/parser.ts +22 -0
  123. package/src/responses/reasoning-replay-cache.ts +168 -11
  124. package/src/responses/schema.ts +1 -1
  125. package/src/router.ts +5 -20
  126. package/src/routing/capability.ts +9 -3
  127. package/src/routing/compatibility/assemble.ts +159 -0
  128. package/src/routing/compatibility/behavior.ts +224 -0
  129. package/src/routing/compatibility/catalog.ts +99 -0
  130. package/src/routing/compatibility/endpoint.ts +52 -0
  131. package/src/routing/compatibility/policy.ts +181 -0
  132. package/src/routing/compatibility/reader.ts +110 -0
  133. package/src/routing/compatibility/subject.ts +191 -0
  134. package/src/routing/compatibility/types.ts +64 -0
  135. package/src/routing/compatibility/version.ts +104 -0
  136. package/src/routing/evaluator.ts +27 -32
  137. package/src/routing/profile.ts +118 -4
  138. package/src/routing/trace.ts +82 -28
  139. package/src/server/auth-cors.ts +7 -0
  140. package/src/server/background-lifecycle.ts +20 -7
  141. package/src/server/chat-completions.ts +2 -2
  142. package/src/server/claude-messages.ts +6 -3
  143. package/src/server/direct-local-http.ts +343 -0
  144. package/src/server/index.ts +80 -18
  145. package/src/server/lifecycle.ts +12 -4
  146. package/src/server/local-management-read-client.ts +90 -0
  147. package/src/server/management/agent-settings-routes.ts +27 -3
  148. package/src/server/management/lab-automation-routes.ts +206 -0
  149. package/src/server/management/lab-routes.ts +13 -0
  150. package/src/server/management/logs-usage-routes.ts +1 -0
  151. package/src/server/management/oauth-account-routes.ts +57 -1
  152. package/src/server/management/provider-routes.ts +16 -0
  153. package/src/server/management/routing-profile-routes.ts +12 -24
  154. package/src/server/management-api.ts +2 -0
  155. package/src/server/management-auth.ts +68 -3
  156. package/src/server/proxy-liveness.ts +3 -2
  157. package/src/server/request-decompress.ts +9 -3
  158. package/src/server/request-log.ts +53 -4
  159. package/src/server/responses/collaboration.ts +93 -17
  160. package/src/server/responses/compact.ts +3 -0
  161. package/src/server/responses/core.ts +202 -15
  162. package/src/server/responses/policy-fallback.ts +1 -0
  163. package/src/server/responses-custom-tool-repair.ts +280 -0
  164. package/src/server/responses-terminal-repair.ts +342 -0
  165. package/src/server/startup-action-control.ts +8 -1
  166. package/src/service.ts +278 -12
  167. package/src/types.ts +49 -3
  168. package/src/usage/expected-prices.ts +30 -0
  169. package/src/usage/log.ts +30 -4
  170. package/src/usage/summary.ts +153 -2
  171. package/src/web-search/loop.ts +1 -1
  172. package/gui/dist/assets/index-Cw1_mxO8.js +0 -70
  173. package/src/cli/internal-dispatch.ts +0 -20
@@ -8,6 +8,7 @@ import { getAccountCredential, getAccountSet, removeAccount, saveAccountCredenti
8
8
  import { loginXai, refreshXaiToken, XAI_LOCAL_CLI_DETACH_WARNING, XaiTokenRequestError } from "./xai";
9
9
  import { ANTHROPIC_OAUTH_BETA, AnthropicTokenError, loginAnthropic, refreshAnthropicToken } from "./anthropic";
10
10
  import { loginKimi, refreshKimiToken } from "./kimi";
11
+ import { loginNous, NousTokenError, refreshNousToken, clearNousRefreshIntent, RefreshIntentIOError } from "./nous";
11
12
  import { loginChatGPT, refreshChatGPTToken } from "./chatgpt";
12
13
  import { loginAntigravity, refreshAntigravityToken } from "./google-antigravity";
13
14
  import { loginCursor, refreshCursorToken } from "./cursor";
@@ -194,6 +195,22 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderDef> = {
194
195
  providerConfig: oauthConfig("kimi"),
195
196
  defaultModel: oauthDefaultModel("kimi"),
196
197
  },
198
+ nous: {
199
+ // Nous Portal device-grant login (RFC 8628) against portal.nousresearch.com.
200
+ // The access token is the per-request inference JWT (scope inference:invoke).
201
+ // Refresh tokens are single-use and rotated server-side on every refresh:
202
+ // keep background refresh lazy-only (the default) so concurrent refreshes
203
+ // cannot trip the Portal's token-reuse revocation.
204
+ login: (ctrl) => loginNous(ctrl),
205
+ refresh: (rt, signal) => refreshNousToken(rt, signal),
206
+ providerConfig: oauthConfig("nous"),
207
+ defaultModel: oauthDefaultModel("nous"),
208
+ // Single-use rotating refresh tokens must never be background-refreshed
209
+ // proactively: concurrent refreshes would trip the Portal's reuse
210
+ // revocation. Anchor the lazy-only default explicitly so it cannot be
211
+ // silently overridden to proactive.
212
+ defaultRefreshPolicy: "lazy-only",
213
+ },
197
214
  kiro: {
198
215
  login: (ctrl, opts) => loginKiro(ctrl, { forceLogin: opts?.forceLogin }),
199
216
  refresh: (rt, signal, credential) => refreshKiroToken(rt, signal, credential),
@@ -436,6 +453,11 @@ function terminal(error:unknown):boolean{
436
453
  if(error instanceof XaiTokenRequestError)return ["invalid_grant","refresh_token_reused","revoked_token"].includes(error.oauthError??"");
437
454
  if(error instanceof AnthropicTokenError)return (error.httpStatus===400||error.httpStatus===401)&&["invalid_grant","refresh_token_reused","revoked","revoked_token","refresh_token_revoked"].includes(error.oauthError??"");
438
455
  if(error instanceof KiroTokenRefreshError)return (error.httpStatus===400||error.httpStatus===401)&&error.oauthError!==undefined;
456
+ if(error instanceof NousTokenError)return error.terminal===true||["invalid_grant","refresh_token_reused","revoked","revoked_token","expired_token"].includes(error.oauthError??"");
457
+ // Local durable-write/read/cleanup failures are operational, not credential
458
+ // death: the provider credential was never rejected or consumed. Never mark
459
+ // the account needsReauth for broken local persistence infrastructure.
460
+ if (error instanceof RefreshIntentIOError) return false;
439
461
  return isTerminalRefreshError(error);
440
462
  }
441
463
  function authoritative(stored:OAuthCredentials,active:boolean,now:()=>number):OAuthCredentials{if(stored.source!=="local-cli")return stored;const disk=detectGrokCliToken();if(!disk)return stored;const allowed=isSameGrokIdentity(stored,disk)||(active&&!hasComparableGrokIdentity(stored,disk));return allowed&&shouldAdoptGrokGeneration(stored,disk,now(),REFRESH_SKEW_MS)?disk:stored;}
@@ -459,6 +481,47 @@ function newerClaudeCredential(stored: OAuthCredentials, now: number): OAuthCred
459
481
  return credentialGeneration(disk) !== credentialGeneration(stored) ? disk : undefined;
460
482
  }
461
483
 
484
+ /**
485
+ * Preserve an already-rotated Nous refresh token (RT-B) after a terminal refresh
486
+ * error (e.g. the returned access JWT lacked `inference:invoke`). The unusable
487
+ * access token is NOT persisted as valid: the recovery credential carries an
488
+ * empty access placeholder with a past expiry so it can never be routed, and the
489
+ * account is marked needsReauth by the caller. Generation-safe: a concurrent
490
+ * newer write wins and is never overwritten.
491
+ */
492
+ async function preserveNousRotatedRefresh(
493
+ provider: string,
494
+ accountId: string,
495
+ rotatedRefresh: string,
496
+ expectedGeneration: string,
497
+ previous: OAuthCredentials,
498
+ ): Promise<{ kind: "persisted"; generation: string } | { kind: "superseded" } | { kind: "failed" }> {
499
+ try {
500
+ const recovery: OAuthCredentials = {
501
+ refresh: rotatedRefresh,
502
+ // Never persist the unusable access token: an empty placeholder with a
503
+ // past expiry can never be observed as a valid credential.
504
+ access: "",
505
+ expires: 0,
506
+ ...(previous.accountId ? { accountId: previous.accountId } : {}),
507
+ ...(previous.email ? { email: previous.email } : {}),
508
+ ...(previous.source ? { source: previous.source } : {}),
509
+ };
510
+ const outcome = await mergeAccountCredential(provider, accountId, recovery, { expectedGeneration });
511
+ if (outcome.superseded) return { kind: "superseded" };
512
+ // Return the exact generation this write produced, so the caller never
513
+ // re-reads the store (a concurrent writer could otherwise supply a different
514
+ // credential generation and be marked needsReauth by mistake).
515
+ return { kind: "persisted", generation: credentialGeneration(recovery) };
516
+ } catch (error) {
517
+ // A store-mutation busy outcome is transient and retryable; surface it
518
+ // unchanged so the caller can retry rather than treating it as a permanent
519
+ // RT-B persistence failure.
520
+ if (error instanceof OAuthMutationBusyError) throw error;
521
+ return { kind: "failed" };
522
+ }
523
+ }
524
+
462
525
  export async function refreshAnthropicAccountWithLock(
463
526
  provider: string,
464
527
  accountId: string,
@@ -572,10 +635,69 @@ export async function refreshGenericAccountWithLock(
572
635
  throw new OAuthLoginRequiredError(provider);
573
636
  }
574
637
  logOAuthEvent("OAuth credentials rotated and persisted", { provider, accountId });
638
+ // Best-effort bookkeeping cleanup: the rotated credential is already
639
+ // durably persisted. A failure to unlink the old-token intent file
640
+ // (EACCES/EPERM/EBUSY/EROFS) must not turn a committed rotation into a
641
+ // failed refresh. The stale intent keys the OLD token, which is no longer
642
+ // stored, so leaving it behind blocks nothing and is safe. It must also
643
+ // never route through the generic refresh error path (no needsReauth).
644
+ if (provider === "nous") {
645
+ try {
646
+ clearNousRefreshIntent(stored.refresh);
647
+ } catch (cleanupErr) {
648
+ logOAuthEvent("OAuth refresh intent cleanup failed (non-fatal)", {
649
+ provider,
650
+ accountId,
651
+ cause: cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr),
652
+ });
653
+ }
654
+ }
575
655
  return fresh.access;
576
656
  } catch (error) {
577
657
  if (error instanceof OAuthMutationBusyError) throw error;
578
658
  if (!terminal(error)) throw error;
659
+ // Nous-specific failure-atomicity: a terminal refresh error that carries
660
+ // an already-issued rotated refresh token (e.g. the access JWT lacked the
661
+ // required `inference:invoke` scope) means the server consumed RT-A and
662
+ // issued RT-B. RT-B must be preserved generation-safely BEFORE forcing
663
+ // reauthentication; discarding it would lose the only usable refresh
664
+ // material and force a full re-auth for no reason.
665
+ if (provider === "nous" && error instanceof NousTokenError) {
666
+ const rotated = error.getRotatedRefresh();
667
+ if (rotated !== undefined && rotated !== stored.refresh) {
668
+ const outcome = await preserveNousRotatedRefresh(
669
+ provider,
670
+ accountId,
671
+ rotated,
672
+ generation,
673
+ stored,
674
+ );
675
+ if (outcome.kind === "persisted") {
676
+ // RT-A's intent is cleared only after RT-B is durably persisted;
677
+ // cleanup itself stays best-effort (a stale RT-A intent keys a token
678
+ // that is no longer stored).
679
+ try {
680
+ clearNousRefreshIntent(stored.refresh);
681
+ } catch (cleanupErr) {
682
+ logOAuthEvent("OAuth refresh intent cleanup failed (non-fatal)", {
683
+ provider,
684
+ accountId,
685
+ cause: cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr),
686
+ });
687
+ }
688
+ // Mark exactly the generation this write produced — never a
689
+ // credential written by a concurrent login between the merge and
690
+ // this step.
691
+ await markAccountNeedsReauthIfGeneration(provider, accountId, outcome.generation, writerGeneration);
692
+ } else {
693
+ // RT-B persistence failed or a newer generation superseded it:
694
+ // never clear RT-A's intent (RT-A was consumed), and mark the old
695
+ // generation needsReauth (a no-op if a newer generation won).
696
+ await markAccountNeedsReauthIfGeneration(provider, accountId, generation, writerGeneration);
697
+ }
698
+ throw new OAuthLoginRequiredError(provider);
699
+ }
700
+ }
579
701
  await markAccountNeedsReauthIfGeneration(provider, accountId, generation, writerGeneration);
580
702
  throw new OAuthLoginRequiredError(provider);
581
703
  }
package/src/oauth/kimi.ts CHANGED
@@ -151,7 +151,21 @@ async function requestDeviceAuthorization(): Promise<{
151
151
  }
152
152
 
153
153
  function parseTokenPayload(payload: TokenResponse, refreshFallback?: string): OAuthCredentials {
154
- if (!payload.access_token || typeof payload.expires_in !== "number") {
154
+ // Number.isFinite is required here: typeof NaN === "number", so the type check
155
+ // alone would let a NaN expires_in through and produce a never-refreshing expiry.
156
+ // Negative durations would stamp an already-past expiry — also malformed.
157
+ // The computed timestamp itself must also stay finite: Number.MAX_VALUE passes
158
+ // Number.isFinite but overflows to Infinity once multiplied by 1000.
159
+ if (
160
+ !payload.access_token
161
+ || typeof payload.expires_in !== "number"
162
+ || !Number.isFinite(payload.expires_in)
163
+ || payload.expires_in < 0
164
+ ) {
165
+ throw new Error("Kimi token response missing required fields");
166
+ }
167
+ const expires = Date.now() + payload.expires_in * 1000 - OAUTH_EXPIRY_SKEW_MS;
168
+ if (!Number.isFinite(expires)) {
155
169
  throw new Error("Kimi token response missing required fields");
156
170
  }
157
171
  const refresh = payload.refresh_token ?? refreshFallback;
@@ -160,7 +174,7 @@ function parseTokenPayload(payload: TokenResponse, refreshFallback?: string): OA
160
174
  return {
161
175
  access: payload.access_token,
162
176
  refresh,
163
- expires: Date.now() + payload.expires_in * 1000 - OAUTH_EXPIRY_SKEW_MS,
177
+ expires,
164
178
  ...identity,
165
179
  };
166
180
  }