peertable 0.8.15 → 0.8.16
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 +1 -1
- package/room/client.mjs +1 -1
- package/skill/launchd/dev.kitepon.peertable-bridges.plist +20 -0
- package/skill/scripts/ensure-all-bridges.sh +38 -0
- package/skill/scripts/leave-seat.sh +5 -0
- package/skill/scripts/parent-watch.mjs +8 -3
- package/skill/scripts/seat-usage.mjs +9 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peertable",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.16",
|
|
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.
|
|
16
|
+
const MCP_VERSION = '0.8.16'
|
|
17
17
|
const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
18
18
|
|
|
19
19
|
const USAGE = `usage:
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>Label</key><string>dev.kitepon.peertable-bridges</string>
|
|
6
|
+
<key>ProgramArguments</key>
|
|
7
|
+
<array>
|
|
8
|
+
<string>/bin/bash</string>
|
|
9
|
+
<string>/Users/kite/Developer/peertable/skill/scripts/ensure-all-bridges.sh</string>
|
|
10
|
+
</array>
|
|
11
|
+
<key>EnvironmentVariables</key>
|
|
12
|
+
<dict>
|
|
13
|
+
<key>PATH</key><string>/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
14
|
+
</dict>
|
|
15
|
+
<key>RunAtLoad</key><true/>
|
|
16
|
+
<key>StartInterval</key><integer>120</integer>
|
|
17
|
+
<key>StandardOutPath</key><string>/Users/kite/Library/Logs/peertable-bridges.log</string>
|
|
18
|
+
<key>StandardErrorPath</key><string>/Users/kite/Library/Logs/peertable-bridges.log</string>
|
|
19
|
+
</dict>
|
|
20
|
+
</plist>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# launchd(dev.kitepon.peertable-bridges)から2分周期で呼ばれ、稼働中の全円卓のbridgeを
|
|
3
|
+
# 生存保証する。ensure-bridge.sh は生きていれば何もしない冪等な入口なので、この巡回は
|
|
4
|
+
# 「死んだら次の周期で自動再起動」を意味する(オーナー裁定 2026-08-29「毎回手で復活させるな」)。
|
|
5
|
+
# 対象卓の発見は実物(.team/setup-state.json の存在)だけを根拠にする。登録簿は持たない。
|
|
6
|
+
set -u
|
|
7
|
+
# launchd環境にはTMPDIRが無く、tmux socketの既定解決($TMPDIR/claude-tmux-sockets/…)が
|
|
8
|
+
# /tmp側へ落ちて全ensureが失敗する(実被弾 2026-08-29)。macOSの正規per-user tempを自前導出する。
|
|
9
|
+
if [ -z "${TMPDIR:-}" ] && command -v getconf >/dev/null; then
|
|
10
|
+
TMPDIR="$(getconf DARWIN_USER_TEMP_DIR 2>/dev/null || true)"
|
|
11
|
+
[ -n "$TMPDIR" ] && export TMPDIR
|
|
12
|
+
fi
|
|
13
|
+
scripts="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
14
|
+
for state in /Users/kite/Developer/*/.team/setup-state.json; do
|
|
15
|
+
[ -f "$state" ] || continue
|
|
16
|
+
proj="$(dirname "$(dirname "$state")")"
|
|
17
|
+
bash "$scripts/ensure-bridge.sh" "$proj" seat-status >/dev/null 2>&1 \
|
|
18
|
+
|| echo "$(date -u +%FT%TZ) ensure失敗: seat-status $proj"
|
|
19
|
+
bash "$scripts/ensure-bridge.sh" "$proj" alarm >/dev/null 2>&1 \
|
|
20
|
+
|| echo "$(date -u +%FT%TZ) ensure失敗: alarm $proj"
|
|
21
|
+
# wakeup は配達先の席名一覧が要る。room 台帳から現行メンバーを実物で読む
|
|
22
|
+
server="$(python3 -c "import json;print(json.load(open('$state'))['server_url'])" 2>/dev/null)"
|
|
23
|
+
room="$(python3 -c "import json;print(json.load(open('$state'))['room'])" 2>/dev/null)"
|
|
24
|
+
if [ -n "$server" ] && [ -n "$room" ]; then
|
|
25
|
+
seats="$(curl -s --max-time 10 "$server/api/$room/members" | python3 -c "
|
|
26
|
+
import json,sys
|
|
27
|
+
try:
|
|
28
|
+
ms=json.load(sys.stdin).get('members',[])
|
|
29
|
+
except Exception:
|
|
30
|
+
ms=[]
|
|
31
|
+
print(' '.join(m['name'] for m in ms if m.get('observe') or m.get('harness')))" 2>/dev/null)"
|
|
32
|
+
if [ -n "$seats" ]; then
|
|
33
|
+
# shellcheck disable=SC2086
|
|
34
|
+
bash "$scripts/ensure-bridge.sh" "$proj" wakeup $seats >/dev/null 2>&1 \
|
|
35
|
+
|| echo "$(date -u +%FT%TZ) ensure失敗: wakeup $proj ($seats)"
|
|
36
|
+
fi
|
|
37
|
+
fi
|
|
38
|
+
done
|
|
@@ -60,6 +60,11 @@ if tmux_at has-session -t "$target" 2>/dev/null; then
|
|
|
60
60
|
fi
|
|
61
61
|
elif tmux_at list-sessions >/dev/null 2>&1; then
|
|
62
62
|
: # serverへ到達でき、対象sessionが無い
|
|
63
|
+
elif [ -n "${member_socket:-}" ] && [ ! -S "${member_socket}" ]; then
|
|
64
|
+
# socketファイル自体が消えている=tmux serverごと死んでいる(OS再起動等)。
|
|
65
|
+
# 席のprocessは存在しえないので「畳めない」ではなく「畳む対象が無い」が正
|
|
66
|
+
# (実被弾 2026-08-29: Mac再起動後のresumeが全席でここに落ちて再起動できなかった)
|
|
67
|
+
echo "SEAT_LEAVE_SESSION_GONE: tmux socket消失(${member_socket})。畳む対象なしとして続行" >&2
|
|
63
68
|
else
|
|
64
69
|
echo "SEAT_LEAVE_SESSION_UNREADABLE: ${member_socket:-tmux}" >&2
|
|
65
70
|
failed=1
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
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
|
-
import { resolveLatticeExecutable, resolvePostToken } from './seat-usage.mjs'
|
|
13
|
+
import { resolveLatticeExecutable, resolvePostToken, parentWatchShouldNotify } from './seat-usage.mjs'
|
|
14
14
|
import { addressedToParent as messageAddressedToParent, latticeStaffingChanged, tableStallUpdate } from './parent-watch-logic.mjs'
|
|
15
15
|
|
|
16
16
|
const args = process.argv.slice(2)
|
|
@@ -171,6 +171,10 @@ function exitWhenParentStdinCloses() {
|
|
|
171
171
|
if (typeof stdin.resume === 'function') stdin.resume()
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
+
async function logQuiet(event) {
|
|
175
|
+
process.stderr.write(`[quiet] ${JSON.stringify(event)}\n`)
|
|
176
|
+
}
|
|
177
|
+
|
|
174
178
|
async function writeEvent(event) {
|
|
175
179
|
await new Promise((resolve, reject) => {
|
|
176
180
|
process.stdout.write(`${JSON.stringify(event)}\n`, error => error ? reject(error) : resolve())
|
|
@@ -197,7 +201,7 @@ async function acceptLatticeState(next) {
|
|
|
197
201
|
state = { ...state, lattice: next, last_event_at: now() }
|
|
198
202
|
saveState(state)
|
|
199
203
|
if (!changed) return false
|
|
200
|
-
await
|
|
204
|
+
await logQuiet({
|
|
201
205
|
schema: 'peertable.parent-watch-event.v1',
|
|
202
206
|
type: 'parent_lattice_update',
|
|
203
207
|
parent,
|
|
@@ -232,7 +236,8 @@ async function acceptMessage(message) {
|
|
|
232
236
|
const matched = addressedToParent(message)
|
|
233
237
|
if (matched) {
|
|
234
238
|
const roomUpdate = message.to === 'all'
|
|
235
|
-
|
|
239
|
+
const emit = parentWatchShouldNotify(message, roomUpdate) ? writeEvent : logQuiet
|
|
240
|
+
await emit({
|
|
236
241
|
schema: 'peertable.parent-watch-event.v1',
|
|
237
242
|
type: roomUpdate ? 'parent_room_update' : 'parent_dm',
|
|
238
243
|
parent,
|
|
@@ -444,3 +444,12 @@ export function subtreeCpuSeconds(psRows, rootPid, { includeChild = () => true,
|
|
|
444
444
|
}
|
|
445
445
|
return total
|
|
446
446
|
}
|
|
447
|
+
|
|
448
|
+
// ---- 親番犬の「起こす価値」判定(2026-08-29 オーナー裁定「番犬が必要な時だけ呼べ」)----
|
|
449
|
+
// 選別は親のターン(有料)でなく機械(無料)が行う。起こすのは親の行動が要るものだけ。
|
|
450
|
+
const ROUTINE_WAIT_DM = /^\s*\[待機\]/u
|
|
451
|
+
export function parentWatchShouldNotify(message, roomUpdate) {
|
|
452
|
+
const body = String(message?.body ?? '')
|
|
453
|
+
if (roomUpdate) return /全タスク完了|\[オーナー宛/u.test(body)
|
|
454
|
+
return !ROUTINE_WAIT_DM.test(body)
|
|
455
|
+
}
|