DSH WorkBuddy Connect
corrinehu/dsh-workbuddy-connect将 WorkBuddy 桌面 App 包含的模型自动接入 DeepSeek Harness,在 DSH 对话窗口里零配置使用。
非官方火山方舟 Coding Plan 模型接入插件:注册 `ark-coding` 路由,12 个模型的容量事实逐项实测,多账户自动轮换,内置钉版 arkcli 拉官方模型目录与真实套餐额度,含设置向导与侧边栏用量卡片。
火山方舟 Coding Plan 的 DeepSeek Harness LLM 提供商插件(非官方社区集成)。
仿照 @mars-sea/dsh-commandcode-provider
的形态做的:注册一个提供商路由、在 Models 页挂一张属于自己的卡片、在设置里开一个
同名分区,把模型目录、容量事实和密钥管理都放在那里。
这是社区集成,不是火山引擎官方产品。你需要自己的火山方舟账号与 Coding Plan 订阅, 并遵守火山引擎的服务条款。
| 设置页 | 侧边栏额度卡片 + 详情面板 |
|---|---|
![]() | ![]() |
![]() | ![]() |
ark-coding —— 直接对着 <span>https://ark.cn-beijing.volces.com/api/coding/v3</span>
的 OpenAI 兼容端点说话,/chat/completions + SSE 流式。reasoning_effort 档位,全部逐项探过(见下表)。ARK_CODING_API_KEY,一个账户都没有时它会被当成兜底账户读进来
(见「配置密钥」)。厂商 · 1024K ctx),因为这段文字出现在 harness 共享的
模型选择器里,那里没有插件侧的语言席位。index.js 的 createDiscovery()):
被调用时用一次性密钥真实打一次 Coding Plan 端点,成功则回传这 12 个模型的
容量事实;密钥只在这一次请求里存在,宿主不存储它,浏览器永远不接触方舟。
它由宿主 Models 卡片上的模型拉取动作(Fetch available models)触发,
不是插件设置页里的按钮。@volcengine/ark-cli 的
平台二进制直接下到插件自己的目录(官方 CDN 优先、GitHub Release 兜底,落盘前校验
sha256),然后用它拉官方模型目录和真实套餐额度。装的是钉住的
@volcengine/ark-cli@1.0.39(实测通过的版本),不是 latest:额度、目录、
聊天取 key 三条链全都建立在它的输出形状上,装 latest 等于每次点安装都默默绑定
当天的最新版。DSH 桌面端(Electron)的 PATH 上通常没有 npm,所以安装不再走
npm install:直接下载即可;PATH 上恰好有 npm(或显式指定了别的版本)时才退回 npm。0.4%)—— 这个账号日常就落在 0.1%~1%,
一律取整会让卡片长期写着 0%,看着像没用量。重置时间照抄控制面给的那一刻,
不按日历推算:实测这个账号的 monthly 重置在 15 日 23:59:59,不是 1 号。单位是 token。模态一栏里 图像 表示该模型接受图片输入(视频/音频输入在 DSH 的模态
词汇里没有对应值,因此不计入)。
| 模型 id | 上下文 | 输出上限 | 模态 | 思考档位 | 厂商 |
|---|---|---|---|---|---|
doubao-seed-evolving | 1M | 256K | 文本 · 图像 | off/minimal/low/medium/high/max | Doubao |
doubao-seed-2.1-pro | 1M | 256K | 文本 · 图像 | off/minimal/low/medium/high/max | Doubao |
doubao-seed-2.1-lite | 1M | 256K | 文本 · 图像 | off/minimal/low/medium/high/max | Doubao |
doubao-seed-2.0-mini | 256K | 128K | 文本 · 图像 | off/minimal/low/medium/high/max | Doubao |
minimax-m3 | 1M | 128K | 文本 · 图像 | off/minimal/low/medium/high/max | MiniMax |
glm-5.3 | 1M | 128K | 文本 | low/medium/high/max | Zhipu |
glm-5.3-flash | 1M | 128K | 文本 · 图像 | off/minimal/low/medium/high/max | Zhipu |
deepseek-v4.1-flash | 1M | 384K | 文本 · 图像 | off/minimal/low/medium/high/max | DeepSeek |
deepseek-v4-flash | 1M | 384K | 文本 | off/minimal/low/medium/high/max | DeepSeek |
deepseek-v4-pro | 1M | 384K | 文本 | off/minimal/low/medium/high/max | DeepSeek |
kimi-k2.8-preview | 1M | 128K | 文本 · 图像 | off/minimal/low/medium/high/max | Moonshot |
kimi-k3 | 1M | 128K | 文本 · 图像 | off/minimal/low/medium/high/max | Moonshot |
glm-5.3-flash 其实能吃图。Ark 自己的 /models 把它记成 in=[text],
但给它一张四象限测试图,它准确答出 blue, yellow, green, magenta。所以这里按支持图像处理。glm-5.3 不能吃图,也不能关思考。图片请求被明确拒绝
(Model only support text input),thinking.type=disabled 与 reasoning_effort=none/minimal
同样被拒。它的可用档位是 low/medium/high/max(off/minimal 被拿掉,其余照收)。minimax-m3 回传的 tool_calls[].type 是 functionfunction(服务端序列化缺陷),
原样回灌第二轮工具调用会 400。适配器重建请求时强制写 type: "function",
多轮工具调用因此可用。deepseek-v4-flash 与 deepseek-v4-pro 按纯文本处理:前者带图三次都在 180 秒内无响应,
后者收图后回答"看不到图片"。DSH 的模态声明宁可少给也不能错给 —— 少给只是在附图片前
拒绝并点名模型,错给会让一个不可完成的请求在消息落盘之后才失败。
# 打包
cd ark-coding-provider && npm pack # 产出 dsh-ark-coding-provider-<version>.tgz
# 装进 profile(把路径换成上面那个 tarball)
dsh plugin --profile web add /path/to/dsh-ark-coding-provider-<version>.tgz
本地路径要用 tarball,不要用目录。 用目录安装时 pnpm 会建符号链接, 而 DSH 的运行时拦截只为「profile 树内」的包解析 harness 包(
@deepseek-ai/dsh-llm等), 链接到 workspace 的包会以真实路径在 profile 树之外被导入,于是所有 harness 包ERR_MODULE_NOT_FOUND,表现为failed to import。
装完重启一次 dsh(替换已加载的包需要新的模块世代)。
桌面版(Electron)跑的是 desktop profile,不是 web,装法有两种:
应用里打开 插件 → 添加插件,填入 tarball 的绝对路径(或 release 里 tgz 的直链)—— 插件管理器认本地路径与 tarball 链接;
或者用桌面自带的 dsh 命令(Windows 上是 dsh.cmd,在应用里 Manage dsh Command
注册):先完全退出桌面应用,再
dsh plugin --profile desktop add C:\path\to\dsh-ark-coding-provider-<version>.tgz
装完重新打开桌面应用。
桌面端不会给宿主进程设 DSH_HOME,这没关系:插件按 DSH 自己的规则解析状态目录
($DSH_HOME → ~/.dsh),和宿主的 sessions / profile / 凭据落在同一个根下。
最省事的路径是设置页:设置 → 火山方舟 → 「账户」卡片,新建一个「API Key」类型的 账户,在那一行里的密钥输入框粘贴后保存(没有名为「API 密钥」的独立卡片)。 密钥写进本机凭据服务,页面只显示"已配置"与来源,永不回传值。
一个账户都没有时,顶层字段会作为兜底账户被读。 官方 Models 页会给每个可配置的
provider 渲染一把密钥编辑器,它写的引用名是从 provider 名推出来的
ARK_CODING_API_KEY(ark-coding → ARK_CODING_API_KEY,插件无法控制)。只要
accounts 是空的,插件就把顶层 apiKeyEnv 当成一个 id 为 default、显示名
「默认账户」的 API Key 账户,顶层 apiKey 则是它的字面量密钥。这条兜底专门用来接住
上面那把编辑器:不接的话,用户粘了 key、卡片显示"已配置",聊天却报"一个可用账户都没有"。
accounts 非空时这条兜底整个不存在,顶层字段不再被任何人读。accounts —— 所以在设置页
删掉「默认账户」那一行没有持久效果,只要 apiKeyEnv 还在,下一个请求它就会回来。apiKeyEnv 显式设成空串(默认值是 ARK_CODING_API_KEY,
不设就是开着)。写成 MY-KEY 这种不合法的凭据引用名同样不会产生兜底账户。accounts 会被整份写回(老配置的迁移路径)——
兜底账户那时就变成 accounts 里的一个普通账户,不再随顶层字段出现或消失。也可以走环境变量(默认引用名 ARK_CODING_API_KEY):
export ARK_CODING_API_KEY="ark-..."
密钥优先级(每个账户各自解析):账户里的字面量 apiKey > 凭据服务 > 启动环境 / .env。
凭据引用名不合法(文法 ^[A-Za-z_][A-Za-z0-9_]*$)时跳过凭据服务,直接落到环境变量;
顶层 apiKey 只在上面的兜底账户里生效,不会补给 accounts 里的其它账户。
账户列表是账户的主要真源(配置里的 accounts)。每个账户有三样东西:类型、名字
(可选)、以及它自己的凭据来源。
「默认账户」不是配置里的账户,是 accounts 为空时的兜底。 它由顶层
apiKeyEnv(凭据引用)+ apiKey(字面量,可选)派生:id default、显示名
「默认账户」、类型 API Key。它没法走 CLI 登录,也不写进 accounts,所以删掉它之后
只要 apiKeyEnv 还在就会自己冒回来,真正关掉它得把 apiKeyEnv 设成空串。
accounts 非空时它完全不出现。保留这条路径是因为它是官方 Models 页密钥编辑器唯一
能落地的地方(详见上面「配置密钥」),代价就是这个"删不掉的默认账户"。
一次 SSO 登录同时解决三件事:聊天用的 API Key、真实套餐额度、官方模型目录。 登录一次之后插件会自己从 arkcli 的 profile 里取 key,不需要你去控制台手抄。
点「添加账户」→ 选「CLI 登录」→(可选)填个名字 → 「开始登录」,然后:
http://127.0.0.1:<端口>/… 地址,并且打不开 ——
这是正常的,那个端口开在跑 DSH 的服务器上,不在你的电脑上。插件会在服务器上把这条请求转发给 arkcli 的回环监听,登录随即完成。
每一步都在设置页里有提示,不需要你自己敲命令。如果你有办法把那个端口打通 (比如 SSH 隧道),登录也会自己完成 —— 向导在轮询,看到成功就会自动跳过去。
凭据是有时效的,到期要重新登录。 设置页会直接告诉你到哪天:插件读 arkcli
落盘的 profile expires_at(<账户 HOME>/.arkcli/config.yaml)换算成日期显示在账户行上,
快到期的会提前提示、过期了会标成需要重新登录。
为什么不写死"N 天":这是服务端策略给的值,会随租户变。实测本机一个 2026-10-16 登录的 Coding Plan 账户给的是 2026-12-15 23:59:59(UTC+8),而不是常被提到的 14 天。 与其把一个可能不对的天数印在界面上,不如把算出来的具体日期给他。
auth status里那个sts_expires_at_ms只有 15 分钟(arkcli 自己会刷新),不能拿来当"何时重登"用。
多账户怎么隔离的:arkcli 的状态目录写死在 $HOME/.arkcli,没有环境变量能改
(它的官方文档明确说了这点)。所以插件给每个 CLI 账户一个独立的 HOME
(<DSH_HOME>/ark-coding/accounts/<账户 id>),用 HOME= 起子进程。这样同一台机器上
可以同时持有多个火山引擎账号,互不覆盖。
Windows 上
HOME不起作用:Go 的os.UserHomeDir()只认%USERPROFILE%。 插件在 Windows 上会把USERPROFILE和HOME都指到该账户的目录;只设HOME会让所有账户都读写宿主的%USERPROFILE%\.arkcli,表现成"登录一个、全部串台"。
账户也可以显式指定 home,但它必须位于 <DSH_HOME>/ark-coding 之下,越界时插件
在用到它的地方直接抛错、那个账户不可用。原因是那条路径会成为 root 子进程的 HOME,
arkcli 会在它下面写 .arkcli/ 凭据:accounts 是浏览器可写的易变字段,若允许任意路径,
一次配置写入就能让 root 往任意目录写文件。日常根本用不到自定义 HOME —— 多账户隔离
靠的就是"每个账户一个派生目录"。
从控制台复制一把方舟 API Key 粘进来。它能聊天,但查不到套餐额度、也拉不到官方模型目录 —— 这不是插件的限制,是火山接口本身的划分:额度与目录在控制面,只认 SSO 身份(或 AK/SK), API Key 只是数据面凭据。所以要看到用量,至少要有一个 CLI 登录的账户。
sidebarQuota 的判据不显示,设置页里照实说"查不到",
不编数字、也不退回任何本机记账(那套机制已删除,理由见上)。accessKeyId + Secret 引用),走 Volcengine V4
签名直接打控制面。仅在没有任何 CLI 账户可用时才会去用它。设置页的模型卡片会把 arkcli 拉到的官方目录并排显示:上下文、最大输入/输出、输入模态、 思考支持、版本、生命周期状态。
它是对照,不是覆盖。 本插件内置那 12 行的容量与模态是直接打 Coding Plan 端点实测
出来的,而 arkcli 给的是方舟通用目录的元数据,两者本来就可能不一致 ——
已知的真实分歧就写在上面那张表的下面(glm-5.3-flash)。所以插件把官方值挂在旁边、
把分歧点标出来,而不是偷偷用较不可靠的来源推翻实测值。
官方目录里如果出现了这 12 个之外的模型,卡片会提示「官方目录里还有 N 个模型不在本套餐列表中」, 这是"官方又发新模型了"的信号。
| 字段 | 默认 | 说明 |
|---|---|---|
apiKeyEnv | ARK_CODING_API_KEY | 只在 accounts 为空时被读取:作为兜底账户(id default)的凭据引用,也是官方 Models 页密钥编辑器写的那个引用名。显式设成空串即关闭这条兜底;引用名不合法(如 MY-KEY)同样不产生兜底账户 |
apiKey | — | 只在 accounts 为空时被读取:作为兜底账户的字面量密钥(带 secret 角色,永不进设置描述)。账户自己的字面量密钥写在账户对象的 apiKey 上,不会补给其它账户 |
apiBase | Coding Plan v3 端点 | 换端点/代理时改这里 |
defaultMaxTokens | 65536 | 未指定输出上限时的请求值,会被逐模型上限夹住 |
requestTimeoutMs | 300000 | 首字节超时 |
streamIdleTimeoutMs | 300000 | 流空闲超时 |
visibleModels | [] | 允许出现在选择器里的模型;空 = 全部 |
modelVisibility | {} | 逐模型开关,优先于 visibleModels |
contextWindowOverrides | {} | 逐模型覆盖上下文长度,上游改容量时不用等插件更新 |
accounts | [] | 账户列表:{id, label, type, apiKeyEnv, apiKey, home}。type 是 apikey / cli;home 只有 CLI 账户用,且必须位于 <DSH_HOME>/ark-coding 之下,越界时插件在用到它的地方直接抛错、那个账户不可用。重复的 id 只保留第一个(同名账户会共用同一个 HOME 与同一组派生缓存)。空 = 回到顶层兜底账户(见上两行) |
activeAccount | '' | 偏好的账户 id,优先用它 |
accessKeyId | '' | AK/SK 备选来源的 AccessKey ID(不是秘密,明文配);留空 = 不用这条路 |
secretAccessKeyEnv | ARK_ACCESS_KEY_SECRET | AK/SK 的 Secret 走这个凭据引用(role: credential-ref),值写凭据服务、不进配置文件 |
region | cn-beijing | AK/SK 打控制面时用的区域;默认值即 usage.js 的 VOLC_DEFAULT_REGION |
arkcliPath | '' | 显式指定 arkcli 可执行文件,优先级高于「插件自装目录 → PATH」。准确性说明:该字段此前读进配置后没有消费者(显式指定不生效),从宿主接线的那一版(v0.5.0)起才真正传给 arkcli 探测 |
catalogRefreshSeconds | 3600 | 官方模型目录的刷新间隔(目录是控制面数据,不必每次轮询都拉) |
usageRefreshSeconds | 60 | 用量轮询间隔 |
sidebarQuota | auto | 左下角用量卡片:auto(有能查额度的绑定就显示)/ on / off。显式档位永远压过自动判据 |
accounts 为空时,能聊的账户就只剩顶层兜底那一个(apiKeyEnv 非空才有;见上)。
它不写进 accounts,所以设置页删掉它并不会让它消失 —— 只要 apiKeyEnv 还在,
下一个请求它就会回来;要关掉就把 apiKeyEnv 设成空串。那时设置页才会显示
"没有账户"的空状态并引导你新建。
除顶层 apiKey(secret 角色,只随组合配置文件落地)外都是易变字段:设置页保存后
加载器原地提交,下一个请求就生效,不需要重启。
| 文件 | 作用 |
|---|---|
index.js | 宿主半边:Config、OpenAI 兼容适配器、模型目录投影、模型发现、Remote 端点、arkcli 编排 |
models.js | 12 个模型的实测目录 + 官方目录对照(annotateModels) |
accounts.js | 账户归一化(apikey / cli 两种类型、accounts 为空时的兜底账户、重复 id 去重)、账户 id 清洗的唯一实现(arkcli.js re-export 这一份)、轮换标记与排序、侧边栏卡片的三态判据 |
usage.js | 套餐额度的备选来源:AK/SK 的 Volcengine V4 签名与 GetCodingPlanUsage 解析 |
arkcli.js | arkcli 桥:探测 / 一键安装(钉住 1.0.39)/ 取额度 / 取目录 / 无浏览器登录编排 |
client.js | 浏览器半边:设置页 + Models 页卡片 + 侧边栏用量,只用主题 token,不引入任何 Client 包 |
cordis.patch.yml | bundle 层:插入 llm-ark-coding 条目 |
icon.svg / locale/*.json | 插件卡片显示元数据 |
DSH 会话历史 ──► buildMessages() ──► OpenAI 兼容 messages
│ · system/developer 并成 system
│ · assistant 只回放可见文本 + tool_calls
│ · tool_call 的 type 强制成 "function"
▼
POST {apiBase}/chat/completions (stream + stream_options.include_usage)
│
▼
SSE 帧 ──► StreamChunk(block-start / text-delta / reasoning-delta /
tool-call-delta / block-end / usage / finish)
失败会归类成 DSH 的稳定失败码(AUTH / RATE_LIMIT / SERVER / TIMEOUT /
TRANSPORT / CONTEXT_WINDOW_EXCEEDED / QUOTA / EMPTY_RESPONSE / PROVIDER_ERROR),
由 dsh-llm-retry 按策略决定要不要重试。其中 PROVIDER_ERROR 承接的是流内
报出的、不属于额度类的上游错误。
图片:上层标了 offloaded 的先降级成占位文本;剩下的读成请求版(长边 1568、
编码 ≤1 MiB)并内联为 data URL。单次请求图片总量超过 20 MiB 时按 DSH 的契约抛
IMAGE_OFFLOAD_REQUIRED,让上层卸载最旧的几张再重试,而不是把一个注定超限的请求发出去。
| 套件 | 结果 | 需要什么 |
|---|---|---|
tests/usage.test.mjs | 42/42 | 不需要密钥;末尾一条要网络(真实控制面) |
tests/arkcli.test.mjs | 174/174 | 不需要密钥、不需要网络(起本地假 arkcli / sleep 做进程实验) |
tests/client.test.mjs | 167/167 | 不需要密钥、不需要网络(内存渲染 + 自建 CSS 解析器) |
tests/host.test.mjs | 80/80 | 不需要密钥、不需要网络;需要 harness 包在场(跑 apply() 与适配器本体) |
tests/stream.test.mjs | 9/9 | 喂假 SSE:不需要密钥、不需要网络;需要 harness 包在场 |
tests/adapter.test.mjs | 需要真实 key 才跑(否则打印 SKIP 并 exit 0) | 一把真实 key + 网络(打真实端点) |
合计 472 项;上面五套在 npm test 里默认全跑(adapter 无密钥时明确跳过,不会假红)。
accounts 为空时的兜底账户、重复 id 去重、CLI 账户、id 清洗),以及侧边栏卡片的三态判据
(auto 有绑定才显示 / on / off / 无法识别的档位退回 auto)。
这一段本身不碰网络 —— 唯一例外是 usage.test.mjs 末尾那条真实打控制面的可达性断言:
它用一对假 AK/SK 发真请求,期望被服务端按鉴权拒绝(以此证明签名头与请求结构被接受)。
因此这一套需要网络,而且网络故障时上游返回的错误文本可能不匹配
/AccessKey|Signature/,会把这条误报成失败(不是插件坏了)。host.test.mjs):12 个模型的目录事实与思考档位、请求体的
max_tokens 夹取与 resolveModel 的默认上限夹取、失败码映射、图片字节预算与
IMAGE_OFFLOAD_REQUIRED 的形状、options.system 是否进请求体、Retry-After 的边界
(0、过去的 HTTP-date、畸形巨值)、脏 token 计数被整形为非负安全整数、SSE 的七条边界
(末帧无换行、没有终止标记的截断、[DONE] 之后的数据、字符串错误帧、裸断连被包成
TRANSPORT、单行上限、空闲超时里 cancel() 拒绝不留未处理 rejection)、账户兜底三态与
重复 id 去重、显式 home 越界被拒、派生缓存键、目录/额度在途去重、arkcliPath 接线、
settings.configure({ auto: false }),以及宿主↔客户端八个端点描述符逐一对齐。
以前这些只存在于需要真实密钥的 adapter 套件里,默认 npm test 对 provider 本体
等于零覆盖;现在不需要密钥也会跑。accounts.js 共用同一份实现:白名单
[A-Za-z0-9._-]、去前导点、截断 64 字符、空值回退 default)、JSON 信封解析
(含"错误信封在 stderr 上")、子进程最小环境(不继承宿主的 ARK_API_KEY)、
超时击杀、按进程组杀掉被 execFileSync 连带起来的孙子进程、凭据到期时间解析、
usage plan / models search / profile apikey get 归一化、登录编排全流程。gap:6px / 窗口内 4px;窄轨道 5px / 2px)。
卡片里必须一行账户信息都没有:akc-sideMeta 在卡片上为 0 个节点。
假上下文忠实模拟 cordis 的注入守卫;假的 $mount 也照真的一样校验贡献(只收 strict codec)。isJsonValue 校验每一块,覆盖文本 / 思考 /
工具调用三条路径 —— 这条就是"输出出来后立刻失败"那个 bug 的回归测试。all streams clean)。package.json 无 playwright 依赖,tests/ 只有
*.test.mjs,其中的 CSS 断言只核对 token 名、从不计算对比度),所以这条
不可由仓库复现。要复现:起一个真实 web 服务,用 Playwright 取每个叶子文本节点,
读 getComputedStyle 的 color 与实际生效背景色,按 WCAG 相对亮度公式算比值。Completed in 4s · 1+1 等于 2. —— 不再中断。跑测试:
npm test # 四套不需要密钥的测试;其中 usage 末尾一条要网络
ARK_CODING_TEST_KEY=ark-... npm test # 再加上打真实端点的适配器测试
远端命名空间必须"谁声明、谁读取"。在 ctx.inject(['remote.settings'], cb) 的回调里读
cb.remote.credentials,cordis 会抛 cannot get property "credentials" without inject;
这个异常发生在注入回调内部,外层的 try/catch 抓不到,页面就永远停在"凭据服务未挂载"。
三个命名空间各自注入、各自读取。(对应用户 2026-10-16 实测报的那条错。)
remote.credentials 的契约是数组 + 信封:describe(refs: string[]) 返回
RemoteResult<Record<ref, CredentialInfo>>,set/unset 返回 RemoteResult<void>。
传单个字符串、或不解 ok/value 信封,都会静默拿到错误结果。
Node 的 fetch 会丢掉 Host 头。Host 在 Fetch 规范里是 forbidden header name,
Node 24 的 undici 直接静默忽略你设的值。而 arkcli 的回环回调监听会校验 Host,
头不对就是 invalid host、登录永远完不成。所以转发回调用的是 node:http.request,
它的头能原样落到 socket 上。(这个坑是写测试时对着真实二进制量出来的,不是推理出来的。)
回环监听只认 Host: 127.0.0.1:<那个端口>。逐条试过:localhost:<port>、裸
127.0.0.1、localhost、[::1]:<port>、端口差一个数字、大小写不同,全部 invalid host。
代码以 127.0.0.1:<port> 为准,并在真收到 invalid host 时自动用 localhost 再试一次,
免得将来某个版本改了校验就把功能锁死。
arkcli 的错误信封打在 stderr 上。它自己的约定是「stdout 只放结构化结果,
错误与解释走 stderr」。只看 stdout 的话,未登录时拿到的"错误信息"会是 stderr 的最后一行,
也就是一个孤零零的 }。宿主两个流都会解析。
arkcli 没有 ARKCLI_HOME,状态目录写死 $HOME/.arkcli。多账户隔离只能靠
换 HOME 起子进程(每个 CLI 账户一个),这也是插件创建
<DSH_HOME>/ark-coding/accounts/<id> 的原因。
1.0.39 的 --no-browser 不是两段式。它会在 127.0.0.1:<随机端口> 上起监听并一直等,
既不读 stdin,也不写 .sso-pending.json,所以 --code 那条路在这个版本上不可用
(包内文档描述的两段式流程与实际行为不一致 —— 以实测为准)。
客户端只收 mode: 'strict' 的参数 codec。协议里还有个 { mode: 'src-json' }(宿主网关
内部自己就用它,宿主侧也照收),但浏览器的贡献校验会当场拒掉,抛
field "input" has no strict codec —— 而它抛在整个 $mount 里,于是八个端点一起失效、
侧边栏永远停在 Loading…。不碰网络的测试全绿、真机全挂,只有真开一次页面才看得见。
现在两半都用 strict codec(create() 返回一个恒真的宽松解析器),并且把假的 $mount
改成会做同样的校验。
node_modules/.bin/arkcli 是包装脚本,不是 CLI 本身。它用 execFileSync 阻塞地去跑
真正的 Go 二进制,所以只杀包装进程会留下一个还被 init 收养、继续 LISTEN 回环端口的
孙子进程 —— 用户以为取消了,端口其实还占着。登录会话因此以 detached: true 起独立进程组,
取消时按进程组(负 pid)杀。
流式 chunk 里一个显式 undefined 属性会让整轮对话在最后一块上失败。DSH 落地每一块
前跑 snapshotJsonValue(@deepseek-ai/dsh-util-values),{ foo: undefined } 在 JSON 里
表示不出来,于是一整轮直接抛
Assistant stream chunk must be losslessly JSON-serializable。用户看到的现象是
"字都打出来了,然后突然没了" —— 因为炸的是最后那块 finish(它以前带着
replayState: undefined)。这个 bug 在 0.2.2 里就存在,不是重构引入的。
修法是两层:拿掉那一处,并在流式内容的唯一出口(parseStream)统一摘掉值为
undefined 的键 —— { foo: undefined } 与 {} 在 JSON 语义上完全等价,摘掉是忠实的。
回归测试见 tests/stream.test.mjs:它不需要网络也不需要密钥(喂假的 SSE),
并且用真的 isJsonValue 校验,而不是自己重写一遍规则。
z.boolean().default(true) 会把"用户没配"填成 true,于是"自动"这一档永远失效。
侧边栏卡片要三态(自动 / 始终显示 / 始终隐藏),而 schemastery 的默认值在
apply() 之前就补齐了 —— 写成布尔 + 默认值,代码里根本读不到 undefined。
所以档位改用 z.union(['auto','on','off']).default('auto'):显式、可回传、
设置文档里存的就是它本身。默认值不是"没配",是"配成了这个值"。
深色主题里 --dsw-alias-brand-primary 是近白色(#f9fafb),#0f1115 那种
深色才是浅色主题的品牌色。所以"品牌底 + 写死 #fff"在深色下就是白字白底:
左下角卡片上那枚「套餐额度」小药丸、主按钮、登录向导的当前步骤条全部看不见
(实测对比度 1.05:1)。正解是主题自带的 on-brand 前景 token
--dsw-alias-label-primary-foreground(浅色 = #fff,深色 = #0f1115)——
Harness 自己的 Switch 滑块用的就是它。这个主题里不许出现字面量白/黑。
状态色不能当正文色用。state-success-primary(#22c55e)和
state-warn-primary(#f59e0b)在两个主题里是同一个中间调,压在白色卡片上只有
2.28 / 2.2 对比度(AA 要 4.5)。状态类文字一律改成"浅色底
(state-*-tertiary)+ 常态文字(label-primary)+ 彩色描边"。
popover 表面在深色主题里是中灰 #61666b(--dsw-alias-bg-overlay),
次级文字色(label-secondary)压在上面只有 3.85。所以详情面板内部
一律用 label-primary 承载文字,层级改由字号/字重/大写表达。
另外进度条轨道不能用 bg-layer-2:它在浅色主题里等于卡片底色(#fff),
轨道完全看不见;用 state-idle-primary(浅 #d4d4d4 / 深 #545557)。
详情面板必须用 position: fixed,不能用 absolute。侧边栏的
sidebarCol / frame 两层都是 overflow: hidden,绝对定位会被直接裁掉;
而 fixed 只在祖先带 transform/filter/contain 时才降级 —— 真页面上量过,
卡片到 <body> 的祖先链上这三样全是 none,所以 fixed 能安全地逃出裁剪。
面板位置在点开时按卡片的 getBoundingClientRect() 算一次,滚动/改窗口就关掉,
不去追着改。
MIT。非官方集成,与火山引擎无关。