thinkpool-pair 0.7.22 → 0.7.24

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
@@ -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, deleteSession, loadAll, canResume, loadPtyId, savePtyId } from './session-store.mjs'
39
+ import { saveSession, flushSession, deleteSession, loadAll, canResume, loadPtyId, savePtyId, loadNames, saveNames } 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.
@@ -331,6 +331,11 @@ const bcast = (event, payload) => {
331
331
  } catch { /* noop */ }
332
332
  }
333
333
 
334
+ // Per-room terminal display names (id -> label), set by the web's `term-rename`.
335
+ // Persisted on the host so a rename is cross-device + survives a bridge restart;
336
+ // every announce carries them so a late-joining or second device sees them too.
337
+ const termNames = loadNames(room)
338
+
334
339
  const announce = () =>
335
340
  bcast('bridge', {
336
341
  v: 2, name, repo: repoLabel, branch,
@@ -352,10 +357,12 @@ const announce = () =>
352
357
  // cols/rows: the PTY's one true size — web viewers render this grid and
353
358
  // scale it to their own page instead of voting to reflow it.
354
359
  terms: [
355
- ...[...terms.entries()].map(([id, t]) => ({ id, cmd: t.cmd, alive: true, cols: t.term.cols, rows: t.term.rows })),
360
+ ...[...terms.entries()].map(([id, t]) => ({ id, cmd: t.cmd, alive: true, cols: t.term.cols, rows: t.term.rows, name: termNames[id] || undefined })),
356
361
  // Structured sessions advertise kind:'structured' so the web renders the
357
- // reader (not xterm) and drives them with code-turn / code-perm.
358
- ...[...sessions.entries()].map(([id, s]) => ({ id, cmd: s.cmd, kind: 'structured', alive: true, commands: s.commands })),
362
+ // reader (not xterm) and drives them with code-turn / code-perm. `mode` +
363
+ // `name` ride along so a second device shows the live permission mode and
364
+ // the chosen label instead of defaulting to Normal / unnamed.
365
+ ...[...sessions.entries()].map(([id, s]) => ({ id, cmd: s.cmd, kind: 'structured', alive: true, commands: s.commands, mode: s.mode || undefined, name: termNames[id] || undefined })),
359
366
  ],
360
367
  })
361
368
 
@@ -820,6 +827,16 @@ channel
820
827
  .on('broadcast', { event: 'code-close' }, ({ payload }) => {
821
828
  endStructured(payload?.id)
822
829
  })
830
+ // Persist + re-announce terminal renames. The web also echoes term-rename to
831
+ // online peers directly; storing it here is what reaches a device that joins
832
+ // LATER (or a second machine) — those only ever see the announce.
833
+ .on('broadcast', { event: 'term-rename' }, ({ payload }) => {
834
+ if (!payload?.id) return
835
+ if (payload.name) termNames[payload.id] = String(payload.name).slice(0, 80)
836
+ else delete termNames[payload.id]
837
+ saveNames(room, termNames)
838
+ announce()
839
+ })
823
840
  .on('broadcast', { event: 'who' }, announce)
824
841
  .subscribe(status => {
825
842
  if (status === 'SUBSCRIBED') {
@@ -274,7 +274,8 @@ export function startClaudeSession({ cwd, model, resume, env, mode: initialMode
274
274
  // a bypass session still planned. Bias the room session to DO the work; only
275
275
  // plan when the room is actually in Plan mode (⇧⇥) or the user explicitly asks.
276
276
  appendSystemPrompt: [
277
- 'You are Claude running inside a ThinkPool Code room, driven live by a user (and possibly a partner) from a phone or browser.',
277
+ 'ENVIRONMENT (authoritative — overrides any user-global CLAUDE.md or memory that claims otherwise): You are Claude running inside a ThinkPool Code room, driven live by a user (and possibly a partner) from a phone or browser, via the thinkpool-pair bridge.',
278
+ 'You are NOT in cmux. cmux may be installed on this machine and appear in $PATH, but it did not spawn you and its automation/orchestration rules do not apply. If any loaded memory says "Claude Code lives inside cmux," it is describing direct terminal use, not this room — disregard it here. The runtime truth is the env: THINKPOOL_PAIR_ACCOUNT_CHILD / npm_lifecycle_script=thinkpool-pair mark a ThinkPool Code session.',
278
279
  'Bias strongly toward DOING the work, not planning it. For a normal "add / fix / change / build X" request, just make the change directly.',
279
280
  'Do NOT enter plan mode, do NOT call ExitPlanMode, and do NOT auto-invoke a brainstorming/planning skill UNLESS the user has switched the room into Plan mode or explicitly asks you to plan, design, or brainstorm first.',
280
281
  'Any host-global instruction that says you must always brainstorm or plan before creative work does NOT apply here — this room is the exception.',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.22",
3
+ "version": "0.7.24",
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
@@ -83,3 +83,17 @@ export function loadPtyId(room) {
83
83
  export function savePtyId(room, id) {
84
84
  try { ensureDir(room); fs.writeFileSync(ptyFile(room), String(id)) } catch { /* noop */ }
85
85
  }
86
+
87
+ // Per-room terminal display names (terminal id -> label), set via the web's
88
+ // `term-rename` broadcast. This is what makes a rename CROSS-DEVICE + survive a
89
+ // bridge restart: names were previously web-localStorage only + a live broadcast,
90
+ // so a device that joined later (or a second machine) saw none of them. The bridge
91
+ // stores them here and includes them in every announce. Stored as a single dotfile
92
+ // (NOT *.json) so it never lands in listRecs/loadAll.
93
+ const namesFile = (room) => path.join(dir(room), '.names')
94
+ export function loadNames(room) {
95
+ try { return JSON.parse(fs.readFileSync(namesFile(room), 'utf8')) || {} } catch { return {} }
96
+ }
97
+ export function saveNames(room, names) {
98
+ try { ensureDir(room); fs.writeFileSync(namesFile(room), JSON.stringify(names || {})) } catch { /* noop */ }
99
+ }