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 +1 -1
- package/package.json +1 -1
- package/thinkpool-capabilities.json +3 -3
- package/viewport.mjs +26 -14
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
|
"schemaVersion": 1,
|
|
3
|
-
"bundleVersion":
|
|
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":
|
|
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
|
|
646
|
-
const normalizedRoute =
|
|
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
|
|
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 =
|
|
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,
|
|
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
|
|
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 ${
|
|
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
|
|
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
|
},
|