@bitkyc08/opencodex 2.55.0 → 2.57.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 (256) hide show
  1. package/bin/ocx.mjs +10 -0
  2. package/gui/dist/assets/{index-BBOZWGB6.css → index-C5-RdDmD.css} +1 -1
  3. package/gui/dist/assets/{index-VuoiWj9J.js → index-Cz7CLdif.js} +21 -21
  4. package/gui/dist/index.html +2 -2
  5. package/package.json +4 -3
  6. package/src/adapters/base.ts +21 -0
  7. package/src/adapters/codebuddy/adapter.ts +2 -1
  8. package/src/adapters/codebuddy/scaffold-guard.ts +248 -0
  9. package/src/adapters/command-code.ts +1 -1
  10. package/src/adapters/cursor/envelope-echo.ts +8 -2
  11. package/src/adapters/cursor/transport-retry.ts +46 -1
  12. package/src/adapters/cursor.ts +4 -0
  13. package/src/adapters/google.ts +7 -7
  14. package/src/adapters/kiro/adapter.ts +42 -1
  15. package/src/adapters/kiro/payload.ts +17 -3
  16. package/src/adapters/kiro/reasoning.ts +70 -7
  17. package/src/adapters/kiro/stream.ts +8 -2
  18. package/src/adapters/kiro/wire.ts +2 -1
  19. package/src/adapters/kiro-events.ts +21 -13
  20. package/src/adapters/kiro-retry.ts +23 -4
  21. package/src/adapters/openai-chat/errors.ts +116 -0
  22. package/src/adapters/openai-chat/messages.ts +346 -0
  23. package/src/adapters/openai-chat/passthrough.ts +146 -0
  24. package/src/adapters/openai-chat/response-events.ts +117 -0
  25. package/src/adapters/openai-chat/tool-call-validation.ts +200 -0
  26. package/src/adapters/openai-chat/tool-name-registry.ts +166 -0
  27. package/src/adapters/openai-chat/tool-schema.ts +495 -0
  28. package/src/adapters/openai-chat/wire.ts +50 -0
  29. package/src/adapters/openai-chat.ts +40 -1452
  30. package/src/adapters/openai-responses/canonical-forward.ts +202 -0
  31. package/src/adapters/openai-responses/image-gen.ts +406 -0
  32. package/src/adapters/openai-responses/internal.ts +3 -0
  33. package/src/adapters/openai-responses/passthrough.ts +642 -0
  34. package/src/adapters/openai-responses/prompt-cache.ts +83 -0
  35. package/src/adapters/openai-responses/reasoning.ts +220 -0
  36. package/src/adapters/openai-responses/request-strips.ts +185 -0
  37. package/src/adapters/openai-responses/tool-output-recovery.ts +509 -0
  38. package/src/adapters/openai-responses/tool-schema.ts +293 -0
  39. package/src/adapters/openai-responses/web-search.ts +156 -0
  40. package/src/adapters/openai-responses.ts +4 -2625
  41. package/src/bridge/errors.ts +58 -0
  42. package/src/bridge/internal.ts +174 -0
  43. package/src/bridge/response-json.ts +630 -0
  44. package/src/bridge/sse.ts +1462 -0
  45. package/src/bridge.ts +5 -2204
  46. package/src/chat/inbound.ts +12 -1
  47. package/src/claude/desktop-profile.ts +66 -9
  48. package/src/claude/outbound.ts +18 -0
  49. package/src/cli/account-main.ts +1 -1
  50. package/src/cli/capabilities.ts +2 -2
  51. package/src/cli/combo.ts +10 -1
  52. package/src/cli/index.ts +48 -5
  53. package/src/cli/registry.ts +2 -1
  54. package/src/cli/system-command.ts +4 -4
  55. package/src/clients/config-export.ts +7 -3
  56. package/src/codex/account-label.ts +14 -3
  57. package/src/codex/account-lifecycle.ts +3 -0
  58. package/src/codex/account-store.ts +184 -35
  59. package/src/codex/account-usability.ts +21 -0
  60. package/src/codex/auth-api/account-list.ts +507 -0
  61. package/src/codex/auth-api/http.ts +32 -0
  62. package/src/codex/auth-api/login-flow.ts +566 -0
  63. package/src/codex/auth-api/login-state.ts +64 -0
  64. package/src/codex/auth-api/main-account-probe.ts +331 -0
  65. package/src/codex/auth-api/pool-mode-gate.ts +274 -0
  66. package/src/codex/auth-api/pool-quota-probe.ts +512 -0
  67. package/src/codex/auth-api/reset-credit-service.ts +431 -0
  68. package/src/codex/auth-api/routes.ts +425 -0
  69. package/src/codex/auth-api/runtime-config.ts +48 -0
  70. package/src/codex/auth-api.ts +27 -3118
  71. package/src/codex/auth-context.ts +252 -35
  72. package/src/codex/catalog/aggregation.ts +80 -1
  73. package/src/codex/catalog/auto-review.ts +507 -0
  74. package/src/codex/catalog/build-entries.ts +981 -0
  75. package/src/codex/catalog/combo-member.ts +375 -0
  76. package/src/codex/catalog/derive-entry.ts +229 -0
  77. package/src/codex/catalog/effort.ts +0 -1
  78. package/src/codex/catalog/gated-native-warn.ts +63 -0
  79. package/src/codex/catalog/gather-capture.ts +533 -0
  80. package/src/codex/catalog/model-hints.ts +691 -0
  81. package/src/codex/catalog/model-visibility.ts +305 -0
  82. package/src/codex/catalog/provider-fetch.ts +52 -2942
  83. package/src/codex/catalog/provider-models.ts +685 -0
  84. package/src/codex/catalog/remote.ts +30 -0
  85. package/src/codex/catalog/restore.ts +132 -0
  86. package/src/codex/catalog/retained-sync.ts +714 -0
  87. package/src/codex/catalog/routed-gather.ts +895 -0
  88. package/src/codex/catalog/subagent-roster.ts +176 -0
  89. package/src/codex/catalog/sync.ts +52 -2698
  90. package/src/codex/cli-install-provenance.ts +7 -1
  91. package/src/codex/convergence.ts +7 -2
  92. package/src/codex/desktop-app/types.ts +11 -2
  93. package/src/codex/desktop-app/windows.ts +5 -5
  94. package/src/codex/inject/config-toml.ts +563 -0
  95. package/src/codex/inject/remove.ts +192 -0
  96. package/src/codex/inject/restore.ts +567 -0
  97. package/src/codex/inject/routing-classify.ts +109 -0
  98. package/src/codex/inject/routing-target.ts +125 -0
  99. package/src/codex/inject.ts +89 -1444
  100. package/src/codex/lineage.ts +458 -0
  101. package/src/codex/model-entitlements.ts +152 -15
  102. package/src/codex/pool-refresh-backoff.ts +161 -0
  103. package/src/codex/quota-rejection.ts +104 -15
  104. package/src/codex/routing/active-account.ts +194 -0
  105. package/src/codex/routing/cache-affinity.ts +70 -0
  106. package/src/codex/routing/cooldown-math.ts +285 -0
  107. package/src/codex/routing/health-store.ts +402 -0
  108. package/src/codex/routing/probe-lease.ts +358 -0
  109. package/src/codex/routing/selection.ts +780 -0
  110. package/src/codex/routing/thread-affinity.ts +586 -0
  111. package/src/codex/routing/transient-hold-dispatch.ts +141 -0
  112. package/src/codex/routing.ts +370 -2271
  113. package/src/codex/shim-fingerprint.ts +223 -0
  114. package/src/codex/shim-inspect.ts +175 -0
  115. package/src/codex/shim-probe.ts +367 -0
  116. package/src/codex/shim-restore-lock.ts +169 -0
  117. package/src/codex/shim-state-file.ts +151 -0
  118. package/src/codex/shim-templates.ts +265 -0
  119. package/src/codex/shim.ts +48 -1268
  120. package/src/codex/warmup.ts +1 -1
  121. package/src/combos/failover.ts +85 -0
  122. package/src/combos/request.ts +17 -10
  123. package/src/combos/types.ts +23 -2
  124. package/src/config/diagnostics.ts +705 -0
  125. package/src/config/feature-flags.ts +55 -0
  126. package/src/config/live-reconcile.ts +403 -0
  127. package/src/config/load-degrade.ts +880 -0
  128. package/src/config/mutation-lock.ts +244 -0
  129. package/src/config/openai-tier-backup.ts +268 -0
  130. package/src/config/pending-teardown.ts +31 -0
  131. package/src/config/persist-unlocked.ts +92 -0
  132. package/src/config/proxy-env.ts +188 -0
  133. package/src/config/salvage.ts +244 -0
  134. package/src/config/schema/config-schema.ts +640 -0
  135. package/src/config/schema/leaf-validators.ts +855 -0
  136. package/src/config/warn-memo.ts +28 -0
  137. package/src/config.ts +234 -4481
  138. package/src/generated/compatibility-version.json +649 -121
  139. package/src/images/loop.ts +1 -1
  140. package/src/lib/errors.ts +17 -0
  141. package/src/lib/request-execution-budget.ts +198 -23
  142. package/src/lib/spend-reservation-ledger.ts +958 -0
  143. package/src/lib/state-store-registrations.ts +6 -2
  144. package/src/lib/test-home-guard.ts +85 -1
  145. package/src/lib/upstream-retry.ts +132 -21
  146. package/src/lib/windows-elevation.ts +76 -14
  147. package/src/lib/workflow-budget.ts +553 -30
  148. package/src/oauth/index.ts +2 -2
  149. package/src/oauth/key-providers.ts +2 -2
  150. package/src/providers/kiro-models.ts +4 -3
  151. package/src/providers/label.ts +19 -1
  152. package/src/providers/model-discovery.ts +16 -0
  153. package/src/providers/quota/account-cache.ts +441 -0
  154. package/src/providers/quota/antigravity.ts +295 -0
  155. package/src/providers/quota/report-cache.ts +320 -0
  156. package/src/providers/quota/vendor-probes-key.ts +1243 -0
  157. package/src/providers/quota/vendor-probes-oauth.ts +590 -0
  158. package/src/providers/quota.ts +324 -3079
  159. package/src/providers/registry/entries-core.ts +1228 -0
  160. package/src/providers/registry/entries-extended.ts +1213 -0
  161. package/src/providers/registry/model-seeds.ts +912 -0
  162. package/src/providers/registry/types.ts +352 -0
  163. package/src/providers/registry.ts +24 -3536
  164. package/src/responses/continuation-ownership.ts +29 -0
  165. package/src/responses/reasoning-envelope.ts +6 -3
  166. package/src/responses/state/replay-fingerprint.ts +80 -0
  167. package/src/responses/state/snapshot-codec.ts +104 -0
  168. package/src/responses/state/spill-failure.ts +118 -0
  169. package/src/responses/state/spill-queue.ts +665 -0
  170. package/src/responses/state/temp-recovery.ts +257 -0
  171. package/src/responses/state.ts +82 -1143
  172. package/src/routing/identity-domains.ts +456 -0
  173. package/src/routing/probe-lease.ts +613 -0
  174. package/src/server/chat-completions.ts +3 -1
  175. package/src/server/chat-native.ts +37 -9
  176. package/src/server/index/bounded-request.ts +88 -0
  177. package/src/server/index/live-sideband.ts +601 -0
  178. package/src/server/index/serve-options.ts +1766 -0
  179. package/src/server/index/startup-warnings.ts +213 -0
  180. package/src/server/index/websocket-handler.ts +339 -0
  181. package/src/server/index.ts +45 -2552
  182. package/src/server/inspection-tee.ts +107 -0
  183. package/src/server/live.ts +46 -1
  184. package/src/server/management/combo-routes.ts +10 -1
  185. package/src/server/management/route-registry.ts +26 -23
  186. package/src/server/management/shared.ts +8 -5
  187. package/src/server/management/workflow-budget-routes.ts +133 -0
  188. package/src/server/management-api.ts +12 -0
  189. package/src/server/relay-eager.ts +2 -0
  190. package/src/server/relay.ts +14 -19
  191. package/src/server/request-log-conversation.ts +9 -7
  192. package/src/server/request-log.ts +372 -4
  193. package/src/server/response-log-body.ts +153 -0
  194. package/src/server/responses/account-change-state.ts +307 -0
  195. package/src/server/responses/adapter-continuation.ts +540 -0
  196. package/src/server/responses/adapter-delivery.ts +208 -0
  197. package/src/server/responses/adapter-dispatch.ts +1042 -0
  198. package/src/server/responses/codex-ws-wire.ts +5 -0
  199. package/src/server/responses/collaboration.ts +74 -4
  200. package/src/server/responses/combo-session-recall.ts +68 -8
  201. package/src/server/responses/compact.ts +113 -17
  202. package/src/server/responses/completion-policy.ts +33 -0
  203. package/src/server/responses/core-auth.ts +529 -0
  204. package/src/server/responses/core-codex-account.ts +907 -0
  205. package/src/server/responses/core-combo-failure.ts +210 -0
  206. package/src/server/responses/core-combo.ts +787 -0
  207. package/src/server/responses/core-errors.ts +170 -0
  208. package/src/server/responses/core-lifetime.ts +95 -0
  209. package/src/server/responses/core-normalize.ts +350 -0
  210. package/src/server/responses/core-opaque-recovery.ts +380 -0
  211. package/src/server/responses/core-options.ts +159 -0
  212. package/src/server/responses/core-replay.ts +298 -0
  213. package/src/server/responses/core.ts +192 -8893
  214. package/src/server/responses/encrypted-payload.ts +0 -1
  215. package/src/server/responses/input-admission.ts +126 -6
  216. package/src/server/responses/passthrough-delivery.ts +869 -0
  217. package/src/server/responses/passthrough-dispatch.ts +1494 -0
  218. package/src/server/responses/passthrough-error.ts +38 -2
  219. package/src/server/responses/passthrough-execution.ts +54 -0
  220. package/src/server/responses/request-prepare.ts +1080 -0
  221. package/src/server/responses/request-send-budget.ts +259 -0
  222. package/src/server/responses/request-sidecar-auth.ts +149 -0
  223. package/src/server/responses/request-spend.ts +147 -0
  224. package/src/server/responses/request-transport.ts +803 -0
  225. package/src/server/responses/response-effects.ts +157 -0
  226. package/src/server/responses/run-turn-execution.ts +476 -0
  227. package/src/server/responses/sidecar-execution.ts +463 -0
  228. package/src/server/responses/terminal-guard.ts +65 -4
  229. package/src/server/responses-image-gen-repair.ts +1 -1
  230. package/src/server/responses-undeclared-tool-guard.ts +9 -5
  231. package/src/server/workflow-refusal.ts +84 -0
  232. package/src/service/windows-ops.ts +210 -16
  233. package/src/service/windows-scheduler.ts +28 -21
  234. package/src/service.ts +1 -1
  235. package/src/types/config.ts +34 -1
  236. package/src/types/request.ts +8 -5
  237. package/src/types/tools.ts +24 -0
  238. package/src/types.ts +2 -0
  239. package/src/update/index.ts +10 -0
  240. package/src/update/stop-contract.d.mts +1 -0
  241. package/src/update/stop-contract.mjs +19 -0
  242. package/src/update/stop-decision.d.mts +1 -1
  243. package/src/update/stop-decision.mjs +12 -3
  244. package/src/usage/log.ts +147 -1
  245. package/src/usage/summary.ts +171 -21
  246. package/src/vision/anthropic-describe.ts +1 -1
  247. package/src/vision/describe.ts +5 -5
  248. package/src/web-search/anthropic-executor.ts +1 -1
  249. package/src/web-search/exa-executor.ts +1 -1
  250. package/src/web-search/executor.ts +1 -1
  251. package/src/web-search/gemini-executor.ts +1 -1
  252. package/src/web-search/loop.ts +1 -1
  253. package/src/web-search/ollama-executor.ts +1 -1
  254. package/src/web-search/parse.ts +67 -14
  255. package/src/web-search/passthrough-bridge.ts +64 -31
  256. package/src/web-search/xai-executor.ts +1 -1
@@ -0,0 +1,463 @@
1
+ import type { ResponsesRequestContext } from "./core-options";
2
+ import type { PreparedResponsesRequest } from "./request-prepare";
3
+ import type { ResponsesTransport } from "./request-transport";
4
+ import type { ResponsesSidecarAuth } from "./request-sidecar-auth";
5
+ import type { ResponsesEffects } from "./response-effects";
6
+ import type { ResponsesSendBudget } from "./request-send-budget";
7
+ import { formatErrorResponse } from "../../bridge";
8
+ import { planWebSearch, buildWebSearchTool, runWithWebSearch } from "../../web-search";
9
+ import {
10
+ planImageBridge,
11
+ planVideoBridge,
12
+ IMAGE_GEN_TOOL_NAME,
13
+ buildImageTool,
14
+ VIDEO_GEN_TOOL_NAME,
15
+ buildVideoTool,
16
+ runWithImageBridge,
17
+ clampImageMaxRounds,
18
+ } from "../../images";
19
+ import type { ProviderAdapter } from "../../adapters/base";
20
+ import { rotateProviderTransportOn429, rateLimitRetryPolicyFor } from "../../providers/key-failover";
21
+ import {
22
+ GENERIC_OAUTH_MAX_FAILOVERS_PER_REQUEST,
23
+ isGenericOAuthFailoverEnabled,
24
+ rotateGenericOAuthAccountOn429,
25
+ failoverAccountSnapshot,
26
+ } from "../../oauth/generic-account-failover";
27
+ import {
28
+ ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST,
29
+ rotateAnthropicAccountOn429,
30
+ getAnthropicPoolAccessSnapshot,
31
+ formatAnthropicProviderForLog,
32
+ } from "../../oauth/anthropic-routing";
33
+ import { resolveWireProtocolOverride } from "../adapter-resolve";
34
+ import { bindRouteReasoningReplayScope, adapterNeedsForcedContinuation } from "./core-replay";
35
+ import { namespacedToolName } from "../../types";
36
+ import { providerFetch } from "./fetch-helpers";
37
+ import type { AttemptRecoveryKind } from "../../usage/log";
38
+ import { recordAdapterReasoning, recordAdapterTier } from "../request-log";
39
+ import { normalizeLogConversationId } from "../request-log-conversation";
40
+ import { rememberResponseState } from "../../responses/state";
41
+ import { trackStreamLifetime } from "../lifecycle";
42
+
43
+ /** One responsibility of the Responses request pipeline; state owners are explicit. */
44
+ export async function executeResponsesSidecars(
45
+ requestContext: Pick<ResponsesRequestContext, "config" | "options" | "logCtx">,
46
+ requestState: Pick<
47
+ PreparedResponsesRequest,
48
+ | "parsed"
49
+ | "route"
50
+ | "inboundWire"
51
+ | "selectedForwardHeaders"
52
+ | "translatorBudget"
53
+ | "rememberKiroDeliveredFinalAnswer"
54
+ | "responseStateOptions"
55
+ >,
56
+ transportState: Pick<
57
+ ResponsesTransport,
58
+ | "adapter"
59
+ | "genericFailoverAccountId"
60
+ | "genericFailovers"
61
+ | "applyFailoverSnapshot"
62
+ | "anthropicPoolAccountId"
63
+ | "anthropicPoolFailovers"
64
+ | "anthropicSessionKey"
65
+ | "commitResolvedOAuthSelection"
66
+ | "resolveSelectionAdapter"
67
+ | "oauthDispatch"
68
+ | "noteRoutedAttemptSend"
69
+ | "bindKeyUsageFromBridge"
70
+ >,
71
+ sidecarState: Pick<ResponsesSidecarAuth, "routedCompaction" | "openAiSidecar">,
72
+ responseEffects: Pick<
73
+ ResponsesEffects,
74
+ | "commitReasoningReplayServingRoute"
75
+ | "continuationStateForResponse"
76
+ | "notifyResponseComplete"
77
+ | "cancelResponseCompletion"
78
+ >,
79
+ sendBudgetState: Pick<ResponsesSendBudget, "reserveCredentialHop">,
80
+ ) {
81
+ const { config, options, logCtx } = requestContext;
82
+ const {
83
+ applyFailoverSnapshot,
84
+ anthropicSessionKey,
85
+ commitResolvedOAuthSelection,
86
+ resolveSelectionAdapter,
87
+ oauthDispatch,
88
+ } = transportState;
89
+ const {
90
+ parsed,
91
+ route,
92
+ inboundWire,
93
+ translatorBudget,
94
+ rememberKiroDeliveredFinalAnswer,
95
+ responseStateOptions,
96
+ } = requestState;
97
+ const { routedCompaction, openAiSidecar } = sidecarState;
98
+ const { reserveCredentialHop } = sendBudgetState;
99
+ const {
100
+ commitReasoningReplayServingRoute,
101
+ continuationStateForResponse,
102
+ notifyResponseComplete,
103
+ cancelResponseCompletion,
104
+ } = responseEffects;
105
+
106
+
107
+ // Tool results are PAIRED by call_id. parseRequest writes it into OcxToolResultMessage.toolCallId
108
+ // (parser.ts:738/752) without validating it, because inputItemSchema's permissive catch-all
109
+ // (schema.ts:106) accepts a tool item whose strict schema failed only for a missing call_id. A
110
+ // translating adapter then consumes `toolCallId: string` holding undefined: kiro-wire.ts:32
111
+ // TypeErrors, ollama-native.ts:334 throws, and anthropic.ts:775 sends
112
+ // "[tool_result without adjacent tool_use: undefined]" upstream (issue #3259).
113
+ //
114
+ // This CANNOT move into the schema. parseRequest (:2812) runs before the passthrough branch
115
+ // (:3719), so a parse-time rejection would also kill forward/key passthrough and routed
116
+ // compaction — paths that never read context.messages, build from _rawBody, and already
117
+ // degrade an unpaired output to "[tool output for unknown call]" on their own.
118
+ //
119
+ // Keyed on the adapter, not on position: routedCompaction skips the passthrough branch above
120
+ // yet still builds from _rawBody (see the :3703 comment).
121
+ if (!("passthrough" in transportState.adapter && transportState.adapter.passthrough)) {
122
+ const unpaired = parsed.context.messages.find(
123
+ message => message.role === "toolResult"
124
+ && (typeof (message as { toolCallId?: unknown }).toolCallId !== "string"
125
+ || (message as { toolCallId: string }).toolCallId.length === 0),
126
+ );
127
+ if (unpaired) {
128
+ // Never interpolate the tool output: this message reaches the client and the logs.
129
+ return formatErrorResponse(
130
+ 400,
131
+ "invalid_request_error",
132
+ "tool result requires a non-empty string call_id",
133
+ );
134
+ }
135
+ }
136
+
137
+ // Image / web-search sidecars: plan once, then dispatch with runTurn-aware priority.
138
+ // Routed-compaction turns must NOT hit the image bridge: compaction clears tools/_webSearch but
139
+ // leaves _imageGeneration, so planImageBridge would activate and return a normal Responses
140
+ // completion instead of the synthetic compaction item Codex expects (#424).
141
+ //
142
+ // Web-search's loop only supports buildRequest/fetch/parseStream — NOT adapter.runTurn. Sending
143
+ // Cursor/runTurn requests into runWithWebSearch produces empty HTTP failures. So:
144
+ // - non-runTurn: web-search wins over image when both eligible (documented priority)
145
+ // - runTurn: image bridge may run (it supports runTurn); web-search is skipped so runTurn
146
+ // can proceed for web-search-only turns
147
+ const wsPlan = !routedCompaction
148
+ ? planWebSearch(config, parsed, false, route.provider, route.modelId, openAiSidecar, {
149
+ admission: options.admission, codexAuthPolicy: options.codexAuthPolicy, providerName: route.providerName,
150
+ })
151
+ : undefined;
152
+ const imgPlan = !routedCompaction ? await planImageBridge(config, parsed, route.provider) : undefined;
153
+ const vidPlan = !routedCompaction ? await planVideoBridge(config, parsed, route.provider) : undefined;
154
+ const canRunWebSearch = !!wsPlan && !transportState.adapter.runTurn;
155
+ const rotateSidecarProviderOn429 = async (
156
+ retryAfter: string | null,
157
+ responseHeaders?: Headers,
158
+ ): Promise<ProviderAdapter | null> => {
159
+ const rotated = rotateProviderTransportOn429(config, route.providerName, route.provider, {
160
+ retryAfter,
161
+ now: Date.now(),
162
+ attemptedKey: route.provider.apiKey,
163
+ promptCacheKey: parsed.options.promptCacheKey,
164
+ });
165
+ if (rotated) {
166
+ route.provider = rotated;
167
+ } else if (
168
+ // A POSITIVE gate, not an early return. An early `return null` here made every later arm
169
+ // unreachable: Anthropic never has a genericFailoverAccountId (isGenericFailoverProvider
170
+ // excludes it), so its sidecar 429s died on this guard before the Anthropic arm below
171
+ // could ever be considered.
172
+ transportState.genericFailoverAccountId
173
+ && transportState.genericFailovers < GENERIC_OAUTH_MAX_FAILOVERS_PER_REQUEST
174
+ && isGenericOAuthFailoverEnabled(config, route.providerName)
175
+ ) {
176
+ // Intersection with the request's shared budget. The sidecar replay is dispatched by the
177
+ // web-search/image loop and never reaches `onSendsConsumed`, so this reservation is the
178
+ // charge; a refusal returns null and the caller keeps the real 429 it already has.
179
+ const hop = reserveCredentialHop(
180
+ "auth-recovery",
181
+ `${route.providerName}|${route.modelId}|sidecar-oauth-429`,
182
+ );
183
+ if (!hop.allowed) return null;
184
+ const nextAccountId = rotateGenericOAuthAccountOn429(
185
+ config,
186
+ route.providerName,
187
+ transportState.genericFailoverAccountId,
188
+ retryAfter,
189
+ );
190
+ if (!nextAccountId) {
191
+ hop.permit?.release();
192
+ return null;
193
+ }
194
+ try {
195
+ const snapshot = await failoverAccountSnapshot(route.providerName, nextAccountId);
196
+ transportState.genericFailovers += 1;
197
+ if (!await applyFailoverSnapshot(snapshot)) {
198
+ hop.permit?.release();
199
+ return null;
200
+ }
201
+ } catch {
202
+ hop.permit?.release();
203
+ return null;
204
+ }
205
+ hop.permit?.use();
206
+ } else if (
207
+ // Anthropic's pool is excluded from generic failover, so without this arm a 429 inside a
208
+ // web-search or image-bridge turn was terminal even with the pool fully enabled -- while
209
+ // the very same 429 on the main response path rotated.
210
+ transportState.anthropicPoolAccountId
211
+ && transportState.anthropicPoolFailovers < ANTHROPIC_POOL_MAX_FAILOVERS_PER_REQUEST
212
+ ) {
213
+ // Same intersection for the Anthropic roster: its own per-request bound still applies,
214
+ // and the shared budget decides whether this request may spend another send at all.
215
+ const hop = reserveCredentialHop(
216
+ "auth-recovery",
217
+ `${route.providerName}|${route.modelId}|sidecar-anthropic-429`,
218
+ );
219
+ if (!hop.allowed) return null;
220
+ const nextAccountId = rotateAnthropicAccountOn429(
221
+ config,
222
+ transportState.anthropicPoolAccountId,
223
+ retryAfter,
224
+ anthropicSessionKey,
225
+ Date.now(),
226
+ responseHeaders,
227
+ );
228
+ if (!nextAccountId) {
229
+ hop.permit?.release();
230
+ return null;
231
+ }
232
+ try {
233
+ // Deliberately NOT applyFailoverSnapshot: that helper exists to pair per-account routing
234
+ // metadata (Copilot origin, Antigravity project, Kiro context) with its bearer. Anthropic
235
+ // carries none, and getAnthropicPoolAccessToken is what enforces its fail-closed
236
+ // local-cli credential rule. Both existing Anthropic rotation sites apply the token the
237
+ // same way.
238
+ const admitted = await commitResolvedOAuthSelection(await getAnthropicPoolAccessSnapshot(nextAccountId));
239
+ if (!admitted) throw new Error("OAuth selection changed during recovery");
240
+ transportState.anthropicPoolAccountId = admitted.accountId;
241
+ transportState.anthropicPoolFailovers += 1;
242
+ route.provider = { ...route.provider, apiKey: admitted.accessToken };
243
+ logCtx.provider = formatAnthropicProviderForLog("anthropic", admitted.accountId, config);
244
+ } catch {
245
+ hop.permit?.release();
246
+ return null;
247
+ }
248
+ hop.permit?.use();
249
+ } else {
250
+ // No key pool, no generic OAuth roster, no Anthropic pool could produce a replacement
251
+ // credential. The 429 is terminal for this sidecar turn.
252
+ return null;
253
+ }
254
+ const rotatedAdapter = resolveSelectionAdapter(
255
+ resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire),
256
+ config.cacheRetention,
257
+ );
258
+ bindRouteReasoningReplayScope({
259
+ parsed,
260
+ providerName: route.providerName,
261
+ provider: route.provider,
262
+ adapterName: rotatedAdapter.name,
263
+ });
264
+ return rotatedAdapter;
265
+ };
266
+ if ((imgPlan || vidPlan) && canRunWebSearch) {
267
+ // Web search takes priority when both are active — the media bridge cannot run
268
+ // alongside runWithWebSearch. Surface a runtime signal so the user knows their
269
+ // configured video/image bridge was skipped for this turn, rather than silently
270
+ // dropping a paid capability.
271
+ if (vidPlan) console.warn("[videos] video bridge skipped: web search is active for this turn");
272
+ if (imgPlan) console.warn("[images] image bridge skipped: web search is active for this turn");
273
+ }
274
+ if ((imgPlan || vidPlan) && (!wsPlan || transportState.adapter.runTurn)) {
275
+ // The image bridge detects a hosted image_generation tool and requires streaming.
276
+ // The video bridge activates from config and injects a tool — it also needs streaming
277
+ // (the loop returns SSE). For video-only (no imgPlan) on a non-streaming request, skip
278
+ // the bridge entirely so enabling the feature doesn't break ordinary non-streaming traffic.
279
+ if (!parsed.stream) {
280
+ if (imgPlan) {
281
+ return formatErrorResponse(400, "invalid_request_error", "image bridge requires stream=true");
282
+ }
283
+ // Video-only: skip bridge for non-streaming requests
284
+ } else {
285
+ // Replace any pre-existing image_gen/video_gen aliases instead of appending duplicate wire names.
286
+ const priorTools = parsed.context.tools ?? [];
287
+ const bridgeTools = [...priorTools.filter(t => {
288
+ if (t.imageGeneration) return false;
289
+ if (t.videoGeneration) return false;
290
+ if (imgPlan && imgPlan.toolNames.has(t.name)) return false;
291
+ if (imgPlan && t.namespace && imgPlan.toolNames.has(namespacedToolName(t.namespace, t.name))) return false;
292
+ // Only strip unnamespaced video_gen aliases — a namespaced MCP video_gen is left alone.
293
+ if (vidPlan && !t.namespace && vidPlan.toolNames.has(t.name)) return false;
294
+ return true;
295
+ })];
296
+ const existingNames = new Set(bridgeTools.map(t => t.name));
297
+ if (imgPlan && !existingNames.has(IMAGE_GEN_TOOL_NAME)) bridgeTools.push(buildImageTool());
298
+ if (vidPlan && !existingNames.has(VIDEO_GEN_TOOL_NAME)) bridgeTools.push(buildVideoTool());
299
+ parsed.context.tools = bridgeTools;
300
+ // Hosted image_generation tool_choice / allowed_tools must target the synthetic function name.
301
+ // Gate on imgPlan — in a video-only turn buildImageTool() was never injected, so rewriting
302
+ // image_generation/image_gen aliases would add an undeclared tool that strict upstreams reject.
303
+ const tc = parsed.options.toolChoice;
304
+ if (imgPlan && tc && typeof tc === "object" && "allowedTools" in tc && Array.isArray(tc.allowedTools)) {
305
+ const mapped = tc.allowedTools.map(name =>
306
+ name === "image_generation" || name === "image_gen" || (imgPlan.toolNames.has(name) ?? false)
307
+ ? IMAGE_GEN_TOOL_NAME
308
+ : name,
309
+ );
310
+ parsed.options.toolChoice = { ...tc, allowedTools: [...new Set(mapped)] };
311
+ } else if (imgPlan && tc && typeof tc === "object" && "name" in tc && typeof tc.name === "string"
312
+ && (tc.name === "image_generation" || imgPlan.toolNames.has(tc.name))) {
313
+ parsed.options.toolChoice = { ...tc, name: IMAGE_GEN_TOOL_NAME };
314
+ }
315
+ const imageProviderFetch = providerFetch(
316
+ route.provider,
317
+ options.codexWsRuntimeIdentity,
318
+ { providerName: route.providerName, modelId: route.modelId },
319
+ );
320
+ const imgResponse = await runWithImageBridge({
321
+ parsed, adapter: transportState.adapter,
322
+ incomingMeta: { headers: requestState.selectedForwardHeaders, abortSignal: options.abortSignal, translatorBudget },
323
+ ...(imgPlan ? { plan: imgPlan } : {}),
324
+ ...(vidPlan ? { videoPlan: vidPlan } : {}),
325
+ forwardHeaders: requestState.selectedForwardHeaders,
326
+ onAttemptSend: (recovery?: AttemptRecoveryKind) =>
327
+ transportState.noteRoutedAttemptSend(logCtx.usageLogInputTokens, recovery),
328
+ abortSignal: options.abortSignal,
329
+ maxRounds: imgPlan && vidPlan
330
+ ? clampImageMaxRounds(Math.min(config.images?.maxRounds ?? 3, config.images?.videoMaxRounds ?? 2))
331
+ : imgPlan
332
+ ? clampImageMaxRounds(config.images?.maxRounds)
333
+ : clampImageMaxRounds(config.images?.videoMaxRounds ?? 2),
334
+ connectTimeoutMs: config.connectTimeoutMs ?? 200_000,
335
+ stallTimeoutSec: config.stallTimeoutSec,
336
+ waitForRequestSlot: imageProviderFetch.waitForPacing,
337
+ fetchImpl: imageProviderFetch.unpacedFetch ?? imageProviderFetch,
338
+ fetchForRequest: (request, iterParsed) => {
339
+ const fetch = providerFetch(route.provider, options.codexWsRuntimeIdentity, {
340
+ dispatchOverride: oauthDispatch(request, iterParsed),
341
+ providerName: route.providerName, modelId: route.modelId,
342
+ });
343
+ return fetch.unpacedFetch ?? fetch;
344
+ },
345
+ onRequestBuilt: request => {
346
+ recordAdapterReasoning(logCtx, request);
347
+ recordAdapterTier(logCtx, request);
348
+ },
349
+ ...(vidPlan?.timeoutMs ? { videoTimeoutMs: vidPlan.timeoutMs } : {}),
350
+ onUsage: usage => {
351
+ // Cursor may assign _cursorConversationId inside the image loop's first runTurn;
352
+ // backfill so Logs can filter/total that opening request (parity with the normal
353
+ // runTurn branch).
354
+ if (!logCtx.conversationId && parsed._cursorConversationId) {
355
+ logCtx.conversationId = normalizeLogConversationId(parsed._cursorConversationId);
356
+ }
357
+ transportState.bindKeyUsageFromBridge(usage);
358
+ },
359
+ on429: rotateSidecarProviderOn429,
360
+ retryOn429Policy: rateLimitRetryPolicyFor(route.provider),
361
+ ...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}),
362
+ ...(options.forceEmptyResponseId ? { forceEmptyResponseId: true } : {}),
363
+ onCompletedResponse: (response, providerState) => {
364
+ commitReasoningReplayServingRoute();
365
+ rememberKiroDeliveredFinalAnswer(transportState.adapter.name, response);
366
+ rememberResponseState(
367
+ parsed._rawBody,
368
+ response,
369
+ continuationStateForResponse(providerState),
370
+ responseStateOptions(adapterNeedsForcedContinuation(transportState.adapter.name)),
371
+ );
372
+ notifyResponseComplete(response);
373
+ },
374
+ });
375
+ if (imgResponse.body) {
376
+ const imgTurnAc = new AbortController();
377
+ imgTurnAc.signal.addEventListener("abort", cancelResponseCompletion, { once: true });
378
+ return new Response(trackStreamLifetime(imgResponse.body, imgTurnAc, undefined, options.turnAdmissionLease), {
379
+ status: imgResponse.status,
380
+ headers: imgResponse.headers,
381
+ });
382
+ }
383
+ return imgResponse;
384
+ } // end else (streaming bridge)
385
+ }
386
+
387
+ // Web-search sidecar: Codex enabled web_search but this is a routed (non-OpenAI) model that can't
388
+ // run it server-side. Expose web_search as a function tool and run searches via the gpt-mini sidecar
389
+ // through the ChatGPT passthrough, looping until the model answers. Otherwise take the normal path.
390
+ // Placed BEFORE the runTurn early-return for non-runTurn adapters so dual-tool turns dispatch
391
+ // through web-search instead of being swallowed. runTurn adapters never enter this branch.
392
+ if (canRunWebSearch && wsPlan) {
393
+ parsed.context.tools = [...(parsed.context.tools ?? []), buildWebSearchTool()];
394
+ // Resolve the mutable route at send time: a 429 rotation replaces route.provider, so retaining
395
+ // one pre-rotation providerFetch would keep the old credential and transport pin.
396
+ const routedProviderFetch = ((input: Parameters<typeof globalThis.fetch>[0], init?: RequestInit) =>
397
+ providerFetch(route.provider, options.codexWsRuntimeIdentity, {
398
+ providerName: route.providerName,
399
+ modelId: route.modelId,
400
+ })(input, init)) as typeof globalThis.fetch;
401
+ const wsResponse = await runWithWebSearch({
402
+ parsed, adapter: transportState.adapter,
403
+ fetchForRequest: (request, iterParsed) => providerFetch(route.provider, options.codexWsRuntimeIdentity, {
404
+ dispatchOverride: oauthDispatch(request, iterParsed),
405
+ providerName: route.providerName, modelId: route.modelId,
406
+ }),
407
+ incomingMeta: {
408
+ headers: requestState.selectedForwardHeaders,
409
+ abortSignal: options.abortSignal,
410
+ translatorBudget,
411
+ providerFetch: routedProviderFetch,
412
+ },
413
+ backend: wsPlan.backend,
414
+ forwardProvider: wsPlan.forwardSidecar?.provider,
415
+ anthropicSidecar: wsPlan.anthropicSidecar,
416
+ xaiSidecar: wsPlan.xaiSidecar,
417
+ geminiSidecar: wsPlan.geminiSidecar,
418
+ xaiSearchOptions: wsPlan.xaiSearchOptions,
419
+ // The exa key never rides the plan: read it from config at unpack time (L9).
420
+ ...(wsPlan.exaConfigured ? { exaApiKey: config.webSearchSidecar?.exaApiKey } : {}),
421
+ hostedTool: wsPlan.hostedTool,
422
+ selectedForwardHeaders: wsPlan.forwardSidecar?.headers ?? requestState.selectedForwardHeaders,
423
+ settings: wsPlan.settings,
424
+ maxSearches: wsPlan.maxSearches,
425
+ forceEmptyResponseId: true,
426
+ abortSignal: options.abortSignal,
427
+ ...(options.onFirstOutput ? { onFirstOutput: options.onFirstOutput } : {}),
428
+ onRequestBuilt: request => {
429
+ recordAdapterReasoning(logCtx, request);
430
+ recordAdapterTier(logCtx, request);
431
+ },
432
+ onAttemptSend: (recovery?: AttemptRecoveryKind) =>
433
+ transportState.noteRoutedAttemptSend(logCtx.usageLogInputTokens, recovery),
434
+ onUsage: usage => {
435
+ transportState.bindKeyUsageFromBridge(usage);
436
+ },
437
+ recordSidecarOutcome: wsPlan.forwardSidecar?.recordOutcome,
438
+ connectTimeoutMs: config.connectTimeoutMs ?? 200_000,
439
+ routedModelStallTimeoutMs: wsPlan.routedModelStallTimeoutMs,
440
+ stallTimeoutSec: wsPlan.stallTimeoutSec,
441
+ streamRoutedModelOutput: wsPlan.streamRoutedModelOutput,
442
+ on429: rotateSidecarProviderOn429,
443
+ retryOn429Policy: rateLimitRetryPolicyFor(route.provider),
444
+ onCompletedResponse: response => {
445
+ commitReasoningReplayServingRoute();
446
+ notifyResponseComplete(response);
447
+ },
448
+ });
449
+ // Register the sidecar stream as an active turn so drainAndShutdown waits for (or aborts)
450
+ // in-flight web-search turns instead of skipping them during graceful shutdown.
451
+ if (wsResponse.body) {
452
+ const wsTurnAc = new AbortController();
453
+ wsTurnAc.signal.addEventListener("abort", cancelResponseCompletion, { once: true });
454
+ return new Response(trackStreamLifetime(wsResponse.body, wsTurnAc, undefined, options.turnAdmissionLease), {
455
+ status: wsResponse.status,
456
+ headers: wsResponse.headers,
457
+ });
458
+ }
459
+ return wsResponse;
460
+ }
461
+
462
+ return undefined;
463
+ }
@@ -13,6 +13,9 @@ const PLAN_OR_COMPLETION_RE = /(?:\b(?:i(?:'|’)m going to|i will|i(?:'|’)ll|
13
13
  const WAITING_FOR_USER_RE = /(?:[??]\s*$|需要我|请(?:确认|选择|提供)|是否|要不要|可以吗|\b(?:do you want|should i|which file|please confirm|please provide)\b)/iu;
14
14
  const EXPLICIT_CONTINUE_RE = /^(?:继续|接着|往下|go on|continue|proceed|keep going)\s*[.!。!]?$/iu;
15
15
  const MAX_ANNOUNCEMENT_CHARS = 280;
16
+ const MAX_RETAINED_EVENTS = 1_024;
17
+ // JavaScript string code units, not UTF-8 bytes or a process-wide memory limit.
18
+ const MAX_RETAINED_CONTENT_CHARS = 64 * 1_024;
16
19
 
17
20
  export const TERMINAL_GUARD_NUDGE =
18
21
  "你刚才只描述了计划,没有执行任何工具。不要再次解释计划,现在立即调用必要工具执行用户任务。" +
@@ -195,7 +198,17 @@ function mergeUsage(first: OcxUsage | undefined, second: OcxUsage | undefined):
195
198
  };
196
199
  }
197
200
 
198
- /** Preserve normal terminals, but withhold one suspicious no-tool terminal for a bounded re-ask. */
201
+ /**
202
+ * Forward adapter events and re-ask only short, suspicious no-tool completions.
203
+ * Retention is bounded per turn; tools or overflow disable analysis without truncating output.
204
+ * Reported usage from completed legs survives a continuation-factory failure. Unreported
205
+ * usage stays absent, and source-iteration failures propagate to the caller's transport handler.
206
+ *
207
+ * @param options Initial stream, parsed history, and continuation callback. The caller owns
208
+ * provider opt-in; the continuation limit defaults to one and is clamped to at most two.
209
+ * @yields Unchanged content events, internal assistant boundaries, and terminal events with
210
+ * accumulated reported usage when available. Returning the iterator closes its active source.
211
+ */
199
212
  export async function* guardTerminalEventStream(options: GuardedEventStreamOptions): AsyncGenerator<AdapterEvent> {
200
213
  const maxContinuations = Math.max(0, Math.min(2, Math.floor(options.maxAutoContinuations ?? 1)));
201
214
  let parsed = options.parsed;
@@ -205,6 +218,10 @@ export async function* guardTerminalEventStream(options: GuardedEventStreamOptio
205
218
 
206
219
  while (true) {
207
220
  const seen: AdapterEvent[] = [];
221
+ let retainedContentChars = 0;
222
+ let retainedText = "";
223
+ let analysisEnabled = (options.adapterName === "anthropic" || options.adapterName === "openai-chat")
224
+ && continuations < maxContinuations;
208
225
  let terminalSeen = false;
209
226
  for await (const event of source) {
210
227
  // Liveness markers and tool argument fragments are passed through to the bridge, but
@@ -216,7 +233,7 @@ export async function* guardTerminalEventStream(options: GuardedEventStreamOptio
216
233
  }
217
234
  if (event.type === "done") {
218
235
  terminalSeen = true;
219
- const analysis = (options.adapterName === "anthropic" || options.adapterName === "openai-chat")
236
+ const analysis = analysisEnabled
220
237
  ? analyzeTerminalTurn(parsed, seen)
221
238
  : { decision: "pass" as const };
222
239
  const normalStop = event.stopReason !== "max_tokens" && event.stopReason !== "content_filter";
@@ -224,11 +241,17 @@ export async function* guardTerminalEventStream(options: GuardedEventStreamOptio
224
241
  accumulatedUsage = mergeUsage(accumulatedUsage, event.usage);
225
242
  continuations += 1;
226
243
  parsed = buildContinuationRequest(parsed, seen);
244
+ seen.length = 0;
245
+ retainedText = "";
227
246
  yield { type: "assistant_boundary" };
228
247
  try {
229
248
  source = await options.continuation(parsed);
230
249
  } catch (error) {
231
- yield { type: "error", message: error instanceof Error ? error.message : String(error) };
250
+ yield {
251
+ type: "error",
252
+ message: error instanceof Error ? error.message : String(error),
253
+ ...(accumulatedUsage ? { usage: accumulatedUsage } : {}),
254
+ };
232
255
  return;
233
256
  }
234
257
  break;
@@ -243,7 +266,45 @@ export async function* guardTerminalEventStream(options: GuardedEventStreamOptio
243
266
  yield usage ? { ...event, usage } : event;
244
267
  return;
245
268
  }
246
- seen.push(event);
269
+ if (analysisEnabled) {
270
+ if (event.type === "tool_call_start") {
271
+ // A real tool call permanently rules out a no-tool continuation for this turn.
272
+ analysisEnabled = false;
273
+ } else if (
274
+ event.type === "text_delta"
275
+ || event.type === "thinking_delta"
276
+ || event.type === "thinking_signature"
277
+ || event.type === "redacted_thinking"
278
+ ) {
279
+ const content = event.type === "text_delta"
280
+ ? event.text
281
+ : event.type === "thinking_delta"
282
+ ? event.thinking
283
+ : event.type === "thinking_signature"
284
+ ? event.signature
285
+ : event.data;
286
+ if (
287
+ seen.length >= MAX_RETAINED_EVENTS
288
+ || content.length > MAX_RETAINED_CONTENT_CHARS - retainedContentChars
289
+ ) {
290
+ analysisEnabled = false;
291
+ } else {
292
+ retainedContentChars += content.length;
293
+ if (event.type === "text_delta") retainedText += content;
294
+ // Match analyzeTerminalTurn's trimmed-text semantics, including split padding.
295
+ if (retainedText.trim().length > MAX_ANNOUNCEMENT_CHARS) {
296
+ analysisEnabled = false;
297
+ } else {
298
+ seen.push(event);
299
+ }
300
+ }
301
+ }
302
+ if (!analysisEnabled) {
303
+ // Never rebuild a continuation from truncated thinking or a partial turn.
304
+ seen.length = 0;
305
+ retainedText = "";
306
+ }
307
+ }
247
308
  yield event;
248
309
  }
249
310
  if (!terminalSeen) return;
@@ -2,7 +2,7 @@ import { collectResponsesToolGroups } from "../responses/tool-groups";
2
2
  import { relaySseWithPayloadRewrite, type SsePayloadRewrite } from "./sse-payload-rewrite";
3
3
  import type { TranslatorBudget } from "../lib/translator-budget";
4
4
 
5
- interface NamespacedTool {
5
+ export interface NamespacedTool {
6
6
  namespace: string;
7
7
  name: string;
8
8
  }
@@ -1,7 +1,7 @@
1
1
  import { collectAmbiguousDottedAliases, dottedAliasIsUnambiguous, wireToolInnerName } from "../responses/tool-name-aliases";
2
2
  import {
3
- CODE_MODE_EXEC_TOOL_NAME,
4
3
  dottedToolName,
4
+ NAMESPACED_BARE_ALIAS_EXCLUDED_NAMES,
5
5
  namespacedToolName,
6
6
  normalizeDeclaredToolName,
7
7
  } from "../types";
@@ -104,10 +104,14 @@ function addWireToolName(
104
104
  if (dottedAliasIsUnambiguous(namespace, name) && !ambiguousDottedAliases?.has(dotted)) {
105
105
  names.add(dotted);
106
106
  }
107
- // `exec` is the one name that also switches on nested-helper normalization, so a bare alias
108
- // for a namespaced MCP tool would silently authorize `exec_command`/`shell_command`/
109
- // `apply_patch`/`view_image` the request never declared. Every other inner name keeps the bare alias.
110
- if (name !== CODE_MODE_EXEC_TOOL_NAME) names.add(name);
107
+ // The code-mode helper spellings do not get a bare alias for a namespaced tool. Bare `exec`
108
+ // switches nested-helper normalization on for a catalog that never declared the shell; bare
109
+ // `exec_command`/`shell_command` switch it off for one that did; bare `write_stdin`/
110
+ // `apply_patch`/`view_image` are simply accepted as declared under a name the caller only ever
111
+ // authorized inside a namespace. This guard named only `exec` and let the other five through,
112
+ // which is the same drift the bridge-side copy had; both now read one list
113
+ // (src/types/tools.ts). Every other inner name keeps the bare alias.
114
+ if (!NAMESPACED_BARE_ALIAS_EXCLUDED_NAMES.has(name)) names.add(name);
111
115
  }
112
116
 
113
117
  /**