dsh-codex-community 0.0.2 → 0.0.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
@@ -3,6 +3,36 @@
3
3
  本项目遵循 Keep a Changelog 的结构;`0.0.x` 为技术预览。
4
4
  This project follows the Keep a Changelog structure; `0.0.x` is a technical preview.
5
5
 
6
+ ## [0.0.3] - 2026-08-29
7
+
8
+ ### 中文
9
+
10
+ #### 新增
11
+
12
+ - OpenAI Codex 设置入口改用独立的代码括号图标,与其他 Provider 插件保持可辨识的视觉差异。
13
+ - 额度卡新增带图标的刷新按钮、刷新中状态、响应式双列布局和更清晰的剩余额度层级;五小时额度始终显示精确重置时间,每周额度在不足 24 小时时显示精确时间,其余时间显示相对天数,完整时间保留在提示与无障碍标签中。
14
+ - 模型设置按“已选择在前、未选择在后”排序;未选择模型默认收起并可按需展开,同时显示已选择、当前显示与总数摘要。
15
+
16
+ #### 修复
17
+
18
+ - 缩短设置页内容高度并避免插件覆盖 Harness 弹窗宽度或滚动容器,改善窄屏与较矮窗口中的滚动体验。
19
+ - 模型能力改为与 Grok Build 一致的紧凑标签,使用 `272K`、`128K` 一类上下文与最大输出表示,不推测 provider catalog 未提供的能力。
20
+ - 在收起未选择模型时取消勾选,键盘焦点会回到展开按钮,避免焦点停留在隐藏元素;重置时间解析也会拒绝无法构造有效日期的值。
21
+
22
+ ### English
23
+
24
+ #### Added
25
+
26
+ - The OpenAI Codex settings entry now uses a dedicated code-brackets icon that remains visually distinct from other provider plugins.
27
+ - Usage cards now provide an icon button with a refreshing state, a responsive two-column layout, and a clearer remaining-usage hierarchy. Five-hour limits always show the exact reset time; weekly limits show the exact time within 24 hours and otherwise use relative days, while the complete timestamp remains available in tooltips and accessible labels.
28
+ - Model settings now sort selected models before unselected models. Unselected models are collapsed by default and can be expanded on demand, with selected, visible, and total counts shown in the summary.
29
+
30
+ #### Fixed
31
+
32
+ - The settings content is shorter and no longer overrides the Harness dialog width or scroll container, improving scrolling in narrow or low-height windows.
33
+ - Model capabilities use compact Grok Build-style labels such as `272K` and `128K` for context and maximum output, without inferring capabilities absent from the provider catalog.
34
+ - Deselecting a model while unselected entries are collapsed returns keyboard focus to the expansion control instead of leaving it on a hidden element; reset-time parsing also rejects values that cannot produce a valid date.
35
+
6
36
  ## [0.0.2] - 2026-08-29
7
37
 
8
38
  ### 中文
package/README.en.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  A Codex plugin for DeepSeek Harness with ChatGPT OAuth sign-in, Codex models, image input, and reliable streaming responses.
6
6
 
7
- > Current version: `0.0.2` technical preview, published under npm `latest` with a full GitHub Release. npm package: `dsh-codex-community`.
7
+ > Current version: `0.0.3` technical preview, published under npm `latest` with a full GitHub Release. npm package: `dsh-codex-community`.
8
8
 
9
9
  ## Features
10
10
 
@@ -16,6 +16,8 @@ A Codex plugin for DeepSeek Harness with ChatGPT OAuth sign-in, Codex models, im
16
16
  - classify `AccountQuotaExceeded` as non-retryable account quota and show a sanitized reset time;
17
17
  - preserve safe partial text after a stream failure, while incomplete tool calls stop without replaying the request;
18
18
  - read the real five-hour and weekly usage limits whenever the settings page opens or is refreshed manually, with a safe fallback and no OAuth credentials exposed to the Web page;
19
+ - present remaining usage in compact cards: five-hour limits show the exact reset time, while weekly limits show the exact time within 24 hours and otherwise use relative days;
20
+ - list selected models first in settings and collapse unselected models by default, with compact `K` labels taken from the current provider catalog for context and maximum output;
19
21
  - use the lightning button to the left of the model selector or `/codex` to choose official Fast (1.5×) and transport for the current session, with Fast off and transport automatic by default.
20
22
 
21
23
  ## Requirements
@@ -28,7 +30,7 @@ A Codex plugin for DeepSeek Harness with ChatGPT OAuth sign-in, Codex models, im
28
30
  Install an exact version:
29
31
 
30
32
  ```sh
31
- dsh plugin --profile web add dsh-codex-community@0.0.2
33
+ dsh plugin --profile web add dsh-codex-community@0.0.3
32
34
  dsh web
33
35
  ```
34
36
 
@@ -56,7 +58,7 @@ Fast and transport preferences are kept only for the current session in the curr
56
58
  Update:
57
59
 
58
60
  ```sh
59
- dsh plugin --profile web update dsh-codex-community@0.0.2
61
+ dsh plugin --profile web update dsh-codex-community@0.0.3
60
62
  dsh web
61
63
  ```
62
64
 
@@ -79,8 +81,8 @@ See [Troubleshooting](docs/troubleshooting.en.md) for diagnostic steps.
79
81
  ## Support boundaries
80
82
 
81
83
  - The `0.0.x` line does not promise a stable API; pin the exact version.
82
- - Linux, macOS, and Windows CI/profile smoke for `0.0.2` reached `3/3`; the accepted commit and platform evidence are in the [acceptance record](docs/releases/v0.0.2.acceptance.json).
83
- - Live OAuth, conversation, image, transport, and Fast network acceptance is `0/13 pending`. These unverified capabilities remain clearly disclosed and will be completed individually after publication.
84
+ - Linux, macOS, and Windows CI/profile smoke for `0.0.3` reached `3/3`; the accepted commit and platform evidence are in the [acceptance record](docs/releases/v0.0.3.acceptance.json).
85
+ - Live OAuth, conversation, image, transport, and Fast network acceptance for `0.0.3` starts in a fresh record at `0/13 pending`. These unverified capabilities remain clearly disclosed and will be completed individually after publication.
84
86
  - The settings page reads usage through the Web-backend compatibility endpoint used by the official Codex client. If that endpoint is unavailable or malformed, the page retains the latest safe reading and falls back to request observation or an unknown state instead of presenting the error as zero remaining usage.
85
87
  - Model names, context windows, and maximum outputs come from the installed provider catalog. The reasoning picker exposes only subscription-Codex levels through Max that the Provider can fully implement, plus each model's verified default. Generic `Default`, `Off`, and `Minimal` entries are hidden, and agent-level `Ultra` is not disguised as a plain reasoning effort.
86
88
  - Fast is limited to GPT-5.4, GPT-5.5, GPT-5.6 Luna, Sol, and Terra and consumes more usage. Other models never receive the Fast service tier.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  面向 DeepSeek Harness 的 Codex 插件,提供 ChatGPT OAuth 登录、Codex 模型、图片输入和可靠的流式回复。
6
6
 
7
- > 当前版本:`0.0.2` 技术预览,使用 npm `latest` 与正式 GitHub Release 发布。npm 包名:`dsh-codex-community`。
7
+ > 当前版本:`0.0.3` 技术预览,使用 npm `latest` 与正式 GitHub Release 发布。npm 包名:`dsh-codex-community`。
8
8
 
9
9
  ## 功能
10
10
 
@@ -16,6 +16,8 @@
16
16
  - 将 `AccountQuotaExceeded` 识别为不可重试的账户配额错误,并显示脱敏后的重置时间;
17
17
  - 流中断时保留安全的半段纯文本;涉及未完成工具调用时停止,不自动重放请求;
18
18
  - 每次进入设置页或手动刷新时读取真实的 5 小时与每周额度;读取失败时安全降级,不向 Web 页面暴露 OAuth 凭据;
19
+ - 使用紧凑的额度卡展示剩余比例:5 小时额度显示精确重置时间,每周额度在不足 24 小时时显示精确时间,其余时间显示相对天数;
20
+ - 在设置页优先展示已选择模型,默认收起未选择模型;上下文和最大输出使用来自当前 provider catalog 的紧凑 `K` 标签;
19
21
  - 使用模型选择器左侧的闪电按钮或 `/codex` 为当前会话切换官方 Fast(1.5 倍速)和传输方式,默认关闭 Fast、自动选择传输。
20
22
 
21
23
  ## 运行要求
@@ -28,7 +30,7 @@
28
30
  请固定安装精确版本:
29
31
 
30
32
  ```sh
31
- dsh plugin --profile web add dsh-codex-community@0.0.2
33
+ dsh plugin --profile web add dsh-codex-community@0.0.3
32
34
  dsh web
33
35
  ```
34
36
 
@@ -56,7 +58,7 @@ Fast 和传输偏好只保存在当前进程的当前会话中,不会持久化
56
58
  更新:
57
59
 
58
60
  ```sh
59
- dsh plugin --profile web update dsh-codex-community@0.0.2
61
+ dsh plugin --profile web update dsh-codex-community@0.0.3
60
62
  dsh web
61
63
  ```
62
64
 
@@ -79,8 +81,8 @@ dsh web
79
81
  ## 支持边界
80
82
 
81
83
  - `0.0.x` 不承诺稳定 API,请固定版本;
82
- - `0.0.2` 的 Linux、macOS 和 Windows CI/profile smoke 已达到 `3/3`,候选提交与平台证据见[验收记录](docs/releases/v0.0.2.acceptance.json);
83
- - 真实 OAuth、对话、图片、transport 和 Fast 网络验收为 `0/13 pending`;这些未验证能力继续如实展示,并在发布后逐项补齐;
84
+ - `0.0.3` 的 Linux、macOS 和 Windows CI/profile smoke 已达到 `3/3`,候选提交与平台证据见[验收记录](docs/releases/v0.0.3.acceptance.json);
85
+ - `0.0.3` 的真实 OAuth、对话、图片、transport 和 Fast 网络验收从独立记录的 `0/13 pending` 开始;这些未验证能力继续如实展示,并在发布后逐项补齐;
84
86
  - 设置页额度通过官方 Codex 客户端使用的 Web 后端兼容接口读取;接口不可用或返回异常时保留最近的安全读数,并降级为请求观测或未知状态,不把错误伪装成零余额;
85
87
  - 模型名称、上下文和最大输出来自当前安装的 provider catalog;推理选择器只展示已按订阅 Codex 目录核验且 Provider 能完整实现的 Low 至 Max 与模型默认值,不展示通用的 `Default`、`Off`、`Minimal`,也不把需要主动任务委派的 `Ultra` 伪装成普通推理档位;
86
88
  - Fast 仅用于 GPT-5.4、GPT-5.5、GPT-5.6 Luna、Sol 和 Terra,并会提高额度消耗;其他模型不会发送 Fast service tier;
@@ -28,6 +28,7 @@ window.__ModuleLoader__.load({
28
28
  unavailable: "当前环境没有可用的 Codex 登录流程。",
29
29
  writableNo: "当前凭据存储不可写。",
30
30
  refresh: "刷新",
31
+ refreshing: "刷新中…",
31
32
  signIn: "登录",
32
33
  signInWithChatGPT: "使用 ChatGPT 登录",
33
34
  signInAgain: "重新登录",
@@ -60,19 +61,22 @@ window.__ModuleLoader__.load({
60
61
  quotaWeekly: "每周额度",
61
62
  quotaMinuteWindow: "分钟额度",
62
63
  quotaRemaining: "剩余",
63
- quotaUsed: "已使用",
64
64
  quotaResetsAt: "重置时间",
65
65
  quotaUpdatedAt: "数据更新时间",
66
+ quotaResetSoon: "即将重置",
67
+ quotaResetPrefix: "",
68
+ quotaResetSuffix: "后重置",
69
+ quotaResetMinute: "分钟",
70
+ quotaResetHour: "小时",
71
+ quotaResetDay: "天",
66
72
  quotaRecentSuccess: "最近一次 Codex 请求成功",
67
73
  quotaSuccessCaution: "这只表示最近一次请求成功,不代表账户剩余额度。",
68
74
  quotaExhausted: "最近观测到 Codex 账户额度已用尽。",
69
75
  quotaObservedAt: "观测时间",
70
76
  quotaResetAt: "观测到的重置时间",
71
- quotaResetIn: "预计还有约",
72
- quotaMinutes: "分钟",
73
77
  quotaNoReset: "未获得通过校验的重置时间;状态会在有限时间后自动回到未知。",
74
78
  modelsTitle: "当前安装的 Codex 模型",
75
- modelsDescription: "选择要在模型选择器中显示的 Codex 模型。当前目录条目的名称、上下文窗口和最大输出直接来自当前安装的 Codex provider catalog,不推测目录未提供的能力;隐藏后,已有会话中保存的精确模型仍可继续使用。",
79
+ modelsDescription: "选择在模型选择器中显示的 Codex 模型。名称、上下文和最大输出来自当前安装的 provider catalog;隐藏模型不影响已有会话。",
76
80
  modelsContextWindow: "上下文",
77
81
  modelsMaxOutput: "最大输出",
78
82
  modelsLoading: "正在读取 Codex 模型目录…",
@@ -83,8 +87,11 @@ window.__ModuleLoader__.load({
83
87
  modelsSaving: "正在保存…",
84
88
  modelsSaved: "模型启用设置已保存。",
85
89
  modelsAtLeastOne: "至少需要启用一个 Codex 模型。",
86
- modelsAllEnabledFollow: "已启用全部当前目录模型;当前覆盖不含额外模型字段,保存后会清除覆盖并自动启用未来新增的目录模型。",
87
- modelsAllEnabledPreserve: "已启用全部当前目录模型;为保留现有模型参数,保存后会固定当前模型列表,未来新增的目录模型不会自动启用。",
90
+ modelsAllEnabledFollow: "已启用全部当前目录模型;保存后将清除列表覆盖,未来新增模型会自动显示。",
91
+ modelsAllEnabledPreserve: "已启用全部当前目录模型;为保留其他模型参数,保存后会固定当前列表,未来新增模型不会自动显示。",
92
+ modelsShowMore: "显示未选择的 {count} 个模型",
93
+ modelsCollapse: "收起未选择的模型",
94
+ modelsSummary: "已选择 {selected}/{total} · 当前显示 {visible}/{total}",
88
95
  modelsRetry: "重新读取",
89
96
  modelsLoadFailed: "读取 Codex 模型目录失败。",
90
97
  modelsSaveFailed: "保存模型启用设置失败。",
@@ -112,6 +119,7 @@ window.__ModuleLoader__.load({
112
119
  unavailable: "No Codex sign-in flow is available in this environment.",
113
120
  writableNo: "The credential store is currently read-only.",
114
121
  refresh: "Refresh",
122
+ refreshing: "Refreshing…",
115
123
  signIn: "Sign in",
116
124
  signInWithChatGPT: "Sign in with ChatGPT",
117
125
  signInAgain: "Sign in again",
@@ -144,19 +152,22 @@ window.__ModuleLoader__.load({
144
152
  quotaWeekly: "Weekly limit",
145
153
  quotaMinuteWindow: "minute limit",
146
154
  quotaRemaining: "Remaining",
147
- quotaUsed: "Used",
148
155
  quotaResetsAt: "Resets",
149
156
  quotaUpdatedAt: "Data updated",
157
+ quotaResetSoon: "Resets soon",
158
+ quotaResetPrefix: "Resets in",
159
+ quotaResetSuffix: "",
160
+ quotaResetMinute: "minute",
161
+ quotaResetHour: "hour",
162
+ quotaResetDay: "day",
150
163
  quotaRecentSuccess: "The latest Codex request succeeded",
151
164
  quotaSuccessCaution: "This only describes the latest request; it does not represent remaining account quota.",
152
165
  quotaExhausted: "Codex account quota was exhausted in the latest observation.",
153
166
  quotaObservedAt: "Observed at",
154
167
  quotaResetAt: "Observed reset time",
155
- quotaResetIn: "Approximately",
156
- quotaMinutes: "minutes remaining",
157
168
  quotaNoReset: "No reset time passed validation; this state automatically returns to unknown after a bounded interval.",
158
169
  modelsTitle: "Codex models in this installation",
159
- modelsDescription: "Choose which Codex models appear in the model selector. For current catalog entries, names, context windows, and maximum output values come directly from the installed Codex provider catalog; capabilities omitted by the catalog are not inferred. Hidden models remain available to existing conversations that saved an exact model.",
170
+ modelsDescription: "Choose which Codex models appear in the model selector. Names, context windows, and maximum output values come from the installed provider catalog; hidden models do not affect existing conversations.",
160
171
  modelsContextWindow: "Context",
161
172
  modelsMaxOutput: "Max output",
162
173
  modelsLoading: "Reading the Codex model catalog…",
@@ -167,8 +178,11 @@ window.__ModuleLoader__.load({
167
178
  modelsSaving: "Saving…",
168
179
  modelsSaved: "Enabled models saved.",
169
180
  modelsAtLeastOne: "At least one Codex model must remain enabled.",
170
- modelsAllEnabledFollow: "All current catalog models are enabled. Because the override has no additional model fields, saving will clear it and automatically enable future catalog additions.",
171
- modelsAllEnabledPreserve: "All current catalog models are enabled. To preserve existing model parameters, saving will keep the current model list; future catalog additions will not be enabled automatically.",
181
+ modelsAllEnabledFollow: "All current catalog models are enabled. Saving clears the list override, so future models appear automatically.",
182
+ modelsAllEnabledPreserve: "All current catalog models are enabled. To preserve other model settings, saving fixes the current list and future models will not appear automatically.",
183
+ modelsShowMore: "Show {count} unselected models",
184
+ modelsCollapse: "Hide unselected models",
185
+ modelsSummary: "Selected {selected}/{total} · Showing {visible}/{total}",
172
186
  modelsRetry: "Reload",
173
187
  modelsLoadFailed: "Could not read the Codex model catalog.",
174
188
  modelsSaveFailed: "Could not save the enabled models.",
@@ -184,6 +198,23 @@ window.__ModuleLoader__.load({
184
198
  legacyReasoningRepairHelp: "This conversation saved an unsupported Off/Minimal level. Click to switch to this model's current default. Like using the model selector, this also saves it as the default model for future conversations.",
185
199
  }
186
200
 
201
+ const NAV_ICON_MARKER = "data-dsh-codex-community-nav-icon"
202
+ const NAV_ICON_STYLE_SELECTOR = 'style[data-plugin-nav-icon="dsh-codex-community"]'
203
+ const NAV_ICON_STATE_KEY = Symbol.for("dsh-codex-community.settings-nav-icon.v1")
204
+ const NAV_ICON_LABELS = new Set([zh.nav.trim(), en.nav.trim()])
205
+ const SETTINGS_DIALOG_SELECTOR = '[role="dialog"][aria-modal="true"]'
206
+ const SETTINGS_NAV_SELECTOR = `${SETTINGS_DIALOG_SELECTOR} > nav`
207
+ const SETTINGS_NAV_BUTTON_SELECTOR = `${SETTINGS_NAV_SELECTOR} button`
208
+ // settings.section has no icon slot in Harness 0.1.1-rc.2. This original code-brackets glyph is applied only to the unique Codex nav entry.
209
+ const CODE_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
210
+ <path d="M5.25 3.25 1.5 8l3.75 4.75M10.75 3.25 14.5 8l-3.75 4.75M9.25 2.5l-2.5 11" fill="none" stroke="black" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
211
+ </svg>`
212
+ const CODE_ICON_MASK = `data:image/svg+xml,${encodeURIComponent(CODE_ICON_SVG).replace(/[!'()*]/gu, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`)}`
213
+ const NAV_ICON_CSS = [
214
+ `[${NAV_ICON_MARKER}]>svg:first-child{display:none!important}`,
215
+ `[${NAV_ICON_MARKER}]::before{content:"";width:16px;height:16px;flex:none;background:currentColor;-webkit-mask:url("${CODE_ICON_MASK}") center/contain no-repeat;mask:url("${CODE_ICON_MASK}") center/contain no-repeat}`,
216
+ ].join("")
217
+
187
218
  function createAuthorizationClient(connection) {
188
219
  const call = async (endpoint, payload, signal) => {
189
220
  const result = await connection.rpc.call(CHANNEL, endpoint, payload, signal)
@@ -421,7 +452,9 @@ window.__ModuleLoader__.load({
421
452
  return { status: "unknown" }
422
453
  }
423
454
  if (value.resetAt === undefined) return { status: value.status, observedAt: value.observedAt }
424
- if (!Number.isSafeInteger(value.resetAt) || value.resetAt <= now) return { status: "unknown" }
455
+ if (!Number.isSafeInteger(value.resetAt) || value.resetAt <= now || !Number.isFinite(new Date(value.resetAt).getTime())) {
456
+ return { status: "unknown" }
457
+ }
425
458
  return {
426
459
  status: value.status,
427
460
  observedAt: value.observedAt,
@@ -458,7 +491,7 @@ window.__ModuleLoader__.load({
458
491
  if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined
459
492
  if (typeof value.usedPercent !== "number" || !Number.isFinite(value.usedPercent) || value.usedPercent < 0 || value.usedPercent > 100) return undefined
460
493
  if (!Number.isSafeInteger(value.windowDurationMins) || value.windowDurationMins < 1 || value.windowDurationMins > 525_600) return undefined
461
- if (!Number.isSafeInteger(value.resetsAt) || value.resetsAt < 0) return undefined
494
+ if (!Number.isSafeInteger(value.resetsAt) || value.resetsAt < 0 || !Number.isFinite(new Date(value.resetsAt).getTime())) return undefined
462
495
  return {
463
496
  usedPercent: value.usedPercent,
464
497
  windowDurationMins: value.windowDurationMins,
@@ -476,6 +509,65 @@ window.__ModuleLoader__.load({
476
509
  return `${window.windowDurationMins} ${t("quotaMinuteWindow")}`
477
510
  }
478
511
 
512
+ function formatResetDistance(resetsAt, t, now = Date.now()) {
513
+ if (!Number.isSafeInteger(resetsAt) || !Number.isSafeInteger(now) || resetsAt < 0 || now < 0) {
514
+ return t("quotaResetSoon")
515
+ }
516
+ const remaining = resetsAt - now
517
+ if (remaining <= 60_000) return t("quotaResetSoon")
518
+ let count
519
+ let unit
520
+ if (remaining < 60 * 60_000) {
521
+ count = Math.ceil(remaining / 60_000)
522
+ unit = t("quotaResetMinute")
523
+ } else if (remaining < 24 * 60 * 60_000) {
524
+ count = Math.ceil(remaining / (60 * 60_000))
525
+ unit = t("quotaResetHour")
526
+ } else {
527
+ count = Math.ceil(remaining / (24 * 60 * 60_000))
528
+ unit = t("quotaResetDay")
529
+ }
530
+ if (t("locale").toLowerCase().startsWith("zh")) {
531
+ return `${count} ${unit}${t("quotaResetSuffix")}`
532
+ }
533
+ return `${t("quotaResetPrefix")} ${count} ${unit}${count === 1 ? "" : "s"}`
534
+ }
535
+
536
+ function renderResetTime(resetsAt, t, { showExact = false, now = Date.now() } = {}) {
537
+ const exact = new Date(resetsAt).toLocaleString(t("locale"))
538
+ const relative = formatResetDistance(resetsAt, t, now)
539
+ const separator = t("locale").toLowerCase().startsWith("zh") ? ":" : ": "
540
+ const exactLabel = `${t("quotaResetsAt")}${separator}${exact}`
541
+ return h("time", {
542
+ dateTime: new Date(resetsAt).toISOString(),
543
+ title: exactLabel,
544
+ "aria-label": showExact ? exactLabel : `${relative} · ${exactLabel}`,
545
+ }, showExact ? exactLabel : relative)
546
+ }
547
+
548
+ function refreshIcon() {
549
+ return h("svg", {
550
+ className: "dshCodexRefreshIcon",
551
+ viewBox: "0 0 16 16",
552
+ fill: "none",
553
+ "aria-hidden": "true",
554
+ focusable: "false",
555
+ },
556
+ h("path", {
557
+ d: "M13.25 5.75A5.5 5.5 0 1 0 13.1 10.6",
558
+ stroke: "currentColor",
559
+ strokeWidth: "1.4",
560
+ strokeLinecap: "round",
561
+ }),
562
+ h("path", {
563
+ d: "M10.25 5.75h3v-3",
564
+ stroke: "currentColor",
565
+ strokeWidth: "1.4",
566
+ strokeLinecap: "round",
567
+ strokeLinejoin: "round",
568
+ }))
569
+ }
570
+
479
571
  function renderObservedQuota(quota, t) {
480
572
  if (quota.status === "recent-success") {
481
573
  return h(React.Fragment, null,
@@ -488,7 +580,9 @@ window.__ModuleLoader__.load({
488
580
  h("p", { className: "dshCodexMuted" }, `${t("quotaObservedAt")} · ${new Date(quota.observedAt).toLocaleString(t("locale"))}`),
489
581
  quota.resetAt === undefined
490
582
  ? h("p", { className: "dshCodexMuted" }, t("quotaNoReset"))
491
- : h("p", { className: "dshCodexMuted" }, `${t("quotaResetAt")} · ${new Date(quota.resetAt).toLocaleString(t("locale"))} · ${t("quotaResetIn")} ${quota.remainingMinutes} ${t("quotaMinutes")}`))
583
+ : h("p", { className: "dshCodexMuted" },
584
+ `${t("quotaResetAt")} · `,
585
+ renderResetTime(quota.resetAt, t)))
492
586
  }
493
587
  return h(React.Fragment, null,
494
588
  h("p", { className: "dshCodexQuotaState dshCodexQuotaUnknown" }, t("quotaUnknown")),
@@ -496,6 +590,7 @@ window.__ModuleLoader__.load({
496
590
  }
497
591
 
498
592
  function renderAccountUsage(usage, t) {
593
+ const now = Date.now()
499
594
  return h(React.Fragment, null,
500
595
  ...usage.rateLimits.map((limit) => {
501
596
  const windows = [limit.primary, limit.secondary].filter((window) => window !== undefined)
@@ -504,26 +599,30 @@ window.__ModuleLoader__.load({
504
599
  : limit.limitName ?? limit.limitId
505
600
  return h("section", { key: limit.limitId, className: "dshCodexQuotaGroup" },
506
601
  h("p", { className: "dshCodexQuotaProduct" }, limitLabel),
507
- ...windows.map((window) => {
508
- const remaining = Math.max(0, Math.min(100, 100 - window.usedPercent))
509
- const remainingText = percentText(remaining)
510
- const usedText = percentText(window.usedPercent)
511
- const label = usageWindowLabel(window, t)
512
- return h("div", { key: `${limit.limitId}-${window.windowDurationMins}`, className: "dshCodexQuotaWindow" },
513
- h("div", { className: "dshCodexQuotaWindowHeader" },
514
- h("span", null, label),
515
- h("strong", null, `${t("quotaRemaining")} ${remainingText}%`)),
516
- h("progress", {
517
- max: 100,
518
- value: remaining,
519
- "aria-label": `${limitLabel} · ${label} · ${t("quotaRemaining")} ${remainingText}%`,
520
- }),
521
- h("div", { className: "dshCodexQuotaMeta" },
522
- h("span", null, `${t("quotaUsed")} ${usedText}%`),
523
- h("span", null, `${t("quotaResetsAt")}:${new Date(window.resetsAt).toLocaleString(t("locale"))}`)))
524
- }))
602
+ h("div", { className: "dshCodexQuotaWindows" },
603
+ ...windows.map((window) => {
604
+ const remaining = Math.max(0, Math.min(100, 100 - window.usedPercent))
605
+ const remainingText = percentText(remaining)
606
+ const label = usageWindowLabel(window, t)
607
+ const resetDistance = window.resetsAt - now
608
+ const showExactReset = window.windowDurationMins === 300
609
+ || (window.windowDurationMins === 10_080
610
+ && resetDistance > 0
611
+ && resetDistance < 24 * 60 * 60_000)
612
+ return h("div", { key: `${limit.limitId}-${window.windowDurationMins}`, className: "dshCodexQuotaWindow" },
613
+ h("div", { className: "dshCodexQuotaWindowHeader" },
614
+ h("span", null, label),
615
+ h("strong", null, `${t("quotaRemaining")} ${remainingText}%`)),
616
+ h("progress", {
617
+ max: 100,
618
+ value: remaining,
619
+ "aria-label": `${limitLabel} · ${label} · ${t("quotaRemaining")} ${remainingText}%`,
620
+ }),
621
+ h("div", { className: "dshCodexQuotaMeta" },
622
+ renderResetTime(window.resetsAt, t, { showExact: showExactReset, now })))
623
+ })))
525
624
  }),
526
- h("p", { className: "dshCodexMuted dshCodexQuotaUpdated" }, `${t("quotaUpdatedAt")}:${new Date(usage.observedAt).toLocaleString(t("locale"))}`))
625
+ h("p", { className: "dshCodexMuted dshCodexQuotaUpdated" }, `${t("quotaUpdatedAt")}${t("locale").toLowerCase().startsWith("zh") ? ":" : ": "}${new Date(usage.observedAt).toLocaleString(t("locale"))}`))
527
626
  }
528
627
 
529
628
  function objectAtPath(root, path) {
@@ -1103,6 +1202,7 @@ window.__ModuleLoader__.load({
1103
1202
  h("section", {
1104
1203
  className: "dshCodexQuota",
1105
1204
  "data-quota-status": quota.status,
1205
+ "aria-busy": usageSnapshot.status === "loading" ? "true" : undefined,
1106
1206
  "aria-live": "polite",
1107
1207
  "aria-labelledby": "dsh-codex-quota-title",
1108
1208
  },
@@ -1111,9 +1211,13 @@ window.__ModuleLoader__.load({
1111
1211
  h("button", {
1112
1212
  type: "button",
1113
1213
  className: "dshCodexButton dshCodexRefreshButton",
1214
+ "data-loading": usageSnapshot.status === "loading" ? "true" : undefined,
1215
+ "aria-busy": usageSnapshot.status === "loading" ? "true" : undefined,
1114
1216
  disabled: actionBusy || usageSnapshot.status === "loading",
1115
1217
  onClick: refresh,
1116
- }, t("refresh"))),
1218
+ },
1219
+ refreshIcon(),
1220
+ h("span", null, usageSnapshot.status === "loading" ? t("refreshing") : t("refresh")))),
1117
1221
  liveUsage === undefined
1118
1222
  ? h("p", { className: "dshCodexQuotaProduct" }, t("quotaProduct"))
1119
1223
  : null,
@@ -1133,13 +1237,17 @@ window.__ModuleLoader__.load({
1133
1237
  const [saving, setSaving] = React.useState(false)
1134
1238
  const [saved, setSaved] = React.useState(false)
1135
1239
  const [failure, setFailure] = React.useState(null)
1240
+ const [expanded, setExpanded] = React.useState(false)
1136
1241
  const generationRef = React.useRef(0)
1137
1242
  const mountedRef = React.useRef(false)
1243
+ const modelToggleRef = React.useRef(null)
1244
+ const focusModelToggleRef = React.useRef(false)
1138
1245
 
1139
1246
  const refresh = React.useCallback(async () => {
1140
1247
  const generation = ++generationRef.current
1141
1248
  setSaved(false)
1142
1249
  setFailure(null)
1250
+ setExpanded(false)
1143
1251
  setSnapshot({ kind: "loading" })
1144
1252
  try {
1145
1253
  const next = await client.load()
@@ -1164,6 +1272,12 @@ window.__ModuleLoader__.load({
1164
1272
  }
1165
1273
  }, [client, refresh])
1166
1274
 
1275
+ React.useEffect(() => {
1276
+ if (!focusModelToggleRef.current) return
1277
+ focusModelToggleRef.current = false
1278
+ if (typeof modelToggleRef.current?.focus === "function") modelToggleRef.current.focus()
1279
+ }, [expanded, selectedIds])
1280
+
1167
1281
  const toggle = (id) => {
1168
1282
  const selected = selectedIds.includes(id)
1169
1283
  if (selected && selectedIds.length === 1) {
@@ -1172,6 +1286,7 @@ window.__ModuleLoader__.load({
1172
1286
  }
1173
1287
  setSaved(false)
1174
1288
  setFailure(null)
1289
+ if (selected && !expanded) focusModelToggleRef.current = true
1175
1290
  setSelectedIds(selected
1176
1291
  ? selectedIds.filter((candidate) => candidate !== id)
1177
1292
  : [...selectedIds, id])
@@ -1204,6 +1319,7 @@ window.__ModuleLoader__.load({
1204
1319
  }
1205
1320
 
1206
1321
  let content
1322
+ let modelSummary = null
1207
1323
  if (snapshot.kind === "loading") {
1208
1324
  content = h("p", { className: "dshCodexMuted", "aria-busy": "true" }, t("modelsLoading"))
1209
1325
  } else if (snapshot.kind === "unavailable") {
@@ -1223,7 +1339,17 @@ window.__ModuleLoader__.load({
1223
1339
  const original = new Set(snapshot.selectedIds)
1224
1340
  const dirty = selected.size !== original.size || [...selected].some((id) => !original.has(id))
1225
1341
  const plan = modelSavePlan(snapshot, selectedIds)
1226
- const modelItems = snapshot.models.map((model) => {
1342
+ const selectedModels = snapshot.models.filter((model) => selected.has(model.id))
1343
+ const unselectedModels = snapshot.models.filter((model) => !selected.has(model.id))
1344
+ const hiddenModelCount = unselectedModels.length
1345
+ const visibleModels = expanded
1346
+ ? [...selectedModels, ...unselectedModels]
1347
+ : selectedModels
1348
+ modelSummary = t("modelsSummary")
1349
+ .replace("{selected}", String(selected.size))
1350
+ .replaceAll("{total}", String(snapshot.models.length))
1351
+ .replace("{visible}", String(visibleModels.length))
1352
+ const modelItems = visibleModels.map((model) => {
1227
1353
  const metadata = [
1228
1354
  ...(model.contextWindow === undefined
1229
1355
  ? []
@@ -1260,9 +1386,21 @@ window.__ModuleLoader__.load({
1260
1386
  content = h(React.Fragment, null,
1261
1387
  snapshot.writable ? null : h("p", { className: "dshCodexMuted" }, t("modelsReadOnly")),
1262
1388
  h("ul", {
1389
+ id: "dsh-codex-model-list",
1263
1390
  className: "dshCodexModelList",
1264
1391
  "aria-labelledby": "dsh-codex-models-title",
1265
1392
  }, ...modelItems),
1393
+ hiddenModelCount === 0 ? null : h("button", {
1394
+ type: "button",
1395
+ className: "dshCodexButton dshCodexModelToggle",
1396
+ "aria-controls": "dsh-codex-model-list",
1397
+ "aria-expanded": expanded ? "true" : "false",
1398
+ ref: modelToggleRef,
1399
+ disabled: saving,
1400
+ onClick: () => setExpanded((current) => !current),
1401
+ }, expanded
1402
+ ? t("modelsCollapse")
1403
+ : t("modelsShowMore").replace("{count}", String(hiddenModelCount))),
1266
1404
  plan.allCatalogSelected ? h("p", { className: "dshCodexMuted" }, t(plan.unsetOverride
1267
1405
  ? "modelsAllEnabledFollow"
1268
1406
  : "modelsAllEnabledPreserve")) : null,
@@ -1286,7 +1424,11 @@ window.__ModuleLoader__.load({
1286
1424
  return h("section", { className: "dshCodexPanel dshCodexCard dshCodexModelPanel" },
1287
1425
  h("header", { className: "dshCodexHeader" },
1288
1426
  h("div", null,
1289
- h("h3", { id: "dsh-codex-models-title" }, t("modelsTitle")),
1427
+ h("div", { className: "dshCodexModelTitleRow" },
1428
+ h("h3", { id: "dsh-codex-models-title" }, t("modelsTitle")),
1429
+ modelSummary === null
1430
+ ? null
1431
+ : h("span", { className: "dshCodexModelSummary" }, modelSummary)),
1290
1432
  h("p", { className: "dshCodexWarning" }, t("modelsDescription")))),
1291
1433
  content)
1292
1434
  }
@@ -1304,6 +1446,7 @@ window.__ModuleLoader__.load({
1304
1446
 
1305
1447
  function apply(ctx) {
1306
1448
  ctx.effect(installStyle, "dsh-codex: client styles")
1449
+ ctx.effect(installSettingsNavIcon, "dsh-codex: settings nav icon")
1307
1450
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-codex: client dictionaries")
1308
1451
  const t = ctx.locale.bind(NS)
1309
1452
  const client = createAuthorizationClient(ctx.connection)
@@ -1338,6 +1481,96 @@ window.__ModuleLoader__.load({
1338
1481
  })
1339
1482
  }
1340
1483
 
1484
+ function installSettingsNavIcon() {
1485
+ if (typeof document === "undefined" || typeof window === "undefined") return undefined
1486
+ const Observer = typeof window.MutationObserver === "function"
1487
+ ? window.MutationObserver
1488
+ : typeof MutationObserver === "function" ? MutationObserver : undefined
1489
+ if (!document.body || typeof document.querySelectorAll !== "function" || Observer === undefined) return undefined
1490
+
1491
+ const isInsideSettingsNav = (target) => {
1492
+ const element = target?.nodeType === 1 ? target : target?.parentElement
1493
+ return typeof element?.closest === "function" && element.closest(SETTINGS_NAV_SELECTOR) !== null
1494
+ }
1495
+ const matchesOrContains = (node, selector) => (
1496
+ (typeof node?.matches === "function" && node.matches(selector))
1497
+ || (typeof node?.querySelector === "function" && node.querySelector(selector) !== null)
1498
+ )
1499
+ const mutationTouchesSettingsNav = (records) => Array.from(records ?? []).some((record) => {
1500
+ if (isInsideSettingsNav(record.target)) return true
1501
+ return [...Array.from(record.addedNodes ?? []), ...Array.from(record.removedNodes ?? [])]
1502
+ .some((node) => matchesOrContains(node, SETTINGS_DIALOG_SELECTOR) || matchesOrContains(node, SETTINGS_NAV_SELECTOR))
1503
+ })
1504
+ const enqueueMicrotask = typeof window.queueMicrotask === "function"
1505
+ ? (callback) => window.queueMicrotask(callback)
1506
+ : (callback) => Promise.resolve().then(callback)
1507
+
1508
+ let state = window[NAV_ICON_STATE_KEY]
1509
+ if (state === undefined) {
1510
+ let style = document.querySelector(NAV_ICON_STYLE_SELECTOR)
1511
+ if (style === null) {
1512
+ style = document.createElement("style")
1513
+ style.dataset.pluginNavIcon = "dsh-codex-community"
1514
+ document.head.appendChild(style)
1515
+ }
1516
+ style.dataset.plugin = "dsh-codex-community"
1517
+ const clearMarkers = () => {
1518
+ for (const button of document.querySelectorAll(`[${NAV_ICON_MARKER}]`)) button.removeAttribute(NAV_ICON_MARKER)
1519
+ }
1520
+ state = {
1521
+ active: true,
1522
+ refs: 0,
1523
+ scheduled: false,
1524
+ style,
1525
+ clearMarkers,
1526
+ scheduleSync() {
1527
+ if (!state.active || state.scheduled) return
1528
+ state.scheduled = true
1529
+ enqueueMicrotask(() => {
1530
+ state.scheduled = false
1531
+ state.sync()
1532
+ })
1533
+ },
1534
+ sync() {
1535
+ if (!state.active) return
1536
+ clearMarkers()
1537
+ const candidates = []
1538
+ for (const button of document.querySelectorAll(SETTINGS_NAV_BUTTON_SELECTOR)) {
1539
+ const icon = button.firstElementChild
1540
+ const labelElement = icon?.nextElementSibling
1541
+ if (icon?.localName !== "svg" || labelElement?.localName !== "span") continue
1542
+ const label = String(labelElement.textContent ?? "").replace(/\s+/gu, " ").trim()
1543
+ if (NAV_ICON_LABELS.has(label)) candidates.push(button)
1544
+ }
1545
+ if (candidates.length === 1) candidates[0].setAttribute(NAV_ICON_MARKER, "")
1546
+ },
1547
+ }
1548
+ state.observer = new Observer((records) => {
1549
+ if (mutationTouchesSettingsNav(records)) state.scheduleSync()
1550
+ })
1551
+ state.observer.observe(document.body, { childList: true, subtree: true, characterData: true })
1552
+ window[NAV_ICON_STATE_KEY] = state
1553
+ }
1554
+
1555
+ state.refs += 1
1556
+ state.style.textContent = NAV_ICON_CSS
1557
+ state.sync()
1558
+ let active = true
1559
+ return () => {
1560
+ if (!active) return
1561
+ active = false
1562
+ if (window[NAV_ICON_STATE_KEY] !== state) return
1563
+ state.refs -= 1
1564
+ if (state.refs > 0) return
1565
+ state.active = false
1566
+ state.observer.disconnect()
1567
+ state.clearMarkers()
1568
+ if (typeof state.style.remove === "function") state.style.remove()
1569
+ else state.style.parentNode?.removeChild(state.style)
1570
+ delete window[NAV_ICON_STATE_KEY]
1571
+ }
1572
+ }
1573
+
1341
1574
  function installStyle() {
1342
1575
  if (typeof document === "undefined") return undefined
1343
1576
  let style = document.querySelector(`style[data-plugin-css="${STYLE_ID}"]`)
@@ -1348,15 +1581,15 @@ window.__ModuleLoader__.load({
1348
1581
  document.head.appendChild(style)
1349
1582
  }
1350
1583
  style.textContent = [
1351
- ".dshCodexPage{box-sizing:border-box;display:flex;width:100%;min-width:0;flex-direction:column;gap:14px;padding:6px 0 32px;color:var(--dsw-alias-label-primary);font:inherit}",
1584
+ ".dshCodexPage{box-sizing:border-box;display:flex;width:100%;min-width:0;flex-direction:column;gap:12px;padding:4px 0 20px;color:var(--dsw-alias-label-primary);font:inherit}",
1352
1585
  ".dshCodexPage *{box-sizing:border-box}",
1353
1586
  ".dshCodexHero{display:flex;min-width:0;flex-direction:column;gap:8px}",
1354
1587
  ".dshCodexHero h2,.dshCodexHero p{margin:0}",
1355
1588
  ".dshCodexHero h2{font-size:16px;font-weight:500;line-height:24px}",
1356
1589
  ".dshCodexHero p{max-width:760px;color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}",
1357
- ".dshCodexPanel{display:flex;width:100%;min-width:0;color:var(--dsw-alias-label-primary);flex-direction:column;gap:14px}",
1590
+ ".dshCodexPanel{display:flex;width:100%;min-width:0;color:var(--dsw-alias-label-primary);flex-direction:column;gap:12px}",
1358
1591
  ".dshCodexPanel p,.dshCodexPanel label{overflow-wrap:anywhere;word-break:break-word}",
1359
- ".dshCodexCard{border:1px solid var(--dsw-alias-border-l2);border-radius:18px;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-3));padding:20px}",
1592
+ ".dshCodexCard{border:1px solid var(--dsw-alias-border-l2);border-radius:18px;background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-layer-3));padding:18px}",
1360
1593
  ".dshCodexHeader{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}",
1361
1594
  ".dshCodexHeader>div{min-width:0}",
1362
1595
  ".dshCodexHeader h3,.dshCodexHeader p,.dshCodexNotices p,.dshCodexQuota p{margin:0}",
@@ -1368,7 +1601,7 @@ window.__ModuleLoader__.load({
1368
1601
  ".dshCodexStatus[data-state=active] .dshCodexStatusDot{background:var(--dsw-alias-state-success-primary)}",
1369
1602
  ".dshCodexStatus[data-state=invalid] .dshCodexStatusDot{background:var(--dsw-alias-state-error-primary)}",
1370
1603
  ".dshCodexStatus[data-state=busy] .dshCodexStatusDot{background:var(--dsw-alias-state-business-primary)}",
1371
- ".dshCodexQuota{display:flex;min-width:0;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:18px}",
1604
+ ".dshCodexQuota{display:flex;min-width:0;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:16px}",
1372
1605
  ".dshCodexQuotaHeader{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:12px}",
1373
1606
  ".dshCodexQuota h3{margin:0;font-size:16px;font-weight:500;line-height:24px}",
1374
1607
  ".dshCodexQuotaProduct{font-size:14px;font-weight:500;line-height:22px}",
@@ -1377,25 +1610,29 @@ window.__ModuleLoader__.load({
1377
1610
  ".dshCodexQuotaUnknown::before{background:var(--dsw-alias-label-tertiary)}",
1378
1611
  ".dshCodexQuotaExhausted::before{background:var(--dsw-alias-state-error-primary)}",
1379
1612
  ".dshCodexQuotaExhausted{color:var(--dsw-alias-state-error-primary)}",
1380
- ".dshCodexQuotaGroup{display:flex;min-width:0;flex-direction:column;gap:10px}",
1613
+ ".dshCodexQuotaGroup{display:flex;min-width:0;flex-direction:column;gap:8px}",
1381
1614
  ".dshCodexQuotaGroup+.dshCodexQuotaGroup{border-top:1px solid var(--dsw-alias-border-l2);padding-top:14px}",
1382
- ".dshCodexQuotaWindow{display:flex;min-width:0;flex-direction:column;gap:7px}",
1383
- ".dshCodexQuotaWindow+.dshCodexQuotaWindow{margin-top:4px}",
1615
+ ".dshCodexQuotaWindows{display:grid;min-width:0;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}",
1616
+ ".dshCodexQuotaWindow{display:flex;min-width:0;flex-direction:column;gap:8px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);padding:11px 12px}",
1384
1617
  ".dshCodexQuotaWindowHeader,.dshCodexQuotaMeta{display:flex;min-width:0;align-items:center;justify-content:space-between;gap:12px}",
1385
1618
  ".dshCodexQuotaWindowHeader{font-size:14px;line-height:22px}",
1386
- ".dshCodexQuotaWindowHeader strong{font-weight:400;color:var(--dsw-alias-label-secondary)}",
1387
- ".dshCodexQuotaMeta{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}",
1619
+ ".dshCodexQuotaWindowHeader strong{font-weight:500;color:var(--dsw-alias-label-secondary)}",
1620
+ ".dshCodexQuotaMeta{justify-content:flex-end;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}",
1621
+ ".dshCodexQuotaMeta time{cursor:help}",
1388
1622
  ".dshCodexQuotaWindow progress{display:block;width:100%;height:8px;overflow:hidden;border:0;border-radius:999px;appearance:none;-webkit-appearance:none;background:var(--dsw-alias-bg-layer-1)}",
1389
1623
  ".dshCodexQuotaWindow progress::-webkit-progress-bar{border-radius:999px;background:var(--dsw-alias-bg-layer-1)}",
1390
1624
  ".dshCodexQuotaWindow progress::-webkit-progress-value{border-radius:999px;background:var(--dsw-alias-state-business-primary)}",
1391
1625
  ".dshCodexQuotaWindow progress::-moz-progress-bar{border-radius:999px;background:var(--dsw-alias-state-business-primary)}",
1392
- ".dshCodexQuotaUpdated{margin-top:2px!important}",
1393
1626
  ".dshCodexMuted{color:var(--dsw-alias-label-tertiary);font-size:14px;line-height:22px}",
1627
+ ".dshCodexQuota .dshCodexQuotaUpdated{margin-top:2px;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px}",
1394
1628
  ".dshCodexWarning{max-width:820px;color:var(--dsw-alias-label-secondary);font-size:14px;line-height:22px}",
1395
1629
  ".dshCodexActions{display:flex;min-width:0;flex-wrap:wrap;gap:8px}",
1396
1630
  ".dshCodexAuthActions{margin-left:auto;justify-content:flex-end}",
1397
1631
  ".dshCodexAuthActions .dshCodexButton{height:36px;min-height:36px;border-radius:18px;padding:0 14px;font-size:14px;line-height:22px}",
1398
- ".dshCodexQuotaHeader .dshCodexRefreshButton{min-height:28px;border:none;border-radius:14px;background:transparent;color:var(--dsw-alias-label-tertiary);padding:0 10px;font-size:12px;line-height:18px}",
1632
+ ".dshCodexQuotaHeader .dshCodexRefreshButton{display:inline-flex;min-height:30px;align-items:center;gap:6px;border:1px solid var(--dsw-alias-border-l2);border-radius:15px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);padding:0 10px;font-size:12px;line-height:18px;white-space:nowrap}",
1633
+ ".dshCodexRefreshIcon{width:14px;height:14px;flex:none}",
1634
+ ".dshCodexRefreshButton[data-loading=true] .dshCodexRefreshIcon{animation:dshCodexRefreshSpin .8s linear infinite}",
1635
+ "@keyframes dshCodexRefreshSpin{to{transform:rotate(360deg)}}",
1399
1636
  ".dshCodexButton{box-sizing:border-box;max-width:100%;white-space:normal;overflow-wrap:anywhere;word-break:break-word;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;cursor:pointer;padding:8px 14px;transition:background-color .15s ease,border-color .15s ease}",
1400
1637
  ".dshCodexButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}",
1401
1638
  ".dshCodexButton:focus-visible,.dshCodexModelOption:has(input:focus-visible){outline:2px solid var(--dsw-alias-brand-primary,var(--dsw-alias-border-l3));outline-offset:2px}",
@@ -1411,19 +1648,23 @@ window.__ModuleLoader__.load({
1411
1648
  ".dshCodexLegacyRepair:disabled{cursor:not-allowed;opacity:.55}",
1412
1649
  ".dshCodexPrimary{border-color:var(--dsw-alias-button-primary-fill,var(--dsw-alias-state-business-primary));background:var(--dsw-alias-button-primary-fill,var(--dsw-alias-state-business-primary));color:var(--dsw-alias-label-primary-foreground,#fff)}",
1413
1650
  ".dshCodexPrimary:hover:not(:disabled){background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-state-business-primary))}",
1414
- ".dshCodexModelList{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));column-gap:36px;row-gap:8px;margin:0;padding:0;list-style:none}",
1415
- ".dshCodexModelOption{display:flex;min-width:0;align-items:flex-start;gap:12px;border-radius:10px;padding:10px 8px;cursor:pointer;transition:background-color .15s ease}",
1651
+ ".dshCodexModelList{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin:0;padding:0;list-style:none}",
1652
+ ".dshCodexModelTitleRow{display:flex;min-width:0;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:4px 12px}",
1653
+ ".dshCodexModelSummary{color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;white-space:nowrap}",
1654
+ ".dshCodexModelOption{display:flex;min-width:0;height:100%;align-items:flex-start;gap:10px;border:1px solid var(--dsw-alias-border-l2);border-radius:11px;background:var(--dsw-alias-bg-layer-1);padding:9px 10px;cursor:pointer;transition:background-color .15s ease,border-color .15s ease}",
1416
1655
  ".dshCodexModelOption:hover{background:var(--dsw-alias-interactive-bg-hover,var(--dsw-alias-bg-layer-2))}",
1656
+ ".dshCodexModelOption[data-selected=true]{border-color:var(--dsw-alias-state-business-primary)}",
1417
1657
  ".dshCodexModelOption:has(input:disabled){cursor:not-allowed;opacity:.65}",
1418
1658
  ".dshCodexModelOption:has(input:disabled):hover{background:transparent}",
1419
1659
  ".dshCodexModelOption input{width:18px;height:18px;flex:none;margin:2px 0 0;accent-color:var(--dsw-alias-state-business-primary)}",
1420
- ".dshCodexModelCopy{display:flex;min-width:0;flex:1;flex-direction:column;gap:7px}",
1421
- ".dshCodexModelHeading{display:flex;min-width:0;flex-direction:column;gap:1px}",
1660
+ ".dshCodexModelCopy{display:flex;min-width:0;flex:1;flex-direction:column;gap:5px}",
1661
+ ".dshCodexModelHeading{display:flex;min-width:0;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:1px 8px}",
1422
1662
  ".dshCodexModelOption strong{overflow-wrap:anywhere;font-size:14px;font-weight:500;line-height:22px}",
1423
1663
  ".dshCodexModelId{overflow-wrap:anywhere;color:var(--dsw-alias-label-tertiary);font-family:inherit;font-size:12px;line-height:18px}",
1424
1664
  ".dshCodexModelCapabilities{display:flex;min-width:0;flex-wrap:wrap;gap:6px}",
1425
- ".dshCodexModelBadge{display:inline-flex;align-items:center;border-radius:999px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:18px;padding:2px 8px}",
1426
- ".dshCodexModelActions{justify-content:flex-end;border-top:1px solid var(--dsw-alias-border-l2);padding-top:20px}",
1665
+ ".dshCodexModelBadge{display:inline-flex;align-items:center;border-radius:999px;background:var(--dsw-alias-bg-layer-2,var(--dsw-alias-bg-module-platform));color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:18px;padding:1px 7px}",
1666
+ ".dshCodexModelToggle{align-self:center;border-radius:999px;padding:6px 12px;font-size:12px;line-height:18px}",
1667
+ ".dshCodexModelActions{justify-content:flex-end;border-top:1px solid var(--dsw-alias-border-l2);padding-top:16px}",
1427
1668
  ".dshCodexNotices{display:flex;min-width:0;flex-direction:column;gap:10px;margin:0;padding:0;list-style:none}",
1428
1669
  ".dshCodexNotices li,.dshCodexPrompt{box-sizing:border-box;min-width:0;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);padding:14px 16px}",
1429
1670
  ".dshCodexNotices a{display:inline-block;max-width:100%;margin-top:8px;color:var(--dsw-alias-state-business-primary)}",
@@ -1434,9 +1675,10 @@ window.__ModuleLoader__.load({
1434
1675
  ".dshCodexPrompt input,.dshCodexPrompt select{box-sizing:border-box;width:100%;min-width:0;height:38px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);font:inherit;padding:0 10px}",
1435
1676
  ".dshCodexError{margin:0;color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}",
1436
1677
  ".dshCodexSuccess{margin:0;color:var(--dsw-alias-state-success-primary);font-size:12px;line-height:18px}",
1437
- "@media(max-width:760px){.dshCodexModelList{grid-template-columns:1fr}.dshCodexModelActions{justify-content:flex-start}}",
1678
+ "@media(prefers-reduced-motion:reduce){.dshCodexRefreshButton[data-loading=true] .dshCodexRefreshIcon{animation:none}}",
1679
+ "@media(max-width:760px){.dshCodexQuotaWindows,.dshCodexModelList{grid-template-columns:1fr}.dshCodexModelActions{justify-content:flex-start}}",
1438
1680
  "@media(max-width:640px){.dshCodexAuthToolbar,.dshCodexHeader{align-items:stretch;flex-direction:column}.dshCodexAuthActions{margin-left:0;justify-content:flex-start}}",
1439
- "@media(max-width:480px){.dshCodexPage{gap:12px;padding:0 0 24px}.dshCodexCard{border-radius:14px;padding:16px}.dshCodexQuotaWindowHeader,.dshCodexQuotaMeta{align-items:flex-start;flex-direction:column;gap:2px}.dshCodexActions{align-items:stretch;flex-direction:column}.dshCodexButton{width:100%}}",
1681
+ "@media(max-width:480px){.dshCodexPage{gap:10px;padding:0 0 16px}.dshCodexCard{border-radius:14px;padding:16px}.dshCodexActions{align-items:stretch;flex-direction:column}.dshCodexButton{width:100%}.dshCodexQuotaHeader .dshCodexRefreshButton{width:auto}.dshCodexQuotaWindowHeader{gap:8px}}",
1440
1682
  ].join("")
1441
1683
  const references = style[STYLE_REF_KEY]
1442
1684
  style[STYLE_REF_KEY] = Number.isSafeInteger(references) && references > 0
@@ -1468,7 +1710,9 @@ window.__ModuleLoader__.load({
1468
1710
  exports.createAuthorizationClient = createAuthorizationClient
1469
1711
  exports.createModelEnablementClient = createModelEnablementClient
1470
1712
  exports.createSessionPreferenceClient = createSessionPreferenceClient
1713
+ exports.formatResetDistance = formatResetDistance
1471
1714
  exports.inject = inject
1715
+ exports.installSettingsNavIcon = installSettingsNavIcon
1472
1716
  exports.safeQuotaSnapshot = safeQuotaSnapshot
1473
1717
  return module.exports
1474
1718
  },
package/docs/README.en.md CHANGED
@@ -10,6 +10,8 @@
10
10
  - [Testing strategy](testing.en.md)
11
11
  - [Release process](releasing.en.md)
12
12
  - [GitHub About and topics](github-about.md)
13
+ - [v0.0.3 Release copy](releases/v0.0.3.md)
14
+ - [v0.0.2 Release copy](releases/v0.0.2.md)
13
15
  - [v0.0.1 Release copy](releases/v0.0.1.md)
14
16
 
15
17
  Every “verified” claim must point to repository tests, CI, or release-artifact evidence. “Wired” and “planned” must not be presented as “supported.”
package/docs/README.md CHANGED
@@ -10,6 +10,8 @@
10
10
  - [测试策略](testing.md)
11
11
  - [发布流程](releasing.md)
12
12
  - [GitHub About 与 Topics](github-about.md)
13
+ - [v0.0.3 Release 文案](releases/v0.0.3.md)
14
+ - [v0.0.2 Release 文案](releases/v0.0.2.md)
13
15
  - [v0.0.1 Release 文案](releases/v0.0.1.md)
14
16
 
15
17
  文档中的“已验证”必须能指向仓库内测试、CI 或发布产物证据;“已接线”和“计划中”不能写成“已支持”。
@@ -4,30 +4,31 @@
4
4
 
5
5
  Last updated: 2026-08-29.
6
6
 
7
- | Component or environment | `0.0.2` evidence | Status |
7
+ | Component or environment | `0.0.3` candidate evidence | Status |
8
8
  | --- | --- | --- |
9
9
  | DeepSeek Harness | `test/fixtures/dsh-runtime/pnpm-lock.yaml` locks the complete `@deepseek-ai/dsh@0.1.1-rc.2` runtime/peer graph and validates the `dsh-llm` schema and stream contract | Exact version verified |
10
- | pi-ai | OAuth, catalog, Codex payload, and replay contract tests against `@earendil-works/pi-ai@0.82.1` | Public contract verified; 0.0.2 live-network acceptance is `0/13 pending` |
11
- | Node.js | Complete local suite on `22.22.2`; the [0.0.2 three-platform candidate run](https://github.com/yoshino-xiao7/dsh-codex/actions/runs/33243698807) covers Node 22/24 | `>=22.19.0 <25`; candidate run passed |
10
+ | pi-ai | OAuth, catalog, Codex payload, and replay contract tests against `@earendil-works/pi-ai@0.82.1` | Public contract verified; 0.0.3 live-network acceptance is `0/13 pending` |
11
+ | Node.js | Complete local suite on `22.22.2`; the [0.0.3 three-platform candidate run](https://github.com/yoshino-xiao7/dsh-codex/actions/runs/33248224386) covers Node 22/24 | `>=22.19.0 <25`; candidate run passed |
12
12
  | macOS | `macos-latest` Node 22/24 complete checks, frozen DSH installation, and Web/profile smoke | Platform gate passed |
13
13
  | Windows x64 | `windows-latest` Node 22/24 complete checks, frozen DSH installation, and Web/profile smoke | Platform gate passed; real user environment pending |
14
14
  | Linux x64 | `ubuntu-latest` Node 22/24 complete checks, frozen DSH installation, and Web/profile smoke | Platform gate passed |
15
15
  | Real ChatGPT OAuth login | Automation does not read or modify a user's real grant | Controlled acceptance pending |
16
- | Codex usage windows | Opening the settings page and refreshing manually both make the Host actively read and strictly parse the real five-hour and weekly limits; both windows were verified in the local DSH settings page | Verified; a failure retains the latest safe reading or falls back to request observation/an unknown state |
16
+ | Codex usage windows | Opening settings and refreshing manually both make the Host actively read and strictly parse the real five-hour and weekly limits; local DSH settings verification covered remaining percentages, refresh state, the exact five-hour time, and the weekly 24-hour threshold | Verified locally; a failure retains the latest safe reading or falls back to request observation/an unknown state |
17
+ | Codex model settings | Web client tests and local Chinese/English settings verification cover installed-catalog fields, compact `K` labels, selected-first ordering, and collapsed unselected models | Verified locally; capabilities absent from the catalog are not inferred |
17
18
  | Real Codex conversation | Automation does not consume user account quota | Controlled acceptance pending |
18
19
  | Text / reasoning / usage / tools / replay | Public PiAiAdapter success, tool-call, and two-turn replay automation passes | Live reasoning, tool round trip, and conversation continuity are pending |
19
20
  | Codex image input | Attachment-seam and budget-projection automation passes | A live request with `maxPixels=4194304` is pending |
20
21
  | auto / SSE / WebSocket / cached | Transport mapping and session isolation pass in automation | One live request through each transport is pending |
21
22
  | Fast / priority tier | Verified that only an explicit per-session choice changes `service_tier` | Account entitlement and live network pending |
22
- | npm / GitHub Release | The strict workflow verifies the candidate, Registry readback, provenance, signatures, and Release assets; artifacts are attached to [`v0.0.2`](https://github.com/yoshino-xiao7/dsh-codex/releases/tag/v0.0.2) | Formal publication is performed by the protected workflow |
23
+ | npm / GitHub Release | The strict workflow verifies the candidate, Registry readback, provenance, signatures, and Release assets; artifacts are attached to [`v0.0.3`](https://github.com/yoshino-xiao7/dsh-codex/releases/tag/v0.0.3) | Formal publication is performed by the protected workflow |
23
24
 
24
- The `0.0.x` line is a technical preview and declares compatibility only with the exact DSH release candidate above. Linux, macOS, and Windows CI/profile smoke for `0.0.2` reached `3/3`; the accepted commit, exact Node versions, and job links are recorded in the [acceptance record](releases/v0.0.2.acceptance.json), with maintainer approval. Live OAuth, conversation, image, transport, and Fast network acceptance remains `0/13 pending` for post-release validation and will be completed individually after publication. These incomplete checks must remain visible and must never be presented as verified capabilities. Reading the usage windows was verified independently in the local settings page and is not one of these 13 checks that consume a model request or require a complete interaction round trip.
25
+ The `0.0.x` line is a technical preview and declares compatibility only with the exact DSH release candidate above. Linux, macOS, and Windows CI/profile smoke for `0.0.3` reached `3/3`. The accepted commit, exact Node versions, and job links are recorded in the [acceptance record](releases/v0.0.3.acceptance.json), with maintainer approval. Live OAuth, conversation, image, transport, and Fast network acceptance starts in a fresh `0/13 pending` record and will be completed individually post-release. These incomplete checks must remain visible and must never be presented as verified capabilities. Usage-window and model-setting behavior was verified independently in the local Chinese and English settings page and is not one of these 13 checks that consume a model request or require a complete interaction round trip.
25
26
 
26
27
  The root `pnpm-lock.yaml` locks plugin dependencies, while `test/fixtures/dsh-runtime/pnpm-lock.yaml` independently locks the complete DSH runtime and peer graph used by compatibility smoke. CI runs `pnpm --dir test/fixtures/dsh-runtime install --frozen-lockfile --ignore-scripts` instead of handing that peer graph to direct npm resolution, avoiding nondeterministic dependency results and uncontrolled memory use. This frozen layer verifies Web/profile integration; it does not claim coverage of native terminal or native-build capabilities in DSH dependencies that require lifecycle scripts. Dependency upgrades must use a pinned-version PR, update and review both lockfiles, repeat complete CI, profile smoke, and supply-chain verification before publication, and renew controlled live validation afterward. The scheduled compatibility workflow only verifies the current locked graph and reports Registry drift; neither a broad semver range nor one scheduled run proves cross-RC compatibility.
27
28
 
28
29
  ## Profile composition
29
30
 
30
- The `0.0.2` bundle does not modify the general `llm-pi-ai` Cordis row. It provides:
31
+ The `0.0.3` bundle does not modify the general `llm-pi-ai` Cordis row. It provides:
31
32
 
32
33
  - the `dsh-codex` route;
33
34
  - the `dsh-codex` settings namespace;
@@ -46,9 +47,9 @@ Fast and transport preferences set by `/codex` exist only for the current proces
46
47
  ## Credentials and capability boundaries
47
48
 
48
49
  - The Codex route accepts ChatGPT OAuth only and does not read an OpenAI Platform API key.
49
- - Cancel sign-in through the plugin settings page or `/codex-login cancel`: cancellation succeeds before write linearization, while an in-progress commit is reported as too late and remains observed through its final state; `logout` always deletes the credential. The current DSH public seam has no post-commit cancellation barrier, so third-party code that calls `ctx.authorization.cancel()` directly bypasses this coordination and is not a supported `0.0.2` interaction path.
50
+ - Cancel sign-in through the plugin settings page or `/codex-login cancel`: cancellation succeeds before write linearization, while an in-progress commit is reported as too late and remains observed through its final state; `logout` always deletes the credential. The current DSH public seam has no post-commit cancellation barrier, so third-party code that calls `ctx.authorization.cancel()` directly bypasses this coordination and is not a supported `0.0.3` interaction path.
50
51
  - When a plugin or hot-reload flow owner is disposed, sign-in is cancelled before commit selection, while a selected credential commit is allowed to reach its final write before disposal completes. An unsupported or malformed stored record is shown as `invalid`, requires signing in again or signing out to clear it, and is never presented as signed in.
51
52
  - The usage card actively reads the real five-hour and weekly limits when the settings page opens and when the user refreshes it manually. The Host returns only strictly parsed windows, percentages, and reset times to the Web page, never OAuth credentials or raw responses. If the endpoint is unavailable or malformed, the latest safe reading is retained and the card falls back to request observation or an unknown state rather than presenting the failure as zero remaining usage.
52
53
  - DSH Web search uses its own provider and credentials; it does not reuse ChatGPT OAuth.
53
- - `0.0.2` does not provide image generation or editing.
54
+ - `0.0.3` does not provide image generation or editing.
54
55
  - Session compaction continues to use DSH's built-in automatic compaction and `/compact`.
@@ -4,30 +4,31 @@
4
4
 
5
5
  最近更新:2026-08-29。
6
6
 
7
- | 组件或环境 | `0.0.2` 证据 | 状态 |
7
+ | 组件或环境 | `0.0.3` 候选证据 | 状态 |
8
8
  | --- | --- | --- |
9
9
  | DeepSeek Harness | `test/fixtures/dsh-runtime/pnpm-lock.yaml` 锁定完整 `@deepseek-ai/dsh@0.1.1-rc.2` runtime/peer 图,并验证 `dsh-llm` schema 与 stream contract | 精确版本已验证 |
10
- | pi-ai | `@earendil-works/pi-ai@0.82.1` 的 OAuth、模型目录、Codex payload 与 replay 合同测试 | 公开合同已验证;0.0.2 真实网络验收 `0/13 pending` |
11
- | Node.js | 本地 `22.22.2` 完整测试;[0.0.2 三平台候选运行](https://github.com/yoshino-xiao7/dsh-codex/actions/runs/33243698807)覆盖 Node 22/24 | `>=22.19.0 <25`;候选运行通过 |
10
+ | pi-ai | `@earendil-works/pi-ai@0.82.1` 的 OAuth、模型目录、Codex payload 与 replay 合同测试 | 公开合同已验证;0.0.3 真实网络验收 `0/13 pending` |
11
+ | Node.js | 本地 `22.22.2` 完整测试;[0.0.3 三平台候选运行](https://github.com/yoshino-xiao7/dsh-codex/actions/runs/33248224386)覆盖 Node 22/24 | `>=22.19.0 <25`;候选运行通过 |
12
12
  | macOS | `macos-latest` Node 22/24 完整检查、冻结 DSH 安装与 Web/profile smoke | 平台门禁通过 |
13
13
  | Windows x64 | `windows-latest` Node 22/24 完整检查、冻结 DSH 安装与 Web/profile smoke | 平台门禁通过;真实用户环境待验收 |
14
14
  | Linux x64 | `ubuntu-latest` Node 22/24 完整检查、冻结 DSH 安装与 Web/profile smoke | 平台门禁通过 |
15
15
  | ChatGPT OAuth 真实登录 | 自动化不读取或修改用户真实 grant | 受控验收待完成 |
16
- | Codex 额度窗口 | 进入设置页和手动刷新都会通过 Host 主动读取并严格解析真实五小时与每周额度;本机 DSH 设置页已验证两种额度窗口 | 已验证;读取失败时保留最近安全读数或降级为请求观测/未知状态 |
16
+ | Codex 额度窗口 | 进入设置页和手动刷新都会通过 Host 主动读取并严格解析真实五小时与每周额度;本机 DSH 设置页已验证剩余比例、刷新状态、五小时精确时间和每周 24 小时阈值 | 本机已验证;读取失败时保留最近安全读数或降级为请求观测/未知状态 |
17
+ | Codex 模型设置 | 安装目录字段、紧凑 `K` 标签、已选择优先与未选择折叠由 Web 客户端测试及本机中英文设置页覆盖 | 本机已验证;不推测目录未提供的能力 |
17
18
  | Codex 真实网络对话 | 自动化不消耗用户账户配额 | 受控验收待完成 |
18
19
  | 文本 / reasoning / usage / 工具 / replay | 公开 PiAiAdapter 成功流、工具调用和两轮 replay 自动化已通过 | 真实 reasoning、工具闭环与连续对话待验收 |
19
20
  | Codex 图片输入 | attachment seam 与预算投影自动化已通过 | `maxPixels=4194304` 真实请求待验收 |
20
21
  | auto / SSE / WebSocket / cached | transport 映射与会话隔离自动化已通过 | 四种真实请求待验收 |
21
22
  | Fast / priority tier | 已验证仅在当前会话开启时改变 `service_tier` | 账号权限与真实网络待验收 |
22
- | npm / GitHub Release | 严格工作流校验候选、Registry 回读、provenance、签名与 Release 资产;发布制品见 [`v0.0.2`](https://github.com/yoshino-xiao7/dsh-codex/releases/tag/v0.0.2) | 正式发布由受保护工作流完成 |
23
+ | npm / GitHub Release | 严格工作流校验候选、Registry 回读、provenance、签名与 Release 资产;发布制品见 [`v0.0.3`](https://github.com/yoshino-xiao7/dsh-codex/releases/tag/v0.0.3) | 正式发布由受保护工作流完成 |
23
24
 
24
- `0.0.x` 是技术预览,只对表中的精确 DSH 预发布版本声明兼容。`0.0.2` 的 Linux、macOS 和 Windows CI/profile smoke 已达到 `3/3`,候选提交、精确 Node 版本与作业链接记录在[验收记录](releases/v0.0.2.acceptance.json)中,并已获得维护者批准。真实 OAuth、对话、图片、transport 和 Fast 网络验收为 `0/13 pending`,正式发布后再逐项补齐;这些未完成项必须如实展示,不能冒充已验证能力。额度窗口读取已独立完成本机设置页验证,不计入这 13 个会消耗模型请求或需要完整交互闭环的验收项。
25
+ `0.0.x` 是技术预览,只对表中的精确 DSH 预发布版本声明兼容。`0.0.3` 的 Linux、macOS 和 Windows CI/profile smoke 已达到 `3/3`,候选提交、精确 Node 版本与作业链接记录在[验收记录](releases/v0.0.3.acceptance.json)中,并已获得维护者批准。真实 OAuth、对话、图片、transport 和 Fast 网络验收从独立的 `0/13 pending` 记录开始,发布后再逐项补齐;这些未完成项必须如实展示,不能冒充已验证能力。额度窗口与模型设置已独立完成本机中英文页面验证,不计入这 13 个会消耗模型请求或需要完整交互闭环的验收项。
25
26
 
26
27
  根目录 `pnpm-lock.yaml` 锁定插件依赖,`test/fixtures/dsh-runtime/pnpm-lock.yaml` 独立锁定兼容性 smoke 的完整 DSH runtime 与 peer 图;CI 使用 `pnpm --dir test/fixtures/dsh-runtime install --frozen-lockfile --ignore-scripts`,不把该 peer 图交给直接 npm 解算,以避免不确定依赖结果和内存失控。该冻结层验证 Web/profile 集成,不声称覆盖 DSH 依赖中需要生命周期脚本的原生终端或本机构建能力。依赖升级必须通过固定版本变更 PR,同时更新并审查两套 lockfile,在发布前重跑完整 CI、profile smoke 和供应链校验,发布后重新记录受控真实验证。定时兼容工作流只验证当前锁定图并报告 Registry 漂移,不能从宽泛 semver 或一次定时运行推断跨 RC 兼容。
27
28
 
28
29
  ## Profile 组合
29
30
 
30
- `0.0.2` 的 bundle 不修改通用 `llm-pi-ai` Cordis row。它提供:
31
+ `0.0.3` 的 bundle 不修改通用 `llm-pi-ai` Cordis row。它提供:
31
32
 
32
33
  - `dsh-codex` route;
33
34
  - `dsh-codex` 设置命名空间;
@@ -46,9 +47,9 @@
46
47
  ## 凭据与功能边界
47
48
 
48
49
  - Codex route 只接受 ChatGPT OAuth,不读取 OpenAI Platform API key;
49
- - 请通过插件设置页或 `/codex-login cancel` 取消登录:写入线性化前会正常取消,提交已经开始时会明确提示无法取消并继续等待最终状态;`logout` 始终删除凭据。DSH 当前公共 seam 没有“提交后禁止取消”能力,第三方代码直接调用 `ctx.authorization.cancel()` 会绕过这一协调,不属于 `0.0.2` 支持的交互路径;
50
+ - 请通过插件设置页或 `/codex-login cancel` 取消登录:写入线性化前会正常取消,提交已经开始时会明确提示无法取消并继续等待最终状态;`logout` 始终删除凭据。DSH 当前公共 seam 没有“提交后禁止取消”能力,第三方代码直接调用 `ctx.authorization.cancel()` 会绕过这一协调,不属于 `0.0.3` 支持的交互路径;
50
51
  - 插件或热重载 flow owner 卸载时,提交选择前的登录会取消,已经进入凭据提交的登录会等待最终写入再完成卸载;不支持类型或结构损坏的已存记录显示为 `invalid`,需要重新登录或退出清除,不会显示为已登录;
51
52
  - 额度卡会在进入设置页和手动刷新时主动读取真实五小时与每周额度;Host 只向 Web 页面返回严格解析后的窗口、百分比和重置时间,不返回 OAuth 凭据或原始响应。接口不可用或结构异常时保留最近一次安全读数,并降级为请求观测或未知状态,不把失败显示成零余额;
52
53
  - DSH 的 Web search 使用它自己的 provider 和凭据,不复用 ChatGPT OAuth;
53
- - `0.0.2` 不提供图片生成或编辑;
54
+ - `0.0.3` 不提供图片生成或编辑;
54
55
  - 会话压缩继续使用 DSH 自带的自动压缩和 `/compact`。
@@ -0,0 +1,160 @@
1
+ {
2
+ "schemaVersion": 3,
3
+ "version": "0.0.3",
4
+ "releaseStatus": "approved",
5
+ "testedCommit": "e94f7a46ae028640655488ebd36d75bf1c157c96",
6
+ "approvedBy": "yoshino-xiao7",
7
+ "approvedAt": "2026-08-29T10:40:48Z",
8
+ "approvalEvidenceUrl": "https://github.com/yoshino-xiao7/dsh-codex/actions/runs/33248224386",
9
+ "platforms": {
10
+ "linux": {
11
+ "status": "passed",
12
+ "testedAt": "2026-08-29T10:37:22Z",
13
+ "runner": "GitHub Actions 1000001583",
14
+ "nodeVersion": "24.19.0",
15
+ "dshVersion": "0.1.1-rc.2",
16
+ "profileSmoke": "passed",
17
+ "evidenceUrl": "https://github.com/yoshino-xiao7/dsh-codex/actions/runs/33248224386/job/99089278195"
18
+ },
19
+ "macos": {
20
+ "status": "passed",
21
+ "testedAt": "2026-08-29T10:37:34Z",
22
+ "runner": "GitHub Actions 1000001585",
23
+ "nodeVersion": "24.18.0",
24
+ "dshVersion": "0.1.1-rc.2",
25
+ "profileSmoke": "passed",
26
+ "evidenceUrl": "https://github.com/yoshino-xiao7/dsh-codex/actions/runs/33248224386/job/99089278177"
27
+ },
28
+ "windows": {
29
+ "status": "passed",
30
+ "testedAt": "2026-08-29T10:39:06Z",
31
+ "runner": "GitHub Actions 1000001581",
32
+ "nodeVersion": "24.19.0",
33
+ "dshVersion": "0.1.1-rc.2",
34
+ "profileSmoke": "passed",
35
+ "evidenceUrl": "https://github.com/yoshino-xiao7/dsh-codex/actions/runs/33248224386/job/99089278146"
36
+ }
37
+ },
38
+ "liveAcceptance": {
39
+ "oauthWebSignIn": {
40
+ "status": "pending",
41
+ "testedAt": "TBD",
42
+ "evidenceUrl": "TBD",
43
+ "assertions": {
44
+ "flowStartedFromSettings": false,
45
+ "credentialConfigured": false,
46
+ "postSignInRequestSucceeded": false
47
+ }
48
+ },
49
+ "modelCatalog": {
50
+ "status": "pending",
51
+ "testedAt": "TBD",
52
+ "evidenceUrl": "TBD",
53
+ "assertions": {
54
+ "settingsCatalogVisible": false,
55
+ "conversationModelSelectable": false
56
+ }
57
+ },
58
+ "textStream": {
59
+ "status": "pending",
60
+ "testedAt": "TBD",
61
+ "evidenceUrl": "TBD",
62
+ "assertions": {
63
+ "nonEmptyTextDelta": false,
64
+ "terminalStopObserved": false
65
+ }
66
+ },
67
+ "reasoningStream": {
68
+ "status": "pending",
69
+ "testedAt": "TBD",
70
+ "evidenceUrl": "TBD",
71
+ "assertions": {
72
+ "reasoningBlockObserved": false,
73
+ "terminalStopObserved": false
74
+ }
75
+ },
76
+ "terminalUsage": {
77
+ "status": "pending",
78
+ "testedAt": "TBD",
79
+ "evidenceUrl": "TBD",
80
+ "assertions": {
81
+ "inputTokensObserved": false,
82
+ "outputTokensObserved": false
83
+ }
84
+ },
85
+ "toolRoundTrip": {
86
+ "status": "pending",
87
+ "testedAt": "TBD",
88
+ "evidenceUrl": "TBD",
89
+ "assertions": {
90
+ "toolCallObserved": false,
91
+ "toolExecuted": false,
92
+ "toolResultReturned": false,
93
+ "followUpSucceeded": false
94
+ }
95
+ },
96
+ "replayContinuity": {
97
+ "status": "pending",
98
+ "testedAt": "TBD",
99
+ "evidenceUrl": "TBD",
100
+ "assertions": {
101
+ "firstTurnSucceeded": false,
102
+ "secondTurnUsedPriorContext": false,
103
+ "secondTurnSucceeded": false
104
+ }
105
+ },
106
+ "imageMaxPixels": {
107
+ "status": "pending",
108
+ "testedAt": "TBD",
109
+ "evidenceUrl": "TBD",
110
+ "assertions": {
111
+ "nativeImageAccepted": false,
112
+ "maxPixels4194304Projected": false,
113
+ "requestSucceeded": false
114
+ }
115
+ },
116
+ "transportAuto": {
117
+ "status": "pending",
118
+ "testedAt": "TBD",
119
+ "evidenceUrl": "TBD",
120
+ "assertions": {
121
+ "requestSucceeded": false
122
+ }
123
+ },
124
+ "transportSse": {
125
+ "status": "pending",
126
+ "testedAt": "TBD",
127
+ "evidenceUrl": "TBD",
128
+ "assertions": {
129
+ "requestSucceeded": false
130
+ }
131
+ },
132
+ "transportWebsocket": {
133
+ "status": "pending",
134
+ "testedAt": "TBD",
135
+ "evidenceUrl": "TBD",
136
+ "assertions": {
137
+ "requestSucceeded": false
138
+ }
139
+ },
140
+ "transportWebsocketCached": {
141
+ "status": "pending",
142
+ "testedAt": "TBD",
143
+ "evidenceUrl": "TBD",
144
+ "assertions": {
145
+ "firstTurnSucceeded": false,
146
+ "secondTurnSucceeded": false
147
+ }
148
+ },
149
+ "fastPriority": {
150
+ "status": "pending",
151
+ "testedAt": "TBD",
152
+ "evidenceUrl": "TBD",
153
+ "assertions": {
154
+ "priorityRequested": false,
155
+ "requestSucceeded": false,
156
+ "noAutomaticDowngrade": false
157
+ }
158
+ }
159
+ }
160
+ }
@@ -0,0 +1,62 @@
1
+ > 发布日期 / Release date:2026-08-29
2
+ > 验收提交 / Accepted commit:e94f7a46ae028640655488ebd36d75bf1c157c96
3
+ > npm:`dsh-codex-community@0.0.3`
4
+ > 发布方式 / Publication:npm `latest` + 正式 GitHub Release / full GitHub Release
5
+ > 平台验收 / Platform acceptance:3/3
6
+ > 发布后真实账号验证 / Post-release live validation:0/13
7
+
8
+ ## 中文
9
+
10
+ ### 变更
11
+
12
+ - OpenAI Codex 设置入口改用独立的代码括号图标;替换只作用于唯一匹配的 Codex 导航项,并在热重载或卸载时完整清理。
13
+ - 额度区改为响应式双列卡片,统一显示“剩余”语义;刷新操作具有图标、刷新中状态和无障碍 busy 标记,并遵循系统的减少动态效果偏好。
14
+ - 五小时额度始终显示精确重置时间;每周额度在距离重置不足 24 小时时显示精确时间,其余时间显示本地化的相对天数。完整时间仍保留在提示与无障碍标签中。
15
+ - 模型列表优先显示已选择模型,未选择模型默认收起并可按需展开;页面同步显示已选择、当前显示与总模型数。
16
+ - 上下文与最大输出使用 `272K`、`128K` 一类紧凑标签,只展示当前安装的 provider catalog 实际提供的字段,不推测额外能力。
17
+ - 设置内容不再覆盖 Harness 弹窗宽度或滚动容器;桌面使用双列、窄屏回落为单列,减少弹窗高度并改善滚动。
18
+ - 在收起状态取消选择模型后,键盘焦点会回到展开按钮;重置时间解析会拒绝无法构造有效日期的值。
19
+
20
+ ### 兼容性
21
+
22
+ - 精确兼容目标保持为 DeepSeek Harness `0.1.1-rc.2` 与 Node.js `>=22.19.0 <25`,不承诺兼容其他 DSH 预发布版本。
23
+ - 本版本仅调整 Web 设置页 bundle 与对应测试,不改变 OAuth、Provider、传输或模型设置持久化合同。
24
+ - 完整检查与 Linux/macOS/Windows `3/3` 已绑定候选提交并由维护者批准;正式工作流仍会在发布前后校验同一候选、供应链证据、Registry 与 GitHub Release。发布后真实账号验收从独立的 `0.0.3` 记录以 `0/13` 开始,不继承旧版本证据。
25
+ - 本版本不提供图片生成或编辑。
26
+
27
+ ### 验收记录
28
+
29
+ - [`v0.0.3.acceptance.json`](v0.0.3.acceptance.json)
30
+
31
+ ### 制品校验
32
+
33
+ - SHA-256:正式工作流生成并随 GitHub Release 附带校验文件。
34
+ - SRI:正式工作流生成、Registry 回读并随 GitHub Release 附带校验文件。
35
+
36
+ ## English
37
+
38
+ ### Changes
39
+
40
+ - The OpenAI Codex settings entry now uses a dedicated code-brackets icon. Replacement is limited to the uniquely matched Codex navigation item and is fully cleaned up on hot reload or disposal.
41
+ - Usage is presented in responsive two-column cards with a consistent “remaining” meaning. Refresh has an icon, a visible loading state, and accessible busy semantics, and its animation respects the reduced-motion preference.
42
+ - Five-hour usage always shows the exact reset time. Weekly usage shows the exact time within 24 hours and otherwise uses localized relative days; the complete timestamp remains available in tooltips and accessible labels.
43
+ - Selected models are listed first, while unselected models are collapsed by default and can be expanded on demand. The page also reports selected, visible, and total model counts.
44
+ - Context and maximum output use compact labels such as `272K` and `128K`. Only fields actually supplied by the installed provider catalog are shown; no additional capabilities are inferred.
45
+ - The settings content no longer overrides the Harness dialog width or scroll container. It uses two columns on desktop and one on narrow screens, reducing height and improving scrolling.
46
+ - Deselecting a model while unselected entries are collapsed returns keyboard focus to the expansion control. Reset-time parsing also rejects values that cannot produce a valid date.
47
+
48
+ ### Compatibility
49
+
50
+ - The exact compatibility target remains DeepSeek Harness `0.1.1-rc.2` with Node.js `>=22.19.0 <25`; compatibility with other DSH prereleases is not claimed.
51
+ - This release changes only the Web settings bundle and its tests. OAuth, Provider, transport, and model-setting persistence contracts are unchanged.
52
+ - Full checks and Linux/macOS/Windows `3/3` are bound to the accepted candidate and approved by the maintainer. The protected workflow still verifies the same candidate, supply-chain evidence, the Registry, and the GitHub Release before and after publication. Post-release live-account acceptance starts at `0/13` in a fresh `0.0.3` record and does not inherit evidence from an older release.
53
+ - This release does not provide image generation or editing.
54
+
55
+ ### Acceptance record
56
+
57
+ - [`v0.0.3.acceptance.json`](v0.0.3.acceptance.json)
58
+
59
+ ### Artifact verification
60
+
61
+ - SHA-256: generated by the protected release workflow and attached to the GitHub Release.
62
+ - SRI: generated by the protected release workflow, read back from the Registry, and attached to the GitHub Release.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-community",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "DeepSeek Harness Codex 社区接入 / Community Codex integration for DeepSeek Harness",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",