peertable 0.8.45 → 0.8.47

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.45",
3
+ "version": "0.8.47",
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.45'
17
+ const MCP_VERSION = '0.8.47'
18
18
  const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
19
19
 
20
20
  const USAGE = `usage:
package/skill/SKILL.md CHANGED
@@ -75,7 +75,7 @@ script は内部で Aiterm の公開 `claude_agent` / `codex_agent` / `grok_agen
75
75
  6.5 **席の稼働状態ブリッジ**: setup/launch/resumeが`ensure-project-runtime.sh`を内部で1回呼び、alarm/seat-status/wakeupを現行版へ収束させるので、**AIが個別bridgeを操作する段は無い**。観測先はmemberの`observe: {tmux_socket, tmux_target}`を優先する。ランプは現在のpaneからbusy/blocked/idle/deadを判定し、通信失敗表示が残るGrok席をbusyにしない
76
76
  6.7 **model / effort変更(本人要請→親実行)**: 本人は希望と理由を自然文で親だけへDMする。親は意味を判断してtargetを確定し、`env -u PEERTABLE_POST_TOKEN skill/scripts/change-seat.sh <project> <member> [--model <model>] [--effort <effort>] [--parent <name>] [--reason <text>]`を実行する。定型文への言い直し、完全一致の再送、本人DMの機械検査は行わない。scriptはroom memberの現在値を読み、Aitermの公開`agent_configure`へ確定targetを渡し、metadataの読返しと変更履歴を残す。targetはlive catalogで検証し、変更後の記録に失敗した場合も成功へ丸めない。
77
77
  - 同じharness内のmodel / effort変更はAitermが同一sessionと会話contextを保って行う。harness変更だけは再起動を伴うため、本人はrole・工程正本・roomログから再着任する
78
- 6.8 **mission 更新(席が自分で実行)**: 工程が変わったら席が `env -u PEERTABLE_POST_TOKEN skill/scripts/set-mission.sh <project> <name> <text>` を打つ。`POST /members` で chip を更新し、`[mission] <name>: <text>` を全員へ1行出す。席は再起動しない。親は代行しない。`change-seat.sh` に mission を足さない。
78
+ 6.8 **mission 更新(席が自分で実行)**: missionはフェーズ・campaign全体での担いであり、今やっている作業・待機状態を書く欄ではない(現在作業はroom発言から機械導出される。オーナー裁定 2026-08-30)。フェーズが変わったら席が `env -u PEERTABLE_POST_TOKEN skill/scripts/set-mission.sh <project> <name> <text>` を打つ。`POST /members` で chip を更新し、`[mission] <name>: <text>` を全員へ1行出す。席は再起動しない。親は代行しない。`change-seat.sh` に mission を足さない。
79
79
 
80
80
  7. **親の着卓**(このセッション): `scripts/parent-join.sh <project> [name] [model] [effort] [harness]` で member 登録とparent-watch cursorのprimeを行う。**`effort` は任意のまま据え置く**——席は `launch-seat.sh` が `--effort` で実際に設定するので「渡した値=実挙動」だが、親は既に走っているセッションで自分の effort を機械的に知る経路が無く、推測して載せると画面が嘘をつく。続けて、ClaudeとGrokはMonitor、Codexはyieldしたbackground tool taskとして**親宛DM番犬**を1世代だけ張る(形は下記「親の operating notes」の番犬仕様)。**着卓完了の条件は、parent-join が投稿する耳疎通probe(`EAR_PROBE_SENT` の nonce)を自分の監視イベントとして受信すること**——番犬プロセスの生存は耳の証拠にならない(前セッションの耳へ吠え続け親宛DMが全損した実被弾 2026-08-22)。受信できないなら監視を張り直す。通常席用wakeup-bridgeに親を載せない。broadcastのkickoffは廃止済み。以後の post も API 直(同 notes)
81
81
  8. **起動確認**: room の members に全員いる / 最初の claim が room に流れる(Lattice 併用モードはそれが Lattice へ到達している=`lattice todo status --json` の active に出ることも確認する。単独モードは room の claim 宣言だけが到達の証拠)/ Web UI で観測できる、をチェックして報告する
@@ -10,6 +10,9 @@ if [ -z "${TMPDIR:-}" ] && command -v getconf >/dev/null; then
10
10
  TMPDIR="$(getconf DARWIN_USER_TEMP_DIR 2>/dev/null || true)"
11
11
  [ -n "$TMPDIR" ] && export TMPDIR
12
12
  fi
13
+ # launchd環境には書込tokenが無い。bridgeのreceipt書込とresumeの席起動に必要なので、
14
+ # 標準の置き場から読み込む(無ければ従来どおり進み、書込systemは自分で degrade を報告する)
15
+ [ -f "$HOME/.config/peertable.env" ] && . "$HOME/.config/peertable.env"
13
16
  scripts="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
14
17
  for state in /Users/kite/Developer/*/.team/setup-state.json; do
15
18
  [ -f "$state" ] || continue
@@ -36,3 +39,44 @@ print(' '.join(m['name'] for m in ms if m.get('observe') or m.get('harness')))"
36
39
  fi
37
40
  fi
38
41
  done
42
+
43
+ # ---- 席の自動蘇生(オーナー恒久裁定「死んだら自動で起こせ。毎回手で復活させるな」)----
44
+ # tmux server死で全席が消える事故が反復した(実被弾 2026-08-29〜30に3回・原因未特定)。
45
+ # 台帳に居るのにtmux sessionが無い席を検知し、**工程が残っている時だけ**resumeで再着席させる。
46
+ # 仕事ゼロの時は起こさない(quotaを浪費しない。SEAT_TUI_GONE通知が親へ届く経路は別に生きている)。
47
+ # 二重起動はresume側のlaunch-seatが同名席を処理するが、周期の重複実行はlockで抑止する。
48
+ for state in /Users/kite/Developer/*/.team/setup-state.json; do
49
+ [ -f "$state" ] || continue
50
+ proj="$(dirname "$(dirname "$state")")"
51
+ lock="$proj/.team/seat-revive.lock"
52
+ # 10分cooldown(前回の蘇生が走っていれば触らない)
53
+ if [ -f "$lock" ] && [ -n "$(find "$lock" -mmin -10 2>/dev/null)" ]; then continue; fi
54
+ server="$(python3 -c "import json;print(json.load(open('$state'))['server_url'])" 2>/dev/null)"
55
+ room="$(python3 -c "import json;print(json.load(open('$state'))['room'])" 2>/dev/null)"
56
+ [ -n "$server" ] && [ -n "$room" ] || continue
57
+ sockprefix=$(node "$scripts/tmux-socket.mjs" --prefix 2>/dev/null) || continue
58
+ # shellcheck disable=SC2206
59
+ conn=($sockprefix)
60
+ dead=""
61
+ for seat in $(curl -s --max-time 10 "$server/api/$room/members" | python3 -c "
62
+ import json,sys
63
+ try: ms=json.load(sys.stdin).get('members',[])
64
+ except Exception: ms=[]
65
+ print(' '.join(m['name'] for m in ms if m.get('harness')))" 2>/dev/null); do
66
+ tmux "${conn[@]}" has-session -t "peer-$seat" 2>/dev/null || dead="$dead $seat"
67
+ done
68
+ [ -n "$dead" ] || continue
69
+ work=$(cd "$proj" && lattice todo status --json 2>/dev/null | python3 -c "
70
+ import json,sys
71
+ try: d=json.load(sys.stdin)
72
+ except Exception: print(0); raise SystemExit
73
+ print(len(d.get('next_ready',[]))+len(d.get('active_set',[]))+len(d.get('audit_pending',[])))" 2>/dev/null)
74
+ if [ "${work:-0}" -gt 0 ]; then
75
+ date -u +%FT%TZ > "$lock"
76
+ echo "$(date -u +%FT%TZ) 席消失を検知($proj:$dead / 残工程 $work)。resumeで自動蘇生する"
77
+ (cd "$proj" && bash "$scripts/resume.sh" "$proj" >> "$proj/.team/seat-revive.log" 2>&1) \
78
+ || echo "$(date -u +%FT%TZ) 自動蘇生に失敗: $proj(seat-revive.log参照)"
79
+ else
80
+ echo "$(date -u +%FT%TZ) 席消失を検知したが残工程ゼロのため起こさない($proj:$dead)"
81
+ fi
82
+ done
@@ -272,7 +272,18 @@ async function nudgeIfDropped(name, busySince) {
272
272
  // to:"all" は失効させない——親の待機宣言運用(起きても返信不要)と両立させるため。
273
273
  const PATROL_INTERVAL_MS = 30_000
274
274
  const PATROL_NAG_INTERVAL_MS = 300_000 // 条件が続く席へは5分間隔で再吠えする(1回きりにしない)
275
+ // 通報先の親名は parent-watch の記録から読む(wakeup-bridge と同じ解決規則)
276
+ const parentName = (() => {
277
+ if (process.env.PEERTABLE_PARENT_NAME) return process.env.PEERTABLE_PARENT_NAME
278
+ try {
279
+ const watch = JSON.parse(readFileSync(join(proj, '.team', 'parent-watch.json'), 'utf8'))
280
+ return typeof watch.parent === 'string' ? watch.parent : null
281
+ } catch { return null }
282
+ })()
283
+ const PATROL_ESCALATE_AFTER = 3 // 連続催促がこの回数に達したら親へ縮退疑いを1回通報する
275
284
  const patrolLastNag = new Map() // seat -> epoch_ms
285
+ const patrolNagStreak = new Map() // seat -> 連続催促数(催促条件が消えたらリセット)
286
+ const patrolEscalated = new Set() // 同一縮退エピソードでの親通報は1回だけ
276
287
  const busyStartedAt = new Map() // seat -> epoch_ms(このbridgeプロセスが観測した最後のターン開始・pane基準)
277
288
  const paneLast = new Map() // seat -> 直前周期のpane生status(番犬系専用。表示合成とは分離)
278
289
  const paneJobCpuSeconds = new Map() // seat -> 前周期の非足場子孫の累積CPU秒
@@ -303,8 +314,32 @@ async function patrolClaims() {
303
314
  lastBusyStartAt: seat => busyStartedAt.get(seat) ?? null,
304
315
  now, lastNag: patrolLastNag, nagIntervalMs: PATROL_NAG_INTERVAL_MS,
305
316
  })
317
+ // 催促対象から外れた席は縮退エピソード終了とみなし、計数と通報記録を消す
318
+ const targetSeats = new Set(targets.map(t => t.seat))
319
+ for (const seat of [...patrolNagStreak.keys()]) {
320
+ if (!targetSeats.has(seat)) { patrolNagStreak.delete(seat); patrolEscalated.delete(seat) }
321
+ }
306
322
  for (const { seat, task } of targets) {
307
323
  patrolLastNag.set(seat, now)
324
+ const streak = (patrolNagStreak.get(seat) ?? 0) + 1
325
+ patrolNagStreak.set(seat, streak)
326
+ // 催促に空返事を続ける縮退(実被弾 2026-08-30: 「作業を続けます」と返すだけの席へ
327
+ // 1時間催促を繰り返し、誰にも知られず空転した)。3回で親へ1回だけ通報する。
328
+ // 親宛DMはparent-watchが即時に親を起こす
329
+ if (streak >= PATROL_ESCALATE_AFTER && !patrolEscalated.has(seat)) {
330
+ patrolEscalated.add(seat)
331
+ try {
332
+ await fetch(`${url}/api/${encodeURIComponent(room)}/messages`, {
333
+ method: 'POST',
334
+ headers: { 'Content-Type': 'application/json', ...(token ? { 'X-Peertable-Token': token } : {}) },
335
+ body: JSON.stringify({ from: 'alarm', to: parentName ?? 'bell',
336
+ body: `[縮退疑い] ${seat} は工程 ${task} を保有したまま、継続催促${streak}回に停止宣言も進捗も返していない。空返事ループの可能性が高い。席の再起動を検討すること` }),
337
+ })
338
+ console.error(`seat-status-bridge: 縮退疑いを親へ通報した(${seat} / ${task} / 催促${streak}回)`)
339
+ } catch (e) {
340
+ console.error(`seat-status-bridge: 縮退通報に失敗: ${e.message}`)
341
+ }
342
+ }
308
343
  try {
309
344
  const res = await fetch(`${url}/api/${encodeURIComponent(room)}/messages`, {
310
345
  method: 'POST',
@@ -54,8 +54,13 @@
54
54
 
55
55
  ## 工程が変わった時の mission
56
56
 
57
+ **mission はフェーズ・campaign 全体での自分の担いを書く欄である**(例:「atlas-casework の受入監査担当」
58
+ 「fact-watch 実装の主担当」)。**今やっている作業・待機状態・進捗を書かない**——現在作業は room 発言
59
+ ([claim]/[引受] 等)からサーバーが機械導出して別に表示する(オーナー裁定 2026-08-30:
60
+ 「ミッションはフェーズや工程全体として見たときのミッション。今やってることを書く場所じゃない」)。
57
61
  着席時の mission は起動スナップショットである。campaign / plan が次のフェーズへ進んだら、
58
- 自分で更新する。親に依頼しない。席は再起動しない。
62
+ 自分で更新する。親に依頼しない。席は再起動しない。更新は**フェーズの変わり目だけ**であり、
63
+ 工程・作業のたびに書き換えない。
59
64
 
60
65
  ```
61
66
  env -u PEERTABLE_POST_TOKEN "$(npm root -g)/peertable/skill/scripts/set-mission.sh" . "$PEERTABLE_MEMBER" "<新しい使命>"