openvisio-agent 0.19.3 → 0.19.4

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/bin/cli.mjs CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openvisio-agent",
3
- "version": "0.19.3",
3
+ "version": "0.19.4",
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": {
@@ -127,6 +127,7 @@ const assertions = [
127
127
  ['Codex recognizes helper authorization as a blocker', events.includes('OPENVISIO_PR_PUSH_AUTH_REQUIRED') && watcher.includes("block?.kind === 'pr-push-authorization-required'")],
128
128
  ['policy blocker is surfaced to the user', watcher.includes('reportPolicyBlock(prompt, activeTaskRef, result.policyBlock, delivery)') && watcher.includes("Action required: I'm blocked")],
129
129
  ['blocker routing carries explicit task identity', watcher.includes('const activeTaskRef = taskRef') && watcher.includes('taskRef: activeTaskRef')],
130
+ ['reply discovery failures stay scoped while failed mutations fail closed', watcher.includes('blockingReplyMcpErrors(result?.mcpErrors)') && watcher.includes('preserving the scoped model reply') && events.includes('export function blockingReplyMcpErrors')],
130
131
  ['all runtime blockers have a delivery path', watcher.includes('publishBlocker') && watcher.includes('WORK_CYCLE_BLOCKED') && watcher.includes('COORDINATION_CYCLE_BLOCKED')],
131
132
  ['ticket blocker cannot self-authorize', watcher.includes("ticketNotice = `I'm paused") && watcher.includes('publishBlocker({ prompt, taskRef, delivery, notice, ticketNotice, pause: true })') && !watcher.includes('test(approvalText)')],
132
133
  ['agent messages use first-person voice', watcher.includes('FIRST-PERSON VOICE') && watcher.includes("I'm blocked") && !watcher.includes('Alex is blocked') && !watcher.includes('Alex is paused')],
package/src/events.mjs CHANGED
@@ -495,6 +495,15 @@ export function conversationNeedsCode(value) {
495
495
  return action && target
496
496
  }
497
497
 
498
+ // Read-only discovery failures in a reply cycle are not failed mutations and
499
+ // must not be inflated into a generic user-facing blocker. The model can give a
500
+ // precise, scoped answer (or say which live fact it could not read). Failed team
501
+ // mutations remain fail-closed so prose can never masquerade as a completed act.
502
+ export function blockingReplyMcpErrors(errors) {
503
+ const mutation = /^(?:update_ticket|post_message|comment_ticket|react_message|create_codebase_branch|create_codebase_commit|write_codebase_file|create_pull_request)$/
504
+ return [...new Set((Array.isArray(errors) ? errors : []).map((name) => String(name || '').replace(/^.*(?:__|[.:/])/, '').replace(/[-.]/g, '_').toLowerCase()).filter((name) => mutation.test(name)))]
505
+ }
506
+
498
507
  // Coordination tickets are imperative board/chat actions, not implementation
499
508
  // work that merely mentions words such as "message", "status", or "label" in
500
509
  // its feature title. Requiring the coordination verb at the start prevents
package/src/watch.mjs CHANGED
@@ -11,7 +11,7 @@ import { join, dirname } from 'node:path'
11
11
  import { fileURLToPath } from 'node:url'
12
12
  import { OV_DIR, DEFAULT_WORKSPACE, readConfig, writeJson, configPath, onPath, fail, ok, info, slugify, stripSlash, chmodSafe } from './lib.mjs'
13
13
  import { connectAgentWs, assertWebSocket } from './ws.mjs'
14
- import { agentAddedByName, buildTaskCompletionReport, claudeEventEvidence, classifyConversationTarget, codexEventEvidence, codexPolicyBlock, combineRuntimeWorkEvidence, conversationNeedsCode, failedTaskRevisionIsCurrent, mentionDedupeKeys, missingRuntimeWorkEvidence, normalizeRenderedMessageText, opencodeEventEvidence, renderedAgentMessages, shouldSuppressCodexDiagnostic, taskAgentId, taskFromEvent, taskIsAwaitingReview, taskIsCompleted, taskIsCoordinationOnly, taskRevision, ticketDisplaySlug } from './events.mjs'
14
+ import { agentAddedByName, blockingReplyMcpErrors, buildTaskCompletionReport, claudeEventEvidence, classifyConversationTarget, codexEventEvidence, codexPolicyBlock, combineRuntimeWorkEvidence, conversationNeedsCode, failedTaskRevisionIsCurrent, mentionDedupeKeys, missingRuntimeWorkEvidence, normalizeRenderedMessageText, opencodeEventEvidence, renderedAgentMessages, shouldSuppressCodexDiagnostic, taskAgentId, taskFromEvent, taskIsAwaitingReview, taskIsCompleted, taskIsCoordinationOnly, taskRevision, ticketDisplaySlug } from './events.mjs'
15
15
  import { createMastraMemory } from './memory.mjs'
16
16
  import { repositoryHasPrPushAuthorization } from './pr-push.mjs'
17
17
  import { createMcpHttpClient } from './mcp-http.mjs'
@@ -834,7 +834,7 @@ function loopBackendWs({ backend, wsUrl, apiKey, identifier, slug, claude, agent
834
834
  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
835
835
  const fastPrompt = (canCode ? CODE_FAST : CYCLE_FAST) + '\n\n' + backendToolRule
836
836
  const coordinatePrompt = COORDINATE + '\n\n' + backendToolRule
837
- const guardedReplyPrompt = 'WATCHER-DELIVERED REPLY: the watcher has already verified that the current source message is addressed to you. Do not call post_message, relay inbox tools, or MCP resource APIs. OpenVisio team-state tools are allowed: when the answer depends on live projects, assignments, tickets, or status, call list_agents/list_projects/list_tasks/get_ticket yourself before answering and never ask the teammate for a slug that those tools can resolve. Return only one natural, context-specific reply of 1-3 sentences. Do not echo the request, announce a plan, or add a generic acknowledgement. If the supplied context says the work was completed, lead with the verified result; if it is blocked, name only the real blocker and next action.' + '\n\n' + backendToolRule
837
+ const guardedReplyPrompt = `WATCHER-DELIVERED REPLY: the watcher has already verified that the current source message is addressed to you. Your current OpenVisio agent identifier is "${identifier}"; do not call list_agents merely to rediscover yourself. Do not call post_message, relay inbox tools, or MCP resource APIs. OpenVisio team-state tools are allowed: when the answer depends on live projects, assignments, tickets, or status, use list_projects/list_tasks/get_ticket as needed and never ask the teammate for a slug that those tools can resolve. A failed read-only discovery call is not a completed action and must not be described as an intervention-level blocker; continue with available live data or state the narrow fact you could not verify. Return only one natural, context-specific reply of 1-3 sentences. Do not echo the request, announce a plan, or add a generic acknowledgement. If the supplied context says the work was completed, lead with the verified result; if it is blocked, name only the real blocker and next action.` + '\n\n' + backendToolRule
838
838
  // Live model state — changeable at runtime by the in-chat `/model` command.
839
839
  // codeModel drives full/sweep cycles; chatModel (if set) the lighter fast/intro
840
840
  // ones, so routine chatter can run cheaper than real code work.
@@ -1530,13 +1530,17 @@ function loopBackendWs({ backend, wsUrl, apiKey, identifier, slug, claude, agent
1530
1530
  finally { if (activeTaskRef) await finalizeFailedTaskPause(activeTaskRef) }
1531
1531
  return
1532
1532
  }
1533
- if (kind !== 'full' && result?.mcpErrors?.length) {
1534
- const notice = `I'm blocked by failed OpenVisio actions: ${result.mcpErrors.join(', ')}. I'm not claiming success; this needs a retry or intervention.`
1533
+ const replyMutationErrors = kind !== 'full' ? blockingReplyMcpErrors(result?.mcpErrors) : []
1534
+ if (replyMutationErrors.length) {
1535
+ const notice = `I couldn't complete the requested OpenVisio action: ${replyMutationErrors.join(', ')}. I haven't claimed that change succeeded.`
1535
1536
  log('COORDINATION_CYCLE_BLOCKED failed MCP calls; publishing blocker')
1536
1537
  try { await publishBlocker({ prompt, taskRef: activeTaskRef, delivery, notice }) }
1537
1538
  catch (e) { log('failed to publish coordination blocker: ' + (e?.message || e)) }
1538
1539
  return
1539
1540
  }
1541
+ if (kind !== 'full' && result?.mcpErrors?.length) {
1542
+ log('reply cycle had read-only MCP failures (' + result.mcpErrors.join(', ') + '); preserving the scoped model reply')
1543
+ }
1540
1544
  // Model prose never proves success or a blocker. Full cycles must produce
1541
1545
  // runtime-observed ticket reads, repository evidence, and ticket updates.
1542
1546
  const ticketCycle = !!activeTaskRef || /ticket\s+#?\d+.*?project\s+\d+/i.test(prompt)