@335g/pi-herdr-fleet 0.0.1
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/README.ja.md +419 -0
- package/README.md +442 -0
- package/approvals.ts +748 -0
- package/audit.ts +153 -0
- package/clean.ts +256 -0
- package/fork.ts +216 -0
- package/herdr-client.ts +300 -0
- package/index.ts +420 -0
- package/package.json +45 -0
- package/recipes.ts +172 -0
- package/review.ts +515 -0
- package/runs.ts +437 -0
- package/scopes.ts +134 -0
- package/worktree.ts +573 -0
package/README.ja.md
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
# pi-herdr-fleet
|
|
2
|
+
|
|
3
|
+
[English](./README.md)
|
|
4
|
+
|
|
5
|
+
[herdr](https://herdr.dev) の pane と Pi の間の承認ブローカー。herdr はどの pane が人間の返答を
|
|
6
|
+
待っているかを知っている。`/fleet` はその一覧を、いま見ている pane の上に overlay で出し、pane を
|
|
7
|
+
切り替えずに blocked のエージェントへ答える。同じコマンドで tab のレイアウトを保存・復元し、
|
|
8
|
+
git 管理外の開発環境を引き継いだ worktree も作れる。さらにその worktree を fork できる。worktree を
|
|
9
|
+
切り、その中で Pi セッションを起動し、タスクを 1 通で渡す。fork はツール `fleet_fork` として登録される
|
|
10
|
+
ので、agent が自分のループから呼べる。
|
|
11
|
+
|
|
12
|
+
Phase 3a は `/fleet fork` まで。レビュー(3b)とマージゲート(3c)はこれから。
|
|
13
|
+
|
|
14
|
+
## 動作条件
|
|
15
|
+
|
|
16
|
+
herdr が管理する pane の中で、対話モードで動いているときだけ有効になる。`HERDR_ENV=1` /
|
|
17
|
+
`HERDR_SOCKET_PATH` / `HERDR_PANE_ID` が揃っていなければ何も登録しない。pi が TUI モードでなければ
|
|
18
|
+
何も起動しない。RPC や print モードには herdr が表示できる pane も、overlay を描く端末も無いため。
|
|
19
|
+
|
|
20
|
+
## インストール
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
pi install npm:@335g/pi-herdr-fleet
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`pi install` はユーザ設定(`~/.pi/agent/settings.json`)に書き込む。`-l` を付けるとプロジェクト設定
|
|
27
|
+
(`.pi/settings.json`)に書き込む。
|
|
28
|
+
|
|
29
|
+
## 使い方
|
|
30
|
+
|
|
31
|
+
- `/fleet` — 承認待ちの pane の一覧を開く
|
|
32
|
+
- `ctrl+shift+a` — コマンドを打たずに同じ一覧を開く
|
|
33
|
+
- `/fleet recipe save <name>` — 現在の tab のレイアウトを保存する
|
|
34
|
+
- `/fleet recipe apply <name> [--start]` — 新しい tab として復元する
|
|
35
|
+
- `/fleet recipe ls` — 保存済みのレシピを一覧する
|
|
36
|
+
- `/fleet worktree create <branch> [--base <ref>] [--label <text>]` — worktree を作り、開発環境を
|
|
37
|
+
引き継ぐ
|
|
38
|
+
- `/fleet fork <branch> --task "<text>" [--base <ref>] [--scope implementation] [--no-install]
|
|
39
|
+
[--no-start]` — worktree を fork し、その中で Pi セッションを起動し、タスクを渡す
|
|
40
|
+
- `/fleet review <branch> --task "<text>" [--base <ref>]` — その worktree の中で読み取り専用の
|
|
41
|
+
レビュワーを起動し、diff と作者のセッションを渡す
|
|
42
|
+
- `/fleet status` — 記録済みの run ごとに branch / scope / 状態 / verdict を出す
|
|
43
|
+
- `/fleet merge <branch> [--force]` — approve 済みのブランチを main checkout にマージする
|
|
44
|
+
- `/fleet clean <branch> [--force]` — マージ済み run の worktree / branch / pane を消す
|
|
45
|
+
- `fleet_fork` ツール — 同じことを agent から呼ぶ。引数は `branch` / `task` / `base` / `scope` /
|
|
46
|
+
`install` / `start`
|
|
47
|
+
- `fleet_review` ツール — 同じことを agent から呼ぶ。引数は `branch` / `task` / `base`
|
|
48
|
+
- `fleet_verdict` ツール — レビュワーが verdict を記録する。引数は `verdict` / `findings`
|
|
49
|
+
- `fleet_status` ツール — 同じ一覧を agent から呼ぶ。引数なし
|
|
50
|
+
- `fleet_merge` ツール — 同じマージを agent から呼ぶ。引数は `branch` / `force`
|
|
51
|
+
- `fleet_clean` ツール — 同じ後始末を agent から呼ぶ。引数は `branch` / `force`
|
|
52
|
+
|
|
53
|
+
| キー | 動作 |
|
|
54
|
+
|------|------|
|
|
55
|
+
| `↑` `↓` | 選択を動かす |
|
|
56
|
+
| `1`〜`9` | その行を開く |
|
|
57
|
+
| `Enter` | 選択中の行を開く |
|
|
58
|
+
| `Esc` | overlay を閉じる |
|
|
59
|
+
|
|
60
|
+
詳細画面では:
|
|
61
|
+
|
|
62
|
+
| キー | 動作 |
|
|
63
|
+
|------|------|
|
|
64
|
+
| `Enter` | 入力したテキストを送ってから Enter(`pane.send_input`) |
|
|
65
|
+
| `ctrl+k` | 入力した内容を生キーとして送る(`pane.send_keys`) |
|
|
66
|
+
| `PageUp` `PageDown` | 質問文をスクロール |
|
|
67
|
+
| `Esc` | 一覧へ戻る |
|
|
68
|
+
|
|
69
|
+
## 承認待ちへの答え方
|
|
70
|
+
|
|
71
|
+
承認ダイアログは文章で答えられる形とは限らない。番号付きの選択肢かもしれないし、yes/no かもしれないし、
|
|
72
|
+
全画面のピッカーかもしれない。そこで詳細画面からは 2 経路を用意し、どちらを使うかは入力から推測せず
|
|
73
|
+
こちらで選ぶ。
|
|
74
|
+
|
|
75
|
+
- **テキスト**(`Enter`)— 入力した内容を、その pane の入力欄に打ったのと同じように送る。
|
|
76
|
+
- **生キー**(`ctrl+k`)— 代わりにキーストロークを送る。入力は空白で区切るので、`esc 1` は `esc` の次に
|
|
77
|
+
`1`、`up up enter` はメニューを辿る。入力が空なら素の `Enter` になる。確認ダイアログではこれが普通。
|
|
78
|
+
|
|
79
|
+
どちらの経路も pane 側の API(`pane.send_input` / `pane.send_keys`)を使う。agent 側は使えない。
|
|
80
|
+
`agent.prompt` は **herdr が blocked と報告している pane を拒否し**(`agent_blocked`)、それはこの
|
|
81
|
+
overlay が答えられる pane のすべてにあたる。`agent.send_keys` も `pane.report_agent` で報告された
|
|
82
|
+
agent を拒否する(`agent_not_ready`)。hook や plugin はその経路で状態を報告する。承認ダイアログに
|
|
83
|
+
答えるのは、その pane への意図的な生入力なので pane 側を使う。
|
|
84
|
+
|
|
85
|
+
次の 2 つは起きない。
|
|
86
|
+
|
|
87
|
+
- **失敗を自動で再送しない。** herdr の timeout は「入力がエージェントに届かなかった」証明ではない。
|
|
88
|
+
送信に失敗したら理由を overlay に出し、その行は一覧に残す。再送するかどうかは本人が決める。
|
|
89
|
+
- **送信が成功しても行を消さない。** 書き込みが成功したことは、エージェントが次に進んだ証明ではない。
|
|
90
|
+
行が消えるのは、herdr がその pane はもう blocked ではないと報告したときだけ。
|
|
91
|
+
|
|
92
|
+
自分自身の pane は一覧に出ない。自分に答えることはデッドロックになるため。
|
|
93
|
+
|
|
94
|
+
## レシピ
|
|
95
|
+
|
|
96
|
+
レシピは herdr の tab レイアウトそのもの。`/fleet recipe save dev` が現在の tab を export し、
|
|
97
|
+
`LayoutNode` の木を `.pi/herdr-fleet/recipes/dev.json` に書く(pi を起動したディレクトリ基準)。
|
|
98
|
+
`pane_id` だけは落とす。閉じた pane の id は再利用できないため。それ以外はそのまま保存する。
|
|
99
|
+
|
|
100
|
+
`/fleet recipe apply dev` はその木を、現在の workspace に**新しい tab** として作る。tab 名は
|
|
101
|
+
レシピ名。いまいる tab を置き換えることはしない。保存した木は元の tab id を持たないし、現在の tab を
|
|
102
|
+
置き換えるとコマンドを実行したセッション自身が死ぬ。既定ではレイアウトのみで、pane は保存された
|
|
103
|
+
`cwd` の素の shell として戻る。`--start` を付けると保存された起動コマンドも再現する。
|
|
104
|
+
|
|
105
|
+
レシピ名はファイル名の1セグメントに制限する(`[A-Za-z0-9][A-Za-z0-9._-]*`)。名前はパスなので、
|
|
106
|
+
`../` が通るとプロジェクトの外に書けてしまう。
|
|
107
|
+
|
|
108
|
+
## worktree と環境の引き継ぎ
|
|
109
|
+
|
|
110
|
+
`/fleet worktree create <branch>` は `herdr worktree create` を呼び、そのあと git 管理外の開発環境を
|
|
111
|
+
新しい checkout にコピーする。
|
|
112
|
+
|
|
113
|
+
- 元のルートにある `.env*`(`.env` / `.env.local` / `.env.example` / `.envrc` …)をコピーする。
|
|
114
|
+
**上書きはしない。** git が既に置いた `.env.example` はそのまま残す。
|
|
115
|
+
- `direnv allow` は、**元の `.envrc` が既に allow されている場合のみ**新しい worktree に対して実行する。
|
|
116
|
+
判定は `direnv status --json` の `state.foundRC.allowed === 0`。allow されていない `.envrc` を
|
|
117
|
+
新しい場所で allow するのは信頼の付与で、拡張がたった今作った worktree に、ユーザが承認していない
|
|
118
|
+
コードを実行させることになる。だから真似するだけで、新しく与えることはしない。
|
|
119
|
+
- direnv が無い、または元に `.envrc` が無い場合はコピーだけして警告を返す。
|
|
120
|
+
- 途中で失敗しても失敗にはしない。worktree は存在するので、作成は成功として報告する。
|
|
121
|
+
|
|
122
|
+
worktree には git が追跡しているものしか来ないため、このコピーが要る。無いと、切った直後に起動した
|
|
123
|
+
Pi が `No API key found` で即死する。
|
|
124
|
+
|
|
125
|
+
## worktree を fork する
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
/fleet fork feat/x --task "uploader にリトライを入れる"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
同じ fork はツールとしても呼べる。**主はツール。** この拡張が担うループの主導は agent 側にあり、
|
|
132
|
+
コマンドだけだと fork のたびに人間が真ん中に入ることになる。コマンドは人間が直接打ちたいときのために
|
|
133
|
+
残していて、どちらも `fork.ts` の同じ関数を呼ぶ。手順の正しさを保つ場所は 1 つだけ。
|
|
134
|
+
|
|
135
|
+
`fleet_fork`:
|
|
136
|
+
|
|
137
|
+
| 引数 | 型 | 既定 | 内容 |
|
|
138
|
+
|---|---|---|---|
|
|
139
|
+
| `branch` | string | 必須 | 新しいブランチ名 |
|
|
140
|
+
| `task` | string | 必須 | 実装セッションに渡すタスク |
|
|
141
|
+
| `base` | string | HEAD | 分岐元 |
|
|
142
|
+
| `scope` | enum | `implementation` | スコープ |
|
|
143
|
+
| `install` | boolean | true | lockfile があれば install する |
|
|
144
|
+
| `start` | boolean | true | pane を作って Pi を起動する |
|
|
145
|
+
|
|
146
|
+
返すのは worktree path / branch / workspace / pane / agent 名、そして環境の警告があるときだけその警告。
|
|
147
|
+
ツールの結果は会話の 1 エントリになるので、長い出力は並べない。
|
|
148
|
+
|
|
149
|
+
手順はこの順で 5 つ。
|
|
150
|
+
|
|
151
|
+
1. 新しいブランチの worktree を作り、上と同じように `.env*` / `.envrc` を引き継ぐ。
|
|
152
|
+
2. **準備** — checkout に lockfile があれば、その中で依存を install する。installer は lockfile で
|
|
153
|
+
決まる(`pnpm-lock.yaml` → pnpm、`yarn.lock` → yarn、`bun.lockb` / `bun.lock` → bun、
|
|
154
|
+
`package-lock.json` → npm)。lockfile が無ければ何もしない。コマンドは新しい pane の shell に
|
|
155
|
+
打ち込み、完了を herdr に待たせる。install の出力は、依頼したセッションの中ではなく、見に行ける
|
|
156
|
+
画面に出る。`--no-install` で飛ばせる。
|
|
157
|
+
3. worktree の workspace に pane を作り(`pane.split` に `--cwd <worktree>` と `--no-focus`)、
|
|
158
|
+
その中で Pi を起動する。agent 名は branch から作り、herdr の `[a-z][a-z0-9_-]{0,31}` に正規化
|
|
159
|
+
して 32 文字で切る。
|
|
160
|
+
4. タスクを **1 通のメッセージ** として `pane.send_input` で送る。`agent.prompt` は使わない。
|
|
161
|
+
herdr が blocked と報告している pane を拒否する点で承認ブローカーと同じ層の問題であり、
|
|
162
|
+
セッションへのプロンプト入力は pane 側の仕事。
|
|
163
|
+
5. worktree path / branch / workspace / pane / agent 名を通知で返す。
|
|
164
|
+
|
|
165
|
+
`--no-start` は手順 1 で止まる。worktree と環境だけができ、その中では何も動かない。
|
|
166
|
+
|
|
167
|
+
### タスクが唯一の指示書
|
|
168
|
+
|
|
169
|
+
**会話履歴は渡さない。** fork されたセッションが受け取るのは、タスク本文、worktree と branch、
|
|
170
|
+
制約、そして「完了」の定義だけ。議論は指示書ではない。fork した側で決めたことはタスク本文に書き写す
|
|
171
|
+
必要があり、決めきれなかったことは向こうでもう一度問うしかない。このプロンプトは `scopes.ts` にあり、
|
|
172
|
+
スコープごとに 1 つずつ入っている。fork が使う `implementation` と、レビュワーの `review`。
|
|
173
|
+
`fleet_fork` の `scope` 引数に出るのは、タスクと worktree だけで組み立てられるスコープだけ。`review` は
|
|
174
|
+
既にある worktree から材料を集める必要があるので、専用のツールから呼ぶ。
|
|
175
|
+
|
|
176
|
+
ツールの引数の説明がそう書いてあるのはこのため。`task` を書くのは、その指示書を書くべき当のモデル。
|
|
177
|
+
|
|
178
|
+
`branch` と `task` は空文字なら弾く。ツールの呼び出し元はモデルなので、中身の無いフィールドは「引数を
|
|
179
|
+
書き忘れた」ときの典型的な形になる。引数がそもそも無い場合はスキーマが弾き、スキーマでは表せない空文字を
|
|
180
|
+
ここで弾く。
|
|
181
|
+
|
|
182
|
+
implementation スコープがセッションに伝えること:
|
|
183
|
+
|
|
184
|
+
- この worktree の中だけで作業し、他の checkout には触らない
|
|
185
|
+
- このブランチにコミットする。worktree の作成、agent の起動、push はしない
|
|
186
|
+
- タスクが決めていないことは推測せず聞く
|
|
187
|
+
- 終わったらコミットと短い報告だけを返す(diff は貼らない)
|
|
188
|
+
|
|
189
|
+
install の失敗は失敗ではなく警告にする。worktree と pane は存在するので、通知で何が起きたかを伝える。
|
|
190
|
+
環境の引き継ぎの警告も同じ扱い。
|
|
191
|
+
|
|
192
|
+
### fork が待つ理由
|
|
193
|
+
|
|
194
|
+
実の pane が、API の見た目どおりに動かない 2 点がある。
|
|
195
|
+
|
|
196
|
+
- `agent.start` は、pane の shell がまだ認識されていない間 `agent_pane_busy` を返す。ここでは install
|
|
197
|
+
をその pane で実行した直後なので普通に起きる。fork はリトライする。
|
|
198
|
+
- herdr が agent を ready と報告してから、実際に入力を受け付けるまでに 3 秒ほどある。この間に送った
|
|
199
|
+
プロンプトは入力欄に残り、送信されない。末尾の Enter が単に失われる。fork は herdr が settled な
|
|
200
|
+
agent を報告するまで待ってからタスクを送る。
|
|
201
|
+
|
|
202
|
+
どちらも実の herdr と Pi で計測したもので、手順がこの順になっている理由でもある。
|
|
203
|
+
|
|
204
|
+
## fork をレビューする
|
|
205
|
+
|
|
206
|
+
```
|
|
207
|
+
/fleet review feat/x --task "uploader にリトライを入れる"
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
レビュワーは 2 人目の Pi セッションで、**実装 worktree の中の新しい pane** で動く。別の worktree では
|
|
211
|
+
ない。同じブランチを 2 つの worktree にチェックアウトすることは git が拒否する。読み取り専用だと
|
|
212
|
+
指示する。レビュー対象を書き換えるレビューはレビューではない。agent 名はブランチの名前に `-review`
|
|
213
|
+
を付けたものになる。agent 名は 1 つしか取れない。
|
|
214
|
+
|
|
215
|
+
`fleet_review`:
|
|
216
|
+
|
|
217
|
+
| 引数 | 型 | 既定 | 内容 |
|
|
218
|
+
|---|---|---|---|
|
|
219
|
+
| `branch` | string | 必須 | 実装セッションが作業したブランチ |
|
|
220
|
+
| `task` | string | 必須 | そのセッションに渡したタスク |
|
|
221
|
+
| `base` | string | main checkout の HEAD | 変更を測る起点 |
|
|
222
|
+
|
|
223
|
+
fork の仕事はできるだけ渡さないことだった(仕事はタスクだから)。レビューの仕事は逆になる。レビュワー
|
|
224
|
+
は worktree を持っているが、何を頼まれたのかも、作者が何を未完成だと知っていたのかも分からない。
|
|
225
|
+
だから seed は 4 つを運ぶ。
|
|
226
|
+
|
|
227
|
+
- worktree で実行した `git diff <base>...HEAD`。diff は 60000 文字で切り、切ったことを seed に明記
|
|
228
|
+
する。半分だけを見たレビュワーは、自分で `git diff` を打つレビュワーより悪い。
|
|
229
|
+
- タスク本文。好みではなく指示書に対して判定させるため。
|
|
230
|
+
- 作者のセッション。herdr がその pane について報告するパス(`session.snapshot` の
|
|
231
|
+
`agent_session.value`)から読む。抜くのは assistant の `text` パートだけ。thinking もツール呼び出しも
|
|
232
|
+
取らない。後者は diff を別の経路で見たものにすぎない。
|
|
233
|
+
- worktree とブランチ、そして作業の制約。
|
|
234
|
+
|
|
235
|
+
セッションは数 MB になる JSONL なので、抜粋は **300 行かつ 20000 文字** で上限を付け、上限は末尾から
|
|
236
|
+
適用する。報告は最新のメッセージであり、直近のコミットの周辺の思考の方が冒頭より効く。読むのは
|
|
237
|
+
ファイルの末尾(4 MB)だけで、切った場合は seed にそう書く。最後のメッセージが作者の報告で、その前が
|
|
238
|
+
そこに至る道筋になる。
|
|
239
|
+
|
|
240
|
+
seed はレビューの終わりをテキスト行ではなく `fleet_verdict` ツール呼び出しに固定する。レビュワーには
|
|
241
|
+
`approve` か `request-changes` と、問題ごとの finding を記録するよう指示し、マージゲートが読むのは
|
|
242
|
+
その呼び出しだけで、散文は読まないと明記する。レビュワーは `-e <この拡張>` 付きで起動する。インスト
|
|
243
|
+
ール未済でも `fleet_verdict` が渡り、開発中のレビューは main checkout の古いコードではなく worktree の
|
|
244
|
+
コードを使う。
|
|
245
|
+
|
|
246
|
+
worktree がどの workspace にも開かれていないブランチは拒否する。レビュワーを置く場所が無い。タスクの
|
|
247
|
+
無いレビューも拒否する。worktree で Pi セッションがもう動いていない場合、読むセッションが無い。その
|
|
248
|
+
場合は「何も書かなかった作者」に見えないよう、seed にそう書く。
|
|
249
|
+
|
|
250
|
+
## verdict とマージ
|
|
251
|
+
|
|
252
|
+
fork・review・verdict はブランチごとに 1 つのファイルに記録する。
|
|
253
|
+
|
|
254
|
+
```
|
|
255
|
+
<main checkout>/.pi/herdr-fleet/runs/<branch>.json # branch の `/` は `-`
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`fleet_fork` が書き、`fleet_review` がレビュワーの pane を加え、`fleet_verdict` が verdict を加える。
|
|
259
|
+
生きたレビュワーのセッションではなくファイルに置くのが要点。pane は閉じられるので、pane が消えたら
|
|
260
|
+
開くゲートはゲートではない。
|
|
261
|
+
|
|
262
|
+
`fleet_verdict`:
|
|
263
|
+
|
|
264
|
+
| 引数 | 型 | 内容 |
|
|
265
|
+
|---|---|---|
|
|
266
|
+
| `verdict` | `approve` / `request-changes` | マージしてよいか |
|
|
267
|
+
| `findings` | `{ path, line?, note }[]` | 問題ごとに 1 件 |
|
|
268
|
+
|
|
269
|
+
呼べるのは、その run が記録したレビュワーの pane だけ。拡張はどの Pi セッションにも入っているので、
|
|
270
|
+
この検査が無いとどのセッションからでも verdict を書ける。`request-changes` の findings は、実装
|
|
271
|
+
セッションがまだ生きていれば `pane.send_input` で送り返す。代わりのセッションは立てない。
|
|
272
|
+
|
|
273
|
+
```
|
|
274
|
+
/fleet status
|
|
275
|
+
/fleet merge feat/x
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
`/fleet status` は run ごとに branch · scope · 状態 · verdict を 1 行で出す。状態は `working` /
|
|
279
|
+
`unreviewed` / `approve` / `request-changes` / `merged` / `cleaned`(ブランチが既に main checkout の
|
|
280
|
+
履歴に入っていれば `merged`、`/fleet clean` が worktree・branch・pane を消していれば `cleaned`)。
|
|
281
|
+
|
|
282
|
+
`/fleet merge` は main checkout で `git merge --no-edit` を実行する。verdict が `approve` でなければ
|
|
283
|
+
(`--force` が無ければ)拒否し、追跡ファイルが汚れていても拒否する。未追跡ファイルは止めない。run の
|
|
284
|
+
記録自体が `.pi/` の下にあるため。worktree は消さない。後始末は別の操作にする。
|
|
285
|
+
|
|
286
|
+
その別の操作が `/fleet clean`。run が記録した pane を閉じ、herdr の `worktree.remove` で worktree を
|
|
287
|
+
消し、main checkout で `git branch -d` を打つ。ブランチが main の履歴に入っていなければ `--force` が
|
|
288
|
+
無い限り拒否する。run 記録とセッション JSONL は消さない — 記録には `cleanedAt` が付くだけ。
|
|
289
|
+
|
|
290
|
+
どちらもツールでもある(`fleet_status` は引数なし、`fleet_merge` は `branch` と任意の `force`、
|
|
291
|
+
`fleet_clean` は `branch` と任意の `force`)。コマンドは、ツールと同じ `statusRuns` / `mergeRun` /
|
|
292
|
+
`cleanRun` を呼ぶ薄いラッパ。人間がキーボードの前にいなくてもループが閉じる — agent が fork し、
|
|
293
|
+
review し、merge し、そのまま後始末できる。
|
|
294
|
+
|
|
295
|
+
## 通知
|
|
296
|
+
|
|
297
|
+
新しく blocked になった pane は pi の通知を出す。切るには:
|
|
298
|
+
|
|
299
|
+
```json
|
|
300
|
+
// ~/.pi/agent/pi-herdr-fleet.json
|
|
301
|
+
{ "notify": false }
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
このファイルから読むのはこれだけ。
|
|
305
|
+
|
|
306
|
+
## 仕組み
|
|
307
|
+
|
|
308
|
+
真実は herdr 側にある。拡張は再構成できない状態を持たない。
|
|
309
|
+
|
|
310
|
+
- 一覧は `session.snapshot` から作り、`pane.agent_status_changed` で直す。想定外の動きをした pane は、
|
|
311
|
+
ローカルで推測せず herdr に聞き直して解決する。
|
|
312
|
+
- 詳細画面の質問文は `agent.read` で読む(`detection` を先に、herdr が何も返さなければ `visible`)。
|
|
313
|
+
blocked になった時点で 1 回だけ読む。
|
|
314
|
+
- すべての呼び出しにタイムアウトを付け、失敗(古い herdr にメソッドが無い、socket が切れた、サーバが
|
|
315
|
+
遅い)は例外ではなく値として返す。overlay は理由を出すだけで、セッションは壊さない。
|
|
316
|
+
- 購読接続は指数バックオフで再接続する。復帰時は購読を張り直し、`session.snapshot` を読み直す。
|
|
317
|
+
古いストリームから作った状態は herdr の状態で置き換わる。
|
|
318
|
+
|
|
319
|
+
## 制限
|
|
320
|
+
|
|
321
|
+
- pane の集合ごとに購読接続が 1 本要る。herdr の `pane.agent_status_changed` は `pane_id` 単位で、
|
|
322
|
+
購読済みの接続に 2 つ目の `events.subscribe` を送ると接続が閉じられる。そのため pane が増えると
|
|
323
|
+
接続も増える。溜め込みはしない。集合は snapshot から作り直して比較する。
|
|
324
|
+
- 分岐 worktree ループは閉じた。fork・review・verdict はブランチごとに記録され、`/fleet status` が
|
|
325
|
+
一覧し、`/fleet merge` は `approve` の無いブランチを拒否し、`/fleet clean` がマージ済み run の
|
|
326
|
+
worktree・branch・pane を消す。run 記録とセッション JSONL は消さない。記録に `cleanedAt` が付く
|
|
327
|
+
だけ。
|
|
328
|
+
- レビュワーは `-e <この拡張>` 付きで起動する。動くのは、起動した側のコードであって、インストール
|
|
329
|
+
済みのコピーではない。
|
|
330
|
+
- `worktree.create` は linked worktree を分岐元にできない。そのため linked worktree の中からの
|
|
331
|
+
`/fleet fork` は main checkout から作られ、呼び出し元の HEAD に固定される。そこにある未コミットの
|
|
332
|
+
変更は fork に入らない。ある場合は警告を出す。
|
|
333
|
+
- `/fleet worktree create` も `/fleet fork` もフォーカスを移さない。新しい workspace は裏で作られる。
|
|
334
|
+
- レシピが記録するのは 1 つの tab。workspace 全体を保存する手段は無いし、保存元の tab に復元する
|
|
335
|
+
手段も無い。
|
|
336
|
+
- 環境マネージャは direnv しか見ていない。mise や asdf の類いは `.envrc` 経由でしか引き継がれない。
|
|
337
|
+
|
|
338
|
+
## 開発
|
|
339
|
+
|
|
340
|
+
```sh
|
|
341
|
+
node packages/pi-herdr-fleet/selfcheck.ts
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
一時 socket に立てた偽の herdr サーバに対して、transport とブローカーを動かす。リクエスト・エラー・
|
|
345
|
+
タイムアウトの縮退、購読と再接続・再同期、購読を拒否されたときにその集合を組み直すこと、自分以外の pane
|
|
346
|
+
だけが並ぶこと、blocked から外れたら行が消えること、最初の snapshot では通知せず snapshot の読み直しで
|
|
347
|
+
再通知しないこと、送信に失敗したら行が残ること、有効条件のガードを確認する。overlay も実際に描画し、
|
|
348
|
+
全行の幅が揃っていることと枠が閉じていることを見る。
|
|
349
|
+
|
|
350
|
+
レシピと環境のコピーは実際の一時ディレクトリで確認する。レシピが `cwd` / `env` / `command` を残して
|
|
351
|
+
`pane_id` を落とすこと、`--start` がコマンド再現の有無を決めること、名前がレシピのディレクトリの外に
|
|
352
|
+
出られないこと、そして direnv を差し替えて、allow されていない元の `.envrc` は新しい worktree でも
|
|
353
|
+
allow されず、allow 済みのものは引き継がれること。
|
|
354
|
+
|
|
355
|
+
fork の部品も同じように確認する。installer を決めるのが lockfile だけで、lockfile の無い checkout には
|
|
356
|
+
install しないこと、`--no-install` が何も打ち込まないこと、install の目印がコマンドの echo に一致
|
|
357
|
+
しないこと(pane は打った文字をそのまま echo するので、目印をリテラルで書くと install が走る前に一致
|
|
358
|
+
してしまう)、0 以外の終了が失敗として報告されること、busy な pane はリトライする一方で直らない
|
|
359
|
+
`agent.start` の失敗はリトライしないこと、何かを打ち込む前に agent の settled を待つこと。seed と
|
|
360
|
+
agent 名は純粋関数なので、タスク・worktree・branch が出来上がる文字列に入ることを見る。
|
|
361
|
+
|
|
362
|
+
ツールはツール自身の面から確認する。`branch` と `task` がスキーマで必須であること、scope の enum が
|
|
363
|
+
registry から来ること、空の `branch` / `task` や未知の scope が herdr に届かないこと、TUI 以外の
|
|
364
|
+
セッションからの呼び出しを拒否すること、失敗は throw すること(戻り値ではエラーフラグは立たない)、
|
|
365
|
+
結果が fork を名指ししつつきれいな環境の話はしないこと、環境の警告はモデルに届くこと。
|
|
366
|
+
|
|
367
|
+
### 受入試験
|
|
368
|
+
|
|
369
|
+
```sh
|
|
370
|
+
packages/pi-herdr-fleet/acceptance.sh
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
実の herdr サーバ、実の pane、実の direnv、実の Pi TUI で通す。herdr の pane の中で実行する。
|
|
374
|
+
ブローカーは自分の pane を一覧に出さないので pane は 3 つ要り、スクリプトがそれを作る。
|
|
375
|
+
`pane.report_agent` で `blocked` を報告した **subject** の shell、別 pane でこの拡張を読み込んだ
|
|
376
|
+
**observer** の Pi、そして呼び出し元の pane。確認するのは、通知、overlay の一覧、詳細画面の質問文、
|
|
377
|
+
回答の 2 経路(テキストと生キー)、`Esc` で overlay が閉じること。閉じるのは自分が作った pane だけ。
|
|
378
|
+
|
|
379
|
+
subject の状態を実エージェントではなく報告で作るので、実行は決定的になる。モデルに答えさせずに、
|
|
380
|
+
socket・購読・overlay・subject の `read` へのキー配送という経路全体を実際に通す。
|
|
381
|
+
|
|
382
|
+
```sh
|
|
383
|
+
packages/pi-herdr-fleet/acceptance-fork.sh
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
`/fleet fork` を、同じ実スタックに実の git と実の npm を足して通す。fork は本物の worktree を作るので、
|
|
387
|
+
このスクリプトはこのリポジトリには触らない。一時ディレクトリに自前の git リポジトリ(lockfile の無い
|
|
388
|
+
base commit 付き)と、observer Pi 用の自前の workspace を作る。作った worktree・workspace・pane と
|
|
389
|
+
direnv の trust はすべて後始末し、それ以外は読むことも閉じることもない。
|
|
390
|
+
|
|
391
|
+
fork 4 つで確認する。worktree・環境のコピー・direnv の trust・install・pane・Pi・seed がすべて起きる
|
|
392
|
+
こと、fork されたセッションが実際にタスクをこなしてコミットすること、lockfile の無い checkout には
|
|
393
|
+
install しないこと、`--no-install` が lockfile を無視すること、`--no-start` が何も動いていない worktree
|
|
394
|
+
を残すこと。
|
|
395
|
+
|
|
396
|
+
続けてツール経路を確認する。agent が実際に使うのはこちらで、コマンドの試験では届かない。observer の
|
|
397
|
+
agent に `fleet_fork` を呼ばせ、worktree、pane、observer の会話に残ったツール結果、fork 先セッションに
|
|
398
|
+
届いた seed を見る。そのあと拒否を 2 つ。空の `task`(ツール自身の検証が弾く)と、`task` をそもそも
|
|
399
|
+
渡さない呼び出し(ツールが動く前にスキーマが弾く)。どちらも worktree を残してはいけない。
|
|
400
|
+
|
|
401
|
+
`acceptance.sh` と違い、ここには動くモデルが要る。fork 先のセッションに作業させるためと、observer に
|
|
402
|
+
ツールを呼ばせるため。API key が環境に無ければ警告を出す。
|
|
403
|
+
|
|
404
|
+
続けて `/fleet review` を、最初の fork のブランチに対して通す。作者の worktree に 2 人目の Pi が入る。
|
|
405
|
+
見るのはレビュワー自身のセッションファイル — タスク、diff、verdict の形、そして作者の報告の断片。
|
|
406
|
+
報告は `git` では出せない唯一の材料になる。そのあとレビュワーの最後の返答が verdict で終わることを
|
|
407
|
+
見る。指示どおり読み取り専用だったかを、worktree が汚れていないことで確かめる。
|
|
408
|
+
|
|
409
|
+
最後に linked worktree からの fork。main checkout から worktree が作られ、linked 側にしか無いコミット
|
|
410
|
+
で fork 点が呼び出し元の HEAD に固定されたことを確かめる。未コミットの変更は引き継がれず、どちらも警告
|
|
411
|
+
として出る。
|
|
412
|
+
|
|
413
|
+
このリポジトリに `tsconfig.json` は無いので、型チェックは明示的に実行する:
|
|
414
|
+
|
|
415
|
+
```sh
|
|
416
|
+
npx tsc --noEmit --target es2022 --module nodenext --moduleResolution nodenext \
|
|
417
|
+
--strict --skipLibCheck --allowImportingTsExtensions --types node \
|
|
418
|
+
packages/pi-herdr-fleet/index.ts
|
|
419
|
+
```
|