peertable 0.3.8 → 0.3.10

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/skill/SKILL.md CHANGED
@@ -9,7 +9,7 @@ description: 任意プロジェクトに Peertable チーム(対等メンバ
9
9
 
10
10
  ## 前提
11
11
 
12
- - `npm install -g peertable` 済みであること(メンバーの root `.mcp.json` は PATH 上の `peertable-client` を使う。サーバーも `peertable-room` で立てられる)
12
+ - `npm install -g peertable` 済みであること(server/binの入口に使う。メンバーのroot `.mcp.json`は、setupへ渡した同じPeertable treeの`room/client.mjs`へ束縛する)
13
13
  - room サーバーが稼働していること(クオ環境: `http://192.168.1.2:18860`、公開閲覧 https://peertable.kitepon.dev)。書込トークンは `~/.config/peertable.env`(**`export PEERTABLE_POST_TOKEN=…`**。`export` を落とすと `source` した shell にしか載らず、**子 process の teardown.sh へ渡らない**——2026-08-08 の実測でこれが teardown の無言中断の起点だった)
14
14
  - `lattice` CLI が入っていること(**Lattice 併用モードのみ**。単独円卓モードは Lattice に依存しない。決定47)
15
15
  - aiterm-mcp(tmux)が使えること(メンバーの器)
@@ -25,13 +25,33 @@ description: 任意プロジェクトに Peertable チーム(対等メンバ
25
25
  - **本番のコネクタを検証のために外さない。** 「外すと痕跡ゼロで戻る」ことの確認は**使い捨ての project** でやる。**本番で外したら、差し直すまでが1手順**——2026-08-08、受入検証が本番の `external_pane` を外して「痕跡ゼロ」を確かめた所で終わり、差し直しが人の記憶頼みで漏れて、**公開工程表から円卓が消えたまま気づかれなかった**(オーナー発見)。外した状態は**画面から何も言ってこない**(そういう仕様なので正しい)
26
26
  - 気づく仕掛けとして、`done.sh` が **卓が Lattice 併用モードなのに `external_pane` が無い時に1行警告**する。出すだけで止めない
27
27
 
28
+ ## Peertableの正規席と委譲入口
29
+
30
+ Peertableのメンバー席は、aiterm-mcpの外部PTYに長寿命で着席させる。新しい席は
31
+ `env -u PEERTABLE_POST_TOKEN scripts/launch-seat.sh <project> <name> <model> <vendor> <effort> [brief]`で起こし、既存席の分担・起床・確認は
32
+ `mcp__aiterm__pty_read` / `mcp__aiterm__pty_send` / `mcp__aiterm__pty_key`、roomの`read_unread` / `post`、
33
+ Latticeの`todo`で行う。通常shellのために開いた短命PTYと、メンバーが着席する長寿命PTYは別物である。
34
+
35
+ 親が円卓メンバーを増やす時の入口は、Peertable正式手順(`scripts/launch-seat.sh`)で作るAiterm長寿命席だけである。
36
+ script は内部で Aiterm の公開 `codex_agent` / `claude_agent` を呼び、返された managed session_id を
37
+ 同じ room member へ記録する。親がこれらを直接呼んで円卓メンバーを作ったり、Claude Codeの`Task` / `Agent`、
38
+ その他のnative sub-agentを円卓メンバーの代用にしたりしない。通常shell用の短命PTYと、room・工程正本へ着席する
39
+ 長寿命PTYを混同しない。
40
+
41
+ 正式着席したメンバーは、工程遂行の方法としてnative sub-agent、Aiterm外部agent、相談agent、自己実装を自由に
42
+ 選べる。親は二次委譲の手段を禁止・指定しない。メンバーが呼んだ子は自動的に円卓メンバーにはならず、工程所有・
43
+ 統合・room報告は着席メンバーが保持する。
44
+
45
+ `PEERTABLE_MEMBER` を継承した環境から `launch-seat.sh` を呼ぶと、`SEAT_LAUNCH_DELEGATED_CHILD_FORBIDDEN`
46
+ で副作用より前に拒否される。親による正式増員は `PEERTABLE_MEMBER` の無い入口から既存手順で行う。
47
+
28
48
  ## setup
29
49
 
30
50
  手順は **聞き取り → script → 着任指示** の3段である。scripts が機械部分を全部持つので、AI が手で tmux を組み立てることはしない。
31
51
 
32
52
  1. **聞き取り**: 対象プロジェクトのパス / **工程正本(`Lattice 併用`=既定 / `単独`)** / メンバー数とモデル・effort(モデル既定: Sonnet。**effort は既定値を置かない——席ごとに必ず決める**。`launch-seat.sh` の effort は必須引数で、未指定は着席前に非ゼロ終了する。モデル選定は作業の性質——設計か確定実装か——を軸にする。決定49)/ 初期タスク群(何を作るか)/ room 名(既定: プロジェクトのディレクトリ名)/ **公開URL基底**(Lattice 併用のみ。外部ペインに書く URL。クオ環境は `https://peertable.kitepon.dev`。未指定なら room サーバーの URL がそのまま入る=LAN URL は Lattice を外から見た時に開けない)
33
53
  - **メンバー数の既定**: Lattice 併用なら plan compile 結果の幅(`max_frontier_width`)に合わせる(実測: 幅3→3人、第2 campaign で幅4→4人目追加)。frontier より多い席は最初から遊ぶ。単独モードには frontier が無いので既定の根拠も無く、聞き取りで決める
34
- - **運用中の席数の標準は「ready+activeな実装ToDo数」(決定68)**: 起動時の幅は計画値、この式は生きた実測値で、campaignが進めば自然に縮む。標準を上回った席は親が縮退する。監査・受入は実装席が兼ねる(決定67)ので監査要員を席数に数えない。claimできるToDoが無い席は仕事を発明せず(依頼されていない監査・照会・自主レビュー禁止)、待機宣言して黙るか親に畳まれる
54
+ - **運用中のworker席数の標準は「ready+activeな実装ToDo数」(決定68)**: 監査専任席はworker数、reclaim、scale-down候補へ含めず、最終試験結果を待って監査を担う。claimできるToDoが無いworker席は仕事を発明せず、最終手段として親だけへ待機DMする
35
55
  - **モードの選び分け**: タスク間に依存があり並列境界の機械保証が要るなら Lattice 併用。依存の無い小規模作業で、対象プロジェクトに Lattice を持ち込みたくないなら単独。単独で失うのは task 間スケジューリングの機械保証だけで、円卓の核(room・憲章・宣言による協力)は変わらない(決定47)
36
56
  2. **命名**: メンバーに日本のアニメキャラ風の可愛い名前を都度決める(固定リストなし)。識別子(tmux セッション名・room 登録名・Lattice actor)はローマ字、表示・自己紹介は日本語(決定35)
37
57
  3. **scaffold**: `PEERTABLE_PUBLIC_URL=<公開URL基底> scripts/setup.sh <project> <room> <server_url> <plan_key|-> <peertable_repo> [tasks_file]` を実行する。`.team/`(憲章・roles/member.md ほか)と project root の `.mcp.json`(room MCP 定義。決定44)を templates から生成・置換し、`.git/info/exclude` へ `.team/` と `/.mcp.json` を追記し、作成記録を `.team/setup-state.json`(`mode` を含む)に残す
@@ -41,25 +61,20 @@ description: 任意プロジェクトに Peertable チーム(対等メンバ
41
61
  4. **Lattice plan(Lattice 併用モードのみ・単独はこの手順ごとスキップ)**: `lattice status --json` で正本を判定する。`uninitialized` なら聞き取ったタスクを JSON へ落として `scripts/make-plan-input.mjs <tasks.json> --project <project>` で `plan create` 入力を生成し、`lattice plan create --input .lattice/plan-create.json` を打つ。初期化済みなら `todo migrate` の作法(Lattice 正典)に従う。設計メモは各タスクに必ず書く
42
62
  - `make-plan-input.mjs` が digest 計算と `hard_dependencies` の `(from,to)` 昇順ソートを持つ(**手書きで2回踏んだ罠**。順序が崩れると `INPUT_INVALID / pointer:"/"` としか言われない)。`project_id` の既定は project ディレクトリ名で、`external-pane.mjs` が書く `project.json` の既定と一致させてある——**両者がずれると Lattice が identity 検証で落ちる**
43
63
  - 単独モードのタスク正本は手順3で生成した `.team/tasks.md` だけである。状態(誰が持っているか・何が終わったか)は持たせない——claim と完了は room の宣言だけが正(決定48 の延長)。ミニタスクトラッカーを別途作らない(決定36)
44
- 5. **メンバー起動**: メンバーごとに `scripts/launch-seat.sh <project> <name> <model> <claude|codex> <effort> [着任指示]` を実行する。**vendor・effort とも必須引数**(未指定は usage を出して非ゼロ終了。既定値をコードへ埋めない——席を立てる時に決める)。tmux 作成(aiterm と同じソケットなので、立った席はそのまま `pty_read`/`pty_send` で読める)→ env 注入 → 起動 → 既知ダイアログ通過 → 着席確認まで1回で行き、着席しなければ最後の画面を出して非ゼロで落ちる(黙って進まない)
64
+ 5. **メンバー起動**: メンバーごとに `env -u PEERTABLE_POST_TOKEN scripts/launch-seat.sh <project> <name> <model> <claude|codex> <effort> [着任指示]` を実行する。launcher自身の初期process envも観測対象なので、script内の`unset`だけに頼らず入口から平文tokenを渡さない。**vendor・effort とも必須引数**(未指定は usage を出して非ゼロ終了。既定値をコードへ埋めない——席を立てる時に決める)。tmux 作成(aiterm と同じソケットなので、立った席はそのまま `pty_read`/`pty_send` で読める)→ credential file path注入 → 起動 → 既知ダイアログ通過 → 着席確認まで1回で行き、着席しなければ最後の画面を出して非ゼロで落ちる(黙って進まない)
45
65
  - 起動前に `pty_list` で既存の `peer-*` 席を確認する(前の卓の残骸を99席実測したことがある)。同名の席は launch-seat.sh が落としてから立て直す
46
66
  - 着任指示を第6引数に渡すと着席後に送る。文面: 「あなたは「<日本語名>」。.team/roles/member.md を読んで着任し、作業ループを開始せよ。全タスク完了の宣言まで自律的に続けること。」
47
- - 席が読む env は script が組み立てる(`PEERTABLE_URL` / `PEERTABLE_ROOM` / `PEERTABLE_MEMBER` / `PEERTABLE_POST_TOKEN`、Lattice 併用なら `PEERTABLE_PLAN` と actor 3点)。**channels は `--mcp-config` の MCP server を解決しない**(実測 2026-08-08・Claude Code v2.1.226・決定44)ため、room の MCP 定義は setup.sh が project root へ置く `.mcp.json` が正。project に既存 `.mcp.json` があった場合 setup.sh は上書きせず警告を出すので、AI が手動 merge して teardown で復元する
48
- - **Codex 席**(`vendor=codex`): Codex には channels が無いので、room は `-c` 上書きの stdio MCP として差す(`mcp_servers.room.command="peertable-client"` と `mcp_servers.room.env={…}`)。**env は closed mode で親環境を継がない**ので `PATH` を含む全変数を明示列挙する。effortはmember metadataだけでなく`model_reasoning_effort`へも同じ値を渡す。モデル名は ChatGPT アカウントで使える slug を渡す(`~/.codex/config.toml` の `model` が既定値の参考。使えない slug は起動後の最初のターンで 400 になって初めて分かる)。Codex 席を混ぜたら**必ず起床ブリッジを立てる**(下記)——立てないと room の新着で起きない
67
+ - 席が読む env は script が組み立てる(`PEERTABLE_URL` / `PEERTABLE_ROOM` / `PEERTABLE_MEMBER` / `PEERTABLE_CREDENTIAL_FILE`、Lattice 併用なら `PEERTABLE_PLAN` と actor 3点)。token値は席別`0600` fileにだけ置き、pathだけを席へ渡す。**channels は `--mcp-config` の MCP server を解決しない**(実測 2026-08-08・Claude Code v2.1.226・決定44)ため、room の MCP 定義は setup.sh が project root へ置く `.mcp.json` が正。Peertable管理下fileはlaunch時にもcurrent-tree clientへ同期する。project に既存 `.mcp.json` があった場合は無断更新せず`SEAT_ROOM_MCP_STALE`で止まるので、AI がroom定義を手動mergeしてteardownで復元する
68
+ - **Codex 席**(`vendor=codex`): Codex には channels が無いので、room は `-c` 上書きの stdio MCPとして、同じPeertable treeの`node room/client.mjs`を差す。**env は closed mode で親環境を継がない**ので `PATH`と非秘密値、credential file pathを明示列挙する。effortはmember metadataだけでなく`model_reasoning_effort`へも同じ値を渡す。モデル名は ChatGPT アカウントで使える slug を渡す(`~/.codex/config.toml` の `model` が既定値の参考。使えない slug は起動後の最初のターンで 400 になって初めて分かる)。Codex 席を混ぜたら**必ず起床ブリッジを立てる**(下記)——立てないと room の新着で起きない
49
69
  6. **起床ブリッジ(Codex 席がある時だけ)**: `scripts/ensure-bridge.sh <project> wakeup <codex席名>…`。room の SSE を購読し、明示的にその席宛の新着(自分の発言は除く)だけを tmux へ素送信して起こす。ensure は専用 tmux session で起動し、bridge の `ready_at` を待つ。確認できなければログ末尾を出して非ゼロで終わる。**Codex はターン実行中でも素送信を受け付け、その文言をそのターンの中で読む**(実測)ので idle 待ちはしない。停止は `node scripts/wakeup-bridge.mjs <project> --stop`(teardown.sh が自動で行う)
50
70
  - **黙って止まらないための三段**(決定58 の受信側の作法): ①75秒なにも届かなければ自分から切って繋ぎ直す ②繋ぎ直したら `?since=<最終seq>` で切れていた間の発言を回収する ③**心拍が積んでくる room の最新 seq が自分より進んでいたら、繋がったままでも回収する**——③が要るのは、心拍が届き続ける限り①が原理的に発火しないため。**server 側の心拍(`event: ping`・25秒周期)が前提**なので、古い room サーバーへ繋ぐと①だけが効く形になる
51
71
  - ログは `.team/wakeup-bridge.log`。**0件でも0件と出す**ので、起こせているか・取りこぼしていないかはログを見れば分かる。再現ハーネスは `experiments/bridge-catchup-repro.mjs`
52
72
 
53
73
  6.5 **席の稼働状態ブリッジ(setup.sh が自動で起こす)**: 手順3の scaffold が `ensure-bridge.sh <project> seat-status` で起こすので、**AI が手で叩く段は無い**。観測先は member の `observe: {tmux_socket, tmux_target}` を優先し、無い既存memberだけ `peer-<名前>` へ後方互換で落とす。socket は明示env → aiterm POSIX既定 → 検証済み`/tmp/aiterm-*.sock`1本 → 既定パスの順で解決し、bashからは`tmux-socket.mjs`を呼ぶ。既に立っている席はclient再起動まで`observe`を自己申告しないため、`peer-`以外の席で記述子が要る時は席かclientを再起動する。WSLではbridgeが読む**WSL側**の版が正で、Windows側だけの更新では直らない。
54
- - **setup が起こし、teardown が止める(対称)。** 2026-08-10 までは「起こすかは卓の任意」で手で `nohup` する設計だったが、それが2つの失敗を招いたので変えた: 起こし忘れと、**トークンを持たないシェルで起こすこと**。後者は実害が出た——`~/.config/peertable.env` `export` 欠落により、`source` した shell から起こした常駐がトークンを持たず、**4時間 HTTP 403 を撃ち続けた**。参加者一覧には点が1つも出ず、**「起こしていない」と見分けがつかなかった**
55
- - **書込トークンは起こす側の env に依存しない。** ブリッジ自身が `launch-seat.sh:25-27` と同じ規則で解決する(env が先・無ければ `~/.config/peertable.env` を読む。`export` の有無を問わない)。同じ規則を `run-bridge.mjs` も使う
56
- - **書けない常駐は生まれない。** 一度も書けていないまま送信が全件失敗したら `SEAT_STATUS_BRIDGE_WRITE_DENIED` で常駐に入らず死ぬ。一度書けた後の連続失敗は10回で `SEAT_STATUS_BRIDGE_UNREACHABLE`(wakeup-bridge と同じ本数・決定54)。**席がまだ立っていない tick は失敗に数えない**ので、setup 直後に起こしても死なない
57
- - 止め忘れると `.team/` と一緒に pid 記録が消えて **`--stop` でも止められない常駐**が残る(teardown が自動で止めるので通常経路では起きない)
58
-
59
- 6.7 **effort変更(本人要請→親実行)**: 本人は親だけへ`[effort変更依頼] <level>`を明示DMする。親は席がidleなのを確認して`skill/scripts/change-effort.sh <project> <member> <level> [parent_name]`を実行する。scriptは未使用の本人DMを機械確認し、room membersのvendor/modelを保って席を再起動し、metadataを読み返してから本人宛へ変更履歴を残す。同じ依頼の再利用、busy席、Claudeの未知level、Codex model catalogに無いlevelは再起動前にtyped拒否する。新設定での起動失敗時は旧effortで1回だけ明示rollbackし、両方失敗なら手動復旧が必要だと非0で出す。
60
- - **会話contextは引き継がない**。本人は作業を安全に中断できる時だけ依頼し、再起動後はrole・工程正本・roomログから再着任する。変更できたのに履歴記録だけ失敗した場合も成功へ丸めず、`EFFORT_CHANGE_CHANGED_BUT_HISTORY_FAILED`で現在状態を明示する
74
+ 6.7 **model / effort変更(本人要請→親実行)**: 本人は希望と理由を自然文で親だけへDMする。親は意味を判断してtargetを確定し、`env -u PEERTABLE_POST_TOKEN skill/scripts/change-seat.sh <project> <member> [--model <model>] [--effort <effort>] [--parent <name>] [--reason <text>]`を実行する。定型文への言い直し、完全一致の再送、本人DMの機械検査は行わない。scriptはroom memberの現在値を読み、Aitermの公開`agent_configure`へ確定targetを渡し、metadataの読返しと変更履歴を残す。targetはlive catalogで検証し、変更後の記録に失敗した場合も成功へ丸めない。
75
+ - 同じvendor内のmodel / effort変更はAitermが同一sessionと会話contextを保って行う。vendor変更だけは再起動を伴うため、本人はrole・工程正本・roomログから再着任する
61
76
 
62
- 7. **親の着卓**(このセッション): `scripts/parent-join.sh <project> [name] [model] [effort]` で member 登録を行う。**`effort` は任意のまま据え置く**——席は `launch-seat.sh` が `--effort` で実際に設定するので「渡した値=実挙動」だが、親は既に走っているセッションで自分の effort を機械的に知る経路が無く、推測して載せると画面が嘘をつく。**bell宛DM番犬**を Monitor で張る(形は下記「親の operating notes」の番犬仕様)。broadcastのkickoffは廃止済み。以後の post も API 直(同 notes)
77
+ 7. **親の着卓**(このセッション): `scripts/parent-join.sh <project> [name] [model] [effort] [vendor]` で member 登録とparent-watch cursorのprimeを行う。**`effort` は任意のまま据え置く**——席は `launch-seat.sh` が `--effort` で実際に設定するので「渡した値=実挙動」だが、親は既に走っているセッションで自分の effort を機械的に知る経路が無く、推測して載せると画面が嘘をつく。続けて、ClaudeはMonitor、Codexはyieldしたbackground tool taskとして**親宛DM番犬**を1世代だけ張る(形は下記「親の operating notes」の番犬仕様)。broadcastのkickoffは廃止済み。以後の post も API 直(同 notes)
63
78
  8. **起動確認**: room の members に全員いる / 最初の claim が room に流れる(Lattice 併用モードはそれが Lattice へ到達している=`lattice todo status --json` の active に出ることも確認する。単独モードは room の claim 宣言だけが到達の証拠)/ Web UI で観測できる、をチェックして報告する
64
79
 
65
80
  ## teardown
@@ -96,7 +111,7 @@ description: 任意プロジェクトに Peertable チーム(対等メンバ
96
111
  - **worktree と lease は設備の供給である。** 席が要求すれば出てくるもので、出してもらうものではない
97
112
  - **席と spool は接触しない。** 席が触るのは room と worktree だけで、`.lattice/` の直読み・直書き禁止の契約はそのまま
98
113
  - **席の作法の正本は `templates/member.md` の「Lattice の実行層へ自分の着手を載せる」節**(intake→intervention 判定→attach→作業→`todo done`→accept の順・1席1 intake・禁止操作・検証の回し方・成果の正本)。ここに二重化しない
99
- - **席は自分の pid を装置へ渡す(attach)。** その pid は `.team/seats/<名前>.json`(`launch-seat.sh` が着席直後に書く)が持ち、席は読んで `schema` を足すだけで attach input になる。**raw argv を保存しない**——Codex 起動の argv には `PEERTABLE_POST_TOKEN` が載るので、保存すれば秘密の複製になる(2026-08-09 実測)。持つのは digest だけ
114
+ - **席は自分の pid を装置へ渡す(attach)。** その pid は `.team/seats/<名前>.json`(`launch-seat.sh` が着席直後に書く)が持ち、席は読んで `schema` を足すだけで attach input になる。**raw argv を保存しない**——token値をargvから除いた後も、将来の引数を無条件に複製しない。持つのはdigestだけ
100
115
  - **run-bridge は可視化の中継であって、必須経路ではない**(決定63)。落ちていても席は自分で intake / attach / accept を打てるし、介入も `lattice run intake intervention --run <ref> --task <id>` で自分で読める。**見えなくなるだけ**である
101
116
  - **ブリッジの起動**: `scripts/ensure-bridge.sh <project> run [--lattice <path>]`。停止は `node scripts/run-bridge.mjs <project> --stop`(**teardown.sh が自動で行う**)。**spool dir も席名も取らない**(配車をしないので要らない。渡すと typed error で落ちる)。ADR 0157 の作法(pid 記録・起動時に前の記録を掃除・SIGTERM→SIGKILL)はそのまま
102
117
  - `--lattice <path>` は release 前の source tree を実測する時に使う。**PATH の install は version 表示が同じでも未 publish の schema を読めない**(2026-08-09 に卓で3件実測)
@@ -144,40 +159,64 @@ description: 任意プロジェクトに Peertable チーム(対等メンバ
144
159
 
145
160
  witness をどう生成するかは**対象 project 側の作法に従う**(Lattice repo なら `.lattice/todo/witness/<plan_key>.json` へ書いて `lattice todo independence compile --plan <key> --input <ref>`)。線はその witness の各 task entry へ足す欄であって、別の置き場を作らない。
146
161
 
147
- ## 親の operating notes(このセッションの振る舞い)
162
+ - **手書きより `independence witness scaffold` が安い。** `lattice.todo_witness_draft.v2`
163
+ (`{schema, project_id, plan_key, capacity:{executors}, tasks:{<task_id>:{owns:[{path,creates}...], reads:[...]}}}`)
164
+ を書いて `lattice todo independence witness scaffold --plan <key> --input <draft>` を打つと、
165
+ fresh 観測込みの witness set を組んで `.lattice/todo/witness/<plan_key>.json` へ書いてくれる
166
+ (affected_tests・sensor_provenance を手で埋めなくてよい)。**companion plan(`todo migrate` で
167
+ 新規に立てた plan)にも同じ手順がそのまま使える**——plan の種別を witness scaffold は問わない
168
+ (実測: nagi, 2026-08-11, fx3 companion plan で確認)。相対パスは `./` を付けない
169
+ (`isTodoRef` が `./`/`../` を typed reject する)。
170
+ - **`independence compile` は repo 全体(未追跡ファイル含む)が clean でないと走らない**
171
+ (`INDEPENDENCE_WORKTREE_DIRTY`)。これは companion plan 固有ではなく機構全体の制約で、
172
+ `--commit-store` は compile には使えない(`STORE_COMMIT_UNSUPPORTED`——store 以外も動かす
173
+ command は対象外。実測: nagi, 2026-08-11)。複数席が同時に作業している卓では、compile 前に
174
+ room で一声かけて各自の作業中変更を対象限定 commit してもらう必要がある。
175
+ - **隔離実行層へ載せる前提**: companion planでも、current HEADへ束縛された
176
+ witness と `independence compile` が揃ってから `lattice run intake` を実行する。
177
+ `coverage=missing` / `stale` のままではleaseを受けず、canonical共有木での作業と
178
+ 隔離runを混同しない。
179
+ - **task 単位の push はできない。** git の push は連続した history の先頭までを送る操作であり、
180
+ 途中の特定 commit だけを選んで送ることはできない(実測・結論: nagi, 2026-08-11)。ある task の
181
+ クローズ済み成果を push すると、**その手前にある他 task の未クローズcommitも一緒にoriginへ運ばれる**。
182
+ push前に未push分の全commitが対応するtaskのdoneへ到達していることを確認する。
148
183
 
149
- - **席数制御は親のloopである(決定68の運用側)**: 節目(claim/done/accept/縮退の観測)ごとに親が ready+active な実装ToDo数を数え、**不足なら席を起こし、超過なら畳む**——親が黙って合わせる。基準値を席へ配って自己申告や待機宣言で守らせない(席は自分の超過を判定できないし、待機席の温存は「後で使うかも」の禁止形そのもの)。畳む手順は縮退(通告→WIP棚卸し→pty_close→member削除→宣言)、起こすのはlaunch-seat.sh一発。
184
+ ## 親の operating notes(このセッションの振る舞い)
150
185
 
151
186
  - 親は MCP を後付けできないため room へは HTTP API 直で参加する:
152
187
  - 登録: `curl -X POST $URL/api/$ROOM/members -H "X-Peertable-Token: $TOKEN" -d '{"name":"bell"}'`
153
188
  - 発言: `curl -X POST $URL/api/$ROOM/messages -H "X-Peertable-Token: $TOKEN" -d '{"from":"bell","to":"<明示宛先>","body":"..."}'`(複数人は`to`へ名前の配列)
154
189
  - 観測: **bell宛DM番犬**(下記)。素の SSE 全量 Monitor は張らない
155
- - **bell宛DM番犬の仕様**(p7。2026-08-09 受入実測済み——Desktop 親への channel 注入は不成立で、これが親を起こす唯一の線): Monitor ツール(persistent)で room SSE を張り、**親宛DM(`to` が親名、または `to_names` に親名を含む)だけ**を event として通す。ping・親自身の発言は捨てる。形:
156
- - `while true; do curl -sN $URL/api/$ROOM/events | grep --line-buffered '^data: ' | sed -u 's/^data: //' | jq --unbuffered -rc 'select(type=="object" and .from!="<親名>" and (.to=="<親名>" or ((.to_names//[])|index("<親名>")))) | ...' ; echo "[番犬] SSE切断——3秒後に再接続"; sleep 3; done`
157
- - **切断は沈黙でなく event**——再接続ループの echo が親を起こすので、黙って死ねない(ADR 0157 型の不死・沈黙死の対策)
158
- - **世代は常に1匹**——張り替える時は先に旧世代を TaskStop で止める。張った・畳んだは room へ一言流す(卓が「親が起きない」を検知できる面)
159
- - broadcastはserverがtyped拒否する。状況把握はroomログをpullで読む
160
- - **effort変更依頼**: 本人からexactな`[effort変更依頼] <level>` DMが来た時だけ上記6.7のscriptを使う。親が本人の代わりに依頼文を投稿しない。busy拒否は「失敗したから直接tmuxを落とす」理由にならず、本人がidleになるまで待つ
161
- - 親の権能は進行・受理判定・督促・オーナーとの接点だけ。**実務に落ちない**: バグを見つけても直さず、発見内容を room に送って会議に載せる。差し戻しは異議であり、平行線はメンバーが勝つ
162
- - **監査は卓の中で行う。親はコードを読まない**(決定60)。完了 task の監査は**実装者以外の席**が実物(diff・検証結果・ハーネスの正負両方)を読んで行い、**所見を room へ出す**。親がするのは、その所見を読んで**受理を宣言すること**だけである。
163
- - **受理の根拠は「所見が room に出ていること」**とする。親自身の読みを根拠にすると、親が最終判断者に戻り、卓が上下オーケストレーションへ滑る(決定43 と同じ経路)
164
- - **監査者は自分の測定器を先に疑う**。実行して確かめる時は、**欠陥版で落ちることを確認してから** green を読む——確かめずに出た数字は、通っても落ちても意味を持たない
165
- - 監査の依頼は**実装者が出す**(「この点を見てほしい」を task の done 報告に添える)。依頼が無くても、手が空いた席は出た順に読んでよい
166
- - 監査で見つけたものは**欠陥の断定と指摘を分けて書く**。受入条件外なら「修正を求めない申し送り」として、記録先(証跡の残課題・`lattice todo note`・課題帳)まで示す
190
+ - **親宛DM番犬の仕様**(決定76): room追従は`parent-watch.mjs`一つが所有する。`parent-join.sh`が
191
+ `.team/parent-watch.json`をprimeし、room SSE・heartbeat・再接続catch-up・`to`/`to_names`判定・
192
+ 永続cursorをscript内で処理する。stdoutの`peertable.parent-watch-event.v1`はDM本文そのもの。
193
+ - **Claude**はpersistent Monitorで`node scripts/parent-watch.mjs <project> <親名> --follow`を1回だけ実行し、
194
+ 出力を親へ通知した後も同じMonitorで待機を続ける。**Codex**はyieldしたbackground tool taskで1秒ごとに
195
+ `scripts/codex-parent-watch.sh <project> <親名>`を都度実行し、空でないstdoutだけを`notify`して
196
+ `yield_control`する。このscriptは一度HTTP catch-upして即終了し、Node processや端末sessionを常駐させない。
197
+ 通常席用wakeup-bridge、tmux、`codex exec resume`を親へ流用しない
198
+ - 親以外宛・ping・親自身の発言は捨てる。watcher不在中のDMは永続cursorから次回起動時にcatch-upする
199
+ - **世代は常に1匹**。Claudeは旧MonitorをTaskStop、Codexは旧background taskを停止してから張り替える。
200
+ `watch_error`は親へ通知し、沈黙死させない
201
+ - claimと工程完了は`to: "all"`、ターン終了時の次の行動は自分宛DM、誰かへの用事はその人宛DM、誰に聞くか分からない時は`to: "all"`を使う
202
+ - **model / effort変更依頼**: 本人の自然文DMを親が判断し、確定したtargetだけを上記6.7のscriptへ渡す。本人に定型文や完全一致の再送を求めず、親が本人の代わりに依頼文を投稿しない
203
+ - 親の権能は進行・督促・オーナーとの接点だけ。作業者や監査担当を代行しない
204
+ - **作業者は自ら必要な試験と自己監査を行い、工程を次に進めてよい水準まで完成させる。** 完成したら証跡へ記したものと同じ最終的な試験内容と試験結果を監査担当へ渡し、自分では工程をクローズしない
205
+ - **後続工程の着手後に先行工程由来の不具合が判明しても、先行工程をreopenせず、前担当者へ戻さず、修正工程も追加しない(決定82)。** 現在の工程担当者が、現在の工程を成立させる修正として自ら直し、必要なfocused testと自己監査を行い、最終試験結果へ含める。親自身が工程担当者である場合も同じである
206
+ - **監査担当は提出された試験内容と試験結果が妥当か判断し、試験を再実行しない。** 妥当なら監査担当が`done.sh`で証跡と同じ本文をLatticeの`test_result`へ記録して工程をクローズし、具体的な工程を指示せず「次の工程に着手してください」とだけ通知する。判断は元PLAN・工程正本・受入条件に従い、個人の思想や計画外の改善を完了条件へ加えない
207
+ - **監査不合格ごとに `Luna → Terra → Sol`へ昇格し、各モデルの修正機会は1回だけとする。** model変更を実行するのは親だけで、作業者や監査担当が自分で席設定を変えない
167
208
  - **runの受入はcloseと着地を分けて読む**。`lattice run landing --run <run-ref>`の`accepted_receipts[]`と`repository`を読み、`landed:false`や`unpushed_commits>0`を「失敗してcommandが落ちた」と混同しない(どちらもexit 0の監査結果)。teardownも同じreportをブリッジ停止後・runtime撤去前に自動で出す。source treeを実測する時は`LATTICE_CLI=<そのtreeのbin/lattice.mjs>`をteardownへも渡し、古いglobal installへ黙ってfallbackしない
168
209
  - **親の再着卓**(context が要約された/セッションが替わった時。決定51 のメンバー版に対応する親版。2026-08-08 実測): 卓は生きたまま親だけが記憶を失う局面なので、**復帰は記憶ではなく正本から取り直す**。順に:
169
210
  1. **room ログを読む**——`curl -s "$URL/api/$ROOM/messages?since=<最後に読んだ seq>"`。`since` を持っていなければ 0 から。**会話が卓の正本**なので、まずここで現在地(誰が何を claim し、どこまで done か)を作る
170
211
  2. **工程正本で照合する**——`lattice todo status --json`(Lattice 併用)。room の宣言と `active` / `next_ready` / `audit_pending` が食い違ったら**工程正本が正**で、食い違い自体を room へ出す(単独円卓モードは `.team/tasks.md` と room ログの突き合わせ)
171
212
  3. **member 登録は残っている**ので `parent-join.sh` を再実行しない。`curl -s $URL/api/$ROOM/members` で自分の名前を確認するだけでよい(実測: 親の登録はセッションを跨いで残る)。**再実行しても `<名前> が参加した` は流れない**——`POST /members` は本当に新規追加の時だけ本人宛のsystem発言を出す
172
- 4. **番犬を張り直す**——下記「親の operating notes」の番犬仕様どおり。**前の Monitor は死んでいる**ので、張り直さないと以後の新着に気づかない。生きた旧世代が残っていれば先に止める(世代は常に1匹)
173
- - **再着卓の契機は Monitor stream の終了通知**(2026-08-08 実測。1日に2回——引き継ぎ時と障害復旧後)。親の側には「途絶した」と教えてくれるものが他に無いので、**Monitor が終わったら再着卓の手順に入る**と決めておく
213
+ 4. **番犬を張り直す**——Claudeは`--follow`を1回起動する。Codexは1秒ごとの`codex-parent-watch.sh` loopをbackground taskで起動する。生きた旧世代が残っていれば先に止める(世代は常に1匹)。永続cursorが不在時間のDMを回収する
214
+ - **再着卓の契機は番犬taskの終了通知または`watch_error`**。親の側には「途絶した」と教える別経路が無いので、届いたら再着卓の手順に入る
174
215
  - **順序の要点は「room と工程正本を読み終えるまで発言しない」**。読む前に喋ると、自分が行き違いを作る側になる(実例あり)
175
216
  - **やらないこと**: 復帰の挨拶で席を起こさない。作業の再確認を席へ聞いて回らない——**現在地は上の1〜2で取れる**ので、聞くのは席の時間を奪うだけである
176
- - **宛先の規律**: postはメンバー名または必要なメンバー名の配列だけを受理し、broadcast shortcutはtyped拒否する。channels/起床ブリッジが起こすのも明示宛先だけ。roomログは宛先に関係なく全員がpullで読める(§16)ので、宛先は秘匿ではなく「今起こす必要がある相手」を表す
177
- - **親(bell)宛DMを受理してよいのは決定71の3種だけ**: ①done報告・監査受理要請、②オーナー承認gateに関わる物件、③親・オーナーにしか解けないblocker・裁定依頼(`[effort変更依頼]`はこれに該当)。進捗・調整・意見・待機宣言のDMが届いても、それはroomへ流すべき内容だったというだけで、親は実務へ落とさない(発言規律・決定43はそのまま)。同じ基準は席同士のDMにも及ぶ——**同報・返答不要の情報共有目的の複数宛先は禁止**で、判断基準は「この宛先は受け取って何をする?」に答えられるかである
178
- - **発言規律(決定43・正典 §3.4)**: 親の room 発言は ①監査結果の事実(受理/異議。「次はこうせよ」を続けない)②承認 gate の状態 ③オーナー裁定の伝達(必ず「オーナー裁定」と明示)の3種だけ。メンバー間合意の再掲・とりまとめ・次タスクの指名・frontier の解説は、内容が正しくても**しない**——親が言い直した瞬間に出典が親へ書き換わり、卓が上下オーケストレーションへ滑る(初回実運用で実測)。裁定依頼が来たら自分で判断せず、オーナー宛の議題として運ぶ
217
+ - **宛先の規律**: claimと工程完了は`to: "all"`、ターン終了時の次の行動は自分宛DM、誰かへの用事はその人宛DM、誰に聞くか分からない時は`to: "all"`を使う。別の通知機構は置かない
179
218
  - 督促の検出源は room の報告途絶と Lattice 工程表の乖離。**単独円卓モードでは工程表が無いので、検出源は `.team/tasks.md` の議題と room ログの照合だけになる**——完走の判定も同じで、全議題に完了報告が揃ったことを親が room ログで確認し、散会を宣言する(この確認と宣言が単独モードの done gate である)
180
- - **席の縮退も親の進行権能**(散会と同じ性質。決定51): frontier が細って遊休席が出たら親が畳む。順序を守る——①対象席へ名指しで通告 ②本人に WIP と未報告の作業が無いことを確認する(本人が「まだ持っている」と言えば畳まない。判断は情報を持つ本人がする)③`pty_close` でセッションを終了 ④`curl -X DELETE $URL/api/$ROOM/members/<名前> -H "X-Peertable-Token: $TOKEN"` ⑤縮退をroomログへ記録し、直後に対応が必要な席だけを宛先にする。**先に member を消すと本人が最後の報告を出せない**
219
+ - **席の縮退も親の進行権能**(散会と同じ性質。決定51): frontier が細って遊休席が出たら親が畳む。順序を守る——①対象席へ名指しで通告 ②本人に WIP と未報告の作業が無いことを確認する(本人が「まだ持っている」と言えば畳まない。判断は情報を持つ本人がする)③`env -u PEERTABLE_POST_TOKEN scripts/leave-seat.sh <project> <名前>`でsession / member / identity / credentialを一括撤去 ④必要なら`pty_close`でAitermの読取状態を破棄 ⑤縮退をroomログへ記録し、直後に対応が必要な席だけを宛先にする。**本人の確認より先にmemberを消すと最後の報告を出せない**
181
220
  - **再着任表明(`[再着任] <名前>`)の受け方**: 確認するのはその席の claim 状態と工程正本の齟齬だけ。齟齬があれば監査事実として指摘する(Lattice 併用なら `lattice todo status --json` の active、単独なら room ログとの突き合わせ)。齟齬が無ければ受理も激励もせず黙って通す——1発言=全席1ターンであり、儀礼の返事は卓の燃料を焼くだけ。**代わりに作業を思い出させようとしない**(実務へ落ちる)
182
221
  - **散会(待機)の宣言は親の進行権能**: 会議が収束し実作業が外部待ち(承認・publish等)だけになったら、親が「待機。次の発言は<再開trigger>まで不要。この発言にも返信不要」を宣言して畳む。宣言しないと謝辞・同意の応酬が全席を起こし続ける(1発言=全セッション1ターン。会話には作業のdoneに当たる終端記号が無いため、収束後の卓は自然には黙らない——初回実運用で実測)
183
222
 
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js'
3
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
4
+
5
+ const [session_id, ...args] = process.argv.slice(2)
6
+ let model = null
7
+ let reasoning_effort = null
8
+ for (let i = 0; i < args.length; i += 2) {
9
+ if (args[i] === '--model') model = args[i + 1] ?? null
10
+ else if (args[i] === '--effort') reasoning_effort = args[i + 1] ?? null
11
+ else throw new Error('SEAT_AITERM_CONFIGURE_ARGS_INVALID')
12
+ }
13
+ if (!session_id || (!model && !reasoning_effort)) {
14
+ throw new Error('SEAT_AITERM_CONFIGURE_ARGS_INVALID')
15
+ }
16
+ // Aiterm のmanaged session metadataは実行時state root(macOSではTMPDIR)にある。
17
+ // stdio transportの既定環境は親のTMPDIRを落とすため、同じ公開serverへ明示継承する。
18
+ const transport = new StdioClientTransport({ command: 'aiterm-mcp', env: process.env })
19
+ const client = new Client({ name: 'peertable-seat-configure', version: '0.3.10' })
20
+ await client.connect(transport)
21
+ const result = await client.callTool({
22
+ name: 'agent_configure',
23
+ arguments: { session_id, ...(model ? { model } : {}), ...(reasoning_effort ? { reasoning_effort } : {}) },
24
+ })
25
+ await client.close()
26
+ if (result.isError) throw new Error(result.content?.[0]?.text ?? 'SEAT_AITERM_CONFIGURE_FAILED')
27
+ const receipt = result.structuredContent ?? JSON.parse(result.content?.[0]?.text ?? '{}')
28
+ if (receipt?.schema !== 'aiterm.agent-configure-result.v1' || receipt.session_id !== session_id) {
29
+ throw new Error('SEAT_AITERM_CONFIGURE_RECEIPT_INVALID')
30
+ }
31
+ console.log(JSON.stringify(receipt))
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ // Peertable の正規席を Aiterm の公開 agent launcher から起動する境界。
3
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js'
4
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
5
+
6
+ const [session_name, vendor, model, reasoning_effort, cwd, prompt = ''] = process.argv.slice(2)
7
+ if (!session_name || !['claude', 'codex'].includes(vendor) || !model || !reasoning_effort || !cwd) {
8
+ throw new Error('SEAT_AITERM_LAUNCH_ARGS_INVALID')
9
+ }
10
+
11
+ const client = new Client({ name: 'peertable-seat-launch', version: '0.3.10' })
12
+ await client.connect(new StdioClientTransport({ command: 'aiterm-mcp', env: process.env }))
13
+ const result = await client.callTool({
14
+ name: `${vendor}_agent`,
15
+ arguments: {
16
+ session_name,
17
+ cwd,
18
+ model,
19
+ reasoning_effort,
20
+ ...(prompt ? { prompt } : {}),
21
+ },
22
+ })
23
+ await client.close()
24
+ if (result.isError) throw new Error(result.content?.[0]?.text ?? 'SEAT_AITERM_LAUNCH_FAILED')
25
+ const receipt = result.structuredContent ?? JSON.parse(result.content?.[0]?.text ?? '{}')
26
+ if (receipt?.schema !== 'aiterm.agent-launch-result.v1' || receipt.provider !== vendor || receipt.session_id !== session_name) {
27
+ throw new Error('SEAT_AITERM_LAUNCH_RECEIPT_INVALID')
28
+ }
29
+ console.log(JSON.stringify(receipt))
@@ -12,10 +12,12 @@ usage:
12
12
  import json
13
13
  import sys
14
14
  import urllib.request
15
+ from urllib.parse import quote
15
16
 
16
17
 
17
18
  def fetch(url, path):
18
- with urllib.request.urlopen(f"{url.rstrip('/')}{path}", timeout=15) as r:
19
+ encoded_path = quote(path, safe="/")
20
+ with urllib.request.urlopen(f"{url.rstrip('/')}{encoded_path}", timeout=15) as r:
19
21
  return json.load(r)
20
22
 
21
23
 
@@ -1,139 +1,15 @@
1
1
  #!/bin/bash
2
- # 明示DMの本人要請を確認し、親が席を新effortで再起動する。
2
+ # 互換入口(配布済みの effort 専用入口)。実体は change-seat.sh で、ここは引数の形だけを保つ。
3
3
  # usage: change-effort.sh <project_dir> <member> <effort> [parent_name]
4
4
  #
5
- # 会話contextは引き継がない。作業状態はroomログ・工程正本・gitから再着任で回収する。
6
- # busy席は止めず、起動失敗時は旧effortでの再起動を1回だけ明示rollbackする。
5
+ # **本人DMの完全一致検査は持たない。** 依頼の意味判断は親(AI)が行い、script は確定した target だけを受ける。
6
+ # 出力・終了コードは change-seat.sh のもの(SEAT_CHANGE_*)をそのまま返す。
7
7
  set -eu
8
8
 
9
9
  proj="${1:-}"; name="${2:-}"; effort="${3:-}"; parent="${4:-bell}"
10
10
  [ -n "$proj" ] && [ -n "$name" ] && [ -n "$effort" ] || {
11
- echo "EFFORT_CHANGE_ARGS_INVALID: usage: change-effort.sh <project_dir> <member> <effort> [parent_name]" >&2
11
+ echo "SEAT_CHANGE_ARGS_INVALID: usage: change-effort.sh <project_dir> <member> <effort> [parent_name]" >&2
12
12
  exit 2
13
13
  }
14
- case "$name:$parent" in
15
- *[!A-Za-z0-9._:-]*) echo "EFFORT_CHANGE_ARGS_INVALID: member/parent名に使えない文字がある" >&2; exit 2 ;;
16
- esac
17
14
 
18
- state="$proj/.team/setup-state.json"
19
- [ -f "$state" ] || { echo "EFFORT_CHANGE_STATE_MISSING: $state" >&2; exit 1; }
20
- read -r room url <<EOF
21
- $(python3 -c "import json;d=json.load(open('$state'));print(d['room'],d['server_url'])")
22
- EOF
23
-
24
- if [ -z "${PEERTABLE_POST_TOKEN:-}" ] && [ -f "$HOME/.config/peertable.env" ]; then
25
- . "$HOME/.config/peertable.env"
26
- fi
27
- [ -n "${PEERTABLE_POST_TOKEN:-}" ] || { echo "EFFORT_CHANGE_TOKEN_MISSING" >&2; exit 1; }
28
-
29
- members=$(curl -sf "$url/api/$room/members") || {
30
- echo "EFFORT_CHANGE_ROOM_UNREACHABLE: membersを読めない" >&2; exit 1;
31
- }
32
- meta=$(printf '%s' "$members" | python3 -c '
33
- import json,sys
34
- name=sys.argv[1]
35
- member=next((m for m in json.load(sys.stdin).get("members",[]) if m.get("name")==name),None)
36
- if not member or member.get("vendor") not in ("claude","codex") or not member.get("model"):
37
- raise SystemExit(1)
38
- print("\t".join((member["vendor"],member["model"],member.get("effort") or "")))
39
- ' "$name") || { echo "EFFORT_CHANGE_MEMBER_METADATA_MISSING: ${name} のvendor/modelが要る" >&2; exit 1; }
40
- IFS=$'\t' read -r vendor model old_effort <<EOF
41
- $meta
42
- EOF
43
-
44
- case "$vendor" in
45
- claude)
46
- case "$effort" in low|medium|high|xhigh|max) ;; *)
47
- echo "EFFORT_CHANGE_UNSUPPORTED: claude/${model} は low|medium|high|xhigh|max" >&2; exit 1 ;;
48
- esac
49
- ;;
50
- codex)
51
- catalog=$(codex debug models 2>/dev/null) || {
52
- echo "EFFORT_CHANGE_MODEL_CATALOG_UNAVAILABLE: codex debug models" >&2; exit 1;
53
- }
54
- if ! printf '%s' "$catalog" | python3 -c '
55
- import json,sys
56
- model,effort=sys.argv[1:3]
57
- entry=next((m for m in json.load(sys.stdin).get("models",[]) if m.get("slug")==model),None)
58
- levels=[] if entry is None else [x.get("effort") for x in entry.get("supported_reasoning_levels",[])]
59
- raise SystemExit(0 if effort in levels else 1)
60
- ' "$model" "$effort"; then
61
- echo "EFFORT_CHANGE_UNSUPPORTED: codex/${model} は effort=${effort} をcatalogで提供していない" >&2
62
- exit 1
63
- fi
64
- ;;
65
- esac
66
-
67
- messages=$(curl -sf "$url/api/$room/messages") || {
68
- echo "EFFORT_CHANGE_ROOM_UNREACHABLE: messagesを読めない" >&2; exit 1;
69
- }
70
- request_seq=$(printf '%s' "$messages" | python3 -c '
71
- import json,sys
72
- name,parent,effort=sys.argv[1:4]
73
- rows=json.load(sys.stdin).get("messages",[])
74
- def addressed(row,target):
75
- return row.get("to")==target or target in row.get("to_names",[])
76
- def exact_dm(row,target):
77
- return row.get("to")==target and not row.get("to_names")
78
- requests=[r for r in rows if r.get("from")==name and exact_dm(r,parent)
79
- and r.get("body")==f"[effort変更依頼] {effort}"]
80
- if not requests: raise SystemExit(1)
81
- req=max(requests,key=lambda r:r.get("seq",0))
82
- marker="request #{}".format(req.get("seq",0))
83
- completed=[r for r in rows if r.get("from")==parent and addressed(r,name)
84
- and str(r.get("body","")).startswith("[effort変更]")
85
- and marker in str(r.get("body",""))]
86
- if completed and max(r.get("seq",0) for r in completed)>=req.get("seq",0): raise SystemExit(1)
87
- print(req["seq"])
88
- ' "$name" "$parent" "$effort") || {
89
- echo "EFFORT_CHANGE_REQUEST_REQUIRED: ${name} → ${parent} の『[effort変更依頼] ${effort}』新着DMが要る" >&2
90
- exit 1
91
- }
92
-
93
- sock="${PEERTABLE_TMUX_SOCKET:-${TMPDIR:-/tmp/}claude-tmux-sockets/claude.sock}"
94
- sess="peer-$name"
95
- tmux -S "$sock" has-session -t "$sess" 2>/dev/null || {
96
- echo "EFFORT_CHANGE_SEAT_MISSING: ${sess}" >&2; exit 1;
97
- }
98
- screen=$(tmux -S "$sock" capture-pane -t "$sess" -p -S -25 2>/dev/null) || {
99
- echo "EFFORT_CHANGE_SEAT_UNREADABLE: $sess" >&2; exit 1;
100
- }
101
- case "$screen" in
102
- *"esc to interrupt"*) echo "EFFORT_CHANGE_SEAT_BUSY: ${sess} は処理中。本人がidleになってから再実行する" >&2; exit 1 ;;
103
- esac
104
-
105
- launch="$(dirname "$0")/launch-seat.sh"
106
- brief="effortが${effort}へ変更され、席を再起動しました。.team/roles/member.mdと工程正本・roomログから再着任し、進行中taskを続けてください。"
107
- if ! "$launch" "$proj" "$name" "$model" "$vendor" "$effort" "$brief"; then
108
- echo "EFFORT_CHANGE_RESTART_FAILED: effort=${effort}。旧effort=${old_effort:-default}へrollbackする" >&2
109
- rollback_brief="effort変更に失敗して旧設定へrollbackしました。.team/roles/member.mdと工程正本・roomログから再着任してください。"
110
- if "$launch" "$proj" "$name" "$model" "$vendor" "$old_effort" "$rollback_brief"; then
111
- echo "EFFORT_CHANGE_ROLLED_BACK: ${name} は旧effort=${old_effort:-default}で再着席" >&2
112
- else
113
- echo "EFFORT_CHANGE_ROLLBACK_FAILED: ${name} の席を手動で復旧する必要がある" >&2
114
- fi
115
- exit 1
116
- fi
117
-
118
- members_after=$(curl -sf "$url/api/$room/members") || {
119
- echo "EFFORT_CHANGE_CHANGED_BUT_UNVERIFIED: 席は再起動済み、membersを読めない" >&2; exit 1;
120
- }
121
- if ! printf '%s' "$members_after" | python3 -c '
122
- import json,sys
123
- name,effort=sys.argv[1:3]
124
- m=next((m for m in json.load(sys.stdin).get("members",[]) if m.get("name")==name),{})
125
- raise SystemExit(0 if m.get("effort")==effort else 1)
126
- ' "$name" "$effort"; then
127
- echo "EFFORT_CHANGE_CHANGED_BUT_UNVERIFIED: 席は再起動済み、member metadataがeffort=${effort}でない" >&2
128
- exit 1
129
- fi
130
-
131
- old_label="${old_effort:-default}"
132
- history=$(python3 -c 'import json,sys;print(json.dumps({"from":sys.argv[1],"to":sys.argv[2],"body":f"[effort変更] {sys.argv[1]} が {sys.argv[2]} の effort を {sys.argv[3]} → {sys.argv[4]} に変更(席を再起動 / request #{sys.argv[5]})"},ensure_ascii=False))' "$parent" "$name" "$old_label" "$effort" "$request_seq")
133
- if ! curl -sf -o /dev/null -X POST "$url/api/$room/messages" \
134
- -H "X-Peertable-Token: $PEERTABLE_POST_TOKEN" -H 'content-type: application/json' -d "$history"; then
135
- echo "EFFORT_CHANGE_CHANGED_BUT_HISTORY_FAILED: ${name} はeffort=${effort}で再着席済み、room履歴の記録に失敗" >&2
136
- exit 1
137
- fi
138
-
139
- echo "EFFORT_CHANGE_OK: ${name} ${old_label} → ${effort}(request #${request_seq} / parent=${parent})"
15
+ exec "$(dirname "$0")/change-seat.sh" "$proj" "$name" --effort "$effort" --parent "$parent"