dsh-model 0.22.3 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -48,7 +48,7 @@ On a dsh-vps server, use `sudo dsh-model setup`. On Windows, run it in a normal
48
48
  `setup` is safe to re-run. It:
49
49
 
50
50
  1. downloads and verifies the engine and runs it on `127.0.0.1:8317/v1`, with key auth enforced;
51
- 2. connects WorkBuddy (dsh-model's own bridge). Setup asks for no keys: turn on OpenCode Zen and other key-based sources from the management page when you need them;
51
+ 2. starts dsh-model's daemon (the management page, check-in and sign-ins run on it). Setup asks for no keys: for a free source we suggest **SiliconFlow** (reachable from China) or **Gemini** (if you can reach Google), added from the management page. WorkBuddy is used for check-in only; its models are not connected to dsh by default;
52
52
  3. installs the management page into dsh and syncs the models into dsh;
53
53
  4. on a VPS, also opens a public address `https://<dsh's domain>:9443/v1` (or `<IP>.sslip.io` when there is no domain).
54
54
 
package/README.md CHANGED
@@ -48,7 +48,7 @@ dsh-model setup
48
48
  `setup` 可以重复执行,它会依次:
49
49
 
50
50
  1. 下载并校验引擎,在本机 `127.0.0.1:8317/v1` 上运行,强制 key 鉴权;
51
- 2. 接入 WorkBuddy(dsh-model 自己的 bridge)。安装时不问任何 key,OpenCode Zen 等要 key 的来源,需要时在管理页里打开;
51
+ 2. 启动 dsh-model 的守护进程(管理页、签到、登录都靠它)。安装时不问任何 key:免费来源推荐**硅基流动**(国内直连)或 **Gemini**(能上外网),装好后在管理页里填 key。WorkBuddy 只用来签到,模型默认不接进 dsh;
52
52
  3. 把管理页装进 dsh,把模型同步进 dsh;
53
53
  4. 在 VPS 上,额外开通对外地址 `https://<dsh 的域名>:9443/v1`(没有域名时用 `<IP>.sslip.io`)。
54
54
 
@@ -30,7 +30,7 @@ import { Mgmt } from '../engine/mgmt.js';
30
30
  import { DshModelError, isDshModelError } from '../errors.js';
31
31
  import { L } from '../i18n.js';
32
32
  import { refreshOpencodeModels, removeOpencodeKey, saveOpencodeKey, validateKeyShape, verifyOpencodeKey } from '../integrations/opencode.js';
33
- import { riskNotice as workbuddyRiskNotice } from '../integrations/workbuddy.js';
33
+ import { blockedNotice as workbuddyBlockedNotice, riskNotice as workbuddyRiskNotice } from '../integrations/workbuddy.js';
34
34
  import { DSH_KEY_NAME, addKey, findActive, loadKeys, revokeKey, rotateKey, saveKeys } from '../keys.js';
35
35
  import { applyKeyChange, dshCandidates, dshKey, loadAll, saveAll, syncAll } from '../ops.js';
36
36
  import { defaultPick, pickedFor } from '../dsh/pick.js';
@@ -301,6 +301,9 @@ export class Daemon {
301
301
  return { enabled: false, riskNotice: upstreamRiskNotice(getUpstream(def.id)) };
302
302
  return { enabled: false, login: await this.startLogin(def) };
303
303
  }
304
+ // WorkBuddy 会拦 dsh 的对话:从关着打开时先提醒一次
305
+ if (def.kind === 'workbuddy' && !state.enabled && !opts.acceptRisk)
306
+ return { enabled: false, riskNotice: workbuddyBlockedNotice() };
304
307
  if (def.kind === 'engine') {
305
308
  const m = await this.mgmt();
306
309
  for (const c of credsFor(await m.credentials(), def))
@@ -37,7 +37,31 @@ async function waitCatalogs(ctx, since, expected, timeoutMs = 45_000) {
37
37
  return cats;
38
38
  }
39
39
  /** 检测 App → 首次打印说明 → bridge 配置与服务 → 等目录。返回目录(调用方随后 syncAll) */
40
+ export const WORKBUDDY_SOURCES = ['workbuddy', 'workbuddy-ai'];
41
+ /** WorkBuddy 会拦 dsh 发出的对话时的提醒(新装默认关、从页面打开前都显示) */
42
+ export function blockedNotice() {
43
+ return L('WorkBuddy 现在会认出 dsh 发出的对话并拦下(「请求被拦截」),在 dsh 里基本用不了;「测一下」能通也不代表能用。dsh-model 不会去绕开这个限制,继续调用有封号风险。签到不受影响。仍要把它的模型接进 dsh 吗?', 'WorkBuddy now recognises conversations sent by dsh and blocks them ("请求被拦截"), so it barely works in dsh; a passing Test does not mean it works. dsh-model will not try to get around this, and continuing risks the account. Check-in is not affected. Connect its models to dsh anyway?');
44
+ }
45
+ /**
46
+ * 只签到(设计 §14.18):第一次装时把 WorkBuddy 的模型关掉,签到照常(签到不看来源开关)。
47
+ * 以前装过的(已有 riskNoticeAt)保持原样;只做一次,之后用户自己打开的不改回去。
48
+ */
49
+ export function applyWorkbuddyDefault(all) {
50
+ if (all.config.workbuddyDefault)
51
+ return;
52
+ if (all.config.bridge?.riskNoticeAt) {
53
+ all.config.workbuddyDefault = 'kept';
54
+ return;
55
+ }
56
+ const off = new Set(all.config.disabledSources ?? []);
57
+ for (const id of WORKBUDDY_SOURCES)
58
+ off.add(id);
59
+ all.config.disabledSources = [...off];
60
+ all.config.workbuddyDefault = 'checkin-only';
61
+ info(L('WorkBuddy 只用来签到:它现在会拦 dsh 的对话,所以模型默认不接进 dsh(要用可在「来源」里打开)', 'WorkBuddy is used for check-in only: it now blocks conversations from dsh, so its models are not connected by default (turn it on under Sources if you want)'));
62
+ }
40
63
  export async function enableWorkbuddy(ctx, all) {
64
+ applyWorkbuddyDefault(all);
41
65
  const variants = await availableVariants(ctx.paths.home);
42
66
  if (!variants.length) {
43
67
  skip(ctx.platform === 'darwin'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-model",
3
- "version": "0.22.3",
3
+ "version": "0.23.0",
4
4
  "description": "One OpenAI-compatible address for all your models: subscriptions (Codex, Kimi…), free tiers (WorkBuddy, Gemini, Groq, OpenRouter), local models (Ollama, LM Studio), daily check-in credits, auto failover. Works with DeepSeek Harness (dsh) and any OpenAI-compatible tool. 一个地址用上你所有的模型。",
5
5
  "keywords": [
6
6
  "dsh-plugin",
package/plugin/client.js CHANGED
@@ -1089,9 +1089,15 @@ window.__ModuleLoader__.load({
1089
1089
  /** 刚装好、接入的来源少于两个时显示:先接一个免费来源 → 模型已经挑好 → 去 dsh 里用 */
1090
1090
  function Guide({ sources, onConnect }) {
1091
1091
  const [done, setDone] = useState(readGuideDone)
1092
- const connected = sources.filter((s) => s.loggedIn && s.enabled)
1093
- if (done || connected.length >= 2) return null
1094
- const free = sources.filter((s) => !s.risky && !(s.loggedIn && s.enabled) && s.pricing && s.pricing.tier !== 'paid')
1092
+ // WorkBuddy 只签到(它拦 dsh 的对话),不算"已接入"
1093
+ const connected = sources.filter((s) => s.loggedIn && s.enabled && s.kind !== 'workbuddy')
1094
+ if (done || connected.length >= 1) return null
1095
+ // 只推两家:国内直连的硅基流动、能上外网的 Gemini
1096
+ const PICKS = [
1097
+ { id: 'siliconflow', why: () => L('国内直连,不用代理;实名认证后部分模型免费', 'Reachable from China without a proxy; some models are free after identity verification') },
1098
+ { id: 'gemini', why: () => L('能上外网就选它:免费模型里质量最好,每天有次数上限', 'If you can reach Google: the best free models, with a daily limit') },
1099
+ ]
1100
+ const picks = PICKS.map((p) => ({ ...p, s: sources.find((s) => s.id === p.id) })).filter((p) => p.s)
1095
1101
  const hide = () => {
1096
1102
  setDone(true)
1097
1103
  try { window.localStorage.setItem(GUIDE_KEY, '1') } catch { /* 无痕模式:只在本次生效 */ }
@@ -1106,9 +1112,11 @@ window.__ModuleLoader__.load({
1106
1112
  h('button', { type: 'button', style: S.btn, onClick: hide }, L('我知道了,不再显示', 'Got it, hide this'))),
1107
1113
  step(1, L('接入一个免费来源', 'Connect a free source'),
1108
1114
  h('div', null,
1109
- h('div', { style: S.meta }, L('先接一个就能用。标「免费·额度少」的要先去对方网站拿一把免费 key。', 'One is enough to start. "Free · limited" ones need a free key from their website first.')),
1110
- h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: 6, marginTop: 6 } },
1111
- free.slice(0, 8).map((s) => h('button', { key: s.id, type: 'button', style: S.btn, onClick: () => onConnect(s) }, `${s.label}${s.pricing.tier === 'free' ? L(' · 免费', ' · free') : ''}`))))),
1115
+ h('div', { style: S.meta }, L('接一个就能用:去对方网站领一把免费 key,点下面的按钮粘贴进来。', 'One is enough: get a free key from their website, then click below and paste it.')),
1116
+ picks.map((p) => h('div', { key: p.id, style: { ...S.line, marginTop: 6, flexWrap: 'wrap' } },
1117
+ h('button', { type: 'button', style: S.btn, onClick: () => onConnect(p.s) }, p.s.label),
1118
+ h('span', { style: { ...S.meta, ...S.grow } }, p.why()),
1119
+ p.s.subscribeUrl ? h('a', { href: p.s.subscribeUrl, target: '_blank', rel: 'noopener noreferrer', style: { color: C.accent, fontSize: 12 } }, L('去领 key ↗', 'Get a key ↗')) : null)))),
1112
1120
  step(2, L('模型已经替你挑好', 'Models are picked for you'),
1113
1121
  h('div', { style: S.meta }, L('每个来源默认挑 5 个放进 dsh(免费的优先)。想换就在下面「来源」里点「选模型」。', 'Each source puts 5 suggested models into dsh (free ones first). Change them with "Models" under Sources below.'))),
1114
1122
  step(3, L('去用', 'Use it'),