@kukonce/dsh-loomy 0.0.0-stage → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,125 @@
1
+ # Changelog
2
+
3
+ ## 0.3.2 — 2026-11-14
4
+
5
+ 版本号发布:README 加了积分胶囊的效果图(docs/pill-composer.png),代码与 0.3.1 完全一致。
6
+
7
+ ## 0.3.1 — 2026-11-13
8
+
9
+ > 发布:npm `@kukonce/dsh-loomy@0.3.1`(`dsh plugin add @kukonce/dsh-loomy`)。
10
+
11
+ ### Fixed — the pill now tracks what chat actually spends
12
+
13
+ The pill read the grant ledger, which freezes the daily pot at claim time (5000 at
14
+ the 00:00-style boot claim) and never moves while the user chats — so after a
15
+ morning of conversations it still said 5000 when the real remaining was, say, 4521.
16
+ The settings card was never wrong because its 刷新积分 button reads the server live.
17
+
18
+ - The gateway's final accounting frame carries `points_consumed`; the adapter now
19
+ books it into a per-day, per-account spend file (`usage.json`, 0600) the moment
20
+ the frame arrives — including a mid-stream abort, since the gateway bills what
21
+ it already sent. `/state` projects the claim's balance minus today's recorded
22
+ spend, clamped so a stale spend file can never show a negative pot.
23
+ - Every live server read is now a sync point, not just a display: the panel's
24
+ 刷新积分 button, `/loomy status` and `/loomy credits` re-anchor the ledger to what
25
+ the server answered and forget the recorded spend (which that answer already
26
+ includes). Each claim — boot, midnight, manual — does the same, so any drift
27
+ (points spent outside this process) self-corrects within a day.
28
+ - The anchor records the local day key, matching `grantedToday` and the projection's
29
+ day match; the server's `dailyCycleDate` can roll minutes off local midnight.
30
+
31
+ ### Changed — the pill now says 每日赠送积分
32
+
33
+ The pill's label (and the popover title) used the generic 「积分」; it now uses the
34
+ official client's copy, 「每日赠送积分」 ("Daily gift points" in English), which is
35
+ what the number actually is.
36
+
37
+ ## 0.3.0 — 2026-11-13
38
+
39
+ ### Added — a points pill beside the model picker
40
+
41
+ When the conversation is pointed at a Loomy model, a small pill now sits to the right
42
+ of the composer (the same spot `dsh-connect-modelscope-token-plan` uses): a traffic-light
43
+ dot, the daily balance left over all signed-in accounts, and whether today's 5000 has
44
+ been claimed. Clicking it opens a per-account breakdown — daily / quota and available
45
+ balance for each account — plus the read time.
46
+
47
+ - The pill mounts only while the selected model's provider is `@loomy/dsh-loomy/*`;
48
+ switching to any other provider unmounts it, and an unmounted pill makes no requests.
49
+ - It polls the panel's `/state` every 30 seconds. That endpoint reads local ledgers only
50
+ — no upstream call — so the pill is free; a live balance refresh still lives on the
51
+ settings card's 刷新积分 button. After three silent cycles the pill marks its numbers
52
+ stale instead of silently keeping them.
53
+ - Read failures keep the last reading on screen (marked stale) rather than blanking the
54
+ pill; "—" means nothing has been read yet, which is a different fact from 0.
55
+ - The pill carries its own stylesheet and `--lo-*` custom properties: the settings card's
56
+ `<style>` does not reach the conversation screen.
57
+
58
+ ## 0.2.0 — 2026-11-13
59
+
60
+ ### Fixed — the desktop import no longer reaches across local users
61
+
62
+ On Windows the Loomy desktop client keeps its session in
63
+ `C:\Users\Public\Loomy\<sha256(username)[:12]>\userData\auth-session.json`. That is
64
+ inside `C:\Users\Public`, a directory every local account shares, and 0.1.0 walked all
65
+ of `C:\Users\Public\Loomy\*` and adopted the most recently updated session. On a machine
66
+ with more than one user, that can be somebody else's account.
67
+
68
+ - Only this user's own directory is read: the hash derived from the current username, plus
69
+ the per-user Electron default under `%APPDATA%`. Nothing enumerates the shared root.
70
+ - A directory may still be named explicitly (`/loomy import <directory>`); that is a
71
+ decision the user makes, not a guess the plugin makes.
72
+ - The import no longer runs by itself on first start. `/loomy import` and the panel
73
+ button do the same work on request; a fresh install with no account just says how to
74
+ sign in. (Silent adoption of a desktop login was the other half of the same problem.)
75
+ - `docs/verify/live-check.mjs` probes the same narrowed locations, so a verification run
76
+ cannot quietly depend on a session the plugin itself would refuse to read.
77
+
78
+ The daily credit claim is unchanged: it still runs at startup and at local midnight, and
79
+ only ever for accounts the user signed in.
80
+
81
+ ## 0.1.0 — 2026-11-11
82
+
83
+ First release.
84
+
85
+ ### Accounts
86
+
87
+ - SMS login through iFlytek CAccount (HMAC-SHA1 signed requests).
88
+ - Adopt the login the Loomy desktop app already has, by reading its `auth-session.json`
89
+ (Windows relocates it to `C:\Users\Public\Loomy\<sha256(username)[:12]>\userData\`, so
90
+ the directory is discovered rather than guessed).
91
+ - Multiple accounts; each becomes its own provider route `@loomy/dsh-loomy/<id>`.
92
+ - A dead session is recorded and surfaced as "登录已失效" instead of failing silently.
93
+
94
+ ### Credits
95
+
96
+ - Reads the two pots separately: 每日赠送 (daily, 5000/day) and 长期 (permanent).
97
+ - Renews the daily pot through `POST /api/v1/points/first-login` — the call the official
98
+ client makes on login, which resets the per-`dailyCycleDate` quota idempotently. Runs at
99
+ startup and at local midnight.
100
+ - Onboarding tasks (a separate one-time 10,000-point pool) are listed but **never** claimed
101
+ automatically, because that is a resource the user may want to spend deliberately.
102
+
103
+ ### Models
104
+
105
+ - Live per-account catalog from `GET /api/v1/models`.
106
+ - Generation models (`type: image`) are listed but excluded from the chat picker: they share
107
+ the chat URL and answer 404 to it.
108
+ - Reasoning levels come from the catalog, falling back to the standard five when a model
109
+ declares none (`auto` does) — an incomplete set makes the harness reject the model
110
+ outright with `UNSUPPORTED_REASONING_EFFORT`.
111
+
112
+ ### Streaming
113
+
114
+ - OpenAI-compatible SSE with `content`, `reasoning_content` and `tool_calls`.
115
+ - Usage accounting from `stream_options.include_usage`.
116
+ - Tool-call arguments are buffered and only announced once a tool name has arrived, so a
117
+ truncated call never reaches the transcript as a parse error.
118
+
119
+ ### Known behaviour
120
+
121
+ - History reasoning is **not** replayed except for the newest assistant turn. Replaying it
122
+ made long sessions slow rather than coherent: a 100-turn conversation carried a 245 kB
123
+ body and took 61 s per turn, against 8 s with the current rule.
124
+ - Intermittent `502 Bad Gateway` from `loomyad.xunfei.cn` under load is upstream and is
125
+ reported as a retryable `SERVER` failure.
package/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-loomy contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ Note: the icon.png and icon-mask.png placeholder artwork is generated work and
24
+ carries this same license. The Loomy and iFlytek names and logos are the property
25
+ of their respective owners; this plugin is an unofficial integration and is not
26
+ affiliated with or endorsed by iFlytek.
package/README.md CHANGED
@@ -1,3 +1,224 @@
1
- # Temporary Holding Version
1
+ # dsh-loomy
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 把 [Loomy](https://loomy.xunfei.cn)(讯飞 Loomy 桌面端)的账号接入 DeepSeek Harness,作为
4
+ 模型提供方使用。
5
+
6
+ 一个 Loomy 账号在 DSH 里长成一条独立的模型路由:
7
+
8
+ ```
9
+ @loomy/dsh-loomy/loomy ← 第一个账号
10
+ @loomy/dsh-loomy/<id> ← 每个账号一条
11
+ ```
12
+
13
+ ![选择 Loomy 模型时,输入框右侧会显示积分胶囊](docs/pill-composer.png)
14
+
15
+ > 选中 Loomy 模型后,输入框右下角出现积分胶囊:绿点表示当日额度还有余量,
16
+ > 显示「每日赠送积分 · 剩余 / 总额 · 可用合计 · 今日已领」,随对话实时扣减,
17
+ > 点击可看每个账号的明细。
18
+
19
+ ## 快速安装
20
+
21
+ ```sh
22
+ dsh plugin --profile desktop add @kukonce/dsh-loomy
23
+ ```
24
+
25
+ (npm 安装;也可以用 `dsh plugin --profile desktop add github:kukonce/dsh-loomy` 走 GitHub 源码。)
26
+
27
+ 装完重启 DSH。第一次启动会自动尝试导入本机 Loomy 桌面端的登录态;没有的话用
28
+ `/loomy login <手机号>` 或设置面板里的「Loomy」分区登录。
29
+
30
+ 登录用手机短信验证码,或者直接复用本机 Loomy 桌面端已有的登录态 —— **不需要 API Key**,
31
+ Loomy 本身就没有 API Key 这种东西。账号的「每日赠送 5000 积分」由插件在启动时和每天
32
+ 本地零点自动刷新。
33
+
34
+ ---
35
+
36
+ ## 装到 DSH 里
37
+
38
+ 插件放在 `~/.dsh/local-plugins/dsh-loomy`,然后在某个 profile 里登记两处:
39
+
40
+ **1. `~/.dsh/profiles/<profile>/package.json`** —— 加依赖和 bundle:
41
+
42
+ ```jsonc
43
+ {
44
+ "dependencies": {
45
+ "@kukonce/dsh-loomy": "file:C:/Users/<you>/.dsh/local-plugins/dsh-loomy"
46
+ },
47
+ "dsh": {
48
+ "profile": {
49
+ "bundles": [
50
+ // …原有条目…
51
+ "@kukonce/dsh-loomy"
52
+ ]
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ **2. 装依赖**(用 DSH 自带的 pnpm,在 profile 目录里跑):
59
+
60
+ ```powershell
61
+ cd ~/.dsh/profiles/<profile>
62
+ node <dsh>/dsh-runtimes/dsh-primary-runtime/dependencies/pnpm/bin/pnpm.mjs install --ignore-scripts
63
+ ```
64
+
65
+ 然后**重启 DSH**。新增的 bundle 是在启动时加载的,热重载不会把一条新的 bundle 拉进来。
66
+
67
+ 装好之后:设置里多一个「Loomy」分区,命令里多一个 `/loomy`。
68
+
69
+ ---
70
+
71
+ ## 登录
72
+
73
+ 插件**不会**自己去导入桌面端登录 —— 导入是一个决定,不是安装的副作用。
74
+
75
+ 原因很实在:Windows 上 Loomy 把登录态放在 `C:\Users\Public\Loomy\<sha256(用户名)[:12]>\userData\`,
76
+ 而 `C:\Users\Public` 是全机共享目录。如果插件去扫 `C:\Users\Public\Loomy\*`,它拿到的「最近更新的
77
+ 那份」很可能就是这台机器上**别人**的登录态。所以导入只会发生在你明确要求的时候,并且默认只读当前
78
+ 用户自己那个哈希目录。
79
+
80
+ 三种方式,等价:
81
+
82
+ ```
83
+ /loomy import [目录] 导入 Loomy 桌面端已有的登录(只读本用户目录)
84
+ /loomy login 13800138000 发送短信验证码
85
+ /loomy login code 123456 用验证码换 session
86
+ ```
87
+
88
+ 也可以在「设置 → Loomy」里点。
89
+
90
+ 登录态存在 `~/.dsh/dsh-loomy/accounts.json`,目录权限 0700、文件 0600。**它是明文的** ——
91
+ Loomy 的 session 本身就是一个不加密的 bearer 串,没有任何一层能把它变成不可读;能读你用户目录
92
+ 的进程就能读它。介意的话退出登录即可(`/loomy logout`)。
93
+
94
+ ---
95
+
96
+ ## 每日 5000 积分是怎么回事
97
+
98
+ Loomy 的积分分两个池子:
99
+
100
+ | 池子 | 额度 | 来源 |
101
+ |---|---|---|
102
+ | 每日赠送(daily) | 5000 / 天 | 每日重置,优先消耗 |
103
+ | 长期 / 永久(permanent) | 不固定 | 注册奖励、新手任务、兑换码 |
104
+
105
+ **让每日池回到 5000 的是 `POST /api/v1/points/first-login`**,不是「读一下余额」。它名字叫
106
+ "首次登录",实际行为是「开始今天的计费周期」:同一个 `dailyCycleDate` 内重复调用是幂等的
107
+ (返回 `alreadyProcessed: true`),所以随便调都是免费的。
108
+
109
+ 插件在三个时机调它:
110
+
111
+ 1. **启动** 之后约 12 秒(等其它插件先安静下来);
112
+ 2. **每天本地零点**,`msUntilNextMidnight()` 算出来的那一刻;
113
+ 3. **面板刷新**时 —— 顺带当余额读取用,因为它是唯一一个一次返回四个数字加今日消耗的接口。
114
+
115
+ 本地留一本账 `daily-grant.json` 记住「今天领过没有」,避免重启后又问一遍;面板上的按钮强制重来。
116
+
117
+ > **注意**:这里没有任何「假装已领取」的逻辑。能不能真的拿到 5000 由服务端判定,插件只是把
118
+ > 官方客户端的那个调用照做了一遍。
119
+
120
+ ---
121
+
122
+ ## 模型
123
+
124
+ 目录是**按账号实时拉的**(`GET /api/v1/models`),不是写死的表 —— 你的账号能用哪些模型,
125
+ 选择器里就只有哪些。这台机器上当前是 9 个(见 [docs/models.md](docs/models.md))。
126
+
127
+ 目录里有 3 个模型(`Hy-Image-3.5-preview`、`doubao-seedream-5-lite`、`qwen-image-3.0-pro`)的
128
+ `type` 是 `image` —— 它们的地址和对话接口相同,但对对话请求一律返回 **404「该模型暂未开放」**
129
+ (2026-11-11 实测)。所以**模型选择器里不会出现它们**:能被选中的只有真正能回答对话的 6 个。
130
+ `/loomy models` 会把它们列出来并标 `[不支持对话]`,免得你以为是插件漏了。
131
+
132
+ ```
133
+ /loomy models 列出当前账号的模型、能力和上下文长度
134
+ ```
135
+
136
+ ---
137
+
138
+ ## 命令
139
+
140
+ ```
141
+ /loomy status 账号、积分、模型数量一览
142
+ /loomy login <手机号> 发验证码
143
+ /loomy login code <验证码> 验证码换 session
144
+ /loomy import [目录] 导入桌面端登录(只读本用户目录;目录可选)
145
+ /loomy logout [id] 退出(账号 id 省略时取第一个)
146
+ /loomy models [id] 当前账号的模型目录
147
+ /loomy credits [id] 积分明细(每日/长期/今日消耗)
148
+ /loomy claim [id] 立刻领取今日赠送额度
149
+ /loomy tasks [id] 新手任务进度(一次性 10000 积分池)
150
+ /loomy claimtask <任务名> 完成一个新手任务并领取
151
+ ```
152
+
153
+ 新手任务奖励**不会自动领** —— 那是每个账号只有一份的长期积分,什么时候花应该由你决定。
154
+
155
+ ---
156
+
157
+ ## 为什么这样做
158
+
159
+ ### 为什么要发两个认证头
160
+
161
+ 同一个 host 上的两组接口读不同的头,而且失败时返回的是 **HTTP 200 + 错误码**,不是 401:
162
+
163
+ - `/api/v1/models`、`/points/*` 这些读 `token: <session>`;
164
+ - `/api/v1/chat/completions` 读 `Authorization: Bearer <session>`(方案名大小写敏感)。
165
+
166
+ 所以插件两边都发。注意错误信息 `缺少 token` 容易误导 —— 它说的是「缺 Authorization」,
167
+ 不是「缺 token」。完整矩阵见 [docs/protocol.md](docs/protocol.md)。
168
+
169
+ ### 为什么要垫高 max_tokens
170
+
171
+ 这些是推理模型,**思考过程和正文共用同一个 `max_tokens` 预算**。实测给 200 tokens 时,
172
+ 预算全被思考吃掉,正文为空、`finish_reason: length` —— 用户看到的是「模型没回答」,
173
+ 实际是被截断了。所以适配器给 `max_tokens` 兜了个 1024 的下限。
174
+
175
+ ### 为什么每个账号一条路由
176
+
177
+ 模型目录和积分余额都是按账号算的,而模型选择器是按 provider 分组的。两个账号挤一条路由,
178
+ 在界面上就变成「一个账号,模型列表一直在变」。
179
+
180
+ ---
181
+
182
+ ## 已知限制和风险
183
+
184
+ - **这是非官方集成。** 没有公开 API,所有接口都是照官方桌面客户端的行为逆出来的,随时可能变。
185
+ - **账号风险和合规。** 用插件调用等同于用 Loomy 客户端调用,但自动化流量可能触发风控。请自己判断
186
+ 是否符合你账号的协议条款。
187
+ - **会话认证。** session 是 bearer 串,没有 refresh 接口。失效了只能重新登录(错误码 `100002`)。
188
+ 插件会把它标成「登录已失效」并提示重登,不会假装还能用。
189
+ - **生图模型不提供。** 目录里有 3 个 `type: image` 的生图模型,它们不接受对话请求,插件既不会
190
+ 在选择器里提供它们,也没有实现生图调用 —— 这里只做对话。
191
+ - **点数由服务端计费。** 插件不预测也不拦截消耗;余额以服务端返回为准。
192
+ - **单机单用户。** 账号表、日志、缓存都在 `~/.dsh/dsh-loomy/` 下,没有多用户隔离。
193
+
194
+ ---
195
+
196
+ ## 文件都在哪
197
+
198
+ ```
199
+ ~/.dsh/dsh-loomy/
200
+ accounts.json 账号表(含 session,明文,0600)
201
+ catalog.json 模型目录缓存(目录接口挂了时兜底)
202
+ daily-grant.json 每日领取账本
203
+ expired.json 哪些账号的 session 被判失效
204
+ ```
205
+
206
+ 整个目录删掉 = 插件回到初始状态。
207
+
208
+ ---
209
+
210
+ ## 协议细节
211
+
212
+ - [docs/protocol.md](docs/protocol.md) —— 主机、认证头矩阵、信封格式、SSE 事件、错误表、未知项
213
+ - [docs/models.md](docs/models.md) —— 模型目录快照
214
+ - [docs/verify/live-check.mjs](docs/verify/live-check.mjs) —— 可重跑的协议自检(47 项)
215
+
216
+ ```powershell
217
+ node ~/.dsh/local-plugins/dsh-loomy/docs/verify/live-check.mjs
218
+ ```
219
+
220
+ ---
221
+
222
+ ## 许可证
223
+
224
+ MIT。图标是占位图。Loomy / 讯飞 的名称和标识归各自所有者,本项目与讯飞无隶属关系。