@eddyskywalker/dsh-chatgpt-subscription 0.57.0 → 0.59.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.
Files changed (60) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +3 -3
  3. package/lib/client.js +136 -23
  4. package/lib/client.js.map +1 -1
  5. package/lib/codemode.js +1565 -0
  6. package/lib/index.js +1121 -530
  7. package/lib/tool-result-pruner.js +1 -1
  8. package/lib/types/client/CodexImageToolView.d.ts.map +1 -1
  9. package/lib/types/client/minimax-code/commands-contract.d.ts +1 -1
  10. package/lib/types/client/progress-updates/progress-row.d.ts.map +1 -1
  11. package/lib/types/client/styles.d.ts.map +1 -1
  12. package/lib/types/compat.d.ts +10 -1
  13. package/lib/types/compat.d.ts.map +1 -1
  14. package/lib/types/entry-codemode.d.ts +37 -0
  15. package/lib/types/entry-codemode.d.ts.map +1 -0
  16. package/lib/types/entry-tool-result-pruner.d.ts +1 -1
  17. package/lib/types/host/agent-preset.d.ts +24 -2
  18. package/lib/types/host/agent-preset.d.ts.map +1 -1
  19. package/lib/types/host/antigravity/image-tool.d.ts +10 -2
  20. package/lib/types/host/antigravity/image-tool.d.ts.map +1 -1
  21. package/lib/types/host/cache-warmup/install.d.ts +10 -4
  22. package/lib/types/host/cache-warmup/install.d.ts.map +1 -1
  23. package/lib/types/host/cache-warmup/projection.d.ts +17 -0
  24. package/lib/types/host/cache-warmup/projection.d.ts.map +1 -1
  25. package/lib/types/host/codemode/discovery.d.ts +33 -0
  26. package/lib/types/host/codemode/discovery.d.ts.map +1 -0
  27. package/lib/types/host/codemode/executor.d.ts +140 -0
  28. package/lib/types/host/codemode/executor.d.ts.map +1 -0
  29. package/lib/types/host/codemode/retention.d.ts +34 -0
  30. package/lib/types/host/codemode/retention.d.ts.map +1 -0
  31. package/lib/types/host/codemode/rules.d.ts +183 -0
  32. package/lib/types/host/codemode/rules.d.ts.map +1 -0
  33. package/lib/types/host/codemode/scope.d.ts +59 -0
  34. package/lib/types/host/codemode/scope.d.ts.map +1 -0
  35. package/lib/types/host/codemode/transport.d.ts +258 -0
  36. package/lib/types/host/codemode/transport.d.ts.map +1 -0
  37. package/lib/types/host/codex-account-pool.d.ts.map +1 -1
  38. package/lib/types/host/codex-adopt.d.ts.map +1 -1
  39. package/lib/types/host/codex-catalog.d.ts +5 -3
  40. package/lib/types/host/codex-catalog.d.ts.map +1 -1
  41. package/lib/types/host/codex-identity.d.ts +130 -0
  42. package/lib/types/host/codex-identity.d.ts.map +1 -0
  43. package/lib/types/host/codex-images.d.ts +12 -0
  44. package/lib/types/host/codex-images.d.ts.map +1 -1
  45. package/lib/types/host/kimi-code/repeat-breaker.d.ts +1 -1
  46. package/lib/types/host/oauth-service.d.ts +0 -16
  47. package/lib/types/host/oauth-service.d.ts.map +1 -1
  48. package/lib/types/host/token-store.d.ts +9 -0
  49. package/lib/types/host/token-store.d.ts.map +1 -1
  50. package/lib/types/host/usage-service.d.ts.map +1 -1
  51. package/lib/types/host/workbuddy/adapter.d.ts.map +1 -1
  52. package/lib/types/host/workbuddy/client.d.ts.map +1 -1
  53. package/lib/types/host/workbuddy/model-catalog.d.ts +56 -44
  54. package/lib/types/host/workbuddy/model-catalog.d.ts.map +1 -1
  55. package/lib/types/host/workbuddy/routes.d.ts.map +1 -1
  56. package/lib/types/shared/workbuddy-contracts.d.ts +8 -8
  57. package/package.json +54 -46
  58. package/presets/dispatch/README.md +12 -3
  59. package/presets/dispatch/agent.cordis.yml +10 -9
  60. package/presets/dispatch/preset.yml +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,49 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.59.0 - 2026-11-06
6
+
7
+ - **[codemode] 程序内的每条子调用重新写回会话:界面能看见「启动了哪个工具」**
8
+ - **缺的是什么**:内核的 PTC 传输会为每条子调用 append `tool/ptc-dispatch-start` / `tool/ptc-dispatch`,界面(`tool-call-tree`)靠这一对事件把子调用画到父调用下面;本行的程序交给内核运行时执行,而那两条事件不在运行时的对外契约里,于是 Code Mode 会话里回放事件**一条都没有**(实测 26 条 `tool/call` 对 0 条 `tool/ptc-dispatch*`),一次程序在界面上只有「一行日志 + 一具黑盒」。
9
+ - **补法**:[`src/host/codemode/transport.ts`](src/host/codemode/transport.ts) 新增 `createDispatchLog`,在子调用**真正进入执行 lane 之后**写 start、结算时写 settle,字段(`rootCallId` / `parentCallId` / `subCallId` / `name` / `arguments`)与内核逐字对齐;`subCallId` 沿用既有的 `<父>:ptc:<n>` 命名,因此 `isNestedCall` 与界面两侧都不用改。
10
+ - **时序诚实**:写调用受互斥闸门串行化,start 记在 lane 里而不是提交时——排队中被放弃的调用一条都不写(与内核「调度器真的开始才记」一致),并发时的顺序不会撒谎。
11
+ - **成对且有界**:失败也写 settle(带 `isError` 与 `error` 身份),只写 start 会留下永远不收尾的行;回放副本的单个文本块截到 8000 字符,程序本身拿到的值不受影响。
12
+ - **不碰程序**:`entry-codemode.ts` 的 `callTool` 只是**多回传** `content` / `meta` / `error.info`,`value` / `text` 语义不变;append 异常一律吞掉,老代 harness 没有这两种事件时降级为「没有回放」,而不是 `code_run` 失败。
13
+ - **验证**:`test/codemode.test.ts` 新增 5 项(成对写入与 id 对齐 / 失败收尾 / 并发时序 / append 抛错降级 / 无会话时静默);把 start 或 settle 各抹掉一处,各有 3 项转红。
14
+
15
+ - **[preset] `dispatch`(调度模式)改用 Code Mode,替代 PTC 呈现**
16
+ - 新增本包子入口 `./codemode`(`src/entry-codemode.ts` + `src/host/codemode/*`):预设自身注册的工具保持原生可调,预设清单以外的工具(MCP、profile 级插件、按 agent 注册的工具集)不进入模型上下文,只能在 `code_run` 程序中通过 `code_tools_list` / `code_tools_search` / `code_tools_describe` 发现后调用。
17
+ - 遮蔽接缝是 per-scope `ctx.tools.setExecutor()`(0.2.2+):`describe()` 决定请求面,`execute()` 在派发前拒绝模型直呼被遮蔽的名字并给出发现指引;程序内子调用整批委托给内核原生 executor,保留完整策略 / 审批 / post-execute 管线。
18
+ - **kimi-code 缓存前提**:说明段恒定(无计数、无 namespace 清单);`code_run` 与 `run_code` 同等享受 `ref://` 大输出预览与 `cache_warmup_result` 精确补读。
19
+ - 代次降级:解析不到 `@deepseek-ai/dsh-tool-executor-native` 时自动退回 `tool-executor-ptc`(或更早的 `agent-tool-presentation`),旧代行为不变。
20
+ - 依赖:新增 `@deepseek-ai/dsh-tool-executor-native`(真实依赖;profile 插件解析不到宿主内核包)。已知限制:不提供程序级 `sandbox_permissions` 升级。(原先还写着「程序内子调用不写回放事件」,该限制已由 0.59.0 的 `createDispatchLog` 解除,见上一条。)
21
+
22
+ ## 0.58.0 - 2026-11-04
23
+
24
+ - **[harness] 支持 DSH 0.2.2-rc.1,并保留 0.2.2-alpha.3、latest 0.2.1-rc.5 与旧代兼容**
25
+ - 开发基线切换到 `0.2.2-rc.1`,同步正式版 Cordis `4.1.0`、Loader `1.1.0` 与 Schemastery `3.19.0`;`peerDependencies` 完整保留自 `0.1.2-alpha.5` 起的全部历史子句并追加 `^0.2.2-rc.1` 及对应正式版 vendor。
26
+ - 核对上游 `dsh-v0.2.2-alpha.3..dsh-v0.2.2-rc.1` 全部变更(387 个 `package.json`,0 个非 `package.json` 源码文件):PTC 独立 executor、计划模式与修剪器 fork、五大宿主缓存守卫及子代理授权接缝均无需改动。上游 `dsh-tool-pwsh-persistent@0.2.2-rc.1` 已补齐发布,`npm exec --package=@deepseek-ai/dsh@0.2.2-rc.1` 闭环恢复正常。
27
+ - **验证**:新基线 `0.2.2-rc.1`、精确 `0.2.2-alpha.3` 洁净室与精确 `0.2.1-rc.5`(当前 npm `latest`)洁净室结果逐项一致,强制全工程类型检查 0 错误,全量 Vitest 均为 **359 文件 / 6584 项通过**(另 2 文件 / 9 项跳过);旧代精确 `0.1.5-rc.1` 洁净室在仅排除已知 `0.1.7+` PTC 测试文件后为 **358 文件 / 6579 项通过**(另 2 文件 / 10 项跳过)。打包产物在本机 `0.2.2-rc.1` 隔离 Web Profile 安装与启动零报错,`/status` 与 13 线路 `/hub/overview` 路由冒烟通过。详见 `.dsh/skills/dsh-harness-upgrade/references/0.2.2-rc.1.md`。
28
+
29
+ - **[codex] 同一工作空间下的不同用户不再互相覆盖:账号池改用「工作空间 + 用户」复合身份(#85)**
30
+ - **缺陷**:账号池的去重键是 `accountId ?? email`,而 `accountId` 来自 `chatgpt_account_id`——同一工作空间的每个成员都共享该值。于是添加第二个成员时命中了第一个成员的行,凭据被原地覆盖:卡片仍显示「已登录 1 个账号」,先前的账号从列表消失、无法再选回。
31
+ - **两种身份分开**:上游工作空间身份(`credentials.accountId`,即请求头 `chatgpt-account-id`)保持不变;本地登录身份改为 `(workspace, user)`,用户 id 优先级 `chatgpt_user_id` → `sub` → 邮箱,且工作空间 id 在有用户声明时**绝不**单独充当身份。
32
+ - 复合身份用显式序列化(JSON 数组)后取 SHA-256 前 32 位十六进制,而不是可读拼接:避免分隔符伪造、`catalogSnapshotName` 把 `[^a-z0-9]` 折成 `_` 后的键碰撞,以及把邮箱写进快照文件名。
33
+ - 新增 `src/host/codex-identity.ts`(`parseJwtClaims`/`extractIdentity` 迁入其中),并把它接进四处身份用途:号池 `dedupeKey`、刷新 single-flight `refreshKey`(回退 `refreshToken`)、模型目录缓存键、无号池路径的额度快照键。
34
+ - **顺带修掉的两处同源问题**:目录缓存原本是单槽且失败回退不看 key,会把 A 账号的目录当成 B 的答案;改为按身份键的有界 Map(8 条)+ 按身份重放持久化快照,失败只回退到**当前身份自己**的条目。`credentials.userId` 随凭据持久化,令令牌轮换不再让一个账号看起来像两个。
35
+ - **兼容**:旧行没有 `userId` 字段,身份从已存令牌的 claims 重新推导,因此升级后重新授权仍命中原行,不会长出重复账号;三个用户声明全缺时才退化为工作空间身份,与改动前一致。
36
+ - **验证**:`test/codex-account-identity.test.ts` 16 项回归(同工作空间两成员独立保存/原地更新/跨工作空间区分/旧行迁移/刷新独立/目录不串/额度不串);把四处规则临时改回旧写法后有 6 项失败,确认测试确实能抓住该缺陷。`npm run typecheck` 0 错误;`npx vitest run` 全量通过。
37
+
38
+ - **[packaging] 补上 `engines.node`:把「旧 LTS 装得上、跑起来才炸」变成安装期可见**
39
+ - 用户反馈称 `package.json` 没有 `engines` 字段、建议补 `">=18.0.0"`。缺字段属实,建议值不成立:`dependencies` 的 `undici ^8.10.0` 自声明 `node >=22.19.0`,且被 tsdown 内联进 `lib/index.js`(宿主侧约 4.2 MB 单文件),代理与 fetch 走的就是这份代码。`>=18.0.0` 会把 Node 18/20 判成受支持,问题只从「装的时候就报」推迟到「运行到代理/流式请求时炸」。
40
+ - 按真实下限声明 `"engines": { "node": ">=22.19.0" }`;README「环境要求」同步写明该数字与来源。
41
+ - `test/package-integrity.test.ts` 新增断言:`engines.node` 必须存在、必须是 `>=X.Y.Z` 写法、且不弱于内置 undici 的下限(防止日后又退回 `>=18.0.0` 这类口号值)。
42
+ - **验证**:`npm run typecheck` 0 错误;`npx vitest run test/package-integrity.test.ts` 全通过。
43
+
44
+ - **[workbuddy] 同步 2026-11-04 随包模型目录与跨区保守能力合并**
45
+ - 按最新国区与国际区实时 `/v3/config` 刷新 `src/host/workbuddy/model-catalog.ts` 离线兜底表:仅最大窗口取并集,默认服务窗口、输出上限、思考档位与图片/关思考开关均取两区交集,避免任一区域网关返回 400;同步更新两区独有模型归属(`regions`)并清空已正式上架的 `UNPUBLISHED_MODELS`。
46
+ - 新增 `isUntouchedModelSelection` 兼容旧版随包默认勾选集合,未手动改过勾选的用户升级后自动获得新目录全集。
47
+
5
48
  ## 0.57.0 - 2026-11-03
6
49
 
7
50
  - **[team] 与内置 Agent Teams 共存:修正 R0.5 判据,并把策略段改成尾部投递**
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  插件最初为接入 ChatGPT Plus / Pro / Business 订阅的 Codex 模型而建,现已演进为统一的**多订阅服务中枢(Subscription Hub)与编码 Agent 增强套件**:在单一插件包内同时提供 **13 条独立模型供应线路**、跨平台加密多账号池、多模态生图/视频工具链、前缀缓存零漂移保护,以及随包分发的 `dispatch` 多子代理编排预设。
6
6
 
7
7
  - **跨平台支持**:Windows、macOS、Linux(含无桌面密钥环的远程服务器部署模式)
8
- - **跨代际兼容**:单一代码库兼容 `@deepseek-ai/dsh` **0.1.2-alpha.5 至 0.2.2-alpha.3** 全系列版本
8
+ - **跨代际兼容**:单一代码库兼容 `@deepseek-ai/dsh` **0.1.2-alpha.5 至 0.2.2-rc.1** 全系列版本
9
9
  - **全栈多语言**:Web 设置中心与宿主侧提示/报错完整支持 **简体中文(`zh`)/ English(`en`)/ Русский(`ru`)**
10
10
 
11
11
  ---
@@ -75,8 +75,8 @@
75
75
  ## 环境要求
76
76
 
77
77
  - **操作系统**:Windows(需内置 PowerShell 以支持 CurrentUser DPAPI)、macOS 或 Linux
78
- - **运行时**:Node.js(建议 22+)与 npm
79
- - **宿主版本**:DeepSeek Harness `@deepseek-ai/dsh` `0.1.2-alpha.5` 至 `0.2.2-alpha.3`
78
+ - **运行时**:Node.js **≥ 22.19**(内置 `undici` 8 的下限,已写入 `package.json` 的 `engines`;建议 24 LTS)与 npm
79
+ - **宿主版本**:DeepSeek Harness `@deepseek-ai/dsh` `0.1.2-alpha.5` 至 `0.2.2-rc.1`
80
80
 
81
81
  ---
82
82
 
package/lib/client.js CHANGED
@@ -155,13 +155,41 @@ window.__ModuleLoader__.load({
155
155
  }
156
156
  //#endregion
157
157
  //#region src/client/CodexImageToolView.tsx
158
+ /**
159
+ * Aspect ratios the Gemini image tool accepts in its `aspectRatio` argument.
160
+ *
161
+ * Mirrors `ANTIGRAVITY_IMAGE_ASPECT_RATIOS` in the host tool schema. The card
162
+ * only *sizes a placeholder* with it, so a drift here degrades to the default
163
+ * square instead of producing a wrong request — the value is never sent
164
+ * anywhere, and the host enum stays the single authority over what is legal.
165
+ */
166
+ const IMAGE_ASPECT_RATIOS = /* @__PURE__ */ new Set([
167
+ "1:1",
168
+ "2:3",
169
+ "3:2",
170
+ "3:4",
171
+ "4:3",
172
+ "4:5",
173
+ "5:4",
174
+ "9:16",
175
+ "16:9",
176
+ "21:9",
177
+ "1:4",
178
+ "4:1",
179
+ "1:8",
180
+ "8:1"
181
+ ]);
182
+ /** The model picks its own shape when the call names no ratio; the card guesses square. */
183
+ const DEFAULT_IMAGE_ASPECT_RATIO = "1:1";
184
+ /** The tray's short side, in px: a hint of the frame, not a billboard. */
185
+ const PLACEHOLDER_SHORT_SIDE = 112;
158
186
  function CodexImageToolView({ block, loadImage, t }) {
159
187
  const settled = "kind" in block;
160
188
  const isError = settled ? block.isError : false;
161
- const prompt = promptFromBlock$1(block);
162
189
  const images = settled && !isError ? imageBlocks(block.content) : [];
163
190
  const label = isError ? t("imageToolFailed") : settled ? t("imageToolDone") : t("imageToolRunning");
164
- const summary = prompt ?? textSummary$1(settled ? block.content : []);
191
+ const summary = promptFromBlock$1(block) ?? textSummary$1(settled ? block.content : []);
192
+ const tray = settled ? null : placeholderTray(aspectRatioFromBlock(block));
165
193
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
166
194
  className: "dsh-codex-image-tool",
167
195
  "data-state": isError ? "error" : settled ? "done" : "running",
@@ -174,12 +202,15 @@ window.__ModuleLoader__.load({
174
202
  }),
175
203
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
176
204
  className: "dsh-codex-image-title",
205
+ role: "status",
206
+ "aria-live": "polite",
177
207
  children: label
178
208
  }),
179
- summary !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
209
+ summary === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
180
210
  className: "dsh-codex-image-summary",
211
+ title: summary,
181
212
  children: summary
182
- }) : null
213
+ })
183
214
  ]
184
215
  }), images.length > 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
185
216
  className: "dsh-codex-image-grid",
@@ -189,7 +220,19 @@ window.__ModuleLoader__.load({
189
220
  label: attachment.name ?? t("image"),
190
221
  t
191
222
  }, String(attachment.attachmentId)))
192
- }) : null]
223
+ }) : tray === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
224
+ className: "dsh-codex-image-grid",
225
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
226
+ className: "dsh-codex-image-placeholder",
227
+ "data-ratio": tray.ratio,
228
+ "data-short-side": tray.axis,
229
+ style: {
230
+ aspectRatio: tray.ratio.replace(":", " / "),
231
+ ...tray.size
232
+ },
233
+ "aria-hidden": "true"
234
+ })
235
+ })]
193
236
  });
194
237
  }
195
238
  function GeneratedImage({ attachment, load, label, t }) {
@@ -223,24 +266,64 @@ window.__ModuleLoader__.load({
223
266
  });
224
267
  }
225
268
  function promptFromBlock$1(block) {
226
- const partial = partialPromptFromBlock(block);
269
+ return argumentFromBlock(block, "prompt");
270
+ }
271
+ /**
272
+ * Size the tray from one short side and let the ratio derive the other.
273
+ *
274
+ * A portrait frame fixes its height, and its width then stays narrow on its own.
275
+ * A landscape frame — and the 4:1 / 8:1 strips are exactly the case that forced
276
+ * this — fixes its width instead: deriving an 8:1 width from a 112px height
277
+ * would be 896px, which `max-width` would then clamp into a near-square box and
278
+ * the strip would stop looking like a strip. `min(…, 100%)` hands a narrow
279
+ * column back to the card and the height shrinks with it, so the shape survives
280
+ * the clamp.
281
+ */
282
+ function placeholderTray(ratio) {
283
+ const [w, h] = ratio.split(":").map(Number);
284
+ return w !== void 0 && h !== void 0 && w >= h ? {
285
+ axis: "width",
286
+ ratio,
287
+ size: { width: `min(${Math.round(PLACEHOLDER_SHORT_SIDE * (w / h))}px, 100%)` }
288
+ } : {
289
+ axis: "height",
290
+ ratio,
291
+ size: { height: PLACEHOLDER_SHORT_SIDE }
292
+ };
293
+ }
294
+ /** The placeholder's shape: the call's own ratio when it is one the tool accepts. */
295
+ function aspectRatioFromBlock(block) {
296
+ const ratio = argumentFromBlock(block, "aspectRatio")?.trim();
297
+ return ratio !== void 0 && IMAGE_ASPECT_RATIOS.has(ratio) ? ratio : DEFAULT_IMAGE_ASPECT_RATIO;
298
+ }
299
+ /**
300
+ * One string argument, read from whichever shape the current stage carries.
301
+ *
302
+ * While the arguments are still streaming, the harness hands over a lazy view
303
+ * instead of the JSON, so `args.text(key)` is tried first; once they are
304
+ * complete the same argument comes back as `argsRaw`.
305
+ */
306
+ function argumentFromBlock(block, key) {
307
+ const partial = partialArgumentFromBlock(block, key);
227
308
  if (partial !== null) return partial;
228
309
  const raw = "kind" in block ? block.call?.argsRaw : dispatchedArgsRaw(block);
229
310
  if (typeof raw !== "string" || raw.trim() === "") return null;
230
311
  try {
231
312
  const value = JSON.parse(raw);
232
- return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.prompt === "string" ? value.prompt : null;
313
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
314
+ const found = value[key];
315
+ return typeof found === "string" ? found : null;
233
316
  } catch {
234
317
  return null;
235
318
  }
236
319
  }
237
- function partialPromptFromBlock(block) {
320
+ function partialArgumentFromBlock(block, key) {
238
321
  if (!("args" in block)) return null;
239
322
  const args = block.args;
240
323
  if (typeof args?.text !== "function") return null;
241
324
  try {
242
- const prompt = args.text.call(args, "prompt");
243
- return typeof prompt === "string" ? prompt : null;
325
+ const value = args.text.call(args, key);
326
+ return typeof value === "string" ? value : null;
244
327
  } catch {
245
328
  return null;
246
329
  }
@@ -23564,18 +23647,32 @@ window.__ModuleLoader__.load({
23564
23647
  .dsh-codex-composer-quota[data-level=warning] strong{color:var(--dsw-alias-label-warning,#d58a24)}
23565
23648
  .dsh-codex-composer-quota[data-level=danger] strong{color:var(--dsw-alias-label-danger,#d94b4b)}
23566
23649
  .dsh-codex-image-tool{border:0.5px solid var(--dsw-alias-border-l2);border-radius:8px;display:flex;flex-direction:column;gap:8px;margin:4px 0 4px 4px;max-width:520px;padding:10px 12px}
23567
- .dsh-codex-image-tool-head{align-items:center;display:flex;font-size:13px;gap:8px;min-width:0}
23650
+ /* One line, always. In a 320px column the label used to be squeezed until it
23651
+ stacked one character per row, which reads as broken text rather than a card.
23652
+ The dot and the label therefore keep their own size (flex:none + nowrap) and
23653
+ the summary is the only thing that gives way — it shrinks to zero width and
23654
+ ellipsizes, with the full prompt left in its title attribute. */
23655
+ .dsh-codex-image-tool-head{align-items:center;display:flex;flex-wrap:nowrap;font-size:13px;gap:8px;min-width:0}
23568
23656
  .dsh-codex-image-dot{background:var(--dsw-alias-button-info-fill,#397ee8);border-radius:50%;corner-shape:round;display:inline-block;flex:none;height:8px;width:8px}
23569
23657
  .dsh-codex-image-tool[data-state=error] .dsh-codex-image-dot{background:var(--dsw-alias-label-danger,#d94b4b)}
23570
- .dsh-codex-image-title{font-weight:600}
23571
- .dsh-codex-image-summary{color:var(--dsw-alias-label-tertiary);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
23658
+ .dsh-codex-image-title{flex:none;font-weight:600;white-space:nowrap}
23659
+ .dsh-codex-image-summary{color:var(--dsw-alias-label-tertiary);flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
23572
23660
  .dsh-codex-image-grid{display:flex;flex-wrap:wrap;gap:8px}
23661
+ /* The tray the image will land in, drawn while the call is still running so the
23662
+ card does not jump from one text row to a full picture when the result
23663
+ arrives. Neither dimension is set here: the view fixes the short side (height
23664
+ for a portrait frame, width for a landscape one) and the inline aspect-ratio
23665
+ derives the other, which is what keeps the 4:1 and 8:1 strips from being
23666
+ clamped into squarer boxes. max-width is the card's edge. The breathe is the
23667
+ same restrained opacity pulse the settings skeleton uses, and the
23668
+ reduced-motion block below turns it off. */
23669
+ .dsh-codex-image-placeholder{animation:dsh-codex-pulse 2s ease-in-out infinite;background:var(--dsw-alias-bg-layer-2);border:0.5px dashed var(--dsw-alias-border-l2);border-radius:8px;display:block;flex:none;max-width:100%}
23573
23670
  .dsh-codex-image-preview{background:var(--dsw-alias-bg-layer-2);border:0.5px solid var(--dsw-alias-border-l2);border-radius:8px;display:block;max-height:260px;max-width:min(100%,360px);object-fit:contain}
23574
23671
  .dsh-codex-image-loading,.dsh-codex-image-failed{border:0.5px dashed var(--dsw-alias-border-l2);border-radius:8px;color:var(--dsw-alias-label-tertiary);display:inline-flex;font-size:12px;line-height:1.4;padding:18px 20px}
23575
23672
  .dsh-codex-image-failed{color:var(--dsw-alias-label-danger,#d94b4b)}
23576
23673
  @keyframes dsh-codex-pulse{0%,100%{opacity:.45}50%{opacity:.85}}
23577
23674
  @media(max-width:560px){.dsh-codex-row,.dsh-codex-pref-row,.dsh-codex-quota-fact,.dsh-codex-reset-credits{align-items:flex-start;flex-direction:column;gap:3px}.dsh-codex-value,.dsh-codex-quota-fact strong{text-align:left}.dsh-codex-actions{justify-content:flex-start}.dsh-codex-grouphead{align-items:flex-start;flex-direction:column;gap:0;padding:12px 0}.dsh-codex-meter-meta{align-items:flex-start;flex-direction:column;gap:2px}.dsh-codex-errorbar{align-items:flex-start;flex-direction:column}.dsh-codex-segments{width:100%}.dsh-codex-select{max-width:none;width:100%}.dsh-codex-context-row{align-items:flex-start;flex-direction:column;gap:5px}.dsh-codex-segments label{flex:1}.dsh-codex-segments span{text-align:center}.dsh-codex-image-tool{max-width:100%;margin-left:0}}
23578
- @media(prefers-reduced-motion:reduce){.dsh-codex-meter>span{transition:none}.dsh-codex-skeleton span{animation:none}}
23675
+ @media(prefers-reduced-motion:reduce){.dsh-codex-meter>span{transition:none}.dsh-codex-skeleton span{animation:none}.dsh-codex-image-placeholder{animation:none}}
23579
23676
  `;
23580
23677
  /**
23581
23678
  * Install the ChatGPT tab's stylesheet as a document-owned, never-removed
@@ -54970,22 +55067,36 @@ window.__ModuleLoader__.load({
54970
55067
  done: "rowDone",
54971
55068
  error: "rowError"
54972
55069
  };
54973
- /** 行的外框样式。 */
55070
+ /**
55071
+ * 行的外框样式。
55072
+ *
55073
+ * 字号与官方工具行标题对齐(13px):同一条时间线上混着 12px 的进展行与 13px 的
55074
+ * 工具行,读者看到的是两套字号。
55075
+ */
54974
55076
  const rowStyle = {
54975
55077
  display: "flex",
54976
55078
  flexDirection: "column",
54977
55079
  gap: "6px",
54978
55080
  width: "100%",
54979
- fontSize: "12px",
55081
+ fontSize: "13px",
54980
55082
  color: "var(--dsw-alias-label-primary, #1f2329)"
54981
55083
  };
54982
- /** 标题行按钮样式(整行可点,去掉原生按钮外观)。 */
55084
+ /**
55085
+ * 标题行按钮样式(整行可点,去掉原生按钮外观)。
55086
+ *
55087
+ * `minHeight: 36px` 与 `padding: 6px 0` 抄的是官方工具行那层 `_row_` 的实测取值
55088
+ * (`height:36px; min-height:36px; padding:6px 0`)。少了这两个值,进展行只有
55089
+ * 18px 左右,夹在一排 36px 的工具行里会明显更矮、更挤——用户报的就是这个。
55090
+ * 用 minHeight 而不是 height:正文换行、标题很长时允许它撑高,官方那种固定高度
55091
+ * 在文字折行时会把内容裁掉。
55092
+ */
54983
55093
  const headerStyle = {
54984
55094
  display: "flex",
54985
- alignItems: "baseline",
55095
+ alignItems: "center",
54986
55096
  gap: "8px",
54987
55097
  width: "100%",
54988
- padding: "0",
55098
+ minHeight: "36px",
55099
+ padding: "6px 0",
54989
55100
  border: "0",
54990
55101
  background: "transparent",
54991
55102
  color: "inherit",
@@ -54997,9 +55108,9 @@ window.__ModuleLoader__.load({
54997
55108
  const titleStyle = {
54998
55109
  flex: "1 1 auto",
54999
55110
  minWidth: "0",
55000
- fontSize: "12px",
55111
+ fontSize: "13px",
55001
55112
  fontWeight: 500,
55002
- lineHeight: "1.5",
55113
+ lineHeight: "16px",
55003
55114
  overflowWrap: "anywhere"
55004
55115
  };
55005
55116
  /** 状态词的小字样式。 */
@@ -55012,6 +55123,8 @@ window.__ModuleLoader__.load({
55012
55123
  /**
55013
55124
  * 正文样式:只管外边距、字号与颜色。
55014
55125
  *
55126
+ * 字号与标题一致(13px)—— 早先正文比标题小一号,展开后两节字不同大,读起来碎。
55127
+ *
55015
55128
  * **不再**在这里写 `whiteSpace: 'pre-wrap'`:正文交给 {@link MarkdownLite} 渲染,
55016
55129
  * 换行该不该保留由它产出的块级元素决定。早先这里是纯文本 `<p>{message}</p>`,
55017
55130
  * 于是列表符号、`**粗体**` 和管道符表格全部原样上屏 —— 同一份进展在侧栏面板里
@@ -55020,8 +55133,8 @@ window.__ModuleLoader__.load({
55020
55133
  const messageStyle = {
55021
55134
  overflowWrap: "anywhere",
55022
55135
  margin: "0",
55023
- fontSize: "12px",
55024
- lineHeight: "1.55",
55136
+ fontSize: "13px",
55137
+ lineHeight: "1.6",
55025
55138
  color: "var(--dsw-alias-label-secondary, #5b6270)"
55026
55139
  };
55027
55140
  /**