peertable 0.8.37 → 0.8.39

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.37",
3
+ "version": "0.8.39",
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.37'
17
+ const MCP_VERSION = '0.8.39'
18
18
  const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
19
19
 
20
20
  const USAGE = `usage:
@@ -174,15 +174,23 @@ const postedReceipts = new Map() // `${seq}:${recipient}` -> `${result}:${reason
174
174
  // failed / seat_unavailable の初回だけ、親宛DMを1通送る——親宛DMは parent-watch が起こすので
175
175
  // 既存の通知経路にそのまま乗り、この bridge 自身は親へ配達しないため再帰しない。
176
176
  const notifiedFailures = new Set() // `${seq}:${recipient}`(delivered への回復で解除し、再failで再通知)
177
+ const failureStreaks = new Map() // `${seq}:${recipient}` -> 連続失敗数
177
178
  async function notifyParentOfFailure(seq, recipient, result, reason) {
178
179
  if (!parentName) return
179
180
  const key = `${seq}:${recipient}`
180
181
  if (result === 'delivered') {
181
182
  notifiedFailures.delete(key)
183
+ failureStreaks.delete(key)
182
184
  return
183
185
  }
184
186
  // not_a_delivery_target は「そもそもTUI配達対象でない」平常応答であって失敗ではない
185
187
  if (reason === 'not_a_delivery_target') return
188
+ // 1回目の失敗は通知しない。数秒後の再試行で自己回復する一過性(paste競合等)が
189
+ // 毎回親を起こしてノイズになった(実被弾 2026-08-30)。実害のある失敗は次周期で
190
+ // 2回目に達するので、検知は数秒遅れるだけで漏れない。
191
+ const streak = (failureStreaks.get(key) ?? 0) + 1
192
+ failureStreaks.set(key, streak)
193
+ if (streak < 2) return
186
194
  if (notifiedFailures.has(key)) return
187
195
  try {
188
196
  const res = await fetch(`${url}/api/${encodeURIComponent(room)}/messages`, {
@@ -486,7 +494,19 @@ async function wake(seat, msgs) {
486
494
  } else {
487
495
  await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'C-u'], { socket: observation.socket }))
488
496
  await sleep(100)
489
- await run('tmux', tmuxArgv(['send-keys', '-l', '-t', observation.target, text], { socket: observation.socket }))
497
+ // 素打ち(send-keys -l)は長文で連続keystrokeとして届き、TUIのpaste burst検知を
498
+ // 半端な状態に落として composer が submit 不能に固まる形を繰り返した(実被弾
499
+ // 2026-08-29: Codex席が同日3回、掃除も効かない入力破損)。正規の bracketed paste
500
+ // (load-buffer→paste-buffer -p)で「1回の貼り付け」として届ける。
501
+ const pasteBuf = `wakeup-${process.pid}-${Date.now()}`
502
+ const pasteFile = join(proj, '.team', `wakeup-paste-${process.pid}.txt`)
503
+ writeFileSync(pasteFile, text)
504
+ try {
505
+ await run('tmux', tmuxArgv(['load-buffer', '-b', pasteBuf, pasteFile], { socket: observation.socket }))
506
+ await run('tmux', tmuxArgv(['paste-buffer', '-p', '-d', '-b', pasteBuf, '-t', observation.target], { socket: observation.socket }))
507
+ } finally {
508
+ try { unlinkSync(pasteFile) } catch { /* 一時fileの掃除失敗は配達判定に影響しない */ }
509
+ }
490
510
  await sleep(750)
491
511
  await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'Enter'], { socket: observation.socket }))
492
512
  }