thinkpool-pair 0.7.225 → 0.7.227

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/account.mjs CHANGED
@@ -778,7 +778,7 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
778
778
  if (!tc || roomPartner.get(target) !== partnerUid) return reply({ error: `Room ${target} is not a room this device shares with you.` })
779
779
  const reqId = randomUUID()
780
780
  pairPending.set(reqId, { reply: (mm) => reply({ ok: mm.ok, ref: mm.ref, error: mm.error }), askerRoom: null })
781
- try { tc.send({ t: 'pair-post-local', reqId, fromRoom: payload.fromRoom, fromHost: payload.fromHost, text: payload.text, terminal: payload.terminal }) }
781
+ try { tc.send({ t: 'pair-post-local', reqId, fromRoom: payload.fromRoom, fromHost: payload.fromHost, fromTerminalName: payload.fromTerminalName, text: payload.text, terminal: payload.terminal }) }
782
782
  catch { pairPending.delete(reqId); reply({ error: `Could not reach room ${target}.` }) }
783
783
  }
784
784
  // Join the per-pair bus for `partnerUid` (idempotent). The channel is PRIVATE so
@@ -801,7 +801,7 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
801
801
  // Forward a local child's cross-room request to the bus (Tier 2 peek / Tier 3 post)
802
802
  // when the target room is NOT served on this machine. Used inside the child handler.
803
803
  const busPeek = (targetRoom, terminal, lines) => busRequestFirst('peer-peek-req', { targetRoom, terminal, lines }, CROSSROOM_BUS.busTimeoutMs)
804
- const busPost = (targetRoom, terminal, text, fromRoom) => busRequestFirst('peer-post-req', { targetRoom, terminal, text, fromRoom, fromHost: machine }, 180_000)
804
+ const busPost = (targetRoom, terminal, text, fromRoom, fromTerminalName) => busRequestFirst('peer-post-req', { targetRoom, terminal, text, fromRoom, fromHost: machine, fromTerminalName }, 180_000)
805
805
 
806
806
  // Re-entrancy guard: bind-room fires tick() AND a 15s interval fires tick(); tick
807
807
  // awaits a code_sessions query before children.set(), so two overlapping runs can
@@ -953,13 +953,13 @@ export async function runAccount(SUPABASE_URL, SUPABASE_ANON) {
953
953
  if (tc) { // LOCAL target — the target child raises its own receipt card
954
954
  const reqId = randomUUID()
955
955
  pairPending.set(reqId, { reply: (mm) => reply({ ok: mm.ok, ref: mm.ref, error: mm.error }), askerRoom: room })
956
- try { tc.send({ t: 'pair-post-local', reqId, fromRoom: room, fromHost: machine, text: m.text, terminal: m.terminal }) }
956
+ try { tc.send({ t: 'pair-post-local', reqId, fromRoom: room, fromHost: machine, fromTerminalName: m.fromTerminalName, text: m.text, terminal: m.terminal }) }
957
957
  catch { pairPending.delete(reqId); reply({ error: `Could not reach session ${target}.` }) }
958
958
  return
959
959
  }
960
960
  // REMOTE target — over the pair bus.
961
961
  if (!pairChannels.size) return reply({ error: `Room ${target} is not on this machine, and no pair bus is open to reach it.` })
962
- const res = await busPost(target, m.terminal, m.text, room)
962
+ const res = await busPost(target, m.terminal, m.text, room, m.fromTerminalName)
963
963
  if (!res) return reply({ error: `Room ${target} did not respond to the hand-off in time.` })
964
964
  reply({ ok: res.ok, ref: res.ref, error: res.error })
965
965
  })()
package/bridge.mjs CHANGED
@@ -1123,7 +1123,7 @@ function raiseCard(entry, req) {
1123
1123
  // the target agent lane, raise the receipt card, and on approval inject the turn one
1124
1124
  // room-hop deep (so the reached agent can read/peek but cannot post onward to a third
1125
1125
  // room — crossRoomPostDecision blocks roomHop≥1). Returns { ok, ref } or { error }.
1126
- async function receiveCrossRoomPost({ fromRoom, fromHost, text, terminal }) {
1126
+ async function receiveCrossRoomPost({ fromRoom, fromHost, fromTerminalName, text, terminal }) {
1127
1127
  if (process.env.TP_PAIRBUS_OFF === '1') return { error: 'Cross-room posting is turned off in this room.' }
1128
1128
  const body = String(text || '').trim()
1129
1129
  if (!body) return { error: 'Empty cross-room post ignored.' }
@@ -1145,13 +1145,13 @@ async function receiveCrossRoomPost({ fromRoom, fromHost, text, terminal }) {
1145
1145
  if (!te?.session) return { error: `That terminal in room ${room} is no longer live.` }
1146
1146
  const fromLabel = `${fromRoom}${fromHost ? ` on ${fromHost}` : ''}`
1147
1147
  let decision = 'deny'
1148
- try { decision = await raiseCard(te, { id: randomUUID(), toolName: 'mcp__thinkpool__post_to_session', input: { session: fromRoom, host: fromHost, text: body, incoming: true }, risk: 'high' }) } catch { decision = 'deny' }
1148
+ try { decision = await raiseCard(te, { id: randomUUID(), toolName: 'mcp__thinkpool__post_to_session', input: { session: fromRoom, host: fromHost, terminalName: fromTerminalName, text: body, incoming: true }, risk: 'high' }) } catch { decision = 'deny' }
1149
1149
  if (decision !== 'allow') return { error: `The people in room ${room} declined the incoming task from ${fromLabel}.` }
1150
1150
  // Injected turn: one room-hop deep, fresh in-room budgets (the loop-breaker reset
1151
1151
  // point is code-turn — a real human turn — which this is NOT, so the deeper hop sticks).
1152
1152
  te.roomHop = 1; te.hop = (te.hop || 0) + 1; te.peekCount = 0; te.postCount = 0; te.pairPeekCount = 0; te.crossRoomPostCount = 0
1153
1153
  const msg = `[From: room ${fromLabel} — relayed via the ThinkPool cross-room Ensemble, approved by a person in this room]\n${body}`
1154
- const evt = { kind: 'you', text: msg, by: `room ${fromRoom}`, crosspost: true }
1154
+ const evt = { kind: 'you', text: msg, by: `room ${fromRoom}`, crosspost: true, relaySourceName: String(fromTerminalName || '').trim().slice(0, 80) || undefined }
1155
1155
  stampEvent(evt); pushLog(te, evt); bcast('code-event', { term: targetId, evt })
1156
1156
  try { te.session.sendTurn(msg) } catch { return { error: `Could not deliver to room ${room} — the lane may have just closed.` } }
1157
1157
  return { ok: true, ref: String(targetId).slice(0, 8) }
@@ -2047,7 +2047,7 @@ function openStructured({ id, runtime = 'claude', model, effort, resume, log, co
2047
2047
  entry.crossRoomPostCount = (entry.crossRoomPostCount || 0) + 1
2048
2048
  // Generous timeout: the TARGET room's person must approve a receipt card,
2049
2049
  // which can take a while. Fails soft (never hangs the turn) past the window.
2050
- const res = await pairRequest('pair-post-req', { targetRoom: target, terminal: args?.terminal, text, fromRoom: room }, 180_000)
2050
+ const res = await pairRequest('pair-post-req', { targetRoom: target, terminal: args?.terminal, text, fromRoom: room, fromTerminalName: termNames[id] || undefined }, 180_000)
2051
2051
  if (res?.error) return okText(res.error)
2052
2052
  return okText(res?.ok ? `Delivered to room ${target}${res.ref ? ` (lane ${res.ref})` : ''} — it will respond in its own room; check back with read_session.` : `Room ${target} did not accept the message.`)
2053
2053
  },
@@ -2087,7 +2087,7 @@ function openStructured({ id, runtime = 'claude', model, effort, resume, log, co
2087
2087
  te.postCount = 0
2088
2088
  // Echo the injected prompt into the TARGET lane so both people see what
2089
2089
  // arrived (rides the existing code-event 'you' path — no new topic).
2090
- const evt = { kind: 'you', text: msg, by: `terminal ${fromRef} (agent)`, crosspost: true }
2090
+ const evt = { kind: 'you', text: msg, by: `terminal ${fromRef} (agent)`, crosspost: true, relaySourceName: termNames[id] || undefined }
2091
2091
  stampEvent(evt)
2092
2092
  pushLog(te, evt)
2093
2093
  bcast('code-event', { term: target.id, evt })
@@ -2177,7 +2177,7 @@ function openStructured({ id, runtime = 'claude', model, effort, resume, log, co
2177
2177
  // to any lane still resets it to hop 0 (code-turn). Mirrors Tier C injection.
2178
2178
  ne.hop = (entry.hop || 0) + 1; ne.peekCount = 0; ne.postCount = 0; ne.spawnTimes = []
2179
2179
  const msg = `[Task from terminal ${fromRef}'s agent — relayed via ThinkPool cross-terminal; you are a fresh lane it opened for you]\n${args.task}`
2180
- const evt = { kind: 'you', text: msg, by: `terminal ${fromRef} (agent)`, crosspost: true }
2180
+ const evt = { kind: 'you', text: msg, by: `terminal ${fromRef} (agent)`, crosspost: true, relaySourceName: termNames[id] || undefined }
2181
2181
  stampEvent(evt); pushLog(ne, evt); bcast('code-event', { term: newId, evt })
2182
2182
  try { ne.session.sendTurn(msg) } catch { return okText(`Opened lane ${newRef}${args?.name ? ` ("${args.name}")` : ''}, but it may still be starting — could not hand off the task. Try post_to_terminal shortly.`) }
2183
2183
  return okText(`Opened agent lane ${newRef}${args?.name ? ` ("${args.name}")` : ''} and handed it the task. It runs in its own lane — check back with read_terminal, then close_terminal when done.`)
package/event-bounds.mjs CHANGED
@@ -83,7 +83,7 @@ export function boundStructuredEvent(event, cap = STRUCTURED_EVENT_CAP) {
83
83
 
84
84
  // Hard fallback: retain ordering/dedup identity and recognisable tool blocks.
85
85
  const identity = {}
86
- for (const key of ['kind', 'seq', 'cid', 'ts', 'term', 'toolUseId', 'isError']) {
86
+ for (const key of ['kind', 'seq', 'cid', 'ts', 'term', 'toolUseId', 'isError', 'by', 'crosspost', 'relaySourceName']) {
87
87
  if (out[key] != null) identity[key] = out[key]
88
88
  }
89
89
  if (Array.isArray(out.blocks)) identity.blocks = out.blocks.slice(0, 4).map(block => compactBlock(block, 6000))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.225",
3
+ "version": "0.7.227",
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/viewport.mjs CHANGED
@@ -350,9 +350,13 @@ export class ViewportManager {
350
350
  await fsp.writeFile(file, result.png)
351
351
  captures[name] = { ...result, file }
352
352
  }
353
- const html = this.root && fs.existsSync(path.join(this.root, 'index.html')) ? path.join(this.root, 'index.html') : null
353
+ // A preview root may be a multi-file build. Its index.html is not a portable
354
+ // artifact: root-relative chunks cannot reliably boot when that text is later
355
+ // injected into a sandboxed srcDoc (Windows Edge exposed the permanent loader).
356
+ // preview_capture is therefore screenshot-only evidence. Editable mockups use
357
+ // render.sh's explicit trusted `source` contract instead.
354
358
  const manifest = {
355
- slug, title: String(title || 'Viewport capture').slice(0, 120), html,
359
+ slug, title: String(title || 'Viewport capture').slice(0, 120),
356
360
  desktop: captures.desktop?.file || '', mobile: captures.mobile?.file || '', ts: Date.now(),
357
361
  }
358
362
  const manifestPath = path.join(this.outbox, `${slug}.json`)