thinkpool-pair 0.7.5 → 0.7.7

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/account.mjs CHANGED
@@ -109,7 +109,9 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
109
109
  // account without per-room probing. We TRACK this machine (name + served room
110
110
  // codes + version) on a per-account channel; the dashboard subscribes read-only.
111
111
  const machine = os.hostname().replace(/\.local$/, '')
112
- const acct = sb.channel(`tpacct:${session.user.id}`, { config: { presence: { key: machine } } })
112
+ const acct = sb.channel(`tpacct:${session.user.id}`, { config: { presence: { key: machine }, broadcast: { self: false } } })
113
+ // Dashboard "Disconnect" → broadcast shutdown → clean exit (presence leaves, bar flips live).
114
+ acct.on('broadcast', { event: 'shutdown' }, () => { process.stderr.write('\n ◇ disconnect requested from the dashboard — stopping bridge.\n'); process.kill(process.pid, 'SIGTERM') })
113
115
  const pushPresence = () => { try { acct.track({ name: machine, version: VERSION, rooms: [...children.keys()], ts: Date.now() }) } catch { /* noop */ } }
114
116
  await new Promise((res) => acct.subscribe((st) => { if (st === 'SUBSCRIBED') { pushPresence(); res() } }))
115
117
 
@@ -130,7 +132,7 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
130
132
  }
131
133
  warned.delete(room)
132
134
  process.stderr.write(`\n ◆ serving ${room}${r.name ? ` "${r.name}"` : ''} → ${dir}${dirs[room] ? '' : ' (default)'}\n`)
133
- const child = spawn(process.execPath, [BRIDGE, room, '--headless'], { cwd: dir, stdio: 'inherit' })
135
+ const child = spawn(process.execPath, [BRIDGE, room, '--headless', '--auto=claude'], { cwd: dir, stdio: 'inherit' })
134
136
  children.set(room, child)
135
137
  child.on('exit', () => { children.delete(room) }) // re-served on the next tick
136
138
  }
@@ -139,7 +141,16 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
139
141
 
140
142
  await tick()
141
143
  const iv = setInterval(tick, 15000)
142
- const stop = (sig) => { clearInterval(iv); try { acct.untrack(); sb.removeChannel(acct) } catch { /* noop */ } for (const c of children.values()) { try { c.kill(sig) } catch { /* noop */ } } setTimeout(() => process.exit(0), 400) }
143
- for (const sig of ['SIGINT', 'SIGTERM']) process.on(sig, () => stop(sig))
144
+ let stopping = false
145
+ const stop = (sig) => {
146
+ if (stopping) return; stopping = true
147
+ clearInterval(iv)
148
+ for (const c of children.values()) { try { c.kill(sig || 'SIGTERM') } catch { /* noop */ } }
149
+ // Flush the presence LEAVE before exiting so the dashboard flips to
150
+ // "not connected" in realtime (don't fire-and-forget the untrack).
151
+ ;(async () => { try { await acct.untrack() } catch { /* noop */ } try { await sb.removeChannel(acct) } catch { /* noop */ } process.exit(0) })()
152
+ setTimeout(() => process.exit(0), 1500) // hard backstop if the flush hangs
153
+ }
154
+ for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP']) process.on(sig, () => stop(sig))
144
155
  await new Promise(() => {})
145
156
  }
package/bridge.mjs CHANGED
@@ -175,6 +175,9 @@ if (_superOwn.includes('--supervise') || _superOwn.includes('--keep-alive')) {
175
175
  }
176
176
 
177
177
  const headless = argv.includes('--headless')
178
+ // Account-mode children pass --auto=<agent> so a served session opens a live
179
+ // terminal automatically (headless, no TTY/picker) instead of an empty room.
180
+ const autoAgent = (argv.find(a => a.startsWith('--auto=')) || '').slice(7) || null
178
181
  // Structured mode (Phase 2, opt-in): Claude Code runs through the Agent SDK
179
182
  // (structured events + risk-tiered permission gate) instead of the PTY byte
180
183
  // relay. Default OFF — the PTY path is untouched. Only applies to `claude`.
@@ -694,16 +697,17 @@ channel
694
697
  if (status === 'SUBSCRIBED') {
695
698
  realtimeHealthy = true; brokenSince = 0
696
699
  channel.track({ name, role: 'bridge' })
697
- if (attachedCmd && !terms.size && !sessions.size) {
700
+ const startCmd = attachedCmd || autoAgent // autoAgent: account-mode auto-open (headless)
701
+ if (startCmd && !terms.size && !sessions.size) {
698
702
  // Claude + structured mode → Agent SDK session; everything else → PTY.
699
703
  // On restart, restore the latest saved structured session for this room:
700
704
  // replay its transcript + resume the live SDK context if recent enough.
701
- if (wantStructured(attachedCmd)) {
705
+ if (wantStructured(startCmd)) {
702
706
  const prev = loadLatest(room)
703
707
  if (prev && (prev.log?.length || prev.sessionId)) openStructured({ id: prev.id, resume: canResume(prev) ? prev.sessionId : undefined, log: prev.log, commands: prev.commands })
704
708
  else openStructured({ id: randomUUID() })
705
709
  }
706
- else openTerm({ id: randomUUID(), cmd: attachedCmd, args: attachedArgs, attached: true })
710
+ else openTerm({ id: randomUUID(), cmd: startCmd, args: attachedArgs, attached: !autoAgent })
707
711
  }
708
712
  announce()
709
713
  process.stderr.write(headless
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.5",
3
+ "version": "0.7.7",
4
4
  "description": "Share a local coding-agent CLI (Claude Code, Codex, Gemini, Aider, …) into a ThinkPool Code room, live.",
5
5
  "type": "module",
6
6
  "bin": {