// Production check against the deployed Space. Uses the real HF token. // PROD_URL=https://lvwerra-interactive-editor.hf.space HF_TOKEN=hf_xxx node test/prod.js import assert from 'node:assert' import * as Y from 'yjs' import { HocuspocusProvider, HocuspocusProviderWebsocket } from '@hocuspocus/provider' import WS from 'ws' const BASE = process.env.PROD_URL || 'https://lvwerra-interactive-editor.hf.space' const TOKEN = process.env.HF_TOKEN if (!TOKEN) throw new Error('HF_TOKEN required') const HANDLE = 'prod-smoke-agent' // The Space is private: the ws upgrade must carry the HF token for the proxy. class AuthedWS extends WS { constructor(url, protocols) { super(url, protocols, { headers: { authorization: `Bearer ${TOKEN}` } }) } } const api = (path, { method = 'GET', body } = {}) => fetch(BASE + path, { method, headers: { authorization: `Bearer ${TOKEN}`, ...(body ? { 'content-type': 'application/json' } : {}) }, body: body ? JSON.stringify(body) : undefined, }).then(r => r.json()) async function waitFor(fn, what, ms = 20000) { const t0 = Date.now() while (Date.now() - t0 < ms) { try { if (await fn()) return } catch {} await new Promise(r => setTimeout(r, 250)) } throw new Error(`timeout waiting for ${what}`) } async function main() { const me = await api('/api/me') assert.ok(me.user?.username, 'authenticated: ' + JSON.stringify(me)) console.log('✓ authenticated as', me.user.username) const { id: docId } = await api('/api/docs', { method: 'POST', body: { title: 'Prod Smoke' } }) assert.ok(docId) console.log('✓ doc created:', docId) const doc = new Y.Doc() const socket = new HocuspocusProviderWebsocket({ url: BASE.replace('https', 'wss') + '/collab', WebSocketPolyfill: AuthedWS, }) const provider = new HocuspocusProvider({ websocketProvider: socket, name: docId, document: doc, token: TOKEN }) provider.attach() const frag = doc.getXmlFragment('default') await waitFor(() => frag.length >= 2, 'ws sync') console.log('✓ websocket collab sync through the Space proxy') const reg = await api('/api/agents', { method: 'POST', body: { handle: HANDLE } }) assert.equal(reg.ok, true, JSON.stringify(reg)) assert.ok(reg.key?.startsWith('ak_'), 'agent key issued') const AGENT = reg.key const agentApi = (path, opts = {}) => fetch(BASE + path, { method: opts.method || 'GET', headers: { authorization: `Bearer ${AGENT}`, ...(opts.body ? { 'content-type': 'application/json' } : {}) }, body: opts.body ? JSON.stringify(opts.body) : undefined, }).then(r => r.json()) const threadId = 'prodth' + Math.random().toString(36).slice(2, 8) doc.transact(() => { const messages = new Y.Array() messages.push([{ id: 'pm1', author: me.user.username, authorType: 'user', text: `@${HANDLE} check this`, ts: Date.now() }]) const t = new Y.Map() t.set('id', threadId) t.set('anchorStart', b64(Y.createRelativePositionFromTypeIndex(frag, 1))) t.set('anchorEnd', b64(Y.createRelativePositionFromTypeIndex(frag, 2))) t.set('excerpt', 'prod excerpt') t.set('resolved', false) t.set('createdBy', me.user.username) t.set('createdAt', Date.now()) t.set('messages', messages) doc.getMap('threads').set(threadId, t) }) const poll = await (async () => { const resp = await fetch(`${BASE}/api/mentions/stream?wait=15`, { headers: { authorization: `Bearer ${AGENT}` } }) let text = '' for await (const chunk of resp.body) text += Buffer.from(chunk).toString() return JSON.parse(text.trim().split('\n').filter(l => !l.startsWith(':')).pop()) })() assert.equal(poll.mentions?.length, 1, JSON.stringify(poll)) console.log('✓ mention delivered via long-poll') const snap = await api(`/api/docs/${docId}`) assert.ok(snap.blocks?.length >= 2) const sugg = await agentApi(`/api/docs/${docId}/suggestions`, { method: 'POST', body: { block_index: 1, replacement_markdown: 'Rewritten by the **prod** smoke agent.', rationale: 'prod test', mention_id: poll.mentions[0].mention_id, thread_id: threadId, }, }) assert.equal(sugg.ok, true, JSON.stringify(sugg)) const reply = await agentApi(`/api/docs/${docId}/threads/${threadId}/reply`, { method: 'POST', body: { text: 'done — see suggestion', mention_id: poll.mentions[0].mention_id }, }) assert.equal(reply.ok, true) console.log('✓ agent reply + suggestion') await waitFor(() => { const msgs = doc.getMap('threads').get(threadId)?.get('messages')?.toArray() || [] return msgs[0]?.mentions?.[0]?.status === 'done' && msgs.some(m => m.authorType === 'agent') }, 'chip done + agent msg synced to client') console.log('✓ status chip ✅ and agent reply visible in live doc') const acc = await api(`/api/docs/${docId}/suggestions/${sugg.suggestion_id}/accept`, { method: 'POST' }) assert.equal(acc.ok, true, JSON.stringify(acc)) await waitFor(async () => (await api(`/api/docs/${docId}`)).markdown.includes('prod'), 'accept applied') console.log('✓ suggestion accepted and applied') // agent adds a figure: upload bytes, reference in a suggestion, accept const pngBytes = Buffer.from( 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAF0lEQVR4nGP8z8Dwn4EIwESMolGFtFEIAK5+AxGmizXcAAAAAElFTkSuQmCC', 'base64' ) const up = await fetch(`${BASE}/api/docs/${docId}/upload`, { method: 'POST', headers: { authorization: `Bearer ${AGENT}`, 'content-type': 'image/png' }, body: pngBytes, }).then(r => r.json()) assert.ok(up.url?.startsWith('/files/'), 'agent upload: ' + JSON.stringify(up)) const figSugg = await agentApi(`/api/docs/${docId}/suggestions`, { method: 'POST', body: { block_index: 1, replacement_markdown: `A paragraph with a figure.\n\n![test figure](${up.url})`, }, }) assert.equal(figSugg.ok, true, JSON.stringify(figSugg)) const figAcc = await api(`/api/docs/${docId}/suggestions/${figSugg.suggestion_id}/accept`, { method: 'POST' }) assert.equal(figAcc.ok, true, JSON.stringify(figAcc)) await waitFor(async () => (await api(`/api/docs/${docId}`)).markdown.includes('![test figure]'), 'figure in doc') const fig = await fetch(`${BASE}${up.url}`, { headers: { authorization: `Bearer ${TOKEN}` } }) assert.equal(fig.status, 200, 'figure served') console.log('✓ agent figure: upload -> suggestion -> accept -> served') // streaming long-poll must survive the proxy well past 60s (heartbeats) console.log('… streaming long-poll test (~70s: mention arrives 65s into the stream)') const streamP = (async () => { const resp = await fetch(`${BASE}/api/mentions/stream?wait=90`, { headers: { authorization: `Bearer ${AGENT}` } }) let text = '' for await (const chunk of resp.body) text += Buffer.from(chunk).toString() return text })() await new Promise(r => setTimeout(r, 65000)) doc.transact(() => { doc.getMap('threads').get(threadId).get('messages').push([ { id: 'pm-stream', author: me.user.username, authorType: 'user', text: `@${HANDLE} still there?`, ts: Date.now() }, ]) }) const sText = await streamP const sLast = sText.trim().split('\n').filter(l => !l.startsWith(':')).pop() const sRes = JSON.parse(sLast) assert.ok(sRes.mentions?.length >= 1, 'stream delivered after 65s: ' + String(sLast).slice(0, 200)) assert.ok(sText.split(':hb').length > 2, 'heartbeats flowed through the proxy') console.log('✓ streaming long-poll: mention delivered 65s in, through the Space proxy') provider.destroy() socket.destroy() try { await api(`/api/docs/${docId}`, { method: 'DELETE' }) await api(`/api/agents/${HANDLE}`, { method: 'DELETE' }) console.log('✓ cleanup (doc + handle deleted)') } catch { console.log('~ cleanup skipped (delete endpoint not in running image yet)') } console.log('\nPROD CHECK PASSED') process.exit(0) } function b64(rel) { return Buffer.from(Y.encodeRelativePosition(rel)).toString('base64url') } main().catch(err => { console.error('PROD CHECK FAILED:', err) process.exit(1) })