@mars-sea/dsh-commandcode-provider 0.11.1 โ†’ 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/lib/client.js CHANGED
@@ -36,150 +36,6 @@ window.__ModuleLoader__.load({
36
36
  };
37
37
  }
38
38
  //#endregion
39
- //#region src/command-locales.ts
40
- const commandcodeCommand = {
41
- zh: {
42
- title: "๐Ÿ“Š Command Code ็”จ้‡{account}",
43
- accountTitle: "๐Ÿ“Š {label}{badges}",
44
- accountSeparator: "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
45
- activeBadge: " โœ… ๅฝ“ๅ‰ไฝฟ็”จ",
46
- invalidCredentialBadge: " โ›” ๅฏ†้’ฅๆ— ๆ•ˆ",
47
- cooldownBadge: " โณ ้™้ขๅ†ทๅดไธญ๏ผŒ้‡็ฝฎ {when}",
48
- rateLimitBadge: " โณ ๅทฒ่พพ้™้ข๏ผˆ็ญ‰ๅพ…็ช—ๅฃๆŽขๆต‹๏ผ‰",
49
- unconfigured: " (ๆœช้…็ฝฎ API ๅฏ†้’ฅ)",
50
- blockedInvalidKey: "โ›” API ๅฏ†้’ฅๆ— ๆ•ˆๆˆ–ๅทฒ่ฟ‡ๆœŸ โ€” ๆœๅŠก็ซฏๆ‹’็ปไบ†ๅ…จ้ƒจ่ฏทๆฑ‚๏ผˆ401๏ผ‰๏ผŒ่ฏทๆฃ€ๆŸฅ่ฏฅ่ดฆๆˆท็š„ๅฏ†้’ฅ้…็ฝฎ",
51
- blockedServiceUnavailable: "โš ๏ธ Command Code ๆœๅŠกๆš‚ๆ—ถไธๅฏ็”จ๏ผˆ5xx๏ผ‰๏ผŒ็จๅŽ้‡่ฏ•",
52
- blockedNetwork: "โš ๏ธ ๆ— ๆณ•่ฟžๆŽฅ Command Code ๆœๅŠก โ€” ่ฏทๆฃ€ๆŸฅ็ฝ‘็ปœๆˆ– API ๅœฐๅ€",
53
- planLine: " ๐Ÿ“ฆ ๅฅ—้ค {name}{status}{period}",
54
- planPeriodSuffix: " ยท ่ดฆๆœŸๆˆชๆญข {date}",
55
- usageHeader: "โ”€โ”€ ่ฏทๆฑ‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
56
- requestsLine: " ๐Ÿ’ฌ ่ฏทๆฑ‚ {n} ๆฌก / ๅคฑ่ดฅ {f} ๆˆๅŠŸ็އ {r}%",
57
- costLine: " ๐Ÿ’ฐ ่Šฑ่ดน {money} ({credits} credits)",
58
- tokensLine: " ๐Ÿ”ค Token {in} ๅ…ฅ / {out} ๅ‡บ",
59
- creditsHeader: "โ”€โ”€ ไฟก็”จ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
60
- monthlyLine: " ๐Ÿ’ณ ๆœˆ้ขๅบฆ {monthly} (ๅทฒ่ดญ {purchased} / ่ต ้€ {free})",
61
- barLine: " โ”” {bar} {pct}%",
62
- windowsHeader: "โ”€โ”€ ็ช—ๅฃ็”จ้‡ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
63
- fiveHourLine: " โฑ 5 ๅฐๆ—ถ {used} / {cap}{warn}",
64
- weeklyLine: " ๐Ÿ“… ๆฏๅ‘จ {used} / {cap}{warn}",
65
- windowBarLine: " โ”” {bar} ้‡็ฝฎ {when}",
66
- exceededWarning: " โš ๏ธ ่ถ…้™!",
67
- resetSuffix: "้‡็ฝฎ {when}",
68
- partialFailures: "โš ๏ธ ้ƒจๅˆ†็ซฏ็‚นๅคฑ่ดฅ: {list}",
69
- noData: "๏ผˆๆ— ๆ•ฐๆฎ โ€” ่ฏทๆฃ€ๆŸฅ API ๅฏ†้’ฅ๏ผ‰",
70
- errorText: "่Žทๅ– Command Code ็”จ้‡ๅคฑ่ดฅ๏ผš{message}",
71
- imageGate: "ๅฝ“ๅ‰ไผš่ฏๅทฒๅŒ…ๅซๅ›พ็‰‡๏ผŒ่€Œๆจกๅž‹ {model} ไธๆ”ฏๆŒๅ›พ็‰‡่พ“ๅ…ฅ๏ผ›่ฏท้€‰ๆ‹ฉๆ”ฏๆŒๅ›พ็‰‡็š„ๆจกๅž‹๏ผŒๆˆ–ๅ…ˆ็งป้™คไผš่ฏไธญ็š„ๅ›พ็‰‡ใ€‚"
72
- },
73
- en: {
74
- title: "๐Ÿ“Š Command Code usage{account}",
75
- accountTitle: "๐Ÿ“Š {label}{badges}",
76
- accountSeparator: "โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
77
- activeBadge: " โœ… active",
78
- invalidCredentialBadge: " โ›” invalid key",
79
- cooldownBadge: " โณ cooling down, resets {when}",
80
- rateLimitBadge: " โณ rate-limited (waiting for window probe)",
81
- unconfigured: " (no API key configured)",
82
- blockedInvalidKey: "โ›” API key invalid or expired โ€” the server rejected every request (401); check the key configured for this account",
83
- blockedServiceUnavailable: "โš ๏ธ Command Code service temporarily unavailable (5xx); try again later",
84
- blockedNetwork: "โš ๏ธ could not reach the Command Code service โ€” check your network or the API base setting",
85
- planLine: " ๐Ÿ“ฆ Plan {name}{status}{period}",
86
- planPeriodSuffix: " ยท period ends {date}",
87
- usageHeader: "โ”€โ”€ Requests โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
88
- requestsLine: " ๐Ÿ’ฌ Requests {n} / failed {f} success rate {r}%",
89
- costLine: " ๐Ÿ’ฐ Spend {money} ({credits} credits)",
90
- tokensLine: " ๐Ÿ”ค Tokens {in} in / {out} out",
91
- creditsHeader: "โ”€โ”€ Credits โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
92
- monthlyLine: " ๐Ÿ’ณ Monthly {monthly} (purchased {purchased} / free {free})",
93
- barLine: " โ”” {bar} {pct}%",
94
- windowsHeader: "โ”€โ”€ Window usage โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€",
95
- fiveHourLine: " โฑ 5-hour {used} / {cap}{warn}",
96
- weeklyLine: " ๐Ÿ“… Weekly {used} / {cap}{warn}",
97
- windowBarLine: " โ”” {bar} resets {when}",
98
- exceededWarning: " โš ๏ธ exceeded!",
99
- resetSuffix: "resets {when}",
100
- partialFailures: "โš ๏ธ some endpoints failed: {list}",
101
- noData: "(no data โ€” check your API key)",
102
- errorText: "Could not fetch Command Code usage: {message}",
103
- imageGate: "This session already contains images, and model {model} does not accept image input; please select an image-capable model, or remove the images from the session first."
104
- }
105
- };
106
- //#endregion
107
- //#region src/client/sessions.ts
108
- /**
109
- * Friendly-error wrapper for the harness's image-session gate.
110
- *
111
- * The host rejects switching to a text-only model while the session already
112
- * contains images with a `model-unavailable` error
113
- * (`dsh-host-apiproxy`'s `session.selectModel` handler). That rejection is
114
- * intentional and cannot be relaxed from the plugin side โ€” the adapter's
115
- * `inputModalities` is exactly what makes the guard work. What we CAN do is
116
- * make the error message friendlier: wrap the shared
117
- * `connection.api.sessions.selectModel` face so a `model-unavailable`
118
- * rejection shows a clear, actionable hint (with the requested model name)
119
- * instead of the raw English harness message.
120
- *
121
- * The wrapper is deliberately narrow: only the `model-unavailable` code is
122
- * rewritten, only when the message matches the image-session gate, and only
123
- * the message text changes โ€” the error code and details pass through
124
- * untouched so any caller that switches on `error.code` keeps working.
125
- *
126
- * The wire types are spelled structurally here (not imported from
127
- * `@deepseek-ai/dsh-host-apiproxy`) so this client bundle does not drag an
128
- * extra peer dependency into the package; the shapes are stable and the
129
- * client build inlines them anyway.
130
- *
131
- * The wrapper takes a `getLocale` thunk because it is reached from a
132
- * non-React path that has no `t` in scope; the supplied thunk reads the
133
- * active locale at call time (typically `() => ctx.locale.getLocale().active`
134
- * in the client entry), and the message template lives in the shared
135
- * `commandcodeCommand` dictionary used by the Host-side `/commandcode`
136
- * command โ€” the same bilingual surface serves both.
137
- *
138
- * This module is deliberately free of React and other client-platform
139
- * imports so the node test runner can exercise it directly.
140
- */
141
- /** Whether a selectModel rejection is the harness's image-session gate. */
142
- function isImageSessionRejection(result) {
143
- return !result.result.ok && result.result.error.code === "model-unavailable" && result.result.error.message.includes("does not accept image input");
144
- }
145
- /** Wrap the shared sessions API so selectModel failures read friendlier. */
146
- function withFriendlyImageError(sessions, getLocale) {
147
- const selectModel = sessions.selectModel.bind(sessions);
148
- return {
149
- ...sessions,
150
- selectModel: async (payload, signal) => {
151
- const result = await selectModel(payload, signal);
152
- if (!isImageSessionRejection(result)) return result;
153
- const model = result.result.error.details?.model ?? payload.model;
154
- const template = commandcodeCommand[getLocale()].imageGate ?? commandcodeCommand.en.imageGate;
155
- return {
156
- ...result,
157
- result: {
158
- ...result.result,
159
- error: {
160
- ...result.result.error,
161
- message: template.replace("{model}", model)
162
- }
163
- }
164
- };
165
- }
166
- };
167
- }
168
- /**
169
- * Install the friendly-error wrapper when a legacy sessions face is present.
170
- *
171
- * @returns whether the wrapper was installed. The rewrite is only UX polish;
172
- * a 0.1.2 connection has no `api.sessions`, and its absence must never block
173
- * the plugin from mounting its settings, credential, or usage surfaces.
174
- */
175
- function installFriendlyImageError(connection, getLocale) {
176
- const api = connection.api;
177
- const sessions = api?.sessions;
178
- if (api === void 0 || sessions === void 0 || typeof sessions.selectModel !== "function") return false;
179
- api.sessions = withFriendlyImageError(sessions, getLocale);
180
- return true;
181
- }
182
- //#endregion
183
39
  //#region src/client/settings.ts
184
40
  /**
185
41
  * Browser controller for the "Command Code" settings page.
@@ -301,6 +157,10 @@ window.__ModuleLoader__.load({
301
157
  min: 1,
302
158
  max: MAX_TIMEOUT_MS
303
159
  }),
160
+ numberField$1("transportMaxRetries", {
161
+ min: 0,
162
+ max: 50
163
+ }),
304
164
  booleanField$1("filterModelsByPlan"),
305
165
  booleanField$1("webSearch"),
306
166
  booleanField$1("showSidebarQuota"),
@@ -454,6 +314,7 @@ window.__ModuleLoader__.load({
454
314
  defaultWorkingDir: this.defaultWorkingDir,
455
315
  requestTimeoutMs: this.field("requestTimeoutMs"),
456
316
  streamIdleTimeoutMs: this.field("streamIdleTimeoutMs"),
317
+ transportMaxRetries: this.field("transportMaxRetries"),
457
318
  filterModelsByPlan: this.field("filterModelsByPlan"),
458
319
  webSearch: this.field("webSearch"),
459
320
  showSidebarQuota: this.field("showSidebarQuota"),
@@ -2294,7 +2155,7 @@ window.__ModuleLoader__.load({
2294
2155
  }
2295
2156
  //#endregion
2296
2157
  //#region package.json
2297
- var version = "0.11.1";
2158
+ var version = "0.11.3";
2298
2159
  var repository = {
2299
2160
  "type": "git",
2300
2161
  "url": "git+https://github.com/Mars-Sea/dsh-commandcode-provider.git"
@@ -2493,6 +2354,7 @@ window.__ModuleLoader__.load({
2493
2354
  "workingDir",
2494
2355
  "requestTimeoutMs",
2495
2356
  "streamIdleTimeoutMs",
2357
+ "transportMaxRetries",
2496
2358
  "filterModelsByPlan",
2497
2359
  "webSearch",
2498
2360
  "showSidebarQuota"
@@ -2638,6 +2500,17 @@ window.__ModuleLoader__.load({
2638
2500
  onReset: () => onReset("streamIdleTimeoutMs"),
2639
2501
  t
2640
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
+ }),
2641
2514
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToggleField, {
2642
2515
  id: "cc-filter-models-by-plan",
2643
2516
  label: t("filterModelsByPlan"),
@@ -3981,12 +3854,68 @@ window.__ModuleLoader__.load({
3981
3854
  }
3982
3855
  //#endregion
3983
3856
  //#region src/client/panel-copy.ts
3984
- /** The panel's literal string table. */
3985
- const PANEL_COPY = {
3857
+ /** The locale namespace both panel slots bind their `t` seat to. */
3858
+ const PANEL_LOCALE_NS = "panel.commandcode";
3859
+ /** The panel's Simplified Chinese string table (the key-set source of truth). */
3860
+ const PANEL_COPY_ZH = {
3861
+ nav: "Command Code",
3862
+ subtitle: "ๅฅ—้คใ€้ขๅบฆไธŽ้…้ข็ช—ๅฃ",
3863
+ refresh: "ๅˆทๆ–ฐ",
3864
+ refreshing: "ๅˆทๆ–ฐไธญโ€ฆ",
3865
+ close: "ๅ…ณ้—ญ",
3866
+ closeHint: "่ฟ”ๅ›žไผš่ฏ",
3867
+ loading: "ๆญฃๅœจ่Žทๅ–่ดฆๆˆท็”จ้‡โ€ฆ",
3868
+ noKey: "ๅฐšๆœช้…็ฝฎ API ๅฏ†้’ฅ",
3869
+ noKeyHint: "ๅœจ ่ฎพ็ฝฎ โ†’ Command Code ไธญ็ฒ˜่ดดๅฏ†้’ฅๆˆ–็™ปๅฝ•ๅŽ๏ผŒ็‚นๅ‡ปๅˆทๆ–ฐใ€‚",
3870
+ plan: "ๅฅ—้ค",
3871
+ credits: "้ขๅบฆ",
3872
+ limits: "้…้ข็ช—ๅฃ",
3873
+ usage: "็”จ้‡",
3874
+ monthly: "ๆœˆ้ขๅบฆ",
3875
+ monthlyLimit: "ๆœˆ้ขๅบฆไธŠ้™",
3876
+ monthlyUsed: "ๆœฌๆœˆๅทฒ็”จ",
3877
+ remaining: "ๅ‰ฉไฝ™",
3878
+ purchased: "ๅทฒ่ดญ",
3879
+ free: "่ต ้€",
3880
+ fiveHour: "5 ๅฐๆ—ถ็ช—ๅฃ",
3881
+ weekly: "ๆฏๅ‘จ็ช—ๅฃ",
3882
+ fiveHourShort: "5 ๅฐๆ—ถ",
3883
+ weeklyShort: "ๆฏๅ‘จ",
3884
+ windowUnlimited: "ไธ้™",
3885
+ exceeded: "ๅทฒ่ถ…้™",
3886
+ exhausted: "ๅทฒ็”จๅฐฝ",
3887
+ resets: "้‡็ฝฎไบŽ",
3888
+ requests: "่ฏทๆฑ‚",
3889
+ failed: "ๅคฑ่ดฅ",
3890
+ successRate: "ๆˆๅŠŸ็އ",
3891
+ spend: "่Šฑ่ดน",
3892
+ tokens: "Token",
3893
+ tokensIn: "ๅ…ฅ",
3894
+ tokensOut: "ๅ‡บ",
3895
+ periodEnds: "่ดฆๆœŸๆˆชๆญข",
3896
+ updated: "ๆ›ดๆ–ฐไบŽ",
3897
+ partial: "้ƒจๅˆ†็ซฏ็‚นๆ•ฐๆฎไธๅฏ็”จ",
3898
+ active: "ๅฝ“ๅ‰ไฝฟ็”จ",
3899
+ coolingDown: "้™้ขๅ†ทๅดไธญ",
3900
+ invalidKey: "ๅฏ†้’ฅๆ— ๆ•ˆ",
3901
+ unconfigured: "ๆœช้…็ฝฎ",
3902
+ unavailable: "ๆ— ๆ•ฐๆฎ",
3903
+ errorInvalidKey: "API ๅฏ†้’ฅๆ— ๆ•ˆๆˆ–ๅทฒ่ฟ‡ๆœŸ",
3904
+ errorInvalidKeyHint: "ๆœๅŠก็ซฏๆ‹’็ปไบ†ๅ…จ้ƒจ่ฏทๆฑ‚๏ผˆ401๏ผ‰ใ€‚่ฏทๆฃ€ๆŸฅ่ฏฅ่ดฆๆˆท็š„ๅฏ†้’ฅ๏ผŒๆˆ–ๅˆฐ commandcode.ai ๆŽงๅˆถๅฐ้‡ๆ–ฐ็”Ÿๆˆใ€‚",
3905
+ errorServiceUnavailable: "Command Code ๆœๅŠกๆš‚ๆ—ถไธๅฏ็”จ",
3906
+ errorServiceUnavailableHint: "ๆœๅŠก็ซฏ่ฟ”ๅ›ž้”™่ฏฏ๏ผˆ5xx๏ผ‰๏ผŒ่ฏท็จๅŽ็‚นๅ‡ปๅˆทๆ–ฐ้‡่ฏ•ใ€‚",
3907
+ errorNetwork: "ๆ— ๆณ•่ฟžๆŽฅ Command Code ๆœๅŠก",
3908
+ errorNetworkHint: "ๆ‰€ๆœ‰่ฏทๆฑ‚้ƒฝๆฒกๆœ‰ๅˆฐ่พพๆœๅŠก็ซฏใ€‚่ฏทๆฃ€ๆŸฅ็ฝ‘็ปœ่ฟžๆŽฅๆˆ– API ๅœฐๅ€่ฎพ็ฝฎใ€‚",
3909
+ errorGeneric: "็”จ้‡่Žทๅ–ๅคฑ่ดฅ"
3910
+ };
3911
+ /** The panel's English string table (must mirror {@link PANEL_COPY_ZH}). */
3912
+ const PANEL_COPY_EN = {
3986
3913
  nav: "Command Code",
3987
3914
  subtitle: "Plans, credits and quota windows",
3988
3915
  refresh: "Refresh",
3989
3916
  refreshing: "Refreshingโ€ฆ",
3917
+ close: "Close",
3918
+ closeHint: "Back to the conversation",
3990
3919
  loading: "Loading account usageโ€ฆ",
3991
3920
  noKey: "No API key configured",
3992
3921
  noKeyHint: "Paste a key โ€” or sign in โ€” under Settings โ†’ Command Code, then refresh.",
@@ -4031,9 +3960,15 @@ window.__ModuleLoader__.load({
4031
3960
  errorNetworkHint: "No request reached the server. Check your network connection or the API base setting.",
4032
3961
  errorGeneric: "Could not fetch account usage"
4033
3962
  };
4034
- /** Look up one panel string. The fallback key keeps a bad call visible, never blank. */
4035
- function panelText(key) {
4036
- return PANEL_COPY[key] ?? key;
3963
+ /** Every panel key, in the English table's declaration order. */
3964
+ const PANEL_KEYS = Object.keys(PANEL_COPY_EN);
3965
+ /**
3966
+ * The English translator. Used as the fallback when no locale seat is
3967
+ * available (a defensive path: every supported engine supplies one) and as the
3968
+ * default in tests that only assert the projection's figures.
3969
+ */
3970
+ function panelTextEN(key) {
3971
+ return PANEL_COPY_EN[key] ?? key;
4037
3972
  }
4038
3973
  //#endregion
4039
3974
  //#region src/client/panel.ts
@@ -4131,7 +4066,7 @@ window.__ModuleLoader__.load({
4131
4066
  };
4132
4067
  }
4133
4068
  /** Build one account's view. */
4134
- function accountView(entry) {
4069
+ function accountView(entry, t) {
4135
4070
  const { report } = entry;
4136
4071
  const account = report.account;
4137
4072
  const plan = report.plan;
@@ -4142,7 +4077,7 @@ window.__ModuleLoader__.load({
4142
4077
  stats.push({
4143
4078
  label: "requests",
4144
4079
  value: String(usage.completedCount),
4145
- sub: `${usage.failedCount} ${panelText("failed")}`
4080
+ sub: `${usage.failedCount} ${t("failed")}`
4146
4081
  });
4147
4082
  stats.push({
4148
4083
  label: "successRate",
@@ -4157,7 +4092,7 @@ window.__ModuleLoader__.load({
4157
4092
  stats.push({
4158
4093
  label: "tokens",
4159
4094
  value: formatTokensCompact(usage.totalTokensIn + usage.totalTokensOut),
4160
- sub: `${formatTokensCompact(usage.totalTokensIn)} ${panelText("tokensIn")} / ${formatTokensCompact(usage.totalTokensOut)} ${panelText("tokensOut")}`
4095
+ sub: `${formatTokensCompact(usage.totalTokensIn)} ${t("tokensIn")} / ${formatTokensCompact(usage.totalTokensOut)} ${t("tokensOut")}`
4161
4096
  });
4162
4097
  }
4163
4098
  const windows = [];
@@ -4208,15 +4143,14 @@ window.__ModuleLoader__.load({
4208
4143
  };
4209
4144
  }
4210
4145
  /**
4211
- * Every panel string, resolved once per projection. One object with all keys
4212
- * (rather than per-field lookups in the components) keeps the copy table and
4213
- * the render sites in lockstep: a key cannot be read from `text` unless
4214
- * {@link PANEL_COPY} declares it.
4146
+ * Every panel string, resolved once per projection through the translator.
4147
+ * One object with all keys (rather than per-field lookups in the components)
4148
+ * keeps the copy table and the render sites in lockstep: a key cannot be read
4149
+ * from `text` unless {@link PANEL_KEYS} declares it.
4215
4150
  */
4216
- function panelStrings() {
4217
- const keys = Object.keys(PANEL_COPY);
4151
+ function panelStrings(t) {
4218
4152
  const out = {};
4219
- for (const key of keys) out[key] = panelText(key);
4153
+ for (const key of PANEL_KEYS) out[key] = t(key);
4220
4154
  return out;
4221
4155
  }
4222
4156
  /**
@@ -4228,6 +4162,7 @@ window.__ModuleLoader__.load({
4228
4162
  */
4229
4163
  function buildPanelView(input) {
4230
4164
  const { usage } = input;
4165
+ const t = input.t ?? panelTextEN;
4231
4166
  const hidden = new Set(input.removingIds ?? []);
4232
4167
  const seen = /* @__PURE__ */ new Set();
4233
4168
  const entries = (usage.report?.accounts ?? []).filter((entry) => {
@@ -4236,7 +4171,7 @@ window.__ModuleLoader__.load({
4236
4171
  seen.add(entry.id);
4237
4172
  return true;
4238
4173
  });
4239
- const accounts = entries.map(accountView);
4174
+ const accounts = entries.map((entry) => accountView(entry, t));
4240
4175
  const selectedEntry = entries.find((entry) => entry.active) ?? entries[0];
4241
4176
  const selectedView = accounts.find((view) => view.id === selectedEntry?.id);
4242
4177
  const footerBars = [];
@@ -4255,18 +4190,18 @@ window.__ModuleLoader__.load({
4255
4190
  const totalCost = selectedEntry?.report.usage?.totalCost;
4256
4191
  const cost = totalCost === void 0 ? "" : money(totalCost);
4257
4192
  let status = "";
4258
- if (entries.length > 0 && !entries.some((entry) => entry.configured)) status = panelText("unconfigured");
4259
- else if (selectedView?.mark !== void 0 && selectedView.mark !== "active") status = panelText(selectedView.mark);
4260
- else if (selectedEntry === void 0) status = panelText("unavailable");
4261
- const planName = selectedView !== void 0 && selectedView.planName !== "" ? selectedView.planName : panelText("nav");
4262
- const titleParts = planName === panelText("nav") ? [planName] : [panelText("nav"), planName];
4193
+ if (entries.length > 0 && !entries.some((entry) => entry.configured)) status = t("unconfigured");
4194
+ else if (selectedView?.mark !== void 0 && selectedView.mark !== "active") status = t(selectedView.mark);
4195
+ else if (selectedEntry === void 0) status = t("unavailable");
4196
+ const planName = selectedView !== void 0 && selectedView.planName !== "" ? selectedView.planName : t("nav");
4197
+ const titleParts = planName === t("nav") ? [planName] : [t("nav"), planName];
4263
4198
  for (const bar of footerBars) {
4264
4199
  const figures = bar.detail === "" ? "" : ` ${bar.detail}`;
4265
- titleParts.push(`${panelText(bar.label)}${figures} (${bar.percent})`);
4200
+ titleParts.push(`${t(bar.label)}${figures} (${bar.percent})`);
4266
4201
  }
4267
- if (cost !== "") titleParts.push(`${panelText("spend")} ${cost}`);
4202
+ if (cost !== "") titleParts.push(`${t("spend")} ${cost}`);
4268
4203
  return {
4269
- text: panelStrings(),
4204
+ text: panelStrings(t),
4270
4205
  planName,
4271
4206
  status,
4272
4207
  footerBars,
@@ -4352,11 +4287,11 @@ window.__ModuleLoader__.load({
4352
4287
  * the sidebar footer card and the dashboard it opens in the center column.
4353
4288
  *
4354
4289
  * Both render one {@link PanelView} projected by `./panel.ts` โ€” no fact is
4355
- * derived here. Strings arrive as keys into `view.text`, and every one of them
4356
- * is English, because the view is built from `./panel-copy.ts` rather than the
4357
- * harness `ctx.locale` namespace (which follows the user's language and would
4358
- * render this panel in Chinese on a Chinese harness โ€” the thing this surface
4359
- * exists to avoid).
4290
+ * derived here. Strings arrive already localized in `view.text`: the slots
4291
+ * declare the `panel.commandcode` locale namespace, so the renderer hands this
4292
+ * component a `t` seat that is passed into `buildPanelView` โ€” the panel follows
4293
+ * the harness's active language (a language switch mints a new `t`, so the
4294
+ * surfaces re-render without an extra channel).
4360
4295
  *
4361
4296
  * The footer card is the panel's home: the sidebar shell renders it in the foot
4362
4297
  * area directly above the Settings seat, so each quota window's own spend and
@@ -4378,7 +4313,8 @@ window.__ModuleLoader__.load({
4378
4313
  return buildPanelView({
4379
4314
  usage,
4380
4315
  apiKeyConfigured: settings.anyAccountConfigured,
4381
- removingIds: settings.accountsRemoving
4316
+ removingIds: settings.accountsRemoving,
4317
+ t: props.t ?? panelTextEN
4382
4318
  });
4383
4319
  }
4384
4320
  /**
@@ -4704,6 +4640,18 @@ window.__ModuleLoader__.load({
4704
4640
  disabled: view.loading,
4705
4641
  onClick: () => props.refresh(),
4706
4642
  children: view.loading ? text("refreshing") : text("refresh")
4643
+ }),
4644
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
4645
+ variant: "ghost",
4646
+ size: "sm",
4647
+ className: "ccp-close",
4648
+ "aria-label": text("close"),
4649
+ title: text("closeHint"),
4650
+ onClick: () => props.close(),
4651
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4652
+ "aria-hidden": "true",
4653
+ children: "ร—"
4654
+ })
4707
4655
  })
4708
4656
  ]
4709
4657
  }),
@@ -5651,6 +5599,165 @@ window.__ModuleLoader__.load({
5651
5599
  "data-ccp-session-cost-anchor": ""
5652
5600
  });
5653
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
+ `;
5654
5761
  //#endregion
5655
5762
  //#region src/client/panel-styles.ts
5656
5763
  /**
@@ -5659,8 +5766,16 @@ window.__ModuleLoader__.load({
5659
5766
  *
5660
5767
  * Returned as a string rather than injected here so the modules stay free of
5661
5768
  * DOM side effects at import time โ€” the client entry installs it once, keyed
5662
- * by the same `data-plugin-css` attribute the settings-page stylesheet uses,
5663
- * 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.
5664
5779
  *
5665
5780
  * Every colour comes from a harness theme alias with a neutral fallback, so
5666
5781
  * the panel follows the active theme (light/dark and any brand pack) without
@@ -5673,9 +5788,12 @@ window.__ModuleLoader__.load({
5673
5788
  * Stylesheet id (the `data-plugin-css` value that makes injection idempotent).
5674
5789
  *
5675
5790
  * The package prefix must match the one the settings-page stylesheet uses
5676
- * (`injectPageCss` in `./index.ts`) and this package's real name: an id is the
5677
- * injection's identity, so a stale fork prefix would let a second copy of the
5678
- * 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.
5679
5797
  */
5680
5798
  const PANEL_CSS_ID = "@mars-sea/dsh-commandcode-provider/CommandCodePanel.module.css";
5681
5799
  /** The panel stylesheet. */
@@ -5689,11 +5807,28 @@ window.__ModuleLoader__.load({
5689
5807
 
5690
5808
  The shell's container is a flex ROW whose occupants (this card and ui-cordis's
5691
5809
  footer chip) each declare a full-width line and shrink-proof flex, so as a row
5692
- it would overflow the column. Both were written for a full-width line, which
5693
- is exactly what a column gives them. Matched by the CSS-module class STEM โ€”
5694
- never a hashed name โ€” so a dsh that renames it degrades to the shell's own
5695
- row rather than breaking. */
5696
- [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}
5697
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}
5698
5833
  .ccp-foot:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover);border-color:var(--dsw-alias-border-l2)}
5699
5834
  .ccp-foot:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:1px}
@@ -5738,6 +5873,11 @@ window.__ModuleLoader__.load({
5738
5873
  .ccp-subtitle{margin:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
5739
5874
  .ccp-spacer{flex:1}
5740
5875
  .ccp-meta{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5;font-variant-numeric:tabular-nums}
5876
+ /* The dashboard's exit: an icon-sized glyph button. The primitive's own
5877
+ geometry is kept (size="sm"); only the glyph is enlarged and optically
5878
+ centred, so it reads as a dismiss control rather than a text button. */
5879
+ .ccp-close{min-width:28px;justify-content:center;padding-left:0;padding-right:0}
5880
+ .ccp-close span{font-size:16px;line-height:1}
5741
5881
  .ccp-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
5742
5882
 
5743
5883
  /* Notices: the no-key guidance, a blocked report, and a stale-data error. */
@@ -5814,6 +5954,8 @@ window.__ModuleLoader__.load({
5814
5954
  requestTimeoutMsHint: "็ญ‰ๅพ…ๅ“ๅบ”้ฆ–ไธชๅญ—่Š‚็š„่ถ…ๆ—ถ๏ผ›้ป˜่ฎค 60000ใ€‚",
5815
5955
  streamIdleTimeoutMs: "ๆต็ฉบ้—ฒ่ถ…ๆ—ถ๏ผˆๆฏซ็ง’๏ผ‰",
5816
5956
  streamIdleTimeoutMsHint: "็”Ÿๆˆๆตๅœๆปžๅคšไน…่ง†ไธบๆ–ญ่ฟž๏ผ›้ป˜่ฎค 300000๏ผˆ้•ฟๆ€่€ƒๆจกๅž‹ๅฏ้™้ป˜ๆ•ฐๅˆ†้’Ÿ๏ผŒ้ป˜่ฎคๅ€ผๅˆปๆ„ๆ”พๅฎฝ๏ผ‰ใ€‚",
5957
+ transportMaxRetries: "็ฝ‘็ปœๅคฑ่ดฅ้‡่ฏ•ๆฌกๆ•ฐ",
5958
+ transportMaxRetriesHint: "่ฟžๆŽฅๅคฑ่ดฅๆ—ถ่‡ชๅŠจ้‡่ฏ•ๅ‡ ๆฌก๏ผŒ้ป˜่ฎค 5๏ผˆๅ„ๆฌก็ญ‰ๅพ… 0.5+1+2+4+8 ็ง’๏ผŒๅˆ่ฎก็บฆ 15 ็ง’๏ผ‰ใ€‚่ถ…่ฟ‡ๅŽ็›ดๆŽฅๆŠฅ้”™๏ผŒไธๅ†ๆŒ‰ๆŒ‡ๆ•ฐ้€€้ฟ้•ฟๆ—ถ้—ด็ญ‰ๅพ…๏ผ›ๅกซ 0 ่กจ็คบไธ้‡่ฏ•ใ€‚้™ๆตใ€5xx ็ญ‰็”ฑๆœๅŠก็ซฏ่ฆๆฑ‚้‡่ฏ•็š„ๅคฑ่ดฅไธๅ—ๆญค้กนๅฝฑๅ“ใ€‚",
5817
5959
  advancedSettings: "้ซ˜็บง่ฎพ็ฝฎ",
5818
5960
  advancedSettingsHint: "API ๅœฐๅ€ใ€ๅทฅไฝœ็›ฎๅฝ•ใ€่ถ…ๆ—ถใ€ๆจกๅž‹่ฟ‡ๆปคใ€่”็ฝ‘ๆœ็ดขไธŽไพง่พนๆ ้ขๅบฆๅก็‰‡็ญ‰ไธๅธธไฟฎๆ”น็š„้€‰้กนใ€‚",
5819
5961
  advancedOverriddenOne: "ๅทฒ่‡ชๅฎšไน‰ 1 ้กน",
@@ -5948,6 +6090,8 @@ window.__ModuleLoader__.load({
5948
6090
  requestTimeoutMsHint: "Time to wait for the first response byte; default 60000.",
5949
6091
  streamIdleTimeoutMs: "Stream idle timeout (ms)",
5950
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.",
5951
6095
  advancedSettings: "Advanced",
5952
6096
  advancedSettingsHint: "Rarely touched options: API base URL, working directory, timeouts, model filtering, web search, and the sidebar quota card.",
5953
6097
  advancedOverriddenOne: "1 customized",
@@ -6067,141 +6211,13 @@ window.__ModuleLoader__.load({
6067
6211
  };
6068
6212
  //#endregion
6069
6213
  //#region src/client/index.ts
6070
- /** CSS for the settings page, injected once (harness bundle convention). */
6071
- const PAGE_CSS = `
6072
- .cc-section{max-width:720px;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px;display:flex}
6073
- .cc-title{margin:0;font-size:18px;font-weight:600}
6074
- .cc-intro{color:var(--dsw-alias-label-tertiary);margin:0;font-size:13px;line-height:1.5}
6075
- .cc-readOnly{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
6076
- .cc-card{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;padding:4px 16px}
6077
- .cc-field{flex-direction:column;gap:6px;padding:12px 0;display:flex}
6078
- .cc-field+.cc-field{border-top:1px solid var(--dsw-alias-border-l2)}
6079
- .cc-fieldHead{align-items:center;gap:8px;display:flex}
6080
- .cc-label{min-width:0;color:var(--dsw-alias-label-primary);flex:1;font-size:13px;font-weight:500;line-height:1.5}
6081
- .cc-badges{align-items:center;gap:8px;display:inline-flex}
6082
- .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}
6083
- .cc-badgeMuted{white-space:nowrap;color:var(--dsw-alias-label-tertiary);border-radius:999px;padding:1px 8px;font-size:11px;line-height:17px}
6084
- .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}
6085
- .cc-reset:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
6086
- .cc-reset:disabled{cursor:default;opacity:.5}
6087
- .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}
6088
- .cc-input:focus-visible{border-color:var(--dsw-alias-brand-primary);outline:none}
6089
- .cc-input:disabled{color:var(--dsw-alias-label-tertiary);cursor:default}
6090
- /* The routing-rule model multi-select: a button trigger that opens an
6091
- * anchored Menu of checkbox rows. The trigger mirrors .cc-input sizing so it
6092
- * sits flush with the sibling account select. */
6093
- .cc-ruleTrigger{align-items:center;gap:8px;display:flex;width:100%;text-align:left;cursor:pointer}
6094
- .cc-ruleTrigger:disabled{cursor:default}
6095
- .cc-ruleTriggerText{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6096
- .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)}
6097
- .cc-checkRow{align-items:center;gap:8px;display:inline-flex;min-width:0}
6098
- .cc-checkRow:hover{cursor:pointer}
6099
- .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}
6100
- .cc-check:checked{background:var(--dsw-alias-brand-primary);border-color:var(--dsw-alias-brand-primary)}
6101
- .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)}
6102
- .cc-checkName{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
6103
- /* The model multi-select search box: stacked under the trigger while the
6104
- * dropdown is open, same input sizing so the pair reads as one control. The
6105
- * box lives inside the Menu anchor (which renders inside the Menu's root
6106
- * span), so focusing/typing it never trips the Menu's outside-click close. */
6107
- .cc-modelSelectAnchor{flex-direction:column;gap:6px;display:flex;width:100%}
6108
- .cc-modelSearch{width:100%}
6109
- .cc-modelSearch::-webkit-search-cancel-button{cursor:pointer}
6110
- /* Selects need their own treatment to sit flush with the text inputs:
6111
- * the UA stylesheet renders <select> border-box (34px total vs the inputs'
6112
- * 36px) and forces its own menulist text metrics, so drop the native
6113
- * chrome entirely (appearance:none), restore content-box so the outer box
6114
- * matches the inputs again, and draw the chevron ourselves. Longhand
6115
- * background-* only โ€” the shorthand would reset .cc-input's background. */
6116
- 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}
6117
- .cc-inputInvalid{border-color:var(--dsw-alias-label-error)}
6118
- .cc-invalid{color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5}
6119
- /* The Advanced card: its header row is one full-width toggle button; the
6120
- * chevron is two borders on a rotated square (no icon dependency). */
6121
- .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}
6122
- .cc-advancedHead:hover .cc-advancedTitle{color:var(--dsw-alias-label-primary)}
6123
- .cc-advancedTitle{color:var(--dsw-alias-label-primary);font-size:13px;font-weight:500;line-height:1.5}
6124
- .cc-advancedSpacer{flex:1}
6125
- .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}
6126
- .cc-chevronUp{transform:rotate(-135deg);margin-bottom:-3px}
6127
- .cc-advancedBody{flex-direction:column;display:flex}
6128
- .cc-advancedBody>.cc-field:first-of-type{border-top:1px solid var(--dsw-alias-border-l2)}
6129
- .cc-hint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
6130
- .cc-footer{justify-content:flex-end;align-items:center;gap:8px;display:flex}
6131
- .cc-toggleRow{align-items:center;gap:8px;cursor:pointer;display:flex}
6132
- .cc-toggleRow:has(.cc-toggle:disabled){cursor:default}
6133
- .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}
6134
- .cc-toggle:checked{background:var(--dsw-alias-brand-primary)}
6135
- .cc-toggle::after{content:'';background:#fff;border-radius:50%;width:14px;height:14px;position:absolute;top:2px;left:2px;transition:left .15s ease}
6136
- .cc-toggle:checked::after{left:14px}
6137
- .cc-toggle:disabled{cursor:default;opacity:.5}
6138
- .cc-failed{min-width:0;color:var(--dsw-alias-label-error);flex:1;margin:0;font-size:12px;line-height:1.5}
6139
- .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}
6140
- .cc-usageHead{align-items:center;gap:8px;display:flex}
6141
- .cc-usageTitle{color:var(--dsw-alias-label-primary);flex:1;margin:0;font-size:13px;font-weight:600;line-height:1.5}
6142
- .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}
6143
- .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}
6144
- .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}
6145
- .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}
6146
- .cc-usageRefresh:hover:not(:disabled){color:var(--dsw-alias-label-primary)}
6147
- .cc-usageRefresh:disabled{cursor:default;opacity:.5}
6148
- .cc-usageHint{color:var(--dsw-alias-label-tertiary);margin:0;font-size:12px;line-height:1.5}
6149
- .cc-usageError{align-items:center;gap:8px;color:var(--dsw-alias-label-error);margin:0;font-size:12px;line-height:1.5;display:flex}
6150
- .cc-usageStats{grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:8px;display:grid}
6151
- .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}
6152
- .cc-usageStatLabel{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
6153
- .cc-usageStatValue{color:var(--dsw-alias-label-primary);font-size:15px;font-weight:600;line-height:1.4}
6154
- .cc-usageStatSub{color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:1.5}
6155
- .cc-usageWindows{flex-direction:column;gap:16px;display:flex}
6156
- .cc-usageWindow{flex-direction:column;gap:6px;display:flex}
6157
- .cc-usageWindowHead{align-items:baseline;gap:8px;display:flex}
6158
- .cc-usageWindowLabel{color:var(--dsw-alias-label-secondary);flex:1;font-size:12px;font-weight:500;line-height:1.5}
6159
- .cc-usageWindowValue{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:500;line-height:1.5}
6160
- .cc-usageExceeded{color:var(--dsw-alias-label-error);font-size:11px;font-weight:500;line-height:1.5}
6161
- .cc-usageBar{overflow:hidden;background:var(--dsw-alias-bg-layer-1);border-radius:999px;height:6px}
6162
- .cc-usageBarFill{background:var(--dsw-alias-brand-primary);border-radius:999px;height:100%;transition:width .3s ease}
6163
- .cc-usageBarFillWarn{background:var(--dsw-alias-label-error)}
6164
- @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}
6165
- .cc-usageMeta{align-items:center;gap:8px;display:flex}
6166
- .cc-accountReport{flex-direction:column;gap:12px;display:flex}
6167
- .cc-tabs{flex-wrap:wrap;gap:6px;display:flex}
6168
- .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}
6169
- .cc-tab:hover:not(.cc-tabActive){color:var(--dsw-alias-label-primary)}
6170
- .cc-tabActive{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-brand-primary)}
6171
- .cc-tabDotOk{background:var(--dsw-alias-brand-primary);border-radius:50%;width:6px;height:6px}
6172
- .cc-tabDotWarn{background:#d97706;border-radius:50%;width:6px;height:6px}
6173
- .cc-tabDotError{background:var(--dsw-alias-label-error);border-radius:50%;width:6px;height:6px}
6174
- .cc-usageMetaSpacer{flex:1}
6175
- .cc-usageUpdated{color:var(--dsw-alias-label-tertiary);margin:0;font-size:11px;line-height:1.5}
6176
- .cc-usagePartial{color:var(--dsw-alias-label-error);margin:0;font-size:11px;line-height:1.5}
6177
- .cc-usageBlocked{border:1px solid var(--dsw-alias-label-error);border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:4px}
6178
- .cc-usageBlockedTitle{color:var(--dsw-alias-label-error);margin:0;font-size:13px;font-weight:600;line-height:1.5}
6179
- .cc-usageBlockedHint{color:var(--dsw-alias-label-secondary);margin:0;font-size:12px;line-height:1.5}
6180
- .cc-version{margin:4px 0 0;text-align:center;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:1.5}
6181
- /* The Models-page provider panel root: unstyled by design โ€” the row card the
6182
- * Models page owns supplies the surface, the panel only stacks its controls. */
6183
- .cc-providerCard{flex-direction:column;display:flex}
6184
- /* The update hint rides the footer version line: warning-tinted (with a
6185
- * muted fallback for themes without the alias), quiet until hovered. */
6186
- .cc-versionLink{color:var(--dsw-alias-state-warning-primary,var(--dsw-alias-label-secondary));text-decoration:none}
6187
- .cc-versionLink:hover{color:var(--dsw-alias-label-primary);text-decoration:underline;text-underline-position:under}
6188
- /* The login panel rides the connection card as one more field row; the
6189
- * authorization link is the only branded element on it. */
6190
- .cc-loginLink{color:var(--dsw-alias-brand-primary);text-decoration:none;font-size:12px;line-height:1.5}
6191
- .cc-loginLink:hover{text-decoration:underline;text-underline-position:under}
6192
- .cc-loginDone{color:var(--dsw-alias-state-success-primary,var(--dsw-alias-label-secondary))}
6193
- .cc-loginError{color:var(--dsw-alias-label-error)}
6194
- .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}
6195
- .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))}
6196
- `;
6197
6214
  /** Inject the page stylesheet once (idempotent per tag). */
6198
6215
  function injectPageCss() {
6199
6216
  if (typeof document === "undefined") return;
6200
- const id = "@mars-sea/dsh-commandcode-provider/CommandCodeSettingsPage.module.css";
6201
- 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;
6202
6218
  const tag = document.createElement("style");
6203
6219
  tag.dataset.plugin = "@mars-sea/dsh-commandcode-provider";
6204
- tag.dataset.pluginCss = id;
6220
+ tag.dataset.pluginCss = PAGE_CSS_ID;
6205
6221
  tag.textContent = PAGE_CSS;
6206
6222
  document.head.appendChild(tag);
6207
6223
  }
@@ -6231,6 +6247,15 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6231
6247
  */
6232
6248
  const PANEL_ID = "commandcode-panel";
6233
6249
  /**
6250
+ * ui-conversation's reserved `main` key, used only as the exit fallback for a
6251
+ * layout whose `selectPanel` predates the `null` "show the Conversation"
6252
+ * selection (see `close` in {@link applyClientSurfaces}). Declared locally for
6253
+ * the same reason as {@link PANEL_ID}: ui-conversation is not a dependency of
6254
+ * this bundle, and the key is a published contract of the layout (its README
6255
+ * names `conversation` as reserved for the Conversation).
6256
+ */
6257
+ const CONVERSATION_PANEL_ID = "conversation";
6258
+ /**
6234
6259
  * The composer figure's entry id in `conversation.composer.dock`. Its own id,
6235
6260
  * not the shipped `stats` cell's: reusing `stats` would REPLACE the tokens /
6236
6261
  * cache-hit / throughput readout rather than inject into it, and that readout
@@ -6245,12 +6270,15 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6245
6270
  */
6246
6271
  function apply(ctx) {
6247
6272
  injectPageCss();
6248
- const connection = ctx.get("connection");
6249
- if (connection !== void 0) installFriendlyImageError(connection, () => ctx.locale.getLocale().active === "zh" ? "zh" : "en");
6250
6273
  ctx.effect(() => ctx.locale.register("settings.commandcode", {
6251
6274
  zh,
6252
6275
  en
6253
6276
  }), "dsh-commandcode-provider: page copy");
6277
+ ctx.effect(() => ctx.locale.register(PANEL_LOCALE_NS, {
6278
+ zh: PANEL_COPY_ZH,
6279
+ en: PANEL_COPY_EN
6280
+ }), "dsh-commandcode-provider: panel copy");
6281
+ const connection = ctx.get("connection");
6254
6282
  const legacyApi = adaptLegacyCredentials(connection?.api?.credentials);
6255
6283
  if (legacyApi !== void 0) {
6256
6284
  applyClientSurfaces(ctx, legacyApi, connection?.hostDescription);
@@ -6463,6 +6491,19 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6463
6491
  open: () => {
6464
6492
  const layout = ctx.get("layout");
6465
6493
  if (typeof layout?.selectPanel === "function") layout.selectPanel(PANEL_ID);
6494
+ },
6495
+ close: () => {
6496
+ const layout = ctx.get("layout");
6497
+ if (typeof layout?.selectPanel !== "function") return;
6498
+ try {
6499
+ layout.selectPanel(null);
6500
+ } catch {
6501
+ try {
6502
+ layout.selectPanel(CONVERSATION_PANEL_ID);
6503
+ } catch (error) {
6504
+ console.error("[dsh-commandcode-provider] could not close the plans & quota panel:", error);
6505
+ }
6506
+ }
6466
6507
  }
6467
6508
  });
6468
6509
  ctx.effect(() => injectPanelCss(), "dsh-commandcode-provider: panel styles");
@@ -6470,6 +6511,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6470
6511
  ctx.slots.inject("main", () => ctx.slots.register({
6471
6512
  name: "main",
6472
6513
  key: PANEL_ID,
6514
+ locale: PANEL_LOCALE_NS,
6473
6515
  inject: panelFace
6474
6516
  }, CommandCodePanel));
6475
6517
  } catch (error) {
@@ -6482,6 +6524,7 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6482
6524
  name: "sidebar.footer.action",
6483
6525
  id: PANEL_ID,
6484
6526
  order: 1,
6527
+ locale: PANEL_LOCALE_NS,
6485
6528
  inject: panelFace
6486
6529
  }, CommandCodeFooterEntry));
6487
6530
  } catch (error) {
@@ -6509,8 +6552,6 @@ select.cc-input{appearance:none;-webkit-appearance:none;-moz-appearance:none;box
6509
6552
  //#endregion
6510
6553
  exports.apply = apply;
6511
6554
  exports.inject = inject;
6512
- exports.isImageSessionRejection = isImageSessionRejection;
6513
- exports.withFriendlyImageError = withFriendlyImageError;
6514
6555
  return module.exports;
6515
6556
  }
6516
6557
  });