解决方案文档(坑 / 疑难问题 / 方法论)

August 14, 2026 · View on GitHub

每条格式:现象 → 根因 → 解决 → 验证 → 位置。 文档导航见 README「Documentation」;API/契约见 docs/api.md 与 docs/engineering.md。

1. 【严重】KoboldCpp 自我派生子进程,插件停服失败

  • 现象child.kill() 后 exit 事件触发(直接子进程已死),但真正的服务器仍在运行、端口仍监听——每次真实冒烟后遗留孤儿进程。
  • 根因koboldcpp-nocuda.exe spawn 后会自我派生一个子进程充当实际服务器。原 stop()「先 graceful kill、exit 就 return」永远走不到 taskkill
  • 解决:Windows 上 stop() 无条件 taskkill /PID <pid> /T /F 杀进程树
  • 验证:真实行为测试 autostart 场景 portAfterDispose: false(§8)。
  • 位置src/launch.ts stop() / forceKill()

2. 【严重】Loader 直接加载 TS 源码失败

  • 现象 ACannot find module 'src/koboldcpp.js' —— 源码 .js 导入不被 Node type-stripping 重写。 解决 A:源码用 .ts 扩展名导入 + tsconfig allowImportingTsExtensions + rewriteRelativeImportExtensions(构建时重写为 .js;harness 官方风格)。
  • 现象 BTypeScript parameter property is not supported in strip-only mode解决 B:禁 parameter properties,用显式字段声明。
  • 验证tests/loader.spec.ts;构建后 lib/index.js 内为 ./koboldcpp.js
  • 位置tsconfig.jsonsrc/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.mjstests/fixtures/loader/cordis.yml

4. 【规范】async disposer 必须被 fiber 等待

  • 现象ctx.effect(() => () => { void server.dispose() })fiber.dispose() 仅 1 ms 返回,服务器未停。
  • 根因:disposer 返回 void → fire-and-forget。
  • 解决async disposerctx.effect(() => async () => { await server.dispose() })(fiber teardown 会 await)。
  • 验证tests/integration.spec.ts「stops the spawned server…」;真实场景 A portAfterDispose: false
  • 位置src/index.ts apply() 末尾。

5. 【环境】Node type-stripping 与 lib 类型差异

  • BufferSource 非 @types/node 全局(stream/web.d.ts 未自动加载)→ 用 ReadableStream<Uint8Array>(历史 SSE 实现,已随工具化重构移除)。
  • 默认 lib 无 esnext.asynciterablestring[] 不满足 AsyncIterable → 测试 helper 用显式 async generator。
  • 位置tsconfig.jsontypes: ["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
notrunningautoStart off + 无服务器 → isError 清晰消息,harness 完好run.isError === true 且含 "is not running";端口保持关闭

运行顺序:notrunning(快)→ autostart(后台,模型加载数分钟)→ 手动启动 koboldcpp 后 reuse每场景前确认无残留进程、端口 5001 空闲

9. 【方法论】契约驱动开发顺序

  1. 先研究 harness 规范与参考实现(docs/cookbook/adding-a-tool.mddsh-llm-deepseektool-todo),再写代码。
  2. peer 版本对齐:npm 发布的 @deepseek-ai/* 滞后于仓库 master;用 0.1.0-rc.6 匹配 master 0.1.0-rc.5 API;写代码前先查 node_modules/*/lib/types 确认导出。
  3. 测试分层:单元 → 工具(真实 ToolRuntime)→ 集成(fake 服务器)→ REAL-composition(Loader)→ 真实行为(真机三场景)。
  4. 每个「真实 bug」都补回归测试(§1 树杀、§4 async disposer、卸载/失败隔离均有测试)。
  5. 行为/API 变更必须同步 docs/api.md、README、术语表。

10. 已知限制与对策

问题现状对策
本机模型无 mmproj,视觉模型「看不到图」链路正常(图片已发送,usage 含图 tokens),模型回复看不到kcpps 配置 "mmproj" 后重启
单序列 KV cache 并发风险工具默认 exclusive 串行调度多 agent 并行时保持串行
图像 >20 MBimageFileToDataUrl 拒绝压缩图片(上限低于 KoboldCpp 默认 32 MB 请求限制)
模型加载期间 idle 停服beginStream/endStream 保护进行中调用已实现;空闲计时器在调用结束后才触发