openvisio-agent 0.18.9 → 0.18.11

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.18.9",
3
+ "version": "0.18.11",
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": {
@@ -25,6 +25,7 @@ const events = readFileSync(join(root, 'src', 'events.mjs'), 'utf8')
25
25
  const memory = readFileSync(join(root, 'src', 'memory.mjs'), 'utf8')
26
26
  const mcpHttp = readFileSync(join(root, 'src', 'mcp-http.mjs'), 'utf8')
27
27
  const opencodeConfig = readFileSync(join(root, 'src', 'opencode-config.mjs'), 'utf8')
28
+ const codexConfig = readFileSync(join(root, 'src', 'codex-config.mjs'), 'utf8')
28
29
  const prPush = readFileSync(join(root, 'src', 'pr-push.mjs'), 'utf8')
29
30
  const cli = readFileSync(join(root, 'bin', 'cli.mjs'), 'utf8')
30
31
  const websocket = readFileSync(join(root, 'src', 'ws.mjs'), 'utf8')
@@ -59,7 +60,9 @@ const assertions = [
59
60
  ['generic coding pickup messages are absent', !watcher.includes("I've picked this up and will return here with the verified result")],
60
61
  ['human-facing ticket references require slugs, never database ids', watcher.includes('TICKET SLUGS, NEVER DATABASE IDS') && watcher.includes('ticketDisplaySlug(task)') && events.includes('export function ticketDisplaySlug') && !events.includes('I finished ticket #${task.id}')],
61
62
  ['rendered backend replies are checked before every guarded thread post', watcher.includes("callMcpTool('list_message_thread'") && watcher.includes('renderedAgentMessages(live') && watcher.includes('same-content-rendered')],
62
- ['Codex cannot race the watcher with post_message', watcher.includes("disabledMcpTools: ['post_message']") && watcher.includes('disabled_tools = [')],
63
+ ['Codex cannot race the watcher with post_message', watcher.includes("disabledMcpTools: ['post_message']") && codexConfig.includes('disabled_tools = [')],
64
+ ['Codex cannot continue without OpenVisio MCP tools', watcher.includes('buildCodexMcpOverride') && codexConfig.includes('required = true') && codexConfig.includes('startup_timeout_sec = 30')],
65
+ ['Codex discovers deferred OpenVisio actions before claiming they are missing', watcher.includes('Codex may defer MCP actions') && watcher.includes('use tool_search to find that exact action') && watcher.includes('initial-list miss is NOT evidence that the server is disconnected')],
63
66
  ['reply delivery keys survive reconnects', watcher.includes('deliveredReplies: [...deliveredReplies]') && watcher.includes('deliveredReplies.has(deliveryKey)')],
64
67
  ['tickets and guarded replies use the behavior-tested serial queue', watcher.includes('createCycleQueue({') && watcher.includes('queues[laneName].enqueue') && cycleQueue.includes('const pending = new Map()')],
65
68
  ['queued assignments are checked again before model execution', watcher.includes('queued ticket no longer actionable; skipped before model start')],
@@ -89,7 +92,7 @@ const assertions = [
89
92
  ['OpenCode emits structured events for runtime evidence', watcher.includes("'--format', 'json'") && watcher.includes('opencodeEventEvidence(event)')],
90
93
  ['OpenCode API-key MCP disables OAuth probing', opencodeConfig.includes("oauth: false") && opencodeConfig.includes('timeout: 15_000')],
91
94
  ['OpenCode identities use isolated configs outside the code workspace', watcher.includes('opencodeRuntimeLayout({ cfgKey, workdir })') && watcher.includes("'--dir', workspace") && watcher.includes('OPENCODE_CONFIG: opencodeConfigPath') && watcher.includes('OPENCODE_CONFIG_CONTENT: JSON.stringify(opencodeConfig)')],
92
- ['OpenCode backend prompts forbid relay and resource-discovery tools', watcher.includes('BACKEND MCP RULE') && watcher.includes('get_marching_orders, poll_inbox, get_resource, list_mcp_resources, list_mcp_resource_templates')],
95
+ ['OpenCode backend prompts forbid relay and resource-discovery tools', watcher.includes('BACKEND MCP RULE') && ['get_marching_orders', 'poll_inbox', 'get_resource', 'list_mcp_resources', 'list_mcp_resource_templates'].every((name) => watcher.includes(name))],
93
96
  ['OpenCode tool failures retain sanitized diagnostics', events.includes('toolError: toolError.replace') && watcher.includes("opencode tool '") && watcher.includes("split(redactKey).join('[redacted]')")],
94
97
  ['all runtime acknowledgements cannot satisfy coding completion', watcher.includes('missingRuntimeWorkEvidence(result') && watcher.includes('claudeEventEvidence(o, turnToolUses)') && watcher.includes('releaseTaskForRetry(activeTaskRef, prompt)')],
95
98
  ['Codex prose cannot masquerade as repository evidence', watcher.includes('codexEventEvidence(event)') && events.includes("item.type === 'agent_message'") && events.includes("event.type === 'item.completed'")],
@@ -0,0 +1,17 @@
1
+ const tomlString = (value) => JSON.stringify(String(value))
2
+
3
+ // Codex treats remote MCP servers as optional unless `required` is set. An
4
+ // optional server may fail during startup while the model turn continues without
5
+ // its tools, which makes an otherwise connected agent incorrectly claim that it
6
+ // cannot inspect OpenVisio. Fail the subprocess instead so the watcher can retry
7
+ // the event and report a real runtime failure without delivering model guesswork.
8
+ export function buildCodexMcpOverride({ mcpUrl, mcpHeaders, disabledMcpTools = [] } = {}) {
9
+ if (!mcpUrl) return ''
10
+ const headerEntries = Object.entries(mcpHeaders || {})
11
+ .map(([key, value]) => `${tomlString(key)} = ${tomlString(value)}`)
12
+ .join(', ')
13
+ const disabled = Array.isArray(disabledMcpTools) ? disabledMcpTools.filter(Boolean) : []
14
+ const headerConfig = headerEntries ? `, http_headers = { ${headerEntries} }` : ''
15
+ const disabledToolConfig = disabled.length ? `, disabled_tools = [${disabled.map(tomlString).join(', ')}]` : ''
16
+ return `mcp_servers={ openvisio-team = { url = ${tomlString(mcpUrl)}, required = true, startup_timeout_sec = 30${headerConfig}${disabledToolConfig} } }`
17
+ }
package/src/watch.mjs CHANGED
@@ -15,6 +15,7 @@ import { createByoMemoryGraph } from './memory.mjs'
15
15
  import { repositoryHasPrPushAuthorization } from './pr-push.mjs'
16
16
  import { createMcpHttpClient } from './mcp-http.mjs'
17
17
  import { buildOpencodeConfig, opencodeRuntimeLayout } from './opencode-config.mjs'
18
+ import { buildCodexMcpOverride } from './codex-config.mjs'
18
19
  import { createCycleQueue } from './cycle-queue.mjs'
19
20
  import { modelProcessOptions, stopModelProcess } from './process-lifecycle.mjs'
20
21
 
@@ -438,8 +439,6 @@ function createOpencodeRunner({ mcpUrl, mcpHeaders, cfgKey, workdir, canCode, ma
438
439
  function createCodexRunner({ mcpUrl, mcpHeaders, workdir, canCode, maxCycleMs, log, debug, model, onTool, systemPrompt }) {
439
440
  const bin = onPath('codex') || 'codex'
440
441
  const cwd = workdir || OV_DIR
441
- const tomlString = (v) => JSON.stringify(String(v))
442
- const headerEntries = Object.entries(mcpHeaders || {}).map(([k, v]) => `${JSON.stringify(k)} = ${tomlString(v)}`).join(', ')
443
442
  let cancelActive = null
444
443
 
445
444
  function runCycle(prompt, cycleModel, cycleOptions = {}) {
@@ -447,10 +446,7 @@ function createCodexRunner({ mcpUrl, mcpHeaders, workdir, canCode, maxCycleMs, l
447
446
  const m = cycleModel || model
448
447
  const full = systemPrompt ? systemPrompt + '\n\n' + prompt : prompt
449
448
  const disabledMcpTools = Array.isArray(cycleOptions.disabledMcpTools) ? cycleOptions.disabledMcpTools.filter(Boolean) : []
450
- const disabledToolConfig = disabledMcpTools.length ? `, disabled_tools = [${disabledMcpTools.map(tomlString).join(', ')}]` : ''
451
- const mcpOverride = mcpUrl
452
- ? `mcp_servers={ openvisio-team = { url = ${tomlString(mcpUrl)}${headerEntries ? `, http_headers = { ${headerEntries} }` : ''}${disabledToolConfig} } }`
453
- : ''
449
+ const mcpOverride = buildCodexMcpOverride({ mcpUrl, mcpHeaders, disabledMcpTools })
454
450
  const args = ['exec', '--ignore-user-config', '--skip-git-repo-check', '--ephemeral', '--json', '--color', 'never',
455
451
  ...(canCode ? ['--approve-for-me'] : ['--sandbox', 'read-only']),
456
452
  ...(m ? ['--model', m] : []),
@@ -769,7 +765,7 @@ function loopBackendWs({ backend, wsUrl, apiKey, identifier, slug, claude, agent
769
765
  const canCode = !!workdir
770
766
  // The Mastra bridge authenticates per-CALL: every openvisio-team tool needs
771
767
  // agent_identifier + agent_api_key as arguments. Hand them over up front.
772
- const credNote = `AUTH: the openvisio-team tools REQUIRE two arguments on EVERY call — agent_identifier: "${identifier}" and agent_api_key: "${apiKey}". Include BOTH on every openvisio-team tool call. Use ONLY names shown in the current tool list. On backend MCP, discover and update work with list_agents, list_projects, list_tasks, list_task_types, get_ticket, update_ticket, list_channels, list_message_thread, and list_activity as applicable. Ticket comments are optional: never invent or call comment_ticket unless that exact tool appears. get_marching_orders, poll_inbox, get_resource, list_mcp_resources, and list_mcp_resource_templates are NOT team-action tools here; never call them. Tools may be namespaced — call whichever names actually appear. The credentials are given here; do NOT hunt for them. Bash/git/gh ARE for code work; this rule only forbids searching for keys.`
768
+ const credNote = `AUTH: the openvisio-team tools REQUIRE two arguments on EVERY call — agent_identifier: "${identifier}" and agent_api_key: "${apiKey}". Include BOTH on every openvisio-team tool call. Codex may defer MCP actions instead of placing them in the initial tool list. If a native tool_search is available and an expected openvisio-team action is not initially visible, use tool_search to find that exact action, then call it; an initial-list miss is NOT evidence that the server is disconnected. Do not ask a teammate to reconnect or provide a slug until tool_search and the applicable live lookup have actually failed. On backend MCP, discover and update work with list_agents, list_projects, list_tasks, list_task_types, get_ticket, update_ticket, list_channels, list_message_thread, and list_activity as applicable. Ticket comments are optional: never invent or call comment_ticket unless that exact tool appears or tool_search finds it. get_marching_orders, poll_inbox, get_resource, list_mcp_resources, and list_mcp_resource_templates are NOT team-action tools here; never call them. Tools may be namespaced — call whichever names actually appear. The credentials are given here; do NOT hunt for them. Bash/git/gh ARE for code work; this rule only forbids searching for keys.`
773
769
  // The STATIC charter + creds are the session system prompt (cached, billed once),
774
770
  // NOT re-sent in every cycle's user message — the big token saving.
775
771
  const systemPrompt = (canCode ? CODE_CHARTER : CHAT_CHARTER) + '\n\n' + credNote
@@ -786,7 +782,7 @@ function loopBackendWs({ backend, wsUrl, apiKey, identifier, slug, claude, agent
786
782
  const codexPushGuide = agent === 'codex' && canCode
787
783
  ? '\n\nCODEX PR DELIVERY: when the repository exists in the local workspace, use that clone for branch creation, edits, tests, and commits; do not inspect or mutate it through linked-codebase MCP tools. To publish the local agent/* branch, run `openvisio-agent push-pr-branch` from the repository, then open the PR with `gh pr create`. The helper can only push HEAD to the matching agent/* branch on the exact authorized origin. If it reports OPENVISIO_PR_PUSH_AUTH_REQUIRED, do not retry or route around it. Report the one-time command `openvisio-agent authorize-pr-push` as the blocker. Use list_codebases/create_codebase_branch/create_codebase_commit/create_pull_request only as a fallback when the repository cannot be obtained locally.'
788
784
  : ''
789
- const backendToolRule = 'BACKEND MCP RULE: the event and watcher already provide the work source. Never call get_marching_orders, poll_inbox, get_resource, list_mcp_resources, list_mcp_resource_templates, or other relay/resource-discovery tools; they are not backend team actions. Use only names present in the current openvisio-team tool list. For discovery use list_agents, list_projects, list_tasks, list_task_types, list_activity, get_ticket, and list_channels as applicable. Never call comment_ticket unless that exact optional tool is present.'
785
+ const backendToolRule = 'BACKEND MCP RULE: the event and watcher already provide the work source. Never call get_marching_orders, poll_inbox, get_resource, list_mcp_resources, or list_mcp_resource_templates; they are not backend team actions. Codex can defer MCP actions: native tool_search is allowed and must be used to find an expected openvisio-team action that is absent from the initial list. An initial-list miss does not mean the MCP server is unavailable. For live discovery use list_agents, list_projects, list_tasks, list_task_types, list_activity, get_ticket, and list_channels as applicable. Never call comment_ticket unless that exact optional tool is present or tool_search finds it.'
790
786
  const fullPrompt = (canCode ? CODE_FULL + codexPushGuide : 'Handle the supplied verified backend ticket with the available OpenVisio tools. Update or comment on the ticket as requested, do not claim repository work in chat-only mode, and stop after the verified action.') + '\n\n' + backendToolRule
791
787
  const fastPrompt = (canCode ? CODE_FAST : CYCLE_FAST) + '\n\n' + backendToolRule
792
788
  const coordinatePrompt = COORDINATE + '\n\n' + backendToolRule