peertable 0.4.31 → 0.4.33
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.4.
|
|
3
|
+
"version": "0.4.33",
|
|
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
|
@@ -13,7 +13,7 @@ import { findModelsDoc, resolveSeatIdentity } from '../skill/scripts/resolve-sea
|
|
|
13
13
|
|
|
14
14
|
// client.mjs 側のハードコード版数。package.json の version と一致していることを
|
|
15
15
|
// diagnostics の version_consistency が見る(2 つの版数源の drift 検出。決定45)
|
|
16
|
-
const MCP_VERSION = '0.4.
|
|
16
|
+
const MCP_VERSION = '0.4.33'
|
|
17
17
|
const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
18
18
|
|
|
19
19
|
const USAGE = `usage:
|
|
@@ -349,6 +349,7 @@ async function runDiagnostics(asJson) {
|
|
|
349
349
|
'scripts/parent-watch-logic.mjs',
|
|
350
350
|
'scripts/seat-credential.mjs',
|
|
351
351
|
'scripts/ensure-room-mcp.mjs',
|
|
352
|
+
'scripts/bridge-record-live.mjs',
|
|
352
353
|
'scripts/leave-seat.sh',
|
|
353
354
|
'scripts/change-seat.sh',
|
|
354
355
|
'scripts/set-mission.sh',
|
package/skill/SKILL.md
CHANGED
|
@@ -67,7 +67,7 @@ script は内部で Aiterm の公開 `claude_agent` / `codex_agent` / `grok_agen
|
|
|
67
67
|
- 席が読む env は script が組み立てる(`PEERTABLE_URL` / `PEERTABLE_ROOM` / `PEERTABLE_MEMBER` / `PEERTABLE_CREDENTIAL_FILE`、Lattice 併用なら `PEERTABLE_PLAN` と actor 3点)。token値は席別`0600` fileにだけ置き、pathだけを席へ渡す。**channels は `--mcp-config` の MCP server を解決しない**(実測 2026-08-08・Claude Code v2.1.226・決定44)ため、room の MCP 定義は setup.sh が project root へ置く `.mcp.json` が正。Peertable管理下fileはlaunch時にもcurrent-tree clientへ同期する。project に既存 `.mcp.json` があった場合は無断更新せず`SEAT_ROOM_MCP_STALE`で止まるので、AI がroom定義を手動mergeしてteardownで復元する
|
|
68
68
|
- **Codex 席**(`vendor=codex`): Codex には channels が無いので、room は `-c` 上書きの stdio MCPとして、同じPeertable treeの`node room/client.mjs`を差す。**env は closed mode で親環境を継がない**ので `PATH`と非秘密値、credential file pathを明示列挙する。effortはmember metadataだけでなく`model_reasoning_effort`へも同じ値を渡す。モデル名は ChatGPT アカウントで使える slug を渡す(`~/.codex/config.toml` の `model` が既定値の参考。使えない slug は起動後の最初のターンで 400 になって初めて分かる)。**DM を席 TUI へ入れる配達は Codex / Grok だけ**(下記)——Claude 席は room client が `notifications/claude/channel` を送る(8/12 に送信ループを削った回帰を 0.4.5 で戻した)。Codex は MCP 通知だけではターンを始めないので、room に残すだけでは席に届いたことにならない
|
|
69
69
|
- **Grok 席**(`vendor=grok`): project rootの`.mcp.json`をGrok Buildが読み、同じroom clientと席固有envをAitermが渡す。**user `~/.grok/config.toml` の booth/lattice 等 MCP は載せない**——席専用 `GROK_HOME`(`.team/seats/<name>.grok-home`、auth.json と ui だけ)を preflight と live 席の両方へ渡す。通らなければ席を立てず、別 model へ落とさない。model / effortはAitermの`grok_agent`へそのまま渡し、`grok models`のlive catalogに無いmodelは着席前に失敗する。初めて開くtreeの既知workspace trustは着席処理が通す。channelsは無いので同じ TUI 配達を使う。Grok TUIの既定はターン中の素送信を今の仕事へ混ぜず入力キューへ積むので、配達はGrok席がidleになるまで送らない
|
|
70
|
-
6. **DM の TUI 配達(Codex / Grok 席がある時)**: `scripts/ensure-bridge.sh <project> wakeup <席名>…`。room の SSE を購読し、明示的にその席宛の新着(自分の発言は除く)だけを席の TUI へ素送信する。専用の起床デーモンではない——Codex / Grok が MCP 通知からターンを始めないための配達口である。ensure は専用 tmux session で起動し、bridge の `ready_at`
|
|
70
|
+
6. **DM の TUI 配達(Codex / Grok 席がある時)**: `scripts/ensure-bridge.sh <project> wakeup <席名>…`。room の SSE を購読し、明示的にその席宛の新着(自分の発言は除く)だけを席の TUI へ素送信する。専用の起床デーモンではない——Codex / Grok が MCP 通知からターンを始めないための配達口である。ensure は専用 tmux session で起動し、bridge の `ready_at` を待つ。確認できなければログ末尾を出して非ゼロで終わる。**pid が生きているだけでは本人ではない**(Windows は pid を再利用する。2026-08-21 実測: 死んだ bridge の pid が oracle-mcp に再利用され、ensure が起動済みと誤認、全員宛キックオフが席へ届かなかった)。live 判定は record の `last_progress_at` が新しいこと。偽の pid は kill しない。Codexは即送信、Grokはidle待ち。親(`parent_watch`)は対象にしない。`observe` 欠落は対象外ではなく配達失敗としてログに出し、再試行する。停止は `node scripts/wakeup-bridge.mjs <project> --stop`(teardown.sh が自動で行う)
|
|
71
71
|
- **黙って止まらないための三段**(決定58 の受信側の作法): ①75秒なにも届かなければ自分から切って繋ぎ直す ②繋ぎ直したら `?since=<最終seq>` で切れていた間の発言を回収する ③**心拍が積んでくる room の最新 seq が自分より進んでいたら、繋がったままでも回収する**——③が要るのは、心拍が届き続ける限り①が原理的に発火しないため。**server 側の心拍(`event: ping`・25秒周期)が前提**なので、古い room サーバーへ繋ぐと①だけが効く形になる
|
|
72
72
|
- ログは `.team/wakeup-bridge.log`。**0件でも0件と出す**ので、TUI へ入れているか・取りこぼしていないかはログを見れば分かる。再現ハーネスは `experiments/bridge-catchup-repro.mjs`
|
|
73
73
|
|
|
@@ -231,7 +231,8 @@ witness をどう生成するかは**対象 project 側の作法に従う**(La
|
|
|
231
231
|
- Lattice 書込には actor 環境変数 3 点が必須
|
|
232
232
|
- `--parallel-frontier` が要るのは、**ready が複数あって誰も着手していない frontier の最初の start だけ**(無いと `PARALLEL_DISPATCH_REQUIRED / parallel_frontier_requires_declaration` で弾かれる)。ready が1件だけ、または既に誰かが着手している frontier へ後から乗る場合は素の `start` でよい。フラグが効くのは**取る task が `next_ready` に居る時だけ**で、他人が着手済みの task へ付けると `PARALLEL_DISPATCH_INVALID / parallel_frontier_not_applicable` になる——「フラグが使えない」ではなく「**その task はもう空いていない**」の意味である。記録があるのに対象工程が未宣言・失効なら `todo start` は `INDEPENDENCE_UNVERIFIED` で拒否する(Lattice ADR 0182)。記録が無い plan の start は従来どおり助言だけ
|
|
233
233
|
- **`done.sh` は feat SHA が `origin/main` の祖先でなければ canonical main へ merge して push する。** 親は着地しない。続けて remaining の independence を compile してから戻る。lattice run receipt の未着地は別軸で、警告のまま
|
|
234
|
-
- **independence compile は remaining A を含める。** 現在の ready だけを compile すると、次の frontier の start が拒否される。`next_ready` が witness に無い compile 自体も `INDEPENDENCE_READY_UNDECLARED` で拒否する。stale なら席が `.team/scripts/independence-refresh.sh`
|
|
234
|
+
- **independence compile は remaining A を含める。** 現在の ready だけを compile すると、次の frontier の start が拒否される。`next_ready` が witness に無い compile 自体も `INDEPENDENCE_READY_UNDECLARED` で拒否する。stale なら席が `.team/scripts/independence-refresh.sh` を打つ。campaign を起こす最初の compile は kickoff より前。H を最初の next_ready に並べると `MAX_TODOS=8` で compile できず、席は intake 待ちで止まる(2026-08-21 8B)。H は最初の frontier の外に置く。途中の stale 再 compile は席が打つ。
|
|
235
|
+
- **部屋へ書いたことを配達成功としない。** `.team/wakeup-bridge-delivery.json` の `last_seq` がキックオフ seq 以上になるまで、席は起きていない。
|
|
235
236
|
- **mission が古くても親は書き換えない。** 工程が変わったら席が `set-mission.sh` を自分で打つ。チップと `[mission]` の1行が正本で、席の再起動はしない
|
|
236
237
|
- 同時書込は `STORE_WRITE_CONFLICT` 等で明示的に負ける。1〜2 秒待って再実行すれば通る(正常系)
|
|
237
238
|
- evidence は記述子 JSON。記述子ファイル自体も repo 内相対パスに置く(repo 外絶対パスは INVALID_ARGUMENTS)。`.team/scripts/done.sh` が正規経路。証跡の置き場は **`evidence/<plan_key>/<task_id>.md`**——task_id は campaign を跨いで再利用されるので、平置きにすると前の campaign の監査証跡を上書きで消す(2026-08-08 実測)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// 常駐 bridge の record が「この pid のこの process」を指しているかの境界。
|
|
3
|
+
// Windows は pid を再利用する。alive な pid だけでは本人ではない
|
|
4
|
+
// (2026-08-21 実測: wakeup-bridge.json の pid が oracle-mcp に再利用され、
|
|
5
|
+
// 本物の配達は死んだまま ensure-bridge が起動済みと誤認した)。
|
|
6
|
+
import { readFileSync } from 'node:fs'
|
|
7
|
+
import { resolve } from 'node:path'
|
|
8
|
+
import { fileURLToPath } from 'node:url'
|
|
9
|
+
|
|
10
|
+
const STALE_MS = 90_000
|
|
11
|
+
|
|
12
|
+
export function bridgeRecordLive(record, now = Date.now()) {
|
|
13
|
+
if (!record || typeof record !== 'object') return false
|
|
14
|
+
const pid = Number(record.pid)
|
|
15
|
+
if (!Number.isInteger(pid) || pid <= 0) return false
|
|
16
|
+
try { process.kill(pid, 0) } catch { return false }
|
|
17
|
+
const stamp = record.last_progress_at || record.progress_at
|
|
18
|
+
if (typeof stamp !== 'string' || stamp.length === 0) return false
|
|
19
|
+
const at = Date.parse(stamp)
|
|
20
|
+
if (!Number.isFinite(at)) return false
|
|
21
|
+
return (now - at) <= STALE_MS
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const invokedDirectly = Boolean(process.argv[1])
|
|
25
|
+
&& resolve(fileURLToPath(import.meta.url)).toLowerCase() === resolve(process.argv[1]).toLowerCase()
|
|
26
|
+
if (invokedDirectly) {
|
|
27
|
+
const path = process.argv[2]
|
|
28
|
+
if (!path) process.exit(2)
|
|
29
|
+
let record
|
|
30
|
+
try { record = JSON.parse(readFileSync(path, 'utf8')) } catch { process.exit(1) }
|
|
31
|
+
process.exit(bridgeRecordLive(record) ? 0 : 1)
|
|
32
|
+
}
|
|
@@ -17,8 +17,7 @@ if [ $# -eq 0 ] && [ -f "$record" ]; then
|
|
|
17
17
|
fi
|
|
18
18
|
fi
|
|
19
19
|
if [ -f "$record" ]; then
|
|
20
|
-
|
|
21
|
-
if [ -n "$pid" ] && node "$(dirname "$0")/pid-alive.mjs" "$pid"; then exit 0; fi
|
|
20
|
+
if node "$(dirname "$0")/bridge-record-live.mjs" "$record"; then exit 0; fi
|
|
22
21
|
if ! "$force" && grep -q 'WRITE_DENIED' "$log" 2>/dev/null; then echo "${name}-bridge: 前回はWRITE_DENIEDで終了。--forceを指定すること" >&2; exit 1; fi
|
|
23
22
|
fi
|
|
24
23
|
# shellcheck disable=SC1091
|
|
@@ -18,6 +18,7 @@ import { join } from 'node:path'
|
|
|
18
18
|
import { promisify } from 'node:util'
|
|
19
19
|
import { resolveSeatObservation, tmuxArgv } from './seat-usage.mjs'
|
|
20
20
|
import { BROADCAST_RECIPIENT, formatWakeNotice, isIdleSelfWake, isWakeupBridgeTarget, shouldDeferGrokWake } from './wakeup-delivery.mjs'
|
|
21
|
+
import { bridgeRecordLive } from './bridge-record-live.mjs'
|
|
21
22
|
|
|
22
23
|
const run = promisify(execFile)
|
|
23
24
|
const [proj, ...rest] = process.argv.slice(2)
|
|
@@ -64,10 +65,32 @@ async function acquireStartupLock() {
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
68
|
+
function readRecord() {
|
|
69
|
+
try { return JSON.parse(readFileSync(record, 'utf8')) } catch { return null }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function writeRecord(next) {
|
|
73
|
+
const temp = `${record}.${process.pid}.tmp`
|
|
74
|
+
writeFileSync(temp, JSON.stringify(next) + '\n')
|
|
75
|
+
renameSync(temp, record)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function touchProgress() {
|
|
79
|
+
if (!existsSync(record)) return
|
|
80
|
+
const current = readRecord()
|
|
81
|
+
if (!current || current.pid !== process.pid) return
|
|
82
|
+
writeRecord({ ...current, last_progress_at: new Date().toISOString() })
|
|
83
|
+
}
|
|
84
|
+
|
|
67
85
|
async function stopRecorded() {
|
|
68
86
|
if (!existsSync(record)) return
|
|
69
|
-
const
|
|
70
|
-
|
|
87
|
+
const saved = readRecord()
|
|
88
|
+
const pid = Number(saved?.pid)
|
|
89
|
+
if (!bridgeRecordLive(saved)) {
|
|
90
|
+
unlinkSync(record)
|
|
91
|
+
log(`他人の pid または死んだ記録を掃除した(pid ${Number.isInteger(pid) ? pid : '不明'})。kill しない`)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
71
94
|
process.kill(pid, 'SIGTERM')
|
|
72
95
|
for (let i = 0; i < 25 && alive(pid); i++) await sleep(200)
|
|
73
96
|
if (alive(pid)) {
|
|
@@ -101,9 +124,10 @@ const parentName = (() => {
|
|
|
101
124
|
}
|
|
102
125
|
})()
|
|
103
126
|
const targetOpts = { parentName }
|
|
104
|
-
|
|
105
|
-
pid: process.pid, room, server_url: url, requested_seats: requestedSeats,
|
|
106
|
-
|
|
127
|
+
writeRecord({
|
|
128
|
+
pid: process.pid, room, server_url: url, requested_seats: requestedSeats,
|
|
129
|
+
started_at: new Date().toISOString(), last_progress_at: new Date().toISOString(),
|
|
130
|
+
})
|
|
107
131
|
releaseStartupLock()
|
|
108
132
|
|
|
109
133
|
const cleanup = () => { if (existsSync(record)) unlinkSync(record); process.exit(0) }
|
|
@@ -167,10 +191,12 @@ async function refreshMembers() {
|
|
|
167
191
|
reconcileSeats()
|
|
168
192
|
await refreshMembers()
|
|
169
193
|
function markReady() {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
194
|
+
const current = readRecord() || {}
|
|
195
|
+
writeRecord({
|
|
196
|
+
...current,
|
|
197
|
+
ready_at: new Date().toISOString(),
|
|
198
|
+
last_progress_at: new Date().toISOString(),
|
|
199
|
+
})
|
|
174
200
|
}
|
|
175
201
|
|
|
176
202
|
function loadDeliveryState() {
|
|
@@ -436,6 +462,7 @@ for (;;) {
|
|
|
436
462
|
let buf = ''
|
|
437
463
|
for await (const chunk of res.body) {
|
|
438
464
|
lastByteAt = Date.now()
|
|
465
|
+
touchProgress()
|
|
439
466
|
buf += Buffer.from(chunk).toString('utf8')
|
|
440
467
|
const parts = buf.split('\n\n')
|
|
441
468
|
buf = parts.pop()
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
|
|
21
21
|
- 着卓(member 登録)と席数制御(決定68の運用側: ready+active実装ToDo数に合わせて起こす/畳む)
|
|
22
22
|
- 作業者から監査担当への最終試験結果提出と、監査担当による工程クローズが正本へ記録されたことの観測
|
|
23
|
-
-
|
|
23
|
+
- 着地と、途中の independence 再 compile は席の仕事である。親は代行しない
|
|
24
|
+
- campaign を起こす最初の remaining A compile は kickoff より前。H を最初の next_ready に並べない(`MAX_TODOS=8`。並べると compile できず席の intake が止まる)
|
|
25
|
+
- 部屋へ書いたことを配達成功としない。`.team/wakeup-bridge-delivery.json` の `last_seq` が対象 seq 以上になるまで、席は起きていない。ensure-bridge の live 判定は `last_progress_at`。pid 生存だけでは本人ではない
|
|
24
26
|
- mission の更新は席の仕事である。親は代行しない
|
|
25
27
|
- 承認 gate・オーナーとの接点、裁定依頼の運搬(自分で判断せずオーナー宛の議題として運ぶ)
|
|
26
28
|
- model / effort 変更依頼への対応(本人の自然文を親が判断し、確定したtargetだけを
|