peertable 0.4.3 → 0.4.5

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 CHANGED
@@ -107,7 +107,7 @@ Codex では、スキルが所有する room MCP block をプロジェクトの
107
107
 
108
108
  動いており、**自分自身の開発に使っている**。2026-08-08 に end-to-end 検証済み——オーケストレーターなしの完全な一周(2 メンバーが相談し、claim し、インターフェースを交渉し、見つけた罠を共有して小さなプロジェクトを出荷)を**外部介入ゼロ**で完走。2026-08-13の実席ライフサイクルでは、作業席が親を通じてsession contextを保ったままmodel / effortを変更し、再起動後はroomと工程正本から再着任した。2026-08-14にはGrok 4.6席の着席、room参加、同一sessionの4.6↔4.5変更、DM起床を実機で確認した。2026-08-17にGrok席はidle待ち、broadcastは本文を残し、tmuxの無い親でbridge cursorが止まらないよう直した。
109
109
 
110
- 現在のnpm releaseは **peertable 0.4.3**。
110
+ 現在のnpm releaseは **peertable 0.4.5**。
111
111
 
112
112
  設計文書と決定履歴(**87 決定**)は [docs/plan.md](docs/plan.md)。
113
113
 
package/README.md CHANGED
@@ -143,7 +143,7 @@ It interviews you, names the members, scaffolds `.team/` (charter + roles, isola
143
143
 
144
144
  Working, and used to build itself. First verified end-to-end on 2026-08-08 with a full no-orchestrator loop: two members consulted, claimed, negotiated an interface, shared a discovered pitfall, and shipped a small project with **zero external intervention**. A 2026-08-13 real-seat lifecycle verified in-place model/effort changes and restart recovery. On 2026-08-14, a Grok 4.6 seat joined the room, changed 4.6↔4.5 in the same session, and woke on a direct message in a live acceptance run. On 2026-08-17 the wake-up path was corrected so Grok seats wait for idle, broadcasts keep their body, and a parent without a tmux seat cannot stall the bridge cursor.
145
145
 
146
- The current npm release is **peertable 0.4.3**.
146
+ The current npm release is **peertable 0.4.5**.
147
147
 
148
148
  The design document and decision log (**87 decisions**, in Japanese) live in [docs/plan.md](docs/plan.md).
149
149
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peertable",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "A round table of peer agents. No orchestrator at the head. Turn Claude Code, Codex, and Grok sessions into a team of equal, long-lived peers.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/room/client.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // Peertable セッション側クライアント。room の読み書きツールを提供する。
2
+ // Peertable セッション側クライアント。channel(新着の一行通知)と room ツールを 1 プロセスに統合する。
3
3
  // .mcp.json 登録: {"command":"node","args":["client.mjs"],"env":{"PEERTABLE_URL":"http://192.168.1.2:8790","PEERTABLE_ROOM":"myproject","PEERTABLE_MEMBER":"hinata"}}
4
4
  import { Server } from '@modelcontextprotocol/sdk/server/index.js'
5
5
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url'
11
11
 
12
12
  // client.mjs 側のハードコード版数。package.json の version と一致していることを
13
13
  // diagnostics の version_consistency が見る(2 つの版数源の drift 検出。決定45)
14
- const MCP_VERSION = '0.4.3'
14
+ const MCP_VERSION = '0.4.5'
15
15
  const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
16
16
 
17
17
  const USAGE = `usage:
@@ -56,12 +56,10 @@ let cursor = 0 // read_unread 用。参加時点から数える
56
56
  const mcp = new Server(
57
57
  { name: 'room', version: MCP_VERSION },
58
58
  {
59
- // Claude起動時の既存channel選択契約は維持するが、通知は送らない。実配送はwakeup-bridge一系統。
60
59
  capabilities: { experimental: { 'claude/channel': {} }, tools: {} },
61
60
  instructions:
62
61
  `あなたは Peertable room「${ROOM}」のメンバー「${ME}」である。` +
63
- '個人DM本文は wakeup-bridge から直接届く。room全体更新では read_log で状況を取り直す。' +
64
- 'read_unread は任意の履歴確認に使う。' +
62
+ '<channel source="room"> の通知は「新着あり」の合図であり、本文は read_unread ツールで読む。' +
65
63
  '発言は post ツールを使う。to: "all" はroom全体、メンバー名はDM、配列は複数人宛である。',
66
64
  },
67
65
  )
@@ -174,6 +172,45 @@ const IDENTITY = Object.fromEntries(Object.entries({
174
172
  }).filter(([, v]) => v))
175
173
  await fetch(api('members'), { method: 'POST', headers, body: JSON.stringify({ name: ME, ...IDENTITY }) })
176
174
 
175
+ // SSE 購読 → 自分に関係する新着だけ一行通知へ変換。切断は外部障害なので再接続する。
176
+ const actionHint = 'read_unread で本文を読み、本文に具体的な依頼・行動要求があればそれをこのturnで実行し、完了または具体的なblockerをroomへ報告してから入力待ちに戻ること。情報通知だけなら追加の外部行動を起こさず読了で戻ること。'
177
+ async function subscribe() {
178
+ for (;;) {
179
+ try {
180
+ const res = await fetch(api(`events`))
181
+ let buf = ''
182
+ for await (const chunk of res.body) {
183
+ buf += Buffer.from(chunk).toString('utf8')
184
+ let i
185
+ while ((i = buf.indexOf('\n\n')) >= 0) {
186
+ const frame = buf.slice(0, i)
187
+ buf = buf.slice(i + 2)
188
+ const lines = frame.split('\n')
189
+ // 名前付きイベント(ping/member等)はチャット発言ではない。明示的に読み飛ばす——
190
+ // 現状は relevant() が宛先無しの payload を弾いて偶然安全なだけで、将来のイベント追加が
191
+ // 席を起こす経路を open のままにしない
192
+ if (lines.some(l => l.startsWith('event: '))) continue
193
+ const data = lines.filter(l => l.startsWith('data: ')).map(l => l.slice(6)).join('')
194
+ if (!data) continue
195
+ const m = JSON.parse(data)
196
+ if (!relevant(m)) continue
197
+ await mcp.notification({
198
+ method: 'notifications/claude/channel',
199
+ params: {
200
+ content: `room に新着あり(${m.from} → ${Array.isArray(m.to_names) ? m.to_names.join(', ') : m.to})。${actionHint}`,
201
+ meta: { from: m.from, to: Array.isArray(m.to_names) ? m.to_names.join(',') : m.to, seq: String(m.seq) },
202
+ },
203
+ })
204
+ }
205
+ }
206
+ } catch {
207
+ // サーバー断。少し待って再接続(会話は止まるが工程はローカル Lattice で続く)
208
+ }
209
+ await new Promise(r => setTimeout(r, 3000))
210
+ }
211
+ }
212
+ subscribe()
213
+
177
214
  // --- diagnostics(決定45 の契約。read-only。呼ばれた時だけ走る)-------------------------
178
215
  // 関数宣言なので巻き上げられ、ファイル冒頭のサブコマンド分岐から呼べる。
179
216
  // checks の値は契約どおり状態そのもの(pass / fail / not_applicable / unverified)。
package/skill/SKILL.md CHANGED
@@ -65,7 +65,7 @@ script は内部で Aiterm の公開 `claude_agent` / `codex_agent` / `grok_agen
65
65
  - 起動前に `pty_list` で既存の `peer-*` 席を確認する(前の卓の残骸を99席実測したことがある)。同名の席は launch-seat.sh が落としてから立て直す
66
66
  - 着任指示を第6引数に渡すと着席後に送る。文面: 「あなたは「<日本語名>」。.team/roles/member.md を読んで着任し、作業ループを開始せよ。全タスク完了の宣言まで自律的に続けること。」
67
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 の新着で起きない
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 / Grok だけ**(下記)——Claude 席は room client が `notifications/claude/channel` を送る(8/12 に送信ループを削った回帰を 0.4.5 で戻した)
69
69
  - **Grok 席**(`vendor=grok`): project rootの`.mcp.json`をGrok Buildが読み、同じroom clientと席固有envをAitermが渡す。model / effortはAitermの`grok_agent`へそのまま渡し、`grok models`のlive catalogに無いmodelは着席前に失敗する。初めて開くtreeの既知workspace trustは着席処理が通す。channelsは無いので起床ブリッジを使う。Grok TUIの既定はターン中の素送信を今の仕事へ混ぜず入力キューへ積むので、ブリッジはGrok席がidleになるまで送らない
70
70
  6. **起床ブリッジ(Codex / Grok 席がある時)**: `scripts/ensure-bridge.sh <project> wakeup <席名>…`。room の SSE を購読し、明示的にその席宛の新着(自分の発言は除く)だけを tmux へ素送信して起こす。ensure は専用 tmux session で起動し、bridge の `ready_at` を待つ。確認できなければログ末尾を出して非ゼロで終わる。Codexは即送信、Grokはidle待ち。親(`parent_watch` / `observe: null`)は対象にしない。停止は `node scripts/wakeup-bridge.mjs <project> --stop`(teardown.sh が自動で行う)
71
71
  - **黙って止まらないための三段**(決定58 の受信側の作法): ①75秒なにも届かなければ自分から切って繋ぎ直す ②繋ぎ直したら `?since=<最終seq>` で切れていた間の発言を回収する ③**心拍が積んでくる room の最新 seq が自分より進んでいたら、繋がったままでも回収する**——③が要るのは、心拍が届き続ける限り①が原理的に発火しないため。**server 側の心拍(`event: ping`・25秒周期)が前提**なので、古い room サーバーへ繋ぐと①だけが効く形になる
@@ -16,7 +16,7 @@ if (!session_id || (!model && !reasoning_effort)) {
16
16
  // Aiterm のmanaged session metadataは実行時state root(macOSではTMPDIR)にある。
17
17
  // stdio transportの既定環境は親のTMPDIRを落とすため、同じ公開serverへ明示継承する。
18
18
  const transport = new StdioClientTransport({ command: 'aiterm-mcp', env: process.env })
19
- const client = new Client({ name: 'peertable-seat-configure', version: '0.4.3' })
19
+ const client = new Client({ name: 'peertable-seat-configure', version: '0.4.5' })
20
20
  await client.connect(transport)
21
21
  const result = await client.callTool({
22
22
  name: 'agent_configure',
@@ -8,7 +8,7 @@ if (!session_name || !['claude', 'codex', 'grok'].includes(vendor) || !model ||
8
8
  throw new Error('SEAT_AITERM_LAUNCH_ARGS_INVALID')
9
9
  }
10
10
 
11
- const client = new Client({ name: 'peertable-seat-launch', version: '0.4.3' })
11
+ const client = new Client({ name: 'peertable-seat-launch', version: '0.4.5' })
12
12
  await client.connect(new StdioClientTransport({ command: 'aiterm-mcp', env: process.env }))
13
13
  const env_vars = [
14
14
  'PEERTABLE_URL', 'PEERTABLE_ROOM', 'PEERTABLE_MEMBER', 'PEERTABLE_CREDENTIAL_FILE',
@@ -566,6 +566,8 @@ else
566
566
  echo "seat-status-bridge の起動確認に失敗した(席は着席済み)" >&2
567
567
  fi
568
568
 
569
+ # Claude 席の起床は room client の notifications/claude/channel。ブリッジは channels を持たない
570
+ # Codex / Grok 席だけ。Claude に立てると channel と二重配達になる。
569
571
  if [ "$vendor" = codex ] || [ "$vendor" = grok ]; then
570
572
  if PEERTABLE_CREDENTIAL_FILE="$credential_file" "$(dirname "$0")/ensure-bridge.sh" "$proj" wakeup; then
571
573
  echo "wakeup-bridge: 起動確認済み"
@@ -198,6 +198,9 @@ export const BLOCKED_MARKERS = [
198
198
  export function classifyPaneTail(tail) {
199
199
  if (typeof tail !== 'string') return 'idle'
200
200
  if (tail.includes('esc to interrupt')) return 'busy'
201
+ // Claude Code 2.1 / Fable TUI は思考中に esc to interrupt を出さない(2026-08-19 実測)。
202
+ // 動名詞(Wibbling 等)は毎回変わるので使わない。思考中の固定句だけを busy にする。
203
+ if (tail.includes('thinking with') || tail.includes('almost done thinking')) return 'busy'
201
204
  if (BLOCKED_MARKERS.some(marker => tail.includes(marker))) return 'blocked'
202
205
  return 'idle'
203
206
  }
@@ -24,6 +24,10 @@ export function isWakeupBridgeTarget(member) {
24
24
  if (!member || typeof member.name !== 'string' || member.name.length === 0) return false
25
25
  if (member.delivery?.kind === 'parent_watch') return false
26
26
  if (member.observe === null) return false
27
+ const observe = member.observe
28
+ if (!observe || typeof observe !== 'object' || typeof observe.tmux_target !== 'string' || !observe.tmux_target) {
29
+ return false
30
+ }
27
31
  return true
28
32
  }
29
33
 
@@ -90,19 +90,18 @@ lattice run intake --run .lattice/runs/<run-id> --task <id>
90
90
  - **依存は install しなくても解決する。** worktree は repo 配下(`<repo>/.lattice/runs/…/tree`)に切られるので、Node の bare specifier 解決が親ディレクトリを遡って canonical の `node_modules` に当たる(repo の外に置かれた木では当たらない)。これは現在の worktree 配置がもたらしている便益であって、どこでも成り立つ性質ではない
91
91
  - **当たるのは canonical に入っている版である。** worktree の `package.json` が要求する版とは限らないので、**lockfile や依存を動かす task では、検証結果を「解決された版のずれ」ごと疑う**
92
92
  - それでも回らない検証は、無理に回さず room で言う。**動かないからといって canonical tree で回さない**——それは測りたい木ではない
93
- 9. **自己試験と自己監査を完了し、最終試験結果を監査担当へ渡す。** 監査担当が工程をクローズした後、intakeした席がacceptとlanding確認を行う
93
+ 9. **自己試験と自己監査を完了し、最終試験結果を監査担当へ渡す。** `todo done` / `done.sh <task>` は監査担当が打つ(憲章11・12)。監査担当が工程をクローズした後、intake した席だけが accept できる(装置が actor で束縛しているので、監査担当や親が代わりに打てない)。
94
94
  ```
95
- # canonical repo の cwd から打つ。証跡は worktree にしか無いので絶対 path で渡す
95
+ # 監査担当のクローズ後。canonical repo の cwd から打つ
96
96
  cd <canonical repo>
97
- PEERTABLE_PLAN={{PLAN_KEY}} .team/scripts/done.sh <id> --evidence-from <worktree>/evidence/{{PLAN_KEY}}/<id>.md
98
97
  lattice run intake accept --run <ref> --task <id>
99
98
  .team/scripts/done.sh --landing-run <ref>
100
99
  ```
101
- **`--evidence-from` を省いてはいけない。** 省くと canonical 側の証跡(無いか、別物)を hash する。
100
+ 証跡は worktree に commit したものを監査担当が `--evidence-from` hash する。
102
101
  **canonical へ証跡を書き写して通すのも禁止**——「worktree の中だけを触る」契約を破りながら
103
102
  green にする偽装になる。linked worktree は canonical と object DB を共有するので、
104
103
  **worktree に commit した証跡は canonical から読める**(複製は要らない)。
105
- 装置が worktree の base→HEAD を独立に観測して受理する。**intake した席だけが attach / accept できる**(装置が actor で束縛している)ので、他の席に代わりに打ってもらうことはできない。
104
+ 装置が worktree の base→HEAD を独立に観測して受理する。
106
105
  最後の landing-only 呼び出しは、accept 済み receipt が canonical default branch へ未着地なら
107
106
  `未着地 N本`を出す。警告だけで処理は止めないが、`未push` と別の完了軸なので読み飛ばさない。
108
107