openvisio-agent 0.15.0 → 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/README.md +1 -1
- package/bin/cli.mjs +2 -2
- package/package.json +1 -1
- package/src/watch.mjs +23 -11
- package/src/ws.mjs +3 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ Runs the **autonomy loop** — the agent replies to @mentions and picks up ticke
|
|
|
58
58
|
|
|
59
59
|
Backend/BYO watchers reconcile immediately whenever the process starts or the WebSocket connects. A direct MCP session uses the backend's actual tools (`list_agents`, `list_projects`, `list_tasks`, `list_task_types`, and `list_activity`) to recover assigned tasks and recent mention activity missed while offline. The same zero-model check runs every five minutes as a safety net; a model starts only when pending work exists.
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Claude uses Haiku for routine coordination and Sonnet for repository work. Codex uses `gpt-5.6-sol` for every cycle, including messages, mentions, triage, board movement, MCP calls, and coding. This intentionally favors reliability and consistent tool use over the cheaper Codex tiers.
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
64
|
openvisio-agent watch --name ada # run in this terminal
|
package/bin/cli.mjs
CHANGED
|
@@ -59,8 +59,8 @@ connect --backend
|
|
|
59
59
|
gpt-5.6-sol; OpenCode uses its configured default.
|
|
60
60
|
Engineers can also change it live from chat: "@agent /model …".
|
|
61
61
|
--chat-model <m> coordination model for chat, triage, and board movement.
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
Claude defaults to haiku. Codex uses gpt-5.6-sol for every
|
|
63
|
+
cycle, including coordination and messaging.
|
|
64
64
|
--no-service skip the background service — just save config + print the
|
|
65
65
|
watch commands to run yourself.
|
|
66
66
|
|
package/package.json
CHANGED
package/src/watch.mjs
CHANGED
|
@@ -215,11 +215,10 @@ export async function runWatch({ flags }) {
|
|
|
215
215
|
const workdir = chatOnly ? '' : (explicitWorkdir || (saved && (saved.workspace || saved.workdir)) || DEFAULT_WORKSPACE)
|
|
216
216
|
if (workdir) { try { mkdirSync(workdir, { recursive: true }) } catch { /* best-effort; spawn will surface a real problem */ } }
|
|
217
217
|
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
// defaults to Terra for coordination and reserves Sol for repository work.
|
|
218
|
+
// Codex uses Sol for every watcher cycle. Reliability and consistent MCP/tool
|
|
219
|
+
// behaviour take priority over maintaining a cheaper coordination lane.
|
|
221
220
|
const defaultModel = agent === 'claude' ? 'sonnet' : agent === 'codex' ? 'gpt-5.6-sol' : ''
|
|
222
|
-
const defaultChatModel = agent === 'claude' ? 'haiku' : agent === 'codex' ? 'gpt-5.6-
|
|
221
|
+
const defaultChatModel = agent === 'claude' ? 'haiku' : agent === 'codex' ? 'gpt-5.6-sol' : ''
|
|
223
222
|
const model = String(flags.model || (saved && saved.model) || defaultModel)
|
|
224
223
|
const chatModel = String(flags['chat-model'] || (saved && saved.chatModel) || defaultChatModel)
|
|
225
224
|
|
|
@@ -529,10 +528,16 @@ function createCycleRunner({ claude, agent, mcpUrl, mcpHeaders, cfgKey, mcpConfi
|
|
|
529
528
|
function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConfig, mcpUrl, workdir, model, chatModel, debug }) {
|
|
530
529
|
const log = (m) => process.stdout.write('[ws ' + new Date().toISOString() + '] ' + m + '\n')
|
|
531
530
|
let handle = null
|
|
531
|
+
let statusEnabled = true
|
|
532
|
+
let lastStatusSentAt = 0
|
|
532
533
|
// Channels the agent is actively working in this cycle — drives the live
|
|
533
534
|
// agent:status broadcast (thinking → working → typing → done).
|
|
534
535
|
const statusTargets = new Set()
|
|
535
|
-
const
|
|
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) }
|
|
536
541
|
const canCode = !!workdir
|
|
537
542
|
// The Mastra bridge authenticates per-CALL: every openvisio-team tool needs
|
|
538
543
|
// agent_identifier + agent_api_key as arguments. Hand them over up front.
|
|
@@ -597,7 +602,7 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
597
602
|
}
|
|
598
603
|
const ensureMcpSession = async () => {
|
|
599
604
|
if (mcpSessionId) return
|
|
600
|
-
const res = await mcpPost({ jsonrpc: '2.0', id: ++mcpRpcId, method: 'initialize', params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'openvisio-agent', version: '0.15.
|
|
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)
|
|
601
606
|
if (!res.ok) throw new Error('MCP initialize HTTP ' + res.status)
|
|
602
607
|
await mcpPayload(res)
|
|
603
608
|
mcpSessionId = res.headers.get('mcp-session-id') || ''
|
|
@@ -700,7 +705,7 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
700
705
|
const prompt = (ctx.length ? ctx.join('\n') + '\n\n' : '') + baseFor(kind)
|
|
701
706
|
// Chat-shaped cycles (mentions/intro) may run on the cheaper chat model; code
|
|
702
707
|
// work (full/sweep) uses the main model.
|
|
703
|
-
const useModel = kind === 'full' ? codeModel : liteModel
|
|
708
|
+
const useModel = agent === 'codex' ? codeModel : kind === 'full' ? codeModel : liteModel
|
|
704
709
|
log('running ' + kind + ' cycle…' + (ctx.length ? ' (' + ctx.length + ' event' + (ctx.length === 1 ? '' : 's') + ')' : '') + (useModel ? ' [' + useModel + ']' : ''))
|
|
705
710
|
// Live status: "working" now + a heartbeat so the UI (and its TTL) stays lit
|
|
706
711
|
// through a long cycle; onTool flips it to "typing" when post_message fires.
|
|
@@ -721,7 +726,7 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
721
726
|
}
|
|
722
727
|
} finally {
|
|
723
728
|
if (heartbeat) clearInterval(heartbeat)
|
|
724
|
-
for (const c of targets) {
|
|
729
|
+
for (const c of targets) { sendStatus(c, 'done'); statusTargets.delete(c) }
|
|
725
730
|
busy = false
|
|
726
731
|
if (queued || pending.length) { const next = queued || 'fast'; queued = null; void drain(next) }
|
|
727
732
|
}
|
|
@@ -838,15 +843,22 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
838
843
|
log('agent:mention in channel ' + (cid != null ? cid : '?') + (threadRoot != null ? ' (thread ' + threadRoot + ')' : ''))
|
|
839
844
|
// Light up the live status the instant we pick this up (thinking → the cycle
|
|
840
845
|
// takes it to working → typing → done).
|
|
841
|
-
if (cid != null) { statusTargets.add(cid);
|
|
846
|
+
if (cid != null) { statusTargets.add(cid); sendStatus(cid, 'thinking') }
|
|
842
847
|
const codingMention = canCode && needsCode(text)
|
|
843
848
|
const ctx = cid != null
|
|
844
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.`
|
|
845
850
|
: undefined
|
|
846
851
|
void drain(codingMention ? 'full' : 'fast', ctx)
|
|
847
852
|
} else if (k === 'error') {
|
|
848
|
-
|
|
849
|
-
|
|
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
|
+
}
|
|
850
862
|
} else {
|
|
851
863
|
log('event ' + k)
|
|
852
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
|
-
|
|
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) => {
|