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

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,18 @@ 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.3] - 2026-09-16
10
+
11
+ ### Added
12
+ - **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.
13
+ - **`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.
14
+
15
+ ### Fixed
16
+ - **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).
17
+ - **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.
18
+ - **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.
19
+ - **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.
20
+
9
21
  ## [0.11.2] - 2026-09-15
10
22
 
11
23
  ### 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"),
@@ -2150,7 +2155,7 @@ window.__ModuleLoader__.load({
2150
2155
  }
2151
2156
  //#endregion
2152
2157
  //#region package.json
2153
- var version = "0.11.2";
2158
+ var version = "0.11.3";
2154
2159
  var repository = {
2155
2160
  "type": "git",
2156
2161
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -2349,6 +2354,7 @@ window.__ModuleLoader__.load({
2349
2354
  "workingDir",
2350
2355
  "requestTimeoutMs",
2351
2356
  "streamIdleTimeoutMs",
2357
+ "transportMaxRetries",
2352
2358
  "filterModelsByPlan",
2353
2359
  "webSearch",
2354
2360
  "showSidebarQuota"
@@ -2494,6 +2500,17 @@ window.__ModuleLoader__.load({
2494
2500
  onReset: () => onReset("streamIdleTimeoutMs"),
2495
2501
  t
2496
2502
  }),
2503
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
2504
+ id: "cc-transport-max-retries",
2505
+ label: t("transportMaxRetries"),
2506
+ hint: t("transportMaxRetriesHint"),
2507
+ state: state.transportMaxRetries,
2508
+ disabled,
2509
+ numeric: true,
2510
+ onEdit: (text) => onEdit("transportMaxRetries", text),
2511
+ onReset: () => onReset("transportMaxRetries"),
2512
+ t
2513
+ }),
2497
2514
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
2498
2515
  id: "cc-filter-models-by-plan",
2499
2516
  label: t("filterModelsByPlan"),
@@ -5582,6 +5599,165 @@ window.__ModuleLoader__.load({
5582
5599
  "data-ccp-session-cost-anchor": ""
5583
5600
  });
5584
5601
  }
5602
+ //#endregion
5603
+ //#region src/client/page-styles.ts
5604
+ /**
5605
+ * Stylesheet for the Command Code settings page — and the Models-page provider
5606
+ * card, which shares its `cc-` classes.
5607
+ *
5608
+ * Returned as a string rather than injected here so the modules stay free of
5609
+ * DOM side effects at import time: `./index.ts` installs it once, keyed by
5610
+ * `PAGE_CSS_ID`. It is a module of its own rather than a literal inside the
5611
+ * client entry so `tests/styles.test.ts` can audit every rule without importing
5612
+ * the React tree (which pulls `*.module.css` through the primitives package and
5613
+ * needs a loader registered first).
5614
+ *
5615
+ * Every colour comes from a harness theme alias with a neutral fallback, so the
5616
+ * page follows the active theme (light/dark and any brand pack) without
5617
+ * hardcoded values. Classes are `cc-` prefixed to stay clear of the panel's
5618
+ * `ccp-` set.
5619
+ *
5620
+ * @module dsh-commandcode-provider/client/page-styles
5621
+ */
5622
+ /**
5623
+ * Stylesheet id (the `data-plugin-css` value that makes injection idempotent).
5624
+ *
5625
+ * The package prefix must match the one the panel stylesheet uses
5626
+ * (`PANEL_CSS_ID` in `./panel-styles.ts`) and this package's real name: an id is
5627
+ * the injection's identity, so a stale fork prefix would let a second copy of
5628
+ * the plugin inject the same rules twice and would misreport the owner in the
5629
+ * DOM. The two ids must also stay distinct from each other — they key two
5630
+ * separate style tags, and a shared id would make the second injection a no-op
5631
+ * that silently drops one stylesheet.
5632
+ */
5633
+ const PAGE_CSS_ID = "@mars-sea/dsh-commandcode-provider/CommandCodeSettingsPage.module.css";
5634
+ /** The settings-page stylesheet. */
5635
+ const PAGE_CSS = `
5636
+ .cc-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}
5637
+ .cc-title{margin:0;font-size:18px;font-weight:600}
5638
+ .cc-intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:1.5}
5639
+ .cc-readOnly{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
5640
+ .cc-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:4px 16px}
5641
+ .cc-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
5642
+ .cc-field+.cc-field{border-top:1px solid var(--dsw-alias-border-l2)}
5643
+ .cc-fieldHead{align-items:center;gap:8px;display:flex}
5644
+ .cc-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}
5645
+ .cc-badges{align-items:center;gap:8px;display:inline-flex}
5646
+ .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}
5647
+ .cc-badgeMuted{white-space:nowrap;color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px}
5648
+ .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}
5649
+ .cc-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
5650
+ .cc-reset:disabled{cursor:default;opacity:.5}
5651
+ .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}
5652
+ .cc-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
5653
+ .cc-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
5654
+ /* The routing-rule model multi-select: a button trigger that opens an
5655
+ * anchored Menu of checkbox rows. The trigger mirrors .cc-input sizing so it
5656
+ * sits flush with the sibling account select. */
5657
+ .cc-ruleTrigger{align-items:center;gap:8px;display:flex;width:100%;text-align:left;cursor:pointer}
5658
+ .cc-ruleTrigger:disabled{cursor:default}
5659
+ .cc-ruleTriggerText{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
5660
+ .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)}
5661
+ .cc-checkRow{align-items:center;gap:8px;display:inline-flex;min-width:0}
5662
+ .cc-checkRow:hover{cursor:pointer}
5663
+ .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}
5664
+ .cc-check:checked{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}
5665
+ .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)}
5666
+ .cc-checkName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
5667
+ /* The model multi-select search box: stacked under the trigger while the
5668
+ * dropdown is open, same input sizing so the pair reads as one control. The
5669
+ * box lives inside the Menu anchor (which renders inside the Menu's root
5670
+ * span), so focusing/typing it never trips the Menu's outside-click close. */
5671
+ .cc-modelSelectAnchor{flex-direction:column;gap:6px;display:flex;width:100%}
5672
+ .cc-modelSearch{width:100%}
5673
+ .cc-modelSearch::-webkit-search-cancel-button{cursor:pointer}
5674
+ /* Selects need their own treatment to sit flush with the text inputs:
5675
+ * the UA stylesheet renders <select> border-box (34px total vs the inputs'
5676
+ * 36px) and forces its own menulist text metrics, so drop the native
5677
+ * chrome entirely (appearance:none), restore content-box so the outer box
5678
+ * matches the inputs again, and draw the chevron ourselves. Longhand
5679
+ * background-* only — the shorthand would reset .cc-input's background. */
5680
+ 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}
5681
+ .cc-inputInvalid{border-color:var(--dsw-alias-label-error)}
5682
+ .cc-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
5683
+ /* The Advanced card: its header row is one full-width toggle button; the
5684
+ * chevron is two borders on a rotated square (no icon dependency). */
5685
+ .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}
5686
+ .cc-advancedHead:hover .cc-advancedTitle{color:var(--dsw-alias-label-primary)}
5687
+ .cc-advancedTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.5}
5688
+ .cc-advancedSpacer{flex:1}
5689
+ .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}
5690
+ .cc-chevronUp{transform:rotate(-135deg);margin-bottom:-3px}
5691
+ .cc-advancedBody{flex-direction:column;display:flex}
5692
+ .cc-advancedBody>.cc-field:first-of-type{border-top:1px solid var(--dsw-alias-border-l2)}
5693
+ .cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
5694
+ .cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
5695
+ .cc-toggleRow{align-items:center;gap:8px;cursor:pointer;display:flex}
5696
+ .cc-toggleRow:has(.cc-toggle:disabled){cursor:default}
5697
+ .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}
5698
+ .cc-toggle:checked{background:var(--dsw-alias-brand-primary)}
5699
+ .cc-toggle::after{content:'';background:#fff;border-radius:50%;width:14px;height:14px;position:absolute;top:2px;left:2px;transition:left .15s ease}
5700
+ .cc-toggle:checked::after{left:14px}
5701
+ .cc-toggle:disabled{cursor:default;opacity:.5}
5702
+ .cc-failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}
5703
+ .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}
5704
+ .cc-usageHead{align-items:center;gap:8px;display:flex}
5705
+ .cc-usageTitle{color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:13px;font-weight:600;line-height:1.5}
5706
+ .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}
5707
+ .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}
5708
+ .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}
5709
+ .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}
5710
+ .cc-usageRefresh:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
5711
+ .cc-usageRefresh:disabled{cursor:default;opacity:.5}
5712
+ .cc-usageHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
5713
+ .cc-usageError{align-items:center;gap:8px;color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5;display:flex}
5714
+ .cc-usageStats{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;display:grid}
5715
+ .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}
5716
+ .cc-usageStatLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
5717
+ .cc-usageStatValue{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
5718
+ .cc-usageStatSub{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
5719
+ .cc-usageWindows{flex-direction:column;gap:16px;display:flex}
5720
+ .cc-usageWindow{flex-direction:column;gap:6px;display:flex}
5721
+ .cc-usageWindowHead{align-items:baseline;gap:8px;display:flex}
5722
+ .cc-usageWindowLabel{color:var(--dsw-alias-label-secondary);flex:1;font-size:12px;font-weight:500;line-height:1.5}
5723
+ .cc-usageWindowValue{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;line-height:1.5}
5724
+ .cc-usageExceeded{color:var(--dsw-alias-label-error);font-size:11px;font-weight:500;line-height:1.5}
5725
+ .cc-usageBar{overflow:hidden;background:var(--dsw-alias-bg-layer-1);border-radius:999px;height:6px}
5726
+ .cc-usageBarFill{background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
5727
+ .cc-usageBarFillWarn{background:var(--dsw-alias-label-error)}
5728
+ @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}
5729
+ .cc-usageMeta{align-items:center;gap:8px;display:flex}
5730
+ .cc-accountReport{flex-direction:column;gap:12px;display:flex}
5731
+ .cc-tabs{flex-wrap:wrap;gap:6px;display:flex}
5732
+ .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}
5733
+ .cc-tab:hover:not(.cc-tabActive){color:var(--dsw-alias-label-primary)}
5734
+ .cc-tabActive{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-brand-primary)}
5735
+ .cc-tabDotOk{background:var(--dsw-alias-brand-primary);border-radius:50%;width:6px;height:6px}
5736
+ .cc-tabDotWarn{background:#d97706;border-radius:50%;width:6px;height:6px}
5737
+ .cc-tabDotError{background:var(--dsw-alias-label-error);border-radius:50%;width:6px;height:6px}
5738
+ .cc-usageMetaSpacer{flex:1}
5739
+ .cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
5740
+ .cc-usagePartial{color:var(--dsw-alias-label-error);margin:0;font-size:11px;line-height:1.5}
5741
+ .cc-usageBlocked{border:1px solid var(--dsw-alias-label-error);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:4px}
5742
+ .cc-usageBlockedTitle{color:var(--dsw-alias-label-error);margin:0;font-size:13px;font-weight:600;line-height:1.5}
5743
+ .cc-usageBlockedHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}
5744
+ .cc-version{margin:4px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
5745
+ /* The Models-page provider panel root: unstyled by design — the row card the
5746
+ * Models page owns supplies the surface, the panel only stacks its controls. */
5747
+ .cc-providerCard{flex-direction:column;display:flex}
5748
+ /* The update hint rides the footer version line: warning-tinted (with a
5749
+ * muted fallback for themes without the alias), quiet until hovered. */
5750
+ .cc-versionLink{color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary));text-decoration:none}
5751
+ .cc-versionLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline;text-underline-position:under}
5752
+ /* The login panel rides the connection card as one more field row; the
5753
+ * authorization link is the only branded element on it. */
5754
+ .cc-loginLink{color:var(--dsw-alias-brand-primary);text-decoration:none;font-size:12px;line-height:1.5}
5755
+ .cc-loginLink:hover{text-decoration:underline;text-underline-position:under}
5756
+ .cc-loginDone{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}
5757
+ .cc-loginError{color:var(--dsw-alias-label-error)}
5758
+ .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}
5759
+ .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))}
5760
+ `;
5585
5761
  //#endregion
5586
5762
  //#region src/client/panel-styles.ts
5587
5763
  /**
@@ -5590,8 +5766,16 @@ window.__ModuleLoader__.load({
5590
5766
  *
5591
5767
  * Returned as a string rather than injected here so the modules stay free of
5592
5768
  * 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.
5769
+ * by the same `data-plugin-css` attribute the settings-page stylesheet
5770
+ * (`./page-styles.ts`) uses, and removes it again when the plugin's fiber
5771
+ * unwinds.
5772
+ *
5773
+ * These rules are GLOBAL CSS: every selector in here can match markup this
5774
+ * plugin did not render. So each one is either qualified by a `ccp-` class of
5775
+ * our own, or anchored to the sidebar region it has to lay out — and the one
5776
+ * foreign anchor carries its justification in place (see the footer card
5777
+ * block; issue #48). `tests/styles.test.ts` audits that containment for both
5778
+ * stylesheets and simulates it against the real foreign class stems.
5595
5779
  *
5596
5780
  * Every colour comes from a harness theme alias with a neutral fallback, so
5597
5781
  * the panel follows the active theme (light/dark and any brand pack) without
@@ -5604,9 +5788,12 @@ window.__ModuleLoader__.load({
5604
5788
  * Stylesheet id (the `data-plugin-css` value that makes injection idempotent).
5605
5789
  *
5606
5790
  * 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.
5791
+ * (`PAGE_CSS_ID` in `./page-styles.ts`) and this package's real name: an id is
5792
+ * the injection's identity, so a stale fork prefix would let a second copy of
5793
+ * the plugin inject the same rules twice and would misreport the owner in the
5794
+ * DOM. The two ids must also stay distinct from each other — they key two
5795
+ * separate style tags, and a shared id would make the second injection a no-op
5796
+ * that silently drops one stylesheet.
5610
5797
  */
5611
5798
  const PANEL_CSS_ID = "@mars-sea/dsh-commandcode-provider/CommandCodePanel.module.css";
5612
5799
  /** The panel stylesheet. */
@@ -5620,11 +5807,28 @@ window.__ModuleLoader__.load({
5620
5807
 
5621
5808
  The shell's container is a flex ROW whose occupants (this card and ui-cordis's
5622
5809
  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}
5810
+ it would overflow the column: this card's flex:0 0 auto cannot shrink, and the
5811
+ chip's width:100% basis would absorb the whole overflow and collapse to
5812
+ nothing. Both were written for a full-width line, which is exactly what a
5813
+ column gives them. Matched by the CSS-module class STEM — never a hashed name
5814
+ — so a dsh that renames it degrades to the shell's own row rather than
5815
+ breaking.
5816
+
5817
+ THE ANCHOR IS LOAD-BEARING, and it is the whole reason this selector is not
5818
+ just [class*="_footerActions"] (issue #48). "footerActions" is NOT a stem this
5819
+ shell owns alone: @deepseek-ai/dsh-client-ui-user-questions renders the
5820
+ ask-user-question dialog's button row as Mbwy4a_footerActions, so the
5821
+ unanchored rule forced THAT row into a column too and stacked the dialog's
5822
+ side-by-side buttons on every page. "footArea" is declared by
5823
+ dsh-client-ui-sidebar alone — verified against every client bundle of the
5824
+ 0.1.6-alpha.1 engine, and against our own cc-/ccp- class names, which collide
5825
+ with no engine class — so anchoring under it keeps the sidebar layout fix and
5826
+ cannot reach another component's markup. The descendant combinator (rather
5827
+ than a child one) deliberately survives a wrapper element appearing between
5828
+ the two: a renamed or restructured shell should degrade to its own row, not
5829
+ lose the fix. tests/styles.test.ts pins both halves of this — the dialog row
5830
+ is no longer matched, the sidebar row still is. */
5831
+ [class*="_footArea"] [class*="_footerActions"]{flex-direction:column}
5628
5832
  .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
5833
  .ccp-foot:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);border-color:var(--dsw-alias-border-l2)}
5630
5834
  .ccp-foot:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
@@ -5750,6 +5954,8 @@ window.__ModuleLoader__.load({
5750
5954
  requestTimeoutMsHint: "等待响应首个字节的超时;默认 60000。",
5751
5955
  streamIdleTimeoutMs: "流空闲超时(毫秒)",
5752
5956
  streamIdleTimeoutMsHint: "生成流停滞多久视为断连;默认 300000(长思考模型可静默数分钟,默认值刻意放宽)。",
5957
+ transportMaxRetries: "网络失败重试次数",
5958
+ transportMaxRetriesHint: "连接失败时自动重试几次,默认 5(各次等待 0.5+1+2+4+8 秒,合计约 15 秒)。超过后直接报错,不再按指数退避长时间等待;填 0 表示不重试。限流、5xx 等由服务端要求重试的失败不受此项影响。",
5753
5959
  advancedSettings: "高级设置",
5754
5960
  advancedSettingsHint: "API 地址、工作目录、超时、模型过滤、联网搜索与侧边栏额度卡片等不常修改的选项。",
5755
5961
  advancedOverriddenOne: "已自定义 1 项",
@@ -5884,6 +6090,8 @@ window.__ModuleLoader__.load({
5884
6090
  requestTimeoutMsHint: "Time to wait for the first response byte; default 60000.",
5885
6091
  streamIdleTimeoutMs: "Stream idle timeout (ms)",
5886
6092
  streamIdleTimeoutMsHint: "How long a stalled stream is treated as dead; default 300000 (deliberately generous — long-thinking models can stay silent for minutes).",
6093
+ transportMaxRetries: "Transport retries",
6094
+ 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
6095
  advancedSettings: "Advanced",
5888
6096
  advancedSettingsHint: "Rarely touched options: API base URL, working directory, timeouts, model filtering, web search, and the sidebar quota card.",
5889
6097
  advancedOverriddenOne: "1 customized",
@@ -6003,141 +6211,13 @@ window.__ModuleLoader__.load({
6003
6211
  };
6004
6212
  //#endregion
6005
6213
  //#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
6214
  /** Inject the page stylesheet once (idempotent per tag). */
6134
6215
  function injectPageCss() {
6135
6216
  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;
6217
+ if (document.querySelector(`style[data-plugin-css="@mars-sea/dsh-commandcode-provider/CommandCodeSettingsPage.module.css"]`) !== null) return;
6138
6218
  const tag = document.createElement("style");
6139
6219
  tag.dataset.plugin = "@mars-sea/dsh-commandcode-provider";
6140
- tag.dataset.pluginCss = id;
6220
+ tag.dataset.pluginCss = PAGE_CSS_ID;
6141
6221
  tag.textContent = PAGE_CSS;
6142
6222
  document.head.appendChild(tag);
6143
6223
  }