openvisio-agent 0.10.1 → 0.11.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/README.md +2 -0
- package/bin/cli.mjs +5 -5
- package/package.json +1 -1
- package/src/watch.mjs +71 -25
package/README.md
CHANGED
|
@@ -56,6 +56,8 @@ Then `watch --name ada` auto-detects the backend agent and runs a WebSocket loop
|
|
|
56
56
|
|
|
57
57
|
Runs the **autonomy loop** — the agent replies to @mentions and picks up tickets on its own. It cheaply polls an inbox endpoint (no model spend when idle) and pokes a single warm Claude Code session only when something new arrives.
|
|
58
58
|
|
|
59
|
+
Routine messages, triage, introductions, catch-up checks, and ticket movement use a lightweight model by default. Claude uses Haiku and Codex uses `gpt-5.6-luna`. Requests that clearly require repository work route to the coding model: Sonnet for Claude and `gpt-5.6-sol` for Codex. Override either lane with `--chat-model` and `--model`.
|
|
60
|
+
|
|
59
61
|
```bash
|
|
60
62
|
openvisio-agent watch --name ada # run in this terminal
|
|
61
63
|
openvisio-agent watch --name ada --install # run in the background, start at login
|
package/bin/cli.mjs
CHANGED
|
@@ -54,12 +54,12 @@ connect --backend
|
|
|
54
54
|
Codex authenticates separately with \`codex login\`; opencode
|
|
55
55
|
authenticates with \`opencode auth login\`.
|
|
56
56
|
--chat-only disable code work — chat/ticket tools only.
|
|
57
|
-
--model <m> model
|
|
58
|
-
|
|
57
|
+
--model <m> coding model. Claude defaults to sonnet; Codex defaults to
|
|
58
|
+
gpt-5.6-sol; OpenCode uses its configured default.
|
|
59
59
|
Engineers can also change it live from chat: "@agent /model …".
|
|
60
|
-
--chat-model <m>
|
|
61
|
-
|
|
62
|
-
--
|
|
60
|
+
--chat-model <m> lightweight coordination model for chat, triage, and board
|
|
61
|
+
movement. Defaults: Claude haiku, Codex gpt-5.6-luna.
|
|
62
|
+
Coding work stays on --model.
|
|
63
63
|
--no-service skip the background service — just save config + print the
|
|
64
64
|
watch commands to run yourself.
|
|
65
65
|
|
package/package.json
CHANGED
package/src/watch.mjs
CHANGED
|
@@ -29,7 +29,9 @@ 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
|
-
' •
|
|
32
|
+
' • USE RECALL, NEVER INVENT IT. Before answering a context-dependent question, search the visible thread and use any available history, search, docs, or recall tools. Reuse verified context instead of asking the user to repeat it. If no record exists, say plainly "I don\'t have a record of that". Never fabricate past events, conversations, results, links, PR numbers, deploy URLs, or figures.',
|
|
33
|
+
' • CLOSE CONCERNS. Never leave a concern, direct question, correction, or blocker addressed to you without a clear response. Acknowledge the concern, act if you can, then report the verified result. If blocked, name the blocker and the exact next action or owner in one message.',
|
|
34
|
+
' • WRITE PLAINLY. Prefer short sentences, commas, periods, and colons. Avoid em dashes except when reproducing quoted text.',
|
|
33
35
|
].join('\n')
|
|
34
36
|
|
|
35
37
|
// ── CHAT-ONLY agents (no --workdir): chat/ticket tools, no code surface. ──────
|
|
@@ -51,6 +53,13 @@ const CYCLE_FAST = [
|
|
|
51
53
|
'Post ONE message total for the thing you are answering — compose it fully, then send once. Never send a reply and then a "better" version; never repeat a reply you already sent. Be sure of your answer before sending. If asked for work you have no tool for, say so plainly and offer to file a ticket. 1-3 sentences, no summary. Then stop.',
|
|
52
54
|
].join('\n')
|
|
53
55
|
|
|
56
|
+
const COORDINATE = [
|
|
57
|
+
'COORDINATION-ONLY cycle. Use the lightweight lane for messaging, triage, ticket comments, assignment, and board movement.',
|
|
58
|
+
'Call get_marching_orders or poll_inbox only when the event context does not already contain enough detail. Use update_ticket to move a ticket to the correct board column when requested or when non-code work is complete.',
|
|
59
|
+
'Do not inspect repositories, edit files, run tests, or write code in this cycle. If the request actually requires code and this cycle was misclassified, acknowledge it once and state that it needs the coding lane. Do not pretend it is complete.',
|
|
60
|
+
'Respond to every direct concern assigned to you, but post only once per item and never duplicate an existing acknowledgement.',
|
|
61
|
+
].join('\n')
|
|
62
|
+
|
|
54
63
|
// ── CODE agents (--workdir given): full file + Bash + git/gh surface. ─────────
|
|
55
64
|
// A stable "who you are / how you work" charter prepended to every code cycle.
|
|
56
65
|
const CODE_CHARTER = [
|
|
@@ -58,7 +67,7 @@ const CODE_CHARTER = [
|
|
|
58
67
|
' • openvisio-team chat/ticket tools — read mentions, post messages, comment on tickets, react (post_message / poll_inbox / comment_ticket / react_message; your runtime may namespace them — use whatever names appear in your tool list).',
|
|
59
68
|
' • Read / Grep / Glob / Edit / Write / MultiEdit — inspect AND change code.',
|
|
60
69
|
' • Bash — git (branch, commit, push a branch), gh (clone repos, open PRs), run tests/builds.',
|
|
61
|
-
'YOUR WORKSPACE: your working directory is a WORKSPACE ROOT that holds the org\'s repos as subfolders.
|
|
70
|
+
'YOUR WORKSPACE: your working directory is a WORKSPACE ROOT that holds the org\'s repos as subfolders. Reuse existing clones and the context you already verified. Read repository AGENTS.md instructions before changing code. For any task: locate the relevant repo under the workspace; clone it only when it is genuinely absent, then work inside that subfolder. Never ask the user for a path you can discover yourself.',
|
|
62
71
|
'CAPABILITY CHECK: before you EVER answer "I can\'t do that", verify against the tools above. If a tool exists for it, DO it. To be explicit: you CAN read/inspect any of the org\'s codebases, clone a repo you don\'t have yet, work on it, create a branch, and raise a PR — say YES to these and then actually do them.',
|
|
63
72
|
'',
|
|
64
73
|
'WORK ETHIC — how a reliable teammate behaves (this is the difference between useful and ignored):',
|
|
@@ -72,9 +81,9 @@ const CODE_CHARTER = [
|
|
|
72
81
|
|
|
73
82
|
const CODE_FULL = [
|
|
74
83
|
'THIS CYCLE: call get_marching_orders and poll_inbox to see assigned tickets + mentions, then act on them.',
|
|
75
|
-
'
|
|
84
|
+
'DO NOT post a promise or pre-work acknowledgement. Start the repository work immediately. Your first task/channel update must contain either a verified result (branch, commit, PR, tests) or a concrete blocker you actually encountered.',
|
|
76
85
|
'For real code work (an assigned ticket, or a mention asking for changes), run the full flow end-to-end:',
|
|
77
|
-
' 1. GET THE CODE: locate the target repo under your workspace root
|
|
86
|
+
' 1. GET THE CODE: use verified thread/history/recall context first, locate the target repo under your workspace root, and read its AGENTS.md. Reuse an existing clone; clone only if absent. Check `git status` before changing anything and preserve unrelated user work. Update from the remote only when it is safe. Do this yourself; never ask the user for a path you can discover.',
|
|
78
87
|
' 2. BRANCH: git checkout -B agent/<short-task-slug>. NEVER work on, commit to, or push main/master.',
|
|
79
88
|
' 3. CHANGE + VERIFY: Read/Edit/Write the files; run the tests or build if the repo has them.',
|
|
80
89
|
' 4. COMMIT + PUSH YOUR BRANCH: git add -A && git commit -m "…"; then git push -u origin agent/<slug>. Only ever push your own agent/* branch. Never --force, never push to main/master, never merge.',
|
|
@@ -206,10 +215,12 @@ export async function runWatch({ flags }) {
|
|
|
206
215
|
const workdir = chatOnly ? '' : (explicitWorkdir || (saved && (saved.workspace || saved.workdir)) || DEFAULT_WORKSPACE)
|
|
207
216
|
if (workdir) { try { mkdirSync(workdir, { recursive: true }) } catch { /* best-effort; spawn will surface a real problem */ } }
|
|
208
217
|
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
const
|
|
212
|
-
const
|
|
218
|
+
// Keep routine coordination on the cheapest capable tier. Reserve the stronger
|
|
219
|
+
// default for coding cycles. Explicit flags and saved choices always win.
|
|
220
|
+
const defaultModel = agent === 'claude' ? 'sonnet' : agent === 'codex' ? 'gpt-5.6-sol' : ''
|
|
221
|
+
const defaultChatModel = agent === 'claude' ? 'haiku' : agent === 'codex' ? 'gpt-5.6-luna' : ''
|
|
222
|
+
const model = String(flags.model || (saved && saved.model) || defaultModel)
|
|
223
|
+
const chatModel = String(flags['chat-model'] || (saved && saved.chatModel) || defaultChatModel)
|
|
213
224
|
|
|
214
225
|
// ONE watcher per agent. A second one (e.g. a manual `watch` alongside the
|
|
215
226
|
// background service, or a stale service) is the #1 cause of duplicate replies:
|
|
@@ -337,8 +348,20 @@ function createCodexRunner({ mcpUrl, mcpHeaders, workdir, canCode, maxCycleMs, l
|
|
|
337
348
|
...(m ? ['--model', m] : []),
|
|
338
349
|
...(mcpOverride ? ['-c', mcpOverride] : []),
|
|
339
350
|
full]
|
|
340
|
-
let child = null, done = false
|
|
341
|
-
const finish = (o) => { if (done) return; done = true; clearTimeout(timer); resolve(o) }
|
|
351
|
+
let child = null, done = false, didCode = false, didMessage = false, outputText = '', jsonlBuffer = ''
|
|
352
|
+
const finish = (o) => { if (done) return; done = true; clearTimeout(timer); resolve({ ...o, didCode, didMessage, outputText }) }
|
|
353
|
+
const inspectLine = (line) => {
|
|
354
|
+
const s = line.trim()
|
|
355
|
+
if (!s) return
|
|
356
|
+
if (/command_execution|file_change|apply_patch|shell_command|exec_command/i.test(s)) didCode = true
|
|
357
|
+
if (/post_message|comment_ticket/i.test(s)) didMessage = true
|
|
358
|
+
try {
|
|
359
|
+
const event = JSON.parse(s)
|
|
360
|
+
const item = event.item ?? event
|
|
361
|
+
if (item?.type === 'agent_message' && typeof item.text === 'string') outputText += ' ' + item.text
|
|
362
|
+
} catch { /* non-JSON diagnostic */ }
|
|
363
|
+
if (debug) log(' · ' + s.slice(0, 220))
|
|
364
|
+
}
|
|
342
365
|
const timer = setTimeout(() => {
|
|
343
366
|
log('codex cycle TIMED OUT after ' + Math.round(maxCycleMs / 1000) + 's — killing')
|
|
344
367
|
try { child && child.kill() } catch { /* gone */ }
|
|
@@ -346,15 +369,20 @@ function createCodexRunner({ mcpUrl, mcpHeaders, workdir, canCode, maxCycleMs, l
|
|
|
346
369
|
}, maxCycleMs)
|
|
347
370
|
log('running codex cycle…' + (m ? ' [' + m + ']' : ''))
|
|
348
371
|
try {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
372
|
+
// Always inspect Codex JSONL so a successful process exit cannot be
|
|
373
|
+
// mistaken for completed work. Keep it out of normal logs unless debug.
|
|
374
|
+
child = spawn(bin, args, { cwd, stdio: ['ignore', 'pipe', 'inherit'] })
|
|
375
|
+
if (child.stdout) child.stdout.on('data', (d) => {
|
|
376
|
+
jsonlBuffer += String(d)
|
|
377
|
+
const lines = jsonlBuffer.split('\n')
|
|
378
|
+
jsonlBuffer = lines.pop() ?? ''
|
|
379
|
+
for (const line of lines) inspectLine(line)
|
|
352
380
|
})
|
|
353
381
|
} catch (e) {
|
|
354
382
|
log('codex spawn failed: ' + (e && e.message ? e.message : e) + ' — is Codex installed and signed in? (`npm i -g @openai/codex`, then `codex login`)')
|
|
355
383
|
return finish({ type: 'result', subtype: 'spawn-failed' })
|
|
356
384
|
}
|
|
357
|
-
child.on('
|
|
385
|
+
child.on('close', (code) => { inspectLine(jsonlBuffer); jsonlBuffer = ''; log('codex cycle done (' + (code === 0 ? 'ok' : 'exit ' + code) + ')'); finish({ type: 'result', subtype: code === 0 ? 'ok' : 'error' }) })
|
|
358
386
|
child.on('error', (e) => { log('codex error: ' + (e && e.message ? e.message : e)); finish({ type: 'result', subtype: 'error' }) })
|
|
359
387
|
})
|
|
360
388
|
}
|
|
@@ -513,7 +541,7 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
513
541
|
let liteModel = chatModel || model
|
|
514
542
|
|
|
515
543
|
let busy = false
|
|
516
|
-
let queued = null // 'full' | 'fast' | 'sweep' | 'intro' — a cycle requested while one was running
|
|
544
|
+
let queued = null // 'full' | 'coord' | 'fast' | 'sweep' | 'intro' — a cycle requested while one was running
|
|
517
545
|
// Tasks we've already reacted to (keyed task-id:agent — so a REASSIGNMENT to a
|
|
518
546
|
// different agent re-triggers), so a noisy stream of task:updated events doesn't
|
|
519
547
|
// re-acknowledge the same assignment.
|
|
@@ -529,8 +557,8 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
529
557
|
const pending = []
|
|
530
558
|
|
|
531
559
|
// Higher rank wins when coalescing cycles requested while one is running.
|
|
532
|
-
const RANK = { fast: 0, intro: 1, sweep: 2, full: 3 }
|
|
533
|
-
const baseFor = (kind) => kind === 'intro' ? INTRO :
|
|
560
|
+
const RANK = { fast: 0, intro: 1, coord: 2, sweep: 2, full: 3 }
|
|
561
|
+
const baseFor = (kind) => kind === 'intro' ? INTRO : kind === 'full' ? fullPrompt : (kind === 'coord' || kind === 'sweep') ? COORDINATE : fastPrompt
|
|
534
562
|
|
|
535
563
|
async function drain(kind, context) {
|
|
536
564
|
if (context) pending.push(context)
|
|
@@ -542,7 +570,7 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
542
570
|
const prompt = (ctx.length ? ctx.join('\n') + '\n\n' : '') + baseFor(kind)
|
|
543
571
|
// Chat-shaped cycles (mentions/intro) may run on the cheaper chat model; code
|
|
544
572
|
// work (full/sweep) uses the main model.
|
|
545
|
-
const useModel =
|
|
573
|
+
const useModel = kind === 'full' ? codeModel : liteModel
|
|
546
574
|
log('running ' + kind + ' cycle…' + (ctx.length ? ' (' + ctx.length + ' event' + (ctx.length === 1 ? '' : 's') + ')' : '') + (useModel ? ' [' + useModel + ']' : ''))
|
|
547
575
|
// Live status: "working" now + a heartbeat so the UI (and its TTL) stays lit
|
|
548
576
|
// through a long cycle; onTool flips it to "typing" when post_message fires.
|
|
@@ -550,7 +578,14 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
550
578
|
emitStatus('working')
|
|
551
579
|
const heartbeat = targets.length ? setInterval(() => emitStatus('working'), 9000) : null
|
|
552
580
|
try {
|
|
553
|
-
await runCycle(prompt, useModel)
|
|
581
|
+
const result = await runCycle(prompt, useModel)
|
|
582
|
+
// Codex can exit successfully after posting only "I'll do it". For coding
|
|
583
|
+
// cycles, treat that as incomplete and give it one focused recovery turn.
|
|
584
|
+
// Do not retry a real blocker, which would only create duplicate noise.
|
|
585
|
+
if (agent === 'codex' && kind === 'full' && result?.subtype === 'ok' && result.didMessage && !result.didCode && !/\b(?:blocked|cannot|can't|missing|need access|permission|unclear)\b/i.test(result.outputText || '')) {
|
|
586
|
+
log('coding cycle only acknowledged the task; running one completion recovery')
|
|
587
|
+
await runCycle('You posted an acknowledgement but performed no repository work. Do not post another acknowledgement. Resume the same assigned task now: inspect the workspace, make the required changes, verify them, commit and push an agent/* branch, open the PR, then post exactly one result update with evidence. If a real blocker appears, report that blocker once.', codeModel)
|
|
588
|
+
}
|
|
554
589
|
} finally {
|
|
555
590
|
if (heartbeat) clearInterval(heartbeat)
|
|
556
591
|
for (const c of targets) { try { handle && handle.sendStatus(c, 'done') } catch { /* noop */ } statusTargets.delete(c) }
|
|
@@ -567,6 +602,11 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
567
602
|
return (`${s.first_name || ''} ${s.last_name || ''}`.trim() || s.name || s.email || '')
|
|
568
603
|
}
|
|
569
604
|
|
|
605
|
+
// Route obvious repository work to the coding lane. Everything else, including
|
|
606
|
+
// chat and board movement, stays on the lightweight coordination model.
|
|
607
|
+
const needsCode = (value) => /\b(?:code|coding|implement|implementation|create|make|design|fix|bug|debug|refactor|test|tests|build|compile|repository|repo|github|git|branch|commit|pull request|pr|endpoint|api|component|feature|page|screen|ui|function|class|database|migration|schema|deploy|release|package|npm|typescript|javascript|python|swift|rust|golang|css|html|file|files|documentation)\b/i.test(String(value || ''))
|
|
608
|
+
const coordinationOnly = (value) => /\b(?:move|moving|status|column|assign|reassign|unassign|comment|reply|message|triage|prioriti[sz]e|label|rename|close|reopen)\b/i.test(String(value || '')) && !needsCode(value)
|
|
609
|
+
|
|
570
610
|
// Engineers change the model under the hood from chat: "/model", "/model sonnet",
|
|
571
611
|
// "use model haiku", "switch model to opus". Returns {report} | {set} | {invalid}.
|
|
572
612
|
const normalizeModel = (s) => {
|
|
@@ -617,7 +657,12 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
617
657
|
if (t.id != null) { seenTasks.add(key); if (seenTasks.size > 500) seenTasks.clear() }
|
|
618
658
|
log('task ' + k.slice(5) + ' #' + (t.id != null ? t.id : '?') + ' (agent ' + agentId + ') “' + (t.title || '') + '”')
|
|
619
659
|
const desc = t.description ? ' — ' + String(t.description).replace(/\s+/g, ' ').slice(0, 400) : ''
|
|
620
|
-
|
|
660
|
+
const taskText = [t.title, t.description, t.type, t.kind, Array.isArray(t.labels) ? t.labels.join(' ') : t.labels].filter(Boolean).join(' ')
|
|
661
|
+
// An assigned task is presumed to require execution. Only explicit board or
|
|
662
|
+
// messaging work stays on the cheap lane; vague verbs such as "create",
|
|
663
|
+
// "make", or "improve" must not strand a coding task in coordination.
|
|
664
|
+
const kind = canCode && !coordinationOnly(taskText) ? 'full' : 'coord'
|
|
665
|
+
void drain(kind, `A task was just ${k === 'task:created' ? 'created and assigned' : 'assigned'} to an agent in this workspace: task #${t.id != null ? t.id : '?'}: "${t.title || ''}"${desc} (agent_id ${agentId}). Call get_marching_orders to confirm it is assigned to YOU. If it is yours, acknowledge it once, complete it with the tools appropriate to this ${kind === 'full' ? 'coding' : 'coordination'} lane, move the ticket when appropriate, and report the verified result. If it is not yours, do nothing and stop.`)
|
|
621
666
|
} else if (k === 'agent:mention') {
|
|
622
667
|
const cid = raw.channel_id != null ? raw.channel_id : (raw.channelId != null ? raw.channelId : null)
|
|
623
668
|
const msg = raw.message && typeof raw.message === 'object' ? raw.message : {}
|
|
@@ -660,10 +705,11 @@ function loopBackendWs({ wsUrl, apiKey, identifier, slug, claude, agent, mcpConf
|
|
|
660
705
|
// Light up the live status the instant we pick this up (thinking → the cycle
|
|
661
706
|
// takes it to working → typing → done).
|
|
662
707
|
if (cid != null) { statusTargets.add(cid); try { handle && handle.sendStatus(cid, 'thinking') } catch { /* best-effort */ } }
|
|
708
|
+
const codingMention = canCode && needsCode(text)
|
|
663
709
|
const ctx = cid != null
|
|
664
|
-
? `You were @mentioned in OpenVisio channel ${cid}${who ? ` by "${who}"` : ''}: "${text}". This mention is FOR YOU. Send EXACTLY ONE reply with post_message
|
|
710
|
+
? `You were @mentioned in OpenVisio channel ${cid}${who ? ` by "${who}"` : ''}: "${text}". This mention is FOR YOU. ${codingMention ? 'This is repository work: complete the coding flow first, then send' : 'Send'} EXACTLY ONE reply with post_message: arguments: channel_id ${cid}${threadRoot != null ? `, parent_id ${threadRoot} (reply IN THAT THREAD, do not start a new top-level message)` : ''}, plus agent_identifier + agent_api_key from the AUTH line above, and a 1-3 sentence reply. Compose the whole answer, then post it ONCE. Do not post a first reply and then a revised version. FIRST read the recent messages in this thread: if you already answered this, or another agent was the one addressed, do NOT post at all. Be sure of your answer before sending.${who ? ` To @mention them back, write their EXACT full name "@${who}". A mention only links when the name matches exactly.` : ''} You ALREADY have the message here. Do not poll_inbox, and after your single reply, STOP.`
|
|
665
711
|
: undefined
|
|
666
|
-
void drain('fast', ctx)
|
|
712
|
+
void drain(codingMention ? 'full' : 'fast', ctx)
|
|
667
713
|
} else if (k === 'error') {
|
|
668
714
|
// Surface the backend's rejection detail instead of a bare "event error".
|
|
669
715
|
log('error event: ' + JSON.stringify(raw).slice(0, 220))
|
|
@@ -747,7 +793,7 @@ function loop({ host, key, slug, claude, agent, mcpConfig, mcpUrl, workdir, mode
|
|
|
747
793
|
}
|
|
748
794
|
// The first inbox response below decides whether a startup sweep is needed.
|
|
749
795
|
// Do not spend a model turn on every service restart when the inbox is empty.
|
|
750
|
-
setInterval(() => { log('daily catch-up sweep'); queuedSpecial = SWEEP + '\n\n' +
|
|
796
|
+
setInterval(() => { log('daily catch-up sweep'); queuedSpecial = SWEEP + '\n\n' + COORDINATE }, 24 * 60 * 60 * 1000)
|
|
751
797
|
}
|
|
752
798
|
|
|
753
799
|
async function check() {
|
|
@@ -773,7 +819,7 @@ function loop({ host, key, slug, claude, agent, mcpConfig, mcpUrl, workdir, mode
|
|
|
773
819
|
if (!busy && queuedSpecial) {
|
|
774
820
|
const p = queuedSpecial; queuedSpecial = null
|
|
775
821
|
busy = true
|
|
776
|
-
try { await runCycle(p,
|
|
822
|
+
try { await runCycle(p, liteModel) } finally { busy = false; lastNewAt = Date.now() }
|
|
777
823
|
await sleep(FAST)
|
|
778
824
|
continue
|
|
779
825
|
}
|
|
@@ -785,7 +831,7 @@ function loop({ host, key, slug, claude, agent, mcpConfig, mcpUrl, workdir, mode
|
|
|
785
831
|
for (const i of items) if (i.startsWith('tk:') || i.startsWith('clm:')) seen.add(i)
|
|
786
832
|
if (hasTaskBacklog) {
|
|
787
833
|
log('startup task backlog -> catch-up sweep')
|
|
788
|
-
queuedSpecial = SWEEP + '\n\n' +
|
|
834
|
+
queuedSpecial = SWEEP + '\n\n' + COORDINATE
|
|
789
835
|
}
|
|
790
836
|
}
|
|
791
837
|
if (res.paused) {
|