openvisio-agent 0.17.5 → 0.17.6

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.17.5",
3
+ "version": "0.17.6",
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": {
@@ -48,10 +48,11 @@ const assertions = [
48
48
  ['Codex policy rejection is captured from stderr', watcher.includes("stdio: ['ignore', 'pipe', 'pipe']") && watcher.includes('inspectDiagnostic(d)')],
49
49
  ['policy rejection cannot be logged as successful', watcher.includes("subtype = policyBlock ? 'blocked'")],
50
50
  ['policy-blocked tickets are persisted and paused', watcher.includes('blockedTasks: [...blockedTasks]') && watcher.includes('WORK_CYCLE_BLOCKED')],
51
- ['policy blocker is surfaced to the user', watcher.includes('reportPolicyBlock(prompt, activeTaskRef, result.policyBlock)') && watcher.includes('Action required: Alex is blocked')],
51
+ ['policy blocker is surfaced to the user', watcher.includes('reportPolicyBlock(prompt, activeTaskRef, result.policyBlock)') && watcher.includes("Action required: I'm blocked")],
52
52
  ['blocker routing carries explicit task identity', watcher.includes('taskRefs: []') && watcher.includes('activeTaskRef') && watcher.includes('taskRef: activeTaskRef')],
53
53
  ['all runtime blockers have a delivery path', watcher.includes('publishBlocker') && watcher.includes('WORK_CYCLE_BLOCKED') && watcher.includes('COORDINATION_CYCLE_BLOCKED')],
54
- ['ticket blocker cannot self-authorize', watcher.includes('ticketNotice = `Alex is paused') && watcher.includes('publishBlocker({ prompt, taskRef, notice, ticketNotice, pause: true })')],
54
+ ['ticket blocker cannot self-authorize', watcher.includes("ticketNotice = `I'm paused") && watcher.includes('publishBlocker({ prompt, taskRef, notice, ticketNotice, pause: true })')],
55
+ ['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')],
55
56
  ['normative certification gates are documented', spec.includes('## Mandatory certification gates')],
56
57
  ]
57
58
  for (const [label, ok] of assertions) {
package/src/events.mjs CHANGED
@@ -46,7 +46,7 @@ export function taskIsAwaitingReview(task, reviewTypeIds = new Set()) {
46
46
  return /\b(?:review|test|testing|qa|quality\s+assurance|verification|approval)\b/i.test(state)
47
47
  }
48
48
 
49
- export function buildTaskCompletionReport(task, { projectId, agentName, fallbackText = '' } = {}) {
49
+ export function buildTaskCompletionReport(task, { projectId, fallbackText = '' } = {}) {
50
50
  if (!task || typeof task !== 'object' || task.id == null) return null
51
51
  if (!taskIsCompleted(task) && !taskIsAwaitingReview(task)) return null
52
52
 
@@ -59,9 +59,8 @@ export function buildTaskCompletionReport(task, { projectId, agentName, fallback
59
59
  const title = String(task.title || 'Untitled task').replace(/\s+/g, ' ').trim().slice(0, 180)
60
60
  const status = String(task.type?.name || task.task_type?.name || task.status || task.state || 'review').replace(/\s+/g, ' ').trim()
61
61
  const verification = /\bVerification:\s*([^\n]{1,180})/i.exec(evidence)?.[1]?.replace(/\s+/g, ' ').trim().replace(/[.]+$/, '') || ''
62
- const who = String(agentName || 'The agent').trim()
63
62
  const mention = requester ? `@${requester} ` : ''
64
- const content = `${mention}${who} finished ticket #${task.id} “${title}” and moved it to ${status}. PR: ${prUrl}.${verification ? ` Verification: ${verification}.` : ''}`
63
+ const content = `${mention}I finished ticket #${task.id} “${title}” and moved it to ${status}. PR: ${prUrl}.${verification ? ` Verification: ${verification}.` : ''}`
65
64
  const revision = task.updated_at ?? task.updatedAt ?? prUrl
66
65
  return { key: `${projectId ?? task.project_id ?? task.projectId ?? '?'}:${task.id}:${revision}`, content, prUrl }
67
66
  }
package/src/watch.mjs CHANGED
@@ -27,6 +27,7 @@ import { agentStateRequest, buildTaskCompletionReport, codexPolicyBlock, request
27
27
  // then walking it back.
28
28
  const REPLY_DISCIPLINE = [
29
29
  'REPLY DISCIPLINE — read the recent messages FIRST, then decide whether to speak at all:',
30
+ ' • FIRST-PERSON VOICE. Speak as yourself: use “I”, “I\'m”, and “my”. Never refer to yourself by your agent name or in the third person, and never restate your own name in introductions, acknowledgements, progress, blockers, or results. The app already shows who sent the message. Sound like a warm, accountable teammate, not a status bot.',
30
31
  ' • IS IT FOR YOU? Act ONLY on messages addressed to YOU — an @mention of your exact name, a direct question to you, or a reply to something YOU said or did. If a DIFFERENT agent or person was @mentioned or asked to do something, STAY OUT: do not answer for them and do not pick up their task. When it is not yours, posting nothing is the correct move.',
31
32
  ' • NO DUPLICATES. Before you post, scan the recent thread/channel for what YOU already said. If you already replied to or acknowledged this exact request, do NOT post again. One acknowledgement per task; one answer per question. While a task is in progress, post again ONLY when you have something genuinely NEW (a result, a link, a real blocker) — never re-post "on it".',
32
33
  ' • BE SURE BEFORE YOU SPEAK. Do not claim something is possible, done, or broken until you have actually verified it — call the tool, read the code, check the real state. Never assert then contradict yourself. If you are unsure, verify FIRST, then give ONE clear, final answer instead of thinking out loud across several messages.',
@@ -107,7 +108,7 @@ const CODE_FAST = [
107
108
  // missed — TASKS especially.
108
109
  const INTRO = [
109
110
  'You have just JOINED this OpenVisio workspace (your first connection). Workspace etiquette: introduce yourself so the team knows you are here and reachable.',
110
- 'Find the most general channel — call poll_inbox (or list channels) and pick the "general"/main one — then post_message there ONCE: give your name, say you are an AI teammate who picks up tasks assigned to you and answers @mentions, and invite people to mention you. 1-2 sentences, warm and professional.',
111
+ 'Find the most general channel — call poll_inbox (or list channels) and pick the "general"/main one — then post_message there ONCE: say in first person that you are here, pick up tasks assigned to you, and answer @mentions. Do not state your name; the app already displays it. Keep it to 1-2 warm, professional sentences.',
111
112
  'Post it EXACTLY ONCE, then stop. Do NOT do any other work this cycle.',
112
113
  ].join('\n')
113
114
  const SWEEP = [
@@ -651,7 +652,6 @@ function loopBackendWs({ backend, wsUrl, apiKey, identifier, slug, claude, agent
651
652
  let lastTaskTriggeredAt = 0
652
653
  let lastInboxSignature = ''
653
654
  let selfAgentId = null
654
- let selfAgentName = slug
655
655
  let mcpSessionId = ''
656
656
  let mcpRpcId = 0
657
657
 
@@ -676,7 +676,7 @@ function loopBackendWs({ backend, wsUrl, apiKey, identifier, slug, claude, agent
676
676
  }
677
677
  const ensureMcpSession = async () => {
678
678
  if (mcpSessionId) return
679
- const res = await mcpPost({ jsonrpc: '2.0', id: ++mcpRpcId, method: 'initialize', params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'openvisio-agent', version: '0.17.5' } } }, false)
679
+ const res = await mcpPost({ jsonrpc: '2.0', id: ++mcpRpcId, method: 'initialize', params: { protocolVersion: '2025-03-26', capabilities: {}, clientInfo: { name: 'openvisio-agent', version: '0.17.6' } } }, false)
680
680
  if (!res.ok) throw new Error('MCP initialize HTTP ' + res.status)
681
681
  await mcpPayload(res)
682
682
  mcpSessionId = res.headers.get('mcp-session-id') || ''
@@ -734,7 +734,7 @@ function loopBackendWs({ backend, wsUrl, apiKey, identifier, slug, claude, agent
734
734
  const taskKey = `${projectId}:${ticketId}`
735
735
  const current = toolData(await callMcpTool('get_ticket', { project_id: projectId, ticket_id: ticketId }))
736
736
  const ticket = current.ticket ?? current.task ?? current
737
- const report = buildTaskCompletionReport(ticket, { projectId, agentName: selfAgentName, fallbackText: result.outputText })
737
+ const report = buildTaskCompletionReport(ticket, { projectId, fallbackText: result.outputText })
738
738
  if (!report) return false
739
739
  if (reportedCompletions.has(report.key)) {
740
740
  pendingCompletionReports.delete(taskKey)
@@ -814,10 +814,10 @@ function loopBackendWs({ backend, wsUrl, apiKey, identifier, slug, claude, agent
814
814
  const command = block?.command || 'the requested external repository action'
815
815
  const payload = [block?.commit && `commit ${block.commit}`, block?.branch && `branch ${block.branch}`, block?.remote && `remote ${block.remote}`].filter(Boolean).join(', ')
816
816
  const approval = block?.commit && block?.branch
817
- ? `Reply with: “I explicitly authorize Alex to push commit ${block.commit} to the exact repository URL for ${block.remote || 'the configured remote'}, on branch ${block.branch}.”`
817
+ ? `Reply with: “I explicitly authorize you to push commit ${block.commit} to the exact repository URL for ${block.remote || 'the configured remote'}, on branch ${block.branch}.”`
818
818
  : 'Explicitly authorize the exact repository URL, commit, and branch in your reply.'
819
- const notice = `Action required: Alex is blocked at \`${command}\`${payload ? ` (${payload})` : ''}. Codex requires confirmation before exporting private repository code. ${approval} The ticket is paused until that approval is recorded.`
820
- const ticketNotice = `Alex is paused at \`${command}\`${payload ? ` (${payload})` : ''}. Repository push confirmation is required in the project channel; no automatic retry will occur.`
819
+ const notice = `Action required: I'm blocked at \`${command}\`${payload ? ` (${payload})` : ''}. Codex requires confirmation before exporting private repository code. ${approval} I've paused the ticket until that approval is recorded.`
820
+ const ticketNotice = `I'm paused at \`${command}\`${payload ? ` (${payload})` : ''}. Repository push confirmation is required in the project channel; I won't retry automatically.`
821
821
  return publishBlocker({ prompt, taskRef, notice, ticketNotice, pause: true })
822
822
  }
823
823
 
@@ -832,7 +832,6 @@ function loopBackendWs({ backend, wsUrl, apiKey, identifier, slug, claude, agent
832
832
  const self = agents.find((a) => String(a.identifier || a.slug || '') === identifier)
833
833
  if (!self?.id) throw new Error('list_agents did not return this BYO agent')
834
834
  selfAgentId = Number(self.id)
835
- selfAgentName = String(self.name || selfAgentName)
836
835
  const projectsData = toolData(await callMcpTool('list_projects'))
837
836
  const projects = Array.isArray(projectsData.projects) ? projectsData.projects : []
838
837
  const assigned = []
@@ -953,14 +952,14 @@ function loopBackendWs({ backend, wsUrl, apiKey, identifier, slug, claude, agent
953
952
  return
954
953
  }
955
954
  if (kind === 'full' && ['timeout', 'spawn-failed', 'error'].includes(result?.subtype)) {
956
- const notice = `Alex is blocked: the coding cycle ended with ${result.subtype}. No completion is being claimed and the ticket remains open for retry.`
955
+ const notice = `I'm blocked because the coding cycle ended with ${result.subtype}. I'm not claiming completion, and I've left the ticket open for retry.`
957
956
  log('WORK_CYCLE_BLOCKED ' + result.subtype + '; publishing blocker')
958
957
  try { await publishBlocker({ prompt, taskRef: activeTaskRef, notice }) }
959
958
  catch (e) { log('failed to publish cycle blocker: ' + (e?.message || e)) }
960
959
  return
961
960
  }
962
961
  if (kind !== 'full' && result?.mcpErrors?.length) {
963
- const notice = `Alex is blocked by failed OpenVisio actions: ${result.mcpErrors.join(', ')}. No success is being claimed; the item needs retry or intervention.`
962
+ const notice = `I'm blocked by failed OpenVisio actions: ${result.mcpErrors.join(', ')}. I'm not claiming success; this needs a retry or intervention.`
964
963
  log('COORDINATION_CYCLE_BLOCKED failed MCP calls; publishing blocker')
965
964
  try { await publishBlocker({ prompt, taskRef: activeTaskRef, notice }) }
966
965
  catch (e) { log('failed to publish coordination blocker: ' + (e?.message || e)) }
@@ -981,7 +980,7 @@ function loopBackendWs({ backend, wsUrl, apiKey, identifier, slug, claude, agent
981
980
  try { await reportPolicyBlock(prompt, activeTaskRef, recovery.policyBlock) }
982
981
  catch (e) { log('failed to publish recovery policy blocker: ' + (e?.message || e)) }
983
982
  } else {
984
- const notice = `Alex is blocked after one recovery attempt. Missing required evidence: ${missing.join('; ')}. The ticket remains open and no completion is being claimed.`
983
+ const notice = `I'm blocked after one recovery attempt. Missing required evidence: ${missing.join('; ')}. I've left the ticket open and I'm not claiming completion.`
985
984
  try { await publishBlocker({ prompt, taskRef: activeTaskRef, notice }) }
986
985
  catch (e) { log('failed to publish recovery blocker: ' + (e?.message || e)) }
987
986
  }
@@ -1065,7 +1064,6 @@ function loopBackendWs({ backend, wsUrl, apiKey, identifier, slug, claude, agent
1065
1064
  const agentsData = toolData(await callMcpTool('list_agents'))
1066
1065
  const self = (Array.isArray(agentsData.agents) ? agentsData.agents : []).find((a) => String(a.identifier || a.slug || '') === identifier)
1067
1066
  selfAgentId = self?.id != null ? Number(self.id) : null
1068
- selfAgentName = String(self?.name || selfAgentName)
1069
1067
  }
1070
1068
  const ticketData = toolData(await callMcpTool('get_ticket', { project_id: projectId, ticket_id: ticketId }))
1071
1069
  const ticket = ticketData.ticket ?? ticketData.task ?? ticketData