SLOPSHOPPER

pdx-hold-probe

Probe (M11): every new turn passes prompt.submit, and a hook can hold it past 10 s inside a $ call.

newpromptprocess
A shopper browsing a rack in a slop shop
README

Purdex

人與 agent 協作的工作站。把 tmux session、Claude Code 串流、對話式 agent 統一在一個介面,跨 workspace、跨 host 並行作業。

仍在 alpha (1.0.0-alpha.233),僅支援 macOS。原名 tmux-box / tmux-ai-term。

能做什麼

  • 多 workspace 並行,每個 workspace 內可開多 tab、tab 可分割多 pane
  • Agent 三種模式:terminal / stream(wrap) / 對話
  • 跨機(Tailnet)多 host 操作
  • 整合 Claude Code、Codex、OpenCode 的 hook 與狀態指示

詞彙與設計定錨見 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

文件

License

MIT

Source 1 files
hooks/register.js 41 lines
1// Probe 2: does an idle session's peer delivery pass through prompt.submit,
2// and can a prompt.submit hook hold the turn past 10 s inside a $ call?
3let n = 0
4
5async function log($, msg) {
6  const line = new Date().toISOString() + ' ' + msg + '\n'
7  try {
8    const path = (await $.session.cwd()) + '/probe.log'
9    const old = await $.fs.read(path).catch(() => '')
10    await $.fs.write(path, old + line)
11  } catch {}
12}
13
14export function register(on) {
15  on('session.start', async ($, e, next) => {
16    await log($, 'loaded session=' + (await $.session.id()))
17    return next(e)
18  })
19
20  on('session.receive', async ($, e, next) => {
21    await log($, 'receive origin=' + JSON.stringify(e.origin) + ' text=' + JSON.stringify(String(e.text).slice(-60)))
22    return next(e)
23  })
24
25  on('prompt.submit', async ($, e, next) => {
26    const id = ++n
27    await log($, 'submit#' + id + ' origin=' + JSON.stringify(e.origin || null) + ' agentId=' + (e.agentId || '-') + ' text=' + JSON.stringify(String(e.text).slice(-60)))
28    if (String(e.text).includes('HOLD15')) {
29      await log($, 'submit#' + id + ' holding 15s via $.process.run')
30      const r = await $.process.run(['/bin/sleep', '15'], { timeoutMs: 60000 })
31      await log($, 'submit#' + id + ' released exit=' + r.exitCode)
32    }
33    return next(e)
34  })
35
36  on('turn.start', async ($, e, next) => {
37    if (!e.agentId) await log($, 'turn.start')
38    return next(e)
39  })
40}
41