peertable 0.8.9 → 0.8.10

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.9",
3
+ "version": "0.8.10",
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.8.9'
16
+ const MCP_VERSION = '0.8.10'
17
17
  const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
18
18
 
19
19
  const USAGE = `usage:
@@ -447,6 +447,14 @@ PY
447
447
  fi
448
448
  seat_tmux=$(tmux_at display-message -p -t "$sess" '#{socket_path}')
449
449
 
450
+ # 席が作る派生tmuxセッションへ PEERTABLE_MEMBER を自動継承させる(2026-08-26 オーナー裁定:
451
+ # 帰属は規約名でなくOSの継承機構で辿る)。tmuxは既定でclientのenvを剥ぐので、update-environmentへ
452
+ # 1回だけ載せる。これで席発のセッションは名前が何でも、session envのPEERTABLE_MEMBERで持ち主が読める
453
+ if ! tmux_at show-options -g update-environment 2>/dev/null | grep -q 'PEERTABLE_MEMBER'; then
454
+ tmux_at set-option -ga update-environment ' PEERTABLE_MEMBER' || \
455
+ echo "SEAT_ENV_STAMP_UNAVAILABLE: update-environment を設定できず、派生セッションの帰属はsession envに載らない" >&2
456
+ fi
457
+
450
458
  # Aiterm管理席の process 起動は公開launch receiptで確定している。旧direct CLI launch向けの
451
459
  # ヘッダ/trust dialog待機をここへ重ねると、brief turnでヘッダが画面外へ流れた正常席をrollbackする。
452
460
  # 必須room MCPの成立は、次の member登録readbackだけで判定する。
@@ -27,7 +27,7 @@ import { execFileSync } from 'node:child_process'
27
27
  import { existsSync, readFileSync, renameSync, writeFileSync, unlinkSync } from 'node:fs'
28
28
  import { join } from 'node:path'
29
29
 
30
- import { STOP_DECLARATION, combineSeatLamp, hasActiveDescendant, subtreeCpuSeconds, patrolTargets, classifyPaneTail, decideBridgeContinuation, deriveMissingSession, isPaneProcessStopped, parsePaneTokenHint, resolveLatticeExecutable, resolvePostToken, resolveSeatObservation, resolveTmuxSocket, supportsMemberObservation, tmuxArgv, tmuxPanePid } from './seat-usage.mjs'
30
+ import { STOP_DECLARATION, attributeJobSession, combineSeatLamp, hasActiveDescendant, subtreeCpuSeconds, patrolTargets, classifyPaneTail, decideBridgeContinuation, deriveMissingSession, isPaneProcessStopped, parsePaneTokenHint, resolveLatticeExecutable, resolvePostToken, resolveSeatObservation, resolveTmuxSocket, supportsMemberObservation, tmuxArgv, tmuxPanePid } from './seat-usage.mjs'
31
31
 
32
32
  const args = process.argv.slice(2)
33
33
  const proj = args[0]
@@ -131,7 +131,7 @@ function readSeat(member, previous, observedAt) {
131
131
  const jobPaneHash = new Map() // `${name}:${session}` -> { hash, changedAt }
132
132
  const jobCpuSeconds = new Map() // `${name}:${session}` -> 前周期の累積CPU秒(root=job本体を含む)
133
133
  let jobObserveFailureLogged = false
134
- function observeJob(socket, name) {
134
+ function observeJob(socket, name, memberNames, envCache) {
135
135
  const listed = tmux(socket, 'list-sessions', '-F', '#{session_name}')
136
136
  if (listed === null) {
137
137
  // 「jobなし」と「観測手段が壊れている」を同じ顔にしない(silent absence禁止・2026-08-25 オーナー裁定)。
@@ -143,7 +143,19 @@ function observeJob(socket, name) {
143
143
  return { alive: false, active: false }
144
144
  }
145
145
  jobObserveFailureLogged = false
146
- const sessions = listed.split('\n').filter(s => s.startsWith(`peer-${name}-job`))
146
+ // 帰属はsession envのPEERTABLE_MEMBER(OSの継承をtmux update-environmentが写した実物)が正。
147
+ // 名前は自由——`-job` 規約は廃止(2026-08-26 オーナー裁定)。env読みは周期内cacheで1セッション1回。
148
+ const names = memberNames?.length ? memberNames : [name]
149
+ const envOf = (session) => {
150
+ if (envCache?.has(session)) return envCache.get(session)
151
+ const out = tmux(socket, 'show-environment', '-t', session, 'PEERTABLE_MEMBER')
152
+ const m = out === null ? null : /^PEERTABLE_MEMBER=(.+)$/m.exec(out)
153
+ const value = m ? m[1].trim() : null
154
+ envCache?.set(session, value)
155
+ return value
156
+ }
157
+ const sessions = listed.split('\n').filter(Boolean)
158
+ .filter(session => attributeJobSession({ session, memberNames: names, env: envOf(session) }) === name)
147
159
  if (sessions.length === 0) return { alive: false, active: false }
148
160
  let active = false
149
161
  let psRows = null
@@ -369,6 +381,8 @@ async function tick() {
369
381
  if (!supported) { console.error(`seat-status-bridge: ${members.length} 席を見たが、server が未対応なので送っていない`); return NOTHING_ATTEMPTED }
370
382
  const now = Date.now()
371
383
  const observedAt = new Date(now).toISOString()
384
+ const memberNames = members.map(m => m.name)
385
+ const envCache = new Map() // session env読みの周期内cache(帰属判定は全memberで同じ実物を見る)
372
386
  let sent = 0
373
387
  let skipped = 0
374
388
  let failed = 0
@@ -385,7 +399,7 @@ async function tick() {
385
399
  {
386
400
  const target = resolveSeatObservation(member, null) ?? resolveSeatObservation(member, defaultSocket())
387
401
  if (target !== null) {
388
- const job = observeJob(target.socket, member.name)
402
+ const job = observeJob(target.socket, member.name, memberNames, envCache)
389
403
  // 席paneの子孫プロセス(Codex内蔵background terminal等)の実働もjob稼働として合成する
390
404
  if (!job.active && observation.status === 'idle') {
391
405
  const panePid = tmuxPanePid(target.socket, target.target)
@@ -330,6 +330,21 @@ export function combineSeatLamp(paneStatus, job) {
330
330
  return paneStatus // 'dead' 等はそのまま
331
331
  }
332
332
 
333
+ // ---- 預け仕事セッションの帰属(2026-08-26 オーナー裁定: 規約名でなくOSの継承機構で辿る)----
334
+ // 正はsession envのPEERTABLE_MEMBER——席のシェルが持つ値をtmuxのupdate-environmentが作成時に
335
+ // 自動で写したもので、席は名前も申告も要らない。envが無い既存セッションだけ、旧来の
336
+ // `peer-<name>-` 前置の最長一致で拾う(後方互換。stamp導入前に立った常駐を殺さないため)。
337
+ // 席セッションそのもの(`peer-<name>` 完全一致)は預け仕事ではない。
338
+ export function attributeJobSession({ session, memberNames, env }) {
339
+ if (memberNames.includes(session.replace(/^peer-/, ''))) return null
340
+ if (env !== null && env !== undefined && memberNames.includes(env)) return env
341
+ let best = null
342
+ for (const n of memberNames) {
343
+ if (session.startsWith(`peer-${n}-`) && (best === null || n.length > best.length)) best = n
344
+ }
345
+ return best
346
+ }
347
+
333
348
  // paneプロセスの子孫に実働(CPU消費)が居るかの判定素材。psの `pid ppid pcpu` 出力を受け取り、
334
349
  // root配下の子孫(root自身は除く)に pcpu >= 閾値 が居れば true。席がCodex内蔵background terminal等、
335
350
  // tmuxセッションを作らずに走らせた預け仕事を、プロセスツリーの実観測で拾う(2026-08-25 オーナー裁定
@@ -21,7 +21,7 @@
21
21
 
22
22
  **kickoff・名指しの依頼DMには、まず `[引受] <要旨>` を room へ返してから着手する(決定104)。** 親はこの引受発言と配送 receipt が揃うまで依頼を未着手として扱う——黙って作業を始めると、依頼が届いていないと判定され再送・再依頼が起きる。
23
23
 
24
- **常駐・長時間ジョブは `peer-<自分の名前>-job<任意suffix>` という名前のtmuxセッションで走らせる(2026-08-25 オーナー裁定)。** `nohup`等で切り離すと観測不能になり、あなたのランプが「何もしていない」表示になる。jobセッションで走らせれば、稼働状況が自分のアクティブランプへ合成される(jobが動いていれば点滅)。複数ジョブは別suffixで並走してよい。ジョブ終了の目覚まし登録は従来どおり行う。
24
+ **常駐・長時間ジョブはtmuxセッションで走らせる。名前は自由(2026-08-26 オーナー裁定で命名規約は廃止)。** あなたのシェルからセッションを作れば、環境変数 `PEERTABLE_MEMBER` がOSの継承でセッションへ自動で刻まれ、観測装置がそれを読んで稼働をあなたのアクティブランプへ合成する(jobが動いていれば点滅)。`nohup`等でtmuxの外へ切り離すと観測不能になるので使わない。複数ジョブの並走は自由。ジョブ終了の目覚まし登録は従来どおり行う。
25
25
 
26
26
  **探索順は active → ready → 待機である。** まず自分の active 工程を完了させる。無ければ全PLAN横断の ready を自律的に claim する。実装も監査担当としての提出待ちも無い時だけ、最終手段として `[待機] ...` を親(bell 等、その卓の親名)だけへDMする。待機を `to: "all"` へ投稿しない。待機のあと自分へ `[次の行動]` を送らない。次に行う作業があるターンだけ自己DMする。空の終了通知は使わない。
27
27