dsh-connect-minimaxcode 0.6.7 → 0.6.9

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/README.en.md CHANGED
@@ -57,10 +57,47 @@ Either:
57
57
  1. **The MiniMax Code desktop app (China region) installed and signed in** — the plugin reuses its sign-in (the easy way)
58
58
  2. **Nothing installed** — press "Sign in to MiniMax Code" on the plugin page; the plugin starts an OAuth device code flow and you confirm with your own MiniMax account in a browser (this needs `account.minimax.cn` to be reachable)
59
59
 
60
- Plus DSH desktop (built-in core `>=0.2.1-rc.2 <0.3.0`, see `engines.dsh` in `package.json`; tested on `0.2.1-rc.2`) or the Web profile.
60
+ Plus DSH desktop (built-in core `>=0.1.6-alpha.2 <0.3.0`, see `engines.dsh` in `package.json`; tested on `0.2.2-rc.1`) or the Web profile.
61
61
 
62
62
  > **Sending a picture needs the host's attachment service**: the plugin does not read images itself — the host's durable attachment service turns a stored reference into request bytes, and the plugin only connects the two. On a host without that service the plugin still loads and text still works; only image requests are refused (`pi-ai image input requires the durable attachment service`).
63
63
 
64
+ ## Compatibility: the oldest DSH this works on
65
+
66
+ **The oldest supported DSH is `0.1.6-alpha.2`** — that is the range in `engines.dsh`. The plugin used to declare `0.2.1-rc.2`, which was a conservative floor rather than a technical one; this time the host packages' published history was searched, and the floor moved down to the version where each feature actually exists.
67
+
68
+ The host publishes its own packages under **the same version numbers as the desktop** (DSH `0.2.2-rc.1` ships `0.2.2-rc.1` packages), so "which release first had this API" can be answered straight from npm. Every row below was read out of that version's published source:
69
+
70
+ | Host capability | First version | What the plugin uses it for |
71
+ | --- | --- | --- |
72
+ | `llm.registerAdapter` | `0.0.1-rc.1` | registering the MiniMax Code provider |
73
+ | `webServer.register` (`kind: 'exact'`) | `0.0.1-rc.1` | the status, action, generation and capability routes |
74
+ | `attachments.saveImage` | `0.0.1-rc.1` | storing a generated picture as an attachment |
75
+ | client `slots` / `sessions` services | `0.0.1-rc.3` | the credit pill and the composer tool row |
76
+ | `conversation.input.left` slot | `0.0.1-rc.1` | where the pill and the generate entry sit |
77
+ | `dsh.client.inject` / `immediately` | `0.0.1-rc.1` | loading the client half |
78
+ | `session.beginSubmission` / `binding()` / `scope()` | `0.1.2-alpha.2` | the picture going out **and showing up in the conversation** |
79
+ | `inputActions.addAttachments` / `conversation.createDrafts` | `0.1.3-alpha.2` | the picture going into the input box (the draft channel) |
80
+ | **`plugins.bundle.config` slot** | **`0.1.6-alpha.2`** | **the plugin page (generation panel, check-in switch, the full bill)** |
81
+ | **`cordis.patch` manifest patch** | **`0.1.6-alpha.2`** | **offering the provider in the model picker without changing the default** |
82
+
83
+ **The last two rows are the real floor.** The plugin page and the manifest patch both begin at `0.1.6-alpha.2`; older hosts only have the plugin manager's own `settings.plugin.item` — a card inside the settings section, on a keyed slot — which is not a plugin page.
84
+
85
+ ### What an older host still gets
86
+
87
+ The plugin is not useless on an older host; it gives up one thing at a time. That is also why the floor sits at `0.1.6-alpha.2` rather than lower: **the plugin page is the plugin's main surface** (the H3 generation panel, the check-in switch and the detailed balance all live there), and claiming support while it is missing would be a lie.
88
+
89
+ | Host version | What still works | What is missing |
90
+ | --- | --- | --- |
91
+ | `>= 0.1.6-alpha.2` | everything | —— |
92
+ | `0.1.3-alpha.2` – `0.1.6-alpha.1` | the models, the credit pill, the generate entry in the composer, a picture reaching the conversation | no plugin page (the generation panel and the check-in switch live there); the manifest patch has no effect, so the model must be picked by hand |
93
+ | `0.1.2-alpha.2` – `0.1.3-alpha.1` | less again: the picture can still be sent straight through the session | no draft channel (`createDrafts` / `addAttachments` did not exist yet) |
94
+ | `< 0.1.2-alpha.2` | the text models and the credit display | a picture reaches the model but **does not show in the conversation** (no local echo, `beginSubmission`) |
95
+
96
+ A host that does not know a manifest field ignores it (the manifest is not validated strictly), so an old host does not refuse the plugin over `cordis.patch` — it merely has no effect.
97
+
98
+ > This table was **read out of the published history of every host package on npm**, version by version — not estimated.
99
+ >
100
+ > **And it does not stop at an inference**: this repository's lockfile resolves the host packages to `0.1.6-alpha.2`, so every release compiles, builds and runs the whole suite **against that oldest host** — the floor is verified on every release, not concluded once. The method is written down in the repository: pull each host package at each version and look for the API in the source it shipped.
64
101
  ## Installation
65
102
 
66
103
  **Option 1: install from the Git repository** (recommended, gets the latest code)
@@ -384,6 +421,47 @@ The OAuth token is valid for about an hour and is refreshed automatically by the
384
421
 
385
422
  ## Changelog
386
423
 
424
+ ### 0.6.9 (2026-11-13)
425
+
426
+ - **The oldest supported DSH moved from `0.2.1-rc.2` down to `0.1.6-alpha.2`.** The old floor was a conservative value, not a technical one. This release searched the host packages' published history version by version and, for every host capability the plugin uses, found the release it first appeared in.
427
+ - **The host publishes its own packages under the same version numbers as the desktop** (DSH `0.2.2-rc.1` ships `0.2.2-rc.1` packages), so "which release first had this API" can be answered straight from npm — every row of the compatibility table was read out of the source that host package version actually shipped.
428
+ - **Two things set the real floor**: the `plugins.bundle.config` slot the plugin page lives in, and the `cordis.patch` manifest patch that offers the provider in the model picker. Both begin at `0.1.6-alpha.2`. Older hosts only have the plugin manager's own `settings.plugin.item` — a card inside the settings section, on a keyed slot — which is not a plugin page.
429
+ - **Older hosts are not useless, they give up one thing at a time** (the README has the full table): from `0.1.3-alpha.2` the models, the credit pill, the generate entry in the composer and a picture reaching the conversation all work, only the plugin page is missing; from `0.1.2-alpha.2` a picture reaches the conversation too, only the draft channel is missing. A host that does not know a manifest field ignores it (the manifest is not validated strictly), so an old host does not refuse the plugin over `cordis.patch` — it merely has no effect.
430
+ - The peer dependency floors for the host packages moved to `>=0.1.6-alpha.2` as well, so an older host's own versions satisfy the range. **The lockfile resolves to `0.1.6-alpha.2`** — so every release compiles, builds and runs the whole suite against that oldest host, and the floor is verified on every release.
431
+ - The README gained a "Compatibility: the oldest DSH this works on" section (in both languages), with the evidence table and the degradation ladder.
432
+
433
+ ### 0.6.8 (2026-11-13)
434
+
435
+
436
+ - **The plan tier moved into the Plan box**: the tier and the day it ends used to be a row of their own below the two boxes, stretched into a band of empty space. The tier is now the Plan box's headline line, wearing **the balance's own big-number size and height** (20px), and the day it ends stays the small text it was, on the right and on the tier's baseline; the box itself is named "Plan" rather than "Plan allowance".
437
+ - **The grid has no hole any more**: the tier row no longer occupies a grid cell, so the check-in and the expiring grants now sit side by side in the second row and both columns are filled — the empty band under the left column and the hole at the bottom right are gone together. The Plan box did not grow by a row either: the tier line takes the place of the invisible number that used to hold the row height open.
438
+ - The tier is still **the value the upstream sends** (`token_plan_tier`), not a fixed label: another plan shows its own name, and only an answer without that field falls back to "M Plan subscription".
439
+
440
+
441
+ - **Fixed "Could not send it to the conversation": the session method was never bound.** Before this release the composer's **Generate an image** entry failed *every* time, reporting "Could not send it to the conversation; generate on the plugin page and download it instead." The plugin took `session.prompt` off the session object and then called it as a standalone function:
442
+
443
+ ```ts
444
+ const send = binding?.session?.prompt
445
+ return typeof send === 'function' ? { prompt: send } : undefined
446
+ ```
447
+
448
+ The host's own `Session.prompt` reaches the gateway **through `this.remote`**:
449
+
450
+ ```ts
451
+ result = await this.remote.session.prompt({ ... })
452
+ ```
453
+
454
+ Once the method is taken off the object, `this` no longer points at the session but at the `{ prompt: send }` wrapper, so `this.remote` is `undefined` and the call throws `undefined is not an object (evaluating 'this.remote.session')`. The method is now bound back to its session (`beginSubmission` for the same reason), and the picture actually goes out.
455
+
456
+ Worth noting: the draft channel in the same file has always written `create.bind(conversation)` — only the send path missed it. And because the send path is tried first, the draft path never runs, so that correct binding could not rescue it.
457
+
458
+ - **A sent picture now shows up in the conversation.** The plugin used to invent its own `requestId` (`minimax-h3-${Date.now()}`) and hand it to `prompt()`: the model received the picture, but the **conversation showed no such message** — the host wants a local submission echo registered with `beginSubmission()` first, and the prompt to carry the identity that registration mints. The dock now does what the host's own composer does, and the picture arrives as part of that turn.
459
+
460
+ - **A refusal is no longer read as a success.** A refused prompt resolves with `{ ok: false }` rather than throwing, and the plugin used to treat that as success and clear the note, leaving nothing on screen. The outcome is now read, and a refusal says so.
461
+
462
+ - **Three new regression cases.** The mock in `tests/generate-dock.spec.ts` used to be an **arrow function that never reads `this`**, so whether the method was bound could not be observed — while the real host reads `this.remote`, and that is exactly the difference. The new cases make the mock read `this.remote` the way the host does, and pin three things: the call keeps the session as its receiver, the prompt carries the identity `beginSubmission` minted, and a refusal is reported. **Revert the fix and they fail at once.**
463
+
464
+
387
465
  ### 0.6.7 (2026-11-12)
388
466
 
389
467
  - **Merged PR [#5](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/5) by [azsora](https://github.com/azsora)**:
@@ -643,6 +721,8 @@ Fixing it yourself is even better — just open the PR. The format does not matt
643
721
  - **[azsora](https://github.com/azsora)** — found the plan's rate-limit endpoint in the desktop app's own traffic and wired the 5-hour and weekly usage windows into the credit pill and the plugin page ([#4](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/4)): the window length is derived from the response's own edges rather than hard-coded. It became 0.5.5.
644
722
  - **[qzz19970504](https://github.com/qzz19970504)** — found and fixed the wiring gap that made every image upload fail ([#3](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/3)): the catalog declared the `image` modality, but the adapter never connected the host's durable attachment service, so pi-ai refused image requests before they were sent. The fix became 0.5.3.
645
723
  - **[azsora](https://github.com/azsora)** — found that the official quota endpoint renamed its window field and inverted its meaning (the share used became the share remaining), closed the gap where a 200 with an unreadable shape never reached the older path, and reworked the pill detail and the page card into rounded sub-cards that say why there are no windows ([#5](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/5)). It became 0.6.7.
724
+ - **[lulunick](https://github.com/lulunick)** — found the real reason the "Generate an image" entry failed **every** time: the plugin took `session.prompt` off the session object and called it as a standalone function, so `this` pointed at the wrapper instead of at the session — and the host's own `Session.prompt` reaches the gateway through `this.remote`, which made the call throw `undefined is not an object`. With the methods bound back to their session the picture actually goes out; he also added the local submission echo the host asks for (`beginSubmission()` — the plugin used to invent its own `requestId`, so the model received the picture and the conversation showed nothing) and reports a refusal honestly (the host resolves with `{ ok: false }` rather than throwing). The test's mock now reads `this.remote` the way the host does, which is what makes all three observable ([#7](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/7)). It became 0.6.8.
725
+ - **[azsora](https://github.com/azsora)** — moved the plan tier into the Plan box as its headline line, at the balance box's own big-number size, named the box "Plan", and left no hole in the grid ([#6](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/6)). It became 0.6.8.
646
726
  - Plugin author: **[JeffTsuiCUG](https://github.com/JeffTsuiCUG)**
647
727
 
648
728
  ## Acknowledgements
package/README.md CHANGED
@@ -57,10 +57,47 @@
57
57
  1. **已安装并登录 MiniMax Code 桌面 App(国内版)** —— 插件直接复用它的登录态(最省事)
58
58
  2. **什么都不装** —— 在插件页面点「登录 MiniMax Code」,插件发起 OAuth 设备码流,你在浏览器里用自己的 MiniMax 账号确认,插件拿到自己的登录态(需要能访问 `account.minimax.cn`)
59
59
 
60
- 另外需要 DSH 桌面版(内置内核 `>=0.2.1-rc.2 <0.3.0`,见 `package.json` 的 `engines.dsh`;实测 `0.2.1-rc.2`)或 Web profile。
60
+ 另外需要 DSH 桌面版(内置内核 `>=0.1.6-alpha.2 <0.3.0`,见 `package.json` 的 `engines.dsh`;实测 `0.2.2-rc.1`)或 Web profile。
61
61
 
62
62
  > **发图需要宿主提供附件服务**:图片不是插件自己读的,而是由宿主的持久化附件服务读成请求字节,插件只负责把附件引用接上去。宿主没有附件服务时,插件照常加载、文本照常对话,只是发图会被拒绝(`pi-ai image input requires the durable attachment service`)。
63
63
 
64
+ ## 兼容性:最低支持哪个 DSH 版本
65
+
66
+ **最低支持 DSH `0.1.6-alpha.2`**(`engines.dsh` 里写的就是这个区间)。这一版之前插件声明的是 `0.2.1-rc.2`,那是个保守的下限,不是技术上的;这次下探宿主各包的发布历史后,把下限一路降到了每个功能真正存在的那一版。
67
+
68
+ 宿主把它自己的各个包按**与桌面端相同的版本号**发布(DSH `0.2.2-rc.1` 里带的就是 `0.2.2-rc.1` 的包),所以「某个 API 最早出现在哪一版」可以直接对着 npm 上的宿主包查。下面这张表就是这么查出来的 —— 每一行都是宿主包那个版本号里的源码里**实际出现过**这个 API:
69
+
70
+ | 宿主能力 | 最早版本 | 插件拿它做什么 |
71
+ | --- | --- | --- |
72
+ | `llm.registerAdapter` | `0.0.1-rc.1` | 注册 MiniMax Code 模型提供者 |
73
+ | `webServer.register`(`kind: 'exact'`) | `0.0.1-rc.1` | 状态、动作、生成、自检四条路由 |
74
+ | `attachments.saveImage` | `0.0.1-rc.1` | 把生成的图存成附件 |
75
+ | 客户端 `slots` / `sessions` 服务 | `0.0.1-rc.3` | 积分胶囊与输入框工具行 |
76
+ | `conversation.input.left` 插槽 | `0.0.1-rc.1` | 胶囊与「生成图片」入口的落位 |
77
+ | `dsh.client.inject` / `immediately` | `0.0.1-rc.1` | 客户端半边能被加载 |
78
+ | `session.beginSubmission` / `binding()` / `scope()` | `0.1.2-alpha.2` | 图片发出去**并在对话里显示** |
79
+ | `inputActions.addAttachments` / `conversation.createDrafts` | `0.1.3-alpha.2` | 图片放进输入框(草稿通道) |
80
+ | **`plugins.bundle.config` 插槽** | **`0.1.6-alpha.2`** | **插件页(生成面板、签到开关、完整账单)** |
81
+ | **`cordis.patch` 清单补丁** | **`0.1.6-alpha.2`** | **把提供者加进模型选择器而不改默认模型** |
82
+
83
+ **最后两行是真正的下限**:插件页与清单补丁都从 `0.1.6-alpha.2` 起才有,更早的宿主只有插件管理器自己的 `settings.plugin.item`(设置页里的一张卡片,keyed 插槽),不是插件页。
84
+
85
+ ### 更早的宿主会怎样
86
+
87
+ 插件在更早的宿主上并非完全不能用,只是逐项减少。这也是为什么下限定在 `0.1.6-alpha.2` 而不是更低:**插件页是插件的主界面**(H3 生成面板、签到开关、完整账单都在里面),缺了它而对外声称支持,是骗人。
88
+
89
+ | 宿主版本 | 还能用什么 | 缺什么 |
90
+ | --- | --- | --- |
91
+ | `>= 0.1.6-alpha.2` | 全部功能 | —— |
92
+ | `0.1.3-alpha.2` ~ `0.1.6-alpha.1` | 模型、积分胶囊、输入框里的生成入口、图片进对话 | 没有插件页(生成面板与签到开关在插件页上);清单补丁不生效,需手动选模型 |
93
+ | `0.1.2-alpha.2` ~ `0.1.3-alpha.1` | 再减:图片仍可经会话直接发出 | 草稿通道不可用(`createDrafts` / `addAttachments` 还没有) |
94
+ | `< 0.1.2-alpha.2` | 文本模型与积分显示 | 图片发得出但**对话里不显示**(没有本地回执 `beginSubmission`) |
95
+
96
+ 宿主不认识的清单字段会被忽略(不是严格校验),所以老宿主不会因为 `cordis.patch` 而拒绝安装插件 —— 它只是不生效。
97
+
98
+ > 这份表是**对着 npm 上宿主各包的发布历史逐版本查出来**的,不是估计的。
99
+ >
100
+ > **而且不只停在推断上**:本仓库的锁文件把宿主包解析到 `0.1.6-alpha.2`,因此每次发布的 CI 都会**对着这个最老的宿主**编译、构建并跑完全部用例 —— 下限是被每次发布验证着的,不是一次性的结论。查询方式见仓库里的说明:把每个宿主包的每个版本拉下来,看它发布出来的源码里有没有那个 API。
64
101
  ## 安装
65
102
 
66
103
  **方式一:从 Git 仓库安装**(推荐,拿到最新代码)
@@ -395,6 +432,47 @@ OAuth 令牌有效期约 1 小时,由客户端自动续期。插件**只在自
395
432
 
396
433
  ## 更新记录
397
434
 
435
+ ### 0.6.9(2026-11-13)
436
+
437
+ - **最低支持的 DSH 版本从 `0.2.1-rc.2` 降到 `0.1.6-alpha.2`**。原来的下限是保守取值,不是技术上的。这一版把宿主各包的发布历史逐版本查了一遍,对每个插件用到的宿主能力,核出它最早出现在宿主包的哪个版本里。
438
+ - **宿主把它自己的包按与桌面端相同的版本号发布**(DSH `0.2.2-rc.1` 里带的就是 `0.2.2-rc.1` 的包),因此「某个 API 最早在哪一版」可以直接对着 npm 查 —— 本次兼容性表里的每一行,都是宿主包该版本号发布出来的源码里**实际出现过**这个 API。
439
+ - **真正的下限由两样东西决定**:插件页所在的 `plugins.bundle.config` 插槽,以及让提供者出现在模型选择器里的 `cordis.patch` 清单补丁 —— 两者都从 `0.1.6-alpha.2` 起才有。更早的宿主只有插件管理器自己的 `settings.plugin.item`(设置页里的一张卡片,keyed 插槽),不是插件页。
440
+ - **更早的宿主并非不能用,只是逐项减少**(README 有完整表格):`0.1.3-alpha.2` 起模型、积分胶囊、输入框里的生成入口、图片进对话都可用,只是没有插件页;`0.1.2-alpha.2` 起图片能发出并在对话里显示,只是没有草稿通道。宿主不认识的清单字段会被忽略(清单不是严格校验),所以老宿主不会因为 `cordis.patch` 拒绝安装插件 —— 只是它不生效。
441
+ - 宿主包的 peer 依赖下限同步降到 `>=0.1.6-alpha.2`,使老宿主自带的版本能满足区间。**锁文件也解析到 `0.1.6-alpha.2`** —— 于是每次发布的 CI 都会对着这个最老的宿主编译、构建并跑完全部用例,下限是被每次发布验证着的。
442
+ - README 新增「兼容性:最低支持哪个 DSH 版本」一节(中英文),含证据表与降级说明。
443
+
444
+ ### 0.6.8(2026-11-13)
445
+
446
+
447
+ - **套餐档位移进 Plan 框**:档位与到期日原本是卡片网格里独立的一行,横在两个框下面,被拉伸成一片空白。现在档位成为 Plan 框的标题行,**字号与高度和余额框的大数字完全一致**(20px),到期日仍是原来的小字、靠右基线对齐;Plan 框的名字由「Plan 额度」简化为「Plan」。
448
+ - **网格不再留洞**:档位行不再占一个网格单元,「每日签到」与「到期批次」因此并排落到第二行,两列都填满 —— 之前左列下方那条空白与右下角的空格子一并消失。Plan 框内的高度也一行不多:档位行顶替的正是原先用来对齐行高的那个隐藏占位。
449
+ - 档位本身仍是**上游给的值**(`token_plan_tier`),不是写死的标签:别的套餐会显示它自己的名字;只有上游没返回该字段时,才回落到「M Plan 订阅」。
450
+
451
+
452
+ - **修复「无法发送到对话」:会话方法没有绑定 `this`**。这一版之前,输入框里的「生成图片」**必然**失败,提示「无法发送到对话,请到插件页生成后下载」。原因在插件把 `session.prompt` 从会话对象上摘下来之后,以独立函数的方式调用:
453
+
454
+ ```ts
455
+ const send = binding?.session?.prompt
456
+ return typeof send === 'function' ? { prompt: send } : undefined
457
+ ```
458
+
459
+ 宿主自己的 `Session.prompt` 是**通过 `this.remote` 走到网关**的:
460
+
461
+ ```ts
462
+ result = await this.remote.session.prompt({ ... })
463
+ ```
464
+
465
+ 方法一旦被摘下来,`this` 就不再指向会话,而是指向外面那个 `{ prompt: send }` 包装对象,于是 `this.remote` 是 `undefined`,调用抛出 `undefined is not an object (evaluating 'this.remote.session')`。现在方法被绑回会话对象(`beginSubmission` 同理),图片就能真正发出去了。
466
+
467
+ 顺带一提:同一个文件里的草稿通道**一直**写着 `create.bind(conversation)`,只是发送通道漏了 —— 而发送通道优先,草稿通道根本走不到,所以那个正确的绑定救不了场。
468
+
469
+ - **发图现在会出现在对话里**。此前插件自己编了一个 `requestId`(`minimax-h3-${Date.now()}`)交给 `prompt()`:模型能收到图片,但**对话里不显示这条消息** —— 宿主要求先用 `beginSubmission()` 注册一次本地回执、再用它给出的身份发送。现在照宿主输入框的做法走,图片作为这一轮消息的一部分出现。
470
+
471
+ - **不再把失败当成功**。`prompt()` 被拒绝时返回的是 `{ ok: false }` 而不是抛异常,插件此前会把它当成功、把提示条清空,于是屏幕上什么都不剩。现在会读出结果,拒绝时如实说「无法发送到对话」。
472
+
473
+ - **新增回归测试(3 个用例)**。此前 `tests/generate-dock.spec.ts` 的 mock 是个**不读 `this`** 的箭头函数,因此方法有没有绑定根本测不出来 —— 真实宿主要读 `this.remote`,差别正在于此。这次的新用例让 mock 像宿主一样读 `this.remote`,三条断言分别钉住:调用带着会话当接收者、发送的是 `beginSubmission` 给出的身份、宿主拒绝时如实提示。**把这三条用例里的修复撤掉,它们会立刻失败。**
474
+
475
+
398
476
  ### 0.6.7(2026-11-12)
399
477
 
400
478
  - **并入 PR [#5](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/5)(作者 [azsora](https://github.com/azsora))**:
@@ -656,6 +734,8 @@ dsh plugin --profile desktop add dsh-qoder-connect
656
734
  - **[azsora](https://github.com/azsora)** —— 从桌面 App 的实际调用里找出了套餐的**限额窗口接口**,并把 5 小时与每周两个窗口的用量接进了积分胶囊与插件页卡片([#4](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/4)):窗口时长从响应自身的起止字段推导,不写死。该功能成为 0.5.5。
657
735
  - **[qzz19970504](https://github.com/qzz19970504)** —— 定位并修复了「发图必然失败」的接线缺口([#3](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/3)):模型目录声明了 `image` 模态,但适配器没有把宿主的持久化附件服务接上,导致带图请求被 pi-ai 在发出前拒掉。该修复成为 0.5.3。
658
736
  - **[azsora](https://github.com/azsora)** —— 找出官方额度接口改名并反转语义(「已用百分比」→「剩余百分比」)这件事,补上了「200 但形状读不懂时也要回落旧路径」的缺口,并把积分胶囊与插件页卡片重排为圆角分栏、让「为什么没有窗口」逐项有说法([#5](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/5))。该合并成为 0.6.7。
737
+ - **[lulunick](https://github.com/lulunick)** —— 找到了「生成图片**必然**失败」的真正原因:插件把 `session.prompt` 从会话对象上摘下来后以独立函数调用,`this` 因此指向了包装对象而不是会话,宿主自己的 `Session.prompt` 正是通过 `this.remote` 走到网关的,于是抛 `undefined is not an object`。方法被绑回会话后图片才真正发得出去;顺带补上了宿主要求的 `beginSubmission()` 本地回执(此前插件自己编 `requestId`,模型收到了图片但对话里不显示),以及被拒绝时(返回 `{ ok: false }` 而非抛异常)的如实提示。他同时把测试里的 mock 改成像宿主一样读 `this.remote`,让这三点钉得住([#7](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/7))。该修复成为 0.6.8。
738
+ - **[azsora](https://github.com/azsora)** —— 把套餐档位移进 Plan 框当标题行,字号与余额框的大数字一致,Plan 框改名为「Plan」,网格也不再留洞([#6](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/6))。该重排成为 0.6.8。
659
739
  - 插件作者:**[JeffTsuiCUG](https://github.com/JeffTsuiCUG)**
660
740
 
661
741
  ## 致谢
package/lib/client.js CHANGED
@@ -50,7 +50,7 @@
50
50
  expiringAmount: '{amount} 积分',
51
51
  nextExpiry: '最近到期日',
52
52
  creditsBox: '积分',
53
- planTitle: 'Plan 额度',
53
+ planTitle: 'Plan',
54
54
  usageTitle: '本机用量',
55
55
  usageTotal: '累计',
56
56
  lots: '到期批次',
@@ -136,7 +136,7 @@
136
136
  expiringAmount: '{amount} credits',
137
137
  nextExpiry: 'Next expiry',
138
138
  creditsBox: 'Credits',
139
- planTitle: 'Plan allowance',
139
+ planTitle: 'Plan',
140
140
  usageTitle: 'Local usage',
141
141
  usageTotal: 'Total',
142
142
  lots: 'Expiring grants',
@@ -440,6 +440,11 @@
440
440
  * it nothing: the rolling windows it does not have are already
441
441
  * explained by the notice beside them. What this adds is for the
442
442
  * accounts that do hold one — the tier's name and the day it ends.
443
+ *
444
+ * It heads the plan box rather than standing on its own below it: the
445
+ * tier is the plan's headline fact, so it wears the balance's own big
446
+ * number size. The day it ends is a detail beside the tier, and keeps
447
+ * the row size.
443
448
  */
444
449
  function planLine(t, plan) {
445
450
  if (plan === undefined || !plan.active)
@@ -448,7 +453,7 @@
448
453
  const expiry = plan.expiresAtMs === undefined
449
454
  ? null
450
455
  : t('planExpires', { date: day(plan.expiresAtMs) });
451
- return h('div', { style: rowStyle, 'data-minimax-plan': true }, h('span', null, tier), h('span', null, expiry ?? ''));
456
+ return h('div', { style: planHeadStyle, 'data-minimax-plan': true }, h('span', null, tier), h('span', { style: planExpiryStyle }, expiry ?? ''));
452
457
  }
453
458
  /** The pill itself; the gate decides whether it is drawn at all. */
454
459
  function CreditPill(props) {
@@ -612,14 +617,32 @@
612
617
  * `binding(id).session.prompt(content, mode, signal, requestId)` is
613
618
  * what the composer calls when the person presses send, so a picture
614
619
  * sent this way is a normal turn.
620
+ *
621
+ * The method is taken off the session object, so it has to be bound to
622
+ * that object before it is called. `prompt` reaches the gateway
623
+ * through `this.remote`, and an unbound call runs with `this` pointing
624
+ * at the wrapper below instead of at the session — which throws
625
+ * `undefined is not an object (evaluating 'this.remote.session')` and
626
+ * leaves the dock reporting that it cannot send the picture.
615
627
  */
616
628
  const sessionFace = () => {
617
629
  const id = sessionId();
618
630
  if (id === undefined)
619
631
  return undefined;
620
632
  const binding = clientContext?.sessions?.binding?.(id);
621
- const send = binding?.session?.prompt;
622
- return typeof send === 'function' ? { prompt: send } : undefined;
633
+ const session = binding?.session;
634
+ const send = session?.prompt;
635
+ if (typeof send !== 'function')
636
+ return undefined;
637
+ // `beginSubmission` is taken off the session for the same reason: it
638
+ // is a method of the session and must keep its receiver.
639
+ const begin = session?.beginSubmission;
640
+ return {
641
+ prompt: send.bind(session),
642
+ ...(typeof begin === 'function'
643
+ ? { beginSubmission: begin.bind(session) }
644
+ : {}),
645
+ };
623
646
  };
624
647
  /**
625
648
  * The conversation's own draft channel.
@@ -692,7 +715,24 @@
692
715
  : t('generateNoBinding'));
693
716
  }
694
717
  if (face !== undefined) {
695
- await face.prompt([part, { type: 'text', text }], 'queue', undefined, `minimax-h3-${Date.now()}`);
718
+ // The host registers a local submission echo before it prompts,
719
+ // and the identity that registration mints is the one the prompt
720
+ // must carry — that is how the sent picture shows up as this
721
+ // turn's message. A made-up identity reaches the model but leaves
722
+ // no echo, so the conversation would not show what was sent.
723
+ const submission = face.beginSubmission?.({
724
+ mode: 'queue',
725
+ text,
726
+ attachments: [],
727
+ });
728
+ const result = await face.prompt([part, { type: 'text', text }], 'queue', undefined, submission?.requestId);
729
+ // A refused prompt resolves with `{ ok: false }` rather than
730
+ // throwing, so the outcome has to be read: either way the picture
731
+ // did not arrive.
732
+ if (result === undefined || result.ok !== true) {
733
+ setNote(t('generateSendFailed'));
734
+ return;
735
+ }
696
736
  setPrompt('');
697
737
  setOpen(false);
698
738
  // The picture's own arrival is the feedback: it is in the
@@ -1020,6 +1060,9 @@
1020
1060
  // account's usage history across both.
1021
1061
  const lots = [...(credits_.upcoming ?? [])].sort((a, b) => a.expireAtMs - b.expireAtMs);
1022
1062
  const windows = rateLimitRows(t, status?.rateLimit);
1063
+ // The tier and the day it ends, for an account that holds one. It
1064
+ // heads the plan box rather than standing on its own below it.
1065
+ const planHead = planLine(t, status?.plan);
1023
1066
  const profileLines = usageProfileRows(t, status?.usageProfile);
1024
1067
  const updated = credits_.updatedAtMs !== undefined
1025
1068
  ? t('updated', { time: new Date(credits_.updatedAtMs).toLocaleTimeString() })
@@ -1027,18 +1070,18 @@
1027
1070
  const loginLine = login?.state === 'signed-in'
1028
1071
  ? (login.source === 'plugin' ? t('loginPlugin') : t('loginDesktop'))
1029
1072
  : undefined;
1030
- return h('div', { style: pageStyle, 'data-minimax-page': true }, h('div', { style: cardStyle, 'data-minimax-credits-card': true }, h('div', { style: cardHeadStyle }, h('span', { style: cardTitleStyle }, t('title')), h('span', { style: mutedStyle }, [loginLine, updated].filter(line => line !== undefined).join(' · '))), h('div', { style: gridStyle }, h('div', { style: boxStyle }, h('div', { style: boxTitleStyle }, t('creditsBox')), h('div', { style: bigNumberStyle }, credits(credits_.total, 2)), row(t('free'), credits(credits_.free, 3)), row(t('purchased'), credits(credits_.purchased, 3)), ...expiryRows(t, credits_).slice(0, 1)), h('div', { style: boxStyle, 'data-minimax-rate-limit': true }, h('div', { style: boxTitleStyle }, t('planTitle')), ...(windows.length > 0
1031
- ? [
1032
- // The invisible number keeps the windows lined up with
1033
- // the balance box's rows, which sit below its own big
1034
- // number. With no windows to align, it is only noise.
1035
- h('div', { style: { ...bigNumberStyle, visibility: 'hidden' }, 'aria-hidden': 'true' }, '—'),
1036
- ...windows,
1037
- ]
1038
- : [rateLimitFallback(t, status?.rateLimit)])),
1039
- // The tier and the day it ends, for an account that holds one.
1040
- // An account without a plan has nothing to say here.
1041
- planLine(t, status?.plan), signinBlock(t, signin, refresh), h('div', { style: boxStyle }, h('div', { style: boxTitleStyle }, t('lots')),
1073
+ return h('div', { style: pageStyle, 'data-minimax-page': true }, h('div', { style: cardStyle, 'data-minimax-credits-card': true }, h('div', { style: cardHeadStyle }, h('span', { style: cardTitleStyle }, t('title')), h('span', { style: mutedStyle }, [loginLine, updated].filter(line => line !== undefined).join(' · '))), h('div', { style: gridStyle }, h('div', { style: boxStyle }, h('div', { style: boxTitleStyle }, t('creditsBox')), h('div', { style: bigNumberStyle }, credits(credits_.total, 2)), row(t('free'), credits(credits_.free, 3)), row(t('purchased'), credits(credits_.purchased, 3)), ...expiryRows(t, credits_).slice(0, 1)), h('div', { style: boxStyle, 'data-minimax-rate-limit': true }, h('div', { style: boxTitleStyle }, t('planTitle')),
1074
+ // The tier heads the box, wearing the balance's own big number
1075
+ // size. An account without a plan has no headline to show, and
1076
+ // the invisible number keeps the windows lined up with the
1077
+ // balance box's rows instead.
1078
+ ...(planHead !== null
1079
+ ? [planHead]
1080
+ : windows.length > 0
1081
+ ? [h('div', { style: { ...bigNumberStyle, visibility: 'hidden' }, 'aria-hidden': 'true' }, '—')]
1082
+ : []), ...(windows.length > 0
1083
+ ? windows
1084
+ : [rateLimitFallback(t, status?.rateLimit)])), signinBlock(t, signin, refresh), h('div', { style: boxStyle }, h('div', { style: boxTitleStyle }, t('lots')),
1042
1085
  // The upstream already caps the list at the five soonest lots
1043
1086
  // with credits left; the sort only keeps that promise honest.
1044
1087
  ...(lots.length === 0
@@ -1177,6 +1220,17 @@
1177
1220
  };
1178
1221
  const cardTitleStyle = { fontWeight: 600 };
1179
1222
  const bigNumberStyle = { fontSize: '20px', fontWeight: 600, marginBottom: '2px' };
1223
+ // The plan's headline line: the tier wears the balance's own big number
1224
+ // size, so the two boxes' headline lines are the same height, and the
1225
+ // day it ends stays at the row size beside it.
1226
+ const planHeadStyle = {
1227
+ ...bigNumberStyle,
1228
+ display: 'flex',
1229
+ justifyContent: 'space-between',
1230
+ alignItems: 'baseline',
1231
+ gap: '16px',
1232
+ };
1233
+ const planExpiryStyle = { fontSize: '13px', fontWeight: 400, lineHeight: '1.6' };
1180
1234
  const mutedStyle = { opacity: 0.7, fontSize: '12px' };
1181
1235
  const actionButtonStyle = {
1182
1236
  appearance: 'none',
package/package.json CHANGED
@@ -2,20 +2,20 @@
2
2
  "name": "dsh-connect-minimaxcode",
3
3
  "displayName": "DSH Connect MiniMax Code",
4
4
  "description": "把本机已登录的 MiniMax Code 桌面 App 模型接入 DeepSeek Harness:零配置、Anthropic Messages 兼容、动态模型目录,并显示积分余额与到期 — bring MiniMax Code desktop app models into DSH with a dynamic model catalog and a credit balance with expiry details.",
5
- "version": "0.6.7",
5
+ "version": "0.6.9",
6
6
  "author": "JeffTsuiCUG",
7
7
  "contributors": [
8
8
  {
9
9
  "name": "JeffTsuiCUG",
10
10
  "url": "https://github.com/JeffTsuiCUG"
11
11
  },
12
- {
13
- "name": "eghrhegpe",
14
- "url": "https://github.com/eghrhegpe"
12
+ {
13
+ "name": "eghrhegpe",
14
+ "url": "https://github.com/eghrhegpe"
15
15
  },
16
- {
17
- "name": "qzz19970504",
18
- "url": "https://github.com/qzz19970504"
16
+ {
17
+ "name": "qzz19970504",
18
+ "url": "https://github.com/qzz19970504"
19
19
  },
20
20
  {
21
21
  "name": "azsora",
@@ -39,7 +39,7 @@
39
39
  "type": "module",
40
40
  "engines": {
41
41
  "node": "^22.19.0 || >=24.0.0",
42
- "dsh": ">=0.2.1-rc.2 <0.3.0-0"
42
+ "dsh": ">=0.1.6-alpha.2 <0.3.0-0"
43
43
  },
44
44
  "icon": "./icon.svg",
45
45
  "main": "lib/index.js",
@@ -82,13 +82,13 @@
82
82
  ],
83
83
  "peerDependencies": {
84
84
  "@deepseek-ai/cordis": ">=4.0.4 <5.0.0",
85
- "@deepseek-ai/dsh-atomic-write": ">=0.1.7-rc.1 <0.3.0-0",
86
- "@deepseek-ai/dsh-attachment": ">=0.1.7-rc.1 <0.3.0-0",
87
- "@deepseek-ai/dsh-home-paths": ">=0.1.7-rc.1 <0.3.0-0",
88
- "@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.1 <0.3.0-0",
89
- "@deepseek-ai/dsh-llm": ">=0.1.7-rc.1 <0.3.0-0",
90
- "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.7-rc.1 <0.3.0-0",
91
- "@deepseek-ai/dsh-settings": ">=0.1.7-rc.1 <0.3.0-0",
85
+ "@deepseek-ai/dsh-atomic-write": ">=0.1.6-alpha.2 <0.3.0-0",
86
+ "@deepseek-ai/dsh-attachment": ">=0.1.6-alpha.2 <0.3.0-0",
87
+ "@deepseek-ai/dsh-home-paths": ">=0.1.6-alpha.2 <0.3.0-0",
88
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.6-alpha.2 <0.3.0-0",
89
+ "@deepseek-ai/dsh-llm": ">=0.1.6-alpha.2 <0.3.0-0",
90
+ "@deepseek-ai/dsh-llm-pi-ai": ">=0.1.6-alpha.2 <0.3.0-0",
91
+ "@deepseek-ai/dsh-settings": ">=0.1.6-alpha.2 <0.3.0-0",
92
92
  "@deepseek-ai/schemastery": ">=3.18.4 <4.0.0",
93
93
  "@earendil-works/pi-ai": ">=0.85.1"
94
94
  },