openvibers/openvibe.shared dock-code.js
dock-code.js
| 1 | // ═══════════════════════════════════════════════════════════════ |
| 2 | // OpenVibe — the Code part (openvibe-shared 4.201.0). Ask 350, the owner: openvibe.codes "should be better interconnected |
| 3 | // and shared … so that you can do chat, codes, and agents all in the same place and in one area". The menu's Vibe place |
| 4 | // is Chat · Code · Agents, and the Dock has the same Code tab. dock.js loads this file the first time it opens: |
| 5 | // OpenVibeDockCode.mount(container, { token, frameBase, base, codes, active, take }) → { refresh(), focus(), destroy() } |
| 6 | // take() → the words another part handed over (Vibe's "Build it in Code"), which go in the box. |
| 7 | // |
| 8 | // One box to start a coding task, the way openvibe.codes starts one (Create: a new website, game or app; OpenVibe: |
| 9 | // a change to OpenVibe itself; My code: a repository of yours), and your latest tasks under it, running ones first, |
| 10 | // each with its state, its repository, how long ago, and Stop while it runs. The reads go through Network's same-origin |
| 11 | // relay (/api/frame/codes-tasks…), because every site's connect-src allows openvibe.network and refuses openvibe.codes; |
| 12 | // `base` reaches Codes itself in a test. While a task runs the list is read again every 8 seconds, only while the part |
| 13 | // is on screen. When Codes cannot be reached, the box still works: it opens openvibe.codes with what you wrote in it. |
| 14 | // Everything is set as text, never markup; a task's address is used only when it is on openvibe.codes. |
| 15 | // ═══════════════════════════════════════════════════════════════ |
| 16 | (function (root) { |
| 17 | ; |
| 18 | if (typeof document === 'undefined' || root.OpenVibeDockCode) return; |
| 19 | |
| 20 | const CODES = 'https://openvibe.codes'; |
| 21 | const SCRIPT_BASE = document.currentScript && document.currentScript.src ? new URL('.', document.currentScript.src).href : '/shared/'; |
| 22 | const POLL_MS = 8000; |
| 23 | const MODES = [['create', 'Create', 'A new website, game or app'], ['openvibe', 'OpenVibe', 'A change to OpenVibe itself'], ['repo', 'My code', 'A repository of yours']]; |
| 24 | const STATE = { queued: 'Waiting', running: 'Running', 'needs-you': 'Needs you', done: 'Done', failed: 'Failed', stopped: 'Stopped' }; |
| 25 | const PLACEHOLDER = { create: 'A goal bar for my sub goal', openvibe: 'Make the menu remember my last tab', repo: 'Fix the login redirect and add a test' }; |
| 26 | const svg = (d) => `<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="${d}"/></svg>`; |
| 27 | const G = { |
| 28 | send: 'M5 12h13M13 6l6 6-6 6', |
| 29 | stop: 'M12 3a9 9 0 1 1 0 18 9 9 0 0 1 0-18zM9.5 9.5h5v5h-5z', |
| 30 | open: 'M14 4h6v6M20 4l-9 9M19 14v5a1.5 1.5 0 0 1-1.5 1.5h-12A1.5 1.5 0 0 1 4 19V7a1.5 1.5 0 0 1 1.5-1.5H10', |
| 31 | code: 'M8 7l-5 5 5 5M16 7l5 5-5 5', |
| 32 | }; |
| 33 | |
| 34 | function styles(d) { |
| 35 | if (d.getElementById('ovdc-styles')) return; |
| 36 | const st = d.createElement('style'); |
| 37 | st.id = 'ovdc-styles'; |
| 38 | st.textContent = ` |
| 39 | .ovdc { display: flex; flex-direction: column; gap: 12px; padding: 10px 10px 14px; } |
| 40 | .ovdc-box { display: grid; gap: 8px; padding: 10px 10px 8px 12px; border-radius: 18px; border: 1px solid var(--border, rgba(255,255,255,.1)); background: var(--bg-card, rgba(255,255,255,.03)); } |
| 41 | .ovdc-box:focus-within { border-color: color-mix(in srgb, var(--accent, #3b82f6) 60%, var(--border, rgba(255,255,255,.1))); } |
| 42 | .ovdc-text { display: block; width: 100%; box-sizing: border-box; min-height: 44px; max-height: 30vh; padding: 2px 0; border: 0; outline: 0; resize: none; background: none; color: var(--text-primary, #ececf1); font: inherit; font-size: 15.5px; line-height: 1.45; } |
| 43 | .ovdc-repo { width: 100%; box-sizing: border-box; height: 36px; padding: 0 10px; border-radius: 10px; border: 1px solid var(--border, rgba(255,255,255,.12)); background: var(--bg-input, rgba(0,0,0,.2)); color: inherit; font: inherit; font-size: 14px; } |
| 44 | .ovdc-repo[hidden] { display: none; } |
| 45 | .ovdc-row { display: flex; align-items: center; gap: 6px; } |
| 46 | .ovdc-modes { flex: 1; min-width: 0; display: flex; gap: 4px; overflow-x: auto; scrollbar-width: none; } |
| 47 | .ovdc-mode { flex: none; height: 32px; padding: 0 11px; border-radius: 999px; border: 1px solid transparent; background: none; color: var(--text-secondary, #a8b3c4); font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; } |
| 48 | .ovdc-mode:hover { color: var(--text-primary, #ececf1); background: var(--bg-hover, rgba(255,255,255,.06)); } |
| 49 | .ovdc-mode[aria-checked="true"] { color: var(--accent-light, #93c5fd); border-color: color-mix(in srgb, var(--accent, #3b82f6) 45%, transparent); background: color-mix(in srgb, var(--accent, #3b82f6) 14%, transparent); } |
| 50 | .ovdc-go { flex: none; display: grid; place-items: center; width: 40px; height: 40px; border: 0; border-radius: 13px; background: var(--accent, #3b82f6); color: var(--on-accent, #fff); cursor: pointer; } |
| 51 | .ovdc-go:disabled { opacity: .45; cursor: default; } |
| 52 | .ovdc-note { margin: 0 2px; font-size: 12.5px; color: var(--text-muted, #8b8b96); } |
| 53 | .ovdc-note:empty { display: none; } |
| 54 | .ovdc-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 0 4px; } |
| 55 | .ovdc-head b { font-size: 13px; letter-spacing: .02em; color: var(--text-secondary, #a8b3c4); } |
| 56 | .ovdc-all { display: inline-flex; align-items: center; gap: 5px; min-height: 32px; font-size: 13px; font-weight: 600; color: var(--accent-light, #93c5fd); text-decoration: none; } |
| 57 | .ovdc-all svg { width: 15px; height: 15px; } |
| 58 | .ovdc-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; } |
| 59 | .ovdc-task { display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 8px; border-radius: 12px; } |
| 60 | .ovdc-task:hover { background: var(--bg-hover, rgba(255,255,255,.05)); } |
| 61 | .ovdc-a { min-width: 0; display: grid; gap: 3px; padding: 9px 4px 9px 10px; color: inherit; text-decoration: none; } |
| 62 | .ovdc-t { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14.5px; font-weight: 600; color: var(--text-primary, #ececf1); } |
| 63 | .ovdc-m { display: flex; align-items: center; gap: 7px; min-width: 0; font-size: 12.5px; color: var(--text-muted, #8b8b96); white-space: nowrap; overflow: hidden; } |
| 64 | .ovdc-s { flex: none; display: inline-flex; align-items: center; gap: 5px; font-weight: 650; } |
| 65 | .ovdc-s::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; } |
| 66 | .ovdc-s[data-s="running"], .ovdc-s[data-s="queued"] { color: var(--accent-light, #93c5fd); } |
| 67 | .ovdc-s[data-s="running"]::before { animation: ovdc-beat 1.2s ease-in-out infinite; } |
| 68 | .ovdc-s[data-s="needs-you"] { color: #fbbf24; } |
| 69 | .ovdc-s[data-s="done"] { color: #4ade80; } |
| 70 | .ovdc-s[data-s="failed"] { color: var(--live-red, #f87171); } |
| 71 | .ovdc-s[data-s="stopped"] { color: var(--text-muted, #8b8b96); } |
| 72 | .ovdc-stop { display: grid; place-items: center; width: 36px; height: 36px; margin-right: 4px; border: 0; border-radius: 10px; background: none; color: var(--text-secondary, #a8b3c4); cursor: pointer; } |
| 73 | .ovdc-stop:hover, .ovdc-stop:focus-visible { background: color-mix(in srgb, var(--live-red, #ef4444) 14%, transparent); color: var(--live-red, #f87171); } |
| 74 | .ovdc-stop svg { width: 19px; height: 19px; } |
| 75 | .ovdc-none { padding: 18px 12px; text-align: center; color: var(--text-muted, #8b8b96); font-size: 13.5px; line-height: 1.45; } |
| 76 | .ovdc-none a { color: var(--accent-light, #93c5fd); } |
| 77 | @keyframes ovdc-beat { 50% { opacity: .35; } } |
| 78 | html[data-ov-motion=off] .ovdc-s::before { animation: none; }`; |
| 79 | d.head.appendChild(st); |
| 80 | } |
| 81 | |
| 82 | const ago = (t) => { |
| 83 | const s = Math.max(0, (Date.now() - new Date(t).getTime()) / 1000); |
| 84 | if (!isFinite(s)) return ''; |
| 85 | return s < 60 ? 'just now' : s < 3600 ? `${Math.floor(s / 60)} min ago` : s < 86400 ? `${Math.floor(s / 3600)} h ago` : `${Math.floor(s / 86400)} d ago`; |
| 86 | }; |
| 87 | const onCodes = (u) => { try { const x = new URL(u); return x.protocol === 'https:' && (x.hostname === 'openvibe.codes' || x.hostname.endsWith('.openvibe.codes')) ? x.href : null; } catch { return null; } }; |
| 88 | const repoName = (r) => String(r || '').replace(/^https?:\/\/[^/]+\//, '').replace(/\.git$/, ''); |
| 89 | |
| 90 | function mount(container, opts = {}) { |
| 91 | const d = container.ownerDocument || document; |
| 92 | styles(d); |
| 93 | const o = { ...opts }; |
| 94 | const tok = () => (typeof o.token === 'function' ? o.token() : o.token) || null; |
| 95 | const codes = String(o.codes || CODES).replace(/\/+$/, ''); |
| 96 | const direct = o.base ? String(o.base).replace(/\/+$/, '') : ''; |
| 97 | const relay = String(o.frameBase || 'https://openvibe.network/api/frame').replace(/\/+$/, ''); |
| 98 | const url = (what, id) => (direct |
| 99 | ? direct + ({ list: '/api/v1/me/tasks?limit=8', start: '/api/v1/tasks', stop: `/api/v1/tasks/${encodeURIComponent(id)}/stop` })[what] |
| 100 | : relay + ({ list: '/codes-tasks?limit=8', start: '/codes-tasks', stop: `/codes-tasks/${encodeURIComponent(id)}/stop` })[what]); |
| 101 | const h = (tag, cls, text) => { const n = d.createElement(tag); if (cls) n.className = cls; if (text != null) n.textContent = text; return n; }; |
| 102 | let gone = false, timer = 0, mode = 'create', tasks = null, reachable = true; |
| 103 | |
| 104 | function loadScript(file, ready) { |
| 105 | if (ready()) return Promise.resolve(true); |
| 106 | return new Promise((resolve) => { |
| 107 | const id = `ovdc-${file}`; |
| 108 | let script = d.getElementById(id); |
| 109 | if (!script) { script = d.createElement('script'); script.id = id; script.src = SCRIPT_BASE + file; script.async = true; d.head.appendChild(script); } |
| 110 | script.addEventListener('load', () => resolve(!!ready()), { once: true }); |
| 111 | script.addEventListener('error', () => resolve(false), { once: true }); |
| 112 | }); |
| 113 | } |
| 114 | function runUrl(id) { |
| 115 | return (direct ? direct + '/api/v1/runs/' : relay + '/codes-runs/') + encodeURIComponent(id); |
| 116 | } |
| 117 | async function openRun(task, from) { |
| 118 | const href = onCodes(task.url); |
| 119 | if (!href || !tok()) return; |
| 120 | const loaded = await Promise.all([ |
| 121 | loadScript('ov-window.js', () => !!root.OVWindow), |
| 122 | loadScript('agent-session.js', () => !!root.OpenVibeAgentSession) |
| 123 | ]); |
| 124 | if (loaded.some((ok) => !ok)) { d.defaultView.location.assign(href); return; } |
| 125 | const body = h('div'); |
| 126 | let stopped = false, controller = null, view = null; |
| 127 | const win = root.OVWindow.open({ id: `codes-run-${task.id}`, title: task.title || 'Coding task', body, kind: 'dialog', size: 'md', from, |
| 128 | onClose: () => { stopped = true; if (controller) controller.abort(); if (view) view.destroy(); } }); |
| 129 | body.appendChild(h('p', 'ovdc-none', 'Loading the run…')); |
| 130 | const headers = () => ({ Authorization: `Bearer ${tok()}`, Accept: 'application/json' }); |
| 131 | try { |
| 132 | const res = await fetch(runUrl(task.id), { headers: headers(), credentials: 'omit' }); |
| 133 | if (!res.ok) throw new Error('The run could not be read.'); |
| 134 | const run = await res.json(); |
| 135 | if (stopped) return; |
| 136 | body.replaceChildren(); |
| 137 | view = root.OpenVibeAgentSession.mount(body, { title: run.title || task.title || 'Coding task', |
| 138 | status: run.status || task.status || 'running', mode: 'card', href }); |
| 139 | let last = 0; |
| 140 | while (!stopped) { |
| 141 | controller = new AbortController(); |
| 142 | const events = await fetch(runUrl(task.id) + '/events', { credentials: 'omit', |
| 143 | headers: { ...headers(), Accept: 'text/event-stream', 'Last-Event-ID': String(last) }, signal: controller.signal }); |
| 144 | if (!events.ok || !events.body) break; |
| 145 | const reader = events.body.getReader(); |
| 146 | const decoder = new TextDecoder(); |
| 147 | let buffer = '', terminal = false; |
| 148 | while (!stopped) { |
| 149 | const part = await reader.read(); |
| 150 | if (part.done) break; |
| 151 | buffer = (buffer + decoder.decode(part.value, { stream: true })).replace(/\r\n/g, '\n'); |
| 152 | let cut; |
| 153 | while ((cut = buffer.indexOf('\n\n')) !== -1) { |
| 154 | const frame = buffer.slice(0, cut); buffer = buffer.slice(cut + 2); |
| 155 | const id = /^id: *(\d+)$/m.exec(frame), data = frame.split('\n').filter((line) => line.startsWith('data:')).map((line) => line.slice(5).trimStart()).join('\n'); |
| 156 | if (id) last = Number(id[1]); |
| 157 | if (!data) continue; |
| 158 | try { const event = JSON.parse(data); view.push(event); if (event.type === 'RUN_FINISHED' || event.type === 'RUN_ERROR') terminal = true; } catch { /* skip one malformed event */ } |
| 159 | } |
| 160 | } |
| 161 | if (terminal || !/running|queued|placed/.test(run.status || '')) break; |
| 162 | await new Promise((resolve) => setTimeout(resolve, 1500)); |
| 163 | } |
| 164 | } catch (err) { |
| 165 | if (!stopped) { body.replaceChildren(); const a = h('a', 'ovk-btn ovk-btn--quiet', 'Open on Codes'); a.href = href; body.appendChild(a); } |
| 166 | } |
| 167 | return win; |
| 168 | } |
| 169 | |
| 170 | container.innerHTML = ''; |
| 171 | container.classList.add('ovdc'); |
| 172 | const box = h('form', 'ovdc-box'); |
| 173 | box.setAttribute('aria-label', 'Start a coding task'); |
| 174 | const text = h('textarea', 'ovdc-text'); |
| 175 | text.rows = 2; text.maxLength = 16000; text.placeholder = PLACEHOLDER.create; |
| 176 | text.setAttribute('aria-label', 'What should we make?'); |
| 177 | const repo = h('input', 'ovdc-repo'); |
| 178 | repo.placeholder = 'Repository: owner/name or its address'; repo.setAttribute('aria-label', 'Repository'); repo.hidden = true; repo.maxLength = 300; |
| 179 | const row = h('div', 'ovdc-row'); |
| 180 | const modes = h('div', 'ovdc-modes'); |
| 181 | modes.setAttribute('role', 'radiogroup'); modes.setAttribute('aria-label', 'Where it works'); |
| 182 | for (const [id, label, hint] of MODES) { |
| 183 | const b = h('button', 'ovdc-mode', label); |
| 184 | b.type = 'button'; b.dataset.mode = id; b.title = hint; |
| 185 | b.setAttribute('role', 'radio'); b.setAttribute('aria-checked', String(id === mode)); |
| 186 | modes.appendChild(b); |
| 187 | } |
| 188 | const go = h('button', 'ovdc-go'); go.type = 'submit'; go.innerHTML = svg(G.send); go.setAttribute('aria-label', 'Start'); go.disabled = true; |
| 189 | row.append(modes, go); |
| 190 | const note = h('p', 'ovdc-note'); note.setAttribute('role', 'status'); |
| 191 | box.append(text, repo, row, note); |
| 192 | const head = h('div', 'ovdc-head'); |
| 193 | const all = h('a', 'ovdc-all'); all.href = `${codes}/`; all.innerHTML = `<span>Open Codes</span>${svg(G.open)}`; |
| 194 | head.append(h('b', null, 'Your tasks'), all); |
| 195 | const list = h('ul', 'ovdc-list'); |
| 196 | container.append(box, head, list); |
| 197 | |
| 198 | const ready = () => { go.disabled = !text.value.trim() || (mode === 'repo' && !repo.value.trim()); }; |
| 199 | text.addEventListener('input', () => { text.style.height = 'auto'; text.style.height = Math.min(text.scrollHeight, d.defaultView.innerHeight * 0.3) + 'px'; ready(); }); |
| 200 | repo.addEventListener('input', ready); |
| 201 | text.addEventListener('keydown', (e) => { if (e.key === 'Enter' && (e.metaKey || e.ctrlKey) && !go.disabled) { e.preventDefault(); box.requestSubmit ? box.requestSubmit() : start(); } }); |
| 202 | modes.addEventListener('click', (e) => { |
| 203 | const b = e.target.closest && e.target.closest('[data-mode]'); if (!b) return; |
| 204 | mode = b.dataset.mode; |
| 205 | modes.querySelectorAll('[data-mode]').forEach((x) => x.setAttribute('aria-checked', String(x === b))); |
| 206 | text.placeholder = PLACEHOLDER[mode]; |
| 207 | repo.hidden = mode !== 'repo'; |
| 208 | ready(); |
| 209 | (mode === 'repo' && !repo.value ? repo : text).focus(); |
| 210 | }); |
| 211 | |
| 212 | const fallback = () => `${codes}/?${new URLSearchParams({ prompt: text.value.trim(), mode, ...(mode === 'repo' ? { repo: repo.value.trim() } : {}) })}`; |
| 213 | async function call(what, id, body) { |
| 214 | const t = tok(); if (!t) throw new Error('signed out'); |
| 215 | const r = await fetch(url(what, id), { method: body || what === 'stop' ? 'POST' : 'GET', headers: { authorization: `Bearer ${t}`, accept: 'application/json', ...(body ? { 'content-type': 'application/json' } : {}) }, body: body ? JSON.stringify(body) : undefined, credentials: 'omit' }); |
| 216 | if (!r.ok) { const e = new Error(`HTTP ${r.status}`); e.status = r.status; try { e.body = await r.json(); } catch { /* */ } throw e; } |
| 217 | return r.json().catch(() => ({})); |
| 218 | } |
| 219 | async function start() { |
| 220 | if (go.disabled) return; |
| 221 | if (!reachable) { d.defaultView.location.assign(fallback()); return; } |
| 222 | go.disabled = true; note.textContent = 'Starting…'; |
| 223 | try { |
| 224 | const r = await call('start', null, { text: text.value.trim(), mode, ...(mode === 'repo' ? { repo: repo.value.trim() } : {}) }); |
| 225 | text.value = ''; text.dispatchEvent(new Event('input')); |
| 226 | note.textContent = ''; |
| 227 | const there = onCodes(r && r.url); |
| 228 | if (there) { const a = h('a', null, 'Follow it on openvibe.codes'); a.href = there; note.textContent = 'Started. '; note.appendChild(a); } |
| 229 | read(); |
| 230 | } catch (e) { |
| 231 | ready(); |
| 232 | if (e.status === 404 || e.status === 502 || e.status === 503 || e.message === 'Failed to fetch') { d.defaultView.location.assign(fallback()); return; } |
| 233 | note.textContent = (e.body && (e.body.message || e.body.error)) || 'It did not start. Try again in a moment.'; |
| 234 | } |
| 235 | } |
| 236 | box.addEventListener('submit', (e) => { e.preventDefault(); start(); }); |
| 237 | |
| 238 | function paint() { |
| 239 | list.replaceChildren(); |
| 240 | if (!tok()) { list.appendChild(h('li', 'ovdc-none', 'Sign in to start coding tasks and follow them here.')); return; } |
| 241 | if (!reachable) { |
| 242 | const li = h('li', 'ovdc-none'); |
| 243 | li.append('Your tasks are on '); |
| 244 | const a = h('a', null, 'openvibe.codes'); a.href = `${codes}/`; li.append(a, '. Starting one here opens it there.'); |
| 245 | list.appendChild(li); return; |
| 246 | } |
| 247 | if (!tasks) return; |
| 248 | if (!tasks.length) { list.appendChild(h('li', 'ovdc-none', 'Nothing yet. Say what to make, and an agent builds it while you watch.')); return; } |
| 249 | for (const t of tasks) { |
| 250 | const li = h('li', 'ovdc-task'); |
| 251 | const there = onCodes(t.url); |
| 252 | const a = h(there ? 'a' : 'div', 'ovdc-a'); |
| 253 | if (there) { a.href = there; a.addEventListener('click', (event) => { |
| 254 | if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return; |
| 255 | event.preventDefault(); openRun(t, a); |
| 256 | }); } |
| 257 | a.appendChild(h('span', 'ovdc-t', t.title || 'A coding task')); |
| 258 | const m = h('span', 'ovdc-m'); |
| 259 | const s = h('span', 'ovdc-s', STATE[t.status] || t.status || ''); s.dataset.s = t.status || ''; |
| 260 | m.appendChild(s); |
| 261 | const bits = [t.mode === 'repo' && t.repo ? repoName(t.repo) : t.mode === 'openvibe' ? 'OpenVibe' : null, t.steps ? `${t.steps} steps` : null, ago(t.updated_at || t.started_at)].filter(Boolean); |
| 262 | if (bits.length) m.appendChild(h('span', null, bits.join(' · '))); |
| 263 | a.appendChild(m); |
| 264 | li.appendChild(a); |
| 265 | if (t.status === 'running' || t.status === 'queued' || t.status === 'needs-you') { |
| 266 | const x = h('button', 'ovdc-stop'); x.type = 'button'; x.innerHTML = svg(G.stop); x.setAttribute('aria-label', `Stop: ${t.title || 'this task'}`); x.title = 'Stop'; |
| 267 | x.addEventListener('click', async () => { x.disabled = true; try { await call('stop', t.id); } catch { /* the next read says */ } read(); }); |
| 268 | li.appendChild(x); |
| 269 | } |
| 270 | list.appendChild(li); |
| 271 | } |
| 272 | } |
| 273 | async function read() { |
| 274 | clearTimeout(timer); |
| 275 | if (gone || !tok()) { paint(); return; } |
| 276 | try { |
| 277 | const j = await call('list'); |
| 278 | reachable = true; |
| 279 | tasks = (Array.isArray(j) ? j : j.tasks || []).slice(0, 8); |
| 280 | tasks.sort((a, b) => (/running|queued|needs-you/.test(b.status) ? 1 : 0) - (/running|queued|needs-you/.test(a.status) ? 1 : 0)); |
| 281 | } catch (e) { |
| 282 | if (e.status === 401 || e.status === 403) tasks = []; |
| 283 | else reachable = false; |
| 284 | } |
| 285 | if (gone) return; |
| 286 | paint(); |
| 287 | if (reachable && tasks && tasks.some((t) => /running|queued/.test(t.status)) && (!o.active || o.active())) timer = setTimeout(read, POLL_MS); |
| 288 | } |
| 289 | // What another part handed over (Vibe's Build it in Code) goes in the box, ready to start. |
| 290 | const take = () => { const t = typeof o.take === 'function' ? o.take() : ''; if (!t) return; text.value = t; text.dispatchEvent(new Event('input')); text.focus(); note.textContent = 'From your chat with Vibe. Start it, or change it first.'; }; |
| 291 | paint(); |
| 292 | read(); |
| 293 | take(); |
| 294 | return { |
| 295 | refresh: () => { take(); read(); }, |
| 296 | focus: () => text.focus(), |
| 297 | destroy() { gone = true; clearTimeout(timer); container.replaceChildren(); container.classList.remove('ovdc'); }, |
| 298 | }; |
| 299 | } |
| 300 | |
| 301 | root.OpenVibeDockCode = { mount }; |
| 302 | if (typeof module === 'object' && module.exports) module.exports = { mount }; |
| 303 | })(typeof window !== 'undefined' ? window : globalThis); |
| 304 |