@newsails/veil-studio 1.0.0

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.
Files changed (87) hide show
  1. package/README.md +181 -0
  2. package/bin/veil-studio.js +142 -0
  3. package/nuxt-app/.output/public/200.html +13 -0
  4. package/nuxt-app/.output/public/404.html +13 -0
  5. package/nuxt-app/.output/public/_nuxt/builds/latest.json +1 -0
  6. package/nuxt-app/.output/public/_nuxt/builds/meta/6b28df26-54af-4fad-a1f0-38808960d9fe.json +1 -0
  7. package/nuxt-app/.output/public/_nuxt/entry.BrrOeBSX.js +120 -0
  8. package/nuxt-app/.output/public/_nuxt/entry.CYnp7zY5.css +1 -0
  9. package/nuxt-app/.output/public/_nuxt/error-404.BbdzCaXe.js +1 -0
  10. package/nuxt-app/.output/public/_nuxt/error-404.JekaaCis.css +1 -0
  11. package/nuxt-app/.output/public/_nuxt/error-500.CNP9nqm1.css +1 -0
  12. package/nuxt-app/.output/public/_nuxt/error-500.DbOlBIIY.js +1 -0
  13. package/nuxt-app/.output/public/_nuxt/index.BEoXSIOu.css +1 -0
  14. package/nuxt-app/.output/public/_nuxt/index.CNms2yAq.js +1 -0
  15. package/nuxt-app/.output/public/_nuxt/materialdesignicons-webfont.B7mPwVP_.ttf +0 -0
  16. package/nuxt-app/.output/public/_nuxt/materialdesignicons-webfont.CSr8KVlo.eot +0 -0
  17. package/nuxt-app/.output/public/_nuxt/materialdesignicons-webfont.Dp5v-WZN.woff2 +0 -0
  18. package/nuxt-app/.output/public/_nuxt/materialdesignicons-webfont.PXm3-2wK.woff +0 -0
  19. package/nuxt-app/.output/public/_nuxt/vue.-sixQ7xP.BlWffD__.js +1 -0
  20. package/nuxt-app/.output/public/index.html +13 -0
  21. package/package.json +37 -0
  22. package/server/index.js +184 -0
  23. package/server/routes/files.js +80 -0
  24. package/server/socket.js +507 -0
  25. package/server/utils/board-state.js +357 -0
  26. package/server/utils/config.js +51 -0
  27. package/server/utils/db.js +484 -0
  28. package/server/utils/element-instances.js +104 -0
  29. package/server/utils/element-registry.js +127 -0
  30. package/server/utils/elements/agent-instance.js +62 -0
  31. package/server/utils/elements/agent.js +93 -0
  32. package/server/utils/elements/annotation.js +30 -0
  33. package/server/utils/elements/approval-gate.js +49 -0
  34. package/server/utils/elements/assumption.js +32 -0
  35. package/server/utils/elements/blocker.js +36 -0
  36. package/server/utils/elements/chat-room.js +47 -0
  37. package/server/utils/elements/chat-view.js +33 -0
  38. package/server/utils/elements/code-block.js +39 -0
  39. package/server/utils/elements/collapsible-group.js +37 -0
  40. package/server/utils/elements/comparison-table.js +38 -0
  41. package/server/utils/elements/constraint.js +32 -0
  42. package/server/utils/elements/decision.js +39 -0
  43. package/server/utils/elements/diff-patch.js +38 -0
  44. package/server/utils/elements/divider.js +30 -0
  45. package/server/utils/elements/document-draft.js +35 -0
  46. package/server/utils/elements/fact-claim.js +36 -0
  47. package/server/utils/elements/feedback-request.js +43 -0
  48. package/server/utils/elements/file-reference.js +31 -0
  49. package/server/utils/elements/filter.js +48 -0
  50. package/server/utils/elements/generator.js +51 -0
  51. package/server/utils/elements/goal.js +36 -0
  52. package/server/utils/elements/html.js +35 -0
  53. package/server/utils/elements/idea.js +32 -0
  54. package/server/utils/elements/image-local.js +21 -0
  55. package/server/utils/elements/image.js +34 -0
  56. package/server/utils/elements/json-object.js +47 -0
  57. package/server/utils/elements/label-tag.js +30 -0
  58. package/server/utils/elements/markdown.js +37 -0
  59. package/server/utils/elements/merger.js +36 -0
  60. package/server/utils/elements/message.js +40 -0
  61. package/server/utils/elements/milestone.js +44 -0
  62. package/server/utils/elements/notification.js +34 -0
  63. package/server/utils/elements/outline.js +35 -0
  64. package/server/utils/elements/primitive.js +36 -0
  65. package/server/utils/elements/pro-con-list.js +39 -0
  66. package/server/utils/elements/processor.js +54 -0
  67. package/server/utils/elements/project.js +40 -0
  68. package/server/utils/elements/question.js +42 -0
  69. package/server/utils/elements/queue.js +85 -0
  70. package/server/utils/elements/research-note.js +41 -0
  71. package/server/utils/elements/section.js +35 -0
  72. package/server/utils/elements/source-collection.js +45 -0
  73. package/server/utils/elements/splitter.js +42 -0
  74. package/server/utils/elements/status-update.js +38 -0
  75. package/server/utils/elements/task.js +72 -0
  76. package/server/utils/elements/template.js +46 -0
  77. package/server/utils/elements/test-case.js +42 -0
  78. package/server/utils/elements/text.js +29 -0
  79. package/server/utils/elements/todo-list.js +57 -0
  80. package/server/utils/elements/url-card.js +37 -0
  81. package/server/utils/elements/web-search-query.js +46 -0
  82. package/server/utils/elements/web-snapshot.js +37 -0
  83. package/server/utils/file-utils.js +88 -0
  84. package/server/utils/session-watcher.js +108 -0
  85. package/server/utils/socket-io.js +14 -0
  86. package/server/utils/veil-client.js +185 -0
  87. package/server/utils/veil-ws.js +207 -0
@@ -0,0 +1,29 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'text',
5
+ name: 'Text',
6
+ description: 'A plain text note.',
7
+ icon: 'type',
8
+ defaultWidth: 200,
9
+ defaultHeight: 80,
10
+ expansionMode: 'inline',
11
+ tags: ['data', 'content'],
12
+ isBuiltIn: true,
13
+
14
+ createInstance(context) {
15
+ const { element } = context
16
+ return {
17
+ getViewData() { return { content: element.data.content || '' } },
18
+ getPorts() {
19
+ return [{ key: 'text-out', direction: 'output', dataType: 'text', label: 'Text Out' }]
20
+ },
21
+ actions: {
22
+ update({ content } = {}) {
23
+ const { updateElement } = require('../board-state.js')
24
+ updateElement(element.id, { data: { ...element.data, content } })
25
+ },
26
+ },
27
+ }
28
+ },
29
+ }
@@ -0,0 +1,57 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'todo-list',
5
+ name: 'Todo List',
6
+ description: 'A simple todo list linked to an agent session.',
7
+ icon: 'check-square',
8
+ defaultWidth: 200,
9
+ defaultHeight: 120,
10
+ expansionMode: 'inline',
11
+ tags: ['cli', 'todo'],
12
+ isBuiltIn: true,
13
+
14
+ createInstance(context) {
15
+ const { element } = context
16
+
17
+ return {
18
+ getViewData() {
19
+ return {
20
+ sessionId: element.data.sessionId || null,
21
+ agentName: element.data.agentName || null,
22
+ items: element.data.items || [],
23
+ }
24
+ },
25
+
26
+ getPorts() {
27
+ return [
28
+ { key: 'items-in', direction: 'input', dataType: 'any', label: 'Items In' },
29
+ { key: 'items-out', direction: 'output', dataType: 'any', label: 'Items Out' },
30
+ ]
31
+ },
32
+
33
+ actions: {
34
+ addItem({ text } = {}) {
35
+ if (!text) throw new Error('text required')
36
+ const { updateElement } = require('../board-state.js')
37
+ const items = [...(element.data.items || []), { id: Date.now().toString(), text, done: false }]
38
+ updateElement(element.id, { data: { ...element.data, items } })
39
+ },
40
+
41
+ toggleItem({ id } = {}) {
42
+ if (!id) throw new Error('id required')
43
+ const { updateElement } = require('../board-state.js')
44
+ const items = (element.data.items || []).map(i => i.id === id ? { ...i, done: !i.done } : i)
45
+ updateElement(element.id, { data: { ...element.data, items } })
46
+ },
47
+
48
+ removeItem({ id } = {}) {
49
+ if (!id) throw new Error('id required')
50
+ const { updateElement } = require('../board-state.js')
51
+ const items = (element.data.items || []).filter(i => i.id !== id)
52
+ updateElement(element.id, { data: { ...element.data, items } })
53
+ },
54
+ },
55
+ }
56
+ },
57
+ }
@@ -0,0 +1,37 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'url-card',
5
+ name: 'URL Card',
6
+ description: 'Display a URL with title, description, and favicon.',
7
+ icon: 'link',
8
+ defaultWidth: 260,
9
+ defaultHeight: 90,
10
+ expansionMode: 'inline',
11
+ tags: ['data', 'web'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element } = context
17
+ return {
18
+ getViewData() {
19
+ return {
20
+ url: element.data.url || '',
21
+ title: element.data.title || '',
22
+ description: element.data.description || '',
23
+ favicon: element.data.favicon || null,
24
+ }
25
+ },
26
+ getPorts() {
27
+ return [{ key: 'url-out', direction: 'output', dataType: 'text', label: 'URL Out' }]
28
+ },
29
+ actions: {
30
+ update(data = {}) {
31
+ const { updateElement } = require('../board-state.js')
32
+ updateElement(element.id, { data: { ...element.data, ...data } })
33
+ },
34
+ },
35
+ }
36
+ },
37
+ }
@@ -0,0 +1,46 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'web-search-query',
5
+ name: 'Web Search Query',
6
+ description: 'A web search query to send to an agent or run directly.',
7
+ icon: 'search',
8
+ defaultWidth: 220,
9
+ defaultHeight: 80,
10
+ expansionMode: 'inline',
11
+ tags: ['research', 'web'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element } = context
17
+ return {
18
+ getViewData() {
19
+ return {
20
+ query: element.data.query || '',
21
+ engine: element.data.engine || null,
22
+ filters: element.data.filters || null,
23
+ }
24
+ },
25
+ getPorts() {
26
+ return [{ key: 'query-out', direction: 'output', dataType: 'text', label: 'Query Out' }]
27
+ },
28
+ actions: {
29
+ sendToAgent({ agentName } = {}) {
30
+ if (!agentName) throw new Error('agentName required')
31
+ const { createElement } = require('../board-state.js')
32
+ createElement({
33
+ type: 'task',
34
+ x: element.x + 240,
35
+ y: element.y,
36
+ data: { agentName, prompt: element.data.query },
37
+ })
38
+ },
39
+ update(data = {}) {
40
+ const { updateElement } = require('../board-state.js')
41
+ updateElement(element.id, { data: { ...element.data, ...data } })
42
+ },
43
+ },
44
+ }
45
+ },
46
+ }
@@ -0,0 +1,37 @@
1
+ 'use strict'
2
+
3
+ module.exports = {
4
+ type: 'web-snapshot',
5
+ name: 'Web Snapshot',
6
+ description: 'A captured snapshot of a web page.',
7
+ icon: 'globe',
8
+ defaultWidth: 240,
9
+ defaultHeight: 100,
10
+ expansionMode: 'popup',
11
+ tags: ['data', 'web', 'research'],
12
+ isBuiltIn: true,
13
+ hiddenFromPalette: true,
14
+
15
+ createInstance(context) {
16
+ const { element } = context
17
+ return {
18
+ getViewData() {
19
+ return {
20
+ url: element.data.url || '',
21
+ title: element.data.title || '',
22
+ content: element.data.content || '',
23
+ capturedAt: element.data.capturedAt || null,
24
+ }
25
+ },
26
+ getPorts() {
27
+ return [{ key: 'text-out', direction: 'output', dataType: 'text', label: 'Content Out' }]
28
+ },
29
+ actions: {
30
+ update(data = {}) {
31
+ const { updateElement } = require('../board-state.js')
32
+ updateElement(element.id, { data: { ...element.data, ...data } })
33
+ },
34
+ },
35
+ }
36
+ },
37
+ }
@@ -0,0 +1,88 @@
1
+ 'use strict'
2
+
3
+ const path = require('path')
4
+ const fs = require('fs')
5
+ const { PROJECT_DIR } = require('./config.js')
6
+
7
+ const EXCLUDED_NAMES = new Set(['node_modules', '.git', '.nuxt', '.output', '.cache'])
8
+ const MAX_DEPTH = 6
9
+
10
+ function _safePath(relativePath) {
11
+ const resolved = path.resolve(PROJECT_DIR, relativePath)
12
+ if (!resolved.startsWith(PROJECT_DIR)) {
13
+ const err = new Error(`Access denied: path outside PROJECT_DIR`)
14
+ err.status = 403
15
+ throw err
16
+ }
17
+ return resolved
18
+ }
19
+
20
+ function buildFileTree(dir = PROJECT_DIR, depth = 0) {
21
+ if (depth >= MAX_DEPTH) return []
22
+ const entries = fs.readdirSync(dir, { withFileTypes: true })
23
+ const result = []
24
+ for (const entry of entries) {
25
+ if (EXCLUDED_NAMES.has(entry.name)) continue
26
+ const fullPath = path.join(dir, entry.name)
27
+ const relPath = path.relative(PROJECT_DIR, fullPath)
28
+ if (entry.isDirectory()) {
29
+ result.push({
30
+ name: entry.name,
31
+ path: relPath,
32
+ type: 'directory',
33
+ children: buildFileTree(fullPath, depth + 1),
34
+ })
35
+ } else {
36
+ const stat = fs.statSync(fullPath)
37
+ result.push({
38
+ name: entry.name,
39
+ path: relPath,
40
+ type: 'file',
41
+ size: stat.size,
42
+ })
43
+ }
44
+ }
45
+ return result
46
+ }
47
+
48
+ function readProjectFile(relativePath) {
49
+ const abs = _safePath(relativePath)
50
+ return fs.readFileSync(abs, 'utf8')
51
+ }
52
+
53
+ function writeProjectFile(relativePath, content) {
54
+ const abs = _safePath(relativePath)
55
+ fs.mkdirSync(path.dirname(abs), { recursive: true })
56
+ fs.writeFileSync(abs, content, 'utf8')
57
+ }
58
+
59
+ function createProjectDir(relativePath) {
60
+ const abs = _safePath(relativePath)
61
+ fs.mkdirSync(abs, { recursive: true })
62
+ }
63
+
64
+ function renameProjectFile(fromRel, toRel) {
65
+ const from = _safePath(fromRel)
66
+ const to = _safePath(toRel)
67
+ fs.mkdirSync(path.dirname(to), { recursive: true })
68
+ fs.renameSync(from, to)
69
+ }
70
+
71
+ function deleteProjectPath(relativePath) {
72
+ const abs = _safePath(relativePath)
73
+ const stat = fs.statSync(abs)
74
+ if (stat.isDirectory()) {
75
+ fs.rmSync(abs, { recursive: true, force: true })
76
+ } else {
77
+ fs.unlinkSync(abs)
78
+ }
79
+ }
80
+
81
+ module.exports = {
82
+ buildFileTree,
83
+ readProjectFile,
84
+ writeProjectFile,
85
+ createProjectDir,
86
+ renameProjectFile,
87
+ deleteProjectPath,
88
+ }
@@ -0,0 +1,108 @@
1
+ 'use strict'
2
+
3
+ const { veilGet } = require('./veil-client.js')
4
+ const { getIO } = require('./socket-io.js')
5
+
6
+ // Map<sessionId, number> — message count baseline (messages already seen by Studio)
7
+ // undefined = not initialized → first syncSession will set baseline without emitting
8
+ const _sessionState = new Map()
9
+
10
+ // Prevent concurrent syncs for the same session
11
+ const _syncing = new Set()
12
+
13
+ // ─── Public API ──────────────────────────────────────────────────────────────
14
+
15
+ async function syncSession(sessionId) {
16
+ if (_syncing.has(sessionId)) return
17
+ _syncing.add(sessionId)
18
+ try {
19
+ await _doSync(sessionId)
20
+ } catch (err) {
21
+ console.warn(`[session-sync] syncSession ${sessionId} failed: ${err.message}`)
22
+ } finally {
23
+ _syncing.delete(sessionId)
24
+ }
25
+ }
26
+
27
+ async function touchSession(sessionId) {
28
+ // Refresh baseline without emitting — call after chat:send completes
29
+ // so the Studio-sent messages are not re-emitted on the next external sync
30
+ try {
31
+ const res = await veilGet(`/sessions/${encodeURIComponent(sessionId)}/messages`)
32
+ const messages = Array.isArray(res) ? res : (res.messages ?? [])
33
+ _sessionState.set(sessionId, messages.length)
34
+ } catch { /* best-effort, ignore errors */ }
35
+ }
36
+
37
+ async function initSessionStates() {
38
+ // Pre-populate message-count baselines for all active sessions so that
39
+ // the first syncSession call only emits truly NEW messages
40
+ try {
41
+ const res = await veilGet('/sessions')
42
+ const sessions = Array.isArray(res) ? res : (res.sessions ?? [])
43
+ const active = sessions.filter(s => s.status !== 'closed' && s.status !== 'deleted')
44
+
45
+ await Promise.all(active.map(async (s) => {
46
+ const id = s.id || s.sessionId
47
+ if (!id || _sessionState.has(id)) return
48
+ try {
49
+ const msgRes = await veilGet(`/sessions/${encodeURIComponent(id)}/messages`)
50
+ const msgs = Array.isArray(msgRes) ? msgRes : (msgRes.messages ?? [])
51
+ _sessionState.set(id, msgs.length)
52
+ } catch {
53
+ _sessionState.set(id, 0)
54
+ }
55
+ }))
56
+
57
+ if (active.length > 0)
58
+ console.log(`[session-sync] Baselines initialized for ${active.length} session(s)`)
59
+ } catch (err) {
60
+ console.warn(`[session-sync] initSessionStates failed: ${err.message}`)
61
+ }
62
+ }
63
+
64
+ // ─── Internal ────────────────────────────────────────────────────────────────
65
+
66
+ async function _doSync(sessionId) {
67
+ const res = await veilGet(`/sessions/${encodeURIComponent(sessionId)}/messages`)
68
+ const messages = Array.isArray(res) ? res : (res.messages ?? [])
69
+
70
+ const baseline = _sessionState.get(sessionId)
71
+
72
+ if (baseline === undefined) {
73
+ // First contact with this session — set baseline without emitting historical messages
74
+ _sessionState.set(sessionId, messages.length)
75
+ return
76
+ }
77
+
78
+ const newMessages = messages.slice(baseline)
79
+ _sessionState.set(sessionId, messages.length)
80
+
81
+ if (newMessages.length === 0) return
82
+
83
+ const io = getIO()
84
+
85
+ for (const msg of newMessages) {
86
+ if (msg.role === 'user') {
87
+ const content = typeof msg.content === 'string'
88
+ ? msg.content
89
+ : (msg.content?.[0]?.text ?? '')
90
+ io.to('board').emit('chat:user-message', {
91
+ sessionId,
92
+ agentName: msg.agentName || '',
93
+ message: content,
94
+ })
95
+ } else if (msg.role === 'assistant') {
96
+ io.to('board').emit('chat:stream', {
97
+ sessionId,
98
+ eventType: 'message',
99
+ data: msg,
100
+ })
101
+ }
102
+ }
103
+
104
+ // Signal end of streaming after the batch
105
+ io.to('board').emit('chat:done', { sessionId, message: null, tokenUsage: null })
106
+ }
107
+
108
+ module.exports = { syncSession, touchSession, initSessionStates }
@@ -0,0 +1,14 @@
1
+ 'use strict'
2
+
3
+ let _io = null
4
+
5
+ function getIO() {
6
+ if (!_io) throw new Error('Socket.IO not initialized yet — call setIO(io) first')
7
+ return _io
8
+ }
9
+
10
+ function setIO(io) {
11
+ _io = io
12
+ }
13
+
14
+ module.exports = { getIO, setIO }
@@ -0,0 +1,185 @@
1
+ 'use strict'
2
+
3
+ const { VEIL_API, VEIL_SECRET } = require('./config.js')
4
+
5
+ function _headers(extra = {}) {
6
+ const h = { 'Content-Type': 'application/json', ...extra }
7
+ if (VEIL_SECRET) h['X-Veil-Secret'] = VEIL_SECRET
8
+ return h
9
+ }
10
+
11
+ async function _checkResponse(res) {
12
+ if (res.ok) return res
13
+ let msg = `VeilCLI error ${res.status}`
14
+ try {
15
+ const body = await res.json()
16
+ if (body && body.error) {
17
+ msg = body.error.message || body.error.code || msg
18
+ }
19
+ } catch (_) {}
20
+ throw new Error(msg)
21
+ }
22
+
23
+ async function veilGet(apiPath, query = {}) {
24
+ const url = new URL(`${VEIL_API}${apiPath}`)
25
+ for (const [k, v] of Object.entries(query)) {
26
+ if (v !== undefined && v !== null) url.searchParams.set(k, v)
27
+ }
28
+ const res = await fetch(url.toString(), { headers: _headers() })
29
+ await _checkResponse(res)
30
+ return res.json()
31
+ }
32
+
33
+ async function veilPost(apiPath, body = {}) {
34
+ const res = await fetch(`${VEIL_API}${apiPath}`, {
35
+ method: 'POST',
36
+ headers: _headers(),
37
+ body: JSON.stringify(body),
38
+ })
39
+ await _checkResponse(res)
40
+ return res.json()
41
+ }
42
+
43
+ async function veilPut(apiPath, body = {}) {
44
+ const res = await fetch(`${VEIL_API}${apiPath}`, {
45
+ method: 'PUT',
46
+ headers: _headers(),
47
+ body: JSON.stringify(body),
48
+ })
49
+ await _checkResponse(res)
50
+ return res.json()
51
+ }
52
+
53
+ async function veilDelete(apiPath, query = {}) {
54
+ const url = new URL(`${VEIL_API}${apiPath}`)
55
+ for (const [k, v] of Object.entries(query)) {
56
+ if (v !== undefined && v !== null) url.searchParams.set(k, v)
57
+ }
58
+ const res = await fetch(url.toString(), {
59
+ method: 'DELETE',
60
+ headers: _headers(),
61
+ })
62
+ await _checkResponse(res)
63
+ return res.json()
64
+ }
65
+
66
+ async function* veilStreamChat(agentName, message, sessionId) {
67
+ const body = { message, sse: true }
68
+ if (sessionId) body.sessionId = sessionId
69
+
70
+ const res = await fetch(`${VEIL_API}/agents/${encodeURIComponent(agentName)}/chat`, {
71
+ method: 'POST',
72
+ headers: _headers(),
73
+ body: JSON.stringify(body),
74
+ })
75
+ await _checkResponse(res)
76
+
77
+ const reader = res.body.getReader()
78
+ const decoder = new TextDecoder()
79
+ let buffer = ''
80
+
81
+ while (true) {
82
+ const { done, value } = await reader.read()
83
+ if (done) break
84
+ buffer += decoder.decode(value, { stream: true })
85
+
86
+ const parts = buffer.split('\n\n')
87
+ buffer = parts.pop()
88
+
89
+ for (const part of parts) {
90
+ const lines = part.trim().split('\n')
91
+ let eventType = null
92
+ let dataLine = null
93
+ for (const line of lines) {
94
+ if (line.startsWith('event:')) eventType = line.slice(6).trim()
95
+ if (line.startsWith('data:')) dataLine = line.slice(5).trim()
96
+ }
97
+ if (!dataLine) continue
98
+ try {
99
+ const data = JSON.parse(dataLine)
100
+ yield { eventType: eventType || 'message', data }
101
+ } catch (_) {}
102
+ }
103
+ }
104
+ }
105
+
106
+ async function* veilStreamSessionEvents(sessionId, signal) {
107
+ const res = await fetch(`${VEIL_API}/sessions/${encodeURIComponent(sessionId)}/events`, {
108
+ headers: _headers({ Accept: 'text/event-stream' }),
109
+ signal,
110
+ })
111
+ await _checkResponse(res)
112
+
113
+ const reader = res.body.getReader()
114
+ const decoder = new TextDecoder()
115
+ let buffer = ''
116
+
117
+ while (true) {
118
+ const { done, value } = await reader.read()
119
+ if (done) break
120
+ buffer += decoder.decode(value, { stream: true })
121
+
122
+ const parts = buffer.split('\n\n')
123
+ buffer = parts.pop()
124
+
125
+ for (const part of parts) {
126
+ const lines = part.trim().split('\n')
127
+ let eventType = null
128
+ let dataLine = null
129
+ for (const line of lines) {
130
+ if (line.startsWith('event:')) eventType = line.slice(6).trim()
131
+ if (line.startsWith('data:')) dataLine = line.slice(5).trim()
132
+ }
133
+ if (!dataLine) continue
134
+ try {
135
+ const data = JSON.parse(dataLine)
136
+ yield { eventType: eventType || 'message', data }
137
+ } catch (_) {}
138
+ }
139
+ }
140
+ }
141
+
142
+ async function* veilStreamTaskEvents(taskId) {
143
+ const res = await fetch(`${VEIL_API}/tasks/${encodeURIComponent(taskId)}/events`, {
144
+ headers: _headers({ Accept: 'text/event-stream' }),
145
+ })
146
+ await _checkResponse(res)
147
+
148
+ const reader = res.body.getReader()
149
+ const decoder = new TextDecoder()
150
+ let buffer = ''
151
+
152
+ while (true) {
153
+ const { done, value } = await reader.read()
154
+ if (done) break
155
+ buffer += decoder.decode(value, { stream: true })
156
+
157
+ const parts = buffer.split('\n\n')
158
+ buffer = parts.pop()
159
+
160
+ for (const part of parts) {
161
+ const lines = part.trim().split('\n')
162
+ let eventType = null
163
+ let dataLine = null
164
+ for (const line of lines) {
165
+ if (line.startsWith('event:')) eventType = line.slice(6).trim()
166
+ if (line.startsWith('data:')) dataLine = line.slice(5).trim()
167
+ }
168
+ if (!dataLine) continue
169
+ try {
170
+ const data = JSON.parse(dataLine)
171
+ yield { eventType: eventType || 'task.event', data }
172
+ } catch (_) {}
173
+ }
174
+ }
175
+ }
176
+
177
+ module.exports = {
178
+ veilGet,
179
+ veilPost,
180
+ veilPut,
181
+ veilDelete,
182
+ veilStreamChat,
183
+ veilStreamSessionEvents,
184
+ veilStreamTaskEvents,
185
+ }