thinkpool-pair 0.6.3 → 0.6.4

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/bridge.mjs CHANGED
@@ -327,7 +327,7 @@ function openStructured({ id, model, resume }) {
327
327
  },
328
328
  requestPermission: (req) => new Promise((resolve) => {
329
329
  entry.pending.set(req.id, resolve)
330
- bcast('code-perm-req', { term: id, id: req.id, toolName: req.toolName, input: req.input, risk: req.risk })
330
+ bcast('code-perm-req', { term: id, id: req.id, toolName: req.toolName, input: req.input, risk: req.risk, plan: req.plan })
331
331
  }),
332
332
  })
333
333
  announce()
@@ -459,7 +459,9 @@ channel
459
459
  .on('broadcast', { event: 'code-perm' }, ({ payload }) => {
460
460
  const s = payload?.term && sessions.get(payload.term)
461
461
  const resolve = s && payload.id && s.pending.get(payload.id)
462
- if (resolve) { s.pending.delete(payload.id); resolve(payload.decision === 'deny' ? 'deny' : 'allow') }
462
+ // Pass the raw decision through normal tools use allow/deny, plan cards
463
+ // use run/accept/keep (claude-session interprets). Default deny on missing.
464
+ if (resolve) { s.pending.delete(payload.id); resolve(payload.decision || 'deny') }
463
465
  })
464
466
  .on('broadcast', { event: 'code-abort' }, ({ payload }) => {
465
467
  const s = payload?.term && sessions.get(payload.term)
@@ -101,6 +101,24 @@ export function startClaudeSession({ cwd, model, resume, onEvent, requestPermiss
101
101
  const preTool = async (hookInput) => {
102
102
  const toolName = hookInput.tool_name
103
103
  const toolInput = hookInput.tool_input
104
+ // ── Plan approval — ExitPlanMode is how the agent presents its plan in
105
+ // plan mode. Render a dedicated plan card (not the generic perm card) with
106
+ // three outcomes: run (exit → default), accept (exit → acceptEdits), keep
107
+ // (deny → stay planning). On approval we flip the SDK permission mode so
108
+ // subsequent tools actually execute.
109
+ if (toolName === 'ExitPlanMode') {
110
+ let choice = 'keep'
111
+ try { choice = await requestPermission?.({ id: randomUUID(), toolName, input: toolInput, risk: 'plan', plan: toolInput?.plan || '' }) ?? 'keep' }
112
+ catch { choice = 'keep' }
113
+ if (choice === 'run' || choice === 'accept') {
114
+ const next = choice === 'accept' ? 'acceptEdits' : 'default'
115
+ mode = next
116
+ try { await q?.setPermissionMode?.(next) } catch { /* only valid mid-stream */ }
117
+ emit({ kind: 'mode', mode: next })
118
+ return { continue: true, hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow', permissionDecisionReason: `Plan approved in the ThinkPool room — proceed (${next} mode).` } }
119
+ }
120
+ return { continue: true, hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'deny', permissionDecisionReason: 'The user chose "keep planning" in the ThinkPool room. Do not exit plan mode — keep refining the plan, then call ExitPlanMode again when ready.' } }
121
+ }
104
122
  const risk = classifyRisk(toolName, toolInput)
105
123
  const auto =
106
124
  risk === 'low' ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.6.3",
3
+ "version": "0.6.4",
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": {