thinkpool-pair 0.6.6 → 0.6.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/bridge.mjs +9 -3
- package/claude-session.mjs +19 -1
- package/package.json +1 -1
package/bridge.mjs
CHANGED
|
@@ -330,6 +330,7 @@ function printLocal(evt) {
|
|
|
330
330
|
case 'pool': return w(evt.pending ? `${A.cyan}◌ pool synthesizing…${A.rst}` : `${A.cyan}◆ pool → ${evt.text}${A.rst}`)
|
|
331
331
|
case 'note': return w(`${A.dim}— ${evt.text} —${A.rst}`)
|
|
332
332
|
case 'clear': return w(`${A.dim}— context cleared —${A.rst}`)
|
|
333
|
+
case 'usage': { const c = evt.ctx; return c ? w(`${A.dim} ctx ${c.pct}% · ${Math.round(c.used / 1000)}k/${Math.round(c.max / 1000)}k${evt.costUsd != null ? ` · $${evt.costUsd.toFixed(3)}` : ''}${A.rst}`) : undefined }
|
|
333
334
|
case 'error': return w(`${A.red}✗ ${evt.message}${A.rst}`)
|
|
334
335
|
case 'tool_result': {
|
|
335
336
|
const c = evt.content
|
|
@@ -370,11 +371,16 @@ function openStructured({ id, model, resume, log }) {
|
|
|
370
371
|
openStructured({ id, model, log: entry.log })
|
|
371
372
|
return
|
|
372
373
|
}
|
|
373
|
-
|
|
374
|
-
|
|
374
|
+
// Chrome events (mode / usage / clear) are transient state, not transcript —
|
|
375
|
+
// broadcast + print them, but keep them out of the persisted/replayed log.
|
|
376
|
+
const chrome = evt.kind === 'mode' || evt.kind === 'usage' || evt.kind === 'clear'
|
|
377
|
+
if (!chrome) {
|
|
378
|
+
entry.log.push(evt)
|
|
379
|
+
if (entry.log.length > STRUCTURED_LOG_MAX) entry.log.shift()
|
|
380
|
+
}
|
|
375
381
|
bcast('code-event', { term: id, evt })
|
|
376
382
|
printLocal(evt)
|
|
377
|
-
persist()
|
|
383
|
+
if (!chrome) persist()
|
|
378
384
|
},
|
|
379
385
|
requestPermission: (req) => new Promise((resolve) => {
|
|
380
386
|
entry.pending.set(req.id, resolve)
|
package/claude-session.mjs
CHANGED
|
@@ -91,6 +91,7 @@ export function startClaudeSession({ cwd, model, resume, onEvent, requestPermiss
|
|
|
91
91
|
let q = null // the live Query — control requests (interrupt /
|
|
92
92
|
// setPermissionMode) route through it once streaming.
|
|
93
93
|
let mode = 'default' // mirrors Claude Code's ⇧⇥ cycle
|
|
94
|
+
const alwaysAllow = new Set() // tool:risk signatures the user chose "don't ask again" for
|
|
94
95
|
|
|
95
96
|
const emit = (evt) => { try { onEvent?.(evt) } catch { /* never let a consumer throw into the loop */ } }
|
|
96
97
|
|
|
@@ -120,15 +121,21 @@ export function startClaudeSession({ cwd, model, resume, onEvent, requestPermiss
|
|
|
120
121
|
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
122
|
}
|
|
122
123
|
const risk = classifyRisk(toolName, toolInput)
|
|
124
|
+
// "Don't ask again" is keyed by tool + risk tier, so allowing medium Bash
|
|
125
|
+
// never silently allows a future destructive one (high always re-asks).
|
|
126
|
+
const sig = `${toolName}:${risk}`
|
|
123
127
|
const auto =
|
|
124
128
|
risk === 'low' ||
|
|
125
129
|
mode === 'bypassPermissions' ||
|
|
126
|
-
(mode === 'acceptEdits' && WRITE_TOOLS.has(toolName) && risk !== 'high')
|
|
130
|
+
(mode === 'acceptEdits' && WRITE_TOOLS.has(toolName) && risk !== 'high') ||
|
|
131
|
+
alwaysAllow.has(sig)
|
|
127
132
|
let decision = 'allow'
|
|
128
133
|
if (!auto) {
|
|
129
134
|
try {
|
|
130
135
|
decision = await requestPermission?.({ id: randomUUID(), toolName, input: toolInput, risk }) ?? 'allow'
|
|
131
136
|
} catch { decision = 'deny' } // a broken permission path must fail safe (deny)
|
|
137
|
+
// "Allow & don't ask again" — remember the signature, then allow.
|
|
138
|
+
if (decision === 'always') { alwaysAllow.add(sig); decision = 'allow' }
|
|
132
139
|
}
|
|
133
140
|
// On deny, permissionDecisionReason IS what the model receives as the
|
|
134
141
|
// tool error — make it a real instruction, not an opaque tag.
|
|
@@ -178,6 +185,17 @@ export function startClaudeSession({ cwd, model, resume, onEvent, requestPermiss
|
|
|
178
185
|
case 'result':
|
|
179
186
|
if (m.session_id) sessionId = m.session_id
|
|
180
187
|
emit({ kind: 'result', subtype: m.subtype, sessionId, costUsd: m.total_cost_usd, usage: m.usage, numTurns: m.num_turns })
|
|
188
|
+
// Surface a usage/context meter (chrome, not a transcript line). The
|
|
189
|
+
// context window % comes from the control request; cost is cumulative.
|
|
190
|
+
;(async () => {
|
|
191
|
+
let ctx = null
|
|
192
|
+
try {
|
|
193
|
+
const c = await q?.getContextUsage?.()
|
|
194
|
+
if (c) ctx = { used: c.totalTokens, max: c.maxTokens, pct: Math.round(c.percentage), model: c.model }
|
|
195
|
+
} catch { /* control req may be unavailable */ }
|
|
196
|
+
const u = m.usage || {}
|
|
197
|
+
emit({ kind: 'usage', costUsd: m.total_cost_usd ?? null, tokens: (u.input_tokens || 0) + (u.output_tokens || 0), ctx })
|
|
198
|
+
})()
|
|
181
199
|
break
|
|
182
200
|
default:
|
|
183
201
|
break
|