openvisio-agent 0.15.1 → 0.15.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openvisio-agent",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "description": "Connect Claude Code, Codex, or OpenCode to an OpenVisio team — MCP tools + optional autonomy — in one command.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/watch.mjs CHANGED
@@ -528,10 +528,16 @@ function createCycleRunner({ claude, agent, mcpUrl, mcpHeaders, cfgKey, mcpConfi
528
528
  function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConfig, mcpUrl, workdir, model, chatModel, debug }) {
529
529
  const log = (m) => process.stdout.write('[ws ' + new Date().toISOString() + '] ' + m + '\n')
530
530
  let handle = null
531
+ let statusEnabled = true
532
+ let lastStatusSentAt = 0
531
533
  // Channels the agent is actively working in this cycle — drives the live
532
534
  // agent:status broadcast (thinking → working → typing → done).
533
535
  const statusTargets = new Set()
534
- const emitStatus = (state) => { for (const c of statusTargets) { try { handle && handle.sendStatus(c, state) } catch { /* best-effort */ } } }
536
+ const sendStatus = (channelId, state) => {
537
+ if (!statusEnabled) return
538
+ try { if (handle) { lastStatusSentAt = Date.now(); handle.sendStatus(channelId, state) } } catch { /* best-effort */ }
539
+ }
540
+ const emitStatus = (state) => { for (const c of statusTargets) sendStatus(c, state) }
535
541
  const canCode = !!workdir
536
542
  // The Mastra bridge authenticates per-CALL: every openvisio-team tool needs
537
543
  // agent_identifier + agent_api_key as arguments. Hand them over up front.
@@ -596,7 +602,7 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
596
602
  }
597
603
  const ensureMcpSession = async () => {
598
604
  if (mcpSessionId) return
599
- const res = await mcpPost({ jsonrpc: '2.0', id: ++mcpRpcId, method: 'initialize', params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'openvisio-agent', version: '0.15.1' } } }, false)
605
+ const res = await mcpPost({ jsonrpc: '2.0', id: ++mcpRpcId, method: 'initialize', params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'openvisio-agent', version: '0.15.2' } } }, false)
600
606
  if (!res.ok) throw new Error('MCP initialize HTTP ' + res.status)
601
607
  await mcpPayload(res)
602
608
  mcpSessionId = res.headers.get('mcp-session-id') || ''
@@ -720,7 +726,7 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
720
726
  }
721
727
  } finally {
722
728
  if (heartbeat) clearInterval(heartbeat)
723
- for (const c of targets) { try { handle && handle.sendStatus(c, 'done') } catch { /* noop */ } statusTargets.delete(c) }
729
+ for (const c of targets) { sendStatus(c, 'done'); statusTargets.delete(c) }
724
730
  busy = false
725
731
  if (queued || pending.length) { const next = queued || 'fast'; queued = null; void drain(next) }
726
732
  }
@@ -837,15 +843,22 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
837
843
  log('agent:mention in channel ' + (cid != null ? cid : '?') + (threadRoot != null ? ' (thread ' + threadRoot + ')' : ''))
838
844
  // Light up the live status the instant we pick this up (thinking → the cycle
839
845
  // takes it to working → typing → done).
840
- if (cid != null) { statusTargets.add(cid); try { handle && handle.sendStatus(cid, 'thinking') } catch { /* best-effort */ } }
846
+ if (cid != null) { statusTargets.add(cid); sendStatus(cid, 'thinking') }
841
847
  const codingMention = canCode && needsCode(text)
842
848
  const ctx = cid != null
843
849
  ? `You were @mentioned in OpenVisio channel ${cid}${who ? ` by "${who}"` : ''}: "${text}". This mention is FOR YOU. ${codingMention ? 'This is repository work: complete the coding flow first, then send' : 'Send'} EXACTLY ONE reply with post_message: arguments: channel_id ${cid}${threadRoot != null ? `, parent_id ${threadRoot} (reply IN THAT THREAD, do not start a new top-level message)` : ''}, plus agent_identifier + agent_api_key from the AUTH line above, and a 1-3 sentence reply. Compose the whole answer, then post it ONCE. Do not post a first reply and then a revised version. FIRST read the recent messages in this thread: if you already answered this, or another agent was the one addressed, do NOT post at all. Be sure of your answer before sending.${who ? ` To @mention them back, write their EXACT full name "@${who}". A mention only links when the name matches exactly.` : ''} You ALREADY have the message here. Do not poll_inbox, and after your single reply, STOP.`
844
850
  : undefined
845
851
  void drain(codingMention ? 'full' : 'fast', ctx)
846
852
  } else if (k === 'error') {
847
- // Surface the backend's rejection detail instead of a bare "event error".
848
- log('error event: ' + JSON.stringify(raw).slice(0, 220))
853
+ const detail = raw && (raw.message || raw.error || raw.reason || raw.code || raw.d?.message || raw.d?.error)
854
+ // Older backend stages reject agent_status. Stop its heartbeat after the
855
+ // first immediate rejection instead of producing an error every nine seconds.
856
+ if (statusEnabled && lastStatusSentAt && Date.now() - lastStatusSentAt < 3000) {
857
+ statusEnabled = false
858
+ log('live agent status unsupported by this backend; disabling status heartbeat' + (detail ? ': ' + String(detail).slice(0, 160) : ''))
859
+ } else {
860
+ log('error event: ' + (detail ? String(detail) : JSON.stringify(raw)).slice(0, 220))
861
+ }
849
862
  } else {
850
863
  log('event ' + k)
851
864
  }
package/src/ws.mjs CHANGED
@@ -79,7 +79,9 @@ export function connectAgentWs({ wsUrl, apiKey, identifier, onEvent, onConnect,
79
79
  try { msg = JSON.parse(typeof ev.data === 'string' ? ev.data : String(ev.data)) } catch { return }
80
80
  const kind = msg && msg.k
81
81
  if (!kind || kind === 'keepalive:ack') return // keepalive round-trip, nothing to do
82
- try { onEvent(kind, msg.d) } catch (e) { log('event handler error: ' + (e && e.message ? e.message : e)) }
82
+ // Error metadata often lives on the JSON envelope rather than `d`. Preserve
83
+ // it so callers do not receive an unhelpful empty object.
84
+ try { onEvent(kind, kind === 'error' ? msg : msg.d) } catch (e) { log('event handler error: ' + (e && e.message ? e.message : e)) }
83
85
  })
84
86
 
85
87
  const down = (why) => {