peertable 0.4.13 → 0.4.14

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 CHANGED
@@ -107,7 +107,7 @@ Codex では、スキルが所有する room MCP block をプロジェクトの
107
107
 
108
108
  動いており、**自分自身の開発に使っている**。2026-08-08 に end-to-end 検証済み——オーケストレーターなしの完全な一周(2 メンバーが相談し、claim し、インターフェースを交渉し、見つけた罠を共有して小さなプロジェクトを出荷)を**外部介入ゼロ**で完走。2026-08-13の実席ライフサイクルでは、作業席が親を通じてsession contextを保ったままmodel / effortを変更し、再起動後はroomと工程正本から再着任した。2026-08-14にはGrok 4.6席の着席、room参加、同一sessionの4.6↔4.5変更、DM起床を実機で確認した。2026-08-17にGrok席はidle待ち、broadcastは本文を残し、tmuxの無い親でbridge cursorが止まらないよう直した。
109
109
 
110
- 現在のnpm releaseは **peertable 0.4.13**。
110
+ 現在のnpm releaseは **peertable 0.4.14**。
111
111
 
112
112
  設計文書と決定履歴(**91 決定**)は [docs/plan.md](docs/plan.md)。
113
113
 
package/README.md CHANGED
@@ -143,7 +143,7 @@ It interviews you, names the members, scaffolds `.team/` (charter + roles, isola
143
143
 
144
144
  Working, and used to build itself. First verified end-to-end on 2026-08-08 with a full no-orchestrator loop: two members consulted, claimed, negotiated an interface, shared a discovered pitfall, and shipped a small project with **zero external intervention**. A 2026-08-13 real-seat lifecycle verified in-place model/effort changes and restart recovery. On 2026-08-14, a Grok 4.6 seat joined the room, changed 4.6↔4.5 in the same session, and woke on a direct message in a live acceptance run. On 2026-08-17 the wake-up path was corrected so Grok seats wait for idle, broadcasts keep their body, and a parent without a tmux seat cannot stall the bridge cursor.
145
145
 
146
- The current npm release is **peertable 0.4.13**.
146
+ The current npm release is **peertable 0.4.14**.
147
147
 
148
148
  The design document and decision log (**91 decisions**, in Japanese) live in [docs/plan.md](docs/plan.md).
149
149
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peertable",
3
- "version": "0.4.13",
3
+ "version": "0.4.14",
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
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url'
11
11
 
12
12
  // client.mjs 側のハードコード版数。package.json の version と一致していることを
13
13
  // diagnostics の version_consistency が見る(2 つの版数源の drift 検出。決定45)
14
- const MCP_VERSION = '0.4.13'
14
+ const MCP_VERSION = '0.4.14'
15
15
  const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
16
16
 
17
17
  const USAGE = `usage:
@@ -297,6 +297,7 @@ async function runDiagnostics(asJson) {
297
297
  'scripts/seat-identity.mjs',
298
298
  'scripts/pid-alive.mjs',
299
299
  'scripts/parent-watch.mjs',
300
+ 'scripts/parent-watch-logic.mjs',
300
301
  'scripts/seat-credential.mjs',
301
302
  'scripts/ensure-room-mcp.mjs',
302
303
  'scripts/leave-seat.sh',
@@ -0,0 +1,11 @@
1
+ export function latticeStaffingChanged(previous, next) {
2
+ if (!next || next.error) return false
3
+ if (!previous || previous.error) return true
4
+ return previous.ready !== next.ready || previous.active !== next.active
5
+ }
6
+
7
+ export function addressedToParent(message, parent) {
8
+ return message?.from !== parent
9
+ && (message?.to === 'all' || message?.to === parent
10
+ || (Array.isArray(message?.to_names) && message.to_names.includes(parent)))
11
+ }
@@ -11,6 +11,7 @@ import { execFileSync } from 'node:child_process'
11
11
  import { existsSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs'
12
12
  import { join } from 'node:path'
13
13
  import { resolveLatticeExecutable } from './seat-usage.mjs'
14
+ import { addressedToParent as messageAddressedToParent, latticeStaffingChanged } from './parent-watch-logic.mjs'
14
15
 
15
16
  const args = process.argv.slice(2)
16
17
  const project = args.shift()
@@ -154,9 +155,17 @@ function releaseLock() {
154
155
  acquireLock()
155
156
  process.on('exit', releaseLock)
156
157
 
157
- const addressedToParent = message => message?.from !== parent
158
- && (message?.to === 'all' || message?.to === parent
159
- || (Array.isArray(message?.to_names) && message.to_names.includes(parent)))
158
+ const addressedToParent = message => messageAddressedToParent(message, parent)
159
+
160
+ function exitWhenParentStdinCloses() {
161
+ if (mode !== '--follow') return
162
+ const stdin = process.stdin
163
+ if (!stdin || stdin.destroyed) return
164
+ const quit = () => process.exit(0)
165
+ stdin.on('end', quit)
166
+ stdin.on('close', quit)
167
+ if (typeof stdin.resume === 'function') stdin.resume()
168
+ }
160
169
 
161
170
  async function writeEvent(event) {
162
171
  await new Promise((resolve, reject) => {
@@ -180,8 +189,7 @@ async function acceptLatticeState(next) {
180
189
  })
181
190
  return true
182
191
  }
183
- const changed = Number.isSafeInteger(previous?.ready) && Number.isSafeInteger(previous?.active)
184
- && previous.ready + previous.active !== next.ready + next.active
192
+ const changed = latticeStaffingChanged(previous, next)
185
193
  state = { ...state, lattice: next, last_event_at: now() }
186
194
  saveState(state)
187
195
  if (!changed) return false
@@ -241,11 +249,29 @@ if (mode === '--next' && (!Number.isFinite(nextWindowMs) || nextWindowMs < 100))
241
249
  process.exit(2)
242
250
  }
243
251
 
252
+ exitWhenParentStdinCloses()
253
+
244
254
  let consecutiveFailures = 0
255
+ let snapshotSent = false
245
256
  for (;;) {
246
257
  const deadline = mode === '--next' ? Date.now() + nextWindowMs : Number.POSITIVE_INFINITY
247
258
  try {
248
259
  if (await catchUp()) process.exit(0)
260
+ if (mode === '--follow' && !snapshotSent) {
261
+ snapshotSent = true
262
+ const lattice = state.lattice
263
+ await writeEvent({
264
+ schema: 'peertable.parent-watch-event.v1',
265
+ type: 'parent_watch_snapshot',
266
+ parent,
267
+ last_seq: state.last_seq,
268
+ ready: lattice?.ready ?? null,
269
+ active: lattice?.active ?? null,
270
+ body: lattice && !lattice.error
271
+ ? staffingBody(lattice)
272
+ : '親番犬を張り直した。room の親宛と工程件数を追従する。',
273
+ })
274
+ }
249
275
  const controller = new AbortController()
250
276
  const remaining = Number.isFinite(deadline) ? Math.max(1, deadline - Date.now()) : null
251
277
  const timer = remaining === null ? null : setTimeout(() => controller.abort(), remaining)
@@ -54,8 +54,10 @@ export は親 shell に伝播しないため**、これをしないまま `latti
54
54
  張り替え時は旧Monitorを止めてから起動する。
55
55
 
56
56
  どちらも`parent-join.sh`が先に作る`.team/parent-watch.json`のcursorを共有する。watcher不在中のDMは
57
- 次回起動時にcatch-upされ、親以外宛・親自身の発言・pingでは親を起こさない。`watch_error`が届いたら
58
- 通常のDMとして扱わず、番犬の再着卓を行う。
57
+ 次回起動時にcatch-upされ、親以外宛・親自身の発言・pingでは親を起こさない。Lattice の ready 件数と
58
+ active 件数は合計ではなくそれぞれ変化したら起こす(実装が全部閉じて親手番1件だけになっても起こす)。
59
+ `--follow` は親セッションの stdin が閉じたら終了する。切れた番犬が seq だけ進めて親が起きない状態を作らない。
60
+ `watch_error`が届いたら通常のDMとして扱わず、番犬の再着卓を行う。
59
61
 
60
62
  ## 試験結果の監査
61
63