@mars-sea/dsh-commandcode-provider 0.11.2 → 0.11.4

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 CHANGED
@@ -6,6 +6,23 @@ 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.4] - 2026-09-17
10
+
11
+ ### Fixed
12
+ - **The Command Code Remotes keep registering on the next Harness generation** ([#49](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/49)). The Typert protocol changed the strict result codec's shape inside the same `mode: 'strict'` tag: every released engine (`0.1.2-rc.1` … `0.1.6-alpha.1`, all 21 published versions) declares `schema: TypertSchema` and refuses registration with `strict codec has no parse() method` without it, while master after `e459e3263` replaced that member with the lazy factory `create: () => TypertSchema` and refuses with `strict codec has no create() factory`. All six Command Code Remotes (`report`, `models`, `prices`, `loginBegin`, `loginStatus`, `loginCancel`) come from one descriptor factory that carried `schema` alone, so on that newer generation none of them registers: the settings page's account card, the plans & quota panel, the price table and the browser login all lose their Host data. `makeRemoteDescriptor()` now carries both members, where `create: () => schema` names the same hand-rolled validator rather than a second copy, so the two generations can never validate against different rules. This is a version-agnostic fix instead of a runtime probe: neither generation inspects the member it does not know, and the member the running engine ignores is inert — which matters because a single-member codec is broken in either direction, and the one-line patch the issue proposed (`schema` → `create`) would have fixed unreleased master by breaking every install that exists today. `tests/wire-shared.test.ts` pins it by running both generations' real validators over every shipped descriptor, with negative controls proving that a codec carrying only one member is rejected by the other generation. The helper is inlined into both `lib/index.js` and `lib/client.js`, so a fix is dead until the bundle is rebuilt — hence the rebuilt bundles in this release.
13
+
14
+ ## [0.11.3] - 2026-09-16
15
+
16
+ ### Added
17
+ - **Thinking text is also read from `reasoning_details`.** The gateway does not normalize the reasoning field across model families, and the difference is per family rather than global: DeepSeek answers with the scalar `reasoning` **plus** an OpenRouter-shaped `reasoning_details` array (`[{ type: 'reasoning.text', text, format, index }]`), while GLM, Qwen and Kimi answer with the DeepSeek-native `reasoning_content` (all measured against the live API on 2026-09-16, alongside the catalog sweep below). The adapter already read the first two spellings, so nothing was broken; the array is now a third fallback, so a family whose thinking arrives only that way is not silently dropped. Entries are read by their `text` member rather than filtered on `type === 'reasoning.text'`: the sibling shapes in that protocol carry `summary` or an encrypted blob instead of `text`, so a plain read already ignores them, while a type filter would drop a text-bearing variant added later — and losing thinking is the failure this exists to prevent.
18
+ - **`transportMaxRetries`: how many transport failures one request absorbs before the failure surfaces** (default 5, 0–50; Settings → Command Code → Advanced). It exists because the route's retry policy cannot express this: `providerRetryPolicy()` is captured once per route, so `TRANSPORT` had to share the near-unbounded window that `RATE_LIMIT`/`SERVER` need, and a connection that cannot be established is not a failure that asks to be waited out. The retries still run on the policy's own cadence (500 ms doubling), so the default spends ~15.5 s (0.5+1+2+4+8) on a blip before reporting it.
19
+
20
+ ### Fixed
21
+ - **Claude models work again on Provider API accounts** ([#46](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/46)). The Provider API serves the whole Claude family only through `/provider/v1/messages`: posted to `/provider/v1/chat/completions`, every catalog Claude id — all eight, verified by posting all 69 models to that endpoint on 2026-09-16 — answers `400 Model "<id>" must be called via /provider/v1/messages (Anthropic Messages shape)`. The adapter's only pre-stream fallback was the Go-plan `upgrade_required` 403, so the rejection surfaced to the user as `PROVIDER_HTTP_ERROR`; and because the non-Go tiers are exactly the ones `resolveProtocol()` sends to the Provider API, the accounts ENTITLED to Claude were the ones that could never use it. Those models now take the CLI transport, which routes every one of them (a lower-plan key gets the ordinary `MODEL_NOT_IN_PLAN` 403 there, never a routing error), so no Messages transport is needed in the adapter. The decision is deliberately NOT written to `protocolCache`, which is keyed by API key alone: remembering it would pin the whole ACCOUNT to the CLI transport and drag DeepSeek, GLM and Qwen — all correctly served by the Provider API — along with it, which is why `rememberProtocol()` is not called on that path. `MESSAGES_ONLY_MODELS` in `src/capabilities.ts` records the measured set, and `requiresMessagesEndpoint()` also treats any `claude-*` id as Messages-only, so a Claude model shipping after this release is routed correctly by an un-updated plugin instead of hard-failing with the 400 above. The same sweep also confirmed the neighbouring report is a non-issue for this plugin: the reasoning-replay and thinking-display paths read every field spelling the gateway emits (see Added).
22
+ - **A transport failure no longer retries toward the 1000-attempt budget** ([#39](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/39), second report). The report after 0.11.1 showed no context-window rejection at all: at ~710k tokens the adapter failed with undici's own `Connect Timeout Error ... timeout: 10000ms` (its default connect timeout — this plugin's `requestTimeoutMs` is 60 s and the connection phase never runs against the body upload) and, on another attempt, `read ECONNRESET` against `/alpha/generate`. Both are `TRANSPORT`, which the retry policy whitelists, so each entered the same 1000-attempt loop the 0.11.2 context-window fix left untouched: with waits doubling from 500 ms, the wait before attempt 11 alone is 512 s and the cumulative wait before it is ~500 s — the `模型请求重试已取消(11/1000)· 482s` row in the report is that 512 s countdown, 30 s in. `TRANSPORT` failures now consume a bounded budget on the `agent/request-error` hook: the first five are retried (~15.5 s of grace — 0.5+1+2+4+8 s waits, and the reporter's own first screenshot, a single `read ECONNRESET` that recovered on the next attempt, stays invisible), and the sixth surfaces the turn with a diagnosis that says what it is (connectivity, *not* a context-window or request-size rejection) and what usually fixes it. The check runs before the budget, so a cancelled turn is never retried and never spends a slot; the budget resets at each new step (one step *is* one model request, so the cap is per request and every step of a turn gets its own grace); and the accounting is keyed per agent, so one session's outage cannot disable another's retries. Everything else — the rate-limit window, a gateway 520, account rotation, the 413 image step-down — is unchanged. The report's "long context" correlation is a red herring, and this change does not depend on ruling it in or out: verified against the live endpoint, request bodies of 1/8/30 MB all returned the provider's own `401` in 1.9/2.7/5.7 s. `tests/transport-retry.test.ts` pins the budget, the per-agent isolation, the cancellation ordering and the message.
23
+ - **A message with no `source` tag no longer kills the whole stream** ([#47](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/47)). `messagesToCC` read `message.source.kind` at four sites to tell a tool result from a plain user message. `source` is producer-supplied and nothing validates it — dsh-llm's type declares it required, but the runtime never checks it and every adapter shipped before this one ignored the field entirely (`dsh-llm-deepseek` never reads it) — so a third-party plugin that assembles its own history can omit it and keep working everywhere else. This adapter, the first to actually read the field, was therefore the first to crash on such a message: `TypeError: Cannot read properties of undefined (reading 'kind')` in the serialization phase, 0–8 ms in, before a request was sent, with an error naming neither the message nor the producer. Both transports now classify a message through one predicate (`isToolResultMessage()`): a PRESENT tag stays authoritative, while an ABSENT one falls back to the content shape — a `user` message whose first block is a `tool-result` is still a tool result, because `pairedToolCalls()` counts such a block from ANY message and dropping it at emission would leave the assistant's tool call unanswered, which the gateway rejects. An untagged plain user message (the reported case, from dsh-mneme's memory pipeline) is now simply converted as the user message it is. Pinned by three tests in `tests/adapter.test.ts`, each run through both transports: the untagged user message, the untagged tool result, and the authoritative tag.
24
+ - **The plugin's stylesheet no longer reaches into other components' markup** ([#48](https://github.com/Mars-Sea/dsh-commandcode-provider/issues/48)). Installing the plugin stacked the `ask_user_question` dialog's side-by-side buttons vertically. The cause was one rule in the panel stylesheet, `[class*="_footerActions"]{flex-direction:column}`, written for the sidebar's footer-action container so this card's full-width line and ui-cordis's chip stack instead of overflowing the shell's flex ROW. `footerActions` is not a stem the sidebar owns alone: `@deepseek-ai/dsh-client-ui-user-questions` renders that dialog's button row as `Mbwy4a_footerActions`, so the unanchored attribute-substring selector matched it too and overrode the row's `display:flex`. The rule is now anchored to the sidebar's own foot area (`[class*="_footArea"] [class*="_footerActions"]`), a stem declared by dsh-client-ui-sidebar alone — audited across every client bundle of the 0.1.6-alpha.1 engine, together with our own `cc-`/`ccp-` class names, which collide with no engine class — so the sidebar layout fix is unchanged and no other component can be reached. The settings-page stylesheet moved to `src/client/page-styles.ts` so both sheets can be audited without importing the React tree, and `tests/styles.test.ts` now enforces the containment in two layers: a structural audit of every selector compound in both sheets, and a simulation against the real foreign class stems proving the dialog's row is no longer matched while the sidebar's still is.
25
+
9
26
  ## [0.11.2] - 2026-09-15
10
27
 
11
28
  ### Added
package/lib/client.js CHANGED
@@ -157,6 +157,10 @@ window.__ModuleLoader__.load({
157
157
  min: 1,
158
158
  max: MAX_TIMEOUT_MS
159
159
  }),
160
+ numberField$1("transportMaxRetries", {
161
+ min: 0,
162
+ max: 50
163
+ }),
160
164
  booleanField$1("filterModelsByPlan"),
161
165
  booleanField$1("webSearch"),
162
166
  booleanField$1("showSidebarQuota"),
@@ -310,6 +314,7 @@ window.__ModuleLoader__.load({
310
314
  defaultWorkingDir: this.defaultWorkingDir,
311
315
  requestTimeoutMs: this.field("requestTimeoutMs"),
312
316
  streamIdleTimeoutMs: this.field("streamIdleTimeoutMs"),
317
+ transportMaxRetries: this.field("transportMaxRetries"),
313
318
  filterModelsByPlan: this.field("filterModelsByPlan"),
314
319
  webSearch: this.field("webSearch"),
315
320
  showSidebarQuota: this.field("showSidebarQuota"),
@@ -1673,6 +1678,12 @@ window.__ModuleLoader__.load({
1673
1678
  * only its own facts.
1674
1679
  */
1675
1680
  function makeRemoteDescriptor(endpoint, method, typeSymbol, schema) {
1681
+ const result = {
1682
+ mode: "strict",
1683
+ typeSymbol,
1684
+ schema,
1685
+ create: () => schema
1686
+ };
1676
1687
  return {
1677
1688
  id: `${REMOTE_PACKAGE}#${endpoint}`,
1678
1689
  service: REMOTE_SERVICE,
@@ -1680,11 +1691,7 @@ window.__ModuleLoader__.load({
1680
1691
  method,
1681
1692
  invocation: { kind: "direct" },
1682
1693
  parameters: [],
1683
- result: {
1684
- mode: "strict",
1685
- typeSymbol,
1686
- schema
1687
- }
1694
+ result
1688
1695
  };
1689
1696
  }
1690
1697
  //#endregion
@@ -2150,7 +2157,7 @@ window.__ModuleLoader__.load({
2150
2157
  }
2151
2158
  //#endregion
2152
2159
  //#region package.json
2153
- var version = "0.11.2";
2160
+ var version = "0.11.4";
2154
2161
  var repository = {
2155
2162
  "type": "git",
2156
2163
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -2349,6 +2356,7 @@ window.__ModuleLoader__.load({
2349
2356
  "workingDir",
2350
2357
  "requestTimeoutMs",
2351
2358
  "streamIdleTimeoutMs",
2359
+ "transportMaxRetries",
2352
2360
  "filterModelsByPlan",
2353
2361
  "webSearch",
2354
2362
  "showSidebarQuota"
@@ -2494,6 +2502,17 @@ window.__ModuleLoader__.load({
2494
2502
  onReset: () => onReset("streamIdleTimeoutMs"),
2495
2503
  t
2496
2504
  }),
2505
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2506
+ id: "cc-transport-max-retries",
2507
+ label: t("transportMaxRetries"),
2508
+ hint: t("transportMaxRetriesHint"),
2509
+ state: state.transportMaxRetries,
2510
+ disabled,
2511
+ numeric: true,
2512
+ onEdit: (text) => onEdit("transportMaxRetries", text),
2513
+ onReset: () => onReset("transportMaxRetries"),
2514
+ t
2515
+ }),
2497
2516
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
2498
2517
  id: "cc-filter-models-by-plan",
2499
2518
  label: t("filterModelsByPlan"),
@@ -5582,6 +5601,165 @@ window.__ModuleLoader__.load({
5582
5601
  "data-ccp-session-cost-anchor": ""
5583
5602
  });
5584
5603
  }
5604
+ //#endregion
5605
+ //#region src/client/page-styles.ts
5606
+ /**
5607
+ * Stylesheet for the Command Code settings page — and the Models-page provider
5608
+ * card, which shares its `cc-` classes.
5609
+ *
5610
+ * Returned as a string rather than injected here so the modules stay free of
5611
+ * DOM side effects at import time: `./index.ts` installs it once, keyed by
5612
+ * `PAGE_CSS_ID`. It is a module of its own rather than a literal inside the
5613
+ * client entry so `tests/styles.test.ts` can audit every rule without importing
5614
+ * the React tree (which pulls `*.module.css` through the primitives package and
5615
+ * needs a loader registered first).
5616
+ *
5617
+ * Every colour comes from a harness theme alias with a neutral fallback, so the
5618
+ * page follows the active theme (light/dark and any brand pack) without
5619
+ * hardcoded values. Classes are `cc-` prefixed to stay clear of the panel's
5620
+ * `ccp-` set.
5621
+ *
5622
+ * @module dsh-commandcode-provider/client/page-styles
5623
+ */
5624
+ /**
5625
+ * Stylesheet id (the `data-plugin-css` value that makes injection idempotent).
5626
+ *
5627
+ * The package prefix must match the one the panel stylesheet uses
5628
+ * (`PANEL_CSS_ID` in `./panel-styles.ts`) and this package's real name: an id is
5629
+ * the injection's identity, so a stale fork prefix would let a second copy of
5630
+ * the plugin inject the same rules twice and would misreport the owner in the
5631
+ * DOM. The two ids must also stay distinct from each other — they key two
5632
+ * separate style tags, and a shared id would make the second injection a no-op
5633
+ * that silently drops one stylesheet.
5634
+ */
5635
+ const PAGE_CSS_ID = "@mars-sea/dsh-commandcode-provider/CommandCodeSettingsPage.module.css";
5636
+ /** The settings-page stylesheet. */
5637
+ const PAGE_CSS = `
5638
+ .cc-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}
5639
+ .cc-title{margin:0;font-size:18px;font-weight:600}
5640
+ .cc-intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:1.5}
5641
+ .cc-readOnly{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
5642
+ .cc-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:4px 16px}
5643
+ .cc-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
5644
+ .cc-field+.cc-field{border-top:1px solid var(--dsw-alias-border-l2)}
5645
+ .cc-fieldHead{align-items:center;gap:8px;display:flex}
5646
+ .cc-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}
5647
+ .cc-badges{align-items:center;gap:8px;display:inline-flex}
5648
+ .cc-badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
5649
+ .cc-badgeMuted{white-space:nowrap;color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px}
5650
+ .cc-reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}
5651
+ .cc-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
5652
+ .cc-reset:disabled{cursor:default;opacity:.5}
5653
+ .cc-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}
5654
+ .cc-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
5655
+ .cc-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
5656
+ /* The routing-rule model multi-select: a button trigger that opens an
5657
+ * anchored Menu of checkbox rows. The trigger mirrors .cc-input sizing so it
5658
+ * sits flush with the sibling account select. */
5659
+ .cc-ruleTrigger{align-items:center;gap:8px;display:flex;width:100%;text-align:left;cursor:pointer}
5660
+ .cc-ruleTrigger:disabled{cursor:default}
5661
+ .cc-ruleTriggerText{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
5662
+ .cc-ruleCaret{flex-shrink:0;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);width:6px;height:6px;margin-right:4px;margin-bottom:2px;transform:rotate(45deg)}
5663
+ .cc-checkRow{align-items:center;gap:8px;display:inline-flex;min-width:0}
5664
+ .cc-checkRow:hover{cursor:pointer}
5665
+ .cc-check{appearance:none;flex-shrink:0;width:15px;height:15px;margin:0;border:1px solid var(--dsw-alias-border-l2);border-radius:4px;background:var(--dsw-alias-bg-layer-1);position:relative}
5666
+ .cc-check:checked{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}
5667
+ .cc-check:checked::after{content:'';position:absolute;top:2px;left:5px;width:3px;height:7px;border:solid #fff;border-width:0 1.5px 1.5px 0;transform:rotate(45deg)}
5668
+ .cc-checkName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
5669
+ /* The model multi-select search box: stacked under the trigger while the
5670
+ * dropdown is open, same input sizing so the pair reads as one control. The
5671
+ * box lives inside the Menu anchor (which renders inside the Menu's root
5672
+ * span), so focusing/typing it never trips the Menu's outside-click close. */
5673
+ .cc-modelSelectAnchor{flex-direction:column;gap:6px;display:flex;width:100%}
5674
+ .cc-modelSearch{width:100%}
5675
+ .cc-modelSearch::-webkit-search-cancel-button{cursor:pointer}
5676
+ /* Selects need their own treatment to sit flush with the text inputs:
5677
+ * the UA stylesheet renders <select> border-box (34px total vs the inputs'
5678
+ * 36px) and forces its own menulist text metrics, so drop the native
5679
+ * chrome entirely (appearance:none), restore content-box so the outer box
5680
+ * matches the inputs again, and draw the chevron ourselves. Longhand
5681
+ * background-* only — the shorthand would reset .cc-input's background. */
5682
+ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box-sizing:content-box;padding-right:32px;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='7' viewBox='0 0 12 7'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23888f98' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 11px center}
5683
+ .cc-inputInvalid{border-color:var(--dsw-alias-label-error)}
5684
+ .cc-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
5685
+ /* The Advanced card: its header row is one full-width toggle button; the
5686
+ * chevron is two borders on a rotated square (no icon dependency). */
5687
+ .cc-advancedHead{align-items:center;gap:8px;display:flex;width:100%;padding:12px 0;background:0 0;border:none;cursor:pointer;font:inherit;text-align:left}
5688
+ .cc-advancedHead:hover .cc-advancedTitle{color:var(--dsw-alias-label-primary)}
5689
+ .cc-advancedTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.5}
5690
+ .cc-advancedSpacer{flex:1}
5691
+ .cc-chevron{flex-shrink:0;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);width:8px;height:8px;margin-right:4px;margin-bottom:2px;transform:rotate(45deg);transition:transform .15s ease}
5692
+ .cc-chevronUp{transform:rotate(-135deg);margin-bottom:-3px}
5693
+ .cc-advancedBody{flex-direction:column;display:flex}
5694
+ .cc-advancedBody>.cc-field:first-of-type{border-top:1px solid var(--dsw-alias-border-l2)}
5695
+ .cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
5696
+ .cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
5697
+ .cc-toggleRow{align-items:center;gap:8px;cursor:pointer;display:flex}
5698
+ .cc-toggleRow:has(.cc-toggle:disabled){cursor:default}
5699
+ .cc-toggle{appearance:none;flex-shrink:0;background:var(--dsw-alias-border-l2);border-radius:999px;width:30px;height:18px;margin:0;cursor:pointer;position:relative;transition:background .15s ease}
5700
+ .cc-toggle:checked{background:var(--dsw-alias-brand-primary)}
5701
+ .cc-toggle::after{content:'';background:#fff;border-radius:50%;width:14px;height:14px;position:absolute;top:2px;left:2px;transition:left .15s ease}
5702
+ .cc-toggle:checked::after{left:14px}
5703
+ .cc-toggle:disabled{cursor:default;opacity:.5}
5704
+ .cc-failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}
5705
+ .cc-usageCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:14px 16px;flex-direction:column;gap:12px;display:flex}
5706
+ .cc-usageHead{align-items:center;gap:8px;display:flex}
5707
+ .cc-usageTitle{color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:13px;font-weight:600;line-height:1.5}
5708
+ .cc-usageAccount{max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
5709
+ .cc-usagePlan{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-brand-primary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;line-height:17px}
5710
+ .cc-usagePlanStatus{white-space:nowrap;color:var(--dsw-alias-label-error);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
5711
+ .cc-usageRefresh{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}
5712
+ .cc-usageRefresh:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
5713
+ .cc-usageRefresh:disabled{cursor:default;opacity:.5}
5714
+ .cc-usageHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
5715
+ .cc-usageError{align-items:center;gap:8px;color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5;display:flex}
5716
+ .cc-usageStats{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;display:grid}
5717
+ .cc-usageStat{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;padding:8px 10px;flex-direction:column;gap:2px;display:flex}
5718
+ .cc-usageStatLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
5719
+ .cc-usageStatValue{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
5720
+ .cc-usageStatSub{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
5721
+ .cc-usageWindows{flex-direction:column;gap:16px;display:flex}
5722
+ .cc-usageWindow{flex-direction:column;gap:6px;display:flex}
5723
+ .cc-usageWindowHead{align-items:baseline;gap:8px;display:flex}
5724
+ .cc-usageWindowLabel{color:var(--dsw-alias-label-secondary);flex:1;font-size:12px;font-weight:500;line-height:1.5}
5725
+ .cc-usageWindowValue{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;line-height:1.5}
5726
+ .cc-usageExceeded{color:var(--dsw-alias-label-error);font-size:11px;font-weight:500;line-height:1.5}
5727
+ .cc-usageBar{overflow:hidden;background:var(--dsw-alias-bg-layer-1);border-radius:999px;height:6px}
5728
+ .cc-usageBarFill{background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
5729
+ .cc-usageBarFillWarn{background:var(--dsw-alias-label-error)}
5730
+ @media (prefers-reduced-motion:reduce){.cc-chevron,.cc-toggle,.cc-toggle::after,.cc-usageBarFill{transition:none}}.cc-usageWindowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
5731
+ .cc-usageMeta{align-items:center;gap:8px;display:flex}
5732
+ .cc-accountReport{flex-direction:column;gap:12px;display:flex}
5733
+ .cc-tabs{flex-wrap:wrap;gap:6px;display:flex}
5734
+ .cc-tab{align-items:center;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:2px 10px;font-size:12px;line-height:18px;display:inline-flex;gap:6px}
5735
+ .cc-tab:hover:not(.cc-tabActive){color:var(--dsw-alias-label-primary)}
5736
+ .cc-tabActive{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-brand-primary)}
5737
+ .cc-tabDotOk{background:var(--dsw-alias-brand-primary);border-radius:50%;width:6px;height:6px}
5738
+ .cc-tabDotWarn{background:#d97706;border-radius:50%;width:6px;height:6px}
5739
+ .cc-tabDotError{background:var(--dsw-alias-label-error);border-radius:50%;width:6px;height:6px}
5740
+ .cc-usageMetaSpacer{flex:1}
5741
+ .cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
5742
+ .cc-usagePartial{color:var(--dsw-alias-label-error);margin:0;font-size:11px;line-height:1.5}
5743
+ .cc-usageBlocked{border:1px solid var(--dsw-alias-label-error);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:4px}
5744
+ .cc-usageBlockedTitle{color:var(--dsw-alias-label-error);margin:0;font-size:13px;font-weight:600;line-height:1.5}
5745
+ .cc-usageBlockedHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}
5746
+ .cc-version{margin:4px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
5747
+ /* The Models-page provider panel root: unstyled by design — the row card the
5748
+ * Models page owns supplies the surface, the panel only stacks its controls. */
5749
+ .cc-providerCard{flex-direction:column;display:flex}
5750
+ /* The update hint rides the footer version line: warning-tinted (with a
5751
+ * muted fallback for themes without the alias), quiet until hovered. */
5752
+ .cc-versionLink{color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary));text-decoration:none}
5753
+ .cc-versionLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline;text-underline-position:under}
5754
+ /* The login panel rides the connection card as one more field row; the
5755
+ * authorization link is the only branded element on it. */
5756
+ .cc-loginLink{color:var(--dsw-alias-brand-primary);text-decoration:none;font-size:12px;line-height:1.5}
5757
+ .cc-loginLink:hover{text-decoration:underline;text-underline-position:under}
5758
+ .cc-loginDone{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}
5759
+ .cc-loginError{color:var(--dsw-alias-label-error)}
5760
+ .cc-saved{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary));margin:0;font-size:12px;font-weight:500;line-height:1.5}
5761
+ .cc-badgeWarn{background:var(--dsw-alias-state-warning-secondary,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary))}
5762
+ `;
5585
5763
  //#endregion
5586
5764
  //#region src/client/panel-styles.ts
5587
5765
  /**
@@ -5590,8 +5768,16 @@ window.__ModuleLoader__.load({
5590
5768
  *
5591
5769
  * Returned as a string rather than injected here so the modules stay free of
5592
5770
  * DOM side effects at import time — the client entry installs it once, keyed
5593
- * by the same `data-plugin-css` attribute the settings-page stylesheet uses,
5594
- * and removes it again when the plugin's fiber unwinds.
5771
+ * by the same `data-plugin-css` attribute the settings-page stylesheet
5772
+ * (`./page-styles.ts`) uses, and removes it again when the plugin's fiber
5773
+ * unwinds.
5774
+ *
5775
+ * These rules are GLOBAL CSS: every selector in here can match markup this
5776
+ * plugin did not render. So each one is either qualified by a `ccp-` class of
5777
+ * our own, or anchored to the sidebar region it has to lay out — and the one
5778
+ * foreign anchor carries its justification in place (see the footer card
5779
+ * block; issue #48). `tests/styles.test.ts` audits that containment for both
5780
+ * stylesheets and simulates it against the real foreign class stems.
5595
5781
  *
5596
5782
  * Every colour comes from a harness theme alias with a neutral fallback, so
5597
5783
  * the panel follows the active theme (light/dark and any brand pack) without
@@ -5604,9 +5790,12 @@ window.__ModuleLoader__.load({
5604
5790
  * Stylesheet id (the `data-plugin-css` value that makes injection idempotent).
5605
5791
  *
5606
5792
  * The package prefix must match the one the settings-page stylesheet uses
5607
- * (`injectPageCss` in `./index.ts`) and this package's real name: an id is the
5608
- * injection's identity, so a stale fork prefix would let a second copy of the
5609
- * plugin inject the same rules twice and would misreport the owner in the DOM.
5793
+ * (`PAGE_CSS_ID` in `./page-styles.ts`) and this package's real name: an id is
5794
+ * the injection's identity, so a stale fork prefix would let a second copy of
5795
+ * the plugin inject the same rules twice and would misreport the owner in the
5796
+ * DOM. The two ids must also stay distinct from each other — they key two
5797
+ * separate style tags, and a shared id would make the second injection a no-op
5798
+ * that silently drops one stylesheet.
5610
5799
  */
5611
5800
  const PANEL_CSS_ID = "@mars-sea/dsh-commandcode-provider/CommandCodePanel.module.css";
5612
5801
  /** The panel stylesheet. */
@@ -5620,11 +5809,28 @@ window.__ModuleLoader__.load({
5620
5809
 
5621
5810
  The shell's container is a flex ROW whose occupants (this card and ui-cordis's
5622
5811
  footer chip) each declare a full-width line and shrink-proof flex, so as a row
5623
- it would overflow the column. Both were written for a full-width line, which
5624
- is exactly what a column gives them. Matched by the CSS-module class STEM —
5625
- never a hashed name — so a dsh that renames it degrades to the shell's own
5626
- row rather than breaking. */
5627
- [class*="_footerActions"]{flex-direction:column}
5812
+ it would overflow the column: this card's flex:0 0 auto cannot shrink, and the
5813
+ chip's width:100% basis would absorb the whole overflow and collapse to
5814
+ nothing. Both were written for a full-width line, which is exactly what a
5815
+ column gives them. Matched by the CSS-module class STEM — never a hashed name
5816
+ — so a dsh that renames it degrades to the shell's own row rather than
5817
+ breaking.
5818
+
5819
+ THE ANCHOR IS LOAD-BEARING, and it is the whole reason this selector is not
5820
+ just [class*="_footerActions"] (issue #48). "footerActions" is NOT a stem this
5821
+ shell owns alone: @deepseek-ai/dsh-client-ui-user-questions renders the
5822
+ ask-user-question dialog's button row as Mbwy4a_footerActions, so the
5823
+ unanchored rule forced THAT row into a column too and stacked the dialog's
5824
+ side-by-side buttons on every page. "footArea" is declared by
5825
+ dsh-client-ui-sidebar alone — verified against every client bundle of the
5826
+ 0.1.6-alpha.1 engine, and against our own cc-/ccp- class names, which collide
5827
+ with no engine class — so anchoring under it keeps the sidebar layout fix and
5828
+ cannot reach another component's markup. The descendant combinator (rather
5829
+ than a child one) deliberately survives a wrapper element appearing between
5830
+ the two: a renamed or restructured shell should degrade to its own row, not
5831
+ lose the fix. tests/styles.test.ts pins both halves of this — the dialog row
5832
+ is no longer matched, the sidebar row still is. */
5833
+ [class*="_footArea"] [class*="_footerActions"]{flex-direction:column}
5628
5834
  .ccp-foot{box-sizing:border-box;flex:0 0 auto;width:100%;min-width:0;font:inherit;color:var(--dsw-alias-label-secondary);text-align:left;cursor:pointer;background:0 0;border:1px solid transparent;border-radius:10px;flex-direction:column;gap:6px;margin:0 0 4px;padding:8px;display:flex}
5629
5835
  .ccp-foot:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);border-color:var(--dsw-alias-border-l2)}
5630
5836
  .ccp-foot:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
@@ -5750,6 +5956,8 @@ window.__ModuleLoader__.load({
5750
5956
  requestTimeoutMsHint: "等待响应首个字节的超时;默认 60000。",
5751
5957
  streamIdleTimeoutMs: "流空闲超时(毫秒)",
5752
5958
  streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认 300000(长思考模型可静默数分钟,默认值刻意放宽)。",
5959
+ transportMaxRetries: "网络失败重试次数",
5960
+ transportMaxRetriesHint: "连接失败时自动重试几次,默认 5(各次等待 0.5+1+2+4+8 秒,合计约 15 秒)。超过后直接报错,不再按指数退避长时间等待;填 0 表示不重试。限流、5xx 等由服务端要求重试的失败不受此项影响。",
5753
5961
  advancedSettings: "高级设置",
5754
5962
  advancedSettingsHint: "API 地址、工作目录、超时、模型过滤、联网搜索与侧边栏额度卡片等不常修改的选项。",
5755
5963
  advancedOverriddenOne: "已自定义 1 项",
@@ -5884,6 +6092,8 @@ window.__ModuleLoader__.load({
5884
6092
  requestTimeoutMsHint: "Time to wait for the first response byte; default 60000.",
5885
6093
  streamIdleTimeoutMs: "Stream idle timeout (ms)",
5886
6094
  streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default 300000 (deliberately generous — long-thinking models can stay silent for minutes).",
6095
+ transportMaxRetries: "Transport retries",
6096
+ transportMaxRetriesHint: "How many times a failed connection is retried automatically; default 5 (waits of 0.5+1+2+4+8s, ~15s in total). After that the failure is reported instead of waiting on the exponential backoff. 0 disables the retries. Rate limits and 5xx answers keep their own, longer retry window.",
5887
6097
  advancedSettings: "Advanced",
5888
6098
  advancedSettingsHint: "Rarely touched options: API base URL, working directory, timeouts, model filtering, web search, and the sidebar quota card.",
5889
6099
  advancedOverriddenOne: "1 customized",
@@ -6003,141 +6213,13 @@ window.__ModuleLoader__.load({
6003
6213
  };
6004
6214
  //#endregion
6005
6215
  //#region src/client/index.ts
6006
- /** CSS for the settings page, injected once (harness bundle convention). */
6007
- const PAGE_CSS = `
6008
- .cc-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}
6009
- .cc-title{margin:0;font-size:18px;font-weight:600}
6010
- .cc-intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:1.5}
6011
- .cc-readOnly{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
6012
- .cc-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:4px 16px}
6013
- .cc-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
6014
- .cc-field+.cc-field{border-top:1px solid var(--dsw-alias-border-l2)}
6015
- .cc-fieldHead{align-items:center;gap:8px;display:flex}
6016
- .cc-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}
6017
- .cc-badges{align-items:center;gap:8px;display:inline-flex}
6018
- .cc-badge{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
6019
- .cc-badgeMuted{white-space:nowrap;color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px}
6020
- .cc-reset{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}
6021
- .cc-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
6022
- .cc-reset:disabled{cursor:default;opacity:.5}
6023
- .cc-input{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);height:34px;font:inherit;color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;line-height:1.5}
6024
- .cc-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
6025
- .cc-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
6026
- /* The routing-rule model multi-select: a button trigger that opens an
6027
- * anchored Menu of checkbox rows. The trigger mirrors .cc-input sizing so it
6028
- * sits flush with the sibling account select. */
6029
- .cc-ruleTrigger{align-items:center;gap:8px;display:flex;width:100%;text-align:left;cursor:pointer}
6030
- .cc-ruleTrigger:disabled{cursor:default}
6031
- .cc-ruleTriggerText{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6032
- .cc-ruleCaret{flex-shrink:0;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);width:6px;height:6px;margin-right:4px;margin-bottom:2px;transform:rotate(45deg)}
6033
- .cc-checkRow{align-items:center;gap:8px;display:inline-flex;min-width:0}
6034
- .cc-checkRow:hover{cursor:pointer}
6035
- .cc-check{appearance:none;flex-shrink:0;width:15px;height:15px;margin:0;border:1px solid var(--dsw-alias-border-l2);border-radius:4px;background:var(--dsw-alias-bg-layer-1);position:relative}
6036
- .cc-check:checked{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}
6037
- .cc-check:checked::after{content:'';position:absolute;top:2px;left:5px;width:3px;height:7px;border:solid #fff;border-width:0 1.5px 1.5px 0;transform:rotate(45deg)}
6038
- .cc-checkName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6039
- /* The model multi-select search box: stacked under the trigger while the
6040
- * dropdown is open, same input sizing so the pair reads as one control. The
6041
- * box lives inside the Menu anchor (which renders inside the Menu's root
6042
- * span), so focusing/typing it never trips the Menu's outside-click close. */
6043
- .cc-modelSelectAnchor{flex-direction:column;gap:6px;display:flex;width:100%}
6044
- .cc-modelSearch{width:100%}
6045
- .cc-modelSearch::-webkit-search-cancel-button{cursor:pointer}
6046
- /* Selects need their own treatment to sit flush with the text inputs:
6047
- * the UA stylesheet renders <select> border-box (34px total vs the inputs'
6048
- * 36px) and forces its own menulist text metrics, so drop the native
6049
- * chrome entirely (appearance:none), restore content-box so the outer box
6050
- * matches the inputs again, and draw the chevron ourselves. Longhand
6051
- * background-* only — the shorthand would reset .cc-input's background. */
6052
- select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box-sizing:content-box;padding-right:32px;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='7' viewBox='0 0 12 7'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23888f98' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 11px center}
6053
- .cc-inputInvalid{border-color:var(--dsw-alias-label-error)}
6054
- .cc-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
6055
- /* The Advanced card: its header row is one full-width toggle button; the
6056
- * chevron is two borders on a rotated square (no icon dependency). */
6057
- .cc-advancedHead{align-items:center;gap:8px;display:flex;width:100%;padding:12px 0;background:0 0;border:none;cursor:pointer;font:inherit;text-align:left}
6058
- .cc-advancedHead:hover .cc-advancedTitle{color:var(--dsw-alias-label-primary)}
6059
- .cc-advancedTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.5}
6060
- .cc-advancedSpacer{flex:1}
6061
- .cc-chevron{flex-shrink:0;border-right:1.5px solid var(--dsw-alias-label-tertiary);border-bottom:1.5px solid var(--dsw-alias-label-tertiary);width:8px;height:8px;margin-right:4px;margin-bottom:2px;transform:rotate(45deg);transition:transform .15s ease}
6062
- .cc-chevronUp{transform:rotate(-135deg);margin-bottom:-3px}
6063
- .cc-advancedBody{flex-direction:column;display:flex}
6064
- .cc-advancedBody>.cc-field:first-of-type{border-top:1px solid var(--dsw-alias-border-l2)}
6065
- .cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
6066
- .cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
6067
- .cc-toggleRow{align-items:center;gap:8px;cursor:pointer;display:flex}
6068
- .cc-toggleRow:has(.cc-toggle:disabled){cursor:default}
6069
- .cc-toggle{appearance:none;flex-shrink:0;background:var(--dsw-alias-border-l2);border-radius:999px;width:30px;height:18px;margin:0;cursor:pointer;position:relative;transition:background .15s ease}
6070
- .cc-toggle:checked{background:var(--dsw-alias-brand-primary)}
6071
- .cc-toggle::after{content:'';background:#fff;border-radius:50%;width:14px;height:14px;position:absolute;top:2px;left:2px;transition:left .15s ease}
6072
- .cc-toggle:checked::after{left:14px}
6073
- .cc-toggle:disabled{cursor:default;opacity:.5}
6074
- .cc-failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}
6075
- .cc-usageCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:14px 16px;flex-direction:column;gap:12px;display:flex}
6076
- .cc-usageHead{align-items:center;gap:8px;display:flex}
6077
- .cc-usageTitle{color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:13px;font-weight:600;line-height:1.5}
6078
- .cc-usageAccount{max-width:40%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
6079
- .cc-usagePlan{white-space:nowrap;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-brand-primary);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:600;line-height:17px}
6080
- .cc-usagePlanStatus{white-space:nowrap;color:var(--dsw-alias-label-error);border-radius:999px;padding:1px 8px;font-size:11px;font-weight:500;line-height:17px}
6081
- .cc-usageRefresh{font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:0 0;border:none;padding:0;font-size:12px;line-height:1.5}
6082
- .cc-usageRefresh:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
6083
- .cc-usageRefresh:disabled{cursor:default;opacity:.5}
6084
- .cc-usageHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
6085
- .cc-usageError{align-items:center;gap:8px;color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5;display:flex}
6086
- .cc-usageStats{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;display:grid}
6087
- .cc-usageStat{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-1);border-radius:8px;padding:8px 10px;flex-direction:column;gap:2px;display:flex}
6088
- .cc-usageStatLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
6089
- .cc-usageStatValue{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
6090
- .cc-usageStatSub{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
6091
- .cc-usageWindows{flex-direction:column;gap:16px;display:flex}
6092
- .cc-usageWindow{flex-direction:column;gap:6px;display:flex}
6093
- .cc-usageWindowHead{align-items:baseline;gap:8px;display:flex}
6094
- .cc-usageWindowLabel{color:var(--dsw-alias-label-secondary);flex:1;font-size:12px;font-weight:500;line-height:1.5}
6095
- .cc-usageWindowValue{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;line-height:1.5}
6096
- .cc-usageExceeded{color:var(--dsw-alias-label-error);font-size:11px;font-weight:500;line-height:1.5}
6097
- .cc-usageBar{overflow:hidden;background:var(--dsw-alias-bg-layer-1);border-radius:999px;height:6px}
6098
- .cc-usageBarFill{background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
6099
- .cc-usageBarFillWarn{background:var(--dsw-alias-label-error)}
6100
- @media (prefers-reduced-motion:reduce){.cc-chevron,.cc-toggle,.cc-toggle::after,.cc-usageBarFill{transition:none}}.cc-usageWindowReset{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
6101
- .cc-usageMeta{align-items:center;gap:8px;display:flex}
6102
- .cc-accountReport{flex-direction:column;gap:12px;display:flex}
6103
- .cc-tabs{flex-wrap:wrap;gap:6px;display:flex}
6104
- .cc-tab{align-items:center;font:inherit;color:var(--dsw-alias-label-secondary);cursor:pointer;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:999px;padding:2px 10px;font-size:12px;line-height:18px;display:inline-flex;gap:6px}
6105
- .cc-tab:hover:not(.cc-tabActive){color:var(--dsw-alias-label-primary)}
6106
- .cc-tabActive{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-brand-primary)}
6107
- .cc-tabDotOk{background:var(--dsw-alias-brand-primary);border-radius:50%;width:6px;height:6px}
6108
- .cc-tabDotWarn{background:#d97706;border-radius:50%;width:6px;height:6px}
6109
- .cc-tabDotError{background:var(--dsw-alias-label-error);border-radius:50%;width:6px;height:6px}
6110
- .cc-usageMetaSpacer{flex:1}
6111
- .cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
6112
- .cc-usagePartial{color:var(--dsw-alias-label-error);margin:0;font-size:11px;line-height:1.5}
6113
- .cc-usageBlocked{border:1px solid var(--dsw-alias-label-error);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:4px}
6114
- .cc-usageBlockedTitle{color:var(--dsw-alias-label-error);margin:0;font-size:13px;font-weight:600;line-height:1.5}
6115
- .cc-usageBlockedHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}
6116
- .cc-version{margin:4px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
6117
- /* The Models-page provider panel root: unstyled by design — the row card the
6118
- * Models page owns supplies the surface, the panel only stacks its controls. */
6119
- .cc-providerCard{flex-direction:column;display:flex}
6120
- /* The update hint rides the footer version line: warning-tinted (with a
6121
- * muted fallback for themes without the alias), quiet until hovered. */
6122
- .cc-versionLink{color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary));text-decoration:none}
6123
- .cc-versionLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline;text-underline-position:under}
6124
- /* The login panel rides the connection card as one more field row; the
6125
- * authorization link is the only branded element on it. */
6126
- .cc-loginLink{color:var(--dsw-alias-brand-primary);text-decoration:none;font-size:12px;line-height:1.5}
6127
- .cc-loginLink:hover{text-decoration:underline;text-underline-position:under}
6128
- .cc-loginDone{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}
6129
- .cc-loginError{color:var(--dsw-alias-label-error)}
6130
- .cc-saved{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary));margin:0;font-size:12px;font-weight:500;line-height:1.5}
6131
- .cc-badgeWarn{background:var(--dsw-alias-state-warning-secondary,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary))}
6132
- `;
6133
6216
  /** Inject the page stylesheet once (idempotent per tag). */
6134
6217
  function injectPageCss() {
6135
6218
  if (typeof document === "undefined") return;
6136
- const id = "@mars-sea/dsh-commandcode-provider/CommandCodeSettingsPage.module.css";
6137
- if (document.querySelector(`style[data-plugin-css="${id}"]`) !== null) return;
6219
+ if (document.querySelector(`style[data-plugin-css="@mars-sea/dsh-commandcode-provider/CommandCodeSettingsPage.module.css"]`) !== null) return;
6138
6220
  const tag = document.createElement("style");
6139
6221
  tag.dataset.plugin = "@mars-sea/dsh-commandcode-provider";
6140
- tag.dataset.pluginCss = id;
6222
+ tag.dataset.pluginCss = PAGE_CSS_ID;
6141
6223
  tag.textContent = PAGE_CSS;
6142
6224
  document.head.appendChild(tag);
6143
6225
  }