thinkpool-pair 0.6.14 → 0.6.16
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/claude-session.mjs +36 -7
- package/package.json +1 -1
package/claude-session.mjs
CHANGED
|
@@ -54,6 +54,21 @@ export function isSafeDocWrite(toolName, input) {
|
|
|
54
54
|
return SAFE_DOC_RE.test(p)
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// The full auto-allow decision (exported for the contract test, so the policy
|
|
58
|
+
// can't drift away from docs/specs/2026-06-15-paired-permission-safe-doc-writes.md).
|
|
59
|
+
// Mirrors the PreToolUse policy: reads always; bypass mode always; acceptEdits for
|
|
60
|
+
// non-high writes; mandated safe-doc writes always; per tool:risk "always allow".
|
|
61
|
+
export function autoAllow({ toolName, input, mode = 'default', alwaysAllow = new Set() }) {
|
|
62
|
+
const risk = classifyRisk(toolName, input)
|
|
63
|
+
return (
|
|
64
|
+
risk === 'low' ||
|
|
65
|
+
mode === 'bypassPermissions' ||
|
|
66
|
+
(mode === 'acceptEdits' && WRITE_TOOLS.has(toolName) && risk !== 'high') ||
|
|
67
|
+
isSafeDocWrite(toolName, input) ||
|
|
68
|
+
alwaysAllow.has(`${toolName}:${risk}`)
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
// ── input stream — a generator we keep open and feed turns into ──
|
|
58
73
|
function makeInputStream() {
|
|
59
74
|
const queue = []
|
|
@@ -108,6 +123,7 @@ export function startClaudeSession({ cwd, model, resume, onEvent, requestPermiss
|
|
|
108
123
|
let mode = 'default' // mirrors Claude Code's ⇧⇥ cycle
|
|
109
124
|
const alwaysAllow = new Set() // tool:risk signatures the user chose "don't ask again" for
|
|
110
125
|
const toolStart = new Map() // tool_use id → start time, for the duration badge
|
|
126
|
+
let effort = null // active reasoning effort for the turn (from the hook input)
|
|
111
127
|
|
|
112
128
|
const emit = (evt) => { try { onEvent?.(evt) } catch { /* never let a consumer throw into the loop */ } }
|
|
113
129
|
|
|
@@ -118,6 +134,11 @@ export function startClaudeSession({ cwd, model, resume, onEvent, requestPermiss
|
|
|
118
134
|
const preTool = async (hookInput) => {
|
|
119
135
|
const toolName = hookInput.tool_name
|
|
120
136
|
const toolInput = hookInput.tool_input
|
|
137
|
+
// The hook input carries the turn's active reasoning effort (post any
|
|
138
|
+
// model downgrade) — the real signal for "thinking with <X> effort".
|
|
139
|
+
// Surface it to the room when it changes; absent on models without effort.
|
|
140
|
+
const lvl = hookInput.effort?.level
|
|
141
|
+
if (lvl && lvl !== effort) { effort = lvl; emit({ kind: 'effort', level: effort }) }
|
|
121
142
|
// ── Plan approval — ExitPlanMode is how the agent presents its plan in
|
|
122
143
|
// plan mode. Render a dedicated plan card (not the generic perm card) with
|
|
123
144
|
// three outcomes: run (exit → default), accept (exit → acceptEdits), keep
|
|
@@ -162,12 +183,7 @@ export function startClaudeSession({ cwd, model, resume, onEvent, requestPermiss
|
|
|
162
183
|
// never silently allows a future destructive one (high always re-asks).
|
|
163
184
|
const sig = `${toolName}:${risk}`
|
|
164
185
|
const safeDoc = isSafeDocWrite(toolName, toolInput)
|
|
165
|
-
const auto =
|
|
166
|
-
risk === 'low' ||
|
|
167
|
-
mode === 'bypassPermissions' ||
|
|
168
|
-
(mode === 'acceptEdits' && WRITE_TOOLS.has(toolName) && risk !== 'high') ||
|
|
169
|
-
safeDoc ||
|
|
170
|
-
alwaysAllow.has(sig)
|
|
186
|
+
const auto = autoAllow({ toolName, input: toolInput, mode, alwaysAllow })
|
|
171
187
|
let decision = 'allow'
|
|
172
188
|
if (!auto) {
|
|
173
189
|
try {
|
|
@@ -186,7 +202,9 @@ export function startClaudeSession({ cwd, model, resume, onEvent, requestPermiss
|
|
|
186
202
|
permissionDecision: denied ? 'deny' : 'allow',
|
|
187
203
|
permissionDecisionReason: denied
|
|
188
204
|
? 'Denied by the user in the ThinkPool room. Do not retry this tool — ask what to do instead.'
|
|
189
|
-
: auto
|
|
205
|
+
: auto
|
|
206
|
+
? (safeDoc ? 'Auto-approved (mandated doc write — .claude/SESSIONS/ or CLAUDE.md).' : `Auto-approved (${mode}).`)
|
|
207
|
+
: 'Approved in the ThinkPool room.',
|
|
190
208
|
},
|
|
191
209
|
}
|
|
192
210
|
}
|
|
@@ -195,6 +213,10 @@ export function startClaudeSession({ cwd, model, resume, onEvent, requestPermiss
|
|
|
195
213
|
abortController: ac,
|
|
196
214
|
permissionMode: 'default',
|
|
197
215
|
hooks: { PreToolUse: [{ hooks: [preTool] }] },
|
|
216
|
+
// Needed for live thinking-token progress (SDKThinkingTokensMessage) to
|
|
217
|
+
// flow during a turn. We ignore the fine-grained stream_event partials in
|
|
218
|
+
// the loop; only the coarse thinking_tokens system message is surfaced.
|
|
219
|
+
includePartialMessages: true,
|
|
198
220
|
}
|
|
199
221
|
if (cwd) opts.cwd = cwd
|
|
200
222
|
if (model) opts.model = model
|
|
@@ -207,6 +229,13 @@ export function startClaudeSession({ cwd, model, resume, onEvent, requestPermiss
|
|
|
207
229
|
if (closed) break
|
|
208
230
|
switch (m.type) {
|
|
209
231
|
case 'system':
|
|
232
|
+
// Live thinking-token progress — the running estimate while the
|
|
233
|
+
// model reasons, surfaced as the indicator's ↓ N tokens. Coarse,
|
|
234
|
+
// emitted during extended thinking; not a per-token stream.
|
|
235
|
+
if (m.subtype === 'thinking_tokens') {
|
|
236
|
+
emit({ kind: 'thinking_tokens', tokens: m.estimated_tokens, delta: m.estimated_tokens_delta })
|
|
237
|
+
break
|
|
238
|
+
}
|
|
210
239
|
if (m.session_id) sessionId = m.session_id
|
|
211
240
|
emit({ kind: 'system', sessionId, model: m.model || model || null })
|
|
212
241
|
break
|