@mars-sea/dsh-commandcode-provider 0.11.4 → 0.11.6
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.
- package/CHANGELOG.md +24 -0
- package/lib/client.js +66 -18
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +171 -36
- package/lib/index.js +429 -73
- package/lib/index.js.map +1 -1
- package/package.json +51 -38
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.11.6] - 2026-09-19
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- **An account the user explicitly selected comes back on its own** ([#51](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/51)). With several accounts, pinning one in the **Active account** dropdown and then hitting a 429 on it moved every later request to `default` for the rest of the process, and re-selecting the account in the settings page could not bring it back — only restarting dsh did (the report's 被切换后需要重启 dsh 才能换回来). The mechanism: a 429 marks the key `unknown`, which only a `/alpha/billing/credits` window probe can clear, and the pool's only probe pass runs once EVERY account is marked — a state that never arrives while another account can still serve. The documented promise ("if it is exhausted, requests fall back to other accounts and return once its window resets") was therefore permanent in practice, and because that state is in-memory, a restart was the only way out. `resolveKey()` now re-checks the account the user explicitly asked for — the manual pin, or a model rule's routed slot — before serving the fallback: its window is probed at most once a minute, a window that is no longer exceeded drops the mark and restores the selection on that very request, and an exhausted one is stamped as a cooldown carrying the provider's own `resetAt`, after which the account returns by the clock with no further probe. Everything that keeps the steady state cheap is unchanged: a failed probe keeps the mark and is retried at most once per interval, a 401 stays out until the stored credential changes, the just-rejected key is still skipped during rotation, and an account nobody explicitly asked for is never probed while another one can serve. One visible side effect: a pinned 429 now shows a real cooldown end in the account usage card instead of an open-ended "rate limited". `tests/accounts.test.ts` pins the cleared-window revival, the clock-based return, the throttled failed probe, the `tried`/401 gates, the zero-probe steady state, and the routing case.
|
|
13
|
+
- **An account that cannot serve no longer traps the turn** ([#51](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/51), follow-up report). The same reporter added that it is "not only `default`": a request would land on an unusable account and fail with a bare `400`, frequently, while the other three accounts were fine. Three verified gaps made that possible. (1) Only 429/401 rotated, but the provider's account-scoped rejections are wider — the official CLI's own classifier (command-code@1.56.0: `parseWindowLimitError`, `isInsufficientCreditsRequestError`, `parseSpendCapError` and its terminal-marker list `["premium_credits_exhausted", "model_not_in_plan", "insufficient credits"]`) accepts the code `RATE_LIMITED` on ANY status plus `400 Insufficient credits`, `USAGE_EXCEEDED`, `PREMIUM_CREDITS_EXHAUSTED` and `MODEL_NOT_IN_PLAN`, all of which this adapter reported as a permanent `PROVIDER_HTTP_ERROR` — and a rejection that is neither marked nor rotated keeps the account "usable", so the pool hands it out again on the next request and the same error returns. (2) Even once classified, rotation could not have moved: the pool was asked to exclude only the just-rejected key and answered with the first *usable* account, which is the same key whenever the rejection marked nothing, so the adapter's already-tried guard ended the loop and the accounts behind it were never reached. (3) The revival probe read only `windowLimits.fiveHour`, so an account whose WEEKLY quota was spent but whose five-hour window was open was revived and handed out on every all-marked pass. The adapter now classifies account-scoped rejections with the CLI's rules and rotates on them, the whole tried set travels with the rejection so one request can walk a four-account pool, `error.rateLimit.reset` (seconds) becomes the mark's own cooldown instead of waiting for a probe, and `probeWindowLimits()` reads every window the endpoint publishes. A rejection that is NOT account-scoped — an invalid tool schema, a context overflow — still fails fast on the first attempt. Two deliberate refusals: an `unavailable` rejection is rotated past but never marked (the fact is about the model or the balance rather than the key, either can change without the key changing, and a `:free` model is still served by a credits-empty account), and when the tried set covers every account the pool still answers with its own all-exhausted diagnosis (earliest reset + the retry wait) only if they are all marked — an unmarked rejection is surfaced as itself rather than mislabelled as an exhausted window. `tests/adapter.test.ts` pins the credits/plan rotations, the `RATE_LIMITED`-code-with-reset path and the no-rotation control; `tests/accounts.test.ts` pins the pool walk, the all-tried diagnosis and the marks that must stay probe-eligible.
|
|
14
|
+
|
|
15
|
+
- **The model picker no longer changes with the serving account** ([#51](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/51), same follow-up report: 《卡片会切换》). The picker's **Hide out-of-plan models** filter asked the account that would serve *right now* — `loadBillingAccess()` resolved a single key through the pool — so with accounts on different plans the model list changed as rotation moved between them, and a model only ANOTHER account could run was hidden outright: no request could ever bring it back, because the filter had already removed it from the picker. The question is now asked of every account in the pool (`modelVisibleForAnyAccount()`, fed by a new optional `resolveAccountKeys` seam the plugin entry implements from `pool.resolvedAccounts()`), so a model is hidden only when NO account can run it — the honest question for a pool, and the one that composes with the entitlement rotation above: a model offered because some account includes it is served by that account after at most one rejected attempt. Nothing else about the filter changes: three requests per account per `BILLING_ACCESS_TTL_MS`, cached per key exactly like the single-account path; an account whose facts are unknown counts as "may include it" (the existing fail-open rule, so a transient billing failure still never hides a usable model); a host without the seam falls back to the serving key; and a tier no account reaches (Provider, in a Go+Pro pool) is still hidden. `tests/adapter.test.ts` pins the union through a per-account fetch stub, the still-hidden tier, the unknown-account fail-open and the per-account caching.
|
|
16
|
+
|
|
17
|
+
- **The plugin boots on DSH Desktop** ([#52](https://github.com/Mars-Sea/dsh-commandcode-provider/pull/52)). An older Desktop release validates the entire peer closure of every active plugin, and its runtime tree ships host packages only — so the three peers whose modules the Web frontend already hands to every client bundle through its `staticModules` seed table (`react`, `@deepseek-ai/dsh-client-ui-primitives`, `@deepseek-ai/dsh-client-ui-slots`) were a hard startup failure there (`desktop profile: … requires missing …`) even though no installed copy of them is ever needed: `lib/client.js` requires exactly those three, and the webview resolves them from the seed on every engine this plugin supports. They now carry `peerDependenciesMeta.optional: true`, and every other peer stays required, because a `dsh.client` row is resolved and served by the Host and a harness peer a fresh generation does not install is a bundle that cannot load. `react` also moved into `devDependencies`: npm and pnpm auto-install only NON-optional peers, and the test suite imports the React component tree at runtime, so without it the next `package-lock.json` refresh would have dropped `react` and turned `npm test` red for a reason that looks unrelated to this change. `tests/package.test.ts` pins the exact optional set, that each optional name is a declared peer, and that each stays a development package.
|
|
18
|
+
- **The issue #51 rotation work is hardened where review found it short.** Four paths could still misbehave, each now pinned by a test. (1) A pool whose untried accounts were all marked, while an account the request had ALREADY used sat unmarked — a credits/plan refusal marks nothing — threw a synthesized `INVALID_CREDENTIAL`/`RATE_LIMIT` that named the wrong cause, counted only the tried-filtered subset ("every configured Command Code account (1)…" out of two), and could hand dsh-llm-retry a ~15-minute wait for a permanent failure; `resolveKey()` now returns undefined in that case exactly as the all-tried branch already did, and builds its diagnosis from the pool's FULL account list (a second resolution that came back empty used to report "every configured account (0) was rejected with 401"). (2) A revival dropped the probe throttle's timestamp, so a pinned account whose window probe said "open" while the chat endpoint still answered 429 was probed once per request, forever — the stamp now survives the revival, and the interval's other half is reachable through a new `now()` clock seam instead of a real minute of waiting. (3) The account-scoped classifier read only spaced wordings, so a code-only `400 {"error":{"code":"INSUFFICIENT_CREDITS"}}` — the shape its own fixture implied — rotated nothing; the code list gained `INSUFFICIENT_CREDITS` and the wording check normalizes underscores, so both wire spellings classify. (4) An unroutable window limit on a non-429 status ended the turn as a dead `PROVIDER_HTTP_ERROR` even though the provider had named its reset; it is now a retryable `RATE_LIMIT` carrying that reset (a plain 429 keeps the `Retry-After` mapping it had). Also: an empty scalar `reasoning: ""` no longer shadows a populated `reasoning_details` array in the same chunk — the array layer already refused to let an empty `text` hide a `summary`, and the scalar layer now agrees, keeping the thinking issue #34's tool-loop continuity depends on. `tests/accounts.test.ts` and `tests/adapter.test.ts` pin all of it, including the 5xx negative control (a code-less 5xx rotates nobody) beside the deliberate exception (a 5xx carrying `RATE_LIMITED` is the window limit it says it is — the classifier reads the code before its status guard).
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **Synced with the official command-code@1.57.0 CLI** (2026-09-19, npm `latest`; no changelog entry yet — the page stops at 1.56.1, and 1.56.2 shipped without one). `COMMAND_CODE_CLI_VERSION` is now `1.57.0`. The CLI's only registry change is the new `z-ai/glm-5.3-flashx` ("GLM-5.3 FlashX": chatComplete, text + image, 1M context, selectable `low`/`high`/`max` efforts, Go and every tier above it, $0.37 in / $1.25 out / $0.075 cache read with no cache-write rate, no deal and no hourly schedule) — the picker now annotates it like its `z-ai/glm-5.3-flash` sibling, through a capability-snapshot entry and a vendored price row. The backend catalog changed under this same check: Meituan's LongCat 2.0 promo ended on 2026-09-19, so the public catalog renamed `meituan/LongCat-2.0:free` to the paid `meituan/LongCat-2.0` ($0.30 in / $1.20 out / $0.006 cache read, same 1M context, text-only, automatic reasoning), the pricing page dropped both the free slug and the 100%-off deal (its deal count fell 6 → 5 and its free count 4 → 3), and the docs list the new id — while the command-code@1.57.0 CLI registry still carries the retired `:free` id, one release behind the backend. The snapshot follows the catalog this plugin actually serves: the new id takes the old one's Go tier, its `FREE` deal is removed rather than left to badge a now-paid model, the automatic-reasoning set is renamed with it, and `longcat-2.0` joins the vendored price table — `node scripts/sync-model-prices.mjs` reports exactly those two rows as real drift. The public catalog serves 71 models, one more than at 1.56.0, with `gpt-6-astra` still a CLI/pricing/docs-only model as before. Static inspection of both bundles finds no transport drift: the `/alpha/*` endpoint set, the `/alpha/generate` converters (`toWireMessages`/`toWireTools`/`toWireToolOutput`) and the stream event vocabulary are byte-identical to 1.56.0, and the version rides every request as `x-command-code-version` as usual. The one 1.56.1 changelog item — "Send session id to BYOK hosts that require it" — adds the `x-opencode-session` header on the BYOK transport, host-gated to `opencode.ai`, so neither of this adapter's transports is affected, and 1.57.0's other additions (a `command-code/<version>` BYOK user agent, a renamed internal warning kind, the removal of the CLI's own image-vision consent prompt) are CLI-side as well. Everything else is re-verified unchanged: the per-model effort map beyond the addition, the subscription plan maps, every plan tier, the five active deals, and the peak/off-peak membership and schedule (still 01:00–04:00 and 06:00–10:00 UTC, Monday–Friday only). One upstream leftover was deliberately NOT snapshotted: the pricing page still embeds a `qwen-3.7-max-2x-usage` deal record for Qwen 3.7 Max, but its own Deals section (five entries) does not list it, its `expires` stamp (2026-06-22) is already in the past, and the vendored rate row already carries the discounted figures.
|
|
23
|
+
|
|
24
|
+
## [0.11.5] - 2026-09-18
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- **The composer session-cost figure survives dsh 0.1.6-alpha.2.** That release deleted the `data-composer-stats` marker from the shipped stats row (`StatsPills`) while leaving the row's markup otherwise identical, and the readout's pill lookup *required* that attribute — so there the lookup matched nothing on every sync and the amount silently stopped appearing beside the token pill (after three misses it logged a single console line), leaving only the usage dialog still carrying per-row prices. `STATS_ROOT` is now a PREFERENCE: when the attribute is absent the lookup falls back to the dock OUTLET (`[data-slot="conversation.composer.dock"]`), the `display:contents` div holding exactly that slot's entries — the shipped `stats` cell and ours — whose LAST dialog trigger is therefore still the token pill. The fallback is deliberately the outlet and never the outlet's PARENT: the same release moved the composer's context-usage ring out of the card into a new footer `div` beside the dock, and that ring's trigger is itself a `button[aria-haspopup="dialog"]` rendered AFTER the dock, so the parent-scoped lookup this previously depended on would have appended the cost to the context meter instead. `tests/session-cost-display.test.ts` now builds both the unmarked 0.1.6-alpha.2 row and a footer-sibling meter, and fails if the cost stops landing on the token pill or the meter becomes the host; the earlier suite constructed its own marked row and accepted a missing anchor as a no-op, which is exactly why the deletion could go dark with every check green.
|
|
28
|
+
- **A second composer's usage dialog can no longer be priced with this session's figures.** dsh 0.1.6-alpha.2 mounts an embedded Conversation in the sidebar, so two composers — each with its own session, dock and dialog — can be live and open at once. The dialog is portaled onto `body` and carries no id or `aria-controls` tying it back to its trigger, so the document-level lookup was exact only while a single composer was guaranteed: the per-row shape confirmation compares against THIS entry's counts, so a dialog belonging to the other session that happened to match would have been decorated with the wrong figures. `resolveDialog()` now decorates only while the document holds EXACTLY ONE `[data-session-stats-usage]` and otherwise declines — the same rule the shape check already follows one level down. Pinned by two tests: with two dialogs open neither is priced, and the survivor is priced once the other composer's dialog unmounts.
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- **dsh 0.1.6-alpha.2 is a declared-compatible engine.** The engine's other changes need no adapter work, and each was verified against the installed release rather than inferred: the Typert strict result codec's member swap (`schema` → the lazy `create()` factory) was already covered by 0.11.4's two-member codec — both the Host and Client contributions register on that release's real registry, which is worth restating because 0.1.6-alpha.2 is the first PUBLISHED engine to require `create()`; `npm run test:engine` passes against it (the bundle links and evaluates there, and the request-image policy is the durable one); every slot this plugin registers into or re-states (`conversation.composer.dock`, `main`, `sidebar.footer.action`, `settings.section`, `settings.models.provider-card`) has an unchanged declaration and the dock still supplies `sessionId`/`useProjection`/`useChat`; the platform seed table, the host packages this plugin imports and the sidebar/footer class stems are unchanged; and the release's new plugin-management page asks nothing of a plugin whose registrations are already cordis effects — which is also what its runtime unload needs. The one genuinely new surface it exposes, a sidebar-mounted embedded Conversation, is what the second fix above defends against.
|
|
32
|
+
|
|
9
33
|
## [0.11.4] - 2026-09-17
|
|
10
34
|
|
|
11
35
|
### Fixed
|
package/lib/client.js
CHANGED
|
@@ -2157,7 +2157,7 @@ window.__ModuleLoader__.load({
|
|
|
2157
2157
|
}
|
|
2158
2158
|
//#endregion
|
|
2159
2159
|
//#region package.json
|
|
2160
|
-
var version = "0.11.
|
|
2160
|
+
var version = "0.11.6";
|
|
2161
2161
|
var repository = {
|
|
2162
2162
|
"type": "git",
|
|
2163
2163
|
"url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
|
|
@@ -5205,12 +5205,25 @@ window.__ModuleLoader__.load({
|
|
|
5205
5205
|
}
|
|
5206
5206
|
//#endregion
|
|
5207
5207
|
//#region src/client/session-cost-display.ts
|
|
5208
|
-
/**
|
|
5208
|
+
/**
|
|
5209
|
+
* The shipped composer stats row (dsh-client-ui-chat `StatsPills`).
|
|
5210
|
+
*
|
|
5211
|
+
* A PREFERENCE, not a requirement: 0.1.6-alpha.2 dropped this attribute from the
|
|
5212
|
+
* row's root while leaving the rest of the markup identical, so a lookup that
|
|
5213
|
+
* insisted on it would silently stop finding the row on that engine. When it is
|
|
5214
|
+
* absent the search falls back to the dock OUTLET — deliberately the outlet and
|
|
5215
|
+
* never the outlet's parent, because from 0.1.6-alpha.2 the parent is the new
|
|
5216
|
+
* composer footer, which holds the `ContextMeter` as well, and that meter's
|
|
5217
|
+
* trigger is itself a `button[aria-haspopup="dialog"]` rendered AFTER the dock.
|
|
5218
|
+
* A parent-scoped "last trigger wins" would therefore append the cost to the
|
|
5219
|
+
* context ring instead of the token pill.
|
|
5220
|
+
*/
|
|
5209
5221
|
const STATS_ROOT = "[data-composer-stats]";
|
|
5210
5222
|
/**
|
|
5211
|
-
* The shipped token-usage dialog's `<dl>`. Unique
|
|
5212
|
-
*
|
|
5213
|
-
*
|
|
5223
|
+
* The shipped token-usage dialog's `<dl>`. Unique WITHIN one `StatsPills`
|
|
5224
|
+
* render: the per-message turn-usage panel has its own dialog with different
|
|
5225
|
+
* markup, so a single composer's usage dialog is unambiguous. It is not unique
|
|
5226
|
+
* across the document once two composers are live — see {@link resolveDialog}.
|
|
5214
5227
|
*/
|
|
5215
5228
|
const USAGE_DIALOG = "[data-session-stats-usage]";
|
|
5216
5229
|
/**
|
|
@@ -5311,7 +5324,7 @@ window.__ModuleLoader__.load({
|
|
|
5311
5324
|
const button = this.resolvePillButton();
|
|
5312
5325
|
if (button === null) {
|
|
5313
5326
|
this.pillMisses += 1;
|
|
5314
|
-
if (this.pillMisses === MISSES_BEFORE_WARNING) console.warn(
|
|
5327
|
+
if (this.pillMisses === MISSES_BEFORE_WARNING) console.warn("[dsh-commandcode-provider] no shipped token pill in this composer's dock outlet to append the session cost to; the figure stays in the usage dialog only");
|
|
5315
5328
|
return;
|
|
5316
5329
|
}
|
|
5317
5330
|
this.pillMisses = 0;
|
|
@@ -5335,11 +5348,24 @@ window.__ModuleLoader__.load({
|
|
|
5335
5348
|
if (this.pillA11y.textContent !== described) this.pillA11y.textContent = described;
|
|
5336
5349
|
}
|
|
5337
5350
|
}
|
|
5338
|
-
/**
|
|
5351
|
+
/**
|
|
5352
|
+
* The shipped token pill, scoped to this entry's own composer.
|
|
5353
|
+
*
|
|
5354
|
+
* The scope is the dock OUTLET (`display: contents`, one per slot), which holds
|
|
5355
|
+
* exactly that slot's entries — the shipped `stats` cell and ours, in their
|
|
5356
|
+
* registration order. {@link STATS_ROOT} narrows that to the stats row on the
|
|
5357
|
+
* engines that still mark it; on the engines that do not, the outlet is
|
|
5358
|
+
* already the narrowest correct container, because the composer footer's
|
|
5359
|
+
* `ContextMeter` sits BESIDE the outlet rather than inside it.
|
|
5360
|
+
*
|
|
5361
|
+
* A missing scope is "no pill", never a document-wide search: the outlet is
|
|
5362
|
+
* what makes this lookup per-composer, and a second composer can be live at
|
|
5363
|
+
* once (0.1.6-alpha.2 mounts an embedded Conversation in the sidebar).
|
|
5364
|
+
*/
|
|
5339
5365
|
resolvePillButton() {
|
|
5340
|
-
const
|
|
5341
|
-
if (
|
|
5342
|
-
const triggers =
|
|
5366
|
+
const scope = this.scope();
|
|
5367
|
+
if (scope === null) return null;
|
|
5368
|
+
const triggers = (scope.querySelector(STATS_ROOT) ?? scope).querySelectorAll(DIALOG_TRIGGER);
|
|
5343
5369
|
return triggers.length === 0 ? null : triggers[triggers.length - 1] ?? null;
|
|
5344
5370
|
}
|
|
5345
5371
|
/**
|
|
@@ -5462,18 +5488,30 @@ window.__ModuleLoader__.load({
|
|
|
5462
5488
|
this.dialogPrices.delete(dd);
|
|
5463
5489
|
}
|
|
5464
5490
|
/**
|
|
5465
|
-
* The shipped usage dialog, or null while it is closed.
|
|
5491
|
+
* The shipped usage dialog, or null while it is closed (or ambiguous).
|
|
5466
5492
|
*
|
|
5467
5493
|
* The dialog is portaled onto `body`, so unlike the pill it cannot be scoped
|
|
5468
|
-
* from this entry
|
|
5469
|
-
*
|
|
5470
|
-
*
|
|
5471
|
-
*
|
|
5494
|
+
* from this entry, and the attribute is unique per DIALOG rather than per
|
|
5495
|
+
* document. While one composer was guaranteed, a document-level lookup was
|
|
5496
|
+
* exact. 0.1.6-alpha.2 ends that guarantee: it mounts an embedded Conversation
|
|
5497
|
+
* in the sidebar, so a second composer — with its own session, its own dock
|
|
5498
|
+
* and its own dialog — can be live and open at the same time.
|
|
5499
|
+
*
|
|
5500
|
+
* There is no DOM link from a dialog back to its trigger (the portaled panel
|
|
5501
|
+
* carries no id and no `aria-controls`), so with two dialogs open document
|
|
5502
|
+
* order says nothing about ownership. The shape confirmation in
|
|
5503
|
+
* {@link dialogShapeMatches} would usually reject the stranger, but it compares
|
|
5504
|
+
* against THIS entry's counts, so a coincidentally matching dialog would be
|
|
5505
|
+
* priced with another session's figures. Ambiguity is therefore answered by
|
|
5506
|
+
* declining to decorate at all — the same rule the shape check already
|
|
5507
|
+
* follows, applied one level up: a dialog we cannot prove is ours is left
|
|
5508
|
+
* alone.
|
|
5472
5509
|
*/
|
|
5473
5510
|
resolveDialog() {
|
|
5474
5511
|
if (this.dialogHost?.isConnected === true) return this.dialogHost;
|
|
5475
5512
|
this.clearDialog();
|
|
5476
|
-
|
|
5513
|
+
const dialogs = this.doc.querySelectorAll(USAGE_DIALOG);
|
|
5514
|
+
return dialogs.length === 1 ? dialogs[0] ?? null : null;
|
|
5477
5515
|
}
|
|
5478
5516
|
/** Give the dialog back: every price removed, every hidden row restored. */
|
|
5479
5517
|
clearDialog() {
|
|
@@ -5544,7 +5582,17 @@ window.__ModuleLoader__.load({
|
|
|
5544
5582
|
}
|
|
5545
5583
|
//#endregion
|
|
5546
5584
|
//#region src/client/session-cost-view.tsx
|
|
5547
|
-
/**
|
|
5585
|
+
/**
|
|
5586
|
+
* The dock outlet this entry renders inside, i.e. what it scopes itself from.
|
|
5587
|
+
*
|
|
5588
|
+
* The OUTLET itself, not its parent. The outlet is one `display:contents` div
|
|
5589
|
+
* holding exactly this slot's entries (the shipped `stats` cell and ours), so it
|
|
5590
|
+
* is the narrowest container that is still per-composer. Its parent is NOT:
|
|
5591
|
+
* from 0.1.6-alpha.2 the composer stacks the dock inside a footer that also
|
|
5592
|
+
* holds the `ContextMeter`, whose trigger is another
|
|
5593
|
+
* `button[aria-haspopup="dialog"]` rendered after the dock — scoping to the
|
|
5594
|
+
* parent would let the cost land on the context ring.
|
|
5595
|
+
*/
|
|
5548
5596
|
const DOCK_ANCHOR = "[data-slot=\"conversation.composer.dock\"]";
|
|
5549
5597
|
/** Module-level constant so the marker's `style` prop never diffs. */
|
|
5550
5598
|
const HIDDEN_STYLE = { display: "none" };
|
|
@@ -5582,7 +5630,7 @@ window.__ModuleLoader__.load({
|
|
|
5582
5630
|
if (typeof document === "undefined") return;
|
|
5583
5631
|
const display = new SessionCostDisplay({
|
|
5584
5632
|
doc: document,
|
|
5585
|
-
scope: () => markerRef.current?.closest(DOCK_ANCHOR)
|
|
5633
|
+
scope: () => markerRef.current?.closest(DOCK_ANCHOR) ?? null
|
|
5586
5634
|
});
|
|
5587
5635
|
displayRef.current = display;
|
|
5588
5636
|
display.start();
|