peertable 0.5.0 → 0.6.0
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 +2 -2
- package/README.md +2 -2
- package/package.json +1 -1
- package/room/client.mjs +7 -5
- package/room/server.mjs +18 -7
- package/skill/02_models.snapshot.md +2 -2
- package/skill/SKILL.md +5 -5
- package/skill/scripts/aiterm-launch.mjs +7 -7
- package/skill/scripts/alarm-bridge.mjs +117 -0
- package/skill/scripts/alarm-set.sh +22 -0
- package/skill/scripts/change-seat.sh +25 -25
- package/skill/scripts/ensure-bridge.sh +1 -1
- package/skill/scripts/ensure-codex-room-mcp.mjs +2 -2
- package/skill/scripts/launch-seat.sh +39 -34
- package/skill/scripts/parent-join.sh +19 -18
- package/skill/scripts/resolve-seat-placement.mjs +28 -27
- package/skill/scripts/seat-status-bridge.mjs +34 -0
- package/skill/scripts/seat-usage.mjs +1 -1
- package/skill/scripts/setup.sh +5 -0
- package/skill/scripts/wakeup-bridge.mjs +47 -8
- package/skill/scripts/wakeup-delivery.mjs +4 -4
- package/skill/templates/member.md +3 -1
- package/skill/templates/parent.md +4 -4
|
@@ -136,7 +136,7 @@ process.on('SIGTERM', cleanup)
|
|
|
136
136
|
process.on('SIGINT', cleanup)
|
|
137
137
|
|
|
138
138
|
// 席ごとに未配達を溜めて、2秒ごとにまとめて1回起こす(連投で席を何度も起こさない)。
|
|
139
|
-
// pending は room message の宛先名から作る。起動引数や
|
|
139
|
+
// pending は room message の宛先名から作る。起動引数や harness は候補集合を決めない。
|
|
140
140
|
const pending = new Map() // name -> Map<seq, message>
|
|
141
141
|
const deliveryStates = new Map() // seq -> { message, targets, delivered }
|
|
142
142
|
let seats = []
|
|
@@ -276,16 +276,41 @@ async function wake(seat, msgs) {
|
|
|
276
276
|
// shell へ room の本文を send-keys すると、本文がそのまま shell コマンドとして実行される
|
|
277
277
|
// (実被弾 2026-08-22: codex 終了後の bash へ配達され `command not found` が走った。
|
|
278
278
|
// 本文次第では席の権限で任意コマンドになる)。配達は止め、毎周期 typed log で叫ぶ。
|
|
279
|
+
// **pane_current_command は「bash の下で agent CLI が走る」形を bash と報告することがある**
|
|
280
|
+
// (実被弾 2026-08-22: 生きている codex 席が bash 判定になり配達が全停止、席が起きなかった)。
|
|
281
|
+
// shell 名だった時だけ pane 配下の生きた子孫を数え、**子孫ゼロの素の shell** だけを遮断する。
|
|
282
|
+
// agent CLI が死ねば子孫も消えるので、塞ぎたい穴(room 本文の shell 実行)はこの条件で塞がる。
|
|
279
283
|
const fg = await run('tmux', tmuxArgv(['display-message', '-p', '-t', observation.target, '#{pane_current_command}'], { socket: observation.socket }))
|
|
280
284
|
const fgCommand = String(fg.stdout ?? '').trim()
|
|
281
285
|
if (['bash', 'zsh', 'sh', 'dash', 'fish', 'tcsh', 'csh', 'ksh'].includes(fgCommand)) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
const panePidOut = await run('tmux', tmuxArgv(['display-message', '-p', '-t', observation.target, '#{pane_pid}'], { socket: observation.socket }))
|
|
287
|
+
const panePid = Number(String(panePidOut.stdout ?? '').trim())
|
|
288
|
+
let descendants = 0
|
|
289
|
+
if (Number.isSafeInteger(panePid) && panePid > 0) {
|
|
290
|
+
const psOut = await run('/bin/ps', ['-axo', 'pid=,ppid='], { env: { ...process.env, LC_ALL: 'C' } })
|
|
291
|
+
const children = new Map()
|
|
292
|
+
for (const line of String(psOut.stdout ?? '').split('\n')) {
|
|
293
|
+
const [pid, ppid] = line.trim().split(/\s+/u).map(Number)
|
|
294
|
+
if (!Number.isSafeInteger(pid) || !Number.isSafeInteger(ppid)) continue
|
|
295
|
+
if (!children.has(ppid)) children.set(ppid, [])
|
|
296
|
+
children.get(ppid).push(pid)
|
|
297
|
+
}
|
|
298
|
+
const queue = [...(children.get(panePid) ?? [])]
|
|
299
|
+
while (queue.length) {
|
|
300
|
+
const pid = queue.pop()
|
|
301
|
+
descendants += 1
|
|
302
|
+
queue.push(...(children.get(pid) ?? []))
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (descendants === 0) {
|
|
306
|
+
log(`SEAT_TUI_GONE: ${seat} の pane は子孫プロセスの無い素の shell(${fgCommand})=agent CLI が終了済み。`
|
|
307
|
+
+ 'shell へのコマンド実行を防ぐため配達しない。席を立て直すか leave-seat で畳むこと')
|
|
308
|
+
return 'deferred'
|
|
309
|
+
}
|
|
285
310
|
}
|
|
286
311
|
const pane = await run('tmux', tmuxArgv(['capture-pane', '-t', observation.target, '-p'], { socket: observation.socket }))
|
|
287
312
|
const screen = String(pane.stdout ?? '')
|
|
288
|
-
if (member.vendor === 'codex') {
|
|
313
|
+
if ((member.harness ?? member.vendor) === 'codex') {
|
|
289
314
|
const dialog = keysForCodexPane(screen)
|
|
290
315
|
if (dialog) {
|
|
291
316
|
for (const key of dialog.keys) {
|
|
@@ -295,9 +320,9 @@ async function wake(seat, msgs) {
|
|
|
295
320
|
return 'deferred'
|
|
296
321
|
}
|
|
297
322
|
}
|
|
298
|
-
if (member.vendor === 'grok') {
|
|
323
|
+
if ((member.harness ?? member.vendor) === 'grok') {
|
|
299
324
|
const tail = screen.split('\n').slice(-14).join('\n')
|
|
300
|
-
if (shouldDeferGrokWake(member.vendor, tail)) {
|
|
325
|
+
if (shouldDeferGrokWake((member.harness ?? member.vendor), tail)) {
|
|
301
326
|
if (!deferredBusy.has(seat)) {
|
|
302
327
|
log(`Grok席が実行中なのでidleまで待つ: ${seat} ← ${msgs.length} 件`)
|
|
303
328
|
deferredBusy.add(seat)
|
|
@@ -321,6 +346,13 @@ async function wake(seat, msgs) {
|
|
|
321
346
|
// 次周期に再試行する(毎回ログに残るので沈黙しない)。
|
|
322
347
|
const markerSource = text.replace(/\s+/gu, '')
|
|
323
348
|
const marker = markerSource.slice(-24)
|
|
349
|
+
// Claude Code は長文入力を折り畳んで本文を画面から消し("paste again to expand" /
|
|
350
|
+
// "[Pasted text")、ターン実行中は "esc to interrupt" でなくスピナー(✻✽·)を出す。
|
|
351
|
+
// この2つで旧検証は「未送信」と誤判定し、しかも回復に撃つ Escape が **Claude の実行中
|
|
352
|
+
// ターンを中断**していた(実被弾 2026-08-22: 監査席が2秒ごとに中断され、監査が進まなかった)。
|
|
353
|
+
// Claude 席は「受理された兆候」を成功とみなし、回復キーに Escape を使わない。
|
|
354
|
+
const CLAUDE_ACCEPTED = /paste again to expand|\[Pasted text|esc to interrupt|✻|✽|✳|·\s*\w+ing…/u
|
|
355
|
+
const isClaude = (member.harness ?? member.vendor) === 'claude'
|
|
324
356
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
325
357
|
await sleep(1200)
|
|
326
358
|
const after = await run('tmux', tmuxArgv(['capture-pane', '-t', observation.target, '-p'], { socket: observation.socket }))
|
|
@@ -329,13 +361,20 @@ async function wake(seat, msgs) {
|
|
|
329
361
|
// 折返しはcapture上で行分割されるだけで空白は挟まらないため、空白除去で復元して照合する
|
|
330
362
|
const tailSquashed = tailLines.join('').replace(/\s+/gu, '')
|
|
331
363
|
const queuedOrRunning = tailJoined.includes('esc to interrupt') || tailJoined.includes('to queue message')
|
|
332
|
-
|
|
364
|
+
|| (isClaude && CLAUDE_ACCEPTED.test(tailJoined))
|
|
365
|
+
const stuck = (!isClaude && tailJoined.includes('esc dismiss'))
|
|
333
366
|
|| (marker.length >= 8 && tailSquashed.includes(marker) && !queuedOrRunning)
|
|
334
367
|
if (!stuck) break
|
|
335
368
|
if (attempt === 2) {
|
|
336
369
|
log(`DELIVERY_STUCK: ${seat} の入力欄に本文が残ったまま送信できない(popup/入力詰まり)。受領を確定せず次周期に再試行する`)
|
|
337
370
|
return 'deferred'
|
|
338
371
|
}
|
|
372
|
+
if (isClaude) {
|
|
373
|
+
// Escape は撃たない(実行中ターンを殺す)。Enter だけ打ち直す
|
|
374
|
+
log(`配達が送信されていない: ${seat}(Claude 席のため Escape は使わず Enter を打ち直す)`)
|
|
375
|
+
await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'Enter'], { socket: observation.socket }))
|
|
376
|
+
continue
|
|
377
|
+
}
|
|
339
378
|
log(`配達が送信されていない(popup/入力詰まり): ${seat}。Escape→Enter で再送信する`)
|
|
340
379
|
await run('tmux', tmuxArgv(['send-keys', '-t', observation.target, 'Escape'], { socket: observation.socket }))
|
|
341
380
|
await sleep(300)
|
|
@@ -36,8 +36,8 @@ export function isWakeupBridgeTarget(member, options = {}) {
|
|
|
36
36
|
&& observe.tmux_target,
|
|
37
37
|
)
|
|
38
38
|
if (hasPane) return true
|
|
39
|
-
const
|
|
40
|
-
return
|
|
39
|
+
const harness = member.harness ?? member.vendor // 旧server互換
|
|
40
|
+
return harness === 'codex' || harness === 'grok'
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -62,8 +62,8 @@ export function isIdleSelfWake(msg) {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
/** Grok 既定はキュー投入。busy 中に積むと今のターンへ混ざらない。 */
|
|
65
|
-
export function shouldDeferGrokWake(
|
|
66
|
-
if (
|
|
65
|
+
export function shouldDeferGrokWake(harness, tail) {
|
|
66
|
+
if (harness !== 'grok') return false
|
|
67
67
|
if (typeof tail !== 'string') return false
|
|
68
68
|
if (classifyPaneTail(tail) === 'busy') return true
|
|
69
69
|
if (tail.includes('send a message to interrupt')) return true
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
## 作業ループ
|
|
14
14
|
|
|
15
15
|
**次にやる仕事があるターンを終える直前に `post(to: "<自分の名前>", message: "[次の行動] ...")` を1回送れ。** この自己DMは席の TUI へ次ターンの入力として入る。仕事があるのに出さないと席は止まる。空の終了通知は使うな。
|
|
16
|
-
**手番が無く待機に入るときは `[次の行動]` 自己DMを出すな。** 親へ `[待機]`
|
|
16
|
+
**手番が無く待機に入るときは `[次の行動]` 自己DMを出すな。** 親へ `[待機]` を一度だけ送り、沈黙する。再開は inbound だけ。待機自己DMは自分を起こし無限ループになる(2026-08-20 実測)。
|
|
17
|
+
**解放条件のある待機は、寝る前に目覚まし係へ条件を登録する(2026-08-22 オーナー設計)。** `"$(dirname "$0")"` 相当の `.team/scripts/alarm-set.sh <project> <自分の名前> "<何の条件か>" "<exit 0 で成立になる判定コマンド>"` を実行してから待機する。条件が成立すると `[待機解放条件成立]` が届いて起きる。**他席への「終わったら教えて」という口約束に解放を依存させない**(相手の死・退席・レース負けで永眠する)。**結果待ちのポーリングに自己DMやターンを使わない**——判定は目覚まし係の shell が無償で回す。
|
|
18
|
+
**停止宣言なしにターンを終えると継続番犬に起こされる。** busy が2分以上続いた席が `[待機]`/`[監査提出]` を出さずに idle 化すると `[継続]` が届く。作業を落としたなら続行し、手番が無いなら待機を宣言し直す。
|
|
17
19
|
|
|
18
20
|
**作業を選ぶのも始めるのもあなたである。** 装置から仕事が降ってくることはないし、着手前に装置の許可を待つこともない(オーナー裁定 2026-08-09・改・裁定1)。Lattice が居る卓では、着手した後に装置が競合を見て介入してくることがある——それは次の「装置が介入してきた時」に従う。
|
|
19
21
|
|
|
@@ -34,14 +34,14 @@
|
|
|
34
34
|
|
|
35
35
|
## 着卓手順
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
登録する。`
|
|
37
|
+
harness に関わらず: `scripts/parent-join.sh <project> [name] [model] [effort] [harness]` で member
|
|
38
|
+
登録する。`harness` は `claude`(既定)、`codex`、または `grok`。Lattice 併用モードなら、
|
|
39
39
|
`source .team/parent-env.sh` で Lattice mutation(`todo reopen` 等)に要る actor 環境変数
|
|
40
40
|
(`LATTICE_TODO_ACTOR_HOST`/`SESSION`/`AGENT`)を親 shell へ持続配線する——**子 process の
|
|
41
41
|
export は親 shell に伝播しないため**、これをしないまま `lattice todo reopen` 等を打つと
|
|
42
42
|
`ACTOR_UNRESOLVED`(`missing_environment=[...]`)で無変更停止する(実測: owner裁定[46]④)。
|
|
43
43
|
|
|
44
|
-
## 新着の検知(room追従は共通、親への通知だけ
|
|
44
|
+
## 新着の検知(room追従は共通、親への通知だけharness別)
|
|
45
45
|
|
|
46
46
|
`scripts/parent-watch.mjs <project> <親名>` がroom SSE、heartbeat、再接続catch-up、宛先判定、
|
|
47
47
|
永続cursorを一括所有する。stdoutへ出る`peertable.parent-watch-event.v1`はDM本文そのものであり、
|
|
@@ -129,7 +129,7 @@ peertable_parent_post() {
|
|
|
129
129
|
3. 工程正本で照合する(Lattice 併用: `lattice todo status --json`。単独: `.team/tasks.md` と
|
|
130
130
|
room ログの突き合わせ)。食い違ったら工程正本が正で、食い違い自体を room へ出す
|
|
131
131
|
4. member 登録は残っているので `parent-join.sh` を再実行しない。名前を確認するだけでよい
|
|
132
|
-
5.
|
|
132
|
+
5. harnessに応じて番犬を張り直す。ClaudeとGrokは旧Monitorを止めて`--follow`を1回起動する。Codexは旧background
|
|
133
133
|
taskを止め、1秒ごとの`--poll` loopを起動する。永続cursorが不在時間のDMをcatch-upする
|
|
134
134
|
6. 順序の要点は「room と工程正本を読み終えるまで発言しない」
|
|
135
135
|
|