Probe (M1): consume a pdx control message in session.receive, then submit and clear.

人與 agent 協作的工作站。把 tmux session、Claude Code 串流、對話式 agent 統一在一個介面,跨 workspace、跨 host 並行作業。
仍在 alpha (
1.0.0-alpha.233),僅支援 macOS。原名tmux-box/tmux-ai-term。
terminal / stream(wrap) / 對話詞彙與設計定錨見 PRODUCT.md。
Go daemon + React 19 SPA + Electron 41 殼。
cd spa && pnpm install
pnpm dev # SPA dev server
pnpm test # vitest
pnpm build # SPA build
pnpm electron:build # 從 root;產出 dist/mac/ + dist/mac-arm64/
環境與跨機開發流程見 CLAUDE.md。
Daemon(Go)需要存取 private module lab.protype.tw/wake/nexen。建置前, 這台機器需完成兩項一次性設定:
go env -w GOPRIVATE=lab.protype.tw
git config --global url."ssh://git@lab.protype.tw:9079/".insteadOf "https://lab.protype.tw/"
make build(或單獨 make check-goenv)會先驗證這兩項,缺一就印出對應指令 並以非零狀態結束,不會進入 go build。
冷快取驗證(確認上述設定不依賴呼叫端的 shell 環境變數也能運作;spec §6 step 0):
GOWORK=off GOMODCACHE=$(mktemp -d) go build ./cmd/pdx
PRODUCT.md — 產品定位CLAUDE.md — 開發流程CHANGELOG.md — 版本歷史hooks/register.js 57 lines1// Probe (M1): can a pdx peer message be consumed by session.receive, and can the
2// mod then drive prompt.submit and /clear from a timer?
3const MARK = '[pdx-relay:control]'
4let phase = 'idle'
5
6async function log($, msg) {
7 const line = new Date().toISOString() + ' [' + phase + '] ' + msg + '\n'
8 try {
9 const path = (await $.session.cwd()) + '/probe.log'
10 const old = await $.fs.read(path).catch(() => '')
11 await $.fs.write(path, old + line)
12 } catch {}
13}
14
15function later($, fn) {
16 $.clock.after(100, () => fn().catch((err) => log($, 'deferred failed: ' + String(err))))
17}
18
19export function register(on) {
20 on('session.start', async ($, e, next) => {
21 await log($, 'loaded session=' + (await $.session.id()))
22 return next(e)
23 })
24
25 on('session.receive', async ($, e, next) => {
26 await log($, 'receive origin=' + JSON.stringify(e.origin) + ' agentId=' + (e.agentId || '-') + ' text=' + JSON.stringify(String(e.text).slice(0, 160)))
27 if (!String(e.text).includes(MARK)) return next(e)
28 phase = 'submitting'
29 later($, async () => {
30 await $.prompt.submit({ text: '這是探測。請只回覆一行:PROBE-SUBMIT-OK' })
31 await log($, 'prompt.submit sent')
32 })
33 return { consumed: 'pdx-relay control (probe)' }
34 })
35
36 on('turn.complete', async ($, e, next) => {
37 const r = await next(e)
38 if (e.agentId) return r
39 await log($, 'turn.complete')
40 if (phase === 'submitting') {
41 phase = 'clearing'
42 later($, async () => {
43 await $.command.run({ command: 'clear' })
44 await log($, 'command.run(clear) returned')
45 })
46 }
47 return r
48 })
49
50 on('classic.SessionStart', async ($, e, next) => {
51 const r = await next(e)
52 await log($, 'classic.SessionStart source=' + e.source + ' session=' + (await $.session.id()))
53 if (e.source === 'clear' && phase === 'clearing') phase = 'done'
54 return r
55 })
56}
57