openvisio-agent 0.14.0 → 0.15.1
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 +3 -3
- package/package.json +1 -1
- package/src/watch.mjs +33 -13
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
|
@@ -58,9 +58,9 @@ 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
|
-
|
|
63
|
-
|
|
61
|
+
--chat-model <m> coordination model for chat, triage, and board movement.
|
|
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,10 +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
|
-
//
|
|
218
|
+
// Codex uses Sol for every watcher cycle. Reliability and consistent MCP/tool
|
|
219
|
+
// behaviour take priority over maintaining a cheaper coordination lane.
|
|
220
220
|
const defaultModel = agent === 'claude' ? 'sonnet' : agent === 'codex' ? 'gpt-5.6-sol' : ''
|
|
221
|
-
const defaultChatModel = agent === 'claude' ? 'haiku' : agent === 'codex' ? 'gpt-5.6-
|
|
221
|
+
const defaultChatModel = agent === 'claude' ? 'haiku' : agent === 'codex' ? 'gpt-5.6-sol' : ''
|
|
222
222
|
const model = String(flags.model || (saved && saved.model) || defaultModel)
|
|
223
223
|
const chatModel = String(flags['chat-model'] || (saved && saved.chatModel) || defaultChatModel)
|
|
224
224
|
|
|
@@ -343,17 +343,35 @@ function createCodexRunner({ mcpUrl, mcpHeaders, workdir, canCode, maxCycleMs, l
|
|
|
343
343
|
...(m ? ['--model', m] : []),
|
|
344
344
|
...(mcpOverride ? ['-c', mcpOverride] : []),
|
|
345
345
|
full]
|
|
346
|
-
let child = null, done = false, didCode = false, didMessage = false, outputText = '', jsonlBuffer = ''
|
|
347
|
-
const
|
|
346
|
+
let child = null, done = false, didCode = false, didRepoMutation = false, didMessage = false, outputText = '', jsonlBuffer = ''
|
|
347
|
+
const mcpCalls = new Set(), mcpErrors = new Set()
|
|
348
|
+
let didMcpTaskRead = false, didMcpTaskUpdate = false
|
|
349
|
+
const finish = (o) => {
|
|
350
|
+
if (done) return
|
|
351
|
+
done = true
|
|
352
|
+
clearTimeout(timer)
|
|
353
|
+
const calls = [...mcpCalls]
|
|
354
|
+
log('codex MCP calls: ' + (calls.length ? calls.join(', ') : 'none') + (mcpErrors.size ? ' (failed: ' + [...mcpErrors].join(', ') + ')' : ''))
|
|
355
|
+
resolve({ ...o, didCode, didRepoMutation, didMessage, didMcpTaskRead, didMcpTaskUpdate, mcpCalls: calls, outputText })
|
|
356
|
+
}
|
|
348
357
|
const inspectLine = (line) => {
|
|
349
358
|
const s = line.trim()
|
|
350
359
|
if (!s) return
|
|
351
360
|
if (/command_execution|file_change|apply_patch|shell_command|exec_command/i.test(s)) didCode = true
|
|
361
|
+
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
362
|
if (/post_message|comment_ticket/i.test(s)) didMessage = true
|
|
353
363
|
try {
|
|
354
364
|
const event = JSON.parse(s)
|
|
355
365
|
const item = event.item ?? event
|
|
356
366
|
if (item?.type === 'agent_message' && typeof item.text === 'string') outputText += ' ' + item.text
|
|
367
|
+
if (item?.type === 'mcp_tool_call') {
|
|
368
|
+
const tool = String(item.tool || item.name || item.method || 'unknown').replace(/^openvisio-team[.:/]/, '')
|
|
369
|
+
mcpCalls.add(tool)
|
|
370
|
+
if (/^(?:get_ticket|list_tasks|list_task_types)$/.test(tool)) didMcpTaskRead = true
|
|
371
|
+
if (tool === 'update_ticket') didMcpTaskUpdate = true
|
|
372
|
+
if (/post_message|comment_ticket/.test(tool)) didMessage = true
|
|
373
|
+
if (/fail|error/i.test(String(item.status || '')) || item.error) mcpErrors.add(tool)
|
|
374
|
+
}
|
|
357
375
|
} catch { /* non-JSON diagnostic */ }
|
|
358
376
|
if (debug) log(' · ' + s.slice(0, 220))
|
|
359
377
|
}
|
|
@@ -578,7 +596,7 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
578
596
|
}
|
|
579
597
|
const ensureMcpSession = async () => {
|
|
580
598
|
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.
|
|
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)
|
|
582
600
|
if (!res.ok) throw new Error('MCP initialize HTTP ' + res.status)
|
|
583
601
|
await mcpPayload(res)
|
|
584
602
|
mcpSessionId = res.headers.get('mcp-session-id') || ''
|
|
@@ -681,7 +699,7 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
681
699
|
const prompt = (ctx.length ? ctx.join('\n') + '\n\n' : '') + baseFor(kind)
|
|
682
700
|
// Chat-shaped cycles (mentions/intro) may run on the cheaper chat model; code
|
|
683
701
|
// work (full/sweep) uses the main model.
|
|
684
|
-
const useModel = kind === 'full' ? codeModel : liteModel
|
|
702
|
+
const useModel = agent === 'codex' ? codeModel : kind === 'full' ? codeModel : liteModel
|
|
685
703
|
log('running ' + kind + ' cycle…' + (ctx.length ? ' (' + ctx.length + ' event' + (ctx.length === 1 ? '' : 's') + ')' : '') + (useModel ? ' [' + useModel + ']' : ''))
|
|
686
704
|
// Live status: "working" now + a heartbeat so the UI (and its TTL) stays lit
|
|
687
705
|
// through a long cycle; onTool flips it to "typing" when post_message fires.
|
|
@@ -690,13 +708,15 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
690
708
|
const heartbeat = targets.length ? setInterval(() => emitStatus('working'), 9000) : null
|
|
691
709
|
try {
|
|
692
710
|
const result = await runCycle(prompt, useModel)
|
|
693
|
-
//
|
|
694
|
-
//
|
|
695
|
-
// Do not retry a real blocker
|
|
711
|
+
// A zero exit is not proof of work. Assigned-ticket cycles must both use
|
|
712
|
+
// the task MCP and leave repository evidence. A simple `ls` no longer
|
|
713
|
+
// counts as completion. Do not retry a verified real blocker.
|
|
696
714
|
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
|
-
|
|
715
|
+
const incomplete = !result?.didRepoMutation || !result?.didMcpTaskRead || !result?.didMcpTaskUpdate
|
|
716
|
+
if (agent === 'codex' && kind === 'full' && result?.subtype === 'ok' && incomplete && !legitimateNoWork) {
|
|
717
|
+
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)
|
|
718
|
+
log('coding cycle incomplete; recovery requires: ' + missing.join(', '))
|
|
719
|
+
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
720
|
}
|
|
701
721
|
} finally {
|
|
702
722
|
if (heartbeat) clearInterval(heartbeat)
|