🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

自动续写 quekenao/dsh-auto-continue-community

DeepSeek Harness
quekenao4 天前有提交已提供中文文档扫描:未发现敏感能力

模型输出被截断时自动接着写完

29.8/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/11/2 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add quekenao/dsh-auto-continue-community
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么[目录]—
dsh 原生插件 · tool
暂无时间戳·判据 →
装得上吗[实装验证]—
本站尚未做安装检查
暂无时间戳·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 4 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🟢 扫描:未发现敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。

扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/dsh-llm@deepseek-ai/dsh-web-app
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

DSH 自动续写插件:模型回复在 max-tokens 处被截断时,自动接着往下写

README

由 DeepSeek 最新模型翻译生成
dsh-auto-continue-community 插件 状态:v0.3.2,已发布至 npm。 全部验证已通过:95/95 单测、代码审查、性能基准、headless 端到端、 Web 端到端(含 2026-10-30 真实 GUI 会话验证,见「部署验证」节)。 安装 dsh plugin add @quekenao/dsh-auto-continue-community 安装后插件通过自带的 cordis.patch.yml bundle 自动激活,无需手动编辑配置。 ⚠️ 适用边界(部署前必读) 本插件仅在以下链路下经过验证,超出该范围不保证行为正确: | 维度 | 适用范围 | | 链路拓扑 | DSH + llama.cpp + 本地模型 | | DSH 版本 | 0.2.1-rc.5 | | @deepseek-ai/cordis | ~4.0.5 | | @deepseek-ai/dsh-llm / dsh-web-app | 0.2.1-rc.5 | | Node.js | >= 24(开发/测试于 v24.18.0) | | 运行端 | 仅 Web 端(dsh web 的 waterfall) | | 客户端 / CLI | 未适配,不在本版本支持范围内(我们不交付客户端支持) |
展开完整文档(README 剩余 1 节)
dsh-auto-continue-community 插件

状态:v0.3.2,已发布至 npm。
全部验证已通过:95/95 单测、代码审查、性能基准、headless 端到端、 Web 端到端(含 2026-10-30 真实 GUI 会话验证,见「部署验证」节)。

安装

dsh plugin add @quekenao/dsh-auto-continue-community

安装后插件通过自带的 cordis.patch.yml bundle 自动激活,无需手动编辑配置。

⚠️ 适用边界(部署前必读)

本插件仅在以下链路下经过验证,超出该范围不保证行为正确:

| 维度 | 适用范围 |
| 链路拓扑 | DSH + llama.cpp + 本地模型 |
| DSH 版本 | 0.2.1-rc.5 |
| @deepseek-ai/cordis | ~4.0.5 |
| @deepseek-ai/dsh-llm / dsh-web-app | 0.2.1-rc.5 |
| Node.js | >= 24(开发/测试于 v24.18.0) |
| 运行端 | 仅 Web 端(dsh web 的 waterfall) |
| 客户端 / CLI | 未适配,不在本版本支持范围内(我们不交付客户端支持) |

原因:插件依赖的 DSH 行为——llm/stream waterfall、agent/assistant-stream 事件、
agent.steer() inbox 通道、BlockAssembler 的 block-index 约定、max-tokens 时丢弃
tool-call 的 kept-filter——都是 DSH 0.2.1-rc.5 这一条版本线的特性。
更换 DSH 大版本、换用非 llama.cpp 后端、或落到客户端 / CLI 端,均超出适用范围。

机器可读的同一份信息也写在 package.json 的 dsh.scope 字段和 peerDependencies / engines 中,便于自动化工具校验。

与内部版的区别

本插件是 dsh-auto-continue(自用版)的社区版副本,针对「发社区/可分发性」做了多项整改:

| 整改 | 自用版 | 社区版(本插件) |
| 调试日志路径 | 硬编码绝对路径 | 可移植:os.tmpdir()/dsh-auto-continue-community-debug.log |
| provider 门控 | 硬编码 local-vision | 可配置(patch config 或环境变量,经 schema 校验),默认 local-vision |
| 续写参数 | 硬编码常量 | 可配置(maxContinuations、timeoutMs、providers、mode),zod schema 校验 + 加载期报错 |
| 续写架构 | 仅缓冲流模式 | 双模式:loop(实时,默认)+ stream(缓冲,回退) |
| 单元测试 | 无 | test/ 下 95 个单测(纯函数 + 集成 + 故障注入) |

功能概述

配置 provider 的本地模型流式响应因 max-tokens 被截断时,插件透明地追加模型调用,将截断的输出补完,使 agent loop 看到完整的回复。

双模式架构(v0.3.0 新增)

插件提供两种互斥的续写模式,加载时二选一(mode 配置字段,默认 loop):

| 维度 | loop(默认,实时) | stream(回退,缓冲) |
| token 实时显示 | ✅ 是——原始步与续写步各自独立流式推送,UI 实时显示 | ❌ 否——缓冲整段上游流,finish 只在流末到达 |
| 续写方式 | 通过 agent.steer() 注入一条新的 agent 步 | 通过 llm/stream waterfall 拦截 + 重调 + 合并 |
| UI 表现 | 两条独立的 assistant 消息(原始 + 续写),各自实时流式 | 一条合并的 assistant 消息,完成后一次性出现 |
| 依赖钩子 | agent/assistant-stream + agent.steer() + agent/dispose | llm/stream |
| 适用场景 | 默认推荐;实时体验 | loop 模式在你的部署中异常时的保证回退 |

设计取舍:loop 模式牺牲了「合并为一条消息」的美观,换来了实时 token 显示——这是用户的核心诉求。续写是一个独立的 agent 步(不是流拼接),因此 UI 显示两条消息,但每条都实时流式。若某次部署中 loop 模式行为异常(如 steer() 不可用),设置 mode: stream 即可回退到缓冲模式。

可配置参数

插件通过 cordis 的 Config(standard-schema,zod)声明配置契约:apply() 收到的是 经过校验并填好默认值的对象(cordis 在 apply 前对 patch config 块做校验)。

| 参数 | 默认值 | 约束 | patch config 键 | 环境变量* | 说明 |
| providers | ["local-vision"] | 字符串数组,长度 ≥ 1 | providers | DSH_AUTOCONTINUE_PROVIDERS(逗号分隔) | 仅拦截这些 provider |
| maxContinuations | 5 | 整数,1–20 | maxContinuations | DSH_AUTOCONTINUE_MAX | 单条响应链最多续写次数 |
| timeoutMs | 120000 | 整数,1–3600000 | timeoutMs | DSH_AUTOCONTINUE_TIMEOUT_MS | 单次续写超时(毫秒),超时降级回原始截断流 |
| mode | "loop" | 枚举:"loop" \| "stream" | mode | DSH_AUTOCONTINUE_MODE | 续写模式:loop(实时)或 stream(缓冲回退) |

\* 环境变量是 resolveConfig 的回退路径:仅当某字段既不在 patch config、也不在 schema 默认值中时才生效。schema 默认值已覆盖四个字段,因此正常注册下环境变量通常不触发。

校验行为(这是与"硬编码常量"版的实质区别):

- 不写 config 块 → cordis 传 undefined 给 schema,object-level .default() 将其解析为默认值,等价于上面"默认值"列。
- 写了合法 config 块 → 校验通过,值生效。
- 写了非法 config 块(如 maxContinuations: 999、providers: []、timeoutMs: 0、 mode: "bogus")→ cordis 在加载阶段抛 ValidationError,启动即报错,而不是 "配置被静默丢弃、插件没生效"。配置拼写错误从此大声失败。

实现细节:Config schema 使用 z.object({...}).default({...})(object-level default)。
字段级 .default() 只填充对象输入中缺失的字段,对 undefined 输入无效——而 cordis.patch.yml 不写 config 块时 cordis 恰好传 undefined。object-level
.default() 确保 undefined 输入被解析为默认值而非校验失败(v0.3.1 Bug #5 修复)。

配置示例

在 cordis.patch.yml 中,config 是 insert 条目上的一个字段(loader 把条目上的 config 透传给插件,与 id/name 同级):

- insert:
- id: auto-continue-community
name: "@quekenao/dsh-auto-continue-community"
config:
providers:
- local-vision
- llama-local
maxContinuations: 3
timeoutMs: 60000
mode: loop # 'loop'(默认)或 'stream'

这个 config 块会经过上面声明的 Config schema 校验:合法则生效,非法则加载时报 ValidationError。

或纯环境变量(不改 yml,走 resolveConfig 回退路径):

set "DSH_AUTOCONTINUE_PROVIDERS=local-vision,llama-local"
set "DSH_AUTOCONTINUE_MAX=3"
set "DSH_AUTOCONTINUE_TIMEOUT_MS=60000"
set "DSH_AUTOCONTINUE_MODE=stream"

工作流程

loop 模式(默认,实时)

agent loop 运行一步(runtime.stream 内部,实时流式推送 UI) │ ▼ agent/assistant-stream emit 事件(逐帧) │ ├─ 首帧(type='start')→ markLoopPending(agent)(标记流在途) │ ├─ finish 帧(type='chunk', chunk.type='finish') │ → recordLoopFinish(agent, chunk.reason.kind) │ ├─ kind === "max-tokens" → state.truncated = true │ └─ 其他 → state.truncated = false, state.count = 0(链结束) │ ├─ max-tokens 且 continuationDecision → continue: │ │ │ ├─ providerOk(agent)?
│ │ (1) 插件 meta.provider ∈ providers → 放行 │ │ (2) agent.provider ∈ providers → 放行 │ │ (3) 无法确定 provider → fail-open 放行 │ │ │ ├─ 最后一条 user message 带 CONTINUATION_MARK → 跳过(重入守卫) │ │ │ ├─ state.count ≥ maxContinuations → 跳过(预算耗尽) │ │ │ └─ 构建续写消息 + agent.steer(continuationMsg) │ │ │ ├─ 有已完成 tool call → 用 CONTINUE_PROMPT_TEXT_WITH_TOOLCALLS │ │ (告知模型 tool call 未执行、不在上下文中、不要重新发出) │ └─ 无 tool call → 用 CONTINUE_PROMPT_TEXT │ │ │ ▼ │ steer() → inbox.nextStep + wakeDriver │ │ │ ▼ │ agent loop 的 turn() 检测到 inbox.nextStep 非空 │ → 执行新一步(续写步),实时流式推送 UI │ └─ 如果续写也 max-tokens → stream observer 再次检测到 → 再次 steer()(受预算约束)如果续写 stop / tool-calls → 链结束

关键:

- 续写是独立的 agent 步,不是流拼接。UI 显示两条 assistant 消息,各自实时流式。
- 注入通道是 agent.steer()(不是 agent/pre-step):pre-step 在 step 开始前触发,而 max-tokens 检测发生在流结束时刻,此时 turn 已关闭、pre-step 不再触发。
steer() 将消息追加到 inbox.nextStep 并唤醒 driver,turn() 的循环在
inbox.nextStep 非空时保持 turn 打开,续写步立即执行。
- agent/dispose 事件清理该 agent 的 loop 状态,防止泄漏。

stream 模式(回退,缓冲)

agent loop 调用 runtime.stream(options) │ ▼ llm/stream waterfall 触发 │ ├─ provider ∉ providers → 透传(next()) │ ├─ 缓冲上游流(collectChunks) │ │ │ ▼ │ analyzeChunks: 检测 finish.kind === "max-tokens" │ │ │ ├─ 未截断 → resetCounter,原样 yield │ │ │ ├─ 截断 + 有未完成 tool call → 跳过续写,原样 yield │ │ (JSON 参数被截断,无法修复;DSH 会丢弃所有 tool-call block) │ │ │ └─ 截断(可续写)→ 检查预算 │ │ │ ├─ 预算耗尽 → 原样 yield │ │ │ └─ 有预算 → 构建续写 prompt │ │ │ ├─ 有已完成 tool call → CONTINUE_PROMPT_TEXT_WITH_TOOLCALLS │ └─ 无 tool call → CONTINUE_PROMPT_TEXT │ │ │ ▼ │ runtime.stream(continuationOptions) │ │ │ ├─ 成功 → mergeChunks(合并两段流) │ │ │ │ │ ├─ block index 偏移:continuation 的 block 加 offset,避免碰撞 │ │ ├─ usage 合并:completion_tokens 求和,prompt_tokens 取 continuation │ │ ├─ replayState 剥离:避免 BlockAssembler 长度检查失败 │ │ └─ 终端 finish:优先 continuation 的 finish │ │ │ ├─ 超时 → abort,yield 原始截断流 │ └─ 错误 → yield 原始截断流(降级) │ │ │ ▼ │ 如果续写未截断 → resetCounter │ 如果续写也截断 → 保留计数(下次调用检查预算)

合并流结构(stream 模式)

[upstream body chunks (minus finish)]
[continuation body chunks (block indices + offset, minus finish)]
[merged usage chunk]
[terminal finish chunk (replayState stripped)]

BlockAssembler 消费顺序:block-start / delta / block-end 按 index 组装, usage 最后写入,finish 最后写入。合并结构与此一致。

计数器生命周期

loop 模式(loopState Map,per agent):
- 每步流结束 → recordLoopFinish(设置 truncated 标志)
- 未截断步结束 → count 重置为 0(链结束)
- 截断 + 注入续写 → consumeContinuation(count+1)
- 预算耗尽(count ≥ maxContinuations)→ 不注入
- agent 销毁 → clearLoopState(清理)
- LRU 清理:Map 超 256 条且距上次清理 ≥60s → 淘汰最旧的,保留 128 条

stream 模式(sessionCounters Map,per session):
- 每次非截断响应 → resetCounter(重置为 0)
- 每次续写 → incCounter(+1)
- 续写也截断 → 不重置(保留计数,下次调用检查预算)
- 预算耗尽(≥ maxContinuations)→ 透传,不再续写
- LRU 清理:同上

重入守卫

续写请求的 messages 最后一条 user message 带有枚举字符串键属性 CONTINUATION_MARK ('__dsh_auto_continue_continuation__',值为 true)。两种模式都检测该标记:
- loop 模式:continuationDecision 检查 lastMsg[CONTINUATION_MARK],有标记则不二次注入。
- stream 模式:waterfall 检测到标记时直接透传,不二次缓冲、不二次续写。

为什么是 enumerable 字符串键? DSH session 层的 snapshotJsonValue 使用 Reflect.ownKeys + propertyIsEnumerable 遍历对象——任何含非枚举 own property 的对象都会被拒绝("non-JSON-serializable data")。续写消息会经 agent.steer() 进入 inbox,并被 session.append() 序列化,因此标记必须是 enumerable 的。字符串键在 JSON.stringify 中可见(session 日志中可见 __dsh_auto_continue_continuation__: true),但不在消息的 content 数组内,对 LLM 不可见。

降级策略

loop 模式:续写步本身失败(模型错误)时,agent loop 会显示错误,用户可手动重试。续写步的 max-tokens 由 stream observer 再次检测(受预算约束),通过 steer() 注入下一步。

stream 模式:续写流失败(超时、网络错误、模型错误)时,插件 yield 原始截断流。agent loop 看到 finish.kind === "max-tokens",UI 显示「已达到输出 token 上限」提示,用户可手动发送「继续」。

模式回退:若 loop 模式在你的部署中行为异常(如 steer() 不可用),设置 mode: stream 即可回退到缓冲模式。两种模式互斥,加载时二选一。

已知限制

1. loop 模式的两条消息:续写是独立的 agent 步,UI 显示两条 assistant 消息(原始 + 续写),而不是一条合并消息。这是实时流式的代价。若需合并为一条,使用 mode: stream(但会失去实时显示)。

2. sticky max-tokens(cosmetic):agent-loop 的 sticky 逻辑(turnEnds 一旦设为 max-tokens 就不再覆盖)导致即使续写步以 stop 结束,turn 仍标记为 max-tokens,UI 显示「已达到输出 token 上限」提示。这是 agent-loop 的行为,不是插件 bug。实际内容已完整,提示是多余的。

3. 未完成 tool call 不续写:如果截断发生在 tool call 的 JSON 参数中间,插件跳过续写。原因:DSH 在 max-tokens finish 时丢弃所有 tool-call block;模型没有部分 block 的上下文;重新发出同样的 tool call 仍会被同样的 max-tokens 上限截断。

4. replayState 被剥离(stream 模式):合并流的 finish chunk 中 replayState 设为 undefined。原因:continuation 的 replay envelope 只覆盖 continuation 自己的 block,合并后 block 总数不匹配,BlockAssembler 会静默丢弃。剥离后 agent loop 不附带 replay 到 assistant message(功能降级,不影响正常对话和 tool call 执行)。loop 模式不受此影响(续写是独立步,有自己完整的 replay envelope)。

5. 模型「假性截断」(false stop)不会触发续写:插件只识别协议层的 finish.reason.kind === "max-tokens"。如果本地模型在输出中途自愿停止(finish reason 为 stop,但文本停在半句话、未闭合代码块),插件按设计不注入续写——这是模型行为,不是插件 bug(2026-10-30 真实 GUI 会话实测复现:qwen3.8:64k 在 4827 token 处输出 EOS,文本停在半句,插件正确停止)。一句话:不输出的是模型本身(模型自愿停止),插件按设计不续写、行为正确。 识别「语义上没写完」需要启发式(如未闭合代码块检测),属于行为变更,不在本版本范围;v0.3.2 的 finish 日志(见「调试」节)使此类情况可被日志直接确认,无需挖事件日志。

调试

文件日志默认关闭。 插件稳定后文件日志很少用到,且每轮对话的每次 LLM 调用都会追加几行,长会话会持续增大(单个会话可达数百 KB),因此改为按需开启。

- 开启方式:启动 dsh web 前设置环境变量 DSH_AUTOCONTINUE_DEBUG=1。未设置时插件完全不写文件(连 node:fs / node:os 都不加载)。
- 日志路径(可移植):os.tmpdir()/dsh-auto-continue-community-debug.log(追加写入,所有会话共用,无轮转)。Windows 下通常是 %TEMP%。
- 启动标记:开启后进程启动时写入 process started (pid ...); plugin code with file logging is active。
- 每个 step 的 finish 都有日志(v0.3.2 新增):
- max-tokens → step ended max-tokens (continuation candidate)(+ 注入/跳过日志);
- 其他 kind(stop / tool-calls / error / aborted / 空)→ step ended <kind> (not max-tokens; chain ends, no continuation)。
这样「为什么这条响应链停在第 N 次续写、没有第 N+1 次」可以直接从 debug 日志回答(典型场景:模型在 stop 处自愿结束,插件按设计不续写),无需再挖 .zstd 会话事件日志里的 finish chunk。
- 无论开关是否开启,所有日志都写入 ctx.logger.info(cordis 内存日志);文件日志只是额外的一份落盘副本。
- 不写 process.stdout(避免 GUI 聊天视图被刷屏)。

安全模型

结论:插件本身安全,攻击面极小,不引入任何新的外部信任边界。 以下副作用面清单来自对 index.js 全量扫描(逐行核对所有可产生副作用的调用点)。

副作用面清单

| 能力 | 是否存在 | 说明 |
| 子进程 / 命令执行 | ❌ 无 | 没有 child_process / exec / spawn,不碰 shell |
| 任意网络请求 | ❌ 无 | 没有 http / fetch / net / dns;续写走 DSH 内部 runtime.stream(),不新增网络出口 |
| 动态代码执行 | ❌ 无 | 没有 eval / new Function / vm,不可被注入执行代码 |
| 任意文件读写 | ❌ 无 | 唯一的文件写是调试日志,且默认关闭 |
| 进程控制 | ❌ 无 | 没有 process.exit / abort |
| 读环境变量 | ✅ 有(只读) | DSH_AUTOCONTINUE_ 五个开关,只读不写 |
| 写调试日志 | ✅ 有(默认关) | appendFileSync 到 os.tmpdir()/dsh-auto-continue-community-debug.log |

逐条说明

1. 唯一的「写」是调试日志,且默认关闭。 不设 DSH_AUTOCONTINUE_DEBUG=1 时,连 node:fs 都不加载(动态 await import 在开关分支内),文件系统零接触。
开启后也只往 tmpdir() 里追加一个固定文件名的日志:路径是 path.join(tmpdir(), 常量),不拼接任何用户输入,无目录穿越风险。

2. 续写不扩大信任边界(关键点)。 插件没有「把数据发给新地方」——它只是让 DSH 把原本就要调用的同一个本地 llama.cpp 再调一次(loop 模式:agent.steer(continuationMsg);stream 模式:runtime.stream(continuationOptions)), provider/model/signal 原样继承自原始请求。原始请求安全(本地模型),续写就安全;它无法把数据导向云端或任意第三方。

3. 数据流是「本地 → 本地模型」,没有外泄通道。 插件处理的是 DSH 内部已有的对话内容,续写请求也回到同一个本地后端。不存在「把上下文发给外部 API」的路径。

4. 失败时是降级,不是破坏。 loop 模式续写步失败时 agent loop 显示错误; stream 模式续写超时/出错时 yield 原始截断流。都不会损坏数据、不会卡死进程。

与解耦原则的一致性

- 未注册时:零加载、零监听、零影响。
- 注册但开关关闭时:不写文件;仅在检测到 max-tokens 截断时才续写(而那本就是它被注册的目的)。
- 自身失效时:续写异常被 try/catch 捕获并降级;cordis 的 _reload 也只停用插件本身,宿主进程不受影响。

两个「非安全但相关」的注意点

- token 成本 / 资源:每次截断会多调一次本地模型,长输出最多续写 maxContinuations(默认 5)次。
是资源消耗,不是安全问题,但在受限环境要心里有数。
- provider 门控是唯一的安全闸门:插件靠「只拦截配置的 provider」来保证不误伤云端 API。
该闸门现在可配置——误把云端 provider 加进 providers,会让本应走云端优化的请求被本地续写逻辑接管。
这不是插件不安全,而是「配置错误会改变行为」。默认值为 ['local-vision'],
只要不改默认、且遵循上方「适用边界」,就不会误伤。

部署验证(2026-10-30)

Headless 验证

使用 headless 独立进程验证(dsh --profile headless --patch <overlay>),不重启 web GUI 或 llama 后端。Overlay 将 headless 默认 agent 路由到 local-vision(本地 llama.cpp 后端),maxTokens 强制 512。

| 检查项 | 结果 |
| 插件加载(apply() completed; mode=loop) | ✅ |
| provider 路由(request/header 显示 local-vision, maxTokens 512) | ✅ |
| 截断检测(3 次 step ended max-tokens) | ✅ |
| steer() 注入(injecting continuation 1/3, 2/3, 3/3) | ✅ |
| 续写次数 ≤ maxContinuations(恰好 3) | ✅ |
| 截断步 outputTokens(三次均 512) | ✅ |
| 最终步正常完成(87 tokens, stop finish) | ✅ |
| 短问题不续写 | ✅ |

Web 端到端验证

在 web surface(port 3098 并行实例,DSH_AUTOCONTINUE_DEBUG=1)上通过 web API 创建会话、发送长 prompt 验证,不重启 port 3097 主实例。agent-default-model 由 launch.patch.yml 覆盖为 local-vision/qwen3.8:64k-vision-Q5-KS,maxTokens 8192。

| 检查项 | 结果 |
| 插件加载(apply() completed; mode=loop,无 ValidationError) | ✅ |
| 截断检测(3 次 step ended max-tokens) | ✅ |
| steer() 注入(injecting continuation 1/5, 2/5, 3/5 via steer()) | ✅ |
| 续写次数 ≤ maxContinuations(3 次,默认上限 5) | ✅ |
| 截断步 outputTokens(三次均 8192) | ✅ |
| 最终步正常完成(3227 tokens, stop finish) | ✅ |
| Session 结构(4 条独立 assistant 消息 + 3 条 continuation 消息) | ✅ |
| 短问题不续写(1 条消息,26 tokens,stop,0 continuation) | ✅ |
| Provider 门控 fail-open(providerOk branch=fail-open) | ✅ |

Bug #5 发现:port 3097 主实例上截断后无续写 → 诊断发现 cordis.patch.yml 无 config: 块时,cordis 传给 zod schema 的是 undefined → z.object 拒绝 undefined → fiber INACTIVE → apply() 未执行。修复:object-level .default()(v0.3.1)。
详见 HANDOFF.md §4 Bug #5 和 §6c。

真实 GUI 会话验证(2026-10-30,port 3097 主实例)

在主实例(pid 7344 / 12272,DSH_AUTOCONTINUE_DEBUG=1)上,用 HANDOFF §2 的 Redis 10 主题 prompt 创建了真实 Web 会话(session-56e48e06-...),并解压 session.v4.jsonl.zstd(多帧 zstd,33 个事件)逐事件核对:

| 检查项 | 结果 |
| step 1 finish = max-tokens(8192 tokens)→ 注入续写 1/5 | ✅ 日志 + 事件日志双确认 |
| step 2 finish = max-tokens(8192 tokens)→ 注入续写 2/5 | ✅ 日志 + 事件日志双确认 |
| step 3 finish = stop(模型自愿 EOS,4827 tokens)→ 不注入续写(设计行为) | ✅ 事件日志 stopReason: "stop" |
| 续写次数 ≤ maxContinuations(2/5) | ✅ 预算未耗尽,链因模型 stop 正常结束 |
| debug 日志完整(appendFileSync 同步写,无缓冲丢失) | ✅ 9 行,与事件日志时间线一致 |

结论:插件逻辑健壮——两次 max-tokens 均正确续写,stop 步按设计不续写。
「2 轮后停止」的观感来自模型在 4827 token 处输出 EOS(文本停在半句,属模型「假性截断」,见「已知限制」第 5 条),不是插件故障。该验证暴露的可观测性缺口(stop 步无日志)已在 v0.3.2 修复。详见 HANDOFF.md §6d。

单元测试

纯函数(analyzeChunks、finishKind、sumUsage、mergeChunks、计数器 store getCounter/incCounter/resetCounter/pruneCounters、 loop 状态 store recordLoopFinish/markLoopPending/continuationDecision/ consumeContinuation/clearLoopState/pruneLoopState、resolveConfig)从 index.js 以 named export 导出,可在隔离环境测试,无需启动 DSH 或 llama.cpp。
bash
cd plugins/dsh-auto-continue-community
npm test # = node --test test/.test.js

覆盖点(95 个测试):
- finishKind:对象/字符串两种 finish reason 形态归一
- analyzeChunks:截断检测;tool-call 完整/不完整判定(含缺 index 的 delta 边界)
- sumUsage:null 处理、completion 求和、prompt 取 continuation
- mergeChunks:block index 偏移避免碰撞、usage 合并为单一 chunk
- 计数器:LRU 顺序、超阈值淘汰、默认值
- resolveConfig:默认值(含 mode: 'loop')、patch config 数组/字符串、env 回退、优先级
- Config schema:默认值填充(含 mode)、合法值通过、越界/空值/非法 mode 产生 issues
- apply():config 走第二参(而非不存在的 ctx.options.config);缺省 config 时降级不崩
- loop 状态:recordLoopFinish(max-tokens 保留 count / stop 重置 count)、 markLoopPending、continuationDecision(未截断/预算耗尽/重入守卫/tool-call prompt 选择)、 consumeContinuation(递增)、clearLoopState(删除)、pruneLoopState(LRU 淘汰)

文件清单

| 文件 | 说明 |
| index.js | 插件主体(双模式;纯函数为 named export) |
| package.json | 包元数据(v0.3.2;scripts.test = node --test test/*.test.js) |
| cordis.patch.yml | 插件注册(含 config 块说明头注,含 mode 字段) |
| test/analyze.test.js | 流分析/合并纯函数单测(12 tests) |
| test/counters.test.js | 计数器 LRU + resolveConfig 单测(14 tests) |
| test/config.test.js | Config schema 校验(含 mode 字段)+ apply() 第二参配置传递回归(10 tests) |
| test/loop-state.test.js | loop 状态 store 纯函数单测(17 tests) |
| test/integration.test.js | 集成测试:mock DSH 框架 waterfall,两种模式全路径(21 tests) |
| test/fault-injection.test.js | 故障注入:null/undefined/畸形帧/序列化不变量/内存泄漏(21 tests) |
| README.md | 本文档 |
| HANDOFF.md | 交接文档(验证结果、Bug 修复记录、待办事项) |
| PUBLISH-GUIDE.md | 发布指南(npm / GitHub / DSH 社区发布流程和注意事项) |

总计 95 个测试,全部通过。

修改历史

- 2026-07-12: 初版(自用版 dsh-auto-continue)+ 4 项审核修复(usage 求和、block index 偏移、null-safe 日志、LRU 清理)
- 2026-07-12:第二项审核修复(replayState 剥离、缺 index delta 判定改进)
- 2026-10-30:自用版文件调试日志改为默认关闭
- 2026-10-30:社区版(本插件)从自用版复制并整改:可移植调试日志路径、provider/参数可配置、纯函数导出 + 23 个单测。
核心算法未改动。
- 2026-10-30:修复 config 传递 bug + 引入 schema 校验。
此前 apply(ctx) 从不存在的 ctx.options.config 取值,patch config 块被静默丢弃(「可配置」只剩环境变量一条腿)。现改为 apply(ctx, config) 接收第二参,并为插件声明 zod Config schema:cordis 在加载阶段校验 config 块,合法填默认值、非法抛 ValidationError。新增 8 个单测(共 31 个),package.json 增 zod peerDependency。
- 2026-10-30:v0.3.0 — 双模式架构。新增 mode 配置字段(loop | stream,默认 loop):
- loop 模式(实时,默认):通过 agent.steer() 注入续写步,通过 agent/assistant-stream 事件检测 max-tokens,token 实时流式显示。
续写是独立的 agent 步(UI 显示两条消息),不是流拼接。
- stream 模式(缓冲,回退):保留原有的 llm/stream 拦截 + 合并逻辑,作为 loop 模式异常时的保证回退。
- 新增 DSH_AUTOCONTINUE_MODE 环境变量。
- 新增 loop 状态 store(loopState Map)+ 6 个纯函数导出。
- 新增 19 个 loop 状态单测(共 50 个)。
- apply() 根据 mode 分派到 registerLoopMode 或 registerStreamMode。
- 2026-10-30:鲁棒性加固。
- CONTINUATION_MARK 从 Symbol.for(...) 改为字符串键 __dsh_auto_continue_continuation__。
- registerLoopMode 中所有 agent.id 访问添加 null/undefined 防护。
- registerStreamMode 添加 runtime / runtime.stream 存在性检查。
- CONTINUATION_MARK 加入 named exports。
- 新增故障注入 + 集成测试(共 95 个)。
- 2026-10-30:P0 真实部署验证 + 4 个架构级 Bug 修复(headless 独立进程,不重启 web GUI)。

验证过程中发现并修复了 4 个导致 loop 模式完全不可用的 P0 Bug:

1. providerOk 始终 false:ReactLoopAgent 无顶层 .provider/.route 字段, cordis 不传 provider-bearing meta,导致 provider 门控永远拒绝。
修复:重写 providerOk 为三级判定——(1) 插件 meta.provider (2) agent.provider (3) fail-open(无法确定时放行)。

2. Stream observer 读错字段:原代码从 frame envelope 读 finish reason,实际 finish 信息在 frame.chunk.reason.kind 内(AssistantStreamFrame 的
chunk 字段)。修复:改为 frame.type==='chunk' && frame.chunk.type==='finish'
时读 frame.chunk.reason.kind。

3. agent/pre-step 是死代码:pre-step waterfall 在 step 开始前触发,而 max-tokens 检测发生在流结束时刻——此时 turn 已关闭,pre-step 不再触发。
修复:删除 pre-step 监听器,改为在 stream observer 中调用
agent.steer(continuationMsg)。steer() 将消息追加到 inbox.nextStep
并唤醒 driver,turn() 的循环在 inbox.nextStep 非空时保持 turn 打开,续写步立即执行。

4. steer() 序列化失败:DSH session 层 walkJsonValue 使用 Reflect.ownKeys + propertyIsEnumerable——任何含非枚举 own property 的对象被拒绝("non-JSON-serializable data")。
修复:CONTINUATION_MARK 改为 enumerable: true;续写消息加 id: randomUUID()(inbox.mutate() 用 message.id 做去重检测)。

验证结果:headless 截断测试 3/3 续写成功(512-token 截断确认),短问题不续写,95/95 单测通过。详见 HANDOFF.md §6。

- 2026-10-30:v0.3.1 — Bug #5 修复 + Web 端到端验证。

Web 端到端测试(port 3098 并行实例)发现插件在 web surface 完全不激活: cordis.patch.yml 无 config: 块时 cordis 传 undefined 给 zod Config schema,
z.object({...}) 无 object-level .default() 时拒绝 undefined 输入,
fiber 进入 INACTIVE,apply() 从未执行。

修复:给 Config 的 z.object 加 object-level
.default({providers, maxContinuations, timeoutMs, mode}),
让 undefined 输入解析为默认值。字段级 .default() 对 undefined 无效(只填充对象输入中缺失的字段),必须用 object-level default。

验证结果:
- Web 长文续写:4 条独立 assistant 消息(1 原始 + 3 续写), 3 次 max-tokens → 3 次 injecting continuation N/5 via steer(),最终步 3227 tokens stop finish。
- Web 短问题不续写:1 条消息,26 tokens,stop,0 continuation。
- 95/95 单测通过。详见 HANDOFF.md §4 Bug #5 和 §6c。

- 2026-10-30: 发布准备。新增 PUBLISH-GUIDE.md,记录 npm / GitHub / DSH 社区发布的完整流程和注意事项。
- 2026-11-01: npm 发布。包名改为 @quekenao/dsh-auto-continue-community,补 LICENSE(MIT)、files 白名单、publishConfig.access=public、 prepublishOnly 测试门禁。dsh plugin add @quekenao/dsh-auto-continue-community 即可安装。
- 2026-10-30: v0.3.2 — 真实 GUI 会话验证 + finish 日志可观测性。
- 真实 GUI 验证通过:在 port 3097 主实例上用 Redis 10 主题 prompt 创建真实会话(session-56e48e06-...),解压 .zstd 事件日志逐事件核对: step 1/2 max-tokens(各 8192 tokens)→ 注入续写 1/5、2/5;step 3 模型自愿 stop(4827 tokens)→ 按设计不续写。插件逻辑健壮,"2 轮后停止" 是模型 false stop 行为,非插件 bug。
- 新增 finish 日志(纯日志,零行为变更):非 max-tokens 的 step finish 也记一行 step ended <kind> (not max-tokens; chain ends, no continuation)。
此前 stop 步无任何日志,"为什么没续写"只能靠挖 .zstd 事件日志定位;现在 debug 日志(DSH_AUTOCONTINUE_DEBUG=1)可直接回答。
- 95/95 单测通过,node --check 通过。
预览分享卡片长什么样 ↓quekenao/dsh-auto-continue-community
分享 · DeepSeek Harness Hub
quekenao/dsh-auto-continue-community

模型输出被截断时自动接着写完

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部