openvisio-agent 0.7.0 → 0.7.1
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/src/watch.mjs +38 -1
package/package.json
CHANGED
package/src/watch.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// than written to disk from a pasted heredoc.
|
|
6
6
|
|
|
7
7
|
import { spawn, spawnSync } from 'node:child_process'
|
|
8
|
-
import { writeFileSync, mkdirSync, existsSync } from 'node:fs'
|
|
8
|
+
import { writeFileSync, mkdirSync, existsSync, readFileSync, unlinkSync } from 'node:fs'
|
|
9
9
|
import { homedir } from 'node:os'
|
|
10
10
|
import { join, dirname } from 'node:path'
|
|
11
11
|
import { OV_DIR, DEFAULT_WORKSPACE, readConfig, writeJson, configPath, onPath, fail, ok, info, slugify, stripSlash, chmodSafe } from './lib.mjs'
|
|
@@ -29,6 +29,7 @@ const REPLY_DISCIPLINE = [
|
|
|
29
29
|
' • 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.',
|
|
30
30
|
' • 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".',
|
|
31
31
|
' • 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.',
|
|
32
|
+
' • NO INVENTED HISTORY. You have NO memory beyond the messages visible in THIS thread and what your tools return right now. Never fabricate past events, competitions, conversations, results, links, PR numbers, deploy URLs, or figures. If you are asked about something you have no actual record of, say plainly "I don\'t have a record of that" — do NOT make one up to play along or be helpful. Only state things you can see or verify.',
|
|
32
33
|
].join('\n')
|
|
33
34
|
|
|
34
35
|
// ── CHAT-ONLY agents (no --workdir): chat/ticket tools, no code surface. ──────
|
|
@@ -131,6 +132,29 @@ const SESSION_IDLE_MS = 1200000
|
|
|
131
132
|
const MAX_CYCLE_MS = 240000
|
|
132
133
|
const MAX_CODE_CYCLE_MS = 900000
|
|
133
134
|
|
|
135
|
+
// Refuse to run a SECOND watcher for the same agent. Two watchers connect to the
|
|
136
|
+
// WS as the same agent and BOTH reply to every mention — the #1 cause of duplicate
|
|
137
|
+
// (and contradicting, if the two are different versions) messages. A pid lock file
|
|
138
|
+
// in ~/.openvisio makes the second start fail fast instead. Stale locks (dead pid)
|
|
139
|
+
// are taken over. Returns { release } or { conflict: <pid> }.
|
|
140
|
+
function acquireSingleInstance(key) {
|
|
141
|
+
const lockPath = join(OV_DIR, 'watch-' + key + '.lock')
|
|
142
|
+
try {
|
|
143
|
+
mkdirSync(OV_DIR, { recursive: true })
|
|
144
|
+
if (existsSync(lockPath)) {
|
|
145
|
+
const pid = parseInt(String(readFileSync(lockPath, 'utf8')).trim(), 10)
|
|
146
|
+
if (pid && pid !== process.pid) {
|
|
147
|
+
let alive = false
|
|
148
|
+
try { process.kill(pid, 0); alive = true } catch (e) { alive = !!(e && e.code === 'EPERM') }
|
|
149
|
+
if (alive) return { conflict: pid }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
writeFileSync(lockPath, String(process.pid))
|
|
153
|
+
} catch { /* if the lock can't be written, don't block the agent from running */ }
|
|
154
|
+
const release = () => { try { if (parseInt(String(readFileSync(lockPath, 'utf8')).trim(), 10) === process.pid) unlinkSync(lockPath) } catch { /* already gone */ } }
|
|
155
|
+
return { release }
|
|
156
|
+
}
|
|
157
|
+
|
|
134
158
|
export async function runWatch({ flags }) {
|
|
135
159
|
const slug = flags.name ? slugify(String(flags.name)) : null
|
|
136
160
|
const saved = slug ? readConfig(slug) : null
|
|
@@ -161,6 +185,19 @@ export async function runWatch({ flags }) {
|
|
|
161
185
|
const model = String(flags.model || (saved && saved.model) || (agent === 'opencode' ? '' : 'sonnet'))
|
|
162
186
|
const chatModel = String(flags['chat-model'] || (saved && saved.chatModel) || '')
|
|
163
187
|
|
|
188
|
+
// ONE watcher per agent. A second one (e.g. a manual `watch` alongside the
|
|
189
|
+
// background service, or a stale service) is the #1 cause of duplicate replies:
|
|
190
|
+
// both connect as the same agent and both answer every mention. Refuse to start.
|
|
191
|
+
if (!flags.install) {
|
|
192
|
+
const lock = acquireSingleInstance(slug || 'openvisio')
|
|
193
|
+
if (lock.conflict) {
|
|
194
|
+
fail(`Another openvisio-agent watcher for "${slug || 'openvisio'}" is already running (pid ${lock.conflict}).\n` +
|
|
195
|
+
` Two watchers for the same agent BOTH reply to every mention — that is what causes duplicate/contradicting messages.\n` +
|
|
196
|
+
` Stop the other one (kill ${lock.conflict}), or rely on ONLY the background service. Refusing to start a second.`)
|
|
197
|
+
}
|
|
198
|
+
process.on('exit', () => { try { lock.release && lock.release() } catch { /* noop */ } })
|
|
199
|
+
}
|
|
200
|
+
|
|
164
201
|
// Backend agents (connect --backend) drive autonomy over a real-time WS instead
|
|
165
202
|
// of REST-polling the frontend relay. Detected by the saved mode / a --ws flag.
|
|
166
203
|
const backendMode = (saved && saved.mode === 'backend') || !!flags.ws
|