import test from 'node:test' import assert from 'node:assert/strict' import fs from 'node:fs' import os from 'node:os' import path from 'node:path' import { Readable } from 'node:stream' import * as plugin from '../index.js' // Settings persist to /omp-advisor.json; keep tests away from the real profile. const PROFILE = fs.mkdtempSync(path.join(os.tmpdir(), 'omp-advisor-test-')) process.env.DSH_PROFILE_DIR = PROFILE const SETTINGS = path.join(PROFILE, 'omp-advisor.json') test.after(() => fs.rmSync(PROFILE, { recursive: true, force: true })) function harness(replies, config = {}) { fs.rmSync(SETTINGS, { force: true }) // live settings load in apply(): never leak between tests const listeners = new Map() const on = (n, f) => { listeners.set(n, [...(listeners.get(n) ?? []), f]); return () => listeners.set(n, listeners.get(n).filter((x) => x !== f)) } const prompts = [] const llm = { async *stream(opts) { prompts.push(opts) const r = replies.shift() ?? '{"verdict":"ok","message":""}' yield { type: 'text-delta', text: r } yield { type: 'finish', reason: { kind: 'stop' } } }, } const events = [] const session = { header: { cwd: '/w' }, requestHeader: () => ({ config: { provider: 'omni', model: 'main-model' } }), surface: { get nodes() { return events.map((_, i) => i) } }, eventAt: (i) => events[i], deriveEventMessage: (e) => e.msg, } const steered = [] const agent = { session, ctx: { on }, steer: (m) => steered.push(m) } const routes = new Map() const webServer = { register: (r) => { routes.set(r.path, r.handler); return () => routes.delete(r.path) } } const connection = { requestRejection: (req) => (req.headers?.['x-reject'] ? 401 : undefined) } const ctx = { agents: { list: () => [] }, llm, on, effect(fn) { return typeof fn === 'function' ? fn() : undefined }, inject: (_deps, fn) => fn({ webServer, connection, effect: (f) => f() }), } plugin.apply(ctx, { minLevel: 'note', blocking: true, ...config }) for (const f of listeners.get('agent/created') ?? []) f({ agent }) const preStep = async (step) => { let out for (const f of listeners.get('agent/pre-step') ?? []) out = await f({ agent, messages: [], step, turn: 1 }, async () => ({ kind: 'enter', messages: [] })) return out } const stopTurn = async () => { for (const f of listeners.get('agent/turn-stopping') ?? []) await f({ agent, turn: 1 }) } const add = (role, content) => events.push({ msg: { role, content } }) /** Call the settings route like the web client does. */ const http = async (method, body, headers = {}) => { const handler = routes.get('/omp-advisor/settings') const req = Readable.from(body === undefined ? [] : [JSON.stringify(body)]) req.method = method req.headers = headers let status = 200; let out = '' const res = { statusCode: 200, setHeader() {}, end: (s) => { status = res.statusCode; out = s ?? '' } } await handler(req, res) return { status, json: out ? JSON.parse(out) : null } } return { preStep, stopTurn, add, steered, prompts, http } } test('parseVerdict', () => { assert.deepEqual(plugin.parseVerdict('x {"verdict":"Concern","message":"m"} y'), { verdict: 'concern', message: 'm' }) assert.equal(plugin.parseVerdict('nope'), null) assert.equal(plugin.parseVerdict('{"verdict":"maybe"}'), null) }) test('a concern is injected into the next step as a system reminder', async () => { const h = harness(['{"verdict":"concern","message":"You deleted tests/ without asking."}']) h.add('user', [{ type: 'text', text: 'clean up the repo' }]) h.add('assistant', [{ type: 'tool-call', name: 'bash', arguments: { command: 'rm -rf tests/' } }]) h.add('tool', [{ type: 'text', text: '' }]) const d = await h.preStep(2) assert.equal(d.kind, 'enter') const text = d.messages.at(-1).content[0].text assert.match(text, /Advisor concern\] You deleted tests\/ without asking/) assert.equal(h.prompts[0].model, 'main-model') assert.match(h.prompts[0].messages[0].content[0].text, /rm -rf tests/) }) test('ok verdicts inject nothing; first step is never reviewed', async () => { const h = harness(['{"verdict":"ok","message":""}']) h.add('user', [{ type: 'text', text: 'hi' }]) assert.equal((await h.preStep(1)).messages.length, 0) assert.equal(h.prompts.length, 0) assert.equal((await h.preStep(2)).messages.length, 0) }) test('blocker at turn end steers the agent', async () => { const h = harness(['{"verdict":"blocker","message":"Do not push: tests are failing."}'], { blocking: false }) h.add('user', [{ type: 'text', text: 'ship it' }]) h.add('assistant', [{ type: 'tool-call', name: 'forge_push', arguments: {} }]) await h.preStep(2) // review starts in background await new Promise((r) => setTimeout(r, 50)) await h.stopTurn() assert.equal(h.steered.length, 1) assert.match(h.steered[0].content[0].text, /ADVISOR BLOCKER.*Do not push/s) }) test('minLevel filters notes and duplicates are suppressed', async () => { const h = harness(['{"verdict":"note","message":"tiny"}', '{"verdict":"concern","message":"same"}', '{"verdict":"concern","message":"same"}'], { minLevel: 'concern' }) h.add('user', [{ type: 'text', text: 'x' }]) assert.equal((await h.preStep(2)).messages.length, 0) // note < minLevel: dropped assert.equal((await h.preStep(3)).messages.length, 1) // concern (blocking review) delivered assert.equal((await h.preStep(4)).messages.length, 0) // identical concern suppressed }) test('settings route: GET/POST, rejection, persistence', async () => { fs.rmSync(SETTINGS, { force: true }) const h = harness([]) assert.equal((await h.http('GET', undefined, { 'x-reject': '1' })).status, 401) const g = await h.http('GET') assert.equal(g.status, 200) assert.equal(g.json.enabled, true) const p = await h.http('POST', { enabled: true, provider: 'omni', model: 'cheap-model', reasoningEffort: 'low' }) assert.equal(p.json.model, 'cheap-model') assert.equal(p.json.reasoningEffort, 'low') assert.deepEqual(JSON.parse(fs.readFileSync(SETTINGS, 'utf8')), { enabled: true, provider: 'omni', model: 'cheap-model', reasoningEffort: 'low' }) assert.equal((await h.http('PUT', {})).status, 405) }) test('UI choice applies on the next review without restart; off disables reviews', async () => { fs.rmSync(SETTINGS, { force: true }) const h = harness(['{"verdict":"ok","message":""}', '{"verdict":"ok","message":""}']) h.add('user', [{ type: 'text', text: 'task' }]) await h.http('POST', { enabled: true, provider: 'omni', model: 'cheap-model', reasoningEffort: 'low' }) await h.preStep(2) assert.equal(h.prompts[0].model, 'cheap-model') assert.equal(h.prompts[0].reasoningEffort, 'low') await h.http('POST', { provider: '', model: '', reasoningEffort: null }) // same as agent await h.preStep(3) assert.equal(h.prompts[1].model, 'main-model') assert.equal(h.prompts[1].reasoningEffort, undefined) await h.http('POST', { enabled: false }) await h.preStep(4) assert.equal(h.prompts.length, 2) // no review while off })