thinkpool-pair 0.7.359 → 0.7.360

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
@@ -2850,7 +2850,7 @@ function openStructured({ id, runtime = 'claude', model, models, effort, resume,
2850
2850
  // Visual QA runs in the bridge process, outside either agent runtime's sandbox.
2851
2851
  // It remains scoped to this lane's cwd and private mockup outbox.
2852
2852
  entry.viewport = new ViewportManager({
2853
- workspaceRoot: cwd || process.cwd(), ownerId: id, outbox: mockupOutbox,
2853
+ workspaceRoot: cwd || process.cwd(), ownerId: id, roomCode: room, outbox: mockupOutbox,
2854
2854
  authContext: () => ({ accessToken: codeAuthToken, supabaseUrl: SUPABASE_URL }),
2855
2855
  designContext: () => {
2856
2856
  const active = designActive.get(id)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.7.359",
3
+ "version": "0.7.360",
4
4
  "description": "Connect Claude Code, Codex, or Hermes on your computer to a Thinkpool Code room.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "bundleVersion": 19,
3
+ "bundleVersion": 20,
4
4
  "contracts": [
5
5
  {
6
6
  "id": "room-coordination",
@@ -88,13 +88,13 @@
88
88
  },
89
89
  {
90
90
  "id": "visual-proof",
91
- "version": 5,
91
+ "version": 6,
92
92
  "routes": [
93
93
  {
94
94
  "id": "visual-proof",
95
95
  "tools": ["preview_start", "preview_capture", "preview_inspect", "preview_stop"],
96
96
  "trigger": "\\b(ui|ux|visual|design|frontend|html|css|page|route|mockup|screenshot|responsive|desktop|mobile|preview)\\b",
97
- "prompt": "For built visual work, run the build, use preview_start, preview_capture at desktop and mobile, preview_inspect when rendered state matters, and preview_stop when finished. For an intentional application-preview Design card, use the project's source-aware Design build command when one exists (for example npm run build:design); otherwise build normally and keep selector-based source matching labeled as fallback. preview_capture uses a fresh isolated browser and is inline verification evidence by default; it must not create a transcript card. Set card=true only when the person asked for a mockup/Design artifact or the visual itself is an intentional deliverable that should remain editable in the transcript. A card requires settled meaningful content at both desktop and mobile, rejects loading/public-auth fallback shells, and is delivered after the final agent response. Room and protected routes automatically use the bridge account as a read-only authenticated visual harness; external REST writes, presence, ordinary room broadcasts, and refresh-token access are blocked, while non-mutating roster/transcript snapshot requests are allowed. Surface PNG evidence only when no interactive source-backed Design card is displayed."
97
+ "prompt": "For built visual work, run the build, use preview_start, preview_capture at desktop and mobile, preview_inspect when rendered state matters, and preview_stop when finished. Omit the preview path to render the current authenticated room; pass / explicitly only when the public landing page is intended. For an intentional application-preview Design card, use the project's source-aware Design build command when one exists (for example npm run build:design); otherwise build normally and keep selector-based source matching labeled as fallback. preview_capture uses a fresh isolated browser and is inline verification evidence by default; it must not create a transcript card. Set card=true only when the person asked for a mockup/Design artifact or the visual itself is an intentional deliverable that should remain editable in the transcript. A card requires settled meaningful content at both desktop and mobile, rejects loading/public-auth fallback shells, and is delivered after the final agent response. Room and protected routes automatically use the bridge account as a read-only authenticated visual harness; external REST writes, presence, ordinary room broadcasts, and refresh-token access are blocked, while non-mutating roster/transcript snapshot requests are allowed. Surface PNG evidence only when no interactive source-backed Design card is displayed."
98
98
  }
99
99
  ],
100
100
  "impact": [
package/viewport.mjs CHANGED
@@ -383,6 +383,8 @@ export class CdpBrowser {
383
383
  expression: `(() => {
384
384
  if (document.querySelector('#invitation-room-title')) return true;
385
385
  if (document.querySelector('#boot, .boot-loader')) return false;
386
+ const room = document.querySelector('[data-tp-preview-room-status]');
387
+ if (room?.dataset.tpPreviewRoomStatus === 'pending') return false;
386
388
  const loading = [...document.querySelectorAll('span')].some((node) => /^(loading session|restoring terminals…|connecting(?:…|\.\.\.))$/i.test((node.textContent || '').trim()));
387
389
  return !loading && (document.body?.innerText || '').trim().length > 1;
388
390
  })()`,
@@ -405,6 +407,7 @@ export class CdpBrowser {
405
407
  loadingSession: [...document.querySelectorAll('span')].some((node) => (node.textContent || '').trim() === 'Loading session'),
406
408
  connecting: [...document.querySelectorAll('span')].some((node) => (node.textContent || '').trim().toLowerCase() === 'connecting…'),
407
409
  invitation: !!document.querySelector('#invitation-room-title'),
410
+ roomStatus: document.querySelector('[data-tp-preview-room-status]')?.dataset.tpPreviewRoomStatus || null,
408
411
  heldLocks: locks.held?.length || 0,
409
412
  pendingLocks: locks.pending?.length || 0,
410
413
  };
@@ -413,7 +416,7 @@ export class CdpBrowser {
413
416
  returnByValue: true,
414
417
  }, sessionId).catch(() => ({ result: { value: {} } }))
415
418
  const d = diagnostic.result?.value || {}
416
- throw new Error(`Authenticated preview did not leave its loading state within 15 seconds (session=${d.sessionPresent ? 'present' : 'missing'}, inlineBoot=${d.inlineBoot ? 'yes' : 'no'}, loadingSession=${d.loadingSession ? 'yes' : 'no'}, connecting=${d.connecting ? 'yes' : 'no'}, authLocks=${d.heldLocks || 0}/${d.pendingLocks || 0}, invitation=${d.invitation ? 'yes' : 'no'}, reads=${authResponses.join(',') || 'none'}, local=${localResponses.join(',') || 'none'}, runtime=${runtimeExceptions.join(' | ') || 'none'}); no screenshot was created.`)
419
+ throw new Error(`Authenticated preview did not leave its loading state within 15 seconds (session=${d.sessionPresent ? 'present' : 'missing'}, inlineBoot=${d.inlineBoot ? 'yes' : 'no'}, loadingSession=${d.loadingSession ? 'yes' : 'no'}, connecting=${d.connecting ? 'yes' : 'no'}, roomStatus=${d.roomStatus || 'none'}, authLocks=${d.heldLocks || 0}/${d.pendingLocks || 0}, invitation=${d.invitation ? 'yes' : 'no'}, reads=${authResponses.join(',') || 'none'}, local=${localResponses.join(',') || 'none'}, runtime=${runtimeExceptions.join(' | ') || 'none'}); no screenshot was created.`)
417
420
  }
418
421
  }
419
422
  const settle = auth
@@ -585,9 +588,10 @@ export class CdpBrowser {
585
588
  export const sharedViewportBrowser = new CdpBrowser()
586
589
 
587
590
  export class ViewportManager {
588
- constructor({ workspaceRoot, ownerId, outbox, browser = sharedViewportBrowser, startPreviewImpl = startPreview, designContext = null, authContext = null } = {}) {
591
+ constructor({ workspaceRoot, ownerId, roomCode, outbox, browser = sharedViewportBrowser, startPreviewImpl = startPreview, designContext = null, authContext = null } = {}) {
589
592
  this.workspaceRoot = path.resolve(workspaceRoot || process.cwd())
590
593
  this.ownerId = ownerId || randomUUID()
594
+ this.roomCode = typeof roomCode === 'string' && roomCode.trim() ? roomCode.trim() : null
591
595
  this.outbox = outbox || path.join(os.tmpdir(), 'thinkpool-viewport-captures', this.ownerId)
592
596
  this.browser = browser
593
597
  this.startPreviewImpl = startPreviewImpl
@@ -623,6 +627,14 @@ export class ViewportManager {
623
627
  return this.preview
624
628
  }
625
629
 
630
+ defaultRoute() {
631
+ return this.roomCode ? `/?r=${encodeURIComponent(this.roomCode)}` : '/'
632
+ }
633
+
634
+ resolveRoute(route) {
635
+ return normalizeRoute(route ?? this.defaultRoute())
636
+ }
637
+
626
638
  pageUrl(route) {
627
639
  const preview = this.requirePreview()
628
640
  return new URL(normalizeRoute(route), preview.url).href
@@ -642,8 +654,8 @@ export class ViewportManager {
642
654
  }
643
655
  }
644
656
 
645
- async capture({ route = '/', viewports = 'both', title = 'Viewport capture', fullPage = true, waitMs = 300, card = false } = {}) {
646
- const normalizedRoute = normalizeRoute(route)
657
+ async capture({ route, viewports = 'both', title = 'Viewport capture', fullPage = true, waitMs = 300, card = false } = {}) {
658
+ const normalizedRoute = this.resolveRoute(route)
647
659
  const url = this.pageUrl(normalizedRoute)
648
660
  const names = viewports === 'both' ? ['desktop', 'mobile'] : [viewports]
649
661
  if (names.some((name) => !DEFAULT_VIEWPORTS[name])) throw new Error('viewports must be "both", "desktop", or "mobile".')
@@ -704,12 +716,12 @@ export class ViewportManager {
704
716
  await fsp.writeFile(tmp, JSON.stringify(manifest))
705
717
  await fsp.rename(tmp, manifestPath)
706
718
  }
707
- return { url, slug, manifestPath, captures, snapshotPath, cardReady }
719
+ return { url, route: normalizedRoute, slug, manifestPath, captures, snapshotPath, cardReady }
708
720
  }
709
721
 
710
- async inspect({ route = '/', viewport = 'mobile', selector, waitMs = 300 } = {}) {
722
+ async inspect({ route, viewport = 'mobile', selector, waitMs = 300 } = {}) {
711
723
  if (!DEFAULT_VIEWPORTS[viewport]) throw new Error('viewport must be "desktop" or "mobile".')
712
- const normalizedRoute = normalizeRoute(route)
724
+ const normalizedRoute = this.resolveRoute(route)
713
725
  return this.browser.inspect({
714
726
  url: this.pageUrl(normalizedRoute), viewport: DEFAULT_VIEWPORTS[viewport], selector, waitMs,
715
727
  auth: await this.previewAuth(normalizedRoute),
@@ -742,9 +754,9 @@ export function createViewportTools({ tool, z, manager }) {
742
754
  ),
743
755
  tool(
744
756
  'preview_capture',
745
- 'Capture this lane preview in a fresh isolated browser at exact desktop (1440×900) and mobile (390×844) CSS viewports. Defaults to both and full-page. Captures are inline verification evidence by default. Room and protected routes automatically use the bridge account as a read-only authenticated visual harness; external REST writes, presence, and room broadcasts are blocked, apart from non-mutating roster/transcript snapshot requests. Set card=true only for a user-requested mockup or visual deliverable that should remain editable in the transcript; complete desktop+mobile settled content is then required and the card arrives after the final response.',
757
+ 'Capture this lane preview in a fresh isolated browser at exact desktop (1440×900) and mobile (390×844) CSS viewports. Omit path to preview the current authenticated room; pass / explicitly for the public landing page. Defaults to both and full-page. Captures are inline verification evidence by default. Room and protected routes automatically use the bridge account as a read-only authenticated visual harness; external REST writes, presence, and room broadcasts are blocked, apart from non-mutating roster/transcript snapshot requests. Set card=true only for a user-requested mockup or visual deliverable that should remain editable in the transcript; complete desktop+mobile settled content is then required and the card arrives after the final response.',
746
758
  {
747
- path: z.string().max(500).optional().describe('route within the preview, e.g. / or /settings; never a full URL'),
759
+ path: z.string().max(500).optional().describe('route within the preview; omit for the current authenticated room, or pass / for the public landing page; never a full URL'),
748
760
  viewports: z.enum(['both', 'desktop', 'mobile']).optional(),
749
761
  title: z.string().max(120).optional(),
750
762
  fullPage: z.boolean().optional(),
@@ -754,13 +766,13 @@ export function createViewportTools({ tool, z, manager }) {
754
766
  async (args) => {
755
767
  try {
756
768
  const result = await manager.capture({
757
- route: args?.path || '/', viewports: args?.viewports || 'both', title: args?.title || 'Viewport capture',
769
+ route: args?.path, viewports: args?.viewports || 'both', title: args?.title || 'Viewport capture',
758
770
  fullPage: args?.fullPage !== false, waitMs: args?.waitMs ?? 300, card: args?.card === true,
759
771
  })
760
772
  const delivery = result.cardReady
761
773
  ? 'The complete room preview card is queued after your final response.'
762
774
  : 'This is verification evidence only; no room card was created.'
763
- const content = [{ type: 'text', text: `Captured ${Object.keys(result.captures).join(' + ')} for ${args?.path || '/'}. ${delivery}` }]
775
+ const content = [{ type: 'text', text: `Captured ${Object.keys(result.captures).join(' + ')} for ${result.route}. ${delivery}` }]
764
776
  for (const [name, capture] of Object.entries(result.captures)) {
765
777
  content.push({ type: 'text', text: `${name}: ${capture.width}×${capture.height}${capture.capped ? ' (height capped)' : ''}` })
766
778
  content.push({ type: 'image', data: capture.png.toString('base64'), mimeType: 'image/png' })
@@ -771,16 +783,16 @@ export function createViewportTools({ tool, z, manager }) {
771
783
  ),
772
784
  tool(
773
785
  'preview_inspect',
774
- 'Inspect rendered DOM state in this lane preview at an exact desktop or mobile viewport. Returns page text, document dimensions, and optional selector text/geometry without changing the page.',
786
+ 'Inspect rendered DOM state in this lane preview at an exact desktop or mobile viewport. Omit path for the current authenticated room; pass / explicitly for the public landing page. Returns page text, document dimensions, and optional selector text/geometry without changing the page.',
775
787
  {
776
- path: z.string().max(500).optional().describe('route within the preview; never a full URL'),
788
+ path: z.string().max(500).optional().describe('route within the preview; omit for the current authenticated room, or pass / for the public landing page; never a full URL'),
777
789
  viewport: z.enum(['desktop', 'mobile']).optional(),
778
790
  selector: z.string().max(500).optional(),
779
791
  waitMs: z.number().int().min(0).max(MAX_SETTLE_MS).optional(),
780
792
  },
781
793
  async (args) => {
782
794
  try {
783
- const result = await manager.inspect({ route: args?.path || '/', viewport: args?.viewport || 'mobile', selector: args?.selector, waitMs: args?.waitMs ?? 300 })
795
+ const result = await manager.inspect({ route: args?.path, viewport: args?.viewport || 'mobile', selector: args?.selector, waitMs: args?.waitMs ?? 300 })
784
796
  return textResult(JSON.stringify(result, null, 2))
785
797
  } catch (error) { return errorResult(error) }
786
798
  },