openvisio-agent 0.14.0 → 0.15.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.
- package/README.md +1 -1
- package/bin/cli.mjs +2 -2
- package/package.json +1 -1
- package/src/watch.mjs +33 -12
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
|
-
Routine messages, triage, introductions, catch-up checks, and ticket movement use a
|
|
61
|
+
Routine messages, triage, introductions, catch-up checks, and ticket movement use a lower-cost model by default. Claude uses Haiku and Codex uses `gpt-5.6-terra`; Luna is intentionally not used because watcher coordination depends on reliable MCP tool use. Requests that require repository work route to the coding model: Sonnet for Claude and `gpt-5.6-sol` for Codex. Override either lane with `--chat-model` and `--model`.
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
64
|
openvisio-agent watch --name ada # run in this terminal
|
package/bin/cli.mjs
CHANGED
|
@@ -58,8 +58,8 @@ connect --backend
|
|
|
58
58
|
--model <m> coding model. Claude defaults to sonnet; Codex defaults to
|
|
59
59
|
gpt-5.6-sol; OpenCode uses its configured default.
|
|
60
60
|
Engineers can also change it live from chat: "@agent /model …".
|
|
61
|
-
--chat-model <m>
|
|
62
|
-
|
|
61
|
+
--chat-model <m> coordination model for chat, triage, and board movement.
|
|
62
|
+
Defaults: Claude haiku, Codex gpt-5.6-terra.
|
|
63
63
|
Coding work stays on --model.
|
|
64
64
|
--no-service skip the background service — just save config + print the
|
|
65
65
|
watch commands to run yourself.
|
package/package.json
CHANGED
package/src/watch.mjs
CHANGED
|
@@ -215,10 +215,11 @@ 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
|
-
// Keep routine coordination on
|
|
219
|
-
//
|
|
218
|
+
// Keep routine coordination on a balanced tool-capable tier. Luna proved too
|
|
219
|
+
// willing to narrate instead of acting in MCP-heavy watcher cycles, so Codex
|
|
220
|
+
// defaults to Terra for coordination and reserves Sol for repository work.
|
|
220
221
|
const defaultModel = agent === 'claude' ? 'sonnet' : agent === 'codex' ? 'gpt-5.6-sol' : ''
|
|
221
|
-
const defaultChatModel = agent === 'claude' ? 'haiku' : agent === 'codex' ? 'gpt-5.6-
|
|
222
|
+
const defaultChatModel = agent === 'claude' ? 'haiku' : agent === 'codex' ? 'gpt-5.6-terra' : ''
|
|
222
223
|
const model = String(flags.model || (saved && saved.model) || defaultModel)
|
|
223
224
|
const chatModel = String(flags['chat-model'] || (saved && saved.chatModel) || defaultChatModel)
|
|
224
225
|
|
|
@@ -343,17 +344,35 @@ function createCodexRunner({ mcpUrl, mcpHeaders, workdir, canCode, maxCycleMs, l
|
|
|
343
344
|
...(m ? ['--model', m] : []),
|
|
344
345
|
...(mcpOverride ? ['-c', mcpOverride] : []),
|
|
345
346
|
full]
|
|
346
|
-
let child = null, done = false, didCode = false, didMessage = false, outputText = '', jsonlBuffer = ''
|
|
347
|
-
const
|
|
347
|
+
let child = null, done = false, didCode = false, didRepoMutation = false, didMessage = false, outputText = '', jsonlBuffer = ''
|
|
348
|
+
const mcpCalls = new Set(), mcpErrors = new Set()
|
|
349
|
+
let didMcpTaskRead = false, didMcpTaskUpdate = false
|
|
350
|
+
const finish = (o) => {
|
|
351
|
+
if (done) return
|
|
352
|
+
done = true
|
|
353
|
+
clearTimeout(timer)
|
|
354
|
+
const calls = [...mcpCalls]
|
|
355
|
+
log('codex MCP calls: ' + (calls.length ? calls.join(', ') : 'none') + (mcpErrors.size ? ' (failed: ' + [...mcpErrors].join(', ') + ')' : ''))
|
|
356
|
+
resolve({ ...o, didCode, didRepoMutation, didMessage, didMcpTaskRead, didMcpTaskUpdate, mcpCalls: calls, outputText })
|
|
357
|
+
}
|
|
348
358
|
const inspectLine = (line) => {
|
|
349
359
|
const s = line.trim()
|
|
350
360
|
if (!s) return
|
|
351
361
|
if (/command_execution|file_change|apply_patch|shell_command|exec_command/i.test(s)) didCode = true
|
|
362
|
+
if (/file_change|apply_patch/i.test(s) || /\b(?:git\s+(?:commit|push)|gh\s+pr\s+create)\b/i.test(s)) didRepoMutation = true
|
|
352
363
|
if (/post_message|comment_ticket/i.test(s)) didMessage = true
|
|
353
364
|
try {
|
|
354
365
|
const event = JSON.parse(s)
|
|
355
366
|
const item = event.item ?? event
|
|
356
367
|
if (item?.type === 'agent_message' && typeof item.text === 'string') outputText += ' ' + item.text
|
|
368
|
+
if (item?.type === 'mcp_tool_call') {
|
|
369
|
+
const tool = String(item.tool || item.name || item.method || 'unknown').replace(/^openvisio-team[.:/]/, '')
|
|
370
|
+
mcpCalls.add(tool)
|
|
371
|
+
if (/^(?:get_ticket|list_tasks|list_task_types)$/.test(tool)) didMcpTaskRead = true
|
|
372
|
+
if (tool === 'update_ticket') didMcpTaskUpdate = true
|
|
373
|
+
if (/post_message|comment_ticket/.test(tool)) didMessage = true
|
|
374
|
+
if (/fail|error/i.test(String(item.status || '')) || item.error) mcpErrors.add(tool)
|
|
375
|
+
}
|
|
357
376
|
} catch { /* non-JSON diagnostic */ }
|
|
358
377
|
if (debug) log(' · ' + s.slice(0, 220))
|
|
359
378
|
}
|
|
@@ -578,7 +597,7 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
578
597
|
}
|
|
579
598
|
const ensureMcpSession = async () => {
|
|
580
599
|
if (mcpSessionId) return
|
|
581
|
-
const res = await mcpPost({ jsonrpc: '2.0', id: ++mcpRpcId, method: 'initialize', params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'openvisio-agent', version: '0.
|
|
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.0' } } }, false)
|
|
582
601
|
if (!res.ok) throw new Error('MCP initialize HTTP ' + res.status)
|
|
583
602
|
await mcpPayload(res)
|
|
584
603
|
mcpSessionId = res.headers.get('mcp-session-id') || ''
|
|
@@ -690,13 +709,15 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
690
709
|
const heartbeat = targets.length ? setInterval(() => emitStatus('working'), 9000) : null
|
|
691
710
|
try {
|
|
692
711
|
const result = await runCycle(prompt, useModel)
|
|
693
|
-
//
|
|
694
|
-
//
|
|
695
|
-
// Do not retry a real blocker
|
|
712
|
+
// A zero exit is not proof of work. Assigned-ticket cycles must both use
|
|
713
|
+
// the task MCP and leave repository evidence. A simple `ls` no longer
|
|
714
|
+
// counts as completion. Do not retry a verified real blocker.
|
|
696
715
|
const legitimateNoWork = /\b(?:no (?:open |assigned |pending )?tasks?|not assigned to (?:me|you)|assigned to (?:another|someone else)|blocked|cannot|can't|missing|need access|permission|unclear)\b/i.test(result?.outputText || '')
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
716
|
+
const incomplete = !result?.didRepoMutation || !result?.didMcpTaskRead || !result?.didMcpTaskUpdate
|
|
717
|
+
if (agent === 'codex' && kind === 'full' && result?.subtype === 'ok' && incomplete && !legitimateNoWork) {
|
|
718
|
+
const missing = [!result.didMcpTaskRead && 'read the ticket through get_ticket/list_tasks', !result.didRepoMutation && 'perform and verify the repository change', !result.didMcpTaskUpdate && 'update the ticket through update_ticket'].filter(Boolean)
|
|
719
|
+
log('coding cycle incomplete; recovery requires: ' + missing.join(', '))
|
|
720
|
+
await runCycle(`The assigned task is NOT complete. Missing evidence: ${missing.join('; ')}. Do not post another acknowledgement or plan. Resume now. First use get_ticket/list_tasks and list_task_types, then do the repository work, verify it, commit and push an agent/* branch, open the PR, call update_ticket with the correct board column, and post exactly one result update with evidence. If a real blocker appears, report it once.`, codeModel)
|
|
700
721
|
}
|
|
701
722
|
} finally {
|
|
702
723
|
if (heartbeat) clearInterval(heartbeat)
|