peertable 0.8.19 → 0.8.21

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.19",
3
+ "version": "0.8.21",
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.19'
16
+ const MCP_VERSION = '0.8.21'
17
17
  const PKG_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
18
18
 
19
19
  const USAGE = `usage:
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs'
3
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js'
4
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
5
+
6
+ const [sessionId, promptFile] = process.argv.slice(2)
7
+ if (!sessionId || !promptFile) throw new Error('SEAT_AITERM_SEND_ARGS_INVALID')
8
+ const prompt = readFileSync(promptFile, 'utf8')
9
+ const client = new Client({ name: 'peertable-seat-send', version: '0.8.21' })
10
+ await client.connect(new StdioClientTransport({ command: 'aiterm-mcp', env: process.env }))
11
+ const result = await client.callTool({
12
+ name: 'pty_send',
13
+ arguments: { session_id: sessionId, text: prompt, enter: true },
14
+ })
15
+ await client.close()
16
+ if (result.isError) throw new Error(result.content?.[0]?.text || 'SEAT_AITERM_SEND_FAILED')
17
+ const receipt = result.structuredContent ?? JSON.parse(result.content?.[0]?.text ?? '{}')
18
+ if (receipt?.schema !== 'aiterm.pty-send-result.v1' || receipt.session_id !== sessionId
19
+ || receipt.mode !== 'agent_dispatch' || receipt.submit_residue === true) {
20
+ throw new Error('SEAT_AITERM_SEND_RECEIPT_INVALID')
21
+ }
22
+ process.stdout.write(`${JSON.stringify(receipt)}\n`)
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ export function keysForClaudeMcpPane(screen) {
6
+ const text = String(screen ?? '')
7
+ if (text.includes('New MCP server found in this project: room')) {
8
+ return { kind: 'single-mcp-consent', keys: ['Down', 'Enter'] }
9
+ }
10
+ if (/new MCP servers found in this project/iu.test(text) && text.includes('Enable selected')) {
11
+ const selected = text.split(/\r?\n/u).filter(line => /\[✔\]/u.test(line))
12
+ if (!selected.some(line => /\broom\b/u.test(line))) return null
13
+ return { kind: 'multi-mcp-consent', keys: [...selected.map(() => 'Down'), 'Enter'] }
14
+ }
15
+ return null
16
+ }
17
+
18
+ const isMain = Boolean(process.argv[1]) && resolve(process.argv[1]) === fileURLToPath(import.meta.url)
19
+ if (isMain) {
20
+ const chunks = []
21
+ for await (const chunk of process.stdin) chunks.push(chunk)
22
+ process.stdout.write(`${JSON.stringify(keysForClaudeMcpPane(Buffer.concat(chunks).toString('utf8')))}\n`)
23
+ }
@@ -474,6 +474,7 @@ codex_update_accepted=false
474
474
  codex_trust_accepted=false
475
475
  claude_trust_accepted=false
476
476
  codex_dialog_helper="$peertable_script_dir/codex-dialog.mjs"
477
+ claude_dialog_helper="$peertable_script_dir/claude-dialog.mjs"
477
478
  pass_codex_pane() {
478
479
  local json key
479
480
  json=$(printf '%s' "$1" | node "$codex_dialog_helper" || true)
@@ -481,6 +482,7 @@ pass_codex_pane() {
481
482
  while IFS= read -r key; do
482
483
  [ -n "$key" ] || continue
483
484
  tmux_at send-keys -t "$sess" "$key" || return 1
485
+ sleep .3
484
486
  done < <(python3 -c 'import json,sys
485
487
  a=json.loads(sys.stdin.read() or "null")
486
488
  print("\n".join(a.get("keys") or []) if isinstance(a, dict) else "")' <<<"$json")
@@ -489,6 +491,18 @@ print("\n".join(a.get("keys") or []) if isinstance(a, dict) else "")' <<<"$json"
489
491
  codex_pane_blocks_ready() {
490
492
  printf '%s' "$1" | node "$codex_dialog_helper" --ready-ok
491
493
  }
494
+ pass_claude_mcp_pane() {
495
+ local json key
496
+ json=$(printf '%s' "$1" | node "$claude_dialog_helper" || true)
497
+ [ -n "$json" ] && [ "$json" != "null" ] || return 1
498
+ while IFS= read -r key; do
499
+ [ -n "$key" ] || continue
500
+ tmux_at send-keys -t "$sess" "$key" || return 1
501
+ done < <(python3 -c 'import json,sys
502
+ a=json.loads(sys.stdin.read() or "null")
503
+ print("\n".join(a.get("keys") or []) if isinstance(a, dict) else "")' <<<"$json")
504
+ return 0
505
+ }
492
506
  while [ $SECONDS -lt "$room_ready_deadline" ]; do
493
507
  # Grok Build は初めて開く作業treeで、room MCPを初期化する前にworkspace trustを尋ねる。
494
508
  # Peertableが正式に着席させるtreeなので、この既知文言だけを一度通す。未知の確認画面を
@@ -551,17 +565,11 @@ while [ $SECONDS -lt "$room_ready_deadline" ]; do
551
565
  # 同意が member 登録より前に出る。選択肢2(this and all future)だけを通す。
552
566
  if [ "$harness" = claude ] && [ "$mcp_consent_accepted" != true ]; then
553
567
  claude_screen=$(tmux_at capture-pane -t "$sess" -p 2>/dev/null || true)
554
- case "$claude_screen" in
555
- *"New MCP server found in this project: room"*)
556
- if ! tmux_at send-keys -t "$sess" Down || ! tmux_at send-keys -t "$sess" Enter; then
557
- echo "SEAT_CLAUDE_MCP_CONSENT_FAILED: room MCP 同意へ応答できない" >&2
558
- exit 1
559
- fi
560
- mcp_consent_accepted=true
561
- room_ready_deadline=$((SECONDS + 90))
562
- echo "claude room MCP consent: accepted"
563
- ;;
564
- esac
568
+ if pass_claude_mcp_pane "$claude_screen"; then
569
+ mcp_consent_accepted=true
570
+ room_ready_deadline=$((SECONDS + 90))
571
+ echo "claude room MCP consent: accepted"
572
+ fi
565
573
  fi
566
574
  room_members=$(curl -sf "$url/api/$room/members" 2>/dev/null || true)
567
575
  if printf '%s' "$room_members" | python3 -c 'import json,sys; name=sys.argv[1]; members=json.load(sys.stdin).get("members",[]); raise SystemExit(0 if any(m.get("name") == name for m in members) else 1)' "$name"; then
@@ -608,6 +616,15 @@ if [ "$brief_dispatched" = true ]; then
608
616
  fi
609
617
 
610
618
  if [ -n "$brief" ] && [ "$brief_dispatched" != true ]; then
619
+ if [ "$brief_in_composer" != true ]; then
620
+ if node "$peertable_script_dir/aiterm-send.mjs" "$sess" "$brief_file" >/dev/null; then
621
+ brief_completed=true
622
+ echo "briefed: $sess(Aiterm pty_send)"
623
+ else
624
+ echo "LAUNCH_BRIEF_SEND_FAILED: Aiterm pty_sendでbriefをdispatchできない" >&2
625
+ exit 1
626
+ fi
627
+ else
611
628
  # Codex はヘッダを描いた後も MCP 初期化を続ける。Aiterm の ready 契約に合わせ、
612
629
  # 同じ可視 pane 内に入力候補行とモデルフッタがある構造を連続して観測する。
613
630
  # hook / MCP warning の更新で画面全体が変わっても、入力欄周辺が ready なら通す。
@@ -675,6 +692,7 @@ if [ -n "$brief" ] && [ "$brief_dispatched" != true ]; then
675
692
  fi
676
693
  brief_completed=true
677
694
  echo "briefed: $sess"
695
+ fi
678
696
  fi
679
697
  fi
680
698