@agentdock/daemon
v0.0.10
Published
PC daemon + CLI for AgentDock — ACP abstraction, TransportHandler, process management
Downloads
1,302
Readme
@agentdock/daemon
PC 守护进程 + CLI — 在用户电脑上运行,管理 AI Agent 子进程并将事件上报到 server。
概述
daemon 是 AgentDock 的本地端,安装在开发者的 PC 上(npx agentdock),职责:
- 进程管理:启动/停止 AI Agent 子进程(Claude Code 等)
- ACP 抽象:统一接口适配不同 CLI(ACP / exec / SDK 路线)
- 事件转换:将 Agent 原始输出(JSONL)映射为 wire 协议的 SessionEnvelope
- 上报:通过 Socket.IO 将事件推送到 server
- 控制服务器:本地 HTTP 端点,供其他进程查询/停止 daemon
在架构中的位置
wire → crypto → daemon依赖 wire(协议定义)和 crypto(签名认证),独立运行在用户 PC 上。
模块结构
src/
├── cli.ts # CLI 入口(agentdock start/stop/status)
├── cli-args.ts # 命令行参数解析
├── acp.ts # AgentBackend — 子进程管理(spawn/readline/stdin/signal)
├── transport.ts # TransportHandler — 按 agent 类型配置超时/过滤器
├── claude-transport.ts # Claude JSONL 解析器(content block → AgentMessage[])
├── copilot-transport.ts # Copilot CLI / OpenCode ACP ndJSON 解析器(M9-1)
├── agentSpawn.ts # AgentType → BackendOptions 分派器(M9-2,6 种 agent)
├── sessionMapper.ts # SessionMapper — AgentMessage → SessionEnvelope 转换 + turn 边界
├── sessionBridge.ts # AgentBackend → SessionMapper → ServerClient 桥接(M5-2)
├── serverClient.ts # SDK 封装(machine-scoped 连接 + sendEnvelope + createSession)
├── interactionDetector.ts # 交互检测器(question / permission-request 提取)
├── rpcHandler.ts # RPC 方法处理(approve/deny/answer/abort/stop)
├── secretManager.ts # 密钥文件读写(master secret 持久化)
├── keyManager.ts # DEK 生命周期管理(生成/封装/解封)
├── pairingClient.ts # 配对客户端(Curve25519 keypair + NaCl Box + 轮询)
├── lock.ts # PID 文件锁(O_EXCL 排他创建 + EPERM/ESRCH 区分)
├── state.ts # 状态文件读写(pid/port/startTime/version/heartbeat)
├── paths.ts # 标准路径(~/.agentdock/daemon.pid, daemon.state.json, daemon.log)
├── controlServer.ts # 本地 HTTP 控制服务器(/health /sessions /stop)
├── daemonLoop.ts # 主循环(心跳 + server 连接 + graceful shutdown)
└── index.ts # Barrel exportsAPI 参考
CLI 命令
npx agentdock [--agent claude] [--port 9876] # 启动 daemon
npx agentdock stop # 停止 daemon
npx agentdock status # 查看状态AgentBackend (acp.ts)
import { createAgentBackend } from '@agentdock/daemon';
import type { AgentBackend, BackendOptions, AgentMessage } from '@agentdock/daemon';
const backend: AgentBackend = createAgentBackend({
command: 'claude',
args: ['--output-format', 'stream-json'],
cwd: '/path/to/project',
spawnFn: childProcess.spawn,
});
// 启动子进程
await backend.start();
// 监听消息
backend.onMessage((msg: AgentMessage) => {
// { type: 'text' | 'tool-call' | 'service' | ..., content: ... }
});
// 发送输入
backend.sendMessage('user input');
// 取消当前操作
backend.cancel(); // SIGINT
// 清理
backend.dispose(); // SIGTERM + cleanupClaude JSONL 解析器 (claude-transport.ts)
import { parseClaudeLine } from '@agentdock/daemon';
// 解析 Claude Code 的 JSONL 输出
// 处理多 content block(thinking + text + tool_use)
const messages: AgentMessage[] = parseClaudeLine(jsonLine);
// 返回数组而非单个(L13 教训:不丢弃后续 block)SessionMapper (sessionMapper.ts)
import { createSessionMapper } from '@agentdock/daemon';
const mapper = createSessionMapper({ userId: 'user-123' });
// AgentMessage → SessionEnvelope
const envelopes = mapper.map(agentMessage);
// 自动管理 turn 边界(turn-start/turn-end)文件锁 (lock.ts)
import { acquireLock, releaseLock, isLocked } from '@agentdock/daemon';
const result = acquireLock(); // O_EXCL 排他创建
// result: { acquired: true } | { acquired: false, pid: 12345 }
releaseLock(); // 先验证 PID 所有权再删除(L12 教训)控制服务器 (controlServer.ts)
import { startControlServer } from '@agentdock/daemon';
const server = await startControlServer({ port: 9876 });
// GET /health → { status: 'ok', pid, uptime }
// GET /sessions → 活跃会话列表
// POST /stop → graceful shutdownDaemon 主循环 (daemonLoop.ts)
import { createDaemonLoop } from '@agentdock/daemon';
const loop = createDaemonLoop({
heartbeatInterval: 30000,
onShutdown: async () => {
/* cleanup */
},
});
await loop.start();
// 心跳 + 信号处理(SIGINT/SIGTERM) + graceful shutdown开发
# 运行测试(131 tests)
pnpm --filter @agentdock/daemon test
# 覆盖率(目标 80%+,实际 97.3%)
pnpm --filter @agentdock/daemon test:coverage
# 本地启动 daemon
pnpm --filter @agentdock/daemon dev设计决策
- PID 文件锁用 O_EXCL:排他创建防止 TOCTOU 竞态(L12 教训)
- parseClaudeLine 返回数组:处理多 content block,不丢弃数据(L13 教训)
- ACP 抽象层:统一接口,后续添加 Codex/Gemini Transport 只需实现新 handler
- EPERM vs ESRCH 区分:判断进程是否存活时,EPERM = 存活但无权限,ESRCH = 不存在
- 控制服务器用原生 http:轻量级,不引入 Fastify 依赖(daemon 应尽量小)
