peertable 0.8.30 → 0.8.32

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
@@ -113,7 +113,7 @@ Windows工場hostはPowerShell 7(`pwsh.exe`)を前提とし、5.1しかな
113
113
 
114
114
  動いており、**自分自身の開発に使っている**。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が止まらないよう直した。
115
115
 
116
- 現在のnpm releaseは **peertable 0.8.30**。
116
+ 現在のnpm releaseは **peertable 0.8.31**。
117
117
 
118
118
  設計文書と決定履歴(**129 決定**)は [docs/plan.md](docs/plan.md)。
119
119
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peertable",
3
- "version": "0.8.30",
3
+ "version": "0.8.32",
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
@@ -14,7 +14,7 @@ import { boundedRecent, boundedUnread } from './message-bounds.mjs'
14
14
 
15
15
  // client.mjs 側のハードコード版数。package.json の version と一致していることを
16
16
  // diagnostics の version_consistency が見る(2 つの版数源の drift 検出。決定45)
17
- const MCP_VERSION = '0.8.30'
17
+ const MCP_VERSION = '0.8.32'
18
18
  const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
19
19
 
20
20
  const USAGE = `usage:
package/room/server.mjs CHANGED
@@ -477,6 +477,13 @@ http.createServer(async (req, res) => {
477
477
  authFailures.delete(`${room.name}/${kind}`)
478
478
  return json(res, 200, { ok: true })
479
479
  }
480
+ // archive teardown はroomと会話ログを残すため、停止済みbridge台帳だけを消す。
481
+ // 消さないと空のarchive roomへstatus_bridge_down警告が恒久表示される。
482
+ if (req.method === 'DELETE' && rest === 'bridges') {
483
+ db.prepare('DELETE FROM bridges WHERE room = ?').run(room.name)
484
+ for (const kind of BRIDGE_KINDS) authFailures.delete(`${room.name}/${kind}`)
485
+ return json(res, 200, { ok: true })
486
+ }
480
487
  // 配送 receipt(決定102)。書き手は wakeup-bridge だけ。同じ (seq, recipient) は最新で上書き
481
488
  // (seat_unavailable → 再試行成功 delivered の遷移を残すため)
482
489
  if (req.method === 'POST' && rest === 'deliveries') {
@@ -172,3 +172,14 @@ test('親post入口は明示envだけでなく共通token解決を使う', () =>
172
172
  assert.ok(source.includes("import { resolvePostToken } from './seat-usage.mjs'"))
173
173
  assert.ok(source.includes('resolvePostToken(process.env)'))
174
174
  })
175
+
176
+ test('teardownはalarm-bridgeを停止してから.teamを削除する', () => {
177
+ const source = readFileSync(new URL('./teardown.sh', import.meta.url), 'utf8')
178
+ const stop = source.indexOf('alarm-bridge.mjs" "$proj" --stop')
179
+ const remove = source.indexOf('rm -rf "$proj/.team"')
180
+ assert.ok(stop >= 0)
181
+ assert.ok(remove >= 0 && stop < remove)
182
+ assert.ok(source.includes('did "alarm-bridge 停止"'))
183
+ assert.ok(source.includes('miss "alarm-bridge 停止に失敗'))
184
+ assert.ok(source.includes('-X DELETE "$url/api/$room/bridges"'))
185
+ })
@@ -128,6 +128,18 @@ else
128
128
  skip "seat-status-bridge(起動記録なし)"
129
129
  fi
130
130
 
131
+ # 工程変化の目覚ましbridgeも同じWindows processとしてlogを保持する。記録を残したまま
132
+ # `.team/`を消すとalarm-bridge.logがEBUSYになり、MCP/exclude復元の途中でteardownが止まる。
133
+ if [ -f "$proj/.team/alarm-bridge.json" ]; then
134
+ if node "$(dirname "$0")/alarm-bridge.mjs" "$proj" --stop; then
135
+ did "alarm-bridge 停止"
136
+ else
137
+ miss "alarm-bridge 停止に失敗(常駐が残る)— 上の ALARM_BRIDGE_STOP_FAILED を見て手で止める"
138
+ fi
139
+ else
140
+ skip "alarm-bridge(起動記録なし)"
141
+ fi
142
+
131
143
  # run-bridge は退役済み(2026-08-22)。旧卓の常駐が残っていれば記録の pid を照合して止める。
132
144
  if [ -f "$proj/.team/run-bridge.json" ]; then
133
145
  rb_pid=$(python3 -c "import json;print(json.load(open('$proj/.team/run-bridge.json')).get('pid',''))" 2>/dev/null || true)
@@ -292,6 +304,14 @@ urllib.request.urlopen(req, timeout=10).read()
292
304
  done <<<"$member_lines"
293
305
  did "メンバー登録の解除(${n}名)— **部屋と過去ログは残す**(${url}/${room})"
294
306
  fi
307
+ if [ -n "${PEERTABLE_POST_TOKEN:-}" ]; then
308
+ bridge_code=$(curl -s -o /dev/null -w '%{http_code}' -X DELETE "$url/api/$room/bridges" -H "X-Peertable-Token: $PEERTABLE_POST_TOKEN" || true)
309
+ if [ "$bridge_code" = 200 ]; then
310
+ did "停止済みbridge台帳の解除(archive roomを未登録表示へ戻す)"
311
+ else
312
+ miss "停止済みbridge台帳の解除に失敗(HTTP ${bridge_code:-000})"
313
+ fi
314
+ fi
295
315
  # room 削除は --purge だけ。トークンを要する唯一の段で、ここだけが外部サービスへの依存境界
296
316
  elif [ "$log_saved" = no ]; then
297
317
  miss "room 削除 $room — ログを保全できていないので消さない(保全より先に消すと会話は二度と戻らない)"
@@ -415,11 +415,23 @@ async function wake(seat, msgs) {
415
415
  // Codexの入力欄は本文とEnterを同じtmux commandで送ると、初回turn完了後に
416
416
  // 本文が入力欄へ残ることがある。再試行時の半入力も含め、正規のsubmitを分離する。
417
417
  // 最後のEnterまで成功しない限りwakeは成功扱いにせず、flushSeatのreceiptも確定しない。
418
- await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'C-u'], { socket: observation.socket }))
419
- await sleep(100)
420
- await run('tmux', tmuxArgv(['send-keys', '-l', '-t', observation.target, text], { socket: observation.socket }))
421
- await sleep(750)
422
- await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'Enter'], { socket: observation.socket }))
418
+ // **前周期の本文が入力欄に残っている時は再打鍵しない**——C-u は複数行composerの1行しか
419
+ // 消せず、周期ごとの全文再打鍵が本文を多重に蓄積させた(実被弾 2026-08-29: 約1時間の
420
+ // STUCKループで入力欄が多重本文+制御文字で汚染され、配達不能が固定化した)。
421
+ const preMarker = text.replace(/\s+/gu, '').slice(-24)
422
+ const prePane = await run('tmux', tmuxArgv(['capture-pane', '-t', observation.target, '-p'], { socket: observation.socket }))
423
+ const preLoaded = preMarker.length >= 8
424
+ && String(prePane.stdout ?? '').split('\n').slice(-14).join('').replace(/\s+/gu, '').includes(preMarker)
425
+ if (preLoaded) {
426
+ log(`入力欄に前周期の本文が残存: ${seat}。再打鍵せず送信だけ再試行する`)
427
+ await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'Enter'], { socket: observation.socket }))
428
+ } else {
429
+ await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'C-u'], { socket: observation.socket }))
430
+ await sleep(100)
431
+ await run('tmux', tmuxArgv(['send-keys', '-l', '-t', observation.target, text], { socket: observation.socket }))
432
+ await sleep(750)
433
+ await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'Enter'], { socket: observation.socket }))
434
+ }
423
435
  // 送信検証。Codex v0.148 は長文入力後に「Create a plan?」popup が出て Enter を呑むことがあり、
424
436
  // 本文が入力欄に残ったまま席が沈黙する(2026-08-22 実測: 2席が無音停止し、人が画面を見るまで
425
437
  // 誰も気づけなかった)。打鍵成功=配達成功とみなさず、画面で送信成立を確認する。
@@ -458,9 +470,16 @@ async function wake(seat, msgs) {
458
470
  await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'Enter'], { socket: observation.socket }))
459
471
  continue
460
472
  }
461
- log(`配達が送信されていない(popup/入力詰まり): ${seat}。Escape→Enter で再送信する`)
462
- await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'Escape'], { socket: observation.socket }))
463
- await sleep(300)
473
+ // Escape は popup が無い時に composer へリテラル ESC として混入し、本文を恒久汚染する
474
+ // (実被弾 2026-08-29: `^[` が本文中へ入り submit 不能が固定化)。popup 解除は
475
+ // 「esc dismiss」表示がある時だけ撃ち、それ以外は Enter だけ打ち直す。
476
+ if (tailJoined.includes('esc dismiss')) {
477
+ log(`配達が送信されていない(popup表示中): ${seat}。Escape→Enter で再送信する`)
478
+ await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'Escape'], { socket: observation.socket }))
479
+ await sleep(300)
480
+ } else {
481
+ log(`配達が送信されていない(入力詰まり): ${seat}。Enter を打ち直す`)
482
+ }
464
483
  await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'Enter'], { socket: observation.socket }))
465
484
  }
466
485
  log(`TUIへ入れた: ${seat} ← ${msgs.length} 件(最新 seq ${last.seq})`)