peertable 0.8.32 → 0.8.34
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 +1 -1
- package/room/client.mjs +1 -1
- package/skill/scripts/wakeup-bridge.mjs +50 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peertable",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.34",
|
|
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.
|
|
17
|
+
const MCP_VERSION = '0.8.34'
|
|
18
18
|
const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
19
19
|
|
|
20
20
|
const USAGE = `usage:
|
|
@@ -166,7 +166,37 @@ async function beatBridge() {
|
|
|
166
166
|
}
|
|
167
167
|
let receiptSupported = null
|
|
168
168
|
const postedReceipts = new Map() // `${seq}:${recipient}` -> `${result}:${reason}`(同内容の再送を抑える)
|
|
169
|
+
// 配達失敗は台帳(pull)に書くだけでは親に届かない(実被弾 2026-08-29: 裁定DMのSTUCKが
|
|
170
|
+
// 1時間誰にも知られず放置された。オーナー裁定「配達失敗だけ届ければいい」)。
|
|
171
|
+
// failed / seat_unavailable の初回だけ、親宛DMを1通送る——親宛DMは parent-watch が起こすので
|
|
172
|
+
// 既存の通知経路にそのまま乗り、この bridge 自身は親へ配達しないため再帰しない。
|
|
173
|
+
const notifiedFailures = new Set() // `${seq}:${recipient}`(delivered への回復で解除し、再failで再通知)
|
|
174
|
+
async function notifyParentOfFailure(seq, recipient, result, reason) {
|
|
175
|
+
if (!parentName) return
|
|
176
|
+
const key = `${seq}:${recipient}`
|
|
177
|
+
if (result === 'delivered') {
|
|
178
|
+
notifiedFailures.delete(key)
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
// not_a_delivery_target は「そもそもTUI配達対象でない」平常応答であって失敗ではない
|
|
182
|
+
if (reason === 'not_a_delivery_target') return
|
|
183
|
+
if (notifiedFailures.has(key)) return
|
|
184
|
+
try {
|
|
185
|
+
const res = await fetch(`${url}/api/${encodeURIComponent(room)}/messages`, {
|
|
186
|
+
method: 'POST', headers: writeHeaders,
|
|
187
|
+
body: JSON.stringify({
|
|
188
|
+
from: 'wakeup', to: parentName,
|
|
189
|
+
body: `[配達失敗] seq=${seq} 宛先=${recipient} 状態=${result}${reason ? ` 理由=${reason}` : ''}。台帳とwakeup-bridge.logを確認し、席の復旧または再送を判断すること`,
|
|
190
|
+
}),
|
|
191
|
+
})
|
|
192
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
193
|
+
notifiedFailures.add(key)
|
|
194
|
+
} catch (error) {
|
|
195
|
+
log(`DELIVERY_FAILURE_NOTIFY_FAILED ${JSON.stringify({ seq, recipient, detail: error.message.split('\n')[0] })}`)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
169
198
|
async function postReceipt(seq, recipient, result, reason = null) {
|
|
199
|
+
await notifyParentOfFailure(seq, recipient, result, reason)
|
|
170
200
|
if (receiptSupported === false) return
|
|
171
201
|
const key = `${seq}:${recipient}`
|
|
172
202
|
const value = `${result}:${reason ?? ''}`
|
|
@@ -312,6 +342,22 @@ function advanceLastSeq() {
|
|
|
312
342
|
if (advanced) saveDeliveryState()
|
|
313
343
|
}
|
|
314
344
|
|
|
345
|
+
// 「入力欄に本文が残っているか」の照合範囲。画面末尾全体で照合すると、配達済みの本文が
|
|
346
|
+
// TUI の会話履歴として画面下部に表示され続ける形(Grok が常時、Codex も直後)を「残存」と
|
|
347
|
+
// 誤検知し、成功した配達を failed 扱いで再試行し続ける(実被弾 2026-08-29: idle の Grok 席へ
|
|
348
|
+
// 空 Enter を打ち続け、receipt が failed で固定された)。入力欄は画面の最下部に居るので、
|
|
349
|
+
// **最後の composer マーカー行(Codex ›・Grok ❯)から末尾まで**だけを照合する。
|
|
350
|
+
// マーカーが見えない画面では従来どおり末尾14行で照合する(狭めすぎて偽成功を作らない)。
|
|
351
|
+
function composerSquashed(screen) {
|
|
352
|
+
const lines = screen.split('\n')
|
|
353
|
+
let start = -1
|
|
354
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
355
|
+
if (/[›❯]/u.test(lines[i])) { start = i; break }
|
|
356
|
+
}
|
|
357
|
+
const region = start >= 0 ? lines.slice(start) : lines.slice(-14)
|
|
358
|
+
return region.join('').replace(/\s+/gu, '')
|
|
359
|
+
}
|
|
360
|
+
|
|
315
361
|
const deferredBusy = new Set()
|
|
316
362
|
async function passKnownCodexDialog(member) {
|
|
317
363
|
if (memberHarness(member) !== 'codex') return false
|
|
@@ -421,7 +467,7 @@ async function wake(seat, msgs) {
|
|
|
421
467
|
const preMarker = text.replace(/\s+/gu, '').slice(-24)
|
|
422
468
|
const prePane = await run('tmux', tmuxArgv(['capture-pane', '-t', observation.target, '-p'], { socket: observation.socket }))
|
|
423
469
|
const preLoaded = preMarker.length >= 8
|
|
424
|
-
&& String(prePane.stdout ?? '')
|
|
470
|
+
&& composerSquashed(String(prePane.stdout ?? '')).includes(preMarker)
|
|
425
471
|
if (preLoaded) {
|
|
426
472
|
log(`入力欄に前周期の本文が残存: ${seat}。再打鍵せず送信だけ再試行する`)
|
|
427
473
|
await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'Enter'], { socket: observation.socket }))
|
|
@@ -451,8 +497,9 @@ async function wake(seat, msgs) {
|
|
|
451
497
|
const after = await run('tmux', tmuxArgv(['capture-pane', '-t', observation.target, '-p'], { socket: observation.socket }))
|
|
452
498
|
const tailLines = String(after.stdout ?? '').split('\n').slice(-14)
|
|
453
499
|
const tailJoined = tailLines.join(' ')
|
|
454
|
-
// 折返しはcapture
|
|
455
|
-
|
|
500
|
+
// 折返しはcapture上で行分割されるだけで空白は挟まらないため、空白除去で復元して照合する。
|
|
501
|
+
// 照合は composer 領域だけ(composerSquashed の注記を参照)
|
|
502
|
+
const tailSquashed = composerSquashed(String(after.stdout ?? ''))
|
|
456
503
|
const queuedOrRunning = tailJoined.includes('esc to interrupt') || tailJoined.includes('to queue message')
|
|
457
504
|
|| (isClaude && CLAUDE_ACCEPTED.test(tailJoined))
|
|
458
505
|
const stuck = (!isClaude && tailJoined.includes('esc dismiss'))
|