thinkpool-pair 0.7.217 → 0.7.219
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 +18 -2
- package/design-edit.mjs +52 -0
- package/package.json +1 -1
package/bridge.mjs
CHANGED
|
@@ -65,7 +65,7 @@ import { writeLaneArtifact, digestSlice, appendDigest, resumeLane } from './flow
|
|
|
65
65
|
import { createFlowWorktree, worktreeSpec } from './flow-worktree.mjs'
|
|
66
66
|
import { startPreview, stopAllPreviews, previews } from './flow-preview.mjs'
|
|
67
67
|
import { ViewportManager, createViewportTools, sharedViewportBrowser } from './viewport.mjs'
|
|
68
|
-
import { designPrompt, resolveDesignSource, validateDesignRequest } from './design-edit.mjs'
|
|
68
|
+
import { designPrompt, resolveDesignSource, restoreDesignSources, syncRealtimeAuth, validateDesignRequest } from './design-edit.mjs'
|
|
69
69
|
// FL-M9 — per-lane preview servers leak (one per done lane, never stopped until shutdown).
|
|
70
70
|
// Lane previews are keyed `lane:<flowId>:<laneId>`; stop a whole flow's set when it assembles
|
|
71
71
|
// (the assembled preview supersedes them) or when a lane is reverted.
|
|
@@ -739,6 +739,13 @@ let myServeUid = null // this bridge's authed uid (null = anon)
|
|
|
739
739
|
}
|
|
740
740
|
}
|
|
741
741
|
|
|
742
|
+
// Private realtime topics authorize the JWT carried by the socket at join time.
|
|
743
|
+
// Supabase's REST auth helpers do not propagate codeAuthToken to Realtime, so pin
|
|
744
|
+
// it before constructing any channel. A direct/anonymous bridge stays on anon.
|
|
745
|
+
if (!await syncRealtimeAuth(supabase, codeAuthToken, SUPABASE_ANON) && codeAuthToken) {
|
|
746
|
+
process.stderr.write('\n ⚠ realtime auth could not be initialized; private bridge features are unavailable.\n')
|
|
747
|
+
}
|
|
748
|
+
|
|
742
749
|
refreshOwnerPlan() // fire-and-forget on boot (codeAuthToken now set from env OR the stored login)
|
|
743
750
|
|
|
744
751
|
// ── Pre-flight budget caps — spec docs/specs/2026-07-01-budget-caps.md (nexos steal #1).
|
|
@@ -1855,6 +1862,9 @@ function openStructured({ id, runtime = 'claude', model, effort, resume, log, co
|
|
|
1855
1862
|
// they land on this session even after other terminals open/close.
|
|
1856
1863
|
const mockupOutbox = ownerOutbox('sessions', id)
|
|
1857
1864
|
entry.mockupWatcher = watchOutbox(mockupOutbox, () => id, true)
|
|
1865
|
+
const restoredDesigns = restoreDesignSources(mockupOutbox, cwd || process.cwd(), id)
|
|
1866
|
+
for (const record of restoredDesigns) designArtifacts.set(record.previewId, record)
|
|
1867
|
+
if (restoredDesigns.length) process.stderr.write(`\n ◆ restored ${restoredDesigns.length} Thinkpool Design artifact${restoredDesigns.length === 1 ? '' : 's'} (${id.slice(0, 8)}).\n`)
|
|
1858
1868
|
// Visual QA runs in the bridge process, outside either agent runtime's sandbox.
|
|
1859
1869
|
// It remains scoped to this lane's cwd and private mockup outbox.
|
|
1860
1870
|
entry.viewport = new ViewportManager({ workspaceRoot: cwd || process.cwd(), ownerId: id, outbox: mockupOutbox })
|
|
@@ -4014,7 +4024,13 @@ if (process.env.THINKPOOL_PAIR_AUTOUPDATE === '1' && VERSION) {
|
|
|
4014
4024
|
if (m.t === 'update-available') surfaceUpdate(m.version)
|
|
4015
4025
|
// Supervisor rotated the owner JWT (account.mjs scheduleTokenRefresh) — adopt
|
|
4016
4026
|
// it so our authed writes (code-mockup) never go stale on a long session (L16).
|
|
4017
|
-
else if (m.t === 'token') {
|
|
4027
|
+
else if (m.t === 'token') {
|
|
4028
|
+
codeAuthToken = m.accessToken || null
|
|
4029
|
+
syncRealtimeAuth(supabase, codeAuthToken, SUPABASE_ANON).then((ok) => {
|
|
4030
|
+
if (!ok && codeAuthToken) process.stderr.write('\n ⚠ realtime auth refresh failed; private bridge features may need a reconnect.\n')
|
|
4031
|
+
})
|
|
4032
|
+
refreshOwnerPlan()
|
|
4033
|
+
}
|
|
4018
4034
|
})
|
|
4019
4035
|
setInterval(() => { try { process.send({ t: 'idle', idle: idleNow(), between: betweenTurns(), webPeer: webPeerPresent() }) } catch { /* parent gone */ } }, 5000).unref()
|
|
4020
4036
|
} else if (VERSION) {
|
package/design-edit.mjs
CHANGED
|
@@ -8,6 +8,20 @@ const MAX_TEXT = 4_000
|
|
|
8
8
|
|
|
9
9
|
const clean = (value, max) => String(value ?? '').replace(/\0/g, '').trim().slice(0, max)
|
|
10
10
|
|
|
11
|
+
// supabase-js does not copy the Auth session into an already-created Realtime
|
|
12
|
+
// client. Private tpdesign:* joins therefore need the bridge owner's JWT pinned
|
|
13
|
+
// explicitly, both at boot and whenever the account supervisor rotates it.
|
|
14
|
+
export async function syncRealtimeAuth(client, accessToken, fallbackToken = null) {
|
|
15
|
+
const token = accessToken || fallbackToken
|
|
16
|
+
if (!token || typeof client?.realtime?.setAuth !== 'function') return false
|
|
17
|
+
try {
|
|
18
|
+
await client.realtime.setAuth(token)
|
|
19
|
+
return true
|
|
20
|
+
} catch {
|
|
21
|
+
return false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
11
25
|
export function sourceRevision(source) {
|
|
12
26
|
return crypto.createHash('sha256').update(String(source)).digest('hex').slice(0, 24)
|
|
13
27
|
}
|
|
@@ -34,6 +48,44 @@ export function resolveDesignSource(file, workspaceRoot) {
|
|
|
34
48
|
return { previewId, revision, sourcePath, workspaceRoot: root, source }
|
|
35
49
|
}
|
|
36
50
|
|
|
51
|
+
// Design provenance is intentionally host-only, but it must survive a bridge
|
|
52
|
+
// restart. Rebuild the in-memory registry from manifests already written by the
|
|
53
|
+
// trusted lane render workflow; do not re-upload or re-broadcast old cards.
|
|
54
|
+
export function restoreDesignSources(box, workspaceRoot, term, limit = 100) {
|
|
55
|
+
if (!box || !workspaceRoot || !term) return []
|
|
56
|
+
let files
|
|
57
|
+
try {
|
|
58
|
+
files = fs.readdirSync(box, { withFileTypes: true })
|
|
59
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.json') && !entry.name.endsWith('.receipt.json'))
|
|
60
|
+
.map((entry) => {
|
|
61
|
+
const file = path.join(box, entry.name)
|
|
62
|
+
return { file, mtimeMs: fs.statSync(file).mtimeMs }
|
|
63
|
+
})
|
|
64
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs)
|
|
65
|
+
.slice(0, Math.max(0, limit))
|
|
66
|
+
} catch { return [] }
|
|
67
|
+
const restored = []
|
|
68
|
+
for (const { file } of files) {
|
|
69
|
+
try {
|
|
70
|
+
const manifest = JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
71
|
+
if (!manifest?.source) continue
|
|
72
|
+
const record = resolveDesignSource(manifest.source, workspaceRoot)
|
|
73
|
+
if (!record) continue
|
|
74
|
+
Object.assign(record, {
|
|
75
|
+
term,
|
|
76
|
+
slug: String(manifest.slug || ''),
|
|
77
|
+
title: String(manifest.title || manifest.slug || ''),
|
|
78
|
+
desktop: manifest.desktop || null,
|
|
79
|
+
mobile: manifest.mobile || null,
|
|
80
|
+
})
|
|
81
|
+
const backupPath = path.join(box, '.design-revisions', `${record.revision}.html`)
|
|
82
|
+
if (fs.existsSync(backupPath)) record.backupPath = backupPath
|
|
83
|
+
restored.push(record)
|
|
84
|
+
} catch { /* stale or malformed manifests are ignored */ }
|
|
85
|
+
}
|
|
86
|
+
return restored
|
|
87
|
+
}
|
|
88
|
+
|
|
37
89
|
function safeTarget(value) {
|
|
38
90
|
if (!value || typeof value !== 'object') return null
|
|
39
91
|
const rect = value.rect && typeof value.rect === 'object' ? {
|