peertable 0.8.22 → 0.8.25

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.
Files changed (56) hide show
  1. package/LICENSE +21 -21
  2. package/README.ja.md +128 -128
  3. package/README.md +164 -164
  4. package/package.json +48 -47
  5. package/room/Dockerfile +7 -7
  6. package/room/client.mjs +17 -6
  7. package/room/message-bounds.mjs +55 -0
  8. package/skill/02_models.snapshot.md +103 -103
  9. package/skill/SKILL.md +265 -265
  10. package/skill/launchd/dev.kitepon.peertable-bridges.plist +20 -20
  11. package/skill/scripts/agent-pane-status.mjs +6 -0
  12. package/skill/scripts/alarm-bridge.mjs +28 -11
  13. package/skill/scripts/alarm-condition.mjs +7 -0
  14. package/skill/scripts/alarm-set.sh +22 -10
  15. package/skill/scripts/alarm-write.mjs +27 -0
  16. package/skill/scripts/archive-room-log.py +0 -0
  17. package/skill/scripts/bridge-record-live.mjs +0 -0
  18. package/skill/scripts/change-effort.sh +0 -0
  19. package/skill/scripts/change-seat.sh +0 -0
  20. package/skill/scripts/claude-dialog.mjs +4 -1
  21. package/skill/scripts/codex-dialog.mjs +18 -5
  22. package/skill/scripts/codex-parent-watch.sh +0 -0
  23. package/skill/scripts/doctor.sh +0 -0
  24. package/skill/scripts/ensure-all-bridges.sh +0 -0
  25. package/skill/scripts/ensure-bridge.sh +19 -6
  26. package/skill/scripts/ensure-codex-room-mcp.mjs +0 -0
  27. package/skill/scripts/ensure-project-runtime.sh +13 -0
  28. package/skill/scripts/ensure-room-mcp.mjs +0 -0
  29. package/skill/scripts/external-pane.mjs +0 -0
  30. package/skill/scripts/grok-seat-config.mjs +47 -0
  31. package/skill/scripts/kickoff-gate.mjs +0 -0
  32. package/skill/scripts/launch-seat.sh +74 -65
  33. package/skill/scripts/leave-seat.sh +0 -0
  34. package/skill/scripts/make-plan-input.mjs +0 -0
  35. package/skill/scripts/parent-join.sh +0 -0
  36. package/skill/scripts/parent-watch.mjs +2 -3
  37. package/skill/scripts/platform/windows/build-bridge-command.mjs +7 -2
  38. package/skill/scripts/resolve-seat-placement.mjs +0 -0
  39. package/skill/scripts/resume.sh +2 -9
  40. package/skill/scripts/runtime-contract.test.mjs +158 -0
  41. package/skill/scripts/runtime-digest.mjs +25 -0
  42. package/skill/scripts/seat-status-bridge.mjs +2 -2
  43. package/skill/scripts/seat-usage.mjs +8 -0
  44. package/skill/scripts/set-mission.sh +0 -0
  45. package/skill/scripts/setup.sh +3 -6
  46. package/skill/scripts/teardown.sh +0 -0
  47. package/skill/scripts/tmux-at.bash +22 -22
  48. package/skill/scripts/upgrade-team-assets.sh +4 -0
  49. package/skill/scripts/vendors/grok/pane-status.mjs +7 -0
  50. package/skill/scripts/wakeup-bridge.mjs +36 -10
  51. package/skill/templates/charter.md +20 -20
  52. package/skill/templates/mcp.json +5 -5
  53. package/skill/templates/member-standalone.md +60 -60
  54. package/skill/templates/member.md +159 -159
  55. package/skill/templates/parent.md +141 -141
  56. package/skill/templates/tasks.md +8 -8
@@ -1,20 +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>
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,6 @@
1
+ #!/usr/bin/env node
2
+ import { classifyPaneTail, paneStatusTail } from './seat-usage.mjs'
3
+
4
+ const chunks = []
5
+ for await (const chunk of process.stdin) chunks.push(chunk)
6
+ process.stdout.write(`${classifyPaneTail(paneStatusTail(Buffer.concat(chunks).toString('utf8')))}\n`)
@@ -21,6 +21,8 @@ import { join } from 'node:path'
21
21
  import { promisify } from 'node:util'
22
22
 
23
23
  import { resolvePostToken } from './seat-usage.mjs'
24
+ import { resolveLatticeExecutable } from './seat-usage.mjs'
25
+ import { latticeTaskAvailable } from './alarm-condition.mjs'
24
26
  import { observePidCommand } from './seat-identity.mjs'
25
27
  import { updateBridgeProgress } from './bridge-record-live.mjs'
26
28
 
@@ -53,6 +55,7 @@ if (flag === '--stop') process.exit(0)
53
55
 
54
56
  const setup = JSON.parse(readFileSync(join(team, 'setup-state.json'), 'utf8'))
55
57
  const { room, server_url: url } = setup
58
+ const lattice = setup.lattice_cli ? resolveLatticeExecutable(setup.lattice_cli) : null
56
59
  const token = resolvePostToken(process.env)
57
60
  if (!token) { console.error('ALARM_BRIDGE_TOKEN_MISSING: 書込トークンが無い'); process.exit(1) }
58
61
  mkdirSync(alarmsDir, { recursive: true })
@@ -86,23 +89,37 @@ async function tick() {
86
89
  if (Date.now() < due) continue
87
90
  let reg
88
91
  try { reg = JSON.parse(readFileSync(file, 'utf8')) } catch { continue } // 書込途中は次周へ
89
- if (typeof reg.seat !== 'string' || typeof reg.script !== 'string' || !reg.seat || !reg.script) {
90
- log(`ALARM_REGISTRATION_INVALID: ${entry}(seat/script が無い)を撤去する`)
92
+ const typed = reg.condition?.type === 'lattice_task_ready'
93
+ const legacy = typeof reg.script === 'string' && reg.script.length > 0
94
+ if (typeof reg.seat !== 'string' || !reg.seat || (!typed && !legacy)) {
95
+ log(`ALARM_REGISTRATION_INVALID: ${entry}(seat/condition が無い)を撤去する`)
91
96
  try { unlinkSync(file) } catch {}
92
97
  continue
93
98
  }
94
99
  nextDue.set(file, Date.now() + Math.max(2, Number(reg.interval_s) || 10) * 1000)
95
100
  let met = false
96
101
  let firedOutput = ''
97
- try {
98
- const { stdout, stderr } = await run('bash', ['-c', reg.script], { timeout: 30_000, maxBuffer: 1024 * 1024 })
99
- met = true
100
- // 発火の証拠を残す: fail-open型の条件は「なぜ成立したか」が出力にしか現れない
101
- // (実被弾 2026-08-26: statusの一過性失敗で即発火したが、何が失敗したか記録が無く未解明)
102
- firedOutput = `${stdout ?? ''}${stderr ?? ''}`.trim().slice(0, 300)
103
- } catch (e) {
104
- // exit 非0 = 条件未成立(正常)。timeout/spawn 失敗はログへ(成立と区別し、握りつぶさない)
105
- if (e.killed || e.code === 'ENOENT') log(`条件スクリプトの実行失敗(${entry}): ${e.message}`)
102
+ if (typed) {
103
+ if (!lattice) {
104
+ log(`ALARM_CONDITION_EVALUATION_FAILED: ${entry} lattice_cli がsetup-stateに無い`)
105
+ } else {
106
+ try {
107
+ const { stdout } = await run(lattice.command, lattice.argv, { timeout: 30_000, maxBuffer: 1024 * 1024, cwd: proj })
108
+ const status = JSON.parse(stdout)
109
+ met = latticeTaskAvailable(status, reg.condition.task_id, reg.condition.plan_key ?? '')
110
+ } catch (e) {
111
+ log(`ALARM_CONDITION_EVALUATION_FAILED: ${entry} ${e.message.split('\n')[0]}`)
112
+ }
113
+ }
114
+ } else {
115
+ try {
116
+ const { stdout, stderr } = await run('bash', ['-c', reg.script], { timeout: 30_000, maxBuffer: 1024 * 1024 })
117
+ met = true
118
+ firedOutput = `${stdout ?? ''}${stderr ?? ''}`.trim().slice(0, 300)
119
+ } catch (e) {
120
+ // exit 非0 = 条件未成立(正常)。timeout/spawn 失敗はログへ(成立と区別し、握りつぶさない)
121
+ if (e.killed || e.code === 'ENOENT') log(`条件スクリプトの実行失敗(${entry}): ${e.message}`)
122
+ }
106
123
  }
107
124
  if (!met) continue
108
125
  try {
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+
3
+ export function latticeTaskAvailable(status, taskId, planKey = '') {
4
+ if (!status || typeof status !== 'object' || typeof taskId !== 'string' || !taskId) return false
5
+ const candidates = [...(status.next_ready ?? []), ...(status.active_set ?? [])]
6
+ return candidates.some(task => task?.task_id === taskId && (!planKey || task?.plan_key === planKey))
7
+ }
@@ -1,14 +1,25 @@
1
1
  #!/bin/bash
2
2
  # 目覚まし係へ待機解放条件を登録する(席が待機に入る前に使う)。
3
- # usage: alarm-set.sh <project_dir> <seat> <note> <script...>
3
+ # usage: alarm-set.sh <project_dir> <seat> <note> --lattice-task-ready <task_id> [--plan <plan_key>]
4
+ # alarm-set.sh <project_dir> <seat> <note> <script...> # 外部条件の後方互換
4
5
  # script は bash -c で実行され、exit 0 で条件成立とみなされる。
5
6
  # **script はシングルクォートで渡すこと**。ダブルクォートだと `$()` や変数が登録側の
6
7
  # shell で先に展開され、判定コマンドでなく展開結果が登録される(かえで実測 2026-08-22)。
7
8
  # 成立すると席へ「[待機解放条件成立] <note>」が配達され、登録は自動で消える。
8
9
  set -eu
9
10
  proj="${1:?project_dir}"; seat="${2:?seat}"; note="${3:?note}"; shift 3
10
- script="$*"
11
- [ -n "$script" ] || { echo "ALARM_SET_SCRIPT_REQUIRED: 条件スクリプトが空" >&2; exit 2; }
11
+ condition_kind=script
12
+ task_id=""; plan_key=""; script=""
13
+ if [ "${1:-}" = "--lattice-task-ready" ]; then
14
+ condition_kind=lattice_task_ready
15
+ task_id="${2:-}"; shift 2
16
+ [ -n "$task_id" ] || { echo "ALARM_SET_TASK_REQUIRED: task_id が空" >&2; exit 2; }
17
+ if [ "${1:-}" = "--plan" ]; then plan_key="${2:-}"; shift 2; fi
18
+ [ "$#" -eq 0 ] || { echo "ALARM_SET_ARGS_INVALID: 余分な引数 $*" >&2; exit 2; }
19
+ else
20
+ script="$*"
21
+ [ -n "$script" ] || { echo "ALARM_SET_SCRIPT_REQUIRED: 条件スクリプトが空" >&2; exit 2; }
22
+ fi
12
23
  # 誤った project path を mkdir -p が幻のツリーとして実体化し、誰も読まない場所へ exit 0 で
13
24
  # 登録が「成功」する(実被弾 2026-08-28: worktree cwd からの相対 'poly' が poly/poly/.team/ を
14
25
  # 生み、席は登録済みと思って15時間停滞)。目覚まし係が読むのは既存卓の .team だけなので、
@@ -18,11 +29,12 @@ proj="$(cd -- "$proj" 2>/dev/null && pwd)" || { echo "ALARM_SET_PROJECT_INVALID:
18
29
  dir="$proj/.team/alarms"
19
30
  mkdir -p "$dir"
20
31
  id="$(date +%s)-$$"
21
- python3 - "$dir/$id.json" "$seat" "$note" "$script" <<'PY'
22
- import json, sys
23
- out, seat, note, script = sys.argv[1:5]
24
- with open(out, 'w') as f:
25
- json.dump({'seat': seat, 'note': note, 'script': script, 'interval_s': 10,
26
- 'created_at': __import__('datetime').datetime.utcnow().isoformat() + 'Z'}, f, ensure_ascii=False)
27
- PY
32
+ script_dir=$(cd "$(dirname "$0")" && pwd -P)
33
+ if [ "$condition_kind" = lattice_task_ready ]; then
34
+ printf '%s\0%s\0%s\0%s\0%s' "$condition_kind" "$seat" "$note" "$task_id" "$plan_key" \
35
+ | node "$script_dir/alarm-write.mjs" "$dir/$id.json"
36
+ else
37
+ printf '%s\0%s\0%s\0%s' "$condition_kind" "$seat" "$note" "$script" \
38
+ | node "$script_dir/alarm-write.mjs" "$dir/$id.json"
39
+ fi
28
40
  echo "ALARM_SET_OK: ${dir}/${id}.json(seat=${seat} note=${note})"
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ // Git Bash→Windows native processのargv文字コード変換を通さず、UTF-8 stdinでalarmを保存する。
3
+ import { writeFileSync } from 'node:fs'
4
+
5
+ const out = process.argv[2]
6
+ if (!out) throw new Error('ALARM_WRITE_PATH_REQUIRED')
7
+
8
+ const chunks = []
9
+ for await (const chunk of process.stdin) chunks.push(chunk)
10
+ const fields = Buffer.concat(chunks).toString('utf8').split('\0')
11
+ const kind = fields.shift()
12
+ let payload
13
+ if (kind === 'script' && fields.length === 3 && fields.every(value => value.length > 0)) {
14
+ const [seat, note, script] = fields
15
+ payload = { seat, note, script }
16
+ } else if (kind === 'lattice_task_ready' && fields.length === 4
17
+ && fields.slice(0, 3).every(value => value.length > 0)) {
18
+ const [seat, note, task_id, plan_key] = fields
19
+ payload = { seat, note, condition: { type: 'lattice_task_ready', task_id, ...(plan_key ? { plan_key } : {}) } }
20
+ } else {
21
+ throw new Error('ALARM_WRITE_PAYLOAD_INVALID')
22
+ }
23
+ writeFileSync(out, `${JSON.stringify({
24
+ ...payload,
25
+ interval_s: 10,
26
+ created_at: new Date().toISOString(),
27
+ })}\n`, { encoding: 'utf8', mode: 0o600 })
File without changes
File without changes
File without changes
File without changes
@@ -19,5 +19,8 @@ const isMain = Boolean(process.argv[1]) && resolve(process.argv[1]) === fileURLT
19
19
  if (isMain) {
20
20
  const chunks = []
21
21
  for await (const chunk of process.stdin) chunks.push(chunk)
22
- process.stdout.write(`${JSON.stringify(keysForClaudeMcpPane(Buffer.concat(chunks).toString('utf8')))}\n`)
22
+ const action = keysForClaudeMcpPane(Buffer.concat(chunks).toString('utf8'))
23
+ if (process.argv.includes('--keys')) {
24
+ if (action) process.stdout.write(`${action.keys.join('\n')}\n`)
25
+ } else process.stdout.write(`${JSON.stringify(action)}\n`)
23
26
  }
@@ -10,19 +10,28 @@ export const COMMAND_APPROVAL_DONT_ASK = "don't ask again"
10
10
 
11
11
  export function keysForCodexPane(screen) {
12
12
  const text = String(screen ?? '')
13
- if (text.includes(MCP_ALLOW_NEEDLE) && text.includes(MCP_ALWAYS_ALLOW)) {
13
+ const lines = text.split(/\r?\n/u).slice(-24)
14
+ const tail = lines.join('\n')
15
+ // scrollbackに残った古いdialogより、現在のbusy表示/通常composerを優先する。
16
+ // active dialog自身の選択肢行(`› 1.`)は通常composerとして数えない。
17
+ const busy = /esc to interrupt/iu.test(tail)
18
+ const idleComposer = lines.some(line => /^\s*›(?:\s*$|\s+(?!\d+\.))/u.test(line))
19
+ && lines.some(line => /gpt-[\w.-]+/iu.test(line) && line.includes('·'))
20
+ if (busy || idleComposer) return null
21
+ const dialogText = tail
22
+ if (dialogText.includes(MCP_ALLOW_NEEDLE) && dialogText.includes(MCP_ALWAYS_ALLOW)) {
14
23
  return { kind: 'mcp-allow', keys: ['Down', 'Down', 'Enter'] }
15
24
  }
16
- if (text.includes(COMMAND_APPROVAL_NEEDLE) && text.toLowerCase().includes(COMMAND_APPROVAL_DONT_ASK)) {
25
+ if (dialogText.includes(COMMAND_APPROVAL_NEEDLE) && dialogText.toLowerCase().includes(COMMAND_APPROVAL_DONT_ASK)) {
17
26
  return { kind: 'command-approval', keys: ['Down', 'Enter'] }
18
27
  }
19
- if (text.includes('Hooks need review')) {
28
+ if (dialogText.includes('Hooks need review')) {
20
29
  return { kind: 'hooks', keys: ['Down', 'Enter'] }
21
30
  }
22
- if (text.includes('Update now')) {
31
+ if (dialogText.includes('Update now')) {
23
32
  return { kind: 'update', keys: ['Down', 'Enter'] }
24
33
  }
25
- if (text.includes('Yes, continue') && text.includes('Do you trust the contents of this directory')) {
34
+ if (dialogText.includes('Yes, continue') && dialogText.includes('Do you trust the contents of this directory')) {
26
35
  return { kind: 'trust', keys: ['Enter'] }
27
36
  }
28
37
  return null
@@ -42,5 +51,9 @@ if (isMain) {
42
51
  if (process.argv.includes('--ready-ok')) {
43
52
  process.exit(blocksCodexReady(screen) ? 2 : 0)
44
53
  }
54
+ if (process.argv.includes('--keys')) {
55
+ if (action) process.stdout.write(`${action.keys.join('\n')}\n`)
56
+ process.exit(0)
57
+ }
45
58
  process.stdout.write(`${JSON.stringify(action)}\n`)
46
59
  }
File without changes
File without changes
File without changes
@@ -4,6 +4,10 @@ set -euo pipefail
4
4
 
5
5
  proj="$1"; name="$2"; shift 2
6
6
  case "$name" in seat-status) script="seat-status-bridge.mjs" ;; wakeup) script="wakeup-bridge.mjs" ;; alarm) script="alarm-bridge.mjs" ;; run) echo "ENSURE_BRIDGE_RETIRED: run-bridge は退役した(2026-08-22)。介入は席が自分の Lattice コマンド応答で受け取る" >&2; exit 1 ;; *) echo "usage: ensure-bridge.sh <project> <seat-status|wakeup|alarm> [args...]" >&2; exit 1 ;; esac
7
+ scripts_dir=$(cd "$(dirname "$0")" && pwd -P)
8
+ repo=$(cd "$scripts_dir/../.." && pwd -P)
9
+ peertable_version=$(node -p "require(process.argv[1]).version" "$repo/package.json")
10
+ peertable_runtime_digest=$(node "$scripts_dir/runtime-digest.mjs")
7
11
  team="$proj/.team"; record="$team/$name-bridge.json"; log="$team/$name-bridge.log"
8
12
  force=false
9
13
  if [ "${1:-}" = "--force" ]; then force=true; shift; fi
@@ -17,7 +21,16 @@ if [ $# -eq 0 ] && [ -f "$record" ]; then
17
21
  fi
18
22
  fi
19
23
  if [ -f "$record" ]; then
20
- if node "$(dirname "$0")/bridge-record-live.mjs" "$record"; then exit 0; fi
24
+ if node "$scripts_dir/bridge-record-live.mjs" "$record"; then
25
+ record_version=$(node -e 'const x=require(process.argv[1]);process.stdout.write(x.peertable_version||"")' "$record")
26
+ record_digest=$(node -e 'const x=require(process.argv[1]);process.stdout.write(x.peertable_runtime_digest||"")' "$record")
27
+ if [ "$record_version" = "$peertable_version" ] && [ "$record_digest" = "$peertable_runtime_digest" ]; then exit 0; fi
28
+ echo "${name}-bridge: Peertable ${record_version:-unknown} → ${peertable_version} の現行runtimeへ自動更新する" >&2
29
+ node "$scripts_dir/$script" "$proj" --stop || {
30
+ echo "${name}-bridge: 旧版bridgeを停止できないため更新を中止する" >&2
31
+ exit 1
32
+ }
33
+ fi
21
34
  if ! "$force" && grep -q 'WRITE_DENIED' "$log" 2>/dev/null; then echo "${name}-bridge: 前回はWRITE_DENIEDで終了。--forceを指定すること" >&2; exit 1; fi
22
35
  fi
23
36
  # shellcheck disable=SC1091
@@ -49,8 +62,8 @@ if [ "$(node -p 'process.platform')" = "win32" ]; then
49
62
  # psmuxが起動する正規shellはPowerShell 7。POSIXの`env`とMSYS pathを文字列のまま
50
63
  # 渡すとnodeへ到達する前に終了するため、Windows nodeに各pathをargvとして変換させ、
51
64
  # PowerShell EncodedCommandを一箇所で組み立てる。
52
- win_command=$(node "$(dirname "$0")/platform/windows/build-bridge-command.mjs" \
53
- "$(dirname "$0")/$script" "$proj" "$log" "$@")
65
+ win_command=$(node "$scripts_dir/platform/windows/build-bridge-command.mjs" \
66
+ "$scripts_dir/$script" "$proj" "$log" "$@")
54
67
  tmux_at new-session -d -s "$session" "$win_command"
55
68
  else
56
69
  tmux_at new-session -d -s "$session" "env${env_prefix} node $(dirname "$0")/$script $(printf '%q ' "$proj" "$@") >> $(printf '%q' "$log") 2>&1"
@@ -60,10 +73,10 @@ for _ in $(seq 1 30); do
60
73
  # **末尾は本物の改行にする。** `"\\n"` と書くと JS がリテラルの `\`+`n` を足し、
61
74
  # record が JSON として壊れる——`--stop` も次回起動も `JSON.parse` で落ちて、
62
75
  # 「止められない・建て直せない常駐」ができる(2026-08-11 実測)。一時 file→rename で原子的に。
63
- node -e 'const fs=require("fs");const p=process.argv[1],a=process.argv.slice(2);
76
+ node -e 'const fs=require("fs");const p=process.argv[1],version=process.argv[2],digest=process.argv[3],a=process.argv.slice(4);
64
77
  const t=`${p}.${process.pid}.tmp`;
65
- fs.writeFileSync(t,JSON.stringify({...JSON.parse(fs.readFileSync(p,"utf8")),args:a})+"\n");
66
- fs.renameSync(t,p)' "$record" "$@"
78
+ fs.writeFileSync(t,JSON.stringify({...JSON.parse(fs.readFileSync(p,"utf8")),peertable_version:version,peertable_runtime_digest:digest,args:a})+"\n");
79
+ fs.renameSync(t,p)' "$record" "$peertable_version" "$peertable_runtime_digest" "$@"
67
80
  exit 0
68
81
  fi
69
82
  sleep .5
File without changes
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+ # 円卓runtimeを一回の呼出しで現行Peertable版へ収束させる正規入口。
3
+ set -euo pipefail
4
+
5
+ proj="${1:-}"
6
+ [ -n "$proj" ] || { echo "usage: ensure-project-runtime.sh <project_dir>" >&2; exit 2; }
7
+ scripts=$(cd "$(dirname "$0")" && pwd -P)
8
+
9
+ for kind in alarm seat-status wakeup; do
10
+ "$scripts/ensure-bridge.sh" "$proj" "$kind"
11
+ done
12
+
13
+ echo "peertable runtime ready: alarm / seat-status / wakeup"
File without changes
File without changes
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ // Peertable席のGrokをroom専用にする。GROK_HOMEだけではClaude/Cursor互換源を読むため、
3
+ // compatを明示OFFにし、project .mcp.jsonのroom以外もnative高優先設定でdisableする。
4
+ import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
5
+ import { dirname, join, resolve } from 'node:path'
6
+
7
+ const [projectArg, outputArg] = process.argv.slice(2)
8
+ if (!projectArg || !outputArg) throw new Error('GROK_SEAT_CONFIG_ARGS_INVALID')
9
+ const project = resolve(projectArg)
10
+ const output = resolve(outputArg)
11
+ const rootMcp = JSON.parse(readFileSync(join(project, '.mcp.json'), 'utf8'))
12
+ const servers = rootMcp?.mcpServers
13
+ if (!servers || typeof servers !== 'object' || Array.isArray(servers) || !servers.room) {
14
+ throw new Error('GROK_SEAT_ROOM_MCP_MISSING')
15
+ }
16
+
17
+ const disabled = Object.keys(servers).filter(name => name !== 'room').sort((a, b) => a.localeCompare(b, 'en'))
18
+ const lines = [
19
+ `disabled_mcp_servers = ${JSON.stringify(disabled)}`,
20
+ '',
21
+ '[ui]',
22
+ 'permission_mode = "always-approve"',
23
+ '',
24
+ '[compat.claude]',
25
+ 'skills = false',
26
+ 'rules = false',
27
+ 'agents = false',
28
+ 'mcps = false',
29
+ 'hooks = false',
30
+ 'sessions = false',
31
+ '',
32
+ '[compat.cursor]',
33
+ 'skills = false',
34
+ 'rules = false',
35
+ 'agents = false',
36
+ 'mcps = false',
37
+ 'hooks = false',
38
+ 'sessions = false',
39
+ '',
40
+ '[compat.codex]',
41
+ 'sessions = false',
42
+ ]
43
+ mkdirSync(dirname(output), { recursive: true, mode: 0o700 })
44
+ const temp = `${output}.${process.pid}.tmp`
45
+ writeFileSync(temp, `${lines.join('\n')}\n`, { encoding: 'utf8', mode: 0o600 })
46
+ chmodSync(temp, 0o600)
47
+ renameSync(temp, output)
File without changes