openvisio-agent 0.3.4 → 0.3.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.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "Connect your coding agent (Claude Code) to an OpenVisio team — MCP tools + optional autonomy — in one command. No shell scripts.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/watch.mjs CHANGED
@@ -11,10 +11,13 @@ import { join, dirname } from 'node:path'
11
11
  import { OV_DIR, readConfig, onPath, fail, ok, info, slugify, stripSlash, chmodSafe } from './lib.mjs'
12
12
  import { connectAgentWs, assertWebSocket } from './ws.mjs'
13
13
 
14
+ // Behaviour prompts. The openvisio-team MCP bridge requires the agent's
15
+ // credentials as ARGUMENTS on every tool call — those are injected at runtime by
16
+ // loopBackendWs (see credNote), NOT baked in here, so nothing needs hunting.
14
17
  const CYCLE = 'Run one OpenVisio autonomy cycle.'
15
- const CYCLE_FAST = 'New chat activity in OpenVisio. Call poll_inbox once, then handle items in .mentions and .followUps ONLY (ignore .tasks/.claimable). Post AT MOST ONE reply per channel: if the same person sent several nudges (e.g. repeated @mentions or "status?"), answer them together in ONE post — never reply once per message. CRITICAL HONESTY RULE: your only tools are the openvisio-team chat/ticket tools. You CANNOT write code, read or clone a repo, or add entries to the API-testing/docs/sheets/flowchart tools you have no tool for any of that. So NEVER say "On it" or "I will do it" for such work. If asked to do something you have no tool for, say plainly in one short message that you cannot do it yourself and what a human would need to do (or offer to file/track a ticket). Do not invent repo names or progress. Reply in 1-3 sentences, no summary; or react_message to dismiss. Then stop.'
16
- const CODE_FULL = 'Run one OpenVisio autonomy cycle. Call get_marching_orders and poll_inbox. You have file + Bash tools and a git repo at your working directory. For assigned tickets or mentions asking for real work: FIRST run "git checkout -B agent/work" to work on a branch, make the actual changes with Read/Edit/Write, run tests if present, then "git add -A && git commit -m ...". NEVER git push, merge, or touch main. Then comment_ticket with a short summary + the branch name (and move it to Done only if auto-move is on), and post a brief channel reply. If you truly cannot (missing repo/specs), say so plainly in one message — never fabricate progress or repo names.'
17
- const CODE_FAST = 'New chat activity in OpenVisio. Call poll_inbox, handle .mentions/.followUps ONLY, AT MOST ONE reply per channel. If a message asks for real work you can do in the git repo at your working directory, DO IT: "git checkout -B agent/work", make the changes with your file/Bash tools, commit locally (NEVER push or merge), then reply briefly with what you did + the branch name. If it needs a repo/spec you do not have, say so plainly — never fabricate progress or invent repo names. 1-3 sentences, no summary. Then stop.'
18
+ const CYCLE_FAST = 'New chat activity in OpenVisio. If a specific mention is given above, post that reply FIRST with mcp__openvisio-team__post_message. Then call poll_inbox and handle any .followUps (thread replies you are part of, even without an @mention) — ignore .tasks/.claimable. Post AT MOST ONE reply per channel: if the same person sent several nudges, answer them together in ONE post. HONESTY: your only tools are the openvisio-team chat/ticket tools you cannot write code or touch other tools; if asked for work you have no tool for, say so plainly in one short message (or offer to file a ticket). Never invent progress. Reply in 1-3 sentences, no summary. Then stop.'
19
+ const CODE_FULL = 'Run one OpenVisio autonomy cycle. Call get_marching_orders and poll_inbox. You have file + Bash tools and a git repo at your working directory FOR CODE WORK ONLY. For assigned tickets or mentions asking for real code work: FIRST "git checkout -B agent/work", make the changes with Read/Edit/Write, run tests if present, then "git add -A && git commit -m ...". NEVER git push, merge, or touch main. Then comment_ticket with a short summary + the branch name, and post a brief channel reply. Do NOT use Bash to find credentials they are given to you. If you truly cannot (missing repo/specs), say so in one message — never fabricate.'
20
+ const CODE_FAST = 'New chat activity in OpenVisio. If a specific mention is given above, post that reply FIRST with mcp__openvisio-team__post_message — do this before any Bash. Then poll_inbox and handle .followUps (thread replies you are part of, even without an @mention), AT MOST ONE reply per channel. Only if a message asks for real CODE work in your git repo, do it on a branch (checkout -B agent/work, edit, commit locally never push/merge) and reply with the branch name. Bash is for git/tests ONLY, never for credentials. 1-3 sentences, no summary. Then stop.'
18
21
 
19
22
  const CODE_TOOLS = ['Read', 'Grep', 'Glob', 'Edit', 'Write', 'MultiEdit', 'TodoWrite', 'Bash', 'mcp__openvisio-team__*']
20
23
  const DENY_TOOLS = ['Bash(git push:*)', 'Bash(git reset --hard:*)', 'Bash(git clean:*)', 'Bash(rm:*)', 'Bash(sudo:*)', 'Bash(chmod:*)', 'Bash(curl:*)', 'Bash(wget:*)', 'Bash(npm publish:*)', 'Bash(pnpm publish:*)', 'Bash(gh pr merge:*)', 'Bash(gh repo:*)']
@@ -154,19 +157,26 @@ function loopBackendWs({ wsUrl, apiKey, identifier, claude, mcpConfig, workdir,
154
157
 
155
158
  let busy = false
156
159
  let queued = null // 'full' | 'fast' — a cycle requested while one was running
160
+ let handle = null
161
+ let lastActivityAt = Date.now()
157
162
  // Context lines from the events themselves (the WS payload already carries the
158
163
  // channel + message / task), so the agent acts on THEM directly instead of
159
164
  // hoping poll_inbox re-surfaces the same item. Accumulated across coalesced
160
165
  // events and drained into the next cycle's prompt.
161
166
  const pending = []
162
167
 
168
+ // The Mastra bridge authenticates per-CALL, not per-connection: every
169
+ // openvisio-team tool needs agent_identifier + agent_api_key as arguments.
170
+ // Hand them to the model up front so it never shells around hunting for them.
171
+ const credNote = `AUTH: the openvisio-team (mcp__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 (post_message, poll_inbox, react_message, comment_ticket, …). They are given to you right here: do NOT search for them, do NOT run Bash/shell/grep/cat/find, do NOT read memory — just call the tools with these exact values.`
172
+
163
173
  async function drain(kind, context) {
164
174
  if (context) pending.push(context)
165
175
  if (busy) { queued = (queued === 'full' || kind === 'full') ? 'full' : 'fast'; log('busy — queued a ' + kind + ' follow-up cycle'); return }
166
176
  busy = true
167
177
  const ctx = pending.splice(0) // take everything accumulated so far
168
178
  const base = kind === 'full' ? fullPrompt : fastPrompt
169
- const prompt = ctx.length ? ctx.join('\n') + '\n\n' + base : base
179
+ const prompt = credNote + '\n\n' + (ctx.length ? ctx.join('\n') + '\n\n' : '') + base
170
180
  log('running ' + kind + ' cycle…' + (ctx.length ? ' (' + ctx.length + ' event' + (ctx.length === 1 ? '' : 's') + ')' : ''))
171
181
  try {
172
182
  await runCycle(prompt)
@@ -186,6 +196,7 @@ function loopBackendWs({ wsUrl, apiKey, identifier, claude, mcpConfig, workdir,
186
196
 
187
197
  function onEvent(k, d) {
188
198
  const raw = d && typeof d === 'object' ? d : {}
199
+ lastActivityAt = Date.now()
189
200
  if (k === 'task:assigned') {
190
201
  const t = raw.task || {}
191
202
  log('task:assigned ' + (t.id != null ? '#' + t.id + ' “' + (t.title || '') + '”' : ''))
@@ -198,7 +209,7 @@ function loopBackendWs({ wsUrl, apiKey, identifier, claude, mcpConfig, workdir,
198
209
  const who = senderName(msg)
199
210
  log('agent:mention in channel ' + (cid != null ? cid : '?'))
200
211
  const ctx = cid != null
201
- ? `You were @mentioned in OpenVisio channel ${cid}${who ? ` by ${who}` : ''}: "${text}". Reply DIRECTLY in that channel now with post_message (channel_id ${cid}), 1-3 sentences addressing it. Do NOT depend on poll_inbox to find it you already have it here. You MAY still call poll_inbox to catch OTHER pending items, but you MUST answer this one.`
212
+ ? `You were @mentioned in OpenVisio channel ${cid}${who ? ` by ${who}` : ''}: "${text}". Your FIRST action must be mcp__openvisio-team__post_message with channel_id ${cid}, a 1-3 sentence reply, and the agent_identifier + agent_api_key from the AUTH line above. You already have the message here do NOT poll_inbox to find it. After posting, you MAY poll_inbox once for other .followUps.`
202
213
  : undefined
203
214
  void drain('fast', ctx)
204
215
  } else {
@@ -206,13 +217,24 @@ function loopBackendWs({ wsUrl, apiKey, identifier, claude, mcpConfig, workdir,
206
217
  }
207
218
  }
208
219
 
220
+ // Follow-up sweep: thread replies you are part of get NO @mention (no WS event
221
+ // fires), so poll the inbox on a slow cadence to catch .followUps — but ONLY
222
+ // within a short window after real activity, so an idle agent costs nothing.
223
+ const FOLLOWUP_MS = 60_000
224
+ const ACTIVE_WINDOW_MS = 5 * 60_000
225
+ const sweep = setInterval(() => {
226
+ if (busy) return
227
+ if (Date.now() - lastActivityAt > ACTIVE_WINDOW_MS) return // idle → don't spend a cycle
228
+ void drain('fast', 'Follow-up check: call poll_inbox and reply to any NEW .followUps (thread replies you are part of, even with no @mention) or unanswered .mentions. If there is nothing new, do nothing and stop — do NOT post.')
229
+ }, FOLLOWUP_MS)
230
+
209
231
  log('up — backend WS watcher on ' + wsUrl + (canCode ? ' [code: ' + workdir + ']' : ''))
210
- const handle = connectAgentWs({ wsUrl, apiKey, identifier, onEvent, log })
232
+ handle = connectAgentWs({ wsUrl, apiKey, identifier, onEvent, log })
211
233
 
212
234
  return new Promise(() => {
213
235
  // Run until killed. Tidy up the socket on termination so a restarting
214
236
  // service doesn't leak a half-open connection.
215
- const bye = () => { try { handle.close() } catch { /* noop */ } process.exit(0) }
237
+ const bye = () => { clearInterval(sweep); try { handle && handle.close() } catch { /* noop */ } process.exit(0) }
216
238
  process.on('SIGTERM', bye)
217
239
  process.on('SIGINT', bye)
218
240
  })
package/src/ws.mjs CHANGED
@@ -95,7 +95,11 @@ export function connectAgentWs({ wsUrl, apiKey, identifier, onEvent, log }) {
95
95
 
96
96
  open()
97
97
 
98
- return {
98
+ const api = {
99
+ /** Fire-and-forget send (no-op if the socket isn't open). */
100
+ send(obj) { try { if (ws && ws.readyState === 1) ws.send(JSON.stringify(obj)) } catch { /* closing */ } },
101
+ /** Show the agent as typing in a channel (docs/WEBSOCKET.md `{type:'typing'}`). */
102
+ sendTyping(channelId) { api.send({ type: 'typing', channel_id: channelId }) },
99
103
  close() {
100
104
  closed = true
101
105
  clearKeepalive()
@@ -103,4 +107,5 @@ export function connectAgentWs({ wsUrl, apiKey, identifier, onEvent, log }) {
103
107
  if (ws) { try { ws.close() } catch { /* already gone */ } ws = null }
104
108
  },
105
109
  }
110
+ return api
106
111
  }