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 +1 -1
- package/scripts/certify.mjs +3 -2
- package/src/events.mjs +2 -3
- package/src/watch.mjs +10 -12
package/package.json
CHANGED
package/scripts/certify.mjs
CHANGED
|
@@ -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(
|
|
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(
|
|
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,
|
|
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}
|
|
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:
|
|
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.
|
|
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,
|
|
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
|
|
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:
|
|
820
|
-
const ticketNotice = `
|
|
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 = `
|
|
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 = `
|
|
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 = `
|
|
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
|