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.
- package/README.ja.md +32 -33
- package/README.md +36 -37
- package/package.json +3 -1
- package/room/client.mjs +24 -33
- package/room/server.mjs +1 -1
- package/skill/SKILL.md +69 -94
- package/skill/scripts/aiterm-client.mjs +48 -0
- package/skill/scripts/aiterm-deliver.mjs +3 -1
- package/skill/scripts/change-seat.mjs +86 -0
- package/skill/scripts/change-seat.sh +3 -276
- package/skill/scripts/change-seat.test.mjs +129 -0
- package/skill/scripts/cli.mjs +77 -0
- package/skill/scripts/doctor.mjs +53 -0
- package/skill/scripts/doctor.sh +2 -213
- package/skill/scripts/ensure-all-bridges.mjs +42 -0
- package/skill/scripts/ensure-all-bridges.sh +3 -80
- package/skill/scripts/ensure-bridge.mjs +11 -0
- package/skill/scripts/ensure-bridge.sh +2 -95
- package/skill/scripts/ensure-codex-room-mcp.mjs +1 -1
- package/skill/scripts/ensure-project-runtime.mjs +85 -0
- package/skill/scripts/ensure-project-runtime.sh +2 -11
- package/skill/scripts/ensure-project-runtime.test.mjs +40 -0
- package/skill/scripts/install-skill.mjs +89 -0
- package/skill/scripts/install-skill.test.mjs +84 -0
- package/skill/scripts/launch-seat.mjs +163 -0
- package/skill/scripts/launch-seat.sh +3 -797
- package/skill/scripts/launch-seat.test.mjs +174 -0
- package/skill/scripts/leave-seat.mjs +44 -0
- package/skill/scripts/leave-seat.sh +3 -92
- package/skill/scripts/leave-seat.test.mjs +64 -0
- package/skill/scripts/legacy-entry.mjs +33 -0
- package/skill/scripts/platform/windows/resolve-lattice-command.mjs +12 -1
- package/skill/scripts/project-runtime.mjs +95 -0
- package/skill/scripts/project-runtime.test.mjs +29 -0
- package/skill/scripts/project-scaffold.mjs +179 -0
- package/skill/scripts/project-scaffold.test.mjs +129 -0
- package/skill/scripts/refresh-seat-identity.mjs +11 -8
- package/skill/scripts/remove-managed-room-mcp.mjs +9 -4
- package/skill/scripts/resume.sh +2 -225
- package/skill/scripts/room-api.mjs +28 -0
- package/skill/scripts/room-public-session.test.mjs +73 -0
- package/skill/scripts/runtime-contract.test.mjs +13 -146
- package/skill/scripts/runtime-launch-command.mjs +19 -0
- package/skill/scripts/runtime-launch-command.test.mjs +32 -0
- package/skill/scripts/seat-approval.mjs +24 -0
- package/skill/scripts/seat-approval.test.mjs +43 -0
- package/skill/scripts/seat-identity.mjs +2 -71
- package/skill/scripts/seat-launch-phase.mjs +11 -0
- package/skill/scripts/seat-observer.mjs +54 -0
- package/skill/scripts/seat-observer.test.mjs +58 -0
- package/skill/scripts/seat-session.mjs +17 -0
- package/skill/scripts/seat-status-bridge.mjs +33 -206
- package/skill/scripts/seat-usage.mjs +3 -334
- package/skill/scripts/setup.sh +3 -215
- package/skill/scripts/teardown.mjs +113 -0
- package/skill/scripts/teardown.sh +3 -410
- package/skill/scripts/teardown.test.mjs +62 -0
- package/skill/scripts/upgrade-team-assets.mjs +268 -0
- package/skill/scripts/upgrade-team-assets.sh +2 -282
- package/skill/scripts/wakeup-bridge.mjs +26 -32
- package/skill/scripts/wakeup-delivery.mjs +4 -14
- package/skill/templates/charter.md +1 -1
- package/skill/templates/member-standalone.md +1 -1
- package/skill/templates/member.md +3 -3
- package/skill/scripts/agent-pane-status.mjs +0 -6
- package/skill/scripts/aiterm-configure.mjs +0 -31
- package/skill/scripts/aiterm-launch.mjs +0 -64
- package/skill/scripts/aiterm-send.mjs +0 -22
- package/skill/scripts/claude-dialog.mjs +0 -26
- package/skill/scripts/codex-dialog.mjs +0 -67
- package/skill/scripts/platform/windows/build-bridge-command.mjs +0 -52
- package/skill/scripts/seat-input.mjs +0 -36
- package/skill/scripts/tmux-at.bash +0 -22
- package/skill/scripts/tmux-socket.mjs +0 -19
- package/skill/scripts/vendors/grok/pane-status.mjs +0 -30
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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,
|
|
74
|
-
|
|
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
|
|
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の円卓メンバーは、親が`
|
|
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の円卓メンバーは、親が`
|
|
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
|
|
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
|
-
**常駐・長時間ジョブは
|
|
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
|
-
}
|