This repository was archived by the owner on Nov 9, 2026. It is now read-only.
Repository navigation
v0.8.9-rc.1 — 不再让不可转译的内容块打死会话 (preview)
Pre-release
Pre-release
v0.8.9-rc.1 — 不再让不可转译的内容块打死会话
Pre-release(预览版)。修的是 #3:
user 消息里只要出现过 harness 注解块(reasoning / tool-call),整条请求会在发出去
之前就失败;而该块已经在持久历史里,于是之后每一轮都失败、且不会自愈——用户侧表现为
「对话框完全不能用了」。
根因
lib/protocol.js三端面的 user 分支都是「不认识就抛」:只认text/image,
其余一律UNSUPPORTED_CONTENT。- 触发源不是异常输入:continuable 子代理的结算通知是 user 角色,
createSettlementMessage会把terminal.output整段展开进去,而
finalAssistantOutput()返回的是子会话最后一条 assistant 消息的原始 content 数组
(含reasoning,有时含tool-call)。 - 因此一条结算通知就能把历史写脏;
ctx.llm.stream每轮重放整段历史 → 每轮都在请求组装阶段抛错。
自动救回路径也堵死:dsh-compaction-basic的压缩请求同样是
[...input.messages, 压缩指令],走的还是本 adapter。
修复
- 丢弃而非抛错:user 消息只承载用户载荷(
text/image),注解块与 merge-extensible
的新类型一律丢弃。这与官方适配器一致——llm-deepseek的flattenText只留 text、
llm-pi-ai的userContent走default: break,两者都把UNSUPPORTED_CONTENT
留给 image。 - assistant 侧同样处理:原来对未知类型也抛,同样会打死会话。
- messages 端面的空 content:纯
reasoning的 assistant 轮原本会产出content: [],
Anthropic 拒空数组;现在改为省略该轮(与 responses 端面既有做法一致)。 - 空 user 轮兜底:块被全部丢弃时不再产出
content: [],而是回退成一条空 turn
(chat 为"",responses / messages 为一个空文本块)。 - image 仍然硬报错:用户上传的二进制内容不能被悄悄抹掉。
- 判定标准写进了
lib/protocol.js顶部的块策略注释,三端面共用。
验证
node test/smoke.mjs:47 项,46 pass + 1 skip(无 key 的活探针)。新增 2 项回归用例,
用 #3 里的结算通知形状断言:
注解块被丢弃且正文保留、全丢后是空 turn 而非空数组、污染历史后三端面组装均不再抛、
image 仍然报错。npm run build:client:产物lib/client.js与src/client/*同步(无差异)。- 触发源确证:本机 123 个会话日志扫描,7842 条非空 assistant 消息中 4103 条带
reasoning块——这类消息不是边角情况。
已知 / 本版未覆盖
- messages 端面:持久化的 tool-call
arguments不是合法 JSON 时仍抛
PROVIDER_PROTOCOL_ERROR,属于同一类「一条历史打死整会话」的风险。它有现存测试刻意断言
(宁可响也不降级),本版未改动,留待单独决定。 - 「空文本块」在 responses / messages 上游是否被接受,未做真机实测(探针需要 key)。
若上游拒收,改成「整条省略该 user 轮」是一行的事。
升级
仓库安装(link: 方式)重启即生效:
dsh plugin --profile web add github xia-sc/dsh-opencode-go
# 然后重启 dsh web,刷新页面本地源码安装用户:拉取 main 后重启 dsh web。
v0.8.9-rc.1 — unrepresentable content blocks no longer brick a conversation
Pre-release. Fixes #3: once a user message
carried a harness annotation block (reasoning / tool-call), every request failed before it
was ever sent — and because that block is durable history, every later turn failed too, with no
self-healing. On the user's side the conversation simply stopped working.
Root cause
- All three surfaces' user branches were "throw on anything unrecognised": only
text/image
were carried, everything else raisedUNSUPPORTED_CONTENT. - The producer is not exotic input. A continuable subagent's settlement notice is a user message,
andcreateSettlementMessageexpandsterminal.outputinto it — which is the child's raw final
assistantcontentarray (reasoning, and sometimes tool-call, included). - One settled subagent therefore poisoned the history, and
ctx.llm.streamreplays the whole
history every turn, so the failure repeated at request-assembly time forever. The automatic
rescue is blocked too:dsh-compaction-basicsends[...input.messages, instruction]through
the same adapter.
Fix
- Drop instead of throw: a user message carries user payload (
text/image) only;
annotation blocks and merge-extensible additions are dropped. This matches the official
adapters —llm-deepseek'sflattenTextkeeps text only andllm-pi-ai'suserContent
falls through viadefault: break; both reserveUNSUPPORTED_CONTENTfor images. - Assistant branches too: unknown types used to throw there as well, with the same
conversation-killing effect. - Empty content on the messages surface: a reasoning-only assistant turn used to emit
content: [], which the Messages API rejects; the turn is now omitted, matching what the
responses surface already did. - Empty-turn fallback: a user turn whose blocks were all dropped becomes one empty turn
(""on chat, one empty text block on responses / messages) rather thancontent: []. - Images still fail loudly: uploaded binary content must not vanish silently.
- The resulting policy is documented at the top of
lib/protocol.jsand shared by all surfaces.
Verified
node test/smoke.mjs: 47 tests, 46 pass + 1 skip (live probe needs a key). Two new regression
tests assert, using the settlement-notice shape from
#3: annotations are dropped while the
payload survives, an all-dropped turn becomes an empty turn rather than an empty array, poisoned
history no longer throws on any surface, and images still fail.npm run build:client:lib/client.jsis in sync withsrc/client/*(no diff).- Producer confirmed: scanning 123 local session logs, 4103 of 7842 non-empty assistant
messages carry areasoningblock — this input is the norm, not a corner case.
Known / not covered here
- Messages surface: stored tool-call
argumentsthat are not valid JSON still throw
PROVIDER_PROTOCOL_ERROR— the same "one history entry kills the conversation" class. It is
covered by an existing test that deliberately asserts the throw, so this release leaves it alone
pending a separate decision. - Whether responses / messages upstreams accept an empty text block was not verified live (the
probe needs a key). If they reject it, omitting the whole user turn instead is a one-line change.
Upgrade
Repo installs (link: style) apply on restart:
dsh plugin --profile web add github xia-sc/dsh-opencode-go
# then restart dsh web and refresh the pageLocal-source installs: pull main and restart dsh web.