thinkpool-pair 0.7.42 → 0.7.43
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 +14 -2
- package/claude-session.mjs +37 -0
- package/package.json +1 -1
package/bridge.mjs
CHANGED
|
@@ -151,7 +151,9 @@ if (argv[0] === 'install-service' || argv[0] === 'uninstall-service') {
|
|
|
151
151
|
// shell export can't reach a serviced bridge — provider.json is its own source
|
|
152
152
|
// of truth. Idempotent; a no-op for the default Anthropic provider. See provider.mjs.
|
|
153
153
|
const { applyProviderEnv } = await import('./provider.mjs')
|
|
154
|
-
|
|
154
|
+
// Captured so the room announce can report the active LLM provider (anthropic,
|
|
155
|
+
// or a custom Anthropic-compatible endpoint) to the web welcome banner.
|
|
156
|
+
const PROVIDER_INFO = applyProviderEnv()
|
|
155
157
|
|
|
156
158
|
if (argv[0] === 'login') { const { runLogin } = await import('./account.mjs'); await runLogin(SUPABASE_URL, SUPABASE_ANON, WEB_BASE) }
|
|
157
159
|
if (argv[0] === 'bind') { const { runBind } = await import('./account.mjs'); runBind(argv[1], argv[2]) }
|
|
@@ -415,6 +417,14 @@ const announce = () =>
|
|
|
415
417
|
// cwd + version: the host's working dir + thinkpool-pair version, shown in
|
|
416
418
|
// the room's welcome banner. Re-sent per announce so late joiners get them.
|
|
417
419
|
cwd, version: VERSION,
|
|
420
|
+
// provider: the LLM endpoint this bridge drives Claude Code through —
|
|
421
|
+
// 'anthropic' (the default, the host's regular Claude login) or 'custom'
|
|
422
|
+
// (any Anthropic-compatible base url: GLM/Z.ai, OpenRouter, a proxy). Shown
|
|
423
|
+
// in the room banner so both drivers see whose model they're on.
|
|
424
|
+
// providerBaseUrl rides along only for custom so the banner can show the
|
|
425
|
+
// host (e.g. openrouter.ai). Older web clients ignore unknown fields.
|
|
426
|
+
provider: PROVIDER_INFO.provider,
|
|
427
|
+
providerBaseUrl: PROVIDER_INFO.baseUrl || undefined,
|
|
418
428
|
// pendingUpdate: a newer thinkpool-pair is out — drives the room's "update ready"
|
|
419
429
|
// chip; re-sent per announce so reloads/late-joiners see it (Slice 3).
|
|
420
430
|
pendingUpdate,
|
|
@@ -702,7 +712,9 @@ function openStructured({ id, model, resume, log, commands, mode }) {
|
|
|
702
712
|
// 'stalled' is transient turn-liveness state (would replay as a stale frozen
|
|
703
713
|
// banner if persisted); 'compaction' is a real transcript milestone (the recap
|
|
704
714
|
// card) and is intentionally NOT chrome, so it persists + replays.
|
|
705
|
-
|
|
715
|
+
// 'suggestion' is Claude Code's predicted-next-prompt ghost text — ephemeral
|
|
716
|
+
// composer UI, broadcast to both clients but never logged/persisted/replayed.
|
|
717
|
+
const chrome = evt.kind === 'mode' || evt.kind === 'usage' || evt.kind === 'clear' || evt.kind === 'compact' || evt.kind === 'stalled' || evt.kind === 'suggestion'
|
|
706
718
|
// compact turn finished (or errored) → clear the pulsing indicator.
|
|
707
719
|
// No "done" ctl line — the SDK's own output in the transcript is the
|
|
708
720
|
// signal (compact summary on success, AbortError text on abort/too-small).
|
package/claude-session.mjs
CHANGED
|
@@ -125,6 +125,14 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
125
125
|
let input = makeInputStream() // `let`: auto-restart swaps in a fresh stream
|
|
126
126
|
let sessionId = resume || null
|
|
127
127
|
let closed = false
|
|
128
|
+
// Cold-start measurement (2026-06-21, spec 2026-06-21-code-session-deploy-stability):
|
|
129
|
+
// stamp when the open begins so we can log spawn→ready (the SDK's first `init`
|
|
130
|
+
// system message — when the terminal becomes usable). New room terminals "take a
|
|
131
|
+
// long time"; settingSources:['user','project','local'] boots every host MCP
|
|
132
|
+
// server + SessionStart hook + plugin before init. This is the A-side baseline;
|
|
133
|
+
// open a terminal with TP_MCP_STRICT=1 (below) for the B-side to isolate MCP's share.
|
|
134
|
+
const spawnT0 = Date.now()
|
|
135
|
+
let readyLogged = false
|
|
128
136
|
let q = null // the live Query — control requests (interrupt /
|
|
129
137
|
// setPermissionMode) route through it once streaming.
|
|
130
138
|
// The session is CREATED in the caller's chosen mode (not hard-coded default):
|
|
@@ -330,7 +338,22 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
330
338
|
// flow during a turn. We ignore the fine-grained stream_event partials in
|
|
331
339
|
// the loop; only the coarse thinking_tokens system message is surfaced.
|
|
332
340
|
includePartialMessages: true,
|
|
341
|
+
// Claude Code's own "predicted next prompt" — the SDK emits at most one
|
|
342
|
+
// `prompt_suggestion` per turn, AFTER the `result` message, suppressed on the
|
|
343
|
+
// first turn / after API errors / in plan mode, and it piggybacks the parent's
|
|
344
|
+
// prompt cache (nearly free). We surface it verbatim to the room composer as a
|
|
345
|
+
// ghost-text chip; nothing is generated on our side. The loop keeps iterating
|
|
346
|
+
// past `result`, so the post-result suggestion is received. Global off switch:
|
|
347
|
+
// CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false.
|
|
348
|
+
promptSuggestions: true,
|
|
333
349
|
}
|
|
350
|
+
// Measurement lever (default OFF — no behavior change). TP_MCP_STRICT=1 makes the
|
|
351
|
+
// room session skip booting the host's filesystem-configured MCP servers
|
|
352
|
+
// (strictMcpConfig + empty mcpServers map), isolating the MCP share of cold-start
|
|
353
|
+
// in the spawn→ready log below. settingSources still loads slash commands /
|
|
354
|
+
// CLAUDE.md / agents either way — strict only governs MCP server sourcing. Flip
|
|
355
|
+
// this to the default (or curate a slim allowlist) once the numbers justify it.
|
|
356
|
+
if (process.env.TP_MCP_STRICT === '1') { opts.strictMcpConfig = true; opts.mcpServers = {} }
|
|
334
357
|
if (cwd) opts.cwd = cwd
|
|
335
358
|
if (model) opts.model = model
|
|
336
359
|
if (resume) opts.resume = resume
|
|
@@ -372,6 +395,14 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
372
395
|
break
|
|
373
396
|
}
|
|
374
397
|
if (m.session_id) sessionId = m.session_id
|
|
398
|
+
// Cold-start timing — the init system message is when the terminal is
|
|
399
|
+
// usable, so spawn→here IS the latency the user waits on. One line per
|
|
400
|
+
// open. Compare a normal open vs one with TP_MCP_STRICT=1 to read off how
|
|
401
|
+
// many ms the host MCP servers cost. (resume opens also pay sanitizeSession.)
|
|
402
|
+
if (!readyLogged) {
|
|
403
|
+
readyLogged = true
|
|
404
|
+
process.stderr.write(`\n ◆ session ready in ${Date.now() - spawnT0}ms — MCP ${process.env.TP_MCP_STRICT === '1' ? 'OFF' : 'on'}${resume ? ', resume' : ', fresh'}.\n`)
|
|
405
|
+
}
|
|
375
406
|
// m.slash_commands (init message) — the commands this session really
|
|
376
407
|
// supports: built-ins + the host's custom .claude/commands. Surfaced
|
|
377
408
|
// so the room composer's autocomplete lists what ACTUALLY exists.
|
|
@@ -432,6 +463,12 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
|
|
|
432
463
|
emit({ kind: 'usage', costUsd: m.total_cost_usd ?? null, tokens: (u.input_tokens || 0) + (u.output_tokens || 0), ctx })
|
|
433
464
|
})()
|
|
434
465
|
break
|
|
466
|
+
case 'prompt_suggestion':
|
|
467
|
+
// Claude Code's own predicted next prompt (promptSuggestions opt-in).
|
|
468
|
+
// Arrives once per turn AFTER `result`; surfaced verbatim as ephemeral
|
|
469
|
+
// chrome (a ghost-text chip in the room composer), never persisted.
|
|
470
|
+
if (m.suggestion) emit({ kind: 'suggestion', text: m.suggestion })
|
|
471
|
+
break
|
|
435
472
|
default:
|
|
436
473
|
break
|
|
437
474
|
}
|