pi-claude-supervisor 0.5.3 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -2
- package/README.cn.md +24 -15
- package/README.md +27 -15
- package/docs/architecture.md +43 -27
- package/docs/automation-hardening-plan.md +2 -1
- package/docs/autonomy-target.md +9 -6
- package/docs/engineering-plan.md +1 -1
- package/docs/implementation-review.md +8 -0
- package/docs/testing.md +22 -10
- package/package.json +1 -1
- package/src/decision-session-store.ts +8 -1
- package/src/index.ts +12 -9
- package/src/policy.ts +98 -1
- package/src/reviewer.ts +64 -9
- package/src/supervisor.ts +6 -4
- package/src/types.ts +3 -1
- package/src/verifier.ts +18 -1
- package/src/worker/environment.ts +51 -3
- package/src/worker/process-adapter.ts +366 -79
- package/src/worker/process-tree.ts +218 -0
- package/src/worker/tmux-adapter.ts +669 -25
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented here.
|
|
4
4
|
|
|
5
|
+
## [0.5.4](https://github.com/btnalit/pi-claude-supervisor/compare/v0.5.3...v0.5.4) (2026-09-15)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* harden unattended local lifecycle ([4f68760](https://github.com/btnalit/pi-claude-supervisor/commit/4f6876044cd8be00b0376239377b02b70aae8473))
|
|
11
|
+
|
|
5
12
|
## [0.5.3](https://github.com/btnalit/pi-claude-supervisor/compare/v0.5.2...v0.5.3) (2026-09-15)
|
|
6
13
|
|
|
7
14
|
|
|
@@ -43,10 +50,12 @@ All notable changes to this project will be documented here.
|
|
|
43
50
|
- Replace shell-policy lexical bypasses with quote-aware parsing, nested-shell inspection and protected Git-ref checks; deny dynamic shell expansions while preserving literal argv values, and reject arbitrary non-Claude automatic executables.
|
|
44
51
|
- Normalize malformed custom Reviewer values to blocking reports, persist recovery baselines and the resolved Claude executable identity, filter automatic environment variables with a deny-by-default allowlist, and compare the exact startup HEAD at the adapter spawn boundary.
|
|
45
52
|
- Keep protected CI checks on the exact checked-out commit while giving automatic-mode fixtures a local validation branch and an owned deterministic Claude executable.
|
|
53
|
+
- Add a Supervisor-owned automatic tmux bridge that renders Claude stream-json in a live PTY and carries structured records through private framing instead of an independent event sidecar; adopted sessions remain manual-only.
|
|
54
|
+
- Add parent-identity tmux guardians, guarded Linux bootstrap readiness/cleanup, explicit nested-Agent/background-reviewer denial, and tolerant Reviewer prose/fence/repeated-JSON parsing with separate display-truncation markers.
|
|
46
55
|
|
|
47
|
-
###
|
|
56
|
+
### Release readiness
|
|
48
57
|
|
|
49
|
-
-
|
|
58
|
+
- The exact-head read-only review, real editable Claude `2.1.270` repair/reacceptance drill and cleanup/lease evidence are recorded in `docs/automation-hardening-plan.md`. The review's pathname TOCTOU concern is explicitly accepted as a false positive for the trusted local-development threat model; host-side broker isolation remains future work for an untrusted-worker mode.
|
|
50
59
|
|
|
51
60
|
### Added
|
|
52
61
|
|
package/README.cn.md
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
|
|
9
9
|
用于 Pi 的 Claude Code Worker 监督扩展。MVP 中 Pi 负责生命周期、状态机、策略门和独立验收;Worker 只是被显式启动的子进程。
|
|
10
10
|
|
|
11
|
-
> `v0.5.
|
|
12
|
-
> process pipe,不是 PTY
|
|
11
|
+
> `v0.5.3` 已发布为单 Worker recovery 基线。默认手动 transport 是无额外依赖的
|
|
12
|
+
> process pipe,不是 PTY;自动模式支持 Claude JSONL 或 Supervisor 自有的 tmux bridge,被接管的
|
|
13
|
+
> tmux session 仍仅限手动交互。当前工作树已实现
|
|
13
14
|
> repairable/persistent 能力拆分、可取消验收/Reviewer、证据完整性门禁、启动前
|
|
14
15
|
> preflight 和阶段进度通知;真实 Claude Code `2.1.270` 允许编辑的
|
|
15
16
|
> repair/reacceptance 演练已在隔离临时 worktree 通过。确认的产品目标是本地开发
|
|
16
17
|
> 完全无人值守;详见 [自动化目标](docs/autonomy-target.md)。代码进入远程仓库或
|
|
17
|
-
> main/integration 分支必须经过独立边界,Worker 不拥有 push/merge
|
|
18
|
-
> JSONL,tmux 保留为手动交互 transport。
|
|
18
|
+
> main/integration 分支必须经过独立边界,Worker 不拥有 push/merge 权限。
|
|
19
19
|
>
|
|
20
20
|
> **无人值守状态:** 自动模式会自主完成本地修改、测试、有限修复、验收、独立 Review
|
|
21
21
|
> 和本地提交检查;无法形成候选时自动挂起为不可发布候选。可选出站通知不授予权限,
|
|
@@ -55,9 +55,10 @@ export PI_CLAUDE_SUPERVISOR_HUMAN_WEBHOOK_FORMAT=generic
|
|
|
55
55
|
# export PI_CLAUDE_SUPERVISOR_HUMAN_WEBHOOK_SECRET='shared-secret'
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
`exit`
|
|
60
|
-
|
|
58
|
+
自动模式支持 `claude-jsonl` 和 Supervisor 自有的 tmux bridge;JSONL 的 `result`、
|
|
59
|
+
`control_request` 和进程 `exit` 事件会唤醒 Decision Worker。tmux bridge 把结构化记录
|
|
60
|
+
通过同一个 live PTY 的私有 terminal framing 传回适配器,不创建独立 JSONL sidecar;
|
|
61
|
+
`adopt-tmux` 仍是手动模式。本版本固定按已验证设备的 Claude CLI `2.1.270` 运行,跨版本兼容性不在本轮范围内。
|
|
61
62
|
|
|
62
63
|
然后在 Pi 中使用:
|
|
63
64
|
|
|
@@ -114,8 +115,9 @@ Claude CLI `2.1.270` 运行,跨版本兼容性不在本轮范围内。
|
|
|
114
115
|
自动模式会将 Decision Worker 会话持久化到状态目录。Pi 非正常重启后,`/supervise sessions`
|
|
115
116
|
会显示 `recoverable` 任务;显式执行 `/supervise recover [--takeover] <task-id>` 会恢复 Decision Worker 上下文并
|
|
116
117
|
重新启动 Claude Worker,不会静默恢复或重复执行任务。旧 Pi 进程已退出且租约确认旧 Worker
|
|
117
|
-
进程组已消失且 cgroup 仍是真实、可读取的空边界时,才可显式添加 `--takeover`;缺失、仍存活或无法确认的 Worker
|
|
118
|
-
Worker
|
|
118
|
+
进程组已消失且 cgroup 仍是真实、可读取的空边界时,才可显式添加 `--takeover`;缺失、仍存活或无法确认的 Worker 会被拒绝。手动 owned tmux
|
|
119
|
+
Worker 可在重启后使用 `adopt-tmux`,而不是 takeover;自动 bridge 会由 parent-death guardian
|
|
120
|
+
在 Supervisor 消失时终止,不提供自动 session 的重接管。
|
|
119
121
|
自动模式下,Decision Worker 在任务授权范围内自动处理普通问题、测试失败和修复轮次,记录假设和证据;无法形成可交付候选时自动挂起并保留证据,而不是要求人工必须在线。可通过 `PI_CLAUDE_SUPERVISOR_REQUIRE_LOCAL_COMMIT=0` 或 task `autonomy.requireLocalCommit` 关闭本地 commit 要求,但自动模式仍要求有效 Git baseline 和非保护 worktree;远程 push 和 main/integration merge 仍由独立边界控制。
|
|
120
122
|
|
|
121
123
|
`v0.5.0` 已完成并发布“多命令验收—独立只读 Reviewer—结构化修复轮次—再次验收”闭环。
|
|
@@ -124,7 +126,7 @@ Worker 应使用 `adopt-tmux`,而不是 takeover。
|
|
|
124
126
|
Reviewer 只能使用 `read`、`grep`、`find`、`ls`,不会修改工作树或批准权限。自动模式在
|
|
125
127
|
Worker 启动前捕获 git baseline,要求完整的 baseline-relative tracked/commit/untracked evidence,
|
|
126
128
|
并在默认情况下要求 Worker 在非保护分支本地 commit;无效输出、证据不完整、重复 finding、P0/P1 或预算耗尽
|
|
127
|
-
会自动挂起候选。自动模式拒绝 process-pipe
|
|
129
|
+
会自动挂起候选。自动模式拒绝 process-pipe,并在模型执行前检查目录、可执行文件、依赖
|
|
128
130
|
和 cgroup;Worker 环境会过滤远程仓库凭据并禁用 Git 全局凭据 helper。详见 [自动化目标](docs/autonomy-target.md)。协同多 Worker 属于后续独立开发阶段,
|
|
129
131
|
自动模式只接受裸的直接 Claude 命令名,会固定解析后的操作者拥有的可执行文件,并请求 fail-closed 的 Claude Code Bash sandbox;任意自定义可执行文件和显式可执行路径会在自动模式拒绝。需要固定路径时设置 `PI_CLAUDE_SUPERVISOR_TRUSTED_CLAUDE`。手动/自定义 Worker 的完整 host-level sandbox 仍需由集成方提供。
|
|
130
132
|
|
|
@@ -134,8 +136,10 @@ Worker 启动前捕获 git baseline,要求完整的 baseline-relative tracked/
|
|
|
134
136
|
|
|
135
137
|
```bash
|
|
136
138
|
export PI_CLAUDE_SUPERVISOR_TRANSPORT=tmux
|
|
137
|
-
export PI_CLAUDE_SUPERVISOR_WORKER='claude
|
|
138
|
-
# tmux
|
|
139
|
+
export PI_CLAUDE_SUPERVISOR_WORKER='claude'
|
|
140
|
+
# 当前手动 tmux 也要求 Linux(用于 pane identity 和清理);可使用 cgroup auto/off。
|
|
141
|
+
# 设置 PI_CLAUDE_SUPERVISOR_MODE=auto 启用 Supervisor 自有的自动 bridge;自动 tmux
|
|
142
|
+
# 要求 Linux cgroup v2 和 parent-death guardian,缺失时会 fail closed。
|
|
139
143
|
# 接管非默认 tmux server 时可选:
|
|
140
144
|
# export PI_CLAUDE_SUPERVISOR_TMUX_SOCKET=/path/to/tmux.sock
|
|
141
145
|
```
|
|
@@ -143,8 +147,12 @@ export PI_CLAUDE_SUPERVISOR_WORKER='claude --permission-mode plan'
|
|
|
143
147
|
`/supervise start <task>` 会在私有 tmux server 中启动 Claude,并返回可复制的 attach 命令。
|
|
144
148
|
可以在另一个终端 attach 到同一个 PTY,观察或人工输入。多行消息通过 tmux buffer 和 Enter
|
|
145
149
|
发送,不会把消息拼接进 shell 命令;`pipe-pane` 记录原始输出,`capture-pane` 检测稳定的 Claude
|
|
146
|
-
输入提示,并复用 watchdog
|
|
147
|
-
|
|
150
|
+
输入提示,并复用 watchdog、审计和独立验收流程。自动模式会在 pane 内启动 bridge:它运行
|
|
151
|
+
Claude stream-json、把可读输出渲染到附着的终端,并通过同一 PTY 的私有 framing 返回结构化
|
|
152
|
+
记录;bridge 及其后代放入受 Supervisor 管理的 Linux cgroup,并由 parent-death guardian
|
|
153
|
+
保护;任一 containment 机制不可用时 fail closed。适配器直接从 PTY 原始 pipe 解析,因此没有
|
|
154
|
+
独立 JSONL sidecar。自动模式拒绝被接管的 session;Supervisor 自有 bridge 支持自动输入串行化、
|
|
155
|
+
权限响应、turn 完成和 stop。
|
|
148
156
|
|
|
149
157
|
如果 Claude 已由你在 tmux 中启动,可以显式接管且不会重放原始任务:
|
|
150
158
|
|
|
@@ -159,7 +167,8 @@ export PI_CLAUDE_SUPERVISOR_WORKER='claude --permission-mode plan'
|
|
|
159
167
|
`tmux kill-session`。`/supervise takeover <task-id>` 会暂停 Decision Worker 自动发送,只有
|
|
160
168
|
`/supervise resume-auto <task-id>` 才恢复。
|
|
161
169
|
|
|
162
|
-
PTY
|
|
170
|
+
PTY 屏幕文字本身不是 Claude JSONL,不能把屏幕文字当作结构化权限证据;只有 Supervisor bridge
|
|
171
|
+
的私有 framing 记录才是结构化证据。TUI 决策应按任务授权策略处理并记录;无法形成候选时可以自动挂起,不要求人工持续在线。普通终端里已经运行的 Claude 不能安全迁移进 tmux;`--resume`
|
|
163
172
|
是读取历史的新进程,不是实时 attach。实时测试请使用 plan/read-only 参数。
|
|
164
173
|
|
|
165
174
|
可以从不同工作目录启动多个任务会话;活动会话不能共享同一 cwd,建议每个任务使用独立 worktree:
|
package/README.md
CHANGED
|
@@ -10,10 +10,10 @@ A policy-gated [Pi](https://pi.dev) extension for supervising a Claude Code work
|
|
|
10
10
|
The MVP keeps Pi in control of lifecycle, state, policy and verification while the
|
|
11
11
|
worker remains an explicitly started child process.
|
|
12
12
|
|
|
13
|
-
> **Release status:** `v0.5.
|
|
13
|
+
> **Release status:** `v0.5.3` is the released single-Worker recovery baseline. The
|
|
14
14
|
> default manual transport is dependency-free process pipes, not PTY. Automatic
|
|
15
|
-
> supervision uses Claude JSONL
|
|
16
|
-
>
|
|
15
|
+
> supervision uses Claude JSONL or the Supervisor-owned tmux bridge; adopted tmux
|
|
16
|
+
> sessions remain manual-only. Repairable-vs-persistent capabilities,
|
|
17
17
|
> cancellable verification, evidence completeness gates, startup preflight and phase
|
|
18
18
|
> progress reporting. A real edit-capable Claude Code `2.1.270` repair/reacceptance
|
|
19
19
|
> drill passed in an isolated temporary worktree. The confirmed product target is
|
|
@@ -148,6 +148,10 @@ candidate from crossing the remote/main boundary. Automatic mode rejects explici
|
|
|
148
148
|
`process-pipe` and preflights runtime prerequisites. Invalid output, unavailable
|
|
149
149
|
evidence, duplicate findings, P0/P1 findings and exhausted repair budgets park the
|
|
150
150
|
candidate without waiting for a human; see [the autonomy target](docs/autonomy-target.md).
|
|
151
|
+
The automatic tmux bridge carries Claude stream-json records inside the same live PTY
|
|
152
|
+
as display output using private terminal framing; it does not create an independent
|
|
153
|
+
structured-event sidecar. Automatic tmux accepts only Supervisor-owned sessions,
|
|
154
|
+
while `adopt-tmux` remains manual-only.
|
|
151
155
|
Coordinated multi-worker scheduling is a later milestone; CI uses deterministic fake
|
|
152
156
|
Workers/replay fixtures, and real multi-worker Claude tests remain authenticated
|
|
153
157
|
manual Spikes. Full host-level sandboxing and low-privilege execution for custom
|
|
@@ -175,9 +179,11 @@ For an interactive Claude Code window, opt in to the tmux transport:
|
|
|
175
179
|
|
|
176
180
|
```bash
|
|
177
181
|
export PI_CLAUDE_SUPERVISOR_TRANSPORT=tmux
|
|
178
|
-
export PI_CLAUDE_SUPERVISOR_WORKER='claude
|
|
179
|
-
# tmux
|
|
180
|
-
#
|
|
182
|
+
export PI_CLAUDE_SUPERVISOR_WORKER='claude'
|
|
183
|
+
# Manual tmux currently requires Linux for process identity and cleanup;
|
|
184
|
+
# it may use cgroup mode auto/off.
|
|
185
|
+
# Set PI_CLAUDE_SUPERVISOR_MODE=auto for the Supervisor-owned automatic bridge;
|
|
186
|
+
# automatic tmux additionally requires Linux cgroup v2 and a parent-death guardian.
|
|
181
187
|
# Optional, only when adopting a non-default tmux server:
|
|
182
188
|
# export PI_CLAUDE_SUPERVISOR_TMUX_SOCKET=/path/to/tmux.sock
|
|
183
189
|
```
|
|
@@ -187,10 +193,14 @@ literal attach command. Use that command in another terminal to watch or
|
|
|
187
193
|
manually interact with the same PTY; attaching is optional for unattended local
|
|
188
194
|
development. The adapter sends multi-line input through
|
|
189
195
|
tmux buffers and Enter, never by interpolating the message into a shell command.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
196
|
+
In automatic mode the Supervisor starts a bridge in the pane: it runs Claude's
|
|
197
|
+
stream-json protocol inside the live PTY, contains the bridge and descendants in
|
|
198
|
+
an owned cgroup, fails closed when that containment or the Linux guardian is
|
|
199
|
+
unavailable, renders readable deltas for the attached terminal, and returns
|
|
200
|
+
structured records through private terminal framing on the same PTY.
|
|
201
|
+
The adapter parses those records from the raw PTY pipe, so there is no independent
|
|
202
|
+
JSONL event sidecar. Automatic mode refuses adopted sessions; use JSONL or the owned
|
|
203
|
+
bridge for unattended decisions, repair and protected command enforcement.
|
|
194
204
|
|
|
195
205
|
A session that you started yourself can be explicitly adopted without replaying
|
|
196
206
|
the task:
|
|
@@ -211,13 +221,15 @@ kill-session` yourself when the adopted window should be closed.
|
|
|
211
221
|
`/supervise takeover <task-id>` disables automatic Decision Worker messages;
|
|
212
222
|
resume them only with `/supervise resume-auto <task-id>`.
|
|
213
223
|
|
|
214
|
-
PTY screen text is not Claude JSONL and must not be treated as structured
|
|
215
|
-
permission evidence
|
|
224
|
+
PTY screen text is not itself Claude JSONL and must not be treated as structured
|
|
225
|
+
permission evidence; only the Supervisor bridge's private framed records are
|
|
226
|
+
authoritative. TUI decisions follow the configured autonomy policy and are
|
|
216
227
|
recorded; an unresolved task may be parked without requiring a human to remain
|
|
217
228
|
online. A normal terminal Claude process cannot be migrated into tmux, and
|
|
218
|
-
`--resume` is historical recovery rather than live PTY attach.
|
|
219
|
-
survive a Pi disconnect and require an explicit `adopt-tmux` after
|
|
220
|
-
|
|
229
|
+
`--resume` is historical recovery rather than live PTY attach. Manual owned tmux
|
|
230
|
+
sessions survive a Pi disconnect and require an explicit `adopt-tmux` after
|
|
231
|
+
restart; automatic owned sessions are terminated by their parent-death guardian
|
|
232
|
+
when the Supervisor disappears. Use plan/read-only flags for live testing.
|
|
221
233
|
|
|
222
234
|
## Development
|
|
223
235
|
|
package/docs/architecture.md
CHANGED
|
@@ -77,24 +77,25 @@ one capability from the other.
|
|
|
77
77
|
|
|
78
78
|
This is a control-boundary fixture and headless transport. It does not emulate a
|
|
79
79
|
terminal. Manual compatibility mode remains `process-pipe`; automatic mode
|
|
80
|
-
(`PI_CLAUDE_SUPERVISOR_MODE=auto`) defaults to Claude JSONL and
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
(`PI_CLAUDE_SUPERVISOR_MODE=auto`) defaults to Claude JSONL and can select the
|
|
81
|
+
Supervisor-owned tmux bridge. The bridge uses the CLI contract validated by the
|
|
82
|
+
fixed-version spike, renders the stream in the pane and carries structured records
|
|
83
|
+
through private framing on the same PTY; explicit adoption remains manual-only.
|
|
83
84
|
|
|
84
85
|
A worker exit automatically triggers cleanup, and terminal status waits for
|
|
85
|
-
that cleanup to be confirmed (or reports a cleanup error). On Linux
|
|
86
|
-
|
|
87
|
-
`required` mode performs a preflight and fails before Claude starts if cgroup
|
|
88
|
-
attachment or cleanup is unavailable.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
When cgroup v2 is unavailable,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
86
|
+
that cleanup to be confirmed (or reports a cleanup error). On Linux, manual
|
|
87
|
+
workers may use cgroup v2 automatically when the current user cgroup is writable;
|
|
88
|
+
the `required` mode performs a preflight and fails before Claude starts if cgroup
|
|
89
|
+
attachment or cleanup is unavailable. Automatic JSONL workers always require the
|
|
90
|
+
same preflight and a guarded cgroup bootstrap; automatic startup fails closed on
|
|
91
|
+
non-Linux hosts or when the boundary cannot be established. Cgroup cleanup kills
|
|
92
|
+
descendants even when they call `setsid()` or create another process group.
|
|
93
|
+
|
|
94
|
+
When cgroup v2 is unavailable, manual mode falls back to detached process-group
|
|
95
|
+
cleanup. That fallback is not recursive: `setsid()` descendants can escape, and
|
|
96
|
+
PID reuse between leader exit and cleanup is a host-level limitation. Production
|
|
97
|
+
deployments that require an atomic boundary should use a service-manager scope,
|
|
98
|
+
Job Object, pidfd-aware reaper, or equivalent supervisor.
|
|
98
99
|
Pi owns graceful `SIGTERM`/`SIGINT` handling and invokes the extension's
|
|
99
100
|
`session_shutdown` hook. The extension does not install a second `process.exit()`
|
|
100
101
|
handler, avoiding races with Pi terminal restoration and other extensions. `SIGSTOP`
|
|
@@ -104,25 +105,37 @@ host-fatal signals.
|
|
|
104
105
|
## tmux/PTY transport
|
|
105
106
|
|
|
106
107
|
`TmuxWorkerAdapter` is an explicit second transport, selected with
|
|
107
|
-
`PI_CLAUDE_SUPERVISOR_TRANSPORT=tmux`.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
`PI_CLAUDE_SUPERVISOR_TRANSPORT=tmux`. Both owned and adopted tmux currently
|
|
109
|
+
require Linux because pane identity and cleanup use `/proc`; manual tmux may use
|
|
110
|
+
cgroup `auto`/`off`. Automatic owned tmux additionally creates a required Linux
|
|
111
|
+
cgroup for the bridge and its descendants; it is rejected when cgroup v2 or the
|
|
112
|
+
parent-death guardian is unavailable.
|
|
113
|
+
An owned manual worker gets a private tmux server/socket and executes the
|
|
114
|
+
validated Claude command directly in the pane. An owned automatic worker instead
|
|
115
|
+
starts the Supervisor bridge through a cgroup-joining pane bootstrap, so its
|
|
116
|
+
bridge identity is not a manual adoption target. The worker
|
|
112
117
|
environment is supplied to the tmux server through the same least-privilege
|
|
113
118
|
environment builder; credentials are not copied into a file; credential-shaped
|
|
114
119
|
command arguments are rejected.
|
|
115
120
|
`load-buffer`, bracketed `paste-buffer` and `send-keys Enter` provide the input
|
|
116
121
|
boundary without interpolating a task into a shell command. C0/C1 terminal
|
|
117
|
-
control bytes are rejected; CRLF is normalized to a newline.
|
|
122
|
+
control bytes are rejected; CRLF is normalized to a newline. In automatic mode,
|
|
123
|
+
the adapter snapshots the trusted direct Claude process and checks both the
|
|
124
|
+
required Linux cgroup and process tree on every poll; a newly executed Claude or
|
|
125
|
+
Reviewer descendant is a runtime policy failure and the owned session is stopped.
|
|
126
|
+
This supplements the lexical Bash/file-tool boundary and is disabled for
|
|
127
|
+
manual/adopted sessions.
|
|
118
128
|
|
|
119
129
|
The transport has three deliberately separate observations:
|
|
120
130
|
|
|
121
131
|
- `pipe-pane` provides an append-only raw PTY log for output polling and audit;
|
|
122
132
|
- `capture-pane` provides a bounded screen snapshot used only for stable prompt
|
|
123
133
|
detection and human display;
|
|
124
|
-
-
|
|
125
|
-
|
|
134
|
+
- the Supervisor-owned bridge emits Claude stream-json records as private framed
|
|
135
|
+
PTY control data; `pipe-pane` carries those records to the adapter without an
|
|
136
|
+
independent JSONL sidecar;
|
|
137
|
+
- Claude's own transcript, when available, remains the structured history. Ordinary
|
|
138
|
+
screen text is never relabeled as JSONL or permission evidence.
|
|
126
139
|
|
|
127
140
|
For an owned initial turn, the adapter emits a synthetic `turn_completed` only
|
|
128
141
|
after output activity and two stable input-prompt observations. Adopting an idle
|
|
@@ -139,9 +152,12 @@ process identity before attaching. Every later input, capture and signal uses
|
|
|
139
152
|
that immutable pane target; a replacement process is refused. Adopted sessions
|
|
140
153
|
are not owned: stop and Pi shutdown detach rather than kill them. Tmux commands
|
|
141
154
|
and serialized input waits have bounded deadlines so shutdown cannot hang
|
|
142
|
-
forever.
|
|
143
|
-
|
|
144
|
-
|
|
155
|
+
forever. Manual sessions started by the adapter survive a Pi disconnect, but recovery
|
|
156
|
+
after restart is explicit re-adoption; automatic sessions are intentionally
|
|
157
|
+
terminated by their parent-death guardian when the Supervisor disappears. The
|
|
158
|
+
extension never claims to attach to an arbitrary non-tmux PTY. Startup cleanup always attempts the
|
|
159
|
+
private tmux server teardown, including after partial session creation, and a
|
|
160
|
+
confirmed `kill-server` is sufficient cleanup evidence. A normal Claude
|
|
145
161
|
`--resume` starts another process from history and is not a live PTY migration.
|
|
146
162
|
|
|
147
163
|
## State machine
|
|
@@ -144,7 +144,8 @@ diff: (none)
|
|
|
144
144
|
- Reviewer evidence 使用任务 baseline-relative diff、baseline 后 commit summaries、受限 untracked regular-file 内容、路径组件/symlink 门禁,并对 incomplete/truncated fail-closed。
|
|
145
145
|
- Acceptance 子进程、证据收集和 Reviewer 共享 abort signal;Pi UI 可看到 startup、Worker heartbeat、acceptance、review、repair 和 candidate/decision phases。
|
|
146
146
|
- 自动模式启动前检查运行目录、cwd、Worker 可执行文件、transport 依赖和 required cgroup;显式
|
|
147
|
-
`process-pipe`
|
|
147
|
+
`process-pipe` 仍仅限手动模式,Supervisor 自有 tmux bridge 可进入自动模式,被接管的
|
|
148
|
+
session 仍仅限手动 PTY transport。
|
|
148
149
|
- Reviewer/Decision Worker 只解析 assistant message 边界的最终文本;permission pending 会在自动响应后清除,候选状态不会被错误解除;扩大的 exec buffer 避免普通大测试报告被误判为命令失败。
|
|
149
150
|
|
|
150
151
|
本轮已完成:
|
package/docs/autonomy-target.md
CHANGED
|
@@ -28,13 +28,15 @@ complete audit trail. These are reliability and containment mechanisms, not requ
|
|
|
28
28
|
to approve every development action. Automatic mode also records and revalidates a full existing
|
|
29
29
|
repository baseline, captures the startup HEAD and requires that exact HEAD again at
|
|
30
30
|
final pre-spawn, requires a non-protected branch and a pinned operator-owned direct Claude
|
|
31
|
-
JSONL Worker, and by default requires a local commit before a candidate is deliverable.
|
|
31
|
+
JSONL Worker or Supervisor-owned tmux bridge, and by default requires a local commit before a candidate is deliverable.
|
|
32
32
|
|
|
33
33
|
## 2. Hard authority boundary
|
|
34
34
|
|
|
35
|
-
Automatic Worker supervision uses the structured JSONL transport
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
Automatic Worker supervision uses either the structured JSONL transport or a
|
|
36
|
+
Supervisor-owned tmux bridge. The bridge runs Claude's stream-json protocol inside the live
|
|
37
|
+
PTY, renders a human-readable display, and returns private framed records through the same
|
|
38
|
+
PTY; adopted tmux sessions remain manual-only. The Worker and local automation do **not**
|
|
39
|
+
receive authority or credentials for:
|
|
38
40
|
|
|
39
41
|
- pushing code to a remote repository;
|
|
40
42
|
- merging into `main` or another protected integration branch;
|
|
@@ -100,8 +102,9 @@ Automatic mode implements the local loop: policy decisions allow ordinary local
|
|
|
100
102
|
continue/redirect/answer/repair, acceptance and independent Review run without a human callback,
|
|
101
103
|
and unresolved situations become `blocked` candidates. The default task autonomy is unattended, requires a local commit, and permits two bounded
|
|
102
104
|
Decision Worker request retries. Automatic startup rejects non-Git/detached/bare/protected
|
|
103
|
-
repository states, malformed baselines, startup-HEAD races,
|
|
104
|
-
non-Claude or untrusted executable identities before Worker
|
|
105
|
+
repository states, malformed baselines, startup-HEAD races, the unstructured
|
|
106
|
+
process-pipe transport and non-Claude or untrusted executable identities before Worker
|
|
107
|
+
startup. The resolved
|
|
105
108
|
executable identity is persisted with the Decision Worker recovery record and must match
|
|
106
109
|
again during recovery.
|
|
107
110
|
|
package/docs/engineering-plan.md
CHANGED
|
@@ -36,7 +36,7 @@ Claude Code Worker
|
|
|
36
36
|
4. Supervisor 因误判导致无限循环、危险操作或不可审计的修改。
|
|
37
37
|
5. 人工无法随时接管或恢复任务。
|
|
38
38
|
|
|
39
|
-
**当前状态:`v0.5.
|
|
39
|
+
**当前状态:`v0.5.3` 已正式发布,已完成固定 Claude Code `2.1.270` 稳定性验证、单 Worker recovery、真实只读 Review drill、隔离临时 worktree 的允许编辑 repair/reacceptance drill、exact-head 独立 Review 和受保护发布。当前工作树已落实 repairable/persistent 能力拆分、verifying stop、paused watchdog、baseline-relative repository evidence、可取消验收/Reviewer、启动 preflight、无人值守权限决策、local-commit enforcement、候选挂起和阶段进度通知。自动模式现在要求 direct Claude JSONL 或 Supervisor 自有 tmux bridge、完整 Git baseline、非保护分支,并请求不可用即失败的 Claude Code sandbox 和无出站域名;任意自定义可执行文件不会进入自动模式。Legacy human/takeover APIs 仅保留显式兼容控制;普通不确定性不再阻塞本地循环。协同多 Worker、手动/自定义集成的 host-level 低权限和网络隔离仍是独立后续里程碑。**
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
@@ -39,6 +39,14 @@ boundary.
|
|
|
39
39
|
|
|
40
40
|
## Residual risks and follow-up hardening
|
|
41
41
|
|
|
42
|
+
The final independent review also raised a pathname TOCTOU concern for Claude
|
|
43
|
+
file-tool authorization. That finding is intentionally recorded as a false positive
|
|
44
|
+
for this release's trusted local-development threat model: normal edits may replace
|
|
45
|
+
file contents, but automatic workers are not treated as hostile same-UID filesystem
|
|
46
|
+
actors, and this policy is a metadata guard rather than a host filesystem isolation
|
|
47
|
+
boundary. A future untrusted-worker mode would need a host-side broker or an OS
|
|
48
|
+
sandbox that prevents `.git` writes.
|
|
49
|
+
|
|
42
50
|
These are verified limitations and follow-up work after the automatic boundary
|
|
43
51
|
hardening:
|
|
44
52
|
|
package/docs/testing.md
CHANGED
|
@@ -57,7 +57,9 @@ uses Claude Code 2.1.270 at
|
|
|
57
57
|
`/home/yancao/.local/share/mise/installs/claude/2.1.270/claude`, including real
|
|
58
58
|
owned tmux turns, pause/resume, and restart re-adoption.
|
|
59
59
|
The adapter regression suite also verifies event subscription, parsed
|
|
60
|
-
`permission_request` events,
|
|
60
|
+
`permission_request` events, the exact nested `control_response` envelope, and
|
|
61
|
+
that an automatic tmux Worker cannot launch a second Claude executable from a
|
|
62
|
+
Worker-created script.
|
|
61
63
|
The automation spike additionally exercises a real Pi SDK Decision Worker with
|
|
62
64
|
Claude: ordinary completion, harmless Bash permission handling, and an
|
|
63
65
|
`AskUserQuestion` denial-to-text fallback followed by automatic verification.
|
|
@@ -118,19 +120,24 @@ relevant checks from a clean host perspective.
|
|
|
118
120
|
|
|
119
121
|
Automatic mode is enabled with `PI_CLAUDE_SUPERVISOR_MODE=auto`; it defaults to
|
|
120
122
|
JSONL and routes `result`, permission, and process-exit events to the persistent
|
|
121
|
-
Pi Decision Worker.
|
|
123
|
+
Pi Decision Worker. Setting `PI_CLAUDE_SUPERVISOR_TRANSPORT=tmux` selects the
|
|
124
|
+
Supervisor-owned live bridge, which carries the same structured records through
|
|
125
|
+
private framing on the PTY rather than an independent JSONL sidecar. Task autonomy
|
|
126
|
+
defaults to unattended local work, a required
|
|
122
127
|
local commit on a non-protected task branch and two bounded Decision Worker retries. Configure
|
|
123
128
|
`PI_CLAUDE_SUPERVISOR_REQUIRE_LOCAL_COMMIT=0` or task `autonomy.requireLocalCommit`
|
|
124
129
|
only to disable the local-commit deliverability check; automatic mode still requires a Git
|
|
125
|
-
baseline and non-protected worktree.
|
|
126
|
-
|
|
130
|
+
baseline and non-protected worktree. Adopted tmux sessions remain manual-only; the automatic bridge requires
|
|
131
|
+
Supervisor ownership. `process-pipe` remains the manual compatibility mode. Candidate/failure notification is optional and outbound-only through
|
|
127
132
|
`PI_CLAUDE_SUPERVISOR_HUMAN_WEBHOOK_URL`; it is not a synchronous approval
|
|
128
133
|
callback. Approval callbacks are deliberately not accepted without a separately
|
|
129
134
|
authenticated endpoint.
|
|
130
135
|
|
|
131
136
|
The tmux transport is selected with `PI_CLAUDE_SUPERVISOR_TRANSPORT=tmux`.
|
|
132
|
-
Automatic mode rejects explicit `process-pipe
|
|
133
|
-
bounded decisions and repair.
|
|
137
|
+
Automatic mode rejects explicit `process-pipe`; use JSONL or the Supervisor-owned
|
|
138
|
+
bridge for bounded decisions and repair. Automatic JSONL and tmux workers also
|
|
139
|
+
require Linux cgroup v2 containment (and the tmux parent-death guardian); startup
|
|
140
|
+
fails closed when it is unavailable. Built-in Claude workers also receive a fail-closed
|
|
134
141
|
sandbox setting (`failIfUnavailable`, `allowUnsandboxedCommands=false`, no outbound
|
|
135
142
|
network domains); verify that startup fails if the sandbox cannot be initialized.
|
|
136
143
|
Automatic startup also requires a full existing Git baseline, non-bare non-protected
|
|
@@ -141,7 +148,7 @@ compares the exact startup HEAD again immediately before spawn. Before release,
|
|
|
141
148
|
private-socket attach,
|
|
142
149
|
multi-line paste, prompt stability while
|
|
143
150
|
Claude is busy, trust/permission policy handling, duplicate send prevention, pane
|
|
144
|
-
replacement refusal, pause/resume, owned-session stop, adopted-session
|
|
151
|
+
replacement refusal, live bridge event framing, pause/resume, owned-session stop, adopted-session
|
|
145
152
|
detach/re-adoption, bounded shutdown, and Pi shutdown without closing an
|
|
146
153
|
attached window. The two gated real-Claude spikes above cover the trust prompt,
|
|
147
154
|
permission prompt, exact output, optional takeover, and adopted detach paths. Use
|
|
@@ -156,7 +163,8 @@ response.
|
|
|
156
163
|
The automated adapter matrix covers external `SIGTERM`, `SIGINT`, `SIGKILL`,
|
|
157
164
|
`SIGSTOP`/`SIGCONT`, SIGTERM refusal/escalation, leader-early-exit descendant
|
|
158
165
|
cleanup, required cgroup bootstrap containment of a pre-attachment detached
|
|
159
|
-
and `setsid()` descendant,
|
|
166
|
+
and `setsid()` descendant, automatic nested-Claude detection across the worker
|
|
167
|
+
cgroup, repeated stop, spawn failure, output truncation,
|
|
160
168
|
blocked stdin write timeouts, and immediate JSONL results. The Supervisor
|
|
161
169
|
matrix also covers retrying failed lifecycle events, preserving startup event
|
|
162
170
|
order, stopping under persistent timeout-event failure, and restoring output
|
|
@@ -185,8 +193,12 @@ isolated temporary worktree. The current hardening plan and evidence paths are r
|
|
|
185
193
|
in [`docs/automation-hardening-plan.md`](automation-hardening-plan.md). Deterministic
|
|
186
194
|
coverage now includes repairable-vs-persistent capability assertions, cancellation
|
|
187
195
|
of acceptance commands, stop-from-verifying precedence, paused watchdog baselining,
|
|
188
|
-
staged/untracked evidence and untracked symlink rejection. The
|
|
189
|
-
|
|
196
|
+
staged/untracked evidence, and untracked symlink/hard-link rejection. The exact-head
|
|
197
|
+
independent read-only review was rerun. Its pathname TOCTOU finding is recorded as a
|
|
198
|
+
false positive for the trusted local-development threat model: automatic workers are
|
|
199
|
+
trusted development agents, and this policy is a metadata guard rather than a host
|
|
200
|
+
filesystem isolation boundary. A hostile same-UID worker would require a separate
|
|
201
|
+
sandbox/broker design and is out of scope for this release.
|
|
190
202
|
|
|
191
203
|
### Acceptance and Reviewer fixtures
|
|
192
204
|
|
package/package.json
CHANGED
|
@@ -168,9 +168,16 @@ export class DecisionSessionStore {
|
|
|
168
168
|
const record = await this.#loadUnlocked(taskId);
|
|
169
169
|
if (!record || record.state !== "active") throw new Error(`Decision Worker recovery record is unavailable: ${taskId}`);
|
|
170
170
|
if (record.recoveryState === "ready" || record.recoveryState === "interrupted") return;
|
|
171
|
-
if (!record.recoveryOwnerPid ||
|
|
171
|
+
if (!record.recoveryOwnerPid || !record.recoveryOwnerStartTime) {
|
|
172
|
+
throw new Error(`Decision Worker recovery owner identity is unavailable: ${taskId}`);
|
|
173
|
+
}
|
|
174
|
+
const currentOwnerStartTime = await processStartTime(record.recoveryOwnerPid);
|
|
175
|
+
if (currentOwnerStartTime === record.recoveryOwnerStartTime) {
|
|
172
176
|
throw new Error(`Decision Worker recovery owner is still live: ${taskId}`);
|
|
173
177
|
}
|
|
178
|
+
if (!currentOwnerStartTime && await processExists(record.recoveryOwnerPid)) {
|
|
179
|
+
throw new Error(`Decision Worker recovery owner identity is unavailable: ${taskId}`);
|
|
180
|
+
}
|
|
174
181
|
await this.#saveUnlocked({
|
|
175
182
|
...record,
|
|
176
183
|
recoveryState: "interrupted",
|
package/src/index.ts
CHANGED
|
@@ -39,19 +39,19 @@ export default function piClaudeSupervisor(pi: ExtensionAPI): void {
|
|
|
39
39
|
if (!["off", "auto", "required"].includes(cgroupMode)) {
|
|
40
40
|
throw new Error(`Unsupported PI_CLAUDE_SUPERVISOR_CGROUP_MODE: ${cgroupMode}; expected off, auto, or required`);
|
|
41
41
|
}
|
|
42
|
-
if (transport === "tmux" && cgroupMode === "required") {
|
|
43
|
-
throw new Error("PI_CLAUDE_SUPERVISOR_CGROUP_MODE=required is unsupported with tmux; use
|
|
42
|
+
if (transport === "tmux" && cgroupMode === "required" && !automation) {
|
|
43
|
+
throw new Error("PI_CLAUDE_SUPERVISOR_CGROUP_MODE=required is unsupported with manual tmux; use automatic mode or cgroup mode auto/off");
|
|
44
44
|
}
|
|
45
|
-
if (automation &&
|
|
46
|
-
throw new Error("automatic supervision requires PI_CLAUDE_SUPERVISOR_TRANSPORT=jsonl; process-pipe
|
|
45
|
+
if (automation && !["jsonl", "tmux"].includes(transport)) {
|
|
46
|
+
throw new Error("automatic supervision requires PI_CLAUDE_SUPERVISOR_TRANSPORT=jsonl or tmux; process-pipe is manual-only");
|
|
47
47
|
}
|
|
48
48
|
const adapter = transport === "tmux"
|
|
49
|
-
? new TmuxWorkerAdapter({ stateDir })
|
|
49
|
+
? new TmuxWorkerAdapter({ stateDir, cgroupMode: automation ? "required" : cgroupMode as "off" | "auto" | "required" })
|
|
50
50
|
: new ProcessWorkerAdapter({
|
|
51
51
|
// Automatic decisions require Claude's structured event stream. The pipe
|
|
52
52
|
// transport remains available for manual/compatibility sessions.
|
|
53
53
|
mode: automation || transport === "jsonl" ? "claude-jsonl" : "process-pipe",
|
|
54
|
-
cgroupMode: cgroupMode as "off" | "auto" | "required",
|
|
54
|
+
cgroupMode: automation ? "required" : cgroupMode as "off" | "auto" | "required",
|
|
55
55
|
});
|
|
56
56
|
const humanWebhook = new HumanWebhookNotifier({
|
|
57
57
|
url: process.env.PI_CLAUDE_SUPERVISOR_HUMAN_WEBHOOK_URL,
|
|
@@ -232,7 +232,10 @@ export default function piClaudeSupervisor(pi: ExtensionAPI): void {
|
|
|
232
232
|
const fileSpec = specPath ? await readTaskSpecFile(specPath, ctx.cwd) : undefined;
|
|
233
233
|
const spec = fileSpec ?? { autonomy: autonomyDefaults() };
|
|
234
234
|
const goal = fileSpec?.goal ?? task;
|
|
235
|
-
|
|
235
|
+
// Adopted sessions are explicit manual compatibility controls. They
|
|
236
|
+
// never enter the automatic Reviewer/decision loop, even when the
|
|
237
|
+
// extension is globally configured for unattended starts.
|
|
238
|
+
const taskAutomation = operation !== "adopt-tmux" && automation && spec.autonomy.unattended;
|
|
236
239
|
if (!goal) throw new Error(operation === "adopt-tmux" ? "Usage: /supervise adopt-tmux [--spec <file>] <tmux-session> <task>" : "Usage: /supervise start [--spec <file>] <task>");
|
|
237
240
|
if (operation === "adopt-tmux" && adapter.capabilities().transport !== "tmux") throw new Error("/supervise adopt-tmux requires PI_CLAUDE_SUPERVISOR_TRANSPORT=tmux");
|
|
238
241
|
const [command, ...workerArgs] = parseCommand(process.env.PI_CLAUDE_SUPERVISOR_WORKER ?? "claude");
|
|
@@ -259,7 +262,7 @@ export default function piClaudeSupervisor(pi: ExtensionAPI): void {
|
|
|
259
262
|
}
|
|
260
263
|
pendingCwds.add(cwdKey);
|
|
261
264
|
const session = new Supervisor(adapter, events, {
|
|
262
|
-
reviewer,
|
|
265
|
+
reviewer: taskAutomation ? reviewer : undefined,
|
|
263
266
|
onCandidate: async (notice) => {
|
|
264
267
|
if (ctx.hasUI) notify(ctx, `Candidate ${notice.status}: ${notice.reason}`, notice.status === "ready" ? "info" : "warning");
|
|
265
268
|
if (humanWebhook.enabled) {
|
|
@@ -475,7 +478,7 @@ export default function piClaudeSupervisor(pi: ExtensionAPI): void {
|
|
|
475
478
|
}
|
|
476
479
|
pendingCwds.add(cwdKey);
|
|
477
480
|
const session = new Supervisor(adapter, events, {
|
|
478
|
-
reviewer,
|
|
481
|
+
reviewer: automaticRecovery ? reviewer : undefined,
|
|
479
482
|
onCandidate: async (notice) => {
|
|
480
483
|
if (ctx.hasUI) notify(ctx, `Candidate ${notice.status}: ${notice.reason}`, notice.status === "ready" ? "info" : "warning");
|
|
481
484
|
if (humanWebhook.enabled) {
|