thinkpool-pair 0.7.128 → 0.7.129

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
@@ -83,7 +83,7 @@ const flowRedispatch = new Map()
83
83
  const flowBudgets = new Map()
84
84
  import { formatPeek, PEEK, siblingsOf, resolveSibling, crossPostDecision, CROSSPOST, spawnDecision, CROSSROOM, formatPairRoster, crossRoomPostDecision } from './cross-terminal.mjs'
85
85
  import { turnInFlight } from './update-gate.mjs'
86
- import { saveSession, flushSession, deleteSession, loadAll, canResume, loadPtyId, savePtyId, loadNames, saveNames, appendDurableEvents, seedDurableEvents, readDurablePage, readDurableOldestSeq } from './session-store.mjs'
86
+ import { saveSession, flushSession, deleteSession, loadAll, canResume, loadPtyId, savePtyId, loadNames, saveNames, appendDurableEvents, seedDurableEvents, readDurablePage, readDurableOldestSeq, loadSummary, saveSummary } from './session-store.mjs'
87
87
  import { stampEvent, makeSeqCounter, maxSeq, seqable, capReplayEvents, chunkReplayEvents, boundEventForBroadcast, inlineImageBlocks } from './event-id.mjs'
88
88
  import { makeThrottledTrack } from './presence.mjs'
89
89
 
@@ -2213,6 +2213,13 @@ channel
2213
2213
  // re-open within a page load).
2214
2214
  .on('broadcast', { event: 'code-summarize' }, async ({ payload }) => {
2215
2215
  const by = payload?.by || null
2216
+ // Peek — any device opening the panel fetches the cached recap (cross-device +
2217
+ // cross-restart persistence) WITHOUT spending a model call. No cache → silent.
2218
+ if (payload?.peek) {
2219
+ const cached = loadSummary(room)
2220
+ if (cached && cached.text) bcast('code-summary', { status: 'done', ...cached })
2221
+ return
2222
+ }
2216
2223
  bcast('code-summary', { status: 'generating', by })
2217
2224
  try {
2218
2225
  // Pull assistant/user text out of a transcript-event array, keep the tail.
@@ -2253,18 +2260,23 @@ channel
2253
2260
  ? `\n\nThere are also ${closedIds.length} CLOSED terminal(s). Recent excerpts (do NOT name them individually):\n${(closedDigests.join('\n---\n') || '(no retained detail)').slice(0, 1400)}`
2254
2261
  : ''
2255
2262
 
2256
- const prompt = `Write a concise recap of this multi-terminal coding session for someone glancing at it. Rules:
2257
- - NAME each LIVE terminal and say whether it is working or done, and briefly what it is doing/did.
2258
- - Do NOT name the CLOSED terminals individually — fold them into ONE aggregate clause ("plus N closed lanes that handled …").
2259
- - 2 to 4 sentences total, plain prose, no markdown, no headers, no bullet lists.
2263
+ const prompt = `Summarize this multi-terminal coding session as a STRUCTURED LIST. Output ONLY these lines, nothing else:
2264
+ - ONE line per LIVE terminal, EXACTLY: <terminal name> | <working or done> | <one clause, max 12 words, no trailing period>
2265
+ - If there are CLOSED terminals, ONE final line, EXACTLY: CLOSED | <count> | <one aggregate clause about what they handled — do NOT name them>
2266
+ Use the pipe "|" as the separator. No headers, no markdown, no bullets, no extra prose. Example:
2267
+ Bridge Whip | working | SDK 0.7.128, detection gates live
2268
+ Reddit | done | GTM strategy mapped
2269
+ CLOSED | 52 | flow-trigger churn and @pool integration
2260
2270
 
2261
2271
  ${liveBlock}${closedBlock}
2262
2272
 
2263
- Recap:`
2273
+ Output:`
2264
2274
 
2265
2275
  const res = await oneShotSummary({ prompt, env: env || process.env, cwd: cwd || process.cwd() })
2266
2276
  if (!res || !res.text) { bcast('code-summary', { status: 'error', error: 'Summary model unavailable on this provider.', by }); return }
2267
- bcast('code-summary', { status: 'done', text: res.text, model: res.model, generatedAt: Date.now(), by })
2277
+ const summary = { text: res.text, model: res.model, generatedAt: Date.now() }
2278
+ try { saveSummary(room, summary) } catch { /* cache is best-effort */ }
2279
+ bcast('code-summary', { status: 'done', ...summary, by })
2268
2280
  process.stderr.write(`\n ◆ session summary generated (${res.model}) for ${by || 'someone'}.\n`)
2269
2281
  } catch (e) {
2270
2282
  bcast('code-summary', { status: 'error', error: String(e?.message || e).slice(0, 140), by })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.128",
3
+ "version": "0.7.129",
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": {
package/session-store.mjs CHANGED
@@ -191,3 +191,14 @@ export function loadNames(room) {
191
191
  export function saveNames(room, names) {
192
192
  try { ensureDir(room); fs.writeFileSync(namesFile(room), JSON.stringify(names || {})) } catch { /* noop */ }
193
193
  }
194
+
195
+ // Session recap cache (2026-07-02, Session Info panel). The last on-demand summary,
196
+ // stored as a dotfile (NOT *.json → never in loadAll) so it survives a bridge restart
197
+ // and any device that opens the panel can fetch it (persists across devices). One per room.
198
+ const summaryFile = (room) => path.join(dir(room), '.summary')
199
+ export function loadSummary(room) {
200
+ try { return JSON.parse(fs.readFileSync(summaryFile(room), 'utf8')) || null } catch { return null }
201
+ }
202
+ export function saveSummary(room, summary) {
203
+ try { ensureDir(room); fs.writeFileSync(summaryFile(room), JSON.stringify(summary || null)) } catch { /* noop */ }
204
+ }