peertable 0.8.55 → 0.8.57

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 (75) hide show
  1. package/README.ja.md +32 -33
  2. package/README.md +36 -37
  3. package/package.json +3 -1
  4. package/room/client.mjs +24 -33
  5. package/room/server.mjs +1 -1
  6. package/skill/SKILL.md +69 -94
  7. package/skill/scripts/aiterm-client.mjs +48 -0
  8. package/skill/scripts/aiterm-deliver.mjs +3 -1
  9. package/skill/scripts/change-seat.mjs +86 -0
  10. package/skill/scripts/change-seat.sh +3 -276
  11. package/skill/scripts/change-seat.test.mjs +129 -0
  12. package/skill/scripts/cli.mjs +77 -0
  13. package/skill/scripts/doctor.mjs +53 -0
  14. package/skill/scripts/doctor.sh +2 -213
  15. package/skill/scripts/ensure-all-bridges.mjs +42 -0
  16. package/skill/scripts/ensure-all-bridges.sh +3 -80
  17. package/skill/scripts/ensure-bridge.mjs +11 -0
  18. package/skill/scripts/ensure-bridge.sh +2 -95
  19. package/skill/scripts/ensure-codex-room-mcp.mjs +1 -1
  20. package/skill/scripts/ensure-project-runtime.mjs +85 -0
  21. package/skill/scripts/ensure-project-runtime.sh +2 -11
  22. package/skill/scripts/ensure-project-runtime.test.mjs +40 -0
  23. package/skill/scripts/install-skill.mjs +89 -0
  24. package/skill/scripts/install-skill.test.mjs +84 -0
  25. package/skill/scripts/launch-seat.mjs +163 -0
  26. package/skill/scripts/launch-seat.sh +3 -797
  27. package/skill/scripts/launch-seat.test.mjs +174 -0
  28. package/skill/scripts/leave-seat.mjs +44 -0
  29. package/skill/scripts/leave-seat.sh +3 -92
  30. package/skill/scripts/leave-seat.test.mjs +64 -0
  31. package/skill/scripts/legacy-entry.mjs +33 -0
  32. package/skill/scripts/platform/windows/resolve-lattice-command.mjs +12 -1
  33. package/skill/scripts/project-runtime.mjs +95 -0
  34. package/skill/scripts/project-runtime.test.mjs +29 -0
  35. package/skill/scripts/project-scaffold.mjs +179 -0
  36. package/skill/scripts/project-scaffold.test.mjs +129 -0
  37. package/skill/scripts/refresh-seat-identity.mjs +11 -8
  38. package/skill/scripts/remove-managed-room-mcp.mjs +9 -4
  39. package/skill/scripts/resume.sh +2 -225
  40. package/skill/scripts/room-api.mjs +28 -0
  41. package/skill/scripts/room-public-session.test.mjs +73 -0
  42. package/skill/scripts/runtime-contract.test.mjs +13 -146
  43. package/skill/scripts/runtime-launch-command.mjs +19 -0
  44. package/skill/scripts/runtime-launch-command.test.mjs +32 -0
  45. package/skill/scripts/seat-approval.mjs +24 -0
  46. package/skill/scripts/seat-approval.test.mjs +43 -0
  47. package/skill/scripts/seat-identity.mjs +2 -71
  48. package/skill/scripts/seat-launch-phase.mjs +11 -0
  49. package/skill/scripts/seat-observer.mjs +54 -0
  50. package/skill/scripts/seat-observer.test.mjs +58 -0
  51. package/skill/scripts/seat-session.mjs +17 -0
  52. package/skill/scripts/seat-status-bridge.mjs +33 -206
  53. package/skill/scripts/seat-usage.mjs +3 -334
  54. package/skill/scripts/setup.sh +3 -215
  55. package/skill/scripts/teardown.mjs +113 -0
  56. package/skill/scripts/teardown.sh +3 -410
  57. package/skill/scripts/teardown.test.mjs +62 -0
  58. package/skill/scripts/upgrade-team-assets.mjs +268 -0
  59. package/skill/scripts/upgrade-team-assets.sh +2 -282
  60. package/skill/scripts/wakeup-bridge.mjs +26 -32
  61. package/skill/scripts/wakeup-delivery.mjs +4 -14
  62. package/skill/templates/charter.md +1 -1
  63. package/skill/templates/member-standalone.md +1 -1
  64. package/skill/templates/member.md +3 -3
  65. package/skill/scripts/agent-pane-status.mjs +0 -6
  66. package/skill/scripts/aiterm-configure.mjs +0 -31
  67. package/skill/scripts/aiterm-launch.mjs +0 -64
  68. package/skill/scripts/aiterm-send.mjs +0 -22
  69. package/skill/scripts/claude-dialog.mjs +0 -26
  70. package/skill/scripts/codex-dialog.mjs +0 -67
  71. package/skill/scripts/platform/windows/build-bridge-command.mjs +0 -52
  72. package/skill/scripts/seat-input.mjs +0 -36
  73. package/skill/scripts/tmux-at.bash +0 -22
  74. package/skill/scripts/tmux-socket.mjs +0 -19
  75. package/skill/scripts/vendors/grok/pane-status.mjs +0 -30
@@ -1,4 +1,4 @@
1
- import { classifyPaneTail } from './seat-usage.mjs'
1
+ import { seatSessionId } from './seat-session.mjs'
2
2
 
3
3
  export const BROADCAST_RECIPIENT = 'all'
4
4
  export const ROOM_UPDATE_FALLBACK =
@@ -36,13 +36,7 @@ export function isWakeupBridgeTarget(member, options = {}) {
36
36
  // Stop 回収が要るので、aiterm-deliver.mjs が receipt の wait_process を切り離して起動する。
37
37
  const parentName = options.parentName
38
38
  if (typeof parentName === 'string' && parentName.length > 0 && member.name === parentName) return false
39
- const observe = member.observe
40
- const hasPane = Boolean(
41
- observe
42
- && typeof observe === 'object'
43
- && typeof observe.tmux_target === 'string'
44
- && observe.tmux_target,
45
- )
39
+ const hasPane = Boolean(seatSessionId(member))
46
40
  if (hasPane) return true
47
41
  const harness = memberHarness(member)
48
42
  return harness === 'codex' || harness === 'grok'
@@ -70,10 +64,6 @@ export function isIdleSelfWake(msg) {
70
64
  }
71
65
 
72
66
  /** Grok 既定はキュー投入。busy 中に積むと今のターンへ混ざらない。 */
73
- export function shouldDeferGrokWake(harness, tail) {
74
- if (harness !== 'grok') return false
75
- if (typeof tail !== 'string') return false
76
- if (classifyPaneTail(tail) === 'busy') return true
77
- if (tail.includes('send a message to interrupt')) return true
78
- return tail.includes('Enter:send now') && /#\d+\s+\[/u.test(tail)
67
+ export function shouldDeferGrokWake(harness, state) {
68
+ return harness === 'grok' && state === 'busy'
79
69
  }
@@ -6,7 +6,7 @@
6
6
  2. room の宛先規則はこれだけである。claim は `post(to: "all")`、次にやる仕事があるターン終了時の次の行動は `post(to: "<自分の名前>")`(message は `[次の行動]` で始める。仕事があるのに出さないと席は止まる)、工程完了は `post(to: "all")`、誰かへの用事は `post(to: "<相手の名前>")` で送る。誰に聞けばよいか分からないことは `post(to: "all")` で聞く。実装・監査・readyが本当に無い時の待機宣言は最終手段として親だけへDMし、`all`へ送らず、そのあと自分へ `[次の行動]` を送らない。別の通知機構は使わない
7
7
  3. **仕事を選ぶのも始めるのも自分である。** どのモードでも、`read_log`で先行claimを確認してから `post(to: "all", message: "[claim] <タスク>")` を送る。これが唯一の着手通知であり、別の着手通知は送らない。**装置から仕事が降ってくることはないし、着手前に装置の許可を待つこともない**(オーナー裁定 2026-08-09)。Lattice の実行層を使う卓では、その後 `todo start` し、自分で `run intake` して隔離 worktree を受け取る——それは設備の供給であって許可証ではなく、装置が返すのは競合した時の「留まれ」だけである(タスクの呼び名は Lattice 併用モードなら task_id、単独円卓モードなら `.team/tasks.md` の議題名)。同じタスクへの先行 claim があれば取り下げるか `[join] <タスク>` へ切り替える
8
8
  4. 分からないことは room で聞く。台帳はない。自分の変更が他の部位に影響するなら、聞かれる前に影響を受けるメンバーを明示宛先にして通知する
9
- 5. 判断は情報を持つ者がする。タスクは席ではなく現場。合流(join)は歓迎される。詰まった仲間には目を貸す。親が円卓メンバーを増やす時は、正式な`launch-seat.sh`でAiterm長寿命席として着任させ、native sub-agent・Task・Agentを円卓席の代用にしない。正式着席したメンバーはnative sub-agent、Aiterm外部agent、相談agent、自己実装を自由に選べる。子は自動的に円卓メンバーではなく、工程所有・統合・room報告は着席メンバーが保持する。親は二次委譲の手段を禁止・指定しない。視点が足りない・詰んでいるなら room で報告して援軍(join)を求める
9
+ 5. 判断は情報を持つ者がする。タスクは席ではなく現場。合流(join)は歓迎される。詰まった仲間には目を貸す。親が円卓メンバーを増やす時は、正式な`peertable launch`でAiterm長寿命席として着任させ、native sub-agent・Task・Agentを円卓席の代用にしない。正式着席したメンバーはnative sub-agent、Aiterm外部agent、相談agent、自己実装を自由に選べる。子は自動的に円卓メンバーではなく、工程所有・統合・room報告は着席メンバーが保持する。親は二次委譲の手段を禁止・指定しない。視点が足りない・詰んでいるなら room で報告して援軍(join)を求める
10
10
  6. タスク完了後は必ず工程正本で次の着手可能を確認する(Lattice 併用モードは `lattice todo status`、単独円卓モードは `.team/tasks.md` と room ログの照合)。残っていれば claim へ戻る。全タスクが終わっていれば `post(to: "all")` で「全タスク完了」を記録する
11
11
  7. 役割逸脱は誰であれ指摘する。これは無礼ではなく義務である
12
12
  8. **親の発言は拘束力を持たない。** 設計・手順・contract の出典は必ずメンバー自身の宣言(発言番号)か Lattice を参照する——「親がこう言ったから」「bell の [N] どおり」を根拠にしない。親が何かを再掲しても正本はメンバーの元発言のまま動かない。親の差し戻しは異議として扱い、反論してよい
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## Peertableの正規席と委譲入口
6
6
 
7
- このprojectの円卓メンバーは、親が`skill/scripts/launch-seat.sh`で着席させたAiterm長寿命外部PTYである。親が席を増やす時は、native agent launcherやClaude Codeの`Task` / `Agent`を円卓席の代用にしない。席間の分担は同じroom(`post` / `read_unread` / `read_log`)と、このモードの工程正本で行い、shell操作用の短命なPTYと、メンバーが長寿命で着席するPTYを混同しない。既存席を読む・起こす入口はaitermの`pty_read` / `pty_send` / `pty_key`である。
7
+ このprojectの円卓メンバーは、親が`peertable launch`で着席させたAiterm長寿命外部PTYである。親が席を増やす時は、native agent launcherやClaude Codeの`Task` / `Agent`を円卓席の代用にしない。席間の分担は同じroom(`post` / `read_unread` / `read_log`)と、このモードの工程正本で行い、shell操作用の短命なPTYと、メンバーが長寿命で着席するPTYを混同しない。既存席を読む・起こす入口はaitermの`pty_observe` / `pty_read` / `pty_send`である。
8
8
 
9
9
  正式着席したメンバーは、工程遂行に必要なnative sub-agent、Aiterm外部agent、相談agent、自己実装を自由に選べる。親は二次委譲の手段を禁止・指定しない。メンバーが呼んだ子は自動的に円卓メンバーにはならず、工程所有・統合・room報告はこの着席メンバーが保持する。
10
10
 
@@ -4,11 +4,11 @@
4
4
 
5
5
  ## Peertableの正規席と委譲入口
6
6
 
7
- このprojectの円卓メンバーは、親が`skill/scripts/launch-seat.sh`で着席させたAiterm長寿命外部PTYである。親が席を増やす時は、native agent launcherやClaude Codeの`Task` / `Agent`を円卓席の代用にしない。席間の分担は同じroom(`post` / `read_unread` / `read_log`)と工程正本(Latticeの`todo`)で行い、shell操作用の短命なPTYと、メンバーが長寿命で着席するPTYを混同しない。既存席を読む・起こす入口はaitermの`pty_read` / `pty_send` / `pty_key`である。
7
+ このprojectの円卓メンバーは、親が`peertable launch`で着席させたAiterm長寿命外部PTYである。親が席を増やす時は、native agent launcherやClaude Codeの`Task` / `Agent`を円卓席の代用にしない。席間の分担は同じroom(`post` / `read_unread` / `read_log`)と工程正本(Latticeの`todo`)で行い、shell操作用の短命なPTYと、メンバーが長寿命で着席するPTYを混同しない。既存席を読む・起こす入口はaitermの`pty_observe` / `pty_read` / `pty_send`である。
8
8
 
9
9
  正式着席したメンバーは、工程遂行に必要なnative sub-agent、Aiterm外部agent、相談agent、自己実装を自由に選べる。親は二次委譲の手段を禁止・指定しない。メンバーが呼んだ子は自動的に円卓メンバーにはならず、工程所有・統合・room報告はこの着席メンバーが保持する。
10
10
 
11
- `PEERTABLE_MEMBER` を継承した環境から `launch-seat.sh` を呼ぶと、`SEAT_LAUNCH_DELEGATED_CHILD_FORBIDDEN` で副作用より前に拒否される。親による正式増員は `PEERTABLE_MEMBER` の無い入口から既存手順で行う。
11
+ `PEERTABLE_MEMBER` を継承した環境から `peertable launch` を呼ぶと、`SEAT_LAUNCH_DELEGATED_CHILD_FORBIDDEN` で副作用より前に拒否される。親による正式増員は `PEERTABLE_MEMBER` の無い入口から既存手順で行う。
12
12
 
13
13
  ## 作業ループ
14
14
 
@@ -21,7 +21,7 @@
21
21
 
22
22
  **kickoff・名指しの依頼DMには、まず `[引受] <要旨>` を room へ返してから着手する(決定104)。** 親はこの引受発言と配送 receipt が揃うまで依頼を未着手として扱う——黙って作業を始めると、依頼が届いていないと判定され再送・再依頼が起きる。
23
23
 
24
- **常駐・長時間ジョブはtmuxセッションで走らせる。名前は自由(2026-08-26 オーナー裁定で命名規約は廃止)。** あなたのシェルからセッションを作れば、環境変数 `PEERTABLE_MEMBER` がOSの継承でセッションへ自動で刻まれ、観測装置がそれを読んで稼働をあなたのアクティブランプへ合成する(jobが動いていれば点滅)。`nohup`等でtmuxの外へ切り離すと観測不能になるので使わない。複数ジョブの並走は自由。ジョブ終了の目覚まし登録は従来どおり行う。
24
+ **常駐・長時間ジョブはAitermの公開PTYで走らせる。名前は自由。** `pty_open`の`env_vars`に`PEERTABLE_MEMBER`と`PEERTABLE_ROOM`を指定して、この席の預け仕事であることを伝える。観測装置は公開APIから同じroomの活動を読み、席のアクティブランプへ合成する。観測できない場所へ切り離さず、複数ジョブの並走は自由。ジョブ終了の目覚まし登録は従来どおり行う。
25
25
 
26
26
  **探索順は active → ready → 待機である。** まず自分の active 工程を完了させる。無ければ全PLAN横断の ready を自律的に claim する。実装も監査担当としての提出待ちも無い時だけ、最終手段として `[待機] ...` を親(bell 等、その卓の親名)だけへDMする。待機を `to: "all"` へ投稿しない。待機のあと自分へ `[次の行動]` を送らない。次に行う作業があるターンだけ自己DMする。空の終了通知は使わない。
27
27
 
@@ -1,6 +0,0 @@
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`)
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Client } from '@modelcontextprotocol/sdk/client/index.js'
3
- import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
4
-
5
- const [session_id, ...args] = process.argv.slice(2)
6
- let model = null
7
- let reasoning_effort = null
8
- for (let i = 0; i < args.length; i += 2) {
9
- if (args[i] === '--model') model = args[i + 1] ?? null
10
- else if (args[i] === '--effort') reasoning_effort = args[i + 1] ?? null
11
- else throw new Error('SEAT_AITERM_CONFIGURE_ARGS_INVALID')
12
- }
13
- if (!session_id || (!model && !reasoning_effort)) {
14
- throw new Error('SEAT_AITERM_CONFIGURE_ARGS_INVALID')
15
- }
16
- // Aiterm のmanaged session metadataは実行時state root(macOSではTMPDIR)にある。
17
- // stdio transportの既定環境は親のTMPDIRを落とすため、同じ公開serverへ明示継承する。
18
- const transport = new StdioClientTransport({ command: 'aiterm-mcp', env: process.env })
19
- const client = new Client({ name: 'peertable-seat-configure', version: '0.4.12' })
20
- await client.connect(transport)
21
- const result = await client.callTool({
22
- name: 'agent_configure',
23
- arguments: { session_id, ...(model ? { model } : {}), ...(reasoning_effort ? { reasoning_effort } : {}) },
24
- })
25
- await client.close()
26
- if (result.isError) throw new Error(result.content?.[0]?.text ?? 'SEAT_AITERM_CONFIGURE_FAILED')
27
- const receipt = result.structuredContent ?? JSON.parse(result.content?.[0]?.text ?? '{}')
28
- if (receipt?.schema !== 'aiterm.agent-configure-result.v1' || receipt.session_id !== session_id) {
29
- throw new Error('SEAT_AITERM_CONFIGURE_RECEIPT_INVALID')
30
- }
31
- console.log(JSON.stringify(receipt))
@@ -1,64 +0,0 @@
1
- #!/usr/bin/env node
2
- // Peertable の正規席を Aiterm の公開 agent launcher から起動する境界。
3
- import { Client } from '@modelcontextprotocol/sdk/client/index.js'
4
- import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'
5
-
6
- const [session_name, harness, model, reasoning_effort, cwd, prompt = ''] = process.argv.slice(2)
7
- if (!session_name || !['claude', 'codex', 'grok'].includes(harness) || !model || !reasoning_effort || !cwd) {
8
- throw new Error('SEAT_AITERM_LAUNCH_ARGS_INVALID')
9
- }
10
-
11
- const client = new Client({ name: 'peertable-seat-launch', version: '0.4.12' })
12
- await client.connect(new StdioClientTransport({ command: 'aiterm-mcp', env: process.env }))
13
- const env_vars = [
14
- 'PEERTABLE_URL', 'PEERTABLE_ROOM', 'PEERTABLE_MEMBER', 'PEERTABLE_CREDENTIAL_FILE',
15
- 'PEERTABLE_HARNESS', 'PEERTABLE_VENDOR', 'PEERTABLE_MODEL', 'PEERTABLE_EFFORT', 'PEERTABLE_ROLE',
16
- 'PEERTABLE_ROLES', 'PEERTABLE_MISSION',
17
- 'PEERTABLE_TMUX_SOCKET',
18
- ]
19
- if (process.env.PEERTABLE_PLAN) env_vars.push(
20
- 'PEERTABLE_PLAN', 'LATTICE_CLI', 'LATTICE_TODO_ACTOR_HOST',
21
- 'LATTICE_TODO_ACTOR_SESSION', 'LATTICE_TODO_ACTOR_AGENT',
22
- )
23
- if (harness === 'grok') {
24
- if (process.env.GROK_HOME) env_vars.push('GROK_HOME')
25
- env_vars.push('GROK_PRIVACY_NOTICE_ROLLOUT')
26
- }
27
- if (harness === 'codex' && process.env.CODEX_HOME) env_vars.push('CODEX_HOME')
28
- const result = await client.callTool({
29
- name: `${harness}_agent`,
30
- arguments: {
31
- session_name,
32
- cwd,
33
- model,
34
- reasoning_effort,
35
- env_vars,
36
- ...(prompt ? { prompt } : {}),
37
- },
38
- })
39
- await client.close()
40
- if (result.isError) {
41
- const text = result.content?.[0]?.text ?? ''
42
- // aiterm v0.29.0からready gate失敗(起動時ダイアログ等でprompt未送信)は明示エラーになった。
43
- // Peertableの席起動は「未送信→ダイアログ自動通過→着任指示を後段で貼る」を正規手順として
44
- // 持っているので、sessionが残るこの失敗だけはnot_sent受領として続行する。
45
- if (text.includes('initial_prompt=not_sent') && text.includes(`session_id: ${session_name}`)) {
46
- console.log(JSON.stringify({
47
- schema: 'aiterm.agent-launch-result.v1',
48
- provider: harness,
49
- harness: `${harness === 'claude' ? 'claude-code' : `${harness}-cli`}`,
50
- session_id: session_name,
51
- managed_completion: true,
52
- event_cursor: null,
53
- wait_command: null,
54
- submit_residue: null,
55
- }))
56
- process.exit(0)
57
- }
58
- throw new Error(text || 'SEAT_AITERM_LAUNCH_FAILED')
59
- }
60
- const receipt = result.structuredContent ?? JSON.parse(result.content?.[0]?.text ?? '{}')
61
- if (receipt?.schema !== 'aiterm.agent-launch-result.v1' || receipt.provider !== harness || receipt.session_id !== session_name) {
62
- throw new Error('SEAT_AITERM_LAUNCH_RECEIPT_INVALID')
63
- }
64
- console.log(JSON.stringify(receipt))
@@ -1,22 +0,0 @@
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`)
@@ -1,26 +0,0 @@
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
- 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`)
26
- }
@@ -1,67 +0,0 @@
1
- #!/usr/bin/env node
2
- // Codex TUI の既知ダイアログ → 送るキー。画面文言だけで判定する。未知の確認は通さない。
3
- import { fileURLToPath } from 'node:url'
4
- import { resolve } from 'node:path'
5
-
6
- export const MCP_ALLOW_NEEDLE = 'Allow the room MCP server to run tool'
7
- export const MCP_ALWAYS_ALLOW = 'Always allow'
8
- export const COMMAND_APPROVAL_NEEDLE = 'Would you like to run the following command?'
9
- export const COMMAND_APPROVAL_DONT_ASK = "don't ask again"
10
-
11
- export function keysForCodexPane(screen) {
12
- const text = String(screen ?? '')
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
- // 長いcommand approvalは質問行が24行より上へ押し出される。現在の選択footerが末尾に
22
- // 在る時だけ全画面を読み、footerが無い通常画面では古いscrollbackを拾わない。
23
- const activeDialogFooter = /Press enter to confirm or esc to (?:cancel|go back)|enter to submit \| esc to cancel/iu.test(tail)
24
- const dialogText = activeDialogFooter ? text : tail
25
- if (dialogText.includes(MCP_ALLOW_NEEDLE) && dialogText.includes(MCP_ALWAYS_ALLOW)) {
26
- return { kind: 'mcp-allow', keys: ['Down', 'Down', 'Enter'] }
27
- }
28
- if (dialogText.includes(COMMAND_APPROVAL_NEEDLE) && dialogText.toLowerCase().includes(COMMAND_APPROVAL_DONT_ASK)) {
29
- return { kind: 'command-approval', keys: ['Down', 'Enter'] }
30
- }
31
- // Codex 0.153: 上限接近時に安いモデルへの切替を勧める modal。モデル配置は 02_models の順位表が決めるので
32
- // 切り替えず「Keep current model (never show again)」(3 番目)を選ぶ(実測 2026-09-04: mio 席が入力不能に)。
33
- if (dialogText.includes('Approaching rate limits') && dialogText.includes('Keep current model')) {
34
- return { kind: 'rate-limit-model-switch', keys: ['Down', 'Down', 'Enter'] }
35
- }
36
- if (dialogText.includes('Hooks need review')) {
37
- return { kind: 'hooks', keys: ['Down', 'Enter'] }
38
- }
39
- if (dialogText.includes('Update now')) {
40
- return { kind: 'update', keys: ['Down', 'Enter'] }
41
- }
42
- if (dialogText.includes('Yes, continue') && dialogText.includes('Do you trust the contents of this directory')) {
43
- return { kind: 'trust', keys: ['Enter'] }
44
- }
45
- return null
46
- }
47
-
48
- export function blocksCodexReady(screen) {
49
- const kind = keysForCodexPane(screen)?.kind
50
- return kind === 'mcp-allow' || kind === 'command-approval'
51
- }
52
-
53
- const isMain = Boolean(process.argv[1]) && resolve(process.argv[1]) === fileURLToPath(import.meta.url)
54
- if (isMain) {
55
- const chunks = []
56
- for await (const chunk of process.stdin) chunks.push(chunk)
57
- const screen = Buffer.concat(chunks).toString('utf8')
58
- const action = keysForCodexPane(screen)
59
- if (process.argv.includes('--ready-ok')) {
60
- process.exit(blocksCodexReady(screen) ? 2 : 0)
61
- }
62
- if (process.argv.includes('--keys')) {
63
- if (action) process.stdout.write(`${action.keys.join('\n')}\n`)
64
- process.exit(0)
65
- }
66
- process.stdout.write(`${JSON.stringify(action)}\n`)
67
- }
@@ -1,52 +0,0 @@
1
- #!/usr/bin/env node
2
- import { resolve } from 'node:path'
3
- import { fileURLToPath } from 'node:url'
4
-
5
- // PEERTABLE_POST_TOKEN は渡さない——EncodedCommand は base64 なだけで process 一覧から
6
- // 復元できる。秘密値は ensure-bridge.sh が credential file へ落とし、ここへはパスだけが来る
7
- const FORWARDED_ENV = [
8
- 'PEERTABLE_TMUX_SOCKET',
9
- 'PEERTABLE_CREDENTIAL_FILE',
10
- 'PEERTABLE_URL',
11
- 'PEERTABLE_PARENT_NAME',
12
- ]
13
-
14
- const quotePowerShell = value => `'${String(value).replaceAll("'", "''")}'`
15
-
16
- export function buildWindowsBridgeLaunch({
17
- script,
18
- project,
19
- log,
20
- args = [],
21
- env = process.env,
22
- node = process.execPath,
23
- pwsh = 'pwsh.exe',
24
- } = {}) {
25
- if (!script || !project || !log) throw new Error('WINDOWS_BRIDGE_LAUNCH_ARGS_INVALID')
26
-
27
- const statements = []
28
- for (const name of FORWARDED_ENV) {
29
- const value = env[name]
30
- if (value) statements.push(`$env:${name} = ${quotePowerShell(value)}`)
31
- }
32
- statements.push('$utf8 = [System.Text.UTF8Encoding]::new($false)')
33
- statements.push('[Console]::InputEncoding = $utf8')
34
- statements.push('[Console]::OutputEncoding = $utf8')
35
- statements.push('$OutputEncoding = $utf8')
36
- const command = [node, script, project, ...args].map(quotePowerShell).join(' ')
37
- statements.push(`& ${command} 2>&1 | Out-File -FilePath ${quotePowerShell(log)} -Append -Encoding utf8`)
38
- statements.push('$bridgeExit = $LASTEXITCODE')
39
- statements.push('exit $bridgeExit')
40
- const encoded = Buffer.from(statements.join('; '), 'utf16le').toString('base64')
41
- const argv = ['-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand', encoded]
42
- return {
43
- command: pwsh,
44
- argv,
45
- tmuxCommand: [pwsh, ...argv].join(' '),
46
- }
47
- }
48
-
49
- if (fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
50
- const [script, project, log, ...args] = process.argv.slice(2)
51
- process.stdout.write(buildWindowsBridgeLaunch({ script, project, log, args }).tmuxCommand)
52
- }
@@ -1,36 +0,0 @@
1
- // 席TUIへの入力方言の**唯一の正本**。
2
- //
3
- // 各harness TUIは入力の作法が異なり(実被弾の履歴: Escapeを撃つとClaudeの実行中ターンが
4
- // 死ぬ 2026-08-22 / 素打ち連打はCodexのcomposerを壊す 2026-08-29 / bracketed pasteの
5
- // 終端コードはGrokへリテラル混入する 2026-08-30)、その違いを呼び出し側のAIやscriptに
6
- // 覚えさせると、片方で検証した変更が他方を壊す事故が構造的に再発する(オーナー裁定
7
- // 2026-08-30「その違いをAIに要求するのが間違い。ツール側がミスが起こらないように作れ」)。
8
- //
9
- // 方言はこの表だけに書く。呼び出し側はharness名を渡して結果を使うだけであり、
10
- // 表に無いharnessは推測で送らず typed error で止まる——新harnessの追加は
11
- // この表へ1行足すまで配達自体ができない、という形でミスを機械的に封じる。
12
- export const HARNESS_INPUT_DIALECTS = Object.freeze({
13
- // Codex CLI: bracketed paste対応(素打ち連打はpaste burst検知を壊すので必ずpaste経由)
14
- codex: Object.freeze({ bracketed_paste: true }),
15
- // Claude Code: bracketed paste対応
16
- claude: Object.freeze({ bracketed_paste: true }),
17
- // Grok Build: bracketed paste**非対応**(終端コード ^[[201~ がリテラル混入して固着する)
18
- grok: Object.freeze({ bracketed_paste: false }),
19
- })
20
-
21
- export function seatInputDialect(harness) {
22
- const dialect = HARNESS_INPUT_DIALECTS[harness]
23
- if (dialect === undefined) {
24
- const error = new Error(`SEAT_INPUT_HARNESS_UNKNOWN: 入力方言表に '${harness}' が無い。`
25
- + 'skill/scripts/seat-input.mjs の HARNESS_INPUT_DIALECTS へ実測に基づく1行を追加するまで、この席へは配達できない')
26
- error.code = 'SEAT_INPUT_HARNESS_UNKNOWN'
27
- throw error
28
- }
29
- return dialect
30
- }
31
-
32
- /** tmux paste-buffer の argv を方言から組む。呼び出し側は -p の要否を知らない。 */
33
- export function seatPasteArgs(harness, bufferName, target) {
34
- const dialect = seatInputDialect(harness)
35
- return ['paste-buffer', ...(dialect.bracketed_paste ? ['-p'] : []), '-d', '-b', bufferName, '-t', target]
36
- }
@@ -1,22 +0,0 @@
1
- # sourced by launch-seat / leave-seat / teardown / ensure-bridge / change-seat
2
- # POSIX は tmux -S <sock>。Windows psmux は -L <aiterm-ns>(-S は既定 namespace へ落ちる)。
3
- _peertable_tmux_scripts=$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
4
- tmux_at() {
5
- local prefix
6
- prefix=$(node "$_peertable_tmux_scripts/tmux-socket.mjs" --prefix) || return 1
7
- # shellcheck disable=SC2206
8
- local -a conn=($prefix)
9
- # tmux server は最初に立てた process の locale を一生使い、C/POSIX/未設定だと UTF-8 の
10
- # send-keys/paste を破壊する(実被弾 2026-08-29: launchd の bridge 巡回が LANG 無しで
11
- # server を立て、以後の日本語込み席起動コマンドが全て化けて Codex 席が着席不能になった)。
12
- # launchd/cron 由来の呼び出しでも server が UTF-8 で立つよう、壊れた既定の時だけ注入する。
13
- # 利用者が C/POSIX 以外を明示設定している場合はその選択を尊重して触らない(aiterm と同じ判定)。
14
- local effective="${LC_ALL:-${LC_CTYPE:-${LANG:-}}}"
15
- if [ -z "$effective" ] || [[ "$effective" =~ ^(C|POSIX)$ ]]; then
16
- local ctype="C.UTF-8"
17
- [ "$(uname)" = "Darwin" ] && ctype="UTF-8"
18
- env -u LC_ALL LC_CTYPE="$ctype" tmux "${conn[@]}" "$@"
19
- else
20
- command tmux "${conn[@]}" "$@"
21
- fi
22
- }
@@ -1,19 +0,0 @@
1
- #!/usr/bin/env node
2
- import { aitermPsmuxNamespace, resolveTmuxSocket, tmuxArgv, usesPsmuxNamespace } from './seat-usage.mjs'
3
-
4
- const result = resolveTmuxSocket(process.env)
5
- if (result.error?.code === 'PEERTABLE_TMUX_SOCKET_AMBIGUOUS' && !usesPsmuxNamespace(process.env)) {
6
- console.error(`${result.error.code}: ${result.candidates.join(', ')}`)
7
- process.exit(1)
8
- }
9
- if (process.argv.includes('--prefix')) {
10
- console.log(tmuxArgv([], { socket: result.socket }).join(' '))
11
- } else if (process.argv.includes('--namespace')) {
12
- console.log(usesPsmuxNamespace(process.env) ? aitermPsmuxNamespace(process.env) : '')
13
- } else if (process.argv.includes('--source')) {
14
- console.log(usesPsmuxNamespace(process.env) ? 'psmux-namespace' : result.source)
15
- } else if (usesPsmuxNamespace(process.env)) {
16
- console.log(aitermPsmuxNamespace(process.env))
17
- } else {
18
- console.log(result.socket)
19
- }
@@ -1,30 +0,0 @@
1
- /** Grok Build TUI固有のpane状態を返す。判定不能ならnull。 */
2
- export function classifyGrokPaneTail(tail) {
3
- if (typeof tail !== 'string') return null
4
-
5
- if (isGrokPrivacyBanner(tail)) return 'blocked'
6
-
7
- // Grok Build は通信失敗後も `Waiting for response` と `[stop]` を画面へ残し、
8
- // 再試行中のように見せる。この状態を busy にすると「モデルが仕事を進めている」
9
- // アクティブランプが点き続けるため、既知の接続失敗を先に blocked と判定する。
10
- if (tail.includes('Connection failed — reqwest error')
11
- || tail.includes('Connection failed - reqwest error')
12
- || tail.includes('Check your network and try again.')) return 'blocked'
13
-
14
- // 完了後も `stop [hooks: 1/3]` はステータス行へ残る。入力欄が戻った
15
- // `Worked for ...` 行を先にidle扱いし、配達を永久保留しない。
16
- if (/Worked for\s+\d+(?:m\d+)?s[\s\S]*?stop\s+\[hooks:\s*\d+\/\d+\]/u.test(tail)) return 'idle'
17
-
18
- if (tail.includes('Waiting for response') || tail.includes('Responding…') || tail.includes('Responding...')) return 'busy'
19
- if (tail.includes('[stop]')) return 'busy'
20
- if (/\[hooks:\s*\d+\/\d+\]/u.test(tail)) return 'busy'
21
- return null
22
- }
23
-
24
- /** Grok TUI の SpaceXAI coding-data バナー。 */
25
- export function isGrokPrivacyBanner(tail) {
26
- return typeof tail === 'string'
27
- && tail.includes('Help improve Grok')
28
- && tail.includes('[Opt out]')
29
- && tail.includes('[Opt in]')
30
- }