@eddyskywalker/dsh-chatgpt-subscription 0.1.12 → 0.1.14

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
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ - 设置页新增内置子代理模型选择,新启动的 spawn/fork 子代理可统一路由到指定的 ChatGPT 订阅模型。
6
+ - 设置页新增 GPT-5.6 Sol / Terra / Luna 有效上下文窗口配置:默认 272K,可选最高 1M,并动态影响 DSH 的模型解析、压缩阈值与溢出判断。
7
+
3
8
  ## 0.1.12 - 2026-08-20
4
9
 
5
10
  - 适配 DSH `0.1.0-rc.8`:peerDependencies / devDependencies 中全部 DSH 包范围从 `^0.1.0-rc.6` 提升至 `^0.1.0-rc.8`,并重新生成 package-lock.json。已在本机 rc.8 依赖集上验证:typecheck、55 项测试与 tsdown 构建全部通过,插件代码无需改动。
package/README.md CHANGED
@@ -39,6 +39,8 @@
39
39
  **设置页**
40
40
 
41
41
  - 展示账号(脱敏 email、套餐、账号 ID 后四位)、连接状态、额度与订阅增强功能开关;
42
+ - 可选择新启动的内置子代理使用哪个 ChatGPT 订阅模型;该设置只改变子代理请求路由,不改变父会话模型;
43
+ - 5.6 Sol / Terra / Luna 默认使用 272K 有效上下文,可在设置中选择最高 1M,用于 DSH 压缩与溢出判断;其他模型保持目录声明值;
42
44
  - 可访问的进度条、窄窗口/200% 缩放布局、深浅主题与 reduced-motion。
43
45
 
44
46
  ## 模型目录
@@ -108,16 +110,17 @@ npx @deepseek-ai/dsh plugin --profile web add "link:C:\absolute\path\to\dsh-chat
108
110
  3. 完成 ChatGPT 登录;
109
111
  4. 执行 **测试连接**。
110
112
 
111
- DSH 模型选择器应显示 **“Codex(ChatGPT 订阅)”**。
113
+ DSH 模型选择器应显示 **“Codex(ChatGPT 订阅)”**。在同一设置页的“增强功能”中,还可配置新子代理模型和 GPT-5.6 系列的有效上下文窗口。子代理模型设置适用于 DSH 内置的 spawn/fork 子代理(其会话标记为 `origin: subagent`);外部 ACP/Codex CLI 等 provider-managed 子代理不经过本插件路由。
112
114
 
113
115
  卸载前建议先在设置页点击 **“注销”**,它会删除当前平台的凭据和 Host 内存中的额度缓存。
114
116
 
115
117
  若 DSH 已异常退出,可在确认路径后手动处理凭据文件:
116
118
 
117
119
  - Windows:`%DSH_HOME%\storages\dsh-chatgpt-subscription\oauth.dpapi`,未设置 `DSH_HOME` 时为 `%USERPROFILE%\.dsh\storages\dsh-chatgpt-subscription\oauth.dpapi`;
120
+ - macOS:凭据位于登录钥匙串,可执行 `security delete-generic-password -s dsh-chatgpt-subscription -a oauth` 删除;
118
121
  - Linux:`$DSH_HOME/storages/dsh-chatgpt-subscription/oauth.json`,未设置 `DSH_HOME` 时为 `~/.dsh/storages/dsh-chatgpt-subscription/oauth.json`。
119
122
 
120
- > Windows 文件只能由创建它的用户通过 DPAPI 解密。Linux 文件是未额外加密的 JSON,依赖目录 `0700` 和文件 `0600` 隔离;不要复制、打印或提交该文件。跨平台迁移需要重新登录。
123
+ > Windows 文件只能由创建它的用户通过 DPAPI 解密。macOS 凭据由登录钥匙串在本机加密保存。Linux 文件是未额外加密的 JSON,依赖目录 `0700` 和文件 `0600` 隔离;不要复制、打印或提交该文件。跨平台迁移需要重新登录。
121
124
 
122
125
  ## 安全边界
123
126
 
@@ -125,8 +128,9 @@ DSH 模型选择器应显示 **“Codex(ChatGPT 订阅)”**。
125
128
  - OAuth token 只发送到 `https://auth.openai.com/oauth/token`;
126
129
  - 模型、图片、搜索与额度地址分别固定为 `https://chatgpt.com/backend-api/codex/responses`、`https://chatgpt.com/backend-api/codex/images/generations`、`https://chatgpt.com/backend-api/codex/alpha/search` 和 `https://chatgpt.com/backend-api/wham/usage`,没有 endpoint override;
127
130
  - Windows token bundle 使用 CurrentUser DPAPI;明文只经过 Host 内存和 PowerShell stdin/stdout;
131
+ - macOS token bundle 存入登录钥匙串,通过系统 `security` 命令读写;明文只经过 Host 内存和 `security` 命令行参数,Keychain 在本机加密保存;
128
132
  - Linux token bundle 原子写入当前用户私有文件并在读取时校验普通文件、所有者与 `0600` 权限,同时拒绝符号链接;该文件没有应用层加密,同 UID 进程、root、备份和磁盘快照仍可读取;
129
- - 两个平台的 token 都不会进入浏览器、`settings.yaml` 或日志;
133
+ - 所有平台的 token 都不会进入浏览器、`settings.yaml` 或日志;
130
134
  - 工具调用只有在参数是完整 JSON 对象时才产生可执行的 `block-end`;畸形参数终止本次生成;
131
135
  - 所有修改状态的路由只接受同源 JSON POST,并校验 `Origin` 与 `Host`。
132
136
 
package/lib/client.js CHANGED
@@ -175,8 +175,6 @@ window.__ModuleLoader__.load({
175
175
  const text = blocks.map((block) => block.type === "text" || block.type === "reasoning" ? block.text : "").filter(Boolean).join(" ").trim();
176
176
  return text === "" ? null : text;
177
177
  }
178
- //#endregion
179
- //#region src/shared/model-catalog.ts
180
178
  const CODEX_MODEL_CATALOG = [
181
179
  {
182
180
  id: "gpt-5.6-sol",
@@ -242,7 +240,15 @@ window.__ModuleLoader__.load({
242
240
  supportsReasoningSummary: false
243
241
  }
244
242
  ];
245
- CODEX_MODEL_CATALOG[0];
243
+ const DEFAULT_CODEX_MODEL = CODEX_MODEL_CATALOG[0];
244
+ const CONFIGURABLE_CONTEXT_MODEL_IDS = [
245
+ "gpt-5.6-sol",
246
+ "gpt-5.6-terra",
247
+ "gpt-5.6-luna"
248
+ ];
249
+ function resolveCodexCatalogEntry(model) {
250
+ return CODEX_MODEL_CATALOG.find((entry) => entry.id === model) ?? DEFAULT_CODEX_MODEL;
251
+ }
246
252
  //#endregion
247
253
  //#region src/client/api.ts
248
254
  var SubscriptionApi = class {
@@ -309,6 +315,7 @@ window.__ModuleLoader__.load({
309
315
  const [authUrl, setAuthUrl] = (0, react.useState)(null);
310
316
  const [popupBlocked, setPopupBlocked] = (0, react.useState)(false);
311
317
  const [connection, setConnection] = (0, react.useState)(null);
318
+ const [contextDrafts, setContextDrafts] = (0, react.useState)({});
312
319
  const load = (0, react.useCallback)(async (quiet = false) => {
313
320
  if (!quiet) setError(null);
314
321
  try {
@@ -419,6 +426,19 @@ window.__ModuleLoader__.load({
419
426
  preferences
420
427
  });
421
428
  });
429
+ const updateContextWindow = async (model) => {
430
+ const current = status?.preferences.contextWindowOverrides[model] ?? resolveCodexCatalogEntry(model).contextWindow;
431
+ const parsed = parseCapacity(contextDrafts[model] ?? String(current));
432
+ if (parsed === null) {
433
+ setError(t("contextWindowInvalid"));
434
+ return;
435
+ }
436
+ await updatePreferences({ contextWindowOverrides: { [model]: parsed } });
437
+ setContextDrafts((drafts) => ({
438
+ ...drafts,
439
+ [model]: String(parsed)
440
+ }));
441
+ };
422
442
  const logout = async () => run("logout", async () => {
423
443
  await apiRef.current.logout();
424
444
  eventSourceRef.current?.close();
@@ -564,38 +584,88 @@ window.__ModuleLoader__.load({
564
584
  }),
565
585
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
566
586
  title: t("enhancements"),
567
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
568
- className: "dsh-codex-pref-row",
569
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("searchProvider") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
570
- className: "dsh-codex-muted",
571
- children: t("searchProviderHint")
572
- })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
573
- className: "dsh-codex-segments",
574
- role: "radiogroup",
575
- "aria-label": t("searchProvider"),
576
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
577
- type: "radio",
578
- name: "dsh-codex-search-provider",
579
- checked: status?.preferences.searchProvider === "dsh",
587
+ children: [
588
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
589
+ className: "dsh-codex-pref-row",
590
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("searchProvider") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
591
+ className: "dsh-codex-muted",
592
+ children: t("searchProviderHint")
593
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
594
+ className: "dsh-codex-segments",
595
+ role: "radiogroup",
596
+ "aria-label": t("searchProvider"),
597
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
598
+ type: "radio",
599
+ name: "dsh-codex-search-provider",
600
+ checked: status?.preferences.searchProvider === "dsh",
601
+ disabled: busy !== null,
602
+ onChange: () => updatePreferences({ searchProvider: "dsh" })
603
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("searchProviderDsh") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
604
+ type: "radio",
605
+ name: "dsh-codex-search-provider",
606
+ checked: status?.preferences.searchProvider === "codex",
607
+ disabled: busy !== null,
608
+ onChange: () => updatePreferences({ searchProvider: "codex" })
609
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("searchProviderCodex") })] })]
610
+ })]
611
+ }),
612
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
613
+ className: "dsh-codex-pref-row",
614
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("subagentModel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
615
+ className: "dsh-codex-muted",
616
+ children: t("subagentModelHint")
617
+ })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
618
+ className: "dsh-codex-select",
619
+ "aria-label": t("subagentModel"),
620
+ value: status?.preferences.subagentModel ?? CODEX_MODEL_CATALOG[0].id,
580
621
  disabled: busy !== null,
581
- onChange: () => updatePreferences({ searchProvider: "dsh" })
582
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("searchProviderDsh") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
583
- type: "radio",
584
- name: "dsh-codex-search-provider",
585
- checked: status?.preferences.searchProvider === "codex",
622
+ onChange: (event) => void updatePreferences({ subagentModel: event.currentTarget.value }),
623
+ children: CODEX_MODEL_CATALOG.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
624
+ value: model.id,
625
+ children: model.name
626
+ }, model.id))
627
+ })]
628
+ }),
629
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
630
+ className: "dsh-codex-context-settings",
631
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("contextWindows") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
632
+ className: "dsh-codex-muted",
633
+ children: t("contextWindowsHint")
634
+ })] }), CONFIGURABLE_CONTEXT_MODEL_IDS.map((model) => {
635
+ const fallback = status?.preferences.contextWindowOverrides[model] ?? resolveCodexCatalogEntry(model).contextWindow;
636
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
637
+ className: "dsh-codex-context-row",
638
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: resolveCodexCatalogEntry(model).name }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
639
+ className: "dsh-codex-capacity-control",
640
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
641
+ type: "text",
642
+ inputMode: "numeric",
643
+ value: contextDrafts[model] ?? formatCapacity(fallback),
644
+ disabled: busy !== null,
645
+ "aria-label": resolveCodexCatalogEntry(model).name + " " + t("contextWindow"),
646
+ onChange: (event) => setContextDrafts((drafts) => ({
647
+ ...drafts,
648
+ [model]: event.currentTarget.value
649
+ })),
650
+ onBlur: () => void updateContextWindow(model),
651
+ onKeyDown: (event) => {
652
+ if (event.key === "Enter") event.currentTarget.blur();
653
+ }
654
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("tokens") })]
655
+ })]
656
+ }, model);
657
+ })]
658
+ }),
659
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
660
+ className: "dsh-codex-check",
661
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
662
+ type: "checkbox",
663
+ checked: status?.preferences.quickQuotaVisible === true,
586
664
  disabled: busy !== null,
587
- onChange: () => updatePreferences({ searchProvider: "codex" })
588
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("searchProviderCodex") })] })]
589
- })]
590
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
591
- className: "dsh-codex-check",
592
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
593
- type: "checkbox",
594
- checked: status?.preferences.quickQuotaVisible === true,
595
- disabled: busy !== null,
596
- onChange: (event) => updatePreferences({ quickQuotaVisible: event.currentTarget.checked })
597
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("quickQuota") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("quickQuotaHint") })] })]
598
- })]
665
+ onChange: (event) => updatePreferences({ quickQuotaVisible: event.currentTarget.checked })
666
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("quickQuota") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("small", { children: t("quickQuotaHint") })] })]
667
+ })
668
+ ]
599
669
  }),
600
670
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Section, {
601
671
  title: t("quota"),
@@ -700,6 +770,7 @@ window.__ModuleLoader__.load({
700
770
  function storageLabel(storage, t) {
701
771
  if (storage === void 0 || !storage.available) return t("storageUnavailable");
702
772
  if (storage.kind === "windows-dpapi") return t("storageWindows");
773
+ if (storage.kind === "macos-keychain") return t("storageMacKeychain");
703
774
  if (storage.kind === "linux-file") return t("storageLinuxFile");
704
775
  if (storage.kind === "memory") return t("storageMemory");
705
776
  return t("storageUnavailable");
@@ -707,6 +778,7 @@ window.__ModuleLoader__.load({
707
778
  function storageNotice(storage, t) {
708
779
  if (storage === void 0 || !storage.available) return t("securityUnavailable");
709
780
  if (storage.kind === "windows-dpapi") return t("securityWindows");
781
+ if (storage.kind === "macos-keychain") return t("securityMacKeychain");
710
782
  if (storage.kind === "linux-file") return t("securityLinuxFile");
711
783
  if (storage.kind === "memory") return t("securityMemory");
712
784
  return t("securityUnavailable");
@@ -775,6 +847,17 @@ window.__ModuleLoader__.load({
775
847
  unitDisplay: "long"
776
848
  }).format(value)} ${t("limitWindow")}`;
777
849
  }
850
+ function parseCapacity(value) {
851
+ const matched = value.trim().toLowerCase().replace(/[,_\s]/g, "").match(/^(\d+(?:\.\d+)?)(k|m)?$/);
852
+ if (matched === null) return null;
853
+ const multiplier = matched[2] === "m" ? 1e6 : matched[2] === "k" ? 1e3 : 1;
854
+ const parsed = Number(matched[1]) * multiplier;
855
+ return Number.isSafeInteger(parsed) && parsed >= 1 && parsed <= 1e6 ? parsed : null;
856
+ }
857
+ function formatCapacity(value) {
858
+ if (value % 1e3 === 0) return `${value / 1e3}K`;
859
+ return String(value);
860
+ }
778
861
  function formatPercent(value) {
779
862
  return `${new Intl.NumberFormat(void 0, { maximumFractionDigits: 1 }).format(value)}%`;
780
863
  }
@@ -819,10 +902,12 @@ window.__ModuleLoader__.load({
819
902
  expires: "令牌到期",
820
903
  storage: "凭据存储",
821
904
  storageWindows: "Windows DPAPI(当前用户加密)",
905
+ storageMacKeychain: "macOS 钥匙串(Keychain 加密)",
822
906
  storageLinuxFile: "Linux 用户私有文件(权限 0600)",
823
907
  storageMemory: "仅 Host 内存(不持久化)",
824
908
  storageUnavailable: "凭据存储不可用",
825
909
  securityWindows: "令牌由 Host 使用 Windows CurrentUser DPAPI 加密,不会进入浏览器、settings.yaml 或日志。",
910
+ securityMacKeychain: "令牌由 Host 存入 macOS 登录钥匙串(Keychain),在本机加密保存,不会进入浏览器、settings.yaml 或日志。",
826
911
  securityLinuxFile: "令牌仅写入 Host 上当前 Linux 用户可读的 0600 文件,但不会额外加密;同 UID 进程、root、备份和磁盘快照仍可能读取。",
827
912
  securityMemory: "令牌仅保留在 Host 内存中,Host 退出后丢失。",
828
913
  securityUnavailable: "Host 无法安全访问凭据存储;请修复文件所有者或权限后重试。",
@@ -849,6 +934,13 @@ window.__ModuleLoader__.load({
849
934
  searchProviderHint: "选择 DSH Web 搜索工具使用默认来源,或改用当前 ChatGPT 订阅的 Codex 搜索来源。",
850
935
  searchProviderDsh: "DSH 默认",
851
936
  searchProviderCodex: "Codex 订阅",
937
+ subagentModel: "子代理模型",
938
+ subagentModelHint: "新启动的内置子代理使用此 ChatGPT 订阅模型;父会话的模型选择不受影响。",
939
+ contextWindows: "GPT-5.6 上下文窗口",
940
+ contextWindowsHint: "默认 272K;可选启用最高 1M。该值用于 DSH 的压缩与溢出判断,可输入 1M、512K 等容量。",
941
+ contextWindow: "上下文窗口",
942
+ contextWindowInvalid: "上下文窗口必须是 1 到 1M 的整数,可使用 K 或 M 后缀。",
943
+ tokens: "tokens",
852
944
  quickQuota: "在输入框旁显示快捷用量",
853
945
  quickQuotaHint: "仅在当前会话选中 codex-chatgpt 模型时显示。",
854
946
  quota: "用量与限额",
@@ -900,10 +992,12 @@ window.__ModuleLoader__.load({
900
992
  expires: "Token expires",
901
993
  storage: "Credential storage",
902
994
  storageWindows: "Windows DPAPI (current-user encrypted)",
995
+ storageMacKeychain: "macOS Keychain (encrypted)",
903
996
  storageLinuxFile: "Linux user-private file (mode 0600)",
904
997
  storageMemory: "Host memory only (not persistent)",
905
998
  storageUnavailable: "Credential storage unavailable",
906
999
  securityWindows: "The Host encrypts tokens with Windows CurrentUser DPAPI; they never enter the browser, settings.yaml, or logs.",
1000
+ securityMacKeychain: "The Host stores tokens in the macOS login Keychain, encrypted on this machine; they never enter the browser, settings.yaml, or logs.",
907
1001
  securityLinuxFile: "Tokens are stored in a mode-0600 file readable only by the current Linux user, but are not additionally encrypted; same-UID processes, root, backups, and disk snapshots can still read them.",
908
1002
  securityMemory: "Tokens remain only in Host memory and are lost when the Host exits.",
909
1003
  securityUnavailable: "The Host cannot safely access credential storage. Fix the file owner or permissions and retry.",
@@ -930,6 +1024,13 @@ window.__ModuleLoader__.load({
930
1024
  searchProviderHint: "Choose whether DSH web search uses its default provider or the Codex search source from this ChatGPT subscription.",
931
1025
  searchProviderDsh: "DSH default",
932
1026
  searchProviderCodex: "Codex subscription",
1027
+ subagentModel: "Subagent model",
1028
+ subagentModelHint: "New built-in subagents use this ChatGPT subscription model; the parent session selection is unchanged.",
1029
+ contextWindows: "GPT-5.6 context windows",
1030
+ contextWindowsHint: "Defaults to 272K, with an optional value up to 1M. DSH uses it for compaction and overflow detection; values such as 1M and 512K are accepted.",
1031
+ contextWindow: "Context window",
1032
+ contextWindowInvalid: "The context window must be an integer from 1 to 1M. K and M suffixes are accepted.",
1033
+ tokens: "tokens",
933
1034
  quickQuota: "Show quick usage beside the composer",
934
1035
  quickQuotaHint: "Shown only when the current session uses a codex-chatgpt model.",
935
1036
  quota: "Usage and limits",
@@ -1008,6 +1109,14 @@ window.__ModuleLoader__.load({
1008
1109
  .dsh-codex-segments input:checked+span{background:var(--dsw-alias-bg-base);box-shadow:0 1px 2px rgba(0,0,0,.08);color:var(--dsw-alias-label-primary)}
1009
1110
  .dsh-codex-segments input:focus-visible+span{outline:2px solid var(--dsw-alias-button-info-fill,#397ee8);outline-offset:2px}
1010
1111
  .dsh-codex-segments input:disabled+span{cursor:default;opacity:.5}
1112
+ .dsh-codex-select,.dsh-codex-capacity-control input{background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l2);border-radius:7px;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;padding:7px 9px}
1113
+ .dsh-codex-select{max-width:210px;min-width:150px}
1114
+ .dsh-codex-context-settings{border-bottom:1px solid var(--dsw-alias-border-l2);display:grid;gap:10px;padding:12px 0}
1115
+ .dsh-codex-context-settings>div>strong{font-size:13px;font-weight:600}
1116
+ .dsh-codex-context-row{align-items:center;display:flex;font-size:12px;gap:14px;justify-content:space-between}
1117
+ .dsh-codex-capacity-control{align-items:center;display:flex;gap:7px}
1118
+ .dsh-codex-capacity-control input{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;text-align:right;width:90px}
1119
+ .dsh-codex-capacity-control small{color:var(--dsw-alias-label-tertiary);font-size:11px;width:42px}
1011
1120
  .dsh-codex-check{align-items:flex-start;border-bottom:1px solid var(--dsw-alias-border-l2);cursor:pointer;display:flex;gap:10px;padding:12px 0}
1012
1121
  .dsh-codex-check input{flex:none;margin-top:2px}
1013
1122
  .dsh-codex-check small{color:var(--dsw-alias-label-secondary);display:block;font-size:12px;line-height:1.45;margin-top:2px}
@@ -1046,7 +1155,7 @@ window.__ModuleLoader__.load({
1046
1155
  .dsh-codex-image-loading,.dsh-codex-image-failed{border:1px dashed var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-tertiary);display:inline-flex;font-size:12px;line-height:1.4;padding:18px 20px}
1047
1156
  .dsh-codex-image-failed{color:var(--dsw-alias-label-danger,#d94b4b)}
1048
1157
  @keyframes dsh-codex-pulse{0%,100%{opacity:.55}50%{opacity:1}}
1049
- @media(max-width:560px){.dsh-codex-row,.dsh-codex-pref-row,.dsh-codex-quota-fact{align-items:flex-start;flex-direction:column;gap:3px}.dsh-codex-value,.dsh-codex-quota-fact strong{text-align:left}.dsh-codex-actions{justify-content:flex-start}.dsh-codex-grouphead{align-items:flex-start;flex-direction:column;gap:0;padding:12px 0}.dsh-codex-meter-meta{align-items:flex-start;flex-direction:column;gap:2px}.dsh-codex-errorbar{align-items:flex-start;flex-direction:column}.dsh-codex-segments{width:100%}.dsh-codex-segments label{flex:1}.dsh-codex-segments span{text-align:center}.dsh-codex-image-tool{max-width:100%;margin-left:0}}
1158
+ @media(max-width:560px){.dsh-codex-row,.dsh-codex-pref-row,.dsh-codex-quota-fact{align-items:flex-start;flex-direction:column;gap:3px}.dsh-codex-value,.dsh-codex-quota-fact strong{text-align:left}.dsh-codex-actions{justify-content:flex-start}.dsh-codex-grouphead{align-items:flex-start;flex-direction:column;gap:0;padding:12px 0}.dsh-codex-meter-meta{align-items:flex-start;flex-direction:column;gap:2px}.dsh-codex-errorbar{align-items:flex-start;flex-direction:column}.dsh-codex-segments{width:100%}.dsh-codex-select{max-width:none;width:100%}.dsh-codex-context-row{align-items:flex-start;flex-direction:column;gap:5px}.dsh-codex-segments label{flex:1}.dsh-codex-segments span{text-align:center}.dsh-codex-image-tool{max-width:100%;margin-left:0}}
1050
1159
  @media(prefers-reduced-motion:reduce){.dsh-codex-meter>span{transition:none}.dsh-codex-skeleton span{animation:none}}
1051
1160
  `;
1052
1161
  function installStyles() {