Spaces:
Sleeping
Sleeping
File size: 8,131 Bytes
0d14b54 53148eb 0d14b54 b1c90fd 0d14b54 53148eb 0d14b54 53148eb 0d14b54 53148eb 0d14b54 b60973d 53148eb b60973d 53148eb b60973d 5a65b82 0d14b54 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | // 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`,
},
})
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)
})
|