解决方案文档(坑 / 疑难问题 / 方法论)
August 14, 2026 · View on GitHub
每条格式:现象 → 根因 → 解决 → 验证 → 位置。 文档导航见 README「Documentation」;API/契约见 docs/api.md 与 docs/engineering.md。
1. 【严重】KoboldCpp 自我派生子进程,插件停服失败
- 现象:
child.kill()后 exit 事件触发(直接子进程已死),但真正的服务器仍在运行、端口仍监听——每次真实冒烟后遗留孤儿进程。 - 根因:
koboldcpp-nocuda.exespawn 后会自我派生一个子进程充当实际服务器。原stop()「先 graceful kill、exit 就 return」永远走不到taskkill。 - 解决:Windows 上
stop()无条件taskkill /PID <pid> /T /F杀进程树。 - 验证:真实行为测试 autostart 场景
portAfterDispose: false(§8)。 - 位置:
src/launch.tsstop()/forceKill()。
2. 【严重】Loader 直接加载 TS 源码失败
- 现象 A:
Cannot find module 'src/koboldcpp.js'—— 源码.js导入不被 Node type-stripping 重写。 解决 A:源码用.ts扩展名导入 + tsconfigallowImportingTsExtensions+rewriteRelativeImportExtensions(构建时重写为.js;harness 官方风格)。 - 现象 B:
TypeScript parameter property is not supported in strip-only mode。 解决 B:禁 parameter properties,用显式字段声明。 - 验证:
tests/loader.spec.ts;构建后lib/index.js内为./koboldcpp.js。 - 位置:
tsconfig.json;src/launch.ts构造器。
3. 【Windows】Loader 配置中的绝对路径必须是 file:// URL
- 现象:cordis.yml
name: 'C:\...'→ERR_UNSUPPORTED_ESM_URL_SCHEME: Received protocol 'c:'。 - 根因:Node ESM loader 只接受相对路径或 URL;盘符路径被当作协议。
- 解决:动态组合用
new URL('../src/index.ts', import.meta.url).href;静态 fixture 用相对路径(相对 config 文件目录解析)。 - 验证:
tests/real-driver.mjs三场景通过。 - 位置:
tests/real-driver.mjs;tests/fixtures/loader/cordis.yml。
4. 【规范】async disposer 必须被 fiber 等待
- 现象:
ctx.effect(() => () => { void server.dispose() })时fiber.dispose()仅 1 ms 返回,服务器未停。 - 根因:disposer 返回 void → fire-and-forget。
- 解决:async disposer:
ctx.effect(() => async () => { await server.dispose() })(fiber teardown 会 await)。 - 验证:
tests/integration.spec.ts「stops the spawned server…」;真实场景 AportAfterDispose: false。 - 位置:
src/index.tsapply()末尾。
5. 【环境】Node type-stripping 与 lib 类型差异
BufferSource非 @types/node 全局(stream/web.d.ts未自动加载)→ 用ReadableStream<Uint8Array>(历史 SSE 实现,已随工具化重构移除)。- 默认 lib 无
esnext.asynciterable,string[]不满足AsyncIterable→ 测试 helper 用显式 async generator。 - 位置:
tsconfig.json(types: ["node"])。
6. 【测试】mock 服务器的健康检查吞掉脚本队列
- 现象:工具测试大面积
SERVER_NOT_RUNNING——ensure()的GET /v1/models消耗了脚本化行为。 - 解决:mock 对
/v1/models无条件应答(不消耗脚本),仅/v1/chat/completions消耗脚本。 - 位置:
tests/mock-server.ts。
7. 【测试】超时杀进程树导致孤儿服务器污染环境
- 现象:长跑冒烟被超时 kill 后 spawn 的 koboldcpp 成孤儿;后续测试「复用」孤儿,误判停服失败。
- 解决(方法论):真实长测用分离后台 + 轮询日志;每次长测前先清理全部 koboldcpp 进程与端口;脚本内置 SAFETY EXIT 兜底。
8. 【方法论】真实行为测试三场景(REAL)
tests/real-driver.mjs 对真实 koboldcpp + 真实模型执行三个场景:
| 场景 | 验证点 | 判据 |
|---|---|---|
autostart | 自拉服务器、工具调用、dispose 停服(树杀) | run.value.text === 'REAL_OK';portAfterDispose === false |
reuse | 复用外部服务器、不杀外部 | 响应快(数百 ms);portAfterDispose === true |
notrunning | autoStart off + 无服务器 → isError 清晰消息,harness 完好 | run.isError === true 且含 "is not running";端口保持关闭 |
运行顺序:notrunning(快)→ autostart(后台,模型加载数分钟)→ 手动启动 koboldcpp 后 reuse。每场景前确认无残留进程、端口 5001 空闲。
9. 【方法论】契约驱动开发顺序
- 先研究 harness 规范与参考实现(
docs/cookbook/adding-a-tool.md、dsh-llm-deepseek、tool-todo),再写代码。 - peer 版本对齐:npm 发布的
@deepseek-ai/*滞后于仓库 master;用0.1.0-rc.6匹配 master0.1.0-rc.5API;写代码前先查node_modules/*/lib/types确认导出。 - 测试分层:单元 → 工具(真实 ToolRuntime)→ 集成(fake 服务器)→ REAL-composition(Loader)→ 真实行为(真机三场景)。
- 每个「真实 bug」都补回归测试(§1 树杀、§4 async disposer、卸载/失败隔离均有测试)。
- 行为/API 变更必须同步 docs/api.md、README、术语表。
10. 已知限制与对策
| 问题 | 现状 | 对策 |
|---|---|---|
本机模型无 mmproj,视觉模型「看不到图」 | 链路正常(图片已发送,usage 含图 tokens),模型回复看不到 | kcpps 配置 "mmproj" 后重启 |
| 单序列 KV cache 并发风险 | 工具默认 exclusive 串行调度 | 多 agent 并行时保持串行 |
| 图像 >20 MB | imageFileToDataUrl 拒绝 | 压缩图片(上限低于 KoboldCpp 默认 32 MB 请求限制) |
| 模型加载期间 idle 停服 | beginStream/endStream 保护进行中调用 | 已实现;空闲计时器在调用结束后才触发 |