peertable 0.3.6 → 0.3.8
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 +22 -4
- package/README.md +32 -8
- package/package.json +1 -1
- package/room/client.mjs +36 -2
- package/room/server.mjs +52 -12
- package/skill/SKILL.md +12 -8
- package/skill/scripts/archive-room-log.py +7 -3
- package/skill/scripts/ensure-bridge.sh +55 -0
- package/skill/scripts/launch-seat.sh +29 -10
- package/skill/scripts/run-bridge.mjs +16 -3
- package/skill/scripts/seat-status-bridge.mjs +123 -28
- package/skill/scripts/seat-usage.mjs +143 -0
- package/skill/scripts/setup.sh +13 -0
- package/skill/scripts/teardown.sh +19 -11
- package/skill/scripts/tmux-socket.mjs +10 -0
- package/skill/scripts/wakeup-bridge.mjs +34 -4
- package/skill/templates/member-standalone.md +2 -1
- package/skill/templates/member.md +3 -2
|
@@ -12,9 +12,10 @@
|
|
|
12
12
|
// 送った文言はそのターンの中で読まれ、指示どおりに動いた(steering が効く)。したがって
|
|
13
13
|
// idle 待ちの経路は持たない——待ちを入れると、混んでいる席ほど起床が遅れる。
|
|
14
14
|
import { execFile } from 'node:child_process'
|
|
15
|
-
import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
15
|
+
import { existsSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
16
16
|
import { join } from 'node:path'
|
|
17
17
|
import { promisify } from 'node:util'
|
|
18
|
+
import { resolveSeatObservation, resolveTmuxSocket } from './seat-usage.mjs'
|
|
18
19
|
|
|
19
20
|
const run = promisify(execFile)
|
|
20
21
|
const [proj, ...rest] = process.argv.slice(2)
|
|
@@ -24,7 +25,8 @@ if (!proj || rest.length === 0) {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const record = join(proj, '.team', 'wakeup-bridge.json')
|
|
27
|
-
const
|
|
28
|
+
const socketResult = resolveTmuxSocket(process.env)
|
|
29
|
+
const sock = socketResult.socket
|
|
28
30
|
const alive = pid => { try { process.kill(pid, 0); return true } catch { return false } }
|
|
29
31
|
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
30
32
|
const log = line => console.log(`[${new Date().toISOString()}] ${line}`)
|
|
@@ -63,6 +65,28 @@ process.on('SIGINT', cleanup)
|
|
|
63
65
|
|
|
64
66
|
// 席ごとに未配達を溜めて、2秒ごとにまとめて1回起こす(連投で席を何度も起こさない)
|
|
65
67
|
const pending = new Map(seats.map(s => [s, []]))
|
|
68
|
+
let members = new Map()
|
|
69
|
+
async function refreshMembers() {
|
|
70
|
+
try {
|
|
71
|
+
const res = await fetch(`${url}/api/${encodeURIComponent(room)}/members`)
|
|
72
|
+
if (!res.ok) throw new Error(`members ${res.status}`)
|
|
73
|
+
const body = await res.json()
|
|
74
|
+
members = new Map(body.members.map(member => [member.name, member]))
|
|
75
|
+
return true
|
|
76
|
+
} catch (error) {
|
|
77
|
+
// 記述子の更新が読めないことは起床停止の理由にしない。直前の map を保ち、
|
|
78
|
+
// 未登録席は wake() の legacy target へ戻す。縮退を必ずログに残す。
|
|
79
|
+
log(`member 記述子を更新できないので直前の観測先で続ける: ${error.message}`)
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
await refreshMembers()
|
|
84
|
+
function markReady() {
|
|
85
|
+
const next = { ...JSON.parse(readFileSync(record, 'utf8')), ready_at: new Date().toISOString() }
|
|
86
|
+
const temp = `${record}.tmp`
|
|
87
|
+
writeFileSync(temp, JSON.stringify(next) + '\n')
|
|
88
|
+
renameSync(temp, record)
|
|
89
|
+
}
|
|
66
90
|
|
|
67
91
|
async function wake(seat, msgs) {
|
|
68
92
|
const last = msgs[msgs.length - 1]
|
|
@@ -71,9 +95,14 @@ async function wake(seat, msgs) {
|
|
|
71
95
|
? `room に新着あり(${last.from} → ${audience})。read_unread で読むこと。`
|
|
72
96
|
: `room に新着 ${msgs.length} 件(最新: ${last.from} → ${audience})。read_unread で読むこと。`
|
|
73
97
|
try {
|
|
74
|
-
|
|
98
|
+
// bridge 起動後の着席も次の配達で取り直す。初回だけの snapshot にすると
|
|
99
|
+
// 新席を「member 不明」として永久に起こせず、旧 peer- 互換より後退する。
|
|
100
|
+
await refreshMembers()
|
|
101
|
+
const observation = resolveSeatObservation(members.get(seat) ?? { name: seat }, sock)
|
|
102
|
+
if (observation === null) throw new Error(`観測記述子も既定 socket も無い: ${seat}`)
|
|
103
|
+
await run('tmux', ['-S', observation.socket, 'send-keys', '-t', observation.target, text])
|
|
75
104
|
await sleep(400)
|
|
76
|
-
await run('tmux', ['-S',
|
|
105
|
+
await run('tmux', ['-S', observation.socket, 'send-keys', '-t', observation.target, 'Enter'])
|
|
77
106
|
log(`起こした: ${seat} ← ${msgs.length} 件(最新 seq ${last.seq})`)
|
|
78
107
|
} catch (error) {
|
|
79
108
|
// 席が畳まれていれば tmux が落ちる。黙って飲まず、毎回出す(何件落としたかも出す)
|
|
@@ -172,6 +201,7 @@ for (;;) {
|
|
|
172
201
|
if (!res.ok) throw new Error(`events ${res.status}`)
|
|
173
202
|
failures = 0
|
|
174
203
|
log('SSE 接続')
|
|
204
|
+
markReady()
|
|
175
205
|
await catchUp('再接続')
|
|
176
206
|
let buf = ''
|
|
177
207
|
for await (const chunk of res.body) {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
1. `.team/tasks.md` で議題を見る。`read_log` で既存の claim と完了報告を照合し、まだ誰も持っていないものを選ぶ
|
|
10
10
|
2. 憲章の手順で`read_log`から先行claimを確認し、roomに`[claim] <タスク>`を記録する。起こす必要のある相手だけを宛先へ入れ、記録だけなら自分宛でよい。**`[claim]` は独立した1発言で出す**——完了報告や他タスクの話と同じ発言に畳まない。宣言としては有効でも、後から機械的に追えなくなり、監査が「宣言が無い」と誤読する(2026-08-08 実測)。単独モードでは room ログが唯一の正本なので、より効く
|
|
11
|
-
3.
|
|
11
|
+
3. 実装する。インターフェースなど他タスクに影響する決定は、影響を受けるメンバーを明示宛先にして一行で共有する。宛先は**その内容で今すぐ動く必要がある相手だけ**——同報・返答不要の情報共有目的で複数人を並べない(決定71)
|
|
12
12
|
4. 完了手順:
|
|
13
13
|
- 何を作り、どう確認したかを自分で確かめる(テスト・実行・実測。「たぶん動く」で閉じない)
|
|
14
14
|
- 変更ファイルを `git add` して commit する(メッセージは日本語一行。対象ファイルを明示して他人の作業中変更を巻き込まない)
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
## 注意
|
|
27
27
|
|
|
28
|
+
- **DM宛先は最小化する(決定71)**: 親宛DMは①done報告・監査受理要請、②オーナー承認gateに関わる物件、③親・オーナーにしか解けないblocker・裁定依頼(`[effort変更依頼]`はこれに該当)の3種だけ。進捗・調整・意見の共有は親宛DMにしない。席同士のDMも、その内容で**今すぐ動く必要がある相手だけ**へ送る——同報・返答不要の情報共有目的の複数宛先は禁止。宛先を選ぶ基準は「この宛先は受け取って何をする?」に答えられるかで、答えられない宛先は外す。記録目的はroomログ(全員pull可読)で足りる
|
|
28
29
|
- 誰が何を持っているかを機械に問い合わせられない卓である。claim と完了の宣言を落とした瞬間に重複作業になるので、宣言を省かない
|
|
29
30
|
- room の新着通知が来たら read_unread で読む。返事が要るものには post で応える
|
|
30
31
|
- **Codex 席の場合**: 起床は channels ではなく wakeup-bridge が担う。`room に新着あり(<誰> → <宛先>)。read_unread で読むこと。` が端末へ直接届くので、Claude 席と同じく read_unread で読む。**作業中でも割り込んで届く**(そのターンの中で読まれる)ので、届いたらその場で手を止めて読み、返事が要るなら post してから元の作業へ戻る。自分の発言では起きない
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
1. `lattice todo status --json` で ready なタスクを見る。{{CLAIM_SCOPE}}
|
|
10
10
|
2. 憲章の手順で room に claim を宣言する。**`[claim]` は独立した1発言で出す**——完了報告や他タスクの話と同じ発言に畳まない。宣言としては有効でも、後から機械的に追えなくなり、監査が「宣言が無い」と誤読する(2026-08-08 実測)
|
|
11
11
|
3. `lattice todo start --plan {{PLAN_KEY}} --task <id>` で着手を記録する。**誰も着手しておらず ready が2件以上ある frontier の先頭を取る時だけ `--parallel-frontier` が必須**(無いと `PARALLEL_DISPATCH_REQUIRED / parallel_frontier_requires_declaration` で弾かれる)。ready が1件だけ、または既に誰かが着手している frontier へ後から乗る時は素の start でよい
|
|
12
|
-
4.
|
|
12
|
+
4. 実装する。インターフェースなど他タスクに影響する決定は、決めた時点で影響を受けるメンバーを明示宛先にして一行で共有する。宛先は**その内容で今すぐ動く必要がある相手だけ**——同報・返答不要の情報共有目的で複数人を並べない(決定71)。broadcastは無く、他のメンバーはroomログをpullで読む。**正しさの確認は実測だけが与える(決定66)**——着手していない工程の設計・契約・手順を思想で検証しない。次工程の起草は「着手できる最小限」で止め、実測で答えられる問いを議論で答えない。監査も対象に触れるもの(diff・実行・突合)だけが監査で、触れない予想は監査ではない
|
|
13
13
|
5. 完了手順:
|
|
14
14
|
- 証跡ファイル `evidence/{{PLAN_KEY}}/<task_id>.md` に「何を作り、どう確認したか」を書く(ディレクトリが無ければ作る。task_id は campaign を跨いで再利用されるので、平置きにすると前の campaign の証跡を上書きで消す)
|
|
15
15
|
- 変更ファイルと証跡を `git add` して commit する(メッセージは日本語一行。対象ファイルを明示して他人の作業中変更を巻き込まない)
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
9. **監査findingに採れるのは欠陥だけ(決定65)。** 受入をblockできるfindingは「実際に再現するエラー・受入条件の不成立・明白な論理破綻・具体的な安全事故経路」だけ。**「安全装置を増やした方がいい」「この構成の方が便利」といった思想・改善提案はfindingではない**——申し送り(`todo note`・課題帳)へ自分で記録して受入はそのまま通し、**実装者に返信・対応の義務を生まない**。思想を1つ1つのToDoの監査結果へ混ぜると開発は永遠に終わらない(オーナー裁定 2026-08-09)。欠陥か思想かの線引きは**あなたの判断**で行う——機械的な形式基準は置かない(基準で縛ると本物の指摘まで形式で弾かれる)。再現手順を添えると監査は速く強くなるが、それは義務ではなく技法である
|
|
29
29
|
10. **監査の重さは2段で使い分け、閉じるのは欠陥の枯渇で決める(決定64)。** 通常のToDoは軽く: 実装者以外の席が対象diff・受入条件・focused testを確認して閉じる。複数席・独立fixture再現・反証という**重い構造は、Wave/Phaseの受入gateと契約クリティカル面(認可・lease・公開契約・データ破壊経路)だけ**。**終了条件は巡数ではない**——欠陥(決定65資格)が出た巡の次は何巡目でも正当で、欠陥の出なかった巡で閉じる。何席・何巡は自分の判断(数の上限で縛らない)。迷ったら軽い方——全ToDoを重くすると卓全体が止まる(2026-08-09実測)
|
|
30
30
|
11. **監査は文脈近接の独立1席(決定67)。** 監査を依頼する時は、**同じ工程・関連ToDoを担当した席**(文脈を既に持つ席)から**1席だけ**選ぶ。無関係の席へ依頼しない(契約・経緯の冷間読み込みでトークンを焼く)。独立の要件は「実装者本人でない」ことだけで、文脈近接と両立する。**自分が依頼されていない監査へ自発参加しない・他席の監査結果へ追認を送らない**。2席目(別レンズ)を付けてよいのは決定64の重い面(契約クリティカル・Wave/Phase gate)だけで、付ける側が理由1行を添える
|
|
31
|
-
12. **claimできるToDoが無いなら仕事を発明しない(決定68)。**
|
|
31
|
+
12. **claimできるToDoが無いなら仕事を発明しない(決定68)。** 依頼されていない監査・他席への状況照会・正典の自主レビューを暇つぶしに始めない(空いた時間は成果ではなくコスト)。**roomへ**「[待機] <名前>」を1行宣言して黙る(親宛DMにしない——決定71。親は決定68の運用側どおりroomログをpullして席数を判断しており、DMで起こす必要はない)。縮退の打診が来たらWIP棚卸し(claim中ToDo・未commit変更・未報告結果・残process/一時fixture)を正直に返す
|
|
32
32
|
13. 1 へ戻る
|
|
33
33
|
|
|
34
34
|
## effortを変更してほしい時
|
|
@@ -116,6 +116,7 @@ lattice run intake --run .lattice/runs/<run-id> --task <id>
|
|
|
116
116
|
|
|
117
117
|
## 注意
|
|
118
118
|
|
|
119
|
+
- **DM宛先は最小化する(決定71)**: 親宛DMは①done報告・監査受理要請、②オーナー承認gateに関わる物件、③親・オーナーにしか解けないblocker・裁定依頼(`[effort変更依頼]`はこれに該当)の3種だけ。進捗・調整・意見の共有や待機宣言は親宛DMにしない。席同士のDMも、その内容で**今すぐ動く必要がある相手だけ**へ送る——同報・返答不要の情報共有目的の複数宛先は禁止。宛先を選ぶ基準は「この宛先は受け取って何をする?」に答えられるかで、答えられない宛先は外す。記録目的はroomログ(全員pull可読)で足りる
|
|
119
120
|
- Lattice の書き込みが `STORE_WRITE_CONFLICT` 等で弾かれたら、1〜2 秒待って同じコマンドを再実行する(同時書込の正常な負け方であり、壊れてはいない)
|
|
120
121
|
- `--parallel-frontier` を付けた start が `parallel_frontier_not_applicable` で弾かれたら、それは**その task がもう `next_ready` に居ない**(他人が着手済み・依存で塞がった)という意味である。フラグの不具合ではないので付け外しで粘らず、`lattice todo status --json` と room ログで claim 状況を確認し直す
|
|
121
122
|
- claim が衝突したら、Lattice の start 記録(誰が in-progress か)を機械の事実として使う。**装置は claim の争いを裁定しない**——装置が見るのは着手済み task 同士の競合だけで、誰が取るかは卓が決める
|