Skip to content

Commit 0c53796

Browse files
amDosionunraid
andauthored
feat: restore daemon supervisor and remoteControlServer command (#170)
Reverse-engineer the missing daemon + remoteControlServer implementation by tracing the call chain from existing code: - src/daemon/main.ts: restore from stub to full supervisor (spawn/monitor workers, exponential backoff restart, graceful shutdown) - src/daemon/workerRegistry.ts: restore from stub to worker dispatcher (remoteControl kind → runBridgeHeadless()) - src/commands/remoteControlServer/: new slash command /remote-control-server (alias /rcs) for managing the daemon from REPL - build.ts + scripts/dev.ts: enable DAEMON feature flag Both official CLI 2.1.92 and our codebase had the command registered in commands.ts but the directory and daemon implementation were missing. The bottom layer (runBridgeHeadless in bridgeMain.ts) was already complete. Co-authored-by: unraid <local@unraid.local>
1 parent 4b44047 commit 0c53796

7 files changed

Lines changed: 826 additions & 6 deletions

File tree

DEV-LOG.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,106 @@
11
# DEV-LOG
22

3+
## Daemon + Remote Control Server 还原 (2026-04-07)
4+
5+
**分支**: `feat/daemon-remote-control-server`
6+
7+
### 背景
8+
9+
`src/commands.ts` 注册了 `remoteControlServer` 命令(双重门控 `feature('DAEMON') && feature('BRIDGE_MODE')`),但 `src/commands/remoteControlServer/` 目录缺失,`src/daemon/main.ts``src/daemon/workerRegistry.ts` 均为 stub。官方 CLI 2.1.92 中情况一致——Anthropic 已预留注册点和底层 `runBridgeHeadless()` 实现,但中间层(daemon supervisor + command 入口)未发布。
10+
11+
通过逐级反向追踪调用链还原完整实现:
12+
```
13+
/remote-control-server (slash command)
14+
→ spawn: claude daemon start
15+
→ daemonMain() (supervisor,管理 worker 生命周期)
16+
→ spawn: claude --daemon-worker=remoteControl
17+
→ runDaemonWorker('remoteControl')
18+
→ runBridgeHeadless(opts, signal) ← 已有完整实现
19+
→ runBridgeLoop() → 接受远程会话
20+
```
21+
22+
### 实现
23+
24+
#### 1. Worker Registry(`src/daemon/workerRegistry.ts`
25+
26+
从 stub 还原为 worker 分发器:
27+
- `runDaemonWorker(kind)``kind` 分发到不同 worker 实现
28+
- `runRemoteControlWorker()` 从环境变量(`DAEMON_WORKER_*`)读取配置,构造 `HeadlessBridgeOpts`,调用 `runBridgeHeadless()`
29+
- 区分 permanent(`EXIT_CODE_PERMANENT = 78`)和 transient 错误,supervisor 据此决定重试或 park
30+
- SIGTERM/SIGINT 信号处理,通过 `AbortController` 传递给 bridge loop
31+
32+
#### 2. Daemon Supervisor(`src/daemon/main.ts`
33+
34+
从 stub 还原为完整 supervisor 进程:
35+
- `daemonMain(args)` 支持子命令:`start`(启动)、`status``stop``--help`
36+
- `runSupervisor()` spawn `remoteControl` worker 子进程,通过环境变量传递配置
37+
- 指数退避重启(2s → 120s),10s 内连续崩溃 5 次则 park worker
38+
- permanent exit code(78)直接 park,不重试
39+
- graceful shutdown:SIGTERM → 转发给 worker → 30s grace → SIGKILL
40+
- CLI 参数支持:`--dir``--spawn-mode``--capacity``--permission-mode``--sandbox``--name`
41+
42+
#### 3. Remote Control Server 命令(`src/commands/remoteControlServer/`
43+
44+
**`index.ts`** — Command 注册:
45+
- 类型 `local-jsx`,名称 `/remote-control-server`,别名 `/rcs`
46+
- 双 feature 门控:`feature('DAEMON') && feature('BRIDGE_MODE')` + `isBridgeEnabled()`
47+
- lazy load `remoteControlServer.tsx`
48+
49+
**`remoteControlServer.tsx`** — REPL 内 UI:
50+
- 首次调用:前置检查(bridge 可用性 + OAuth token)→ spawn daemon 子进程
51+
- 再次调用:弹出管理对话框(停止/重启/继续),显示 PID 和最近 5 行日志
52+
- 模块级 state 跨调用保持 daemon 进程引用
53+
- graceful stop:SIGTERM → 10s grace → SIGKILL
54+
55+
#### 4. Feature Flag 启用
56+
57+
`build.ts` / `scripts/dev.ts``DEFAULT_BUILD_FEATURES` / `DEFAULT_FEATURES` 新增 `DAEMON`
58+
59+
DAEMON 仅有编译时 feature flag 门控,无 GrowthBook gate。
60+
61+
### `/remote-control` 的区别
62+
63+
| | `/remote-control` | `/remote-control-server` (daemon) |
64+
|---|---|---|
65+
| 模式 | 单会话,REPL 内交互式 bridge | 多会话,daemon 持久化服务器 |
66+
| 生命周期 | 跟 REPL 会话绑定 | 独立后台进程,崩溃自动重启 |
67+
| 并发 | 1 个远程连接 | 默认 4 个,可配置 `--capacity` |
68+
| 隔离 | 共享当前目录 | 支持 `worktree` 模式隔离 |
69+
| 底层 | `initReplBridge()` | `runBridgeHeadless()``runBridgeLoop()` |
70+
71+
### 修改文件
72+
73+
| 文件 | 变更 |
74+
|------|------|
75+
| `build.ts` | `DEFAULT_BUILD_FEATURES` 新增 `DAEMON` |
76+
| `scripts/dev.ts` | `DEFAULT_FEATURES` 新增 `DAEMON` |
77+
| `src/daemon/main.ts` | 从 stub 还原为 supervisor 实现 |
78+
| `src/daemon/workerRegistry.ts` | 从 stub 还原为 worker 分发器 |
79+
| `src/commands/remoteControlServer/index.ts` | **新增** command 注册 |
80+
| `src/commands/remoteControlServer/remoteControlServer.tsx` | **新增** REPL UI |
81+
82+
### 验证
83+
84+
| 项目 | 结果 |
85+
|------|------|
86+
| `bun run build` | ✅ 成功 (490 files) |
87+
| tsc 新文件检查 | ✅ 无新增类型错误 |
88+
89+
### 使用方式
90+
91+
```bash
92+
# CLI 直接启动 daemon
93+
bun run dev daemon start
94+
bun run dev daemon start --spawn-mode=worktree --capacity=8
95+
96+
# REPL 内
97+
/remote-control-server # 或 /rcs
98+
```
99+
100+
前提:需要 Anthropic OAuth 登录(`claude login`)。
101+
102+
---
103+
3104
## /ultraplan 启用 + GrowthBook Fallback 加固 + Away Summary 改进 (2026-04-06)
4105

5106
**分支**: `feat/ultraplan-enablement`

build.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const DEFAULT_BUILD_FEATURES = [
2828
'KAIROS_BRIEF',
2929
'AWAY_SUMMARY',
3030
'ULTRAPLAN',
31+
// P2: daemon + remote control server
32+
'DAEMON',
3133
]
3234

3335
// Collect FEATURE_* env vars → Bun.build features

scripts/dev.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ const DEFAULT_FEATURES = [
3535
// P1: API-dependent features
3636
"EXTRACT_MEMORIES", "VERIFICATION_AGENT",
3737
"KAIROS_BRIEF", "AWAY_SUMMARY", "ULTRAPLAN",
38+
// P2: daemon + remote control server
39+
"DAEMON",
3840
];
3941

4042
// Any env var matching FEATURE_<NAME>=1 will also enable that feature.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { feature } from 'bun:bundle'
2+
import { isBridgeEnabled } from '../../bridge/bridgeEnabled.js'
3+
import type { Command } from '../../commands.js'
4+
5+
function isEnabled(): boolean {
6+
if (!feature('DAEMON') || !feature('BRIDGE_MODE')) {
7+
return false
8+
}
9+
return isBridgeEnabled()
10+
}
11+
12+
const remoteControlServer = {
13+
type: 'local-jsx',
14+
name: 'remote-control-server',
15+
aliases: ['rcs'],
16+
description:
17+
'Start a persistent Remote Control server (daemon) that accepts multiple sessions',
18+
isEnabled,
19+
get isHidden() {
20+
return !isEnabled()
21+
},
22+
immediate: true,
23+
load: () => import('./remoteControlServer.js'),
24+
} satisfies Command
25+
26+
export default remoteControlServer

0 commit comments

Comments
 (0)