Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- server/api.js +5 -4
- server/collab.js +20 -2
- test/prod.js +6 -1
- test/smoke.js +15 -3
server/api.js
CHANGED
|
@@ -191,9 +191,10 @@ export function apiRouter() {
|
|
| 191 |
|
| 192 |
// --- mention long-poll ---
|
| 193 |
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
|
|
|
| 197 |
if (result.error) return res.status(400).json(result)
|
| 198 |
res.json(result)
|
| 199 |
})
|
|
@@ -288,7 +289,7 @@ Work loop — repeat until the user tells you to stop:
|
|
| 288 |
The stream sends ":hb" heartbeat lines while idle; the final line is JSON:
|
| 289 |
{"mentions": [...]}. Empty output or an empty list just means the wait expired —
|
| 290 |
immediately make the same call again. This is the normal idle state; one call per
|
| 291 |
-
~50 minutes costs almost nothing.
|
| 292 |
Each mention has: mention_id, doc_id, thread_id, instruction, anchored_text (the text the
|
| 293 |
comment is attached to), thread_messages (conversation so far), requested_by.
|
| 294 |
If "about_suggestion" is set, the comment discusses one of your earlier suggestions —
|
|
|
|
| 191 |
|
| 192 |
// --- mention long-poll ---
|
| 193 |
|
| 194 |
+
// Instant snapshot of outstanding work (nothing is claimed) — for debugging.
|
| 195 |
+
// The `wait` param is gone: waiting happens on /mentions/stream.
|
| 196 |
+
router.get('/mentions', requireUser, (req, res) => {
|
| 197 |
+
const result = collab.snapshotMentions(req.user.username, req.agent?.handle)
|
| 198 |
if (result.error) return res.status(400).json(result)
|
| 199 |
res.json(result)
|
| 200 |
})
|
|
|
|
| 289 |
The stream sends ":hb" heartbeat lines while idle; the final line is JSON:
|
| 290 |
{"mentions": [...]}. Empty output or an empty list just means the wait expired —
|
| 291 |
immediately make the same call again. This is the normal idle state; one call per
|
| 292 |
+
~50 minutes costs almost nothing.
|
| 293 |
Each mention has: mention_id, doc_id, thread_id, instruction, anchored_text (the text the
|
| 294 |
comment is attached to), thread_messages (conversation so far), requested_by.
|
| 295 |
If "about_suggestion" is set, the comment discusses one of your earlier suggestions —
|
server/collab.js
CHANGED
|
@@ -302,7 +302,7 @@ async function claimAndEnrich(tasks) {
|
|
| 302 |
return out
|
| 303 |
}
|
| 304 |
|
| 305 |
-
|
| 306 |
let handles = handlesOwnedBy(username)
|
| 307 |
if (onlyHandle) {
|
| 308 |
// agent-key poll: exactly this handle
|
|
@@ -314,7 +314,25 @@ export function pollMentions(username, waitSeconds, onlyHandle = null, opts = {}
|
|
| 314 |
const agents = store.getAgents()
|
| 315 |
handles = handles.filter(h => !agents[h]?.keyHash)
|
| 316 |
}
|
| 317 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
for (const h of handles) agentLastPoll.set(h, Date.now())
|
| 319 |
|
| 320 |
const now = claimable(handles)
|
|
|
|
| 302 |
return out
|
| 303 |
}
|
| 304 |
|
| 305 |
+
function pollableHandles(username, onlyHandle) {
|
| 306 |
let handles = handlesOwnedBy(username)
|
| 307 |
if (onlyHandle) {
|
| 308 |
// agent-key poll: exactly this handle
|
|
|
|
| 314 |
const agents = store.getAgents()
|
| 315 |
handles = handles.filter(h => !agents[h]?.keyHash)
|
| 316 |
}
|
| 317 |
+
return handles
|
| 318 |
+
}
|
| 319 |
+
|
| 320 |
+
const NO_HANDLES_ERROR = 'no agent handles registered for your account (handles with a key must poll with that key) — POST /api/agents first'
|
| 321 |
+
|
| 322 |
+
// Instant, non-claiming view of outstanding work — for debugging. Receiving
|
| 323 |
+
// (and claiming) work happens through the streaming poll.
|
| 324 |
+
export function snapshotMentions(username, onlyHandle = null) {
|
| 325 |
+
const handles = pollableHandles(username, onlyHandle)
|
| 326 |
+
if (!handles.length) return { error: NO_HANDLES_ERROR }
|
| 327 |
+
const mentions = Object.values(mentionState.tasks)
|
| 328 |
+
.filter(t => handles.includes(t.handle) && (t.status === 'pending' || t.status === 'claimed'))
|
| 329 |
+
.map(t => ({ mention_id: t.id, doc_id: t.docId, thread_id: t.threadId, handle: t.handle, status: t.status, instruction: t.text, created_at: t.ts }))
|
| 330 |
+
return { snapshot: true, mentions }
|
| 331 |
+
}
|
| 332 |
+
|
| 333 |
+
export function pollMentions(username, waitSeconds, onlyHandle = null, opts = {}) {
|
| 334 |
+
const handles = pollableHandles(username, onlyHandle)
|
| 335 |
+
if (!handles.length) return Promise.resolve({ error: NO_HANDLES_ERROR })
|
| 336 |
for (const h of handles) agentLastPoll.set(h, Date.now())
|
| 337 |
|
| 338 |
const now = claimable(handles)
|
test/prod.js
CHANGED
|
@@ -82,7 +82,12 @@ async function main() {
|
|
| 82 |
doc.getMap('threads').set(threadId, t)
|
| 83 |
})
|
| 84 |
|
| 85 |
-
const poll = await
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
assert.equal(poll.mentions?.length, 1, JSON.stringify(poll))
|
| 87 |
console.log('✓ mention delivered via long-poll')
|
| 88 |
|
|
|
|
| 82 |
doc.getMap('threads').set(threadId, t)
|
| 83 |
})
|
| 84 |
|
| 85 |
+
const poll = await (async () => {
|
| 86 |
+
const resp = await fetch(`${BASE}/api/mentions/stream?wait=15`, { headers: { authorization: `Bearer ${AGENT}` } })
|
| 87 |
+
let text = ''
|
| 88 |
+
for await (const chunk of resp.body) text += Buffer.from(chunk).toString()
|
| 89 |
+
return JSON.parse(text.trim().split('\n').filter(l => !l.startsWith(':')).pop())
|
| 90 |
+
})()
|
| 91 |
assert.equal(poll.mentions?.length, 1, JSON.stringify(poll))
|
| 92 |
console.log('✓ mention delivered via long-poll')
|
| 93 |
|
test/smoke.js
CHANGED
|
@@ -54,6 +54,14 @@ function api(token, path_, { method = 'GET', body } = {}) {
|
|
| 54 |
}).then(r => r.json())
|
| 55 |
}
|
| 56 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
function connect(docId, token) {
|
| 58 |
const doc = new Y.Doc()
|
| 59 |
const provider = new HocuspocusProvider({
|
|
@@ -153,8 +161,12 @@ async function main() {
|
|
| 153 |
}, 'mention chip')
|
| 154 |
console.log('✓ mention detected, chip set')
|
| 155 |
|
| 156 |
-
// 6. the
|
| 157 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
assert.equal(poll.mentions?.length, 1, 'mention delivered: ' + JSON.stringify(poll))
|
| 159 |
const mention = poll.mentions[0]
|
| 160 |
assert.equal(mention.handle, 'test-agent')
|
|
@@ -205,7 +217,7 @@ async function main() {
|
|
| 205 |
{ id: 'msg-followup', author: 'alice', authorType: 'user', text: 'hmm, can you double check the link?', ts: Date.now() },
|
| 206 |
])
|
| 207 |
})
|
| 208 |
-
const followPoll = await
|
| 209 |
assert.equal(followPoll.mentions?.length, 1, 'follow-up delivered')
|
| 210 |
assert.equal(followPoll.mentions[0].implicit_follow_up, true, 'marked implicit')
|
| 211 |
const dismissed = await api(AGENT, `/api/mentions/${followPoll.mentions[0].mention_id}/dismiss`, { method: 'POST' })
|
|
|
|
| 54 |
}).then(r => r.json())
|
| 55 |
}
|
| 56 |
|
| 57 |
+
async function streamPoll(key, wait = 10) {
|
| 58 |
+
const resp = await fetch(`${BASE}/api/mentions/stream?wait=${wait}`, { headers: { authorization: `Bearer ${key}` } })
|
| 59 |
+
let text = ''
|
| 60 |
+
for await (const chunk of resp.body) text += Buffer.from(chunk).toString()
|
| 61 |
+
const last = text.trim().split('\n').filter(l => !l.startsWith(':')).pop()
|
| 62 |
+
return JSON.parse(last)
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
function connect(docId, token) {
|
| 66 |
const doc = new Y.Doc()
|
| 67 |
const provider = new HocuspocusProvider({
|
|
|
|
| 161 |
}, 'mention chip')
|
| 162 |
console.log('✓ mention detected, chip set')
|
| 163 |
|
| 164 |
+
// 6. snapshot shows the mention without claiming; the stream claims it
|
| 165 |
+
const snap0 = await api(AGENT, '/api/mentions')
|
| 166 |
+
assert.equal(snap0.snapshot, true, 'snapshot flagged')
|
| 167 |
+
assert.equal(snap0.mentions?.length, 1, 'snapshot shows pending: ' + JSON.stringify(snap0))
|
| 168 |
+
assert.equal(snap0.mentions[0].status, 'pending', 'snapshot does not claim')
|
| 169 |
+
const poll = await streamPoll(AGENT, 10)
|
| 170 |
assert.equal(poll.mentions?.length, 1, 'mention delivered: ' + JSON.stringify(poll))
|
| 171 |
const mention = poll.mentions[0]
|
| 172 |
assert.equal(mention.handle, 'test-agent')
|
|
|
|
| 217 |
{ id: 'msg-followup', author: 'alice', authorType: 'user', text: 'hmm, can you double check the link?', ts: Date.now() },
|
| 218 |
])
|
| 219 |
})
|
| 220 |
+
const followPoll = await streamPoll(AGENT, 10)
|
| 221 |
assert.equal(followPoll.mentions?.length, 1, 'follow-up delivered')
|
| 222 |
assert.equal(followPoll.mentions[0].implicit_follow_up, true, 'marked implicit')
|
| 223 |
const dismissed = await api(AGENT, `/api/mentions/${followPoll.mentions[0].mention_id}/dismiss`, { method: 'POST' })
|