dsh-codex-subscription 1.4.0 → 1.5.0

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/AGENTS.md CHANGED
@@ -6,7 +6,7 @@ Use this guide when a user asks an Agent to install, update, verify, or remove
6
6
  ## Safety
7
7
 
8
8
  - Confirm the target DSH installation and profile. Use `web` only when it is the user's target.
9
- - Use the exact `1.4.0` package below; do not install a moving branch.
9
+ - Use the exact `1.5.0` package below; do not install a moving branch.
10
10
  - Never print OAuth credentials, account IDs, authorization callbacks, or credential-store contents.
11
11
  - Preserve the DSH profile, unrelated plugins, sessions, and saved sign-in.
12
12
  - Do not start, stop, or restart DSH without explicit permission.
@@ -14,23 +14,19 @@ Use this guide when a user asks an Agent to install, update, verify, or remove
14
14
 
15
15
  ## Locate DSH
16
16
 
17
- On Windows, check the command and common Portable entry points without recursively scanning disks:
17
+ On Windows, check the standard DSH and official npm entry points without recursively scanning disks:
18
18
 
19
19
  ```powershell
20
20
  Get-Command dsh -ErrorAction SilentlyContinue
21
21
  Get-Command npx -ErrorAction SilentlyContinue
22
- Test-Path -LiteralPath '.\dsh.exe' -PathType Leaf
23
- Test-Path -LiteralPath "$env:USERPROFILE\Downloads\DSH-Portable\dsh.exe" -PathType Leaf
24
22
  Get-CimInstance Win32_Process -Filter "Name = 'node.exe'" |
25
23
  Where-Object { $_.CommandLine -match '@deepseek-ai[\\/]dsh[\\/]lib[\\/]bin\.js' } |
26
24
  Select-Object ExecutablePath, CommandLine
27
25
  ```
28
26
 
29
- A current DSH-Portable includes `dsh.exe`. Missing system Node.js or pnpm is normal; do not install
30
- either globally for this plugin. If the Portable copy does not contain `dsh.exe`, update DSH-Portable
31
- instead of recreating its package-manager environment in this installer. For a matching running process,
32
- the Portable root is the parent of `runtime`; use its `dsh.exe`. If several targets remain possible, stop
33
- and ask the user which one to change.
27
+ DSH-Portable exposes the same standard `dsh plugin` contract. Missing system pnpm is normal; do not
28
+ install it globally for this plugin. If several targets remain possible, stop and ask the user which one
29
+ to change.
34
30
 
35
31
  ## Install or update
36
32
 
@@ -38,19 +34,13 @@ For an official DSH installation run through npm, keep the complete `npx` prefix
38
34
  run command does not create a global `dsh` command:
39
35
 
40
36
  ```sh
41
- npx -y @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add dsh-codex-subscription@1.4.0
37
+ npx -y @deepseek-ai/dsh@0.1.1-rc.2 plugin --profile web add dsh-codex-subscription@1.5.0
42
38
  ```
43
39
 
44
40
  With an existing global `dsh` command:
45
41
 
46
42
  ```sh
47
- dsh plugin --profile web add dsh-codex-subscription@1.4.0
48
- ```
49
-
50
- From a DSH-Portable folder:
51
-
52
- ```powershell
53
- .\dsh.exe plugin --profile web add dsh-codex-subscription@1.4.0
43
+ dsh plugin --profile web add dsh-codex-subscription@1.5.0
54
44
  ```
55
45
 
56
46
  Use the same `add` command to update or repair. This is the complete package-changing operation.
@@ -66,11 +56,9 @@ dsh plugin --profile web list dsh-codex-subscription --depth 0
66
56
  dsh --profile web --dump-config
67
57
  ```
68
58
 
69
- For DSH-Portable, replace `dsh` with `.\dsh.exe`. Static acceptance requires:
70
-
71
59
  For the official npm route, run the same checks with `npx -y @deepseek-ai/dsh@0.1.1-rc.2` in place of `dsh`.
72
60
 
73
- 1. `dsh-codex-subscription` version `1.4.0` appears exactly once.
61
+ 1. `dsh-codex-subscription` version `1.5.0` appears exactly once.
74
62
  2. `codex-subscription` appears exactly once in the composed config.
75
63
  3. No unrelated plugin or profile was changed and DSH was not restarted.
76
64
 
@@ -85,12 +73,6 @@ the user explicitly asks for a live model, search, or image-generation check.
85
73
  dsh plugin --profile web remove dsh-codex-subscription
86
74
  ```
87
75
 
88
- For DSH-Portable:
89
-
90
- ```powershell
91
- .\dsh.exe plugin --profile web remove dsh-codex-subscription
92
- ```
93
-
94
76
  For the official npm route:
95
77
 
96
78
  ```sh
package/DIRECTORY.md CHANGED
@@ -18,7 +18,7 @@ Use a ChatGPT subscription in DeepSeek Harness through independent OAuth sign-in
18
18
  - Codex CLI required: no
19
19
  - OpenAI API key required: no
20
20
  - Platforms: official DSH plus DSH-Portable product forms for Windows, macOS, and Linux
21
- - Main capabilities: Codex models, quota, web search, image generation, optional composer quota, optional Fast mode, secret-free support diagnostics
21
+ - Main capabilities: Codex models, quota and expiring resets, web search, image preview/download/refinement, optional composer quota, optional Fast mode, secret-free support diagnostics and direct feedback
22
22
  - Safety boundary: subscription failures remain visible and never silently fall back to another paid provider
23
23
 
24
24
  ## Install
package/README.md CHANGED
@@ -37,7 +37,7 @@ With an existing `dsh` command, install with:
37
37
  dsh plugin --profile web add dsh-codex-subscription
38
38
  ```
39
39
 
40
- From a DSH-Portable product folder, run `./dsh plugin ...` (`.\dsh.exe plugin ...` in Windows PowerShell). See below for the complete official npm, Agent, update, and uninstall routes.
40
+ DSH-Portable exposes the same standard plugin command, so the command above also applies there. See below for the complete official npm, Agent, update, and uninstall routes.
41
41
 
42
42
  ## Why this plugin
43
43
 
@@ -47,9 +47,9 @@ From a DSH-Portable product folder, run `./dsh plugin ...` (`.\dsh.exe plugin ..
47
47
  | **Recoverable and diagnosable** | Sign-in state reconciles automatically; Settings can create a support report without credentials or account identifiers |
48
48
  | **Visible quota** | Keep backend-provided standard Codex, Spark, and other limits separate |
49
49
  | **Composer quota** | Choose a compact percentage, progress bar, or no inline quota display |
50
- | **Safe quota reset** | Use a reset only for an exhausted Codex quota, with a cooldown, checkbox, and typed confirmation |
50
+ | **Safe quota reset** | See the next expiry and deliberately try a reset with a cooldown and acknowledgement |
51
51
  | **Subscription search** | Explicitly choose DSH default search or Codex subscription search |
52
- | **Codex image generation** | Describe an image in a DSH conversation and view the generated result in that session |
52
+ | **Codex image generation** | Preview or download the original, then describe refinements in the same conversation |
53
53
  | **Fast mode (Beta)** | Switch between Standard and Fast directly in the composer |
54
54
 
55
55
  These capabilities reuse the same local ChatGPT sign-in. Subscription routing failures stay visible and never silently switch to another paid route.
@@ -117,7 +117,7 @@ npx -y @deepseek-ai/dsh@0.1.1-rc.2 --profile web --dump-config
117
117
  </details>
118
118
 
119
119
  <details>
120
- <summary>An existing <code>dsh</code> command or a DSH-Portable folder</summary>
120
+ <summary>An existing <code>dsh</code> command</summary>
121
121
 
122
122
  ```sh
123
123
  dsh plugin --profile web add dsh-codex-subscription
@@ -125,8 +125,7 @@ dsh plugin --profile web list dsh-codex-subscription --depth 0
125
125
  dsh --profile web --dump-config
126
126
  ```
127
127
 
128
- For DSH-Portable, replace `dsh` with `./dsh` (`.\dsh.exe` in PowerShell). The plugin list should contain
129
- one `dsh-codex-subscription`, and the config should contain one `codex-subscription` entry.
128
+ The plugin list should contain one `dsh-codex-subscription`, and the config should contain one `codex-subscription` entry.
130
129
 
131
130
  </details>
132
131
 
@@ -144,10 +143,10 @@ Restart DSH manually after installation, then:
144
143
  - A clear choice between DSH default search and Codex subscription search;
145
144
  - Actual backend-provided quota, reset time, and freshness;
146
145
  - Separate standard Codex, Codex-Spark, Credits, and other independent limits;
147
- - Deliberate quota-reset redemption for exhausted Codex limits, with layered confirmation and no automatic retry;
146
+ - Visible quota-reset count and earliest expiry, with deliberate early redemption, layered confirmation, and no automatic retry;
148
147
  - Optional percentage or progress bar for the selected Codex model (off by default);
149
148
  - Standard or Fast mode for supported Codex models directly in the composer (Beta);
150
- - A copyable support report in Settings that excludes OAuth credentials, account identifiers, and authorization timestamps;
149
+ - A copyable support report and direct feedback link in Settings; the report excludes OAuth credentials, account identifiers, and authorization timestamps;
151
150
  - Visible errors when subscription routing is unavailable, with no silent paid fallback.
152
151
 
153
152
  ### Composer quota
@@ -162,10 +161,14 @@ Standard Codex uses the lowest remaining window returned by the service; Spark u
162
161
 
163
162
  ### Safe quota reset
164
163
 
165
- If ChatGPT reports an available quota reset, Settings shows it in a compact row. Redemption stays disabled
166
- until a Codex model quota is exhausted. Using one requires a five-second cooldown, an acknowledgement checkbox,
167
- and the exact confirmation phrase. Cancel never consumes a reset, rapid repeated clicks are single-flight, and an
168
- uncertain network result is never retried automatically.
164
+ If ChatGPT reports an available quota reset, Settings shows its count and earliest disclosed expiry in a compact row.
165
+ You may deliberately try it before a quota reaches 100%, which is useful for a reset nearing expiry. ChatGPT still
166
+ decides whether a window needs resetting and may return **nothing to reset** without spending the reset. The final
167
+ action requires an acknowledgement checkbox and five-second cooldown. Cancel never consumes a reset, rapid repeated
168
+ clicks are single-flight, and an uncertain network result is never retried automatically.
169
+
170
+ Generated images open in a plugin-owned preview with **Download original**. To refine one, continue describing the
171
+ change in the same conversation; the durable conversation image remains available to the next model turn.
169
172
 
170
173
  ### Composer speed (Beta)
171
174
 
@@ -198,14 +201,10 @@ dsh plugin --profile web remove dsh-codex-subscription
198
201
 
199
202
  </details>
200
203
 
201
- From a DSH-Portable folder, replace `dsh` above with `.\dsh.exe`.
202
-
203
204
  ## Troubleshooting
204
205
 
205
206
  - **`dsh` is not recognized:** the official npm route does not create a global `dsh` command; use the complete `npx -y @deepseek-ai/dsh@0.1.1-rc.2 ...` command above;
206
- - **`dsh.exe` is not recognized:** that file is not in the current folder; enter the DSH-Portable folder first or use the one-line Windows setup;
207
- - **DSH-Portable is not found:** enter its folder and rerun setup, or run `.\dsh.exe plugin --profile web add dsh-codex-subscription` directly;
208
- - **More than one DSH exists:** the one-line setup combines running copies with Portable folders detected in common locations; enter its number. For an Agent, use the intended folder or pass `-DshPath` explicitly;
207
+ - **More than one DSH exists:** run the standard command from the intended DSH environment. For an Agent, specify the target or pass `-DshPath` explicitly;
209
208
  - **Setup still fails:** send the Agent guide above to an Agent. Do not delete the profile or change the system PATH to force an install.
210
209
  - **Need to report a problem:** generate a **Support diagnostics** report at the bottom of Settings, then open the [bug report form](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml). The form collects exact DSH/plugin versions and reproduction steps. The generated report excludes credentials and account identifiers; still do not attach sign-in URLs, authorization codes, or browser callback addresses.
211
210
 
package/README.zh-CN.md CHANGED
@@ -38,7 +38,7 @@
38
38
  dsh plugin --profile web add dsh-codex-subscription
39
39
  ```
40
40
 
41
- DSH-Portable 用户在产品目录中运行 `./dsh plugin ...`,Windows PowerShell 使用 `.\dsh.exe plugin ...`。完整的官方 npm、Agent 安装、更新和卸载方式见下文。
41
+ DSH-Portable 也提供相同的标准插件命令,因此同样使用上面的命令。完整的官方 npm、Agent 安装、更新和卸载方式见下文。
42
42
 
43
43
  ## 核心优势
44
44
 
@@ -48,9 +48,9 @@ DSH-Portable 用户在产品目录中运行 `./dsh plugin ...`,Windows PowerSh
48
48
  | **可恢复、可诊断** | 登录状态会自动对账;设置页可生成不含凭据和账号标识的支持报告 |
49
49
  | **额度可见** | 普通 Codex、Spark 等服务端实际返回的额度分开显示 |
50
50
  | **输入框额度** | 可选择紧凑百分比、进度条或完全关闭输入框额度显示 |
51
- | **安全额度重置** | 仅在 Codex 额度用尽后使用重置,并经过冷静期、勾选和输入确认 |
51
+ | **安全额度重置** | 直接查看最早到期时间,并通过冷静期和知情确认主动尝试重置 |
52
52
  | **订阅搜索** | 可在 DSH 默认搜索与 Codex 订阅搜索之间明确切换 |
53
- | **Codex 图片生成** | DSH 对话里直接描述画面,生成结果会显示在当前会话中 |
53
+ | **Codex 图片生成** | 预览或下载原图,并在同一会话继续描述修改内容 |
54
54
  | **高速模式(Beta)** | 直接在输入框切换标准或高速,无需离开当前会话 |
55
55
 
56
56
  这些能力共用同一份本机 ChatGPT 登录。订阅路由失败时会明确报错,不会静默切换到其他付费路由。
@@ -117,7 +117,7 @@ npx -y @deepseek-ai/dsh@0.1.1-rc.2 --profile web --dump-config
117
117
  </details>
118
118
 
119
119
  <details>
120
- <summary>已经能运行 <code>dsh</code>,或位于 DSH-Portable 目录</summary>
120
+ <summary>已经能运行 <code>dsh</code></summary>
121
121
 
122
122
  ```sh
123
123
  dsh plugin --profile web add dsh-codex-subscription
@@ -125,8 +125,7 @@ dsh plugin --profile web list dsh-codex-subscription --depth 0
125
125
  dsh --profile web --dump-config
126
126
  ```
127
127
 
128
- DSH-Portable 请把 `dsh` 换成 `./dsh`(PowerShell 使用 `.\dsh.exe`)。安装列表中应只有一个
129
- `dsh-codex-subscription`,配置中应只有一个 `codex-subscription` 条目。
128
+ 安装列表中应只有一个 `dsh-codex-subscription`,配置中应只有一个 `codex-subscription` 条目。
130
129
 
131
130
  </details>
132
131
 
@@ -144,10 +143,10 @@ DSH-Portable 请把 `dsh` 换成 `./dsh`(PowerShell 使用 `.\dsh.exe`)。
144
143
  - DSH 默认搜索与 Codex 订阅搜索可随时切换;
145
144
  - 设置页显示服务端返回的额度、重置时间和更新时间;
146
145
  - 普通 Codex、Codex-Spark、Credits 等独立额度分开显示;
147
- - 已用尽的 Codex 额度可主动使用重置卡,并经过分层确认且不会自动重试;
146
+ - 显示重置卡数量与最早到期时间,也允许在额度未完全用尽时主动尝试,并经过分层确认且不会自动重试;
148
147
  - 输入框可用百分比或进度条显示当前 Codex 模型的剩余额度(默认关闭);
149
148
  - 输入框可为支持的 Codex 模型切换标准或高速模式(Beta);
150
- - 设置页可生成并复制无敏感信息的支持诊断,报告不包含 OAuth 凭据、账号标识或授权时间;
149
+ - 设置页可生成并复制无敏感信息的支持诊断,并直接打开反馈入口;报告不包含 OAuth 凭据、账号标识或授权时间;
151
150
  - 订阅路由不可用时明确报错,不会静默切换到其他付费路由。
152
151
 
153
152
  ### 输入框额度
@@ -161,9 +160,11 @@ Spark 使用独立额度。插件不会写死“5 小时 + 每周”,也不会
161
160
 
162
161
  ### 安全使用额度重置
163
162
 
164
- ChatGPT 返回可用重置卡时,设置页会用紧凑的一行显示。只有至少一个 Codex 模型额度已用尽后才能使用;
165
- 最终操作还需要等待 5 秒、勾选知情确认,并输入完全一致的确认短语。取消不会消耗,快速连续点击只允许一次请求,
166
- 网络结果不确定时也不会自动重试。
163
+ ChatGPT 返回可用重置卡时,设置页会用紧凑的一行显示数量和服务端提供的最早到期时间。即使额度尚未到 100%,
164
+ 也可以主动尝试使用,适合重置卡即将过期的情况;是否需要重置仍由 ChatGPT 判断,服务端可能返回“当前无需重置”且不扣次数。
165
+ 最终操作需要勾选知情确认并等待 5 秒。取消不会消耗,快速连续点击只允许一次请求,网络结果不确定时也不会自动重试。
166
+
167
+ 生成的图片可在插件自有预览中下载原图。需要继续修改时,直接在同一会话描述改动;持久化的会话图片会继续提供给下一轮模型。
167
168
 
168
169
  ### 输入框速度(Beta)
169
170
 
@@ -196,14 +197,10 @@ dsh plugin --profile web remove dsh-codex-subscription
196
197
 
197
198
  </details>
198
199
 
199
- DSH-Portable 在其目录中把上述 `dsh` 换成 `.\dsh.exe`。
200
-
201
200
  ## 常见问题
202
201
 
203
202
  - **`dsh` 无法识别**:官方 npm 方式本来就不会创建全局 `dsh` 命令,请使用上面的完整 `npx -y @deepseek-ai/dsh@0.1.1-rc.2 ...` 命令;
204
- - **`dsh.exe` 无法识别**:当前目录没有该文件。请先进入 DSH-Portable 目录,或使用 Windows 单行助手;
205
- - **找不到 DSH-Portable**:进入它的目录后重新执行安装命令,或直接运行 `.\dsh.exe plugin --profile web add dsh-codex-subscription`;
206
- - **电脑上有多个 DSH**:单行助手会合并正在运行的副本与常用目录内检测到的 Portable,输入对应编号即可;交给 Agent 时让它进入目标目录,或明确传入 `-DshPath`;
203
+ - **电脑上有多个 DSH**:请在目标 DSH 环境中运行标准命令;交给 Agent 时明确目标,或传入 `-DshPath`;
207
204
  - **安装仍然失败**:把上面的 Agent 文档链接发给 Agent,不要删除 profile 或随意修改系统 PATH。
208
205
  - **需要提交问题**:在设置页底部生成“支持诊断”,然后打开[使用问题表单](https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml)。表单会收集准确的 DSH/插件版本和复现步骤;报告不含凭据和账号标识,仍不要附上登录链接、授权码或浏览器回调地址。
209
206
 
package/lib/client.js CHANGED
@@ -127,6 +127,7 @@ window.__ModuleLoader__.load({
127
127
  ];
128
128
  const NS = "settings.codexSubscription";
129
129
  const CHANNEL = "/codex-subscription";
130
+ const SUPPORT_ISSUE_URL = "https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml";
130
131
  const QUICK_QUOTA_REFRESH_EVENT = "dsh-codex-subscription:refresh-quick-quota";
131
132
  const QUICK_QUOTA_REFRESH_MS = 6e4;
132
133
  const zh = {
@@ -153,6 +154,7 @@ window.__ModuleLoader__.load({
153
154
  diagnosticsCopy: "复制诊断",
154
155
  diagnosticsCopied: "已复制",
155
156
  diagnosticsFailed: "无法生成诊断信息。",
157
+ feedbackOpen: "反馈问题",
156
158
  searchTitle: "搜索来源",
157
159
  searchDsh: "DSH 默认",
158
160
  searchDshHint: "当前搜索服务",
@@ -183,13 +185,14 @@ window.__ModuleLoader__.load({
183
185
  monthlyCreditLimit: "Credits 月度消费上限",
184
186
  resetCredits: "可用额度重置次数",
185
187
  resetCreditsValue: "{count} 次",
186
- resetUse: "使用一次重置",
187
- resetUnavailable: "仅当至少一个 Codex 模型额度已用尽时可使用。",
188
+ resetUse: "查看并使用",
188
189
  resetPreparing: "正在读取重置详情…",
189
190
  resetConfirmTitle: "确认使用额度重置",
190
- resetWarning: "此操作会立即消耗 1 次额度重置,且无法撤销。请确认你确实需要恢复已用尽的模型额度。",
191
- resetAcknowledge: "我知道这会消耗 1 次额度重置",
192
- resetTypeHint: "输入 {phrase} 以继续",
191
+ resetWarning: " ChatGPT 执行重置,将立即消耗 1 次且无法撤销。",
192
+ resetEarlyWarning: "当前模型额度尚未用尽;ChatGPT 可能执行重置,也可能判定暂无需重置且不消耗次数。",
193
+ resetAcknowledge: "我知道这次操作可能立即消耗 1 次重置",
194
+ resetCreditExpires: "最早到期:{value}",
195
+ resetCreditExpiryUnknown: "到期时间未提供",
193
196
  resetWait: "请再等待 {count} 秒",
194
197
  resetFinal: "消耗 1 次并重置额度",
195
198
  resetUsing: "正在重置…",
@@ -202,8 +205,7 @@ window.__ModuleLoader__.load({
202
205
  resetExpired: "本次确认已失效,请重新开始。",
203
206
  resetInProgress: "额度重置正在处理中。",
204
207
  resetTooEarly: "请等待冷静期结束后再确认。",
205
- resetPhraseMismatch: "确认短语不一致。",
206
- resetNotExhausted: "当前 Codex 模型额度尚未用尽。",
208
+ resetAcknowledgeRequired: "请先确认已了解这次操作可能消耗重置次数。",
207
209
  resetAccountChanged: "登录账号已变更,请重新开始。",
208
210
  creditsNote: "仅显示 Codex 为此账户或工作区实际返回的额外 Credits、消费上限或额度重置次数;三者不是同一项。",
209
211
  creditsUsed: "已用 {used} / {limit} credits",
@@ -240,7 +242,9 @@ window.__ModuleLoader__.load({
240
242
  imageLoading: "正在加载图片…",
241
243
  imageLoadFailed: "图片加载失败,点击重试",
242
244
  imagePreview: "图片预览",
243
- imageClosePreview: "关闭预览"
245
+ imageClosePreview: "关闭预览",
246
+ imageDownload: "下载原图",
247
+ imageRefineHint: "继续在当前会话描述修改内容,即可基于这张图片继续调整。"
244
248
  };
245
249
  const en = {
246
250
  nav: "Codex",
@@ -266,6 +270,7 @@ window.__ModuleLoader__.load({
266
270
  diagnosticsCopy: "Copy report",
267
271
  diagnosticsCopied: "Copied",
268
272
  diagnosticsFailed: "Could not create diagnostics.",
273
+ feedbackOpen: "Report a problem",
269
274
  searchTitle: "Search source",
270
275
  searchDsh: "DSH default",
271
276
  searchDshHint: "Current search service",
@@ -296,13 +301,14 @@ window.__ModuleLoader__.load({
296
301
  monthlyCreditLimit: "Monthly Credits spending cap",
297
302
  resetCredits: "Available quota resets",
298
303
  resetCreditsValue: "{count} available",
299
- resetUse: "Use one reset",
300
- resetUnavailable: "Available only when at least one Codex model quota is exhausted.",
304
+ resetUse: "Review and use",
301
305
  resetPreparing: "Reading reset details…",
302
306
  resetConfirmTitle: "Confirm quota reset",
303
- resetWarning: "This immediately consumes one quota reset and cannot be undone. Continue only if you intend to restore an exhausted model quota.",
304
- resetAcknowledge: "I understand this consumes one quota reset",
305
- resetTypeHint: "Type {phrase} to continue",
307
+ resetWarning: "If ChatGPT performs the reset, one reset is consumed immediately and cannot be restored.",
308
+ resetEarlyWarning: "No model quota is exhausted. ChatGPT may reset it, or report that nothing needs resetting without consuming a reset.",
309
+ resetAcknowledge: "I understand this may consume one reset now",
310
+ resetCreditExpires: "Earliest expiry: {value}",
311
+ resetCreditExpiryUnknown: "Expiration time not provided",
306
312
  resetWait: "Wait {count} more seconds",
307
313
  resetFinal: "Consume one reset",
308
314
  resetUsing: "Resetting…",
@@ -315,8 +321,7 @@ window.__ModuleLoader__.load({
315
321
  resetExpired: "This confirmation expired. Start again.",
316
322
  resetInProgress: "A quota reset is already in progress.",
317
323
  resetTooEarly: "Wait for the cooldown before confirming.",
318
- resetPhraseMismatch: "The confirmation phrase does not match.",
319
- resetNotExhausted: "The current Codex model quota is not exhausted.",
324
+ resetAcknowledgeRequired: "Confirm that you understand this may consume a reset.",
320
325
  resetAccountChanged: "The signed-in account changed. Start again.",
321
326
  creditsNote: "Shows only extra Credits, spending caps, or quota resets returned for this account or workspace; these are separate items.",
322
327
  creditsUsed: "{used} / {limit} credits used",
@@ -353,7 +358,9 @@ window.__ModuleLoader__.load({
353
358
  imageLoading: "Loading image…",
354
359
  imageLoadFailed: "Image failed to load. Click to retry",
355
360
  imagePreview: "Image preview",
356
- imageClosePreview: "Close preview"
361
+ imageClosePreview: "Close preview",
362
+ imageDownload: "Download original",
363
+ imageRefineHint: "Describe the changes in this conversation to continue refining this image."
357
364
  };
358
365
  const STYLE = `
359
366
  .codexSubscription{display:flex;flex-direction:column;gap:10px;max-width:720px;color:var(--dsw-alias-label-primary);container-type:inline-size}
@@ -372,7 +379,7 @@ window.__ModuleLoader__.load({
372
379
  .codexSubscriptionFlow p{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionCode{width:max-content;max-width:100%;font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;letter-spacing:.08em;overflow-wrap:anywhere}
373
380
  .codexSubscriptionError{font-size:13px;line-height:20px;color:var(--dsw-alias-state-error-primary)}.codexSubscriptionInput{width:100%;box-sizing:border-box}
374
381
  .codexSubscriptionRecover{display:flex;align-items:center;justify-content:space-between;gap:12px}.codexSubscriptionRecover .codexSubscriptionError{flex:1}.codexSubscriptionRecover button{flex:0 0 auto}
375
- .codexSubscriptionDiagnostics pre{max-height:240px;margin:0;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/17px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--dsw-alias-label-secondary)}
382
+ .codexSubscriptionDiagnostics pre{max-height:240px;margin:0;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);overflow:auto;white-space:pre-wrap;overflow-wrap:anywhere;font:11px/17px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--dsw-alias-label-secondary)}.codexSubscriptionLink{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;min-height:32px;padding:0 13px;border:1px solid var(--dsw-alias-border-l2);border-radius:999px;background:transparent;color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;text-decoration:none;white-space:nowrap}.codexSubscriptionLink:hover{background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionLink:focus-visible{outline:2px solid var(--dsw-alias-border-l3);outline-offset:2px}
376
383
  .codexSubscriptionSectionTitle{display:flex;flex:1;min-width:0;flex-direction:column;gap:2px}.codexSubscriptionFreshness{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
377
384
  .codexSubscriptionRefresh{flex:0 0 auto;min-width:72px;width:max-content;white-space:nowrap!important;word-break:keep-all!important;overflow-wrap:normal!important;writing-mode:horizontal-tb!important}.codexSubscriptionRefresh *{white-space:nowrap!important;word-break:keep-all!important;writing-mode:horizontal-tb!important}
378
385
  .codexSubscriptionEmpty{padding:18px;border:1px dashed var(--dsw-alias-border-l3);border-radius:10px;text-align:center;font-size:13px;line-height:20px;color:var(--dsw-alias-label-tertiary)}
@@ -382,13 +389,13 @@ window.__ModuleLoader__.load({
382
389
  .codexSubscriptionLimit progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexSubscriptionLimit progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionLimit progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionLimitMeta{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
383
390
  .codexSubscriptionCreditSection{display:flex;flex-direction:column;gap:7px}.codexSubscriptionCreditNote{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionCreditRows{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}.codexSubscriptionCreditBalance,.codexSubscriptionSpendLimit{min-width:0;border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-module-platform)}
384
391
  .codexSubscriptionCreditBalance{display:flex;flex-direction:column;gap:6px}.codexSubscriptionCreditBalance span,.codexSubscriptionCreditLabel{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionCreditBalance strong{font:600 18px/24px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
385
- .codexSubscriptionResetSummary{display:flex;align-items:center;justify-content:space-between;gap:10px}.codexSubscriptionResetBalance{display:flex;flex-direction:column;gap:8px}.codexSubscriptionResetBalance .codexSubscriptionActions{justify-content:flex-start}.codexSubscriptionResetFlow{display:flex;flex-direction:column;gap:9px;border:1px solid var(--dsw-alias-state-warning-border,var(--dsw-alias-border-l2));border-radius:10px;padding:11px 12px;background:var(--dsw-alias-bg-module-platform)}.codexSubscriptionResetFlow h4{margin:0;font-size:13px;line-height:20px;font-weight:600}.codexSubscriptionResetWarning{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionResetCheck{display:flex;align-items:flex-start;gap:8px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);cursor:pointer}.codexSubscriptionResetCheck input{margin:3px 0 0;accent-color:var(--dsw-alias-label-primary)}.codexSubscriptionResetPhrase{font:600 12px/18px ui-monospace,SFMono-Regular,Consolas,monospace}.codexSubscriptionResetFinal{border-color:var(--dsw-alias-state-error-primary)!important;color:var(--dsw-alias-state-error-primary)!important}.codexSubscriptionResetResult{font-size:12px;line-height:18px;color:var(--dsw-alias-state-success-primary)}
392
+ .codexSubscriptionResetSummary{display:flex;align-items:center;justify-content:space-between;gap:10px}.codexSubscriptionResetMeta{display:flex;min-width:0;flex-direction:column;gap:1px}.codexSubscriptionResetBalance{display:flex;flex-direction:column;gap:8px}.codexSubscriptionResetBalance .codexSubscriptionActions{justify-content:flex-start}.codexSubscriptionResetFlow{display:flex;flex-direction:column;gap:10px;border-top:1px solid var(--dsw-alias-border-l2);padding-top:10px}.codexSubscriptionResetFlow h4{margin:0;font-size:13px;line-height:20px;font-weight:500}.codexSubscriptionResetWarning{font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary)}.codexSubscriptionResetExpiry{font-size:11px;line-height:17px;color:var(--dsw-alias-label-tertiary)}.codexSubscriptionResetCheck{display:flex;align-items:flex-start;gap:8px;padding:9px 10px;border-radius:8px;background:var(--dsw-alias-bg-module-platform);font-size:12px;line-height:18px;color:var(--dsw-alias-label-primary);cursor:pointer}.codexSubscriptionResetCheck input{margin:3px 0 0;accent-color:var(--dsw-alias-label-primary)}.codexSubscriptionResetFinal{border-color:var(--dsw-alias-state-error-primary)!important;color:var(--dsw-alias-state-error-primary)!important}.codexSubscriptionResetResult{font-size:12px;line-height:18px;color:var(--dsw-alias-state-success-primary)}
386
393
  .codexSubscriptionSpendLimit{display:flex;flex-direction:column;gap:8px}.codexSubscriptionSpendTop{display:flex;align-items:baseline;justify-content:space-between;gap:12px}.codexSubscriptionSpendTop strong{font:600 16px/22px ui-monospace,SFMono-Regular,Consolas,monospace;font-variant-numeric:tabular-nums}.codexSubscriptionSpendLimit progress{width:100%;height:6px;border:0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-border-l3);accent-color:var(--dsw-alias-brand-primary,#3964fe);-webkit-appearance:none;appearance:none}.codexSubscriptionSpendLimit progress::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexSubscriptionSpendLimit progress::-webkit-progress-value{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}.codexSubscriptionSpendLimit progress::-moz-progress-bar{background:var(--dsw-alias-brand-primary,#3964fe);border-radius:999px}
387
394
  .codexComposerQuota{display:inline-flex;align-items:center;flex:0 0 auto;height:28px;box-sizing:border-box;padding:0;color:var(--dsw-alias-label-secondary);font-family:inherit;font-size:12px;line-height:20px;font-weight:500;font-variant-numeric:tabular-nums;white-space:nowrap;user-select:none}.codexComposerQuotaBar{display:block;width:40px;height:4px;border:0;border-radius:999px;overflow:hidden;background:var(--dsw-alias-border-l3);accent-color:var(--dsw-alias-label-secondary);-webkit-appearance:none;appearance:none}.codexComposerQuotaBar::-webkit-progress-bar{background:var(--dsw-alias-border-l3);border-radius:999px}.codexComposerQuotaBar::-webkit-progress-value{background:var(--dsw-alias-label-secondary);border-radius:999px}.codexComposerQuotaBar::-moz-progress-bar{background:var(--dsw-alias-label-secondary);border-radius:999px}
388
395
  .codexModelSelect{position:relative;min-width:0}.codexModelSelectTrigger{display:flex;align-items:center;gap:4px;min-width:0;max-width:min(360px,45cqw);height:28px;padding:0 4px 0 8px;border:0;border-radius:24px;outline:0;background:transparent;color:var(--dsw-alias-label-secondary);font-size:13px;font-weight:500;line-height:20px;cursor:pointer}.codexModelSelectTrigger:hover:not(:disabled),.codexModelSelectTrigger[aria-expanded=true]{background:var(--dsw-alias-interactive-bg-hover)}.codexModelSelectTrigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3)}.codexModelSelectTrigger:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectBolt{display:block;flex:none;width:14px;height:14px;color:var(--dsw-alias-label-primary)}.codexModelSelectLabel{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectEffort{flex:none;color:var(--dsw-alias-label-caption)}.codexModelSelectChevron{flex:none;color:var(--dsw-alias-label-caption);transition:transform 120ms}.codexModelSelectTrigger[aria-expanded=true] .codexModelSelectChevron{transform:rotate(180deg)}
389
396
  .codexModelSelectMenu,.codexModelSelectSubmenu{position:absolute;z-index:30;box-sizing:border-box;width:max-content;min-width:min(240px,calc(100vw - 32px));max-width:min(420px,calc(100vw - 32px));max-height:min(360px,calc(100vh - 96px));padding:4px;border:1px solid var(--dsw-alias-border-inverted);border-radius:12px;background:var(--dsw-specific-menu);box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary);overflow:hidden}.codexModelSelectMenu{right:0;bottom:calc(100% + 8px)}.codexModelSelectSubmenu{right:calc(100% + 8px);bottom:0;min-width:min(230px,calc(100vw - 32px))}.codexModelSelectCell{display:flex;align-items:center;gap:8px;width:100%;min-width:100%;height:40px;box-sizing:border-box;padding:0 10px;border:0;border-radius:10px;background:transparent;color:inherit;font-size:14px;line-height:22px;text-align:left;cursor:pointer}.codexModelSelectCell:hover,.codexModelSelectCell:focus-visible,.codexModelSelectCell[data-open=true]{background:var(--dsw-alias-interactive-bg-hover);outline:0}.codexModelSelectCell:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectCellLabel{flex:none;white-space:nowrap}.codexModelSelectCellValue{flex:auto;min-width:0;overflow:hidden;color:var(--dsw-alias-label-tertiary);text-align:right;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectCellChevron{flex:none;color:var(--dsw-alias-label-tertiary)}.codexModelSelectGroups{min-height:0;max-height:352px;overflow-y:auto}.codexModelSelectGroup+.codexModelSelectGroup{margin-top:4px}.codexModelSelectGroupTitle{position:sticky;top:0;z-index:1;padding:5px 8px 3px;background:var(--dsw-specific-menu);color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:500;line-height:18px}.codexModelSelectOption{display:flex;align-items:center;gap:8px;width:100%;min-width:100%;min-height:38px;box-sizing:border-box;padding:6px 8px;border:0;border-radius:10px;outline:0;background:transparent;color:inherit;text-align:left;cursor:pointer}.codexModelSelectOption:hover:not(:disabled),.codexModelSelectOption:focus-visible{background:var(--dsw-alias-interactive-bg-hover)}.codexModelSelectOption:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.codexModelSelectOptionCopy{display:flex;flex:1;min-width:0;flex-direction:column}.codexModelSelectOptionName{overflow:hidden;color:inherit;font-size:14px;font-weight:500;line-height:20px;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectOptionDescription{overflow:hidden;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:18px;text-overflow:ellipsis;white-space:nowrap}.codexModelSelectCheck{display:grid;place-items:center;flex:0 0 18px;color:var(--dsw-alias-label-primary)}.codexModelSelectStatus,.codexModelSelectEmpty{padding:10px;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:20px}.codexModelSelectError,.codexModelSelectWarning{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:4px;padding:7px 8px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);font-size:12px;line-height:18px}.codexModelSelectWarning{background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-state-warn-label)}.codexModelSelectRetry{flex:none;padding:0;border:0;background:transparent;color:inherit;font:inherit;font-weight:600;cursor:pointer}
390
397
  .codexModelSelectMenu{overflow:visible}
391
- .codexImageTool{display:flex;flex-direction:column;gap:8px;margin:4px 0;color:var(--dsw-alias-label-primary)}.codexImageToolRow{display:flex;align-items:center;min-height:24px;gap:8px;font-size:13px;line-height:20px}.codexImageToolIcon{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--dsw-alias-label-secondary)}.codexImageToolIcon::before{content:'';width:8px;height:8px;border:1.5px solid currentColor;border-radius:3px}.codexImageTool[data-state=running] .codexImageToolIcon::before{border-radius:50%;border-right-color:transparent;animation:codexImageSpin 800ms linear infinite}.codexImageTool[data-state=error] .codexImageToolIcon::before{border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-state-error-primary)}.codexImageToolTitle{font-weight:500}.codexImageToolState{color:var(--dsw-alias-label-tertiary)}.codexImageToolError{margin:0 0 0 24px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}.codexImageToolGallery{margin-left:24px}.codexGeneratedImageFrame{display:flex;align-items:center;justify-content:center;width:min(240px,100%);min-height:120px;padding:0;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);cursor:pointer}.codexGeneratedImageFrame img{display:block;width:100%;max-height:240px;object-fit:contain}.codexGeneratedImageRetry{min-height:36px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);cursor:pointer}.codexGeneratedImageLightbox{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:32px;border:0;background:rgba(0,0,0,.72)}.codexGeneratedImageLightbox img{display:block;max-width:min(1100px,calc(100vw - 64px));max-height:calc(100vh - 64px);object-fit:contain}.codexGeneratedImageClose{position:absolute;top:16px;right:16px;width:36px;height:36px;border:1px solid rgba(255,255,255,.35);border-radius:50%;background:rgba(0,0,0,.48);color:#fff;font-size:20px;line-height:1;cursor:pointer}@keyframes codexImageSpin{to{transform:rotate(360deg)}}
398
+ .codexImageTool{display:flex;flex-direction:column;gap:8px;margin:4px 0;color:var(--dsw-alias-label-primary)}.codexImageToolRow{display:flex;align-items:center;min-height:24px;gap:8px;font-size:13px;line-height:20px}.codexImageToolIcon{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--dsw-alias-label-secondary)}.codexImageToolIcon::before{content:'';width:8px;height:8px;border:1.5px solid currentColor;border-radius:3px}.codexImageTool[data-state=running] .codexImageToolIcon::before{border-radius:50%;border-right-color:transparent;animation:codexImageSpin 800ms linear infinite}.codexImageTool[data-state=error] .codexImageToolIcon::before{border-color:var(--dsw-alias-state-error-primary);background:var(--dsw-alias-state-error-primary)}.codexImageToolTitle{font-weight:500}.codexImageToolState{color:var(--dsw-alias-label-tertiary)}.codexImageToolError{margin:0 0 0 24px;font-size:12px;line-height:18px;color:var(--dsw-alias-state-error-primary)}.codexImageToolGallery{margin-left:24px}.codexGeneratedImageFrame{display:flex;align-items:center;justify-content:center;width:min(240px,100%);min-height:120px;padding:0;overflow:hidden;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-tertiary);cursor:pointer}.codexGeneratedImageFrame img{display:block;width:100%;max-height:240px;object-fit:contain}.codexGeneratedImageRetry{min-height:36px;padding:0 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-module-platform);color:var(--dsw-alias-label-secondary);cursor:pointer}.codexGeneratedImageLightbox{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center;padding:32px;border:0;background:rgba(0,0,0,.72)}.codexGeneratedImageLightbox img{display:block;max-width:min(1100px,calc(100vw - 64px));max-height:calc(100vh - 112px);object-fit:contain}.codexGeneratedImageClose{position:absolute;top:16px;right:16px;width:36px;height:36px;border:1px solid rgba(255,255,255,.35);border-radius:50%;background:rgba(0,0,0,.48);color:#fff;font-size:20px;line-height:1;cursor:pointer}.codexGeneratedImageActions{position:absolute;left:50%;bottom:18px;transform:translateX(-50%);display:flex;align-items:center;gap:10px;max-width:min(90vw,720px);padding:6px 8px 6px 12px;border:1px solid rgba(255,255,255,.2);border-radius:999px;background:rgba(20,20,20,.82);color:rgba(255,255,255,.72);font-size:12px;line-height:18px;backdrop-filter:blur(12px)}.codexGeneratedImageDownload{flex:0 0 auto;padding:5px 10px;border-radius:999px;background:rgba(255,255,255,.12);color:#fff;text-decoration:none;white-space:nowrap}.codexGeneratedImageDownload:hover{background:rgba(255,255,255,.2)}.codexGeneratedImageDownload:focus-visible,.codexGeneratedImageClose:focus-visible{outline:2px solid #fff;outline-offset:2px}.codexGeneratedImageRefineHint{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@keyframes codexImageSpin{to{transform:rotate(360deg)}}
392
399
  @container (max-width:560px){.codexSubscriptionCreditRows{grid-template-columns:1fr}}
393
400
  @container (max-width:480px){.codexSubscriptionAccountRow,.codexSubscriptionSectionHead{align-items:flex-start;flex-direction:column}.codexSubscriptionActions{width:100%}.codexSubscriptionSearchChoices{grid-template-columns:1fr}}
394
401
  @media(max-width:640px){.codexSubscriptionCard{padding:14px}}
@@ -413,12 +420,20 @@ window.__ModuleLoader__.load({
413
420
  const date = new Date(value);
414
421
  return Number.isFinite(date.getTime()) ? date : void 0;
415
422
  };
423
+ const imageDownloadName = (attachment) => {
424
+ const fallback = "codex-generated-image.png";
425
+ if (typeof attachment?.name !== "string") return fallback;
426
+ const cleaned = attachment.name.replace(/[<>:"/\\|?*\u0000-\u001f]/gu, "-").replace(/[. ]+$/u, "").trim();
427
+ if (cleaned === "") return fallback;
428
+ return cleaned.toLowerCase().endsWith(".png") ? cleaned : `${cleaned}.png`;
429
+ };
416
430
  function CodexGeneratedImage({ attachment, loadImage, t }) {
417
431
  const [attempt, setAttempt] = (0, react.useState)(0);
418
432
  const [error, setError] = (0, react.useState)(false);
419
433
  const [open, setOpen] = (0, react.useState)(false);
420
434
  const [src, setSrc] = (0, react.useState)();
421
435
  const closeRef = (0, react.useRef)(null);
436
+ const downloadRef = (0, react.useRef)(null);
422
437
  const triggerRef = (0, react.useRef)(null);
423
438
  (0, react.useEffect)(() => {
424
439
  let live = true;
@@ -445,7 +460,9 @@ window.__ModuleLoader__.load({
445
460
  setOpen(false);
446
461
  } else if (event.key === "Tab") {
447
462
  event.preventDefault();
448
- closeRef.current?.focus();
463
+ const controls = [downloadRef.current, closeRef.current].filter(Boolean);
464
+ const current = controls.indexOf(document.activeElement);
465
+ controls[event.shiftKey ? current <= 0 ? controls.length - 1 : current - 1 : (current + 1) % controls.length]?.focus();
449
466
  }
450
467
  };
451
468
  document.addEventListener("keydown", handleKeyDown);
@@ -456,6 +473,7 @@ window.__ModuleLoader__.load({
456
473
  };
457
474
  }, [open]);
458
475
  const label = attachment.name ?? t("imageLabel");
476
+ const downloadName = imageDownloadName(attachment);
459
477
  if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
460
478
  type: "button",
461
479
  className: "codexGeneratedImageRetry",
@@ -481,18 +499,35 @@ window.__ModuleLoader__.load({
481
499
  "aria-modal": "true",
482
500
  "aria-label": t("imagePreview"),
483
501
  onClick: () => setOpen(false),
484
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
485
- src,
486
- alt: label,
487
- onClick: (event) => event.stopPropagation()
488
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
489
- ref: closeRef,
490
- type: "button",
491
- className: "codexGeneratedImageClose",
492
- "aria-label": t("imageClosePreview"),
493
- onClick: () => setOpen(false),
494
- children: "×"
495
- })]
502
+ children: [
503
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
504
+ src,
505
+ alt: label,
506
+ onClick: (event) => event.stopPropagation()
507
+ }),
508
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
509
+ className: "codexGeneratedImageActions",
510
+ onClick: (event) => event.stopPropagation(),
511
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
512
+ className: "codexGeneratedImageRefineHint",
513
+ children: t("imageRefineHint")
514
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
515
+ ref: downloadRef,
516
+ className: "codexGeneratedImageDownload",
517
+ href: src,
518
+ download: downloadName,
519
+ children: t("imageDownload")
520
+ })]
521
+ }),
522
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
523
+ ref: closeRef,
524
+ type: "button",
525
+ className: "codexGeneratedImageClose",
526
+ "aria-label": t("imageClosePreview"),
527
+ onClick: () => setOpen(false),
528
+ children: "×"
529
+ })
530
+ ]
496
531
  })] });
497
532
  }
498
533
  function CodexImageToolRow({ block, loadImage, t }) {
@@ -1311,18 +1346,28 @@ window.__ModuleLoader__.load({
1311
1346
  })]
1312
1347
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1313
1348
  className: "codexSubscriptionActions",
1314
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1315
- type: "button",
1316
- variant: "outline",
1317
- disabled: busy,
1318
- onClick: load,
1319
- children: t("diagnosticsLoad")
1320
- }), report === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1321
- type: "button",
1322
- variant: "outline",
1323
- onClick: copy,
1324
- children: copied ? t("diagnosticsCopied") : t("diagnosticsCopy")
1325
- })]
1349
+ children: [
1350
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1351
+ type: "button",
1352
+ variant: "outline",
1353
+ disabled: busy,
1354
+ onClick: load,
1355
+ children: t("diagnosticsLoad")
1356
+ }),
1357
+ report === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1358
+ type: "button",
1359
+ variant: "outline",
1360
+ onClick: copy,
1361
+ children: copied ? t("diagnosticsCopied") : t("diagnosticsCopy")
1362
+ }),
1363
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
1364
+ className: "codexSubscriptionLink",
1365
+ href: SUPPORT_ISSUE_URL,
1366
+ target: "_blank",
1367
+ rel: "noreferrer",
1368
+ children: t("feedbackOpen")
1369
+ })
1370
+ ]
1326
1371
  })]
1327
1372
  }),
1328
1373
  report === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", { children: JSON.stringify(report, null, 2) }),
@@ -1349,11 +1394,21 @@ window.__ModuleLoader__.load({
1349
1394
  children: fill(t("resets"), { value })
1350
1395
  });
1351
1396
  }
1352
- function ResetCreditControl({ rpc, t, count, exhausted, onConsumed }) {
1397
+ function ResetCreditExpiry({ expiresAt, t }) {
1398
+ const date = validDate(expiresAt);
1399
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1400
+ className: "codexSubscriptionResetExpiry",
1401
+ children: date === void 0 ? t("resetCreditExpiryUnknown") : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("time", {
1402
+ dateTime: date.toISOString(),
1403
+ title: date.toLocaleString(),
1404
+ children: fill(t("resetCreditExpires"), { value: date.toLocaleString() })
1405
+ })
1406
+ });
1407
+ }
1408
+ function ResetCreditControl({ rpc, t, count, nextExpiresAt, hasExhaustedQuota, onConsumed }) {
1353
1409
  const [challenge, setChallenge] = (0, react.useState)();
1354
1410
  const [resetBusy, setResetBusy] = (0, react.useState)(false);
1355
1411
  const [resetAcknowledged, setResetAcknowledged] = (0, react.useState)(false);
1356
- const [resetPhrase, setResetPhrase] = (0, react.useState)("");
1357
1412
  const [resetCountdown, setResetCountdown] = (0, react.useState)(0);
1358
1413
  const [resetError, setResetError] = (0, react.useState)();
1359
1414
  const [resetResult, setResetResult] = (0, react.useState)();
@@ -1368,24 +1423,22 @@ window.__ModuleLoader__.load({
1368
1423
  return () => window.clearInterval(timer);
1369
1424
  }, [challenge]);
1370
1425
  const prepareReset = () => {
1371
- if (resetBusy || !exhausted) return;
1426
+ if (resetBusy) return;
1372
1427
  setResetBusy(true);
1373
1428
  setResetError(void 0);
1374
1429
  setResetResult(void 0);
1375
1430
  rpc.call(CHANNEL, "reset-credit/prepare", {}).then(unwrap).then((next) => {
1376
1431
  setChallenge(next);
1377
1432
  setResetAcknowledged(false);
1378
- setResetPhrase("");
1379
1433
  }).catch((error) => setResetError(resetCreditErrorText(error, t))).finally(() => setResetBusy(false));
1380
1434
  };
1381
1435
  const cancelReset = () => {
1382
1436
  if (resetBusy) return;
1383
1437
  setChallenge(void 0);
1384
1438
  setResetAcknowledged(false);
1385
- setResetPhrase("");
1386
1439
  setResetError(void 0);
1387
1440
  };
1388
- const resetReady = challenge !== void 0 && resetAcknowledged && resetPhrase === challenge.confirmPhrase && resetCountdown === 0;
1441
+ const resetReady = challenge !== void 0 && resetAcknowledged && resetCountdown === 0;
1389
1442
  const consumeReset = () => {
1390
1443
  if (resetBusy) return;
1391
1444
  if (!resetReady) return;
@@ -1394,11 +1447,10 @@ window.__ModuleLoader__.load({
1394
1447
  setResetResult(void 0);
1395
1448
  rpc.call(CHANNEL, "reset-credit/consume", {
1396
1449
  challengeId: challenge.challengeId,
1397
- phrase: resetPhrase
1450
+ acknowledged: resetAcknowledged
1398
1451
  }).then(unwrap).then((result) => {
1399
1452
  setChallenge(void 0);
1400
1453
  setResetAcknowledged(false);
1401
- setResetPhrase("");
1402
1454
  const message = result.code === "reset" ? t("resetSuccess") : result.code === "nothing_to_reset" ? t("resetNothing") : result.code === "no_credit" ? t("resetNoCredit") : t("resetAlready");
1403
1455
  setResetResult(message);
1404
1456
  onConsumed();
@@ -1409,14 +1461,19 @@ window.__ModuleLoader__.load({
1409
1461
  children: [
1410
1462
  challenge === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1411
1463
  className: "codexSubscriptionResetSummary",
1412
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: fill(t("resetCreditsValue"), { count }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1464
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1465
+ className: "codexSubscriptionResetMeta",
1466
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: fill(t("resetCreditsValue"), { count }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetCreditExpiry, {
1467
+ expiresAt: nextExpiresAt,
1468
+ t
1469
+ })]
1470
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1413
1471
  className: "codexSubscriptionActions",
1414
1472
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
1415
1473
  type: "button",
1416
1474
  variant: "outline",
1417
- disabled: !exhausted || resetBusy,
1475
+ disabled: resetBusy,
1418
1476
  "aria-busy": resetBusy,
1419
- title: !exhausted ? t("resetUnavailable") : void 0,
1420
1477
  onClick: prepareReset,
1421
1478
  children: resetBusy ? t("resetPreparing") : t("resetUse")
1422
1479
  })
@@ -1434,9 +1491,13 @@ window.__ModuleLoader__.load({
1434
1491
  className: "codexSubscriptionResetWarning",
1435
1492
  children: challenge.description
1436
1493
  }) : null,
1494
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ResetCreditExpiry, {
1495
+ expiresAt: challenge.creditExpiresAt,
1496
+ t
1497
+ }),
1437
1498
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1438
1499
  className: "codexSubscriptionResetWarning",
1439
- children: t("resetWarning")
1500
+ children: t(hasExhaustedQuota ? "resetWarning" : "resetEarlyWarning")
1440
1501
  }),
1441
1502
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1442
1503
  className: "codexSubscriptionResetCheck",
@@ -1447,20 +1508,6 @@ window.__ModuleLoader__.load({
1447
1508
  onChange: (event) => setResetAcknowledged(event.target.checked)
1448
1509
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("resetAcknowledge") })]
1449
1510
  }),
1450
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1451
- className: "codexSubscriptionPreferenceCopy",
1452
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1453
- className: "codexSubscriptionCreditLabel",
1454
- children: fill(t("resetTypeHint"), { phrase: challenge.confirmPhrase })
1455
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Input, {
1456
- className: "codexSubscriptionInput codexSubscriptionResetPhrase",
1457
- value: resetPhrase,
1458
- disabled: resetBusy,
1459
- autoComplete: "off",
1460
- spellCheck: false,
1461
- onChange: (event) => setResetPhrase(event.target.value)
1462
- })]
1463
- }),
1464
1511
  resetCountdown > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1465
1512
  className: "codexSubscriptionCreditNote",
1466
1513
  role: "status",
@@ -1509,8 +1556,7 @@ window.__ModuleLoader__.load({
1509
1556
  ["This quota reset confirmation is no longer valid", "resetExpired"],
1510
1557
  ["This quota reset is already in progress", "resetInProgress"],
1511
1558
  ["Wait before confirming this quota reset", "resetTooEarly"],
1512
- ["The quota reset confirmation phrase does not match", "resetPhraseMismatch"],
1513
- ["The current Codex quota is not exhausted", "resetNotExhausted"],
1559
+ ["You must acknowledge that this may consume one quota reset", "resetAcknowledgeRequired"],
1514
1560
  ["The signed-in ChatGPT account changed", "resetAccountChanged"]
1515
1561
  ])).get(error instanceof Error ? error.message : "") ?? "resetFailed");
1516
1562
  }
@@ -1648,7 +1694,8 @@ window.__ModuleLoader__.load({
1648
1694
  rpc,
1649
1695
  t,
1650
1696
  count: visibleUsage.resetCredits.availableCount,
1651
- exhausted,
1697
+ nextExpiresAt: visibleUsage.resetCredits.nextExpiresAt,
1698
+ hasExhaustedQuota: exhausted,
1652
1699
  onConsumed: () => load(true)
1653
1700
  })]
1654
1701
  }) : null,
package/lib/index.js CHANGED
@@ -503,7 +503,7 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0 } = {
503
503
  }
504
504
  //#endregion
505
505
  //#region src/version.js
506
- const PACKAGE_VERSION = "1.4.0";
506
+ const PACKAGE_VERSION = "1.5.0";
507
507
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
508
508
  //#endregion
509
509
  //#region src/codex-search.js
@@ -643,6 +643,13 @@ const nonEmpty = (value) => typeof value === "string" && value.trim().length > 0
643
643
  function encodedLimit(decodedBytes) {
644
644
  return Math.ceil(decodedBytes / 3) * 4;
645
645
  }
646
+ function validBase64Body(value, end) {
647
+ for (let index = 0; index < end; index += 1) {
648
+ const code = value.charCodeAt(index);
649
+ if (!(code >= 65 && code <= 90 || code >= 97 && code <= 122 || code >= 48 && code <= 57 || code === 43 || code === 47)) return false;
650
+ }
651
+ return true;
652
+ }
646
653
  async function readJsonWithin(response, maximumBytes) {
647
654
  const contentLength = Number(response.headers.get("content-length"));
648
655
  if (Number.isFinite(contentLength) && contentLength > maximumBytes) throw new Error("Codex image response exceeds the image size limit");
@@ -670,8 +677,8 @@ async function readJsonWithin(response, maximumBytes) {
670
677
  /** Strictly decode one PNG returned by the subscription backend. */
671
678
  function decodeCodexPng(value, maximumBytes) {
672
679
  const encoded = nonEmpty(value);
673
- if (encoded === void 0 || encoded.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(encoded)) throw new Error("Codex returned an invalid base64 PNG");
674
- const padding = encoded.endsWith("==") ? 2 : encoded.endsWith("=") ? 1 : 0;
680
+ const padding = encoded?.endsWith("==") ? 2 : encoded?.endsWith("=") ? 1 : 0;
681
+ if (encoded === void 0 || encoded.length % 4 !== 0 || !validBase64Body(encoded, encoded.length - padding)) throw new Error("Codex returned an invalid base64 PNG");
675
682
  const decodedBytes = encoded.length / 4 * 3 - padding;
676
683
  if (decodedBytes > maximumBytes) throw new Error("Codex image exceeds the image size limit");
677
684
  const data = Buffer.from(encoded, "base64");
@@ -941,7 +948,18 @@ function spendControlReachedOf(value) {
941
948
  function resetCreditsOf(value) {
942
949
  if (value === void 0 || value === null) return void 0;
943
950
  if (!record$1(value) || !Number.isSafeInteger(value.available_count) || value.available_count < 0) throw new Error("Codex returned malformed reset credit details");
944
- return { availableCount: value.available_count };
951
+ if (value.credits !== void 0 && value.credits !== null && !Array.isArray(value.credits)) throw new Error("Codex returned malformed reset credit details");
952
+ const expirations = (value.credits ?? []).filter((credit) => record$1(credit) && credit.status?.toLowerCase?.() === "available").map((credit) => {
953
+ if (Number.isSafeInteger(credit.expires_at) && credit.expires_at > 0) return credit.expires_at * 1e3;
954
+ if (typeof credit.expires_at === "string" && credit.expires_at.length <= 64) {
955
+ const parsed = Date.parse(credit.expires_at);
956
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
957
+ }
958
+ }).filter((expiration) => expiration !== void 0);
959
+ return {
960
+ availableCount: value.available_count,
961
+ ...expirations.length === 0 ? {} : { nextExpiresAt: Math.min(...expirations) }
962
+ };
945
963
  }
946
964
  /** Reduce the provider payload to a browser-safe quota projection. */
947
965
  function parseCodexUsage(value) {
@@ -1047,7 +1065,6 @@ function createCodexUsageReader(options) {
1047
1065
  //#region src/reset-credits.js
1048
1066
  const CODEX_RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
1049
1067
  const CODEX_RESET_CONSUME_URL = `${CODEX_RESET_CREDITS_URL}/consume`;
1050
- const RESET_CONFIRM_PHRASE = "USE RESET";
1051
1068
  const DEFAULT_CONFIRM_DELAY_MS = 5e3;
1052
1069
  const DEFAULT_CHALLENGE_TTL_MS = 6e4;
1053
1070
  const DEFAULT_TIMEOUT_MS = 15e3;
@@ -1069,15 +1086,22 @@ function credentialsOf(auth, credential) {
1069
1086
  accountId
1070
1087
  };
1071
1088
  }
1089
+ function expirationOf(value) {
1090
+ if (value === void 0 || value === null) return void 0;
1091
+ if (Number.isSafeInteger(value) && value > 0) return value * 1e3;
1092
+ if (typeof value === "string" && value.length > 0 && value.length <= 64) {
1093
+ const parsed = Date.parse(value);
1094
+ if (Number.isFinite(parsed) && parsed > 0) return parsed;
1095
+ }
1096
+ throw new Error("ChatGPT returned malformed quota reset details");
1097
+ }
1072
1098
  function parseDetails(value, now) {
1073
1099
  if (!record(value) || !Number.isSafeInteger(value.available_count) || value.available_count < 0 || !Array.isArray(value.credits)) throw new Error("ChatGPT returned malformed quota reset details");
1074
1100
  if (value.available_count === 0) throw new Error("No quota reset is available");
1075
1101
  const available = value.credits.filter((credit) => record(credit) && typeof credit.id === "string" && credit.id.length > 0 && credit.id.length <= 256 && typeof credit.status === "string" && credit.status.toLowerCase() === "available").map((credit) => {
1076
- const expiresAt = credit.expires_at === void 0 || credit.expires_at === null ? void 0 : credit.expires_at * 1e3;
1077
- if (expiresAt !== void 0 && (!Number.isSafeInteger(credit.expires_at) || credit.expires_at <= 0)) throw new Error("ChatGPT returned malformed quota reset details");
1078
1102
  return {
1079
1103
  credit,
1080
- expiresAt
1104
+ expiresAt: expirationOf(credit.expires_at)
1081
1105
  };
1082
1106
  }).filter(({ expiresAt }) => expiresAt === void 0 || expiresAt > now).sort((a, b) => (a.expiresAt ?? Number.MAX_SAFE_INTEGER) - (b.expiresAt ?? Number.MAX_SAFE_INTEGER));
1083
1107
  if (available.length === 0) throw new Error("No usable quota reset is available");
@@ -1097,14 +1121,13 @@ function parseConsumeResult(value) {
1097
1121
  "already_redeemed"
1098
1122
  ].includes(value.code)) throw new Error("ChatGPT returned an unreadable quota reset response");
1099
1123
  const windowsReset = Array.isArray(value.windows_reset) ? value.windows_reset.filter((item) => typeof item === "string").slice(0, 16) : [];
1124
+ const windowsResetCount = Number.isSafeInteger(value.windows_reset) && value.windows_reset >= 0 && value.windows_reset <= 16 ? value.windows_reset : void 0;
1100
1125
  return {
1101
1126
  code: value.code,
1102
- windowsReset
1127
+ windowsReset,
1128
+ ...windowsResetCount === void 0 ? {} : { windowsResetCount }
1103
1129
  };
1104
1130
  }
1105
- function quotaIsExhausted(usage) {
1106
- return Array.isArray(usage?.rateLimits) && usage.rateLimits.some((limit) => limit?.id !== "code_review" && Array.isArray(limit?.windows) && limit.windows.some((window) => Number.isFinite(window?.usedPercent) && window.usedPercent >= 100));
1107
- }
1108
1131
  /**
1109
1132
  * Host-only reset redemption. The browser receives an opaque, short-lived
1110
1133
  * challenge; account ids, bearer tokens, credit ids, and idempotency keys stay
@@ -1161,14 +1184,14 @@ function createCodexResetCreditService(options) {
1161
1184
  return {
1162
1185
  challengeId,
1163
1186
  availableCount: details.availableCount,
1164
- confirmPhrase: RESET_CONFIRM_PHRASE,
1165
1187
  readyAt,
1166
1188
  expiresAt,
1189
+ ...details.creditExpiresAt === void 0 ? {} : { creditExpiresAt: details.creditExpiresAt },
1167
1190
  ...details.title === void 0 ? {} : { title: details.title },
1168
1191
  ...details.description === void 0 ? {} : { description: details.description }
1169
1192
  };
1170
1193
  },
1171
- async consume({ challengeId, phrase, signal } = {}) {
1194
+ async consume({ challengeId, acknowledged, signal } = {}) {
1172
1195
  const challenge = typeof challengeId === "string" ? challenges.get(challengeId) : void 0;
1173
1196
  if (challenge === void 0) throw new Error("This quota reset confirmation is no longer valid");
1174
1197
  if (challenge.state === "pending") throw new Error("This quota reset is already in progress");
@@ -1177,13 +1200,9 @@ function createCodexResetCreditService(options) {
1177
1200
  challenges.delete(challengeId);
1178
1201
  throw new Error("This quota reset confirmation is no longer valid");
1179
1202
  }
1180
- if (phrase !== "USE RESET") throw new Error("The quota reset confirmation phrase does not match");
1203
+ if (acknowledged !== true) throw new Error("You must acknowledge that this may consume one quota reset");
1181
1204
  challenge.state = "pending";
1182
1205
  try {
1183
- if (!quotaIsExhausted(await usageReader.read({
1184
- force: true,
1185
- signal
1186
- }))) throw new Error("The current Codex quota is not exhausted");
1187
1206
  const { access, accountId } = await resolveCredentials(signal);
1188
1207
  if (accountId !== challenge.accountId) throw new Error("The signed-in ChatGPT account changed");
1189
1208
  const response = await fetchReset(CODEX_RESET_CONSUME_URL, {
@@ -1316,7 +1335,7 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
1316
1335
  ok: true,
1317
1336
  value: endpoint === "reset-credit/prepare" ? await resetCreditService.prepare({ signal }) : await resetCreditService.consume({
1318
1337
  challengeId: payload?.challengeId,
1319
- phrase: payload?.phrase,
1338
+ acknowledged: payload?.acknowledged,
1320
1339
  signal
1321
1340
  })
1322
1341
  };
@@ -1331,8 +1350,7 @@ function createSubscriptionRpcHandler({ authHandler, usageReader, resetCreditSer
1331
1350
  "This quota reset confirmation is no longer valid",
1332
1351
  "This quota reset is already in progress",
1333
1352
  "Wait before confirming this quota reset",
1334
- "The quota reset confirmation phrase does not match",
1335
- "The current Codex quota is not exhausted",
1353
+ "You must acknowledge that one quota reset will be consumed",
1336
1354
  "The signed-in ChatGPT account changed"
1337
1355
  ]);
1338
1356
  const fallback = endpoint === "reset-credit/prepare" ? "Could not prepare a quota reset" : "Could not use the quota reset";
@@ -1476,4 +1494,4 @@ function apply(ctx) {
1476
1494
  ctx.effect(() => ctx.connection.rpc.handle(CHANNEL, handler, { authority: "loopback" }), "codex-subscription: loopback account RPC");
1477
1495
  }
1478
1496
  //#endregion
1479
- export { CODEX_IMAGE_GENERATION_URL, CODEX_IMAGE_TOOL_NAME, CODEX_RESET_CONSUME_URL, CODEX_RESET_CREDITS_URL, CODEX_USAGE_URL, CodexLoginCoordinator, DshOAuthCredentialStore, RESET_CONFIRM_PHRASE, apply, assertCodexAuthUrl, commandForCodexAuthUrl, createCodexAuthService, createCodexImageTool, createCodexResetCreditService, createCodexRpcHandler, createCodexUsageReader, createSearchProviderSwitcher, createSubscriptionDiagnostics, createSubscriptionRpcHandler, decodeCodexPng, inject, name, openCodexAuthUrl, parseCodexUsage };
1497
+ export { CODEX_IMAGE_GENERATION_URL, CODEX_IMAGE_TOOL_NAME, CODEX_RESET_CONSUME_URL, CODEX_RESET_CREDITS_URL, CODEX_USAGE_URL, CodexLoginCoordinator, DshOAuthCredentialStore, apply, assertCodexAuthUrl, commandForCodexAuthUrl, createCodexAuthService, createCodexImageTool, createCodexResetCreditService, createCodexRpcHandler, createCodexUsageReader, createSearchProviderSwitcher, createSubscriptionDiagnostics, createSubscriptionRpcHandler, decodeCodexPng, inject, name, openCodexAuthUrl, parseCodexUsage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Use ChatGPT and Codex subscriptions in DeepSeek Harness with OAuth, quota, safe resets, web search, images, and Fast mode",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",