cortico-world-workbuddy 0.1.5 → 0.1.7
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 +45 -0
- package/README.md +46 -5
- package/describe.json +11 -6
- package/dist/webui.js +283 -11
- package/package.json +1 -1
- package/scripts/verify-ui.mjs +32 -0
- package/src/ENV_PROMPT.md +3 -0
- package/src/config.js +13 -0
- package/src/promo.js +321 -0
- package/src/safety.js +168 -0
- package/src/upstream.js +116 -8
- package/src/usage.js +279 -0
- package/src/webui/client.js +318 -8
- package/src/world.js +358 -28
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## 0.1.7
|
|
4
|
+
|
|
5
|
+
按 WorkBuddy 官方对话给出的合规口径,给桥接加「安全清单」闸门(仍只在本机,未推送 / 未发布)。
|
|
6
|
+
|
|
7
|
+
- **新增 `src/safety.js`**:把 WorkBuddy 的筛选原则固化成 `SAFE_MODEL_IDS`(21 个公开对话模型)、
|
|
8
|
+
分组表 `SAFE_MODEL_GROUPS`、谨慎档 `CAUTION_MODEL_IDS`(hy3 系列与 preview)与 `classifyModel()`。
|
|
9
|
+
`classifyModel` 把目录里每个模型判成 `safe / caution / alias / disabled / media / code / retired /
|
|
10
|
+
format / experimental / unknown` 十档,附中文理由;`allow` 只对前两档为真。
|
|
11
|
+
- **闸门默认开启**(`worlds.workbuddy.safeOnly = true`):`wb_chat`、`wb_select_model` 与面板的
|
|
12
|
+
「使用」按钮都会拒绝安全清单外的模型,并给出「判定档位 + 理由 + 如何解除」的说明;
|
|
13
|
+
关掉这个配置才能调用 disabled / 内部别名 / 预览等模型,风险自负。
|
|
14
|
+
- **`wb_list_models` 默认只列安全清单**(按 DeepSeek / GLM / 混元 / Kimi / 其他分组,带此刻倍率),
|
|
15
|
+
传 `all=true` 才追加其余模型及其排除理由。
|
|
16
|
+
- **被排除的典型项**:`wb-lite`(目录 `disabled:true`)、`auto` / `fast-model` / `balanced-model` /
|
|
17
|
+
`deep-model`(内部路由别名)、`hunyuan-image-alpha*` / `kling-video-v3` / `minimax-video-h3`(媒体)、
|
|
18
|
+
`codewise-*`(代码补全)、`hy4-preview*`(实验预览)、19 个已下架(11102)、`glm-5.0-turbo`(11150)。
|
|
19
|
+
- **模型目录面板**新增「合规」列(安全清单 / 安全清单·谨慎 / 各排除档位,悬停看理由),
|
|
20
|
+
默认只显示安全清单,勾选「显示全部模型(含不建议项)」才展开;不安全项的选择按钮置灰。
|
|
21
|
+
- `envPromptVars()` 增加 `workbuddy.safeOnly`;`ENV_PROMPT.md` 增加一条:闸门开着时不要绕过拒绝,
|
|
22
|
+
应该告诉用户原因并改推荐安全清单里的模型。
|
|
23
|
+
- `describe.json` 的 `envPrompt` 与 `wb_list_models` 描述同步(多了 `all` 参数)。
|
|
24
|
+
|
|
25
|
+
## 0.1.6
|
|
26
|
+
|
|
27
|
+
新增「用量」面板与真实的时段优惠计算(只在本机验证,未发布)。
|
|
28
|
+
|
|
29
|
+
- **优惠来自 `/v3/config`**:改用应用身份 UA(`WorkBuddy/5.8.3 … CLI/2.161.0`)拉取目录,
|
|
30
|
+
同时拿到 `modelPromotions` / `modelTiers`。用 CLI 身份只能读到精简后的目录,
|
|
31
|
+
促销与档位字段整个缺失——这是之前倍率「永远显示原价」的根因。
|
|
32
|
+
- **新增 `src/promo.js`**:解析 `schedule`(`timezone` / `validFrom` / `validUntil` / 跨零点的 `daily`
|
|
33
|
+
时段、`dailyUnusable`)、`discount`(`discountedCredits` 就是生效倍率本身,不是相对系数)、
|
|
34
|
+
`badge`(含 `display:"activeOnly"`)与 `priority`;按优先级选出此刻生效的那条。
|
|
35
|
+
`priceViewOf(model, now)` 给出 `{base, effective, promoId, badge, windowText, hoverText, isFree}`。
|
|
36
|
+
- **新增「用量」面板**(`world:workbuddy` 第 4 个页签):区间概览(今天 / 近 7 天 / 近 30 天)、
|
|
37
|
+
按模型聚合、积分包明细、限时优惠清单、最近调用明细;带「刷新余额」与「清空本地账本」。
|
|
38
|
+
- **新增 `src/usage.js`**:每次 `wb_chat` 往 `<dataDir>/workbuddy-usage.jsonl` 记一行
|
|
39
|
+
(tokens 来自 SSE 的 `usage`,积分来自 `usage.credit` 即上游真实扣费,倍率记当时生效值),
|
|
40
|
+
读的时候裁掉 90 天以前的行;余额快照另存 `workbuddy-balance.json`。
|
|
41
|
+
- `fetchCredits` 现在返回 `{total, used, packages[]}`(逐包剩余 / 已用 / 周期),
|
|
42
|
+
`wb_credits` 一并打印「本周期已用」。
|
|
43
|
+
- `wb_chat` 回执补充「用量:输入 N tok / 输出 N tok / 缓存命中 N tok / 共 N tok / 本次消耗积分 N(N ms)」。
|
|
44
|
+
- `wb_list_models` 标注此刻生效倍率(含原价对比、时段)与优惠徽标;`wb_select_model` 提示此刻倍率。
|
|
45
|
+
- 环境提示词新增 `{{workbuddy.todayCredit}}` / `{{workbuddy.todayTokens}}`,并在倍率旁说明已含生效中的限时优惠。
|
|
46
|
+
- 详情页徽标增加「今日积分」。
|
|
47
|
+
|
|
3
48
|
## 0.1.5
|
|
4
49
|
|
|
5
50
|
发布前收尾:停用 / 卸载彻底不留痕,并把元数据指向真实仓库。
|
package/README.md
CHANGED
|
@@ -139,18 +139,58 @@ Coopanion (Coo)
|
|
|
139
139
|
|
|
140
140
|
| 面板 | 内容 |
|
|
141
141
|
|---|---|
|
|
142
|
-
| 模型目录 | 刷新目录;逐模型一行:名称/id
|
|
142
|
+
| 模型目录 | 刷新目录;逐模型一行:名称/id、**合规档位**、**此刻倍率**(含限时折扣时显示 `x0.5 ← x0.79 23:00–07:50`,x0 标「免费」)、上下文、推理档、「选用」按钮;默认只列安全清单,勾选「显示全部模型(含不建议项)」才展开其余项 |
|
|
143
143
|
| 账号 | 登录态来源(本机发现 / 手动)、掩码 token、积分余额查询、重新检测、手动 token 存取 |
|
|
144
144
|
| 是否启用 WorkBuddy | 「中转站优先」开关 + 主进程状态,与开始页按钮同步 |
|
|
145
|
+
| 用量 | 区间概览(今天 / 近 7 天 / 近 30 天)、按模型聚合、积分包明细、限时优惠清单、最近调用;带「刷新余额」「清空本地账本」 |
|
|
146
|
+
|
|
147
|
+
### 安全清单(合规闸门)
|
|
148
|
+
|
|
149
|
+
WorkBuddy 官方对话给出的口径是:只调用「官方公开目录里、当前可正常对话」的模型,
|
|
150
|
+
`disabled`、内部路由别名、媒体/代码补全与状态不明的模型都不要经外部软件调用,否则有风控/封号风险。
|
|
151
|
+
这里把它落成代码:
|
|
152
|
+
|
|
153
|
+
- 白名单 21 个(`src/safety.js` 的 `SAFE_MODEL_IDS`,按 DeepSeek / GLM / 混元 / Kimi / 其他分组):
|
|
154
|
+
`deepseek-v4.1-flash`、`deepseek-v4-flash`、`deepseek-v4-pro`、`deepseek-v3-2-volc`、
|
|
155
|
+
`glm-5.3-flash`、`glm-5.3`、`glm-5.2`、`hy3`、`hy3-b`、`hy3-c`、`hy3-x`、`hunyuan-chat`、
|
|
156
|
+
`kimi-k2.5`、`kimi-k2.8-preview`、`kimi-k3-1`、`kimi-k3.1-pro`、`minimax-m2.7`、
|
|
157
|
+
`minimax-m3.1-flash`、`mimo-v2.6-pro`、`space-bunny`、`step-5-preview`。
|
|
158
|
+
其中 `hy3*` 与 `*preview` 标为「谨慎」(免费试用/预览,别做高频自动化)。
|
|
159
|
+
- 其余全部**拒绝**:`wb-lite`(目录 `disabled:true`)、`auto` / `fast-model` / `balanced-model` /
|
|
160
|
+
`deep-model`(内部路由别名)、4 个媒体模型、`codewise-*`、`hy4-preview*`、19 个已下架的、
|
|
161
|
+
`glm-5.0-turbo`(11150 请求格式不符)。
|
|
162
|
+
- 闸门是 `worlds.workbuddy.safeOnly`(默认 `true`)。开着时 `wb_chat` / `wb_select_model` / 面板「选用」
|
|
163
|
+
三处一致拒绝,并说明「判定档位 + 理由 + 怎么解除」;关掉才放行,属于明示的风险自负。
|
|
164
|
+
- 清单是按 2026-11 的官方目录定的,官方加新模型不会被自动信任——这正是「未列入安全清单」这一档的意义。
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
### 倍率与限时优惠怎么算
|
|
168
|
+
|
|
169
|
+
- 目录来自 `GET /v3/config`(应用身份 UA):一次同时拿到 `models`、`modelPromotions`、`modelTiers`。
|
|
170
|
+
用 CLI 身份只能读到精简目录,促销字段会整段缺失——这是「倍率永远显示原价」的根因。
|
|
171
|
+
- 一条促销的生效条件是 `schedule`:`timezone`(默认按条目给的时区算)、`validFrom`/`validUntil`、
|
|
172
|
+
`daily` 时段(支持 `23:00`→`07:50` 这类跨零点窗口)。命中的条目按 `priority` 取最高;平手时促销优先于档位。
|
|
173
|
+
- `discount.discountedCredits`(如 `0.50x`)**就是生效倍率本身**,不是相对原价的系数。
|
|
174
|
+
`displayMode: "replace"` 直接替换显示,`"strikethrough"` 表示「划线原价 + 新价」并排。
|
|
175
|
+
- `badge.display: "activeOnly"` 的徽标只在生效时段出现(如「限时免费」试用)。
|
|
176
|
+
|
|
177
|
+
### 用量统计口径
|
|
178
|
+
|
|
179
|
+
- 每次 `wb_chat` 记一行到 `%APPDATA%\..\Coopanion\data\home\companion\data\workbuddy-usage.jsonl`
|
|
180
|
+
(实机路径;实际就是 `ctx.dataDir`):`ts/model/region/ok/promptTokens/completionTokens/totalTokens/cachedTokens/reasoningTokens/credit/multiplier/promoId/source/durationMs`。
|
|
181
|
+
- `credit` 取 SSE 最后一段的 `usage.credit`,即**上游真实扣费**;倍率 0.11 的模型单次请求不足 1 分时上游就是 0,这里照抄不折算。
|
|
182
|
+
- 积分包余额另存 `workbuddy-balance.json`(`POST /v2/billing/meter/get-user-resource` 的 `*Precise` 字段求和),
|
|
183
|
+
面板上的「刷新余额」会重取一次。
|
|
184
|
+
- 账本只保留 90 天,读的时候顺带裁掉;「清空本地账本」会把它清成空文件,服务端数据不受影响。
|
|
145
185
|
|
|
146
186
|
## 工具(给 Coo / 模型调用)
|
|
147
187
|
|
|
148
188
|
| 工具 | 作用 |
|
|
149
189
|
|---|---|
|
|
150
|
-
| `wb_list_models` |
|
|
151
|
-
| `wb_select_model` | 按 id 选择 `wb_chat`
|
|
152
|
-
| `wb_chat` |
|
|
153
|
-
| `wb_credits` |
|
|
190
|
+
| `wb_list_models` | 列出**安全清单内**的模型及此刻倍率(含限时优惠与时段),并单列优惠清单;传 `all=true` 才带上被排除项与理由(只读) |
|
|
191
|
+
| `wb_select_model` | 按 id 选择 `wb_chat` 的默认模型,持久化;提示此刻倍率;安全清单外会被拒 |
|
|
192
|
+
| `wb_chat` | 用选中模型回答一个单轮问题(**真实消耗积分**,需主进程在跑);回执附输入/输出/缓存 token 与本次积分;安全清单外会被拒 |
|
|
193
|
+
| `wb_credits` | 只读查询积分余额与本周期已用(需主进程在跑) |
|
|
154
194
|
|
|
155
195
|
## 配置(`worlds.workbuddy.*`)
|
|
156
196
|
|
|
@@ -164,6 +204,7 @@ Coopanion (Coo)
|
|
|
164
204
|
| `envPrompt` | `true` | 是否把桥接状态写进 system 前缀(关掉后「中转站优先」也不生效) |
|
|
165
205
|
| `prefer` | `false` | 「中转站优先」,由开始页按钮 / 详情页开关写入 |
|
|
166
206
|
| `hostInject` | `true` | 是否注入「开始」页按钮(关掉后不碰宿主文件,只用详情页开关) |
|
|
207
|
+
| `safeOnly` | `true` | 只允许调用安全清单内的公开对话模型;关掉会放行 disabled / 别名 / 预览等项,风险自负 |
|
|
167
208
|
|
|
168
209
|
## 开发与校验
|
|
169
210
|
|
package/describe.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"tools": [
|
|
3
3
|
{
|
|
4
4
|
"name": "wb_list_models",
|
|
5
|
-
"description": "List the WorkBuddy models
|
|
5
|
+
"description": "List the WorkBuddy chat models that are safe to call (official public catalog only), each with its credit multiplier (e.g. x0.79, already including time-limited discounts), context window and reasoning efforts. Read-only; costs nothing. Pass all=true to also list models that are disabled, internal routing aliases, media/code models or delisted ones — those are not callable through this bridge while worlds.workbuddy.safeOnly is on.",
|
|
6
6
|
"summary": {
|
|
7
|
-
"zh": "
|
|
8
|
-
"en": "Lists WorkBuddy models
|
|
7
|
+
"zh": "列出 WorkBuddy 公开对话模型(安全清单)及各自的积分倍率、上下文与推理档;all=true 时附上被排除的项与原因。",
|
|
8
|
+
"en": "Lists the safe WorkBuddy chat models with multiplier, context and reasoning efforts; all=true also lists excluded ones with reasons."
|
|
9
9
|
},
|
|
10
10
|
"tags": [
|
|
11
11
|
"read"
|
|
@@ -13,12 +13,17 @@
|
|
|
13
13
|
"parameters": {
|
|
14
14
|
"type": "object",
|
|
15
15
|
"additionalProperties": false,
|
|
16
|
-
"properties": {
|
|
16
|
+
"properties": {
|
|
17
|
+
"all": {
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"description": "Also list models outside the safe list (shown with their risk reason)."
|
|
20
|
+
}
|
|
21
|
+
}
|
|
17
22
|
}
|
|
18
23
|
},
|
|
19
24
|
{
|
|
20
25
|
"name": "wb_select_model",
|
|
21
|
-
"description": "Select which WorkBuddy model wb_chat should use by default. The id must come from wb_list_models. Persists across restarts.",
|
|
26
|
+
"description": "Select which WorkBuddy model wb_chat should use by default. The id must come from wb_list_models. Refused when worlds.workbuddy.safeOnly is on and the model is outside the safe list. Persists across restarts.",
|
|
22
27
|
"summary": {
|
|
23
28
|
"zh": "选择 wb_chat 默认调用的 WorkBuddy 模型。",
|
|
24
29
|
"en": "Selects the default WorkBuddy model for wb_chat."
|
|
@@ -98,5 +103,5 @@
|
|
|
98
103
|
}
|
|
99
104
|
}
|
|
100
105
|
],
|
|
101
|
-
"envPrompt": "## WorkBuddy bridge\n\nThis machine has a WorkBuddy desktop sign-in bridged in (region: {{workbuddy.region}}; signed in: {{workbuddy.authed}}).\nThe model catalog holds {{workbuddy.catalogSize}} models. The currently selected model is `{{workbuddy.model}}` with default credit multiplier {{workbuddy.multiplier}}.\nWorkBuddy desktop main process: {{workbuddy.process}}. Relay-first switch: {{workbuddy.priority}}.\n\n- Use `wb_list_models` to see every model with its multiplier before choosing; prefer low-multiplier models for casual tasks.\n- Use `wb_select_model` when the user asks to switch the WorkBuddy model.\n- `wb_chat` only works while the WorkBuddy desktop main process is running; otherwise it returns「未检测到 WorkBuddy 主进程」. Every call spends the user's own credits at that model's multiplier.\n- `wb_credits` is read-only and costs nothing (also requires the desktop process).\n\n### Relay first: {{workbuddy.priority}}\n\n- Switch `on` **and** process `running`: every user turn that expects a conversational answer (question, chat, writing, reasoning, rewrite, translation, summary) must be answered by a WorkBuddy model first — call `wb_chat` (omit `model` to use `{{workbuddy.model}}`), then speak the returned answer back in your own voice and name the model that answered. Only fall back to your own model if the call fails.\n- Switch `off` (default): do not call `wb_chat`; answer with your own model. If the user explicitly asks for WorkBuddy, `wb_chat` is still allowed — the desktop process must be running.\n- Tool errors and empty answers are not a reason to flip the switch; just say what happened.\n"
|
|
106
|
+
"envPrompt": "## WorkBuddy bridge\n\nThis machine has a WorkBuddy desktop sign-in bridged in (region: {{workbuddy.region}}; signed in: {{workbuddy.authed}}).\nThe model catalog holds {{workbuddy.catalogSize}} models. The currently selected model is `{{workbuddy.model}}` with default credit multiplier {{workbuddy.multiplier}}.\nWorkBuddy desktop main process: {{workbuddy.process}}. Relay-first switch: {{workbuddy.priority}}.\nSpent today through the bridge: {{workbuddy.todayCredit}} credits / {{workbuddy.todayTokens}} tokens (locally recorded).\nThe multiplier above already includes any time-limited discount or free window that is active right now; `wb_list_models` also marks which offers are active and their hours.\n\n- Use `wb_list_models` to see every model with its multiplier before choosing; prefer low-multiplier models for casual tasks.\n- Safe-list gate: {{workbuddy.safeOnly}}. When it is `on`, only the models WorkBuddy publishes as public chat models are callable — `disabled` entries, internal routing aliases (`auto`, `fast-model`, `balanced-model`, `deep-model`), media/code models and delisted ones are refused. Do not try to work around a refusal; tell the user why and offer a safe model instead. `wb_list_models` with `all=true` shows the refused ones with reasons.\n- Use `wb_select_model` when the user asks to switch the WorkBuddy model.\n- `wb_chat` only works while the WorkBuddy desktop main process is running; otherwise it returns「未检测到 WorkBuddy 主进程」. Every call spends the user's own credits at that model's multiplier.\n- `wb_credits` is read-only and costs nothing (also requires the desktop process).\n\n### Relay first: {{workbuddy.priority}}\n\n- Switch `on` **and** process `running`: every user turn that expects a conversational answer (question, chat, writing, reasoning, rewrite, translation, summary) must be answered by a WorkBuddy model first — call `wb_chat` (omit `model` to use `{{workbuddy.model}}`), then speak the returned answer back in your own voice and name the model that answered. Only fall back to your own model if the call fails.\n- Switch `off` (default): do not call `wb_chat`; answer with your own model. If the user explicitly asks for WorkBuddy, `wb_chat` is still allowed — the desktop process must be running.\n- Tool errors and empty answers are not a reason to flip the switch; just say what happened.\n"
|
|
102
107
|
}
|
package/dist/webui.js
CHANGED
|
@@ -48,7 +48,43 @@ var TEXT = {
|
|
|
48
48
|
repair: "\u91CD\u65B0\u6CE8\u5165\u5F00\u59CB\u9875\u6309\u94AE",
|
|
49
49
|
repairing: "\u6B63\u5728\u91CD\u65B0\u6CE8\u5165\u2026",
|
|
50
50
|
repaired: "\u5DF2\u4FEE\u590D\uFF0C\u91CD\u65B0\u6253\u5F00\u63A7\u5236\u53F0\u5373\u53EF\u770B\u5230\u6309\u94AE",
|
|
51
|
-
repairFail: "\u4FEE\u590D\u5931\u8D25"
|
|
51
|
+
repairFail: "\u4FEE\u590D\u5931\u8D25",
|
|
52
|
+
usageTitle: "\u7528\u91CF",
|
|
53
|
+
usageDesc: "\u672C\u673A\u8BB0\u8D26\u7684\u8C03\u7528\u7528\u91CF\u4E0E WorkBuddy \u79EF\u5206\u5305\u4F59\u989D\u3002\u79EF\u5206\u53D6\u4E0A\u6E38\u6BCF\u6B21\u8C03\u7528\u56DE\u4F20\u7684\u771F\u5B9E\u6263\u8D39\uFF08\u53EF\u80FD\u4E3A 0\uFF09\uFF0C\u500D\u7387\u6309\u76EE\u5F55 + \u9650\u65F6\u4F18\u60E0\u5B9E\u65F6\u8BA1\u7B97\u3002",
|
|
54
|
+
usageRefresh: "\u5237\u65B0\u4F59\u989D",
|
|
55
|
+
usageClear: "\u6E05\u7A7A\u672C\u5730\u8D26\u672C",
|
|
56
|
+
usageCleared: "\u672C\u5730\u8D26\u672C\u5DF2\u6E05\u7A7A",
|
|
57
|
+
metricCalls: "\u8C03\u7528",
|
|
58
|
+
metricCredit: "\u79EF\u5206",
|
|
59
|
+
metricInput: "\u8F93\u5165 tok",
|
|
60
|
+
metricOutput: "\u8F93\u51FA tok",
|
|
61
|
+
spanToday: "\u4ECA\u5929",
|
|
62
|
+
spanWeek: "\u8FD1 7 \u5929",
|
|
63
|
+
spanMonth: "\u8FD1 30 \u5929",
|
|
64
|
+
byModel: "\u6309\u6A21\u578B",
|
|
65
|
+
balanceTitle: "\u79EF\u5206\u5305\u4F59\u989D",
|
|
66
|
+
colPkgName: "\u79EF\u5206\u5305",
|
|
67
|
+
colPkgRemain: "\u5269\u4F59",
|
|
68
|
+
colPkgUsed: "\u5DF2\u7528",
|
|
69
|
+
colPkgCycle: "\u5468\u671F",
|
|
70
|
+
promosTitle: "\u9650\u65F6\u4F18\u60E0 / \u514D\u8D39",
|
|
71
|
+
promoActive: "\u751F\u6548\u4E2D",
|
|
72
|
+
promoSoon: "\u672A\u751F\u6548",
|
|
73
|
+
recentTitle: "\u6700\u8FD1\u8C03\u7528",
|
|
74
|
+
colTime: "\u65F6\u95F4",
|
|
75
|
+
colTokens: "token",
|
|
76
|
+
colDuration: "\u8017\u65F6",
|
|
77
|
+
noData: "\u6682\u65E0\u6570\u636E",
|
|
78
|
+
totalCredit: "\u5408\u8BA1\u79EF\u5206",
|
|
79
|
+
todayCredit: "\u4ECA\u65E5\u79EF\u5206",
|
|
80
|
+
todayTokens: "\u4ECA\u65E5 token",
|
|
81
|
+
colSafety: "\u5408\u89C4",
|
|
82
|
+
safetySafe: "\u5B89\u5168\u6E05\u5355",
|
|
83
|
+
safetyCaution: "\u5B89\u5168\u6E05\u5355\xB7\u8C28\u614E",
|
|
84
|
+
showAllModels: "\u663E\u793A\u5168\u90E8\u6A21\u578B\uFF08\u542B\u4E0D\u5EFA\u8BAE\u9879\uFF09",
|
|
85
|
+
unsafePick: "\u8BE5\u6A21\u578B\u4E0D\u5728\u5B89\u5168\u6E05\u5355\u5185\uFF0C\u5DF2\u62D2\u7EDD\u9009\u62E9",
|
|
86
|
+
safeOnlyOn: "\u4EC5\u8C03\u7528\u5B89\u5168\u6E05\u5355\u6A21\u578B",
|
|
87
|
+
safeOnlyOff: "safeOnly \u5DF2\u5173\u95ED\uFF1A\u5168\u90E8\u6A21\u578B\u53EF\u8C03\u7528\uFF08\u542B WorkBuddy \u4E0D\u5EFA\u8BAE\u5916\u90E8\u8C03\u7528\u7684\u9879\uFF09"
|
|
52
88
|
},
|
|
53
89
|
en: {
|
|
54
90
|
refresh: "Refresh from WorkBuddy",
|
|
@@ -98,16 +134,62 @@ var TEXT = {
|
|
|
98
134
|
repair: "Re-inject the home button",
|
|
99
135
|
repairing: "Re-injecting\u2026",
|
|
100
136
|
repaired: "Fixed. Reopen the console to see the button",
|
|
101
|
-
repairFail: "Repair failed"
|
|
137
|
+
repairFail: "Repair failed",
|
|
138
|
+
usageTitle: "Usage",
|
|
139
|
+
usageDesc: "Locally recorded call usage plus the WorkBuddy credit balance. Credits come from the per-call figure the upstream returns (may be 0); multipliers combine the catalog with time-limited offers in real time.",
|
|
140
|
+
usageRefresh: "Refresh balance",
|
|
141
|
+
usageClear: "Clear local ledger",
|
|
142
|
+
usageCleared: "Local ledger cleared",
|
|
143
|
+
metricCalls: "Calls",
|
|
144
|
+
metricCredit: "Credits",
|
|
145
|
+
metricInput: "Input tok",
|
|
146
|
+
metricOutput: "Output tok",
|
|
147
|
+
spanToday: "Today",
|
|
148
|
+
spanWeek: "Last 7 days",
|
|
149
|
+
spanMonth: "Last 30 days",
|
|
150
|
+
byModel: "By model",
|
|
151
|
+
balanceTitle: "Credit packages",
|
|
152
|
+
colPkgName: "Package",
|
|
153
|
+
colPkgRemain: "Remaining",
|
|
154
|
+
colPkgUsed: "Used",
|
|
155
|
+
colPkgCycle: "Cycle",
|
|
156
|
+
promosTitle: "Limited-time offers / free",
|
|
157
|
+
promoActive: "active",
|
|
158
|
+
promoSoon: "scheduled",
|
|
159
|
+
recentTitle: "Recent calls",
|
|
160
|
+
colTime: "Time",
|
|
161
|
+
colTokens: "tokens",
|
|
162
|
+
colDuration: "Latency",
|
|
163
|
+
noData: "No data",
|
|
164
|
+
totalCredit: "Total credits",
|
|
165
|
+
todayCredit: "Credits today",
|
|
166
|
+
todayTokens: "Tokens today",
|
|
167
|
+
colSafety: "Policy",
|
|
168
|
+
safetySafe: "Safe list",
|
|
169
|
+
safetyCaution: "Safe list \xB7 caution",
|
|
170
|
+
showAllModels: "Show all models (including discouraged ones)",
|
|
171
|
+
unsafePick: "This model is outside the safe list and cannot be selected",
|
|
172
|
+
safeOnlyOn: "Safe-list only",
|
|
173
|
+
safeOnlyOff: "safeOnly is off: every model is callable (including ones WorkBuddy discourages)"
|
|
102
174
|
}
|
|
103
175
|
};
|
|
104
176
|
function strings(ctx) {
|
|
105
177
|
return TEXT[ctx.language === "en" ? "en" : "zh"];
|
|
106
178
|
}
|
|
107
179
|
function fmtMultiplier(m, s) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
180
|
+
const price = m?.price;
|
|
181
|
+
const base = typeof price?.base === "number" ? price.base : typeof m?.creditMultiplier === "number" ? m.creditMultiplier : null;
|
|
182
|
+
const eff = typeof price?.effective === "number" ? price.effective : base;
|
|
183
|
+
if (base === null) return { text: "\u2014", free: false, promo: "" };
|
|
184
|
+
if (eff === 0) return { text: `x0 \xB7 ${s.multiplierFree}${price?.windowText ? ` ${price.windowText}` : ""}`, free: true, promo: price?.badge ?? "" };
|
|
185
|
+
if (typeof eff === "number" && eff < base) {
|
|
186
|
+
return {
|
|
187
|
+
text: `x${eff} \u2190 x${base}${price?.windowText ? ` ${price.windowText}` : ""}`,
|
|
188
|
+
free: false,
|
|
189
|
+
promo: price?.badge ?? ""
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return { text: `x${base}`, free: false, promo: price?.badge ?? "" };
|
|
111
193
|
}
|
|
112
194
|
function fmtTs(ms, s) {
|
|
113
195
|
if (!ms) return s.notRefreshed;
|
|
@@ -122,33 +204,42 @@ function mountModels(ctx) {
|
|
|
122
204
|
const { ui } = ctx;
|
|
123
205
|
const sheet = ui.sheet({
|
|
124
206
|
title: s.colModel === "\u6A21\u578B" ? "WorkBuddy \u6A21\u578B\u76EE\u5F55" : "WorkBuddy model catalog",
|
|
125
|
-
desc: s.
|
|
207
|
+
desc: s.colModel === "\u6A21\u578B" ? "\u9ED8\u8BA4\u53EA\u5217 WorkBuddy \u516C\u5F00\u5BF9\u8BDD\u6A21\u578B\uFF08\u5B89\u5168\u6E05\u5355\uFF09\uFF1B\u52FE\u9009\u300C\u663E\u793A\u5168\u90E8\u6A21\u578B\u300D\u53EF\u67E5\u770B\u88AB\u6392\u9664\u9879\u53CA\u5176\u7406\u7531\u3002" : 'Only WorkBuddy public chat models are listed by default; tick "Show all models" to see excluded ones and why.'
|
|
126
208
|
});
|
|
127
209
|
ctx.root.append(sheet.el);
|
|
128
210
|
const head = ui.rowbar();
|
|
129
211
|
const refreshBtn = ui.button(s.refresh, { variant: "primary" });
|
|
130
212
|
const stateChip = ui.chip("", "plain");
|
|
131
213
|
const currentChip = ui.chip("", "accent");
|
|
132
|
-
|
|
214
|
+
const allBox = ui.checkbox(s.showAllModels, { checked: false });
|
|
215
|
+
head.append(refreshBtn, stateChip, currentChip, allBox.el);
|
|
133
216
|
sheet.body.append(head);
|
|
134
217
|
const errLine = ui.msgline();
|
|
135
218
|
sheet.body.append(errLine);
|
|
136
219
|
const table = ui.table({
|
|
137
|
-
head: [s.colModel, s.colMultiplier, s.colContext, s.colReasoning, s.colAction],
|
|
220
|
+
head: [s.colModel, s.colSafety, s.colMultiplier, s.colContext, s.colReasoning, s.colAction],
|
|
138
221
|
maxHeight: "calc(100vh - 320px)"
|
|
139
222
|
});
|
|
140
223
|
sheet.body.append(table.el);
|
|
141
224
|
let state = null;
|
|
142
225
|
function renderChips() {
|
|
143
|
-
|
|
226
|
+
const models = state?.models ?? [];
|
|
227
|
+
const safeCount = models.filter((m) => m.safety?.allow).length;
|
|
228
|
+
stateChip.textContent = `${s.lastRefresh}: ${fmtTs(state?.catalogAt, s)} \xB7 ${safeCount}/${models.length} ${s.models} \xB7 ${state?.safeOnly === false ? s.safeOnlyOff : s.safeOnlyOn}`;
|
|
144
229
|
currentChip.textContent = `${s.currentModel}: ${state?.model || "\u2014"}`;
|
|
145
230
|
}
|
|
231
|
+
function pickable(m) {
|
|
232
|
+
if (state?.safeOnly === false) return true;
|
|
233
|
+
return m.safety?.allow !== false;
|
|
234
|
+
}
|
|
146
235
|
function renderTable() {
|
|
147
|
-
const
|
|
236
|
+
const all = state?.models ?? [];
|
|
237
|
+
const models = allBox.checked ? all : all.filter((m) => m.safety?.allow);
|
|
148
238
|
table.clear(models.length ? void 0 : s.empty);
|
|
149
239
|
for (const m of models) {
|
|
150
240
|
const mul = fmtMultiplier(m, s);
|
|
151
241
|
const isCurrent = state.model === m.id;
|
|
242
|
+
const safety = m.safety ?? {};
|
|
152
243
|
const nameCell = ui.h("div");
|
|
153
244
|
nameCell.append(ui.h("div", null, m.name || m.id));
|
|
154
245
|
const sub = ui.h("div", "dim", m.id);
|
|
@@ -156,11 +247,26 @@ function mountModels(ctx) {
|
|
|
156
247
|
sub.style.opacity = "0.65";
|
|
157
248
|
nameCell.append(sub);
|
|
158
249
|
if (m.description) nameCell.title = m.description;
|
|
250
|
+
const safePill = ui.pill(
|
|
251
|
+
safety.label ?? "\u2014",
|
|
252
|
+
safety.allow ? safety.caution ? "plain" : "on" : "plain"
|
|
253
|
+
);
|
|
254
|
+
if (safety.reason) safePill.title = safety.reason;
|
|
255
|
+
if (!safety.allow) {
|
|
256
|
+
safePill.style.opacity = "0.6";
|
|
257
|
+
nameCell.style.opacity = "0.6";
|
|
258
|
+
}
|
|
159
259
|
const mulPill = ui.pill(mul.text, mul.free ? "on" : "plain");
|
|
260
|
+
if (mul.promo) mulPill.title = mul.promo;
|
|
261
|
+
const allowed = pickable(m);
|
|
160
262
|
const btn = ui.button(isCurrent ? s.selected : s.select, {
|
|
161
263
|
size: "sm",
|
|
162
264
|
variant: isCurrent ? "plain" : "primary",
|
|
163
265
|
onClick: async () => {
|
|
266
|
+
if (!allowed) {
|
|
267
|
+
ui.toast(`${s.unsafePick}${safety.reason ? `\uFF1A${safety.reason}` : ""}`, "bad");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
164
270
|
const off = ui.disable(btn);
|
|
165
271
|
try {
|
|
166
272
|
const r = await ctx.invoke("select", [m.id]);
|
|
@@ -180,8 +286,13 @@ function mountModels(ctx) {
|
|
|
180
286
|
}
|
|
181
287
|
});
|
|
182
288
|
if (isCurrent) btn.disabled = true;
|
|
289
|
+
if (!allowed) {
|
|
290
|
+
btn.disabled = true;
|
|
291
|
+
btn.title = safety.reason ?? "";
|
|
292
|
+
}
|
|
183
293
|
table.addRow([
|
|
184
294
|
nameCell,
|
|
295
|
+
safePill,
|
|
185
296
|
mulPill,
|
|
186
297
|
m.contextWindow ? String(m.contextWindow) : "\u2014",
|
|
187
298
|
m.reasoning?.supportedEfforts?.join(" / ") ?? "\u2014",
|
|
@@ -189,6 +300,10 @@ function mountModels(ctx) {
|
|
|
189
300
|
]);
|
|
190
301
|
}
|
|
191
302
|
}
|
|
303
|
+
allBox.input.addEventListener("change", () => {
|
|
304
|
+
renderChips();
|
|
305
|
+
renderTable();
|
|
306
|
+
});
|
|
192
307
|
async function load() {
|
|
193
308
|
state = await ctx.invoke("state");
|
|
194
309
|
renderChips();
|
|
@@ -387,11 +502,168 @@ function mountPriority(ctx) {
|
|
|
387
502
|
statusLine.textContent = String(err?.message ?? err);
|
|
388
503
|
});
|
|
389
504
|
}
|
|
505
|
+
function fmtNum(n) {
|
|
506
|
+
if (typeof n !== "number" || !Number.isFinite(n)) return "\u2014";
|
|
507
|
+
return String(Math.round(n * 100) / 100);
|
|
508
|
+
}
|
|
509
|
+
function mountUsage(ctx) {
|
|
510
|
+
const s = strings(ctx);
|
|
511
|
+
const { ui } = ctx;
|
|
512
|
+
const sheet = ui.sheet({ title: `${s.usageTitle} \xB7 WorkBuddy`, desc: s.usageDesc });
|
|
513
|
+
ctx.root.append(sheet.el);
|
|
514
|
+
const bar = ui.rowbar();
|
|
515
|
+
const refreshBtn = ui.button(s.usageRefresh, { variant: "primary", size: "sm" });
|
|
516
|
+
const clearBtn = ui.button(s.usageClear, { variant: "plain", size: "sm" });
|
|
517
|
+
const todayChip = ui.chip("", "accent");
|
|
518
|
+
const balanceChip = ui.chip("", "plain");
|
|
519
|
+
bar.append(refreshBtn, clearBtn, todayChip, balanceChip);
|
|
520
|
+
sheet.body.append(bar);
|
|
521
|
+
const errLine = ui.msgline();
|
|
522
|
+
sheet.body.append(errLine);
|
|
523
|
+
const overview = ui.table({
|
|
524
|
+
head: ["", s.metricCalls, s.metricCredit, s.metricInput, s.metricOutput],
|
|
525
|
+
maxHeight: "220px"
|
|
526
|
+
});
|
|
527
|
+
sheet.body.append(overview.el);
|
|
528
|
+
const modelSection = ui.section(s.byModel);
|
|
529
|
+
const modelTable = ui.table({
|
|
530
|
+
head: [s.colModel, s.metricCalls, s.metricCredit, s.metricInput, s.metricOutput],
|
|
531
|
+
maxHeight: "260px"
|
|
532
|
+
});
|
|
533
|
+
modelSection.append(modelTable.el);
|
|
534
|
+
sheet.body.append(modelSection);
|
|
535
|
+
const pkgSection = ui.section(s.balanceTitle);
|
|
536
|
+
const pkgTable = ui.table({
|
|
537
|
+
head: [s.colPkgName, s.colPkgRemain, s.colPkgUsed, s.colPkgCycle],
|
|
538
|
+
maxHeight: "220px"
|
|
539
|
+
});
|
|
540
|
+
pkgSection.append(pkgTable.el);
|
|
541
|
+
sheet.body.append(pkgSection);
|
|
542
|
+
const promoSection = ui.section(s.promosTitle);
|
|
543
|
+
const promoWrap = ui.h("div");
|
|
544
|
+
promoSection.append(promoWrap);
|
|
545
|
+
sheet.body.append(promoSection);
|
|
546
|
+
const recentSection = ui.section(s.recentTitle);
|
|
547
|
+
const recentTable = ui.table({
|
|
548
|
+
head: [s.colTime, s.colModel, s.colTokens, s.metricCredit, s.colDuration],
|
|
549
|
+
maxHeight: "300px"
|
|
550
|
+
});
|
|
551
|
+
recentSection.append(recentTable.el);
|
|
552
|
+
sheet.body.append(recentSection);
|
|
553
|
+
let state = null;
|
|
554
|
+
function spanRow(label, bucket) {
|
|
555
|
+
const b = bucket ?? {};
|
|
556
|
+
return [label, String(b.calls ?? 0), fmtNum(b.credit), fmtNum(b.prompt), fmtNum(b.completion)];
|
|
557
|
+
}
|
|
558
|
+
function render() {
|
|
559
|
+
const totals = state?.summary?.totals ?? {};
|
|
560
|
+
todayChip.textContent = `${s.todayCredit}: ${fmtNum(totals.today?.credit)} \xB7 ${s.todayTokens}: ${fmtNum(
|
|
561
|
+
(totals.today?.prompt ?? 0) + (totals.today?.completion ?? 0)
|
|
562
|
+
)}`;
|
|
563
|
+
balanceChip.textContent = `${s.balanceTitle}: ${fmtNum(state?.balance?.total)}`;
|
|
564
|
+
const spans = [spanRow(s.spanToday, totals.today), spanRow(s.spanWeek, totals.week), spanRow(s.spanMonth, totals.month)];
|
|
565
|
+
overview.clear();
|
|
566
|
+
for (const row of spans) overview.addRow(row);
|
|
567
|
+
const byModel = state?.summary?.byModel ?? [];
|
|
568
|
+
modelTable.clear(byModel.length > 0 ? void 0 : s.noData);
|
|
569
|
+
for (const m of byModel) {
|
|
570
|
+
modelTable.addRow([m.model, String(m.calls ?? 0), fmtNum(m.credit), fmtNum(m.prompt), fmtNum(m.completion)]);
|
|
571
|
+
}
|
|
572
|
+
const packages = state?.balance?.packages ?? [];
|
|
573
|
+
pkgTable.clear(packages.length > 0 ? void 0 : s.noData);
|
|
574
|
+
for (const p of packages) {
|
|
575
|
+
const cycle = [p.cycleStart, p.cycleEnd].filter(Boolean).join(" \u2192 ") || "\u2014";
|
|
576
|
+
pkgTable.addRow([p.name ?? "\u2014", fmtNum(p.remain), fmtNum(p.used), cycle]);
|
|
577
|
+
}
|
|
578
|
+
promoWrap.replaceChildren();
|
|
579
|
+
const promos = state?.promos ?? [];
|
|
580
|
+
if (promos.length === 0) {
|
|
581
|
+
promoWrap.append(ui.h("div", "dim", s.noData));
|
|
582
|
+
}
|
|
583
|
+
for (const p of promos) {
|
|
584
|
+
const row = ui.h("div");
|
|
585
|
+
row.style.display = "flex";
|
|
586
|
+
row.style.gap = "8px";
|
|
587
|
+
row.style.alignItems = "center";
|
|
588
|
+
row.style.padding = "4px 0";
|
|
589
|
+
row.append(ui.pill(p.badge || p.id, p.active ? "on" : "plain"));
|
|
590
|
+
const text = [
|
|
591
|
+
p.discountText || p.hoverText,
|
|
592
|
+
p.windowText || p.absoluteText,
|
|
593
|
+
(p.models ?? []).join(" / "),
|
|
594
|
+
p.active ? s.promoActive : s.promoSoon
|
|
595
|
+
].filter(Boolean).join(" \xB7 ");
|
|
596
|
+
const label = ui.h("div", "dim", text);
|
|
597
|
+
label.style.fontSize = "12px";
|
|
598
|
+
if (p.hoverText) label.title = p.hoverText;
|
|
599
|
+
row.append(label);
|
|
600
|
+
promoWrap.append(row);
|
|
601
|
+
}
|
|
602
|
+
const recent = state?.summary?.recent ?? [];
|
|
603
|
+
recentTable.clear(recent.length > 0 ? void 0 : s.noData);
|
|
604
|
+
for (const r of recent) {
|
|
605
|
+
const when = (() => {
|
|
606
|
+
try {
|
|
607
|
+
return new Date(r.ts).toLocaleString();
|
|
608
|
+
} catch {
|
|
609
|
+
return String(r.ts);
|
|
610
|
+
}
|
|
611
|
+
})();
|
|
612
|
+
recentTable.addRow([
|
|
613
|
+
when,
|
|
614
|
+
r.model ?? "\u2014",
|
|
615
|
+
fmtNum(r.totalTokens ?? (r.promptTokens ?? 0) + (r.completionTokens ?? 0)),
|
|
616
|
+
r.ok === false ? "\u2014" : fmtNum(r.credit),
|
|
617
|
+
typeof r.durationMs === "number" ? `${r.durationMs} ms` : "\u2014"
|
|
618
|
+
]);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
async function load() {
|
|
622
|
+
state = await ctx.invoke("state");
|
|
623
|
+
errLine.textContent = state?.ok === false ? state.error ?? "" : "";
|
|
624
|
+
render();
|
|
625
|
+
}
|
|
626
|
+
refreshBtn.addEventListener("click", async () => {
|
|
627
|
+
const off = ui.disable(refreshBtn);
|
|
628
|
+
refreshBtn.textContent = s.refreshing;
|
|
629
|
+
try {
|
|
630
|
+
state = await ctx.invoke("refresh");
|
|
631
|
+
render();
|
|
632
|
+
ui.toast(`${s.balanceTitle}: ${fmtNum(state?.balance?.total)}`);
|
|
633
|
+
} catch (err) {
|
|
634
|
+
errLine.textContent = String(err?.message ?? err);
|
|
635
|
+
ui.toast(String(err?.message ?? err), "bad");
|
|
636
|
+
} finally {
|
|
637
|
+
refreshBtn.textContent = s.usageRefresh;
|
|
638
|
+
off.dispose();
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
clearBtn.addEventListener("click", async () => {
|
|
642
|
+
const off = ui.disable(clearBtn);
|
|
643
|
+
try {
|
|
644
|
+
state = await ctx.invoke("clear");
|
|
645
|
+
render();
|
|
646
|
+
ui.toast(s.usageCleared);
|
|
647
|
+
} catch (err) {
|
|
648
|
+
ui.toast(String(err?.message ?? err), "bad");
|
|
649
|
+
} finally {
|
|
650
|
+
off.dispose();
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
ctx.stream({
|
|
654
|
+
message: () => load().catch(() => {
|
|
655
|
+
})
|
|
656
|
+
});
|
|
657
|
+
load().catch((err) => {
|
|
658
|
+
errLine.textContent = String(err?.message ?? err);
|
|
659
|
+
});
|
|
660
|
+
}
|
|
390
661
|
var client_default = {
|
|
391
662
|
panels: {
|
|
392
663
|
models: { mount: mountModels },
|
|
393
664
|
account: { mount: mountAccount },
|
|
394
|
-
priority: { mount: mountPriority }
|
|
665
|
+
priority: { mount: mountPriority },
|
|
666
|
+
usage: { mount: mountUsage }
|
|
395
667
|
}
|
|
396
668
|
};
|
|
397
669
|
export {
|
package/package.json
CHANGED
package/scripts/verify-ui.mjs
CHANGED
|
@@ -341,6 +341,38 @@ for (let run = 1; run <= RUNS; run += 1) {
|
|
|
341
341
|
return dom === false && api.prefer === false ? true : false;
|
|
342
342
|
});
|
|
343
343
|
row.checks.panelToggleOff = true;
|
|
344
|
+
// --- 用量面板:页签 → 渲染 → 概览表有行 ---
|
|
345
|
+
await evaluate(clickByText('用量'));
|
|
346
|
+
const usage = await waitFor('用量面板渲染', async () => {
|
|
347
|
+
const v = await evaluate(`(() => {
|
|
348
|
+
const btn = [...document.querySelectorAll('button')].find((b) => b.textContent.trim() === '刷新余额' && b.offsetParent !== null);
|
|
349
|
+
const chip = [...document.querySelectorAll('*')].find((n) => n.children.length === 0 && (n.textContent || '').startsWith('今日积分'));
|
|
350
|
+
const rows = document.querySelectorAll('table tr').length;
|
|
351
|
+
return { hasRefresh: !!btn, chip: (chip?.textContent || '').trim(), rows };
|
|
352
|
+
})()`);
|
|
353
|
+
return v.hasRefresh && v.rows >= 4 ? v : false;
|
|
354
|
+
});
|
|
355
|
+
row.checks.usageRendered = usage.hasRefresh === true;
|
|
356
|
+
row.checks.usageRows = usage.rows;
|
|
357
|
+
row.checks.usageChip = usage.chip;
|
|
358
|
+
if (SHOTS && run === 1) { await sleep(300); await shot('world-usage'); }
|
|
359
|
+
// --- 模型目录:合规列 + 默认只列安全清单 ---
|
|
360
|
+
await evaluate(clickByText('模型目录'));
|
|
361
|
+
const modelsView = await waitFor('模型目录渲染', async () => {
|
|
362
|
+
const v = await evaluate(`(() => {
|
|
363
|
+
const th = [...document.querySelectorAll('th')].map((n) => n.textContent.trim());
|
|
364
|
+
const rows = document.querySelectorAll('table tr').length;
|
|
365
|
+
const safePills = [...document.querySelectorAll('td span')].filter((n) => /安全清单/.test(n.textContent || '')).length;
|
|
366
|
+
const allBox = [...document.querySelectorAll('input[type="checkbox"]')].length;
|
|
367
|
+
return { hasPolicy: th.includes('合规'), headers: th.join('|'), rows, safePills, checkboxes: allBox };
|
|
368
|
+
})()`);
|
|
369
|
+
return v.hasPolicy && v.rows > 10 ? v : false;
|
|
370
|
+
});
|
|
371
|
+
row.checks.modelsPolicyColumn = modelsView.hasPolicy;
|
|
372
|
+
row.checks.modelsRows = modelsView.rows;
|
|
373
|
+
row.checks.modelsSafePills = modelsView.safePills;
|
|
374
|
+
row.checks.modelsHasShowAll = modelsView.checkboxes >= 1;
|
|
375
|
+
if (SHOTS && run === 1) { await sleep(300); await shot('world-models'); }
|
|
344
376
|
if (KEEP) await shot(`run${run}-world`);
|
|
345
377
|
void pane;
|
|
346
378
|
} catch (err) {
|
package/src/ENV_PROMPT.md
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
This machine has a WorkBuddy desktop sign-in bridged in (region: {{workbuddy.region}}; signed in: {{workbuddy.authed}}).
|
|
4
4
|
The model catalog holds {{workbuddy.catalogSize}} models. The currently selected model is `{{workbuddy.model}}` with default credit multiplier {{workbuddy.multiplier}}.
|
|
5
5
|
WorkBuddy desktop main process: {{workbuddy.process}}. Relay-first switch: {{workbuddy.priority}}.
|
|
6
|
+
Spent today through the bridge: {{workbuddy.todayCredit}} credits / {{workbuddy.todayTokens}} tokens (locally recorded).
|
|
7
|
+
The multiplier above already includes any time-limited discount or free window that is active right now; `wb_list_models` also marks which offers are active and their hours.
|
|
6
8
|
|
|
7
9
|
- Use `wb_list_models` to see every model with its multiplier before choosing; prefer low-multiplier models for casual tasks.
|
|
10
|
+
- Safe-list gate: {{workbuddy.safeOnly}}. When it is `on`, only the models WorkBuddy publishes as public chat models are callable — `disabled` entries, internal routing aliases (`auto`, `fast-model`, `balanced-model`, `deep-model`), media/code models and delisted ones are refused. Do not try to work around a refusal; tell the user why and offer a safe model instead. `wb_list_models` with `all=true` shows the refused ones with reasons.
|
|
8
11
|
- Use `wb_select_model` when the user asks to switch the WorkBuddy model.
|
|
9
12
|
- `wb_chat` only works while the WorkBuddy desktop main process is running; otherwise it returns「未检测到 WorkBuddy 主进程」. Every call spends the user's own credits at that model's multiplier.
|
|
10
13
|
- `wb_credits` is read-only and costs nothing (also requires the desktop process).
|
package/src/config.js
CHANGED
|
@@ -29,6 +29,12 @@ export const WB_DEFAULTS = {
|
|
|
29
29
|
prefer: false,
|
|
30
30
|
/** 是否允许改写宿主控制台 index.html 注入「中转站优先」按钮;关闭后不注入也不还原。 */
|
|
31
31
|
hostInject: true,
|
|
32
|
+
/**
|
|
33
|
+
* 只允许调用 WorkBuddy 公开对话模型(安全清单)。
|
|
34
|
+
* 关掉后能用 disabled / 内部别名 / 预览等模型,但那属于「已知会违反对方使用建议」的操作,
|
|
35
|
+
* 默认必须是 true。
|
|
36
|
+
*/
|
|
37
|
+
safeOnly: true,
|
|
32
38
|
};
|
|
33
39
|
|
|
34
40
|
/** World 配置组(WebUI 通用渲染的旋钮)。模型选择走自定义面板,不在这里。 */
|
|
@@ -84,6 +90,13 @@ export const WB_CONFIG_GROUP = {
|
|
|
84
90
|
description: '在「开始」页加一个「中转站优先」按钮(会改写 Coopanion 的 build/console/index.html,停用或卸载时自动还原)。关掉后只能用 World 详情页的开关。',
|
|
85
91
|
'x-hot': true,
|
|
86
92
|
},
|
|
93
|
+
'worlds.workbuddy.safeOnly': {
|
|
94
|
+
type: 'boolean',
|
|
95
|
+
title: '只调用安全清单模型',
|
|
96
|
+
description:
|
|
97
|
+
'默认开:wb_chat / wb_select_model 只接受 WorkBuddy 公开目录里的对话模型(21 个),disabled、内部路由别名、媒体与代码补全模型一律拒绝。关掉可绕过限制,但那属于违反对方使用建议的操作,风险自负。',
|
|
98
|
+
'x-hot': true,
|
|
99
|
+
},
|
|
87
100
|
},
|
|
88
101
|
},
|
|
89
102
|
};
|