peertable 0.8.28 → 0.8.29

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peertable",
3
- "version": "0.8.28",
3
+ "version": "0.8.29",
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.28'
17
+ const MCP_VERSION = '0.8.29'
18
18
  const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
19
19
 
20
20
  const USAGE = `usage:
@@ -347,50 +347,38 @@ async function wake(seat, msgs) {
347
347
  // shell へ room の本文を send-keys すると、本文がそのまま shell コマンドとして実行される
348
348
  // (実被弾 2026-08-22: codex 終了後の bash へ配達され `command not found` が走った。
349
349
  // 本文次第では席の権限で任意コマンドになる)。配達は止め、毎周期 typed log で叫ぶ。
350
- // **pane_current_command は「bash の下で agent CLI が走る」形を bash と報告することがある**
351
- // (実被弾 2026-08-22: 生きている codex 席が bash 判定になり配達が全停止、席が起きなかった)。
352
- // shell 名だった時は pane 配下の子孫を辿り、**当該 harness CLI プロセスが実在する時だけ**配達する。
353
- // 「子孫がゼロかどうか」では足りない——agent CLI が死んでも、CLI が起こした背景ターミナル等の
354
- // 子孫が pane shell の下に生き残り、防御をすり抜けて room 本文が bash で実行された
355
- // (実被弾 2026-08-29: codex 死亡後の pane へ broadcast が配達され `command not found` が走った)。
356
- const fg = await run('tmux', tmuxArgv(['display-message', '-p', '-t', observation.target, '#{pane_current_command}'], { socket: observation.socket }))
357
- const fgCommand = String(fg.stdout ?? '').trim()
358
- if (['bash', 'zsh', 'sh', 'dash', 'fish', 'tcsh', 'csh', 'ksh'].includes(fgCommand)) {
359
- const harness = memberHarness(member)
360
- const panePidOut = await run('tmux', tmuxArgv(['display-message', '-p', '-t', observation.target, '#{pane_pid}'], { socket: observation.socket }))
361
- const panePid = Number(String(panePidOut.stdout ?? '').trim())
362
- let harnessAlive = false
363
- if (Number.isSafeInteger(panePid) && panePid > 0 && typeof harness === 'string' && harness) {
364
- const psOut = await run('/bin/ps', ['-axo', 'pid=,ppid=,command='], { env: { ...process.env, LC_ALL: 'C' } })
365
- const children = new Map()
366
- const commands = new Map()
367
- for (const line of String(psOut.stdout ?? '').split('\n')) {
368
- const m = line.trim().match(/^(\d+)\s+(\d+)\s+(.*)$/u)
369
- if (!m) continue
370
- const pid = Number(m[1]); const ppid = Number(m[2])
371
- if (!children.has(ppid)) children.set(ppid, [])
372
- children.get(ppid).push(pid)
373
- commands.set(pid, m[3])
374
- }
375
- const queue = [...(children.get(panePid) ?? [])]
376
- while (queue.length) {
377
- const pid = queue.pop()
378
- const argv0 = String(commands.get(pid) ?? '').split(/\s+/u, 1)[0]
379
- const base = argv0.split('/').pop()
380
- if (base === harness || (harness === 'claude' && base === 'node' && String(commands.get(pid) ?? '').includes('claude'))) {
381
- harnessAlive = true
382
- break
383
- }
384
- queue.push(...(children.get(pid) ?? []))
350
+ // **打鍵が届く先は pane tty の前面プロセスグループだけ**なので、配達可否はそれで判定する。
351
+ // 「前面コマンド名がshellか」(実被弾 2026-08-22: bash 配下で生きる codex を誤遮断)でも、
352
+ // 「pane 子孫に agent CLI が実在するか」(実被弾 2026-08-29: SIGTSTP で停止した codex は
353
+ // 実在するが打鍵は bash が受け、room 本文がコマンド実行された)でも足りない。
354
+ // ps STAT `+` tty の前面プロセスグループを OS が直接教える印であり、
355
+ // 死亡・停止(T)・背面化のすべてを一つの条件で塞ぐ。
356
+ const ttyOut = await run('tmux', tmuxArgv(['display-message', '-p', '-t', observation.target, '#{pane_tty}'], { socket: observation.socket }))
357
+ const paneTty = String(ttyOut.stdout ?? '').trim().replace(/^\/dev\//u, '')
358
+ const SHELLS = ['bash', 'zsh', 'sh', 'dash', 'fish', 'tcsh', 'csh', 'ksh']
359
+ let foregroundIsAgent = false
360
+ let foregroundLabel = '(不明)'
361
+ if (paneTty) {
362
+ const psOut = await run('/bin/ps', ['-t', paneTty, '-o', 'stat=,command='], { env: { ...process.env, LC_ALL: 'C' } })
363
+ for (const line of String(psOut.stdout ?? '').split('\n')) {
364
+ const m = line.trim().match(/^(\S+)\s+(.*)$/u)
365
+ if (!m || !m[1].includes('+')) continue
366
+ const base = String(m[2]).split(/\s+/u, 1)[0].split('/').pop()
367
+ if (SHELLS.includes(base)) {
368
+ foregroundLabel = base
369
+ continue
385
370
  }
371
+ foregroundIsAgent = true
372
+ break
386
373
  }
387
- if (!harnessAlive) {
388
- log(`SEAT_TUI_GONE: ${seat} の pane(前面 ${fgCommand})の子孫に ${memberHarness(member) ?? '?'} CLI が見つからない=agent 終了済み。`
389
- + 'shell へのコマンド実行を防ぐため配達しない。席を立て直すか leave-seat で畳むこと')
390
- const error = new Error(`SEAT_TUI_GONE: ${seat}`)
391
- error.code = 'SEAT_TUI_GONE'
392
- throw error
393
- }
374
+ }
375
+ if (!foregroundIsAgent) {
376
+ log(`SEAT_TUI_GONE: ${seat} pane tty 前面が agent CLI でない(前面: ${foregroundLabel})=`
377
+ + '打鍵は shell に落ちる(agent 死亡・SIGTSTP 停止・背面化)。'
378
+ + 'shell へのコマンド実行を防ぐため配達しない。席を立て直すか leave-seat で畳むこと')
379
+ const error = new Error(`SEAT_TUI_GONE: ${seat}`)
380
+ error.code = 'SEAT_TUI_GONE'
381
+ throw error
394
382
  }
395
383
  if (await passKnownCodexDialog(member)) return 'deferred'
396
384
  const pane = await run('tmux', tmuxArgv(['capture-pane', '-t', observation.target, '-p'], { socket: observation.socket }))