thinkpool-pair 0.7.12 → 0.7.14
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 -5
- package/claude-session.mjs +31 -1
- package/package.json +1 -1
- package/session-store.mjs +8 -0
package/bridge.mjs
CHANGED
|
@@ -36,7 +36,7 @@ import { spawn } from 'node:child_process'
|
|
|
36
36
|
import { randomUUID } from 'node:crypto'
|
|
37
37
|
import { createClient } from '@supabase/supabase-js'
|
|
38
38
|
import { startClaudeSession } from './claude-session.mjs'
|
|
39
|
-
import { saveSession, flushSession,
|
|
39
|
+
import { saveSession, flushSession, loadAll, canResume, loadPtyId, savePtyId } from './session-store.mjs'
|
|
40
40
|
|
|
41
41
|
// Public client creds (the same anon values the web app ships — safe to embed).
|
|
42
42
|
// Override with TP_SUPABASE_URL / TP_SUPABASE_ANON if you ever need to.
|
|
@@ -776,11 +776,15 @@ channel
|
|
|
776
776
|
const startCmd = attachedCmd || autoAgent // autoAgent: account-mode auto-open (headless)
|
|
777
777
|
if (startCmd && !terms.size && !sessions.size) {
|
|
778
778
|
// Claude + structured mode → Agent SDK session; everything else → PTY.
|
|
779
|
-
// On restart, restore
|
|
780
|
-
// replay
|
|
779
|
+
// On restart, restore EVERY saved structured session for this room (not
|
|
780
|
+
// just the latest) — replay each transcript + resume its live SDK context
|
|
781
|
+
// when recent enough. Without this, backgrounded terminals died on a
|
|
782
|
+
// bridge restart even though their state was on disk. Bounded by KEEP=8;
|
|
783
|
+
// only runs on a fresh start (guard above), so a network re-subscribe
|
|
784
|
+
// with live sessions won't re-spawn them.
|
|
781
785
|
if (wantStructured(startCmd)) {
|
|
782
|
-
const
|
|
783
|
-
if (
|
|
786
|
+
const all = loadAll(room).filter((r) => r.log?.length || r.sessionId)
|
|
787
|
+
if (all.length) for (const rec of all) openStructured({ id: rec.id, resume: canResume(rec) ? rec.sessionId : undefined, log: rec.log, commands: rec.commands, mode: rec.mode })
|
|
784
788
|
else openStructured({ id: randomUUID() })
|
|
785
789
|
}
|
|
786
790
|
else {
|
package/claude-session.mjs
CHANGED
|
@@ -127,6 +127,15 @@ export function startClaudeSession({ cwd, model, resume, mode: initialMode = 'de
|
|
|
127
127
|
const alwaysAllow = new Set() // tool:risk signatures the user chose "don't ask again" for
|
|
128
128
|
const toolStart = new Map() // tool_use id → start time, for the duration badge
|
|
129
129
|
let effort = null // active reasoning effort for the turn (from the hook input)
|
|
130
|
+
// Live token count for the thinking indicator — mirrors Claude Code's
|
|
131
|
+
// "↓ N tokens": the turn's billed OUTPUT tokens (thinking is billed as
|
|
132
|
+
// output, so this is the full count, not just the reasoning estimate). A
|
|
133
|
+
// tool-using turn emits several assistant messages; message_delta carries
|
|
134
|
+
// the running output_tokens for the CURRENT message, so we fold finished
|
|
135
|
+
// messages into turnBaseOut and add the live message's count on top. Reset
|
|
136
|
+
// per turn on `result`.
|
|
137
|
+
let turnBaseOut = 0 // output tokens from completed messages this turn
|
|
138
|
+
let curMsgOut = 0 // latest output_tokens for the in-flight message
|
|
130
139
|
|
|
131
140
|
const emit = (evt) => { try { onEvent?.(evt) } catch { /* never let a consumer throw into the loop */ } }
|
|
132
141
|
|
|
@@ -286,7 +295,11 @@ export function startClaudeSession({ cwd, model, resume, mode: initialMode = 'de
|
|
|
286
295
|
// model reasons, surfaced as the indicator's ↓ N tokens. Coarse,
|
|
287
296
|
// emitted during extended thinking; not a per-token stream.
|
|
288
297
|
if (m.subtype === 'thinking_tokens') {
|
|
289
|
-
|
|
298
|
+
// The reasoning-phase estimate (smooth, but approximate). Only
|
|
299
|
+
// surface it BEFORE real output streams — once message_delta gives
|
|
300
|
+
// us authoritative output_tokens (which already include thinking),
|
|
301
|
+
// that supersedes the estimate so the count never jumps backwards.
|
|
302
|
+
if (turnBaseOut + curMsgOut === 0) emit({ kind: 'thinking_tokens', tokens: m.estimated_tokens, delta: m.estimated_tokens_delta })
|
|
290
303
|
break
|
|
291
304
|
}
|
|
292
305
|
if (m.session_id) sessionId = m.session_id
|
|
@@ -312,8 +325,25 @@ export function startClaudeSession({ cwd, model, resume, mode: initialMode = 'de
|
|
|
312
325
|
}
|
|
313
326
|
}
|
|
314
327
|
break
|
|
328
|
+
case 'stream_event': {
|
|
329
|
+
// Live output-token progress for the thinking indicator. message_delta
|
|
330
|
+
// carries the running output_tokens for the current assistant message;
|
|
331
|
+
// message_start opens a new one (fold the finished message into the
|
|
332
|
+
// turn base first). Authoritative + monotonic within a turn.
|
|
333
|
+
const ev = m.event
|
|
334
|
+
if (ev?.type === 'message_start') {
|
|
335
|
+
turnBaseOut += curMsgOut
|
|
336
|
+
curMsgOut = ev.message?.usage?.output_tokens || 0
|
|
337
|
+
emit({ kind: 'thinking_tokens', tokens: turnBaseOut + curMsgOut })
|
|
338
|
+
} else if (ev?.type === 'message_delta' && ev.usage) {
|
|
339
|
+
curMsgOut = ev.usage.output_tokens ?? curMsgOut
|
|
340
|
+
emit({ kind: 'thinking_tokens', tokens: turnBaseOut + curMsgOut })
|
|
341
|
+
}
|
|
342
|
+
break
|
|
343
|
+
}
|
|
315
344
|
case 'result':
|
|
316
345
|
if (m.session_id) sessionId = m.session_id
|
|
346
|
+
turnBaseOut = 0; curMsgOut = 0 // reset the live token count for the next turn
|
|
317
347
|
emit({ kind: 'result', subtype: m.subtype, sessionId, costUsd: m.total_cost_usd, usage: m.usage, numTurns: m.num_turns })
|
|
318
348
|
// Surface a usage/context meter (chrome, not a transcript line). The
|
|
319
349
|
// context window % comes from the control request; cost is cumulative.
|
package/package.json
CHANGED
package/session-store.mjs
CHANGED
|
@@ -56,6 +56,14 @@ export function canResume(rec) {
|
|
|
56
56
|
return !!(rec && rec.sessionId && rec.savedAt && (Date.now() - rec.savedAt) < RESUME_MAX_AGE_MS)
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// EVERY saved session for the room, oldest→newest (so tabs reappear in order).
|
|
60
|
+
// Drives resume-ALL on a bridge restart — without this only the latest session
|
|
61
|
+
// came back and every backgrounded terminal died on restart despite its state
|
|
62
|
+
// sitting on disk. Bounded by KEEP (prune keeps the newest 8).
|
|
63
|
+
export function loadAll(room) {
|
|
64
|
+
return listRecs(room).sort((a, b) => (a.savedAt || 0) - (b.savedAt || 0))
|
|
65
|
+
}
|
|
66
|
+
|
|
59
67
|
// Last PTY terminal id auto-opened for an attached command. A reconnecting
|
|
60
68
|
// bridge reuses it so it re-attaches to the SAME terminal tab instead of
|
|
61
69
|
// minting a fresh UUID and breeding a new dormant "Terminal N" each restart.
|