peertable 0.4.26 → 0.4.28

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.26",
3
+ "version": "0.4.28",
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.26'
16
+ const MCP_VERSION = '0.4.28'
17
17
  const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
18
18
 
19
19
  const USAGE = `usage:
@@ -91,6 +91,16 @@ if (rest[0] === '--stop') process.exit(0)
91
91
  const requestedSeats = rest
92
92
  const state = JSON.parse(readFileSync(join(proj, '.team', 'setup-state.json'), 'utf8'))
93
93
  const { room, server_url: url } = state
94
+ const parentName = (() => {
95
+ if (process.env.PEERTABLE_PARENT_NAME) return process.env.PEERTABLE_PARENT_NAME
96
+ try {
97
+ const watch = JSON.parse(readFileSync(join(proj, '.team', 'parent-watch.json'), 'utf8'))
98
+ return typeof watch.parent === 'string' ? watch.parent : null
99
+ } catch {
100
+ return null
101
+ }
102
+ })()
103
+ const targetOpts = { parentName }
94
104
  writeFileSync(record, JSON.stringify({
95
105
  pid: process.pid, room, server_url: url, requested_seats: requestedSeats, started_at: new Date().toISOString(),
96
106
  }) + '\n')
@@ -124,7 +134,7 @@ function reconcileSeats() {
124
134
  const next = new Set()
125
135
  if (membersObserved) {
126
136
  for (const member of members.values()) {
127
- if (!isWakeupBridgeTarget(member)) continue
137
+ if (!isWakeupBridgeTarget(member, targetOpts)) continue
128
138
  next.add(member.name)
129
139
  if (!pending.has(member.name)) pending.set(member.name, new Map())
130
140
  }
@@ -227,7 +237,7 @@ async function wake(seat, msgs) {
227
237
  // 配送直前に member ledger を取り直し、current name -> descriptor の一経路だけを使う。
228
238
  await refreshMembers()
229
239
  const member = members.get(seat)
230
- if (!isWakeupBridgeTarget(member)) return 'skipped'
240
+ if (!isWakeupBridgeTarget(member, targetOpts)) return 'skipped'
231
241
  const observation = resolveSeatObservation(member, null)
232
242
  if (observation === null) {
233
243
  const code = members.has(seat) ? 'DESCRIPTOR_MISSING' : 'MEMBER_MISSING'
@@ -265,7 +275,7 @@ function dispatch(msg) {
265
275
  advanceLastSeq()
266
276
  return
267
277
  }
268
- const targets = recipientNames(msg).filter(seat => isWakeupBridgeTarget(members.get(seat)))
278
+ const targets = recipientNames(msg).filter(seat => isWakeupBridgeTarget(members.get(seat), targetOpts))
269
279
  const state = { message: msg, targets: new Set(targets), delivered: new Set() }
270
280
  for (const seat of targets) {
271
281
  if (delivered.has(deliveryKey(msg.seq, seat))) state.delivered.add(seat)
@@ -20,13 +20,24 @@ export function formatWakeNotice(msg) {
20
20
  }
21
21
 
22
22
  /**
23
- * 親番犬は別経路なので通常席の TUI 配達から外す。
24
- * observe 欠落は対象外ではない——配達できない故障であり、黙って飛ばさない。
23
+ * 親は parent-watch が配達する。通常席の TUI 配達から外す。
24
+ * Codex / Grok の observe 欠落は対象外ではない——配達できない故障であり、黙って飛ばさない。
25
25
  */
26
- export function isWakeupBridgeTarget(member) {
26
+ export function isWakeupBridgeTarget(member, options = {}) {
27
27
  if (!member || typeof member.name !== 'string' || member.name.length === 0) return false
28
28
  if (member.delivery?.kind === 'parent_watch') return false
29
- return true
29
+ const parentName = options.parentName
30
+ if (typeof parentName === 'string' && parentName.length > 0 && member.name === parentName) return false
31
+ const observe = member.observe
32
+ const hasPane = Boolean(
33
+ observe
34
+ && typeof observe === 'object'
35
+ && typeof observe.tmux_target === 'string'
36
+ && observe.tmux_target,
37
+ )
38
+ if (hasPane) return true
39
+ const vendor = member.vendor || member.settings?.vendor
40
+ return vendor === 'codex' || vendor === 'grok'
30
41
  }
31
42
 
32
43
  /**
@@ -106,8 +106,13 @@ peertable_parent_post() {
106
106
  fi
107
107
  local to="$1"
108
108
  shift
109
- python3 -c 'import json,sys; print(json.dumps({"from":sys.argv[1],"to":sys.argv[2],"body":sys.argv[3]}))' \
110
- "$PEERTABLE_PARENT_NAME" "$to" "$*" \
109
+ local poster
110
+ poster="$(npm root -g)/peertable/skill/scripts/post-message.mjs"
111
+ if [ ! -f "$poster" ]; then
112
+ echo "PEERTABLE_POSTER_MISSING: $poster" >&2
113
+ return 1
114
+ fi
115
+ node "$poster" "$PEERTABLE_PARENT_NAME" "$to" "$*" \
111
116
  | curl -sf -X POST "$PEERTABLE_ROOM_API/messages" \
112
117
  -H "X-Peertable-Token: $PEERTABLE_POST_TOKEN" \
113
118
  -H 'content-type: application/json' --data-binary @-