Daemon 守护进程与 IPC 协议

SeekClaw Daemon 通过本地 IPC 向桌面端、IDE 插件和其他客户端开放 Runtime。当前协议版本为 2.1,使用一行一个 JSON 对象的 JSONL 消息,不是完整的 JSON-RPC 2.0 实现。

连接地址

  • Windows Named Pipe:\\.\pipe\seekclaw
  • Linux / macOS Unix Socket:~/.seekclaw/daemon.sock

请求和响应都必须以换行符结尾。一次连接可以在 Agent 输出过程中继续发送控制请求。

{"id":1,"method":"chat","params":{"sessionId":"20260731-120000-a1b2c3","message":"分析当前项目"}}
{"id":1,"event":"thinking","sessionId":"20260731-120000-a1b2c3","data":"正在检查项目结构"}
{"id":1,"event":"delta","sessionId":"20260731-120000-a1b2c3","data":"这是一个 .NET 项目。"}
{"id":1,"event":"done","sessionId":"20260731-120000-a1b2c3","data":"这是一个 .NET 项目。"}

响应保持统一事件信封:id 对应请求,event 表示事件类型,data 始终为字符串。result 中的结构化结果会编码成 JSON 字符串,由客户端再次解析。

协议与状态

方法 参数 说明
ping 返回 pong
protocol.info 返回协议版本、能力与方法列表
workspace.get 返回当前工作区路径、项目类型和模式
workspace.open { "path": "..." } 验证并切换 Runtime 工作区
workspace.init 初始化当前工作区的 SeekClaw 目录
agent.mode.get 返回 planreadonlyeditauto
agent.mode.switch { "mode": "edit" } 切换并持久化 Agent 模式
agent.steer { "sessionId": "...", "message": "..." } 向正在运行的 turn 添加指导,不取消当前请求

workspace.open 会清除当前连接的旧式恢复 Session。新的 chat 请求应携带 sessionId,项目任务同时携带 workspace;turn 启动时会捕获自己的工作区,之后的工作区、模式或模型切换不会改变已经运行的 turn。

执行与取消

chat 是兼容旧客户端的主要执行方法,agent.runTurnagent/runTurn 是别名。

支持视觉的模型可以接收 images 数组。每张图片包含客户端生成的 id、文件名、MIME 类型和不带 Data URL 前缀的 Base64 数据;单次最多 10 张、单张最多 10 MB、合计最多 40 MB。支持 image/pngimage/jpegimage/webpimage/gif,纯图片消息可以省略 message

{"id":9,"method":"chat","params":{"message":"比较两张图片","images":[{"id":"a","name":"before.png","mediaType":"image/png","data":"..."},{"id":"b","name":"after.webp","mediaType":"image/webp","data":"..."}]}}
{"id":10,"method":"chat","params":{"message":"修复测试","reasoningLevel":"high"}}
{"id":11,"method":"agent.cancel","params":{"requestId":10}}

正在运行的 turn 可以接收附加指导。agent.steer 会把消息放入当前 turn 的指导队列,当前模型请求结束后再加入上下文并继续下一步,不会取消或打断正在进行的请求:

{"id":12,"method":"agent.steer","params":{"sessionId":"20260731-120000-a1b2c3","message":"也请检查边界情况"}}
{"id":12,"event":"result","sessionId":"20260731-120000-a1b2c3","data":"guidance queued"}

agent.cancelrequestId 可省略,此时取消当前连接的活动 turn。取消请求本身返回 result,被取消的 chat 最终返回:

{"id":11,"event":"result","data":"cancellation requested for 10"}
{"id":10,"event":"cancelled","data":"取消前已生成的部分文本"}

session.get 返回的消息中,assistant 消息带 modelRefprovider/model),用于界面标注每条回答来自哪个模型。

流式事件包括 thinkingdeltasteerstatusimage_viewtool_starttool_doneworkflowworkflowdetailsstepkind(start/think/tool/verify/repair/compact/done/error)、labeldetail,用于客户端实时绘制执行流程图。steer 表示附加指导已经进入当前 turn 的上下文;image_viewdetails.imageId 指明模型正在查看哪张上传图片。终止事件包括 donecancellederror

其他方法

方法 参数 说明
session.list { "workspace": "...", "global": false, "includeArchived": true } 按工作区或全局范围列出 Session
session.get { "id": "...", "workspace": "..." } 读取 Session 及其消息
session.update { "id": "...", "title": "...", "reasoningLevel": "high" } 更新标题、思考深度等 Session 元数据
session.archive { "id": "...", "archived": true } 归档或恢复 Session
session.delete { "id": "..." } 永久删除 Session
session.truncate { "id": "...", "keepCount": 5 } 保留前 N 条消息("重新生成"用),返回剩余消息数
session.resume { "id": "...", "global": false } 恢复 Session
session.new { "workspace": "...", "reasoningLevel": "high" }{ "global": true } 创建并绑定一个新 Session
model.list 列出可用的 provider/model 引用
model.catalog 返回模型详情、能力和活动状态
model.switch { "model": "provider/model" } 切换并持久化模型
model.test { "model": "provider/model" } 发送最小真实请求测试模型
prompt.optimize { "text": "...", "model": "provider/model" } 使用指定或当前模型优化提示词,不创建 Session
doctor 返回 Runtime 健康检查摘要
doctor.run 返回结构化 Runtime 与 Provider 检查
lock.list 返回当前文件写锁的“文件-任务”占用表快照
factory.reset 清空全局配置、会话与 SQLite 数据,恢复出厂设置并重建数据库
shutdown 取消全部活动 turn,返回 bye 并优雅停止 Daemon

Session 方法可传 workspace 指向具体项目,也可传 global: true 使用不绑定目录的全局 Session 空间。includeArchived 控制列表是否包含已归档任务。Desktop 在第一次发送消息时才调用 session.new,因此新建一个空白任务不会产生无内容的 Session。

reasoningLevel 使用统一枚举:nonelowmediumhighmaxxhighultra。它不是 Provider API 参数;Runtime 会根据模型能力和 Provider 适配后再生成请求。xhigh/ultra 属于扩展档位,默认最高能力为 max,DeepSeek 会明确将二者转换为 max

Desktop 管理方法

Desktop 设置中心通过结构化方法管理与 CLI 相同的配置,不直接读取配置文件。显式存储的 Provider apiKey 会通过 provider.list 返回,以便 Desktop 直接显示和编辑。Runtime 不会从环境变量读取 API Key;MCP 环境变量仍仅返回键名,不返回值。

方法 说明
routing.get 返回 { "failoverEnabled": true },读取失败转移开关
routing.set 参数 { "failoverEnabled": false },保存失败转移开关
schedule.list 列出全部计划任务(含下次运行时间与上次结果)
schedule.create / schedule.update 参数 { "name", "prompt", "cron", "workspace"? , "enabled"? }cron 为 5 段表达式(分 时 日 月 周,本地时区),非法表达式返回错误
schedule.toggle 参数 { "id", "enabled" },启用/暂停任务
schedule.delete 参数 { "id" },删除任务
schedule.run 参数 { "id" },立即触发一次执行(后台运行,不等执行完成即返回 started
profile.list/upsert/use/remove 管理运行 Profile
provider.list/upsert/use/remove/test 管理和测试 Provider
mcp.list/upsert/remove/reload 管理、重连 MCP Server 并刷新工具注册
skill.list/toggle 查询和启用/禁用 Skill
usage.get 返回按模型聚合的调用、Token、成本和延迟

project.upsert 拒绝把用户主目录或 SeekClaw 全局状态目录(~/.seekclaw)注册为项目;project.remove 支持 keepSessions: true,清理无效项目行时保留数据库中的会话。

Daemon 会先建立 IPC 监听,再在后台初始化 MCP。mcp.reload、MCP 配置修改和工作区切换都会先注销旧工具与 Prompt,再串行连接新配置。

计划任务:Daemon 启动时内置调度器,每 10 秒检查一次启用中的计划任务,到点后为每个到期任务新建一个 Session 并执行一次 Agent turn(使用隔离 Runtime,与普通任务共享 HttpClient 连接池、熔断器与文件写锁),结果(成功/失败/取消与截断输出)记录回任务。手动 schedule.run 会立即触发一次并重新计算下次运行时间。单次执行有超时保护(agent.scheduledTurnTimeoutSeconds,默认 30 分钟),超时会中止并记录,避免卡死的回合永久阻塞调度器。计划任务只在 Daemon 运行时才会触发;桌面端关闭后不会自动补跑。

Daemon 对每个 chat 请求按 sessionId 创建独立的 Agent turn。每个 turn 使用隔离的 Runtime、Prompt/Skills、MCP 注册和事件订阅,因此同一连接或多个连接可以并发运行任意数量的任务;并发度由 CPU、内存、Provider 和本机 I/O 性能共同决定。管理类配置写入仍然串行化,避免配置文件互相覆盖,但不会阻塞已经启动的 Agent turn。

Daemon 进程内维护一个集中式文件写锁协调器(Task Coordinator),作为所有并发 turn 的文件锁唯一信任源。write_fileedit_file 在修改文件前会先按工作区+文件路径申请写锁:锁空闲则授予并完成修改后释放;锁被其他任务占用时工具最多等待 30 秒,超时后返回明确的失败提示(附当前持有者),要求模型等待并重新读取最新内容后重试。edit_file 在拿到锁后才读取文件,因此修改总是作用于磁盘上的最新内容,避免并行任务互相覆盖或产生无效编辑。turn 结束时(含取消)协调器会释放该任务持有的全部锁。可通过 lock.list 查看当前占用表。

Desktop 的 Daemon 生命周期

打包版 Desktop 启动时先连接本地端点。若没有 Daemon,它会从 resources/runtime 启动自包含 Runtime,并在 24 次短间隔探测内等待端点就绪。Desktop 只记录自己创建的子进程;退出时向该实例发送 shutdown,等待其退出,超时后才终止进程。若启动时连接的是外部 Daemon,退出 Desktop 只断开连接。

An unhandled error has occurred. Reload 🗙

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.