Files
BridgeSwarm/native-host/test-bare.js
T
snxraven e761cb4725
CI / Build & Test (push) Successful in 35m3s
Updates
2026-09-08 02:18:20 -04:00

878 lines
30 KiB
JavaScript

/**
* Bare-only host checks (path jail, pack register, agent loop).
* Run: node ./node_modules/bare/bin/bare ./test-bare.js
*/
require('bare-process/global');
const path = require('bare-path');
const fs = require('bare-fs');
const os = require('bare-os');
function ok(cond, msg) {
if (!cond) throw new Error(msg || 'assertion failed');
}
function throws(fn, re) {
let hit = false;
try {
fn();
} catch (err) {
hit = true;
if (re && !re.test(String(err.message || err))) {
throw new Error('threw unexpected: ' + err.message);
}
}
if (!hit) throw new Error('expected throw');
}
function tmpDir(prefix) {
const base = (os.tmpdir && os.tmpdir()) || '/tmp';
const dir = path.join(base, prefix + Date.now() + '_' + Math.random().toString(36).slice(2, 8));
fs.mkdirSync(dir, { recursive: true });
return dir;
}
function testPackRegister() {
const registry = require('./capabilities/registry.js');
const qvac = require('./capabilities/qvac.js');
const agent = require('./capabilities/agent.js');
const q = qvac.createQvacPack();
const a = agent.createAgentPack();
ok(q.id === 'qvac', 'qvac id');
ok(a.id === 'agent', 'agent id');
ok(typeof q.commands.load === 'function', 'qvac.load');
ok(typeof q.commands.audioGen === 'function', 'qvac.audioGen');
ok(typeof q.commands.pushDesktopFrame === 'function', 'qvac.pushDesktopFrame');
ok(typeof a.commands.prompt === 'function', 'agent.prompt');
registry.registerPack(q);
registry.registerPack(a);
const discord = require('./capabilities/discord.js');
const d = discord.createDiscordPack();
ok(d.id === 'discord', 'discord id');
ok(typeof d.commands.surface === 'function', 'discord.surface');
ok(typeof d.commands.construct === 'function', 'discord.construct');
registry.registerPack(d);
ok(registry.hasPack('qvac'), 'has qvac');
ok(registry.hasPack('agent'), 'has agent');
ok(registry.hasPack('discord'), 'has discord');
const engine = require('./qvac/engine.js');
ok(engine.publicStatus().enabled === false, 'qvac off by default');
ok(engine.publicStatus().available === false, 'unavailable when disabled');
}
function testAudiogenHelpers() {
const ag = require('./qvac/audiogen.js');
const wav = require('./qvac/wav.js');
ok(ag.isAudiogenLoad({ modelType: 'audiogen' }), 'audiogen load');
ok(ag.isAudiogenLoad({ model: 'acestep-turbo-q4' }), 'acestep id');
ok(ag.engineOf({ model: 'minimax-music3' }) === 'minimax', 'minimax engine');
const stripped = ag.stripMiniMaxParams({ caption: 'x', bpm: 120, lyrics: '[Instrumental]' });
ok(stripped.rejected.indexOf('bpm') >= 0, 'bpm rejected');
ok(stripped.params.lyrics === '[Instrumental]', 'lyrics kept');
let threw = false;
try {
ag.genParams({ caption: 'x', bpm: 98 }, 'minimax');
} catch (err) {
threw = /ACE-Step/.test(err.message);
}
ok(threw, 'minimax gen rejects ACE fields');
const pcm = new Uint8Array(16);
const blob = wav.createWav(pcm, 48000, 2, 16);
ok(blob.byteLength === 60, 'wav size');
ok(String.fromCharCode(blob[0], blob[1], blob[2], blob[3]) === 'RIFF', 'riff');
const sdk = ag.pickKeys(
{ engine: 'acestep', useGPU: true, ditModelSrc: 'DIT', caption: 'x', lmTemperature: 0.9 },
ag.SDK_LOAD_KEYS
);
ok(sdk.engine === undefined, 'pickKeys drops engine');
ok(sdk.useGPU === true, 'pickKeys keeps useGPU');
ok(sdk.lmTemperature === undefined, 'pickKeys drops gen extras from load keys');
return ag
.buildLoadOpts({ model: 'acestep-turbo-q4', modelType: 'audiogen', engine: 'acestep', useGPU: true }, async (n) => n)
.then((built) => {
ok(built.engine === 'acestep', 'host engine acestep');
ok(built.loadOpts.modelConfig.engine === undefined, 'SDK modelConfig has no engine');
ok(built.loadOpts.modelConfig.useGPU === true, 'useGPU forwarded');
ok(built.loadOpts.modelConfig.ditModelSrc === ag.DIT['turbo-q4'], 'dit from catalog variant');
ok(built.loadOpts.modelConfig.textEncModelSrc, 'textEnc set');
ok(built.loadOpts.modelType === 'audiogen', 'modelType audiogen');
return ag.buildLoadOpts({ model: 'minimax-music3', engine: 'minimax' }, async (n) => n).then(
() => {
throw new Error('minimax load should throw');
},
(err) => {
ok(/ACE-Step/.test(err.message), 'minimax load rejected');
}
);
});
}
function testPathJail() {
const tmp = tmpDir('bs-jail-');
process.env.BRIDGE_SWARM_STORAGE = tmp;
const paths = require('./capabilities/paths.js');
const jail = path.join(tmp, 'workspace');
fs.mkdirSync(jail, { recursive: true });
fs.writeFileSync(path.join(jail, 'ok.txt'), 'in');
const inside = paths.resolveUnderRoot(jail, 'ok.txt');
ok(inside.indexOf(jail) === 0, 'inside jail');
throws(() => paths.resolveUnderRoot(jail, '../etc/passwd'), /escapes/);
throws(() => paths.resolveUnderRoot(jail, '/etc/passwd'), /escapes/);
ok(paths.isPathInside(jail, path.join(jail, 'a', 'b')), 'nested inside');
ok(!paths.isPathInside(jail, path.join(tmp, 'outside')), 'outside rejected');
const sandbox = require('./agent/sandbox.js');
sandbox.setGrants({ roots: [jail] });
throws(() => sandbox.resolvePath('http://127.0.0.1:4173', '/etc/passwd', jail), /escapes/);
}
async function testAgentLoopFakeComplete() {
const tmp = tmpDir('bs-agent-');
process.env.BRIDGE_SWARM_STORAGE = tmp;
const engine = require('./qvac/engine.js');
const loop = require('./agent/loop.js');
const sessions = require('./agent/sessions.js');
const origComplete = engine.complete;
const origLoad = engine.load;
const origGet = engine.getLoaded;
engine.getLoaded = () => ({
modelId: 'fake',
friendlyId: 'qwen3.5-4b',
constant: 'QWEN3_5_4B_MULTIMODAL_Q4_K_M',
ctxSize: 8192,
});
engine.load = async () => engine.getLoaded();
let n = 0;
engine.complete = async (opts, onEvent) => {
n += 1;
if (n === 1) {
const call = { id: 'c1', name: 'list_dir', arguments: { path: '.' } };
if (onEvent) onEvent({ type: 'toolCall', call });
return { text: '', toolCalls: [call] };
}
if (onEvent) onEvent({ type: 'contentDelta', delta: 'listed' });
return { text: 'listed', toolCalls: [] };
};
const cwd = path.join(tmp, 'agent', 'ws');
fs.mkdirSync(cwd, { recursive: true });
fs.writeFileSync(path.join(cwd, 'hello.txt'), 'hi');
const sandbox = require('./agent/sandbox.js');
sandbox.setGrants({ roots: [cwd] });
const session = sessions.create({
origin: 'http://127.0.0.1:4173',
cwd,
model: 'qwen3.5-4b',
title: 'test',
});
const chunks = [];
const result = await loop.runTurn({
session,
userText: 'list files',
jobId: 'job_test',
payload: { _origin: 'http://127.0.0.1:4173', permissionMode: 'always-approve' },
emit(type, p) {
chunks.push({ type, kind: p && p.kind, evType: p && p.type });
},
});
engine.complete = origComplete;
engine.load = origLoad;
engine.getLoaded = origGet;
ok(result.ok, 'loop ok');
ok(n >= 2, 'two completions');
ok(
chunks.some((c) => c.evType === 'tool_call' || c.evType === 'tool_result' || c.kind === 'session_update'),
'streamed updates'
);
}
testPackRegister();
testPathJail();
testAudiogenHelpers()
.then(() => testAgentLoopFakeComplete())
.then(() => testCustomToolLoop())
.then(() => testHostWorkspaceOffLoop())
.then(() => testPlanWriteGateLoop())
.then(() => testAskUserLoop())
.then(() => testGoalPrematureLoop())
.then(() => testStationarityLoop())
.then(() => testSessionDestroyLoop())
.then(() => testParallelWritesLoop())
.then(() => testSamePathWriteOrderLoop())
.then(() => testGrepGlobLoop())
.then(() => testBareVersionsCoerce())
.then(() => testQvacOffByDefault())
.then(() => testDiscordPack())
.then(() => {
console.log('ok — bare host checks passed');
})
.catch((err) => {
console.error(err && err.stack ? err.stack : err);
process.exitCode = 1;
});
async function testBareVersionsCoerce() {
const origDesc = Object.getOwnPropertyDescriptor(Bare, 'versions');
const orig = Bare.versions && Bare.versions.bare;
const patch = require('./qvac/semver-patch.js');
try {
Bare.versions.bare = '^1.30.3';
const engine = require('./qvac/engine.js');
engine.normalizeBareVersions();
ok(/^\d+\.\d+\.\d+/.test(String(Bare.versions.bare)), 'coerced caret range to concrete version');
ok(Bare.versions.bare !== '^1.30.3', 'no longer a range');
const { satisfies } = require('bare-semver');
ok(satisfies('^1.30.3', '^1.30.3') === true, 'satisfies coerces caret runtime version');
Object.defineProperty(Bare, 'versions', {
configurable: true,
enumerable: true,
get() {
return { bare: '^1.30.3' };
},
});
patch.normalizeBareVersions();
ok(Bare.versions.bare !== '^1.30.3', 'replaced getter Bare.versions with concrete version');
} finally {
if (origDesc) Object.defineProperty(Bare, 'versions', origDesc);
else if (orig) Bare.versions.bare = orig;
}
}
async function testQvacOffByDefault() {
const qvac = require('./capabilities/qvac.js');
const agent = require('./capabilities/agent.js');
const q = qvac.createQvacPack();
const a = agent.createAgentPack();
let qReply = null;
await q.commands.load({ payload: {}, reply(r) { qReply = r; }, emit() {} });
ok(qReply && qReply.ok === false && /disabled/i.test(qReply.error), 'qvac.load refused when disabled');
let frameReply = null;
await q.commands.pushDesktopFrame({
payload: { dataUrl: 'data:image/png;base64,AA==' },
reply(r) { frameReply = r; },
emit() {},
});
ok(frameReply && frameReply.ok === false && /disabled/i.test(frameReply.error), 'qvac.pushDesktopFrame refused when disabled');
let aReply = null;
await a.commands.create({
payload: { _origin: 'http://127.0.0.1:4173' },
reply(r) { aReply = r; },
emit() {},
});
ok(aReply && aReply.ok === false && /disabled/i.test(aReply.error), 'agent.create refused when disabled');
let st = null;
await q.commands.status({ payload: {}, reply(r) { st = r; }, emit() {} });
ok(st && st.enabled === false, 'status reports disabled');
ok(st && st.available === false, 'status available false');
}
async function testDiscordPack() {
require('./discord/ws-bootstrap.cjs');
ok(typeof AbortController === 'function', 'AbortController after discord bootstrap');
ok(typeof fetch === 'function' || typeof globalThis.fetch === 'function', 'fetch after discord bootstrap');
const discord = require('./capabilities/discord.js');
const pack = discord.createDiscordPack();
let denied = null;
await pack.commands.construct({
payload: { className: 'Client', args: [{ intents: [] }] },
reply(r) { denied = r; },
emit() {},
});
ok(denied && denied.ok === false && /disabled/i.test(denied.error), 'construct refused when disabled');
let en = null;
await pack.commands.setEnabled({
payload: { enabled: true },
reply(r) { en = r; },
emit() {},
});
ok(en && en.ok && en.enabled === true, 'setEnabled on');
let surface = null;
await pack.commands.surface({
payload: {},
reply(r) { surface = r; },
emit() {},
});
ok(surface && surface.ok, 'surface ok: ' + (surface && surface.error));
ok(surface.surface && surface.surface.GatewayIntentBits, 'GatewayIntentBits present');
ok(surface.surface._classes && surface.surface._classes.indexOf('Client') >= 0, 'Client listed');
const guilds = surface.surface.GatewayIntentBits.Guilds;
let constructed = null;
await pack.commands.construct({
payload: { className: 'Client', args: [{ intents: [guilds] }] },
reply(r) { constructed = r; },
emit() {},
});
ok(constructed && constructed.ok && constructed.handle, 'construct Client');
let destroyed = null;
await pack.commands.destroy({
payload: { handle: constructed.handle },
reply(r) { destroyed = r; },
emit() {},
});
ok(destroyed && destroyed.ok, 'destroy Client');
async function dj(cmd, payload) {
let out = null;
await pack.commands[cmd]({
payload: payload || {},
reply(r) { out = r; },
emit() {},
});
return out;
}
const first = await dj('construct', {
className: 'Client',
args: [{ intents: [guilds] }],
_tabId: 11,
_pageEpoch: 'doc-a',
});
ok(first && first.ok && first.handle, 'construct tab-scoped Client');
const replace = await dj('construct', {
className: 'Client',
args: [{ intents: [guilds] }],
_tabId: 11,
_pageEpoch: 'doc-b',
});
ok(replace && replace.ok && replace.handle, 'reload construct replaces same tab');
ok(replace.handle !== first.handle, 'replacement is a new handle');
let st = await dj('status');
ok(st && st.clients === 1, 'same-tab replace keeps one Client, got ' + (st && st.clients));
const other = await dj('construct', {
className: 'Client',
args: [{ intents: [guilds] }],
_tabId: 12,
_pageEpoch: 'other',
});
ok(other && other.ok, 'second tab Client');
st = await dj('status');
ok(st && st.clients === 2, 'two tabs, two Clients');
const lateHide = await dj('dropTab', { tabId: 11, pageEpoch: 'doc-a' });
ok(lateHide && lateHide.ok && lateHide.dropped === 0, 'old document pagehide does not kill the new Client');
st = await dj('status');
ok(st && st.clients === 2, 'clients unchanged after stale epoch dropTab');
const closed = await dj('dropTab', { tabId: 12 });
ok(closed && closed.ok && closed.dropped === 1, 'tab close drops that tab only');
st = await dj('status');
ok(st && st.clients === 1, 'other tab still live');
const listed = await dj('listClients');
ok(listed && listed.ok && listed.clients.length === 1, 'listClients remaining');
ok(String(listed.clients[0].tabId) === '11', 'remaining client is tab 11');
await dj('dropTab', { tabId: 11 });
st = await dj('status');
ok(st && st.clients === 0, 'all tab-scoped Clients dropped');
const orphans = [];
for (let i = 0; i < 4; i++) {
const o = await dj('construct', { className: 'Client', args: [{ intents: [guilds] }] });
ok(o && o.ok, 'orphan Client ' + i);
orphans.push(o.handle);
}
st = await dj('status');
ok(st && st.clients === 4, 'four orphans fill the cap');
const reclaim = await dj('construct', {
className: 'Client',
args: [{ intents: [guilds] }],
_tabId: 99,
_pageEpoch: 'reclaim',
});
ok(reclaim && reclaim.ok, 'construct at max reclaims orphans');
st = await dj('status');
ok(st && st.clients === 1, 'orphans dropped, one tab-scoped Client remains, got ' + (st && st.clients));
await pack.commands.setEnabled({
payload: { enabled: false },
reply() {},
emit() {},
});
}
async function testCustomToolLoop() {
const tmp = tmpDir('bs-custom-');
process.env.BRIDGE_SWARM_STORAGE = tmp;
const engine = require('./qvac/engine.js');
const loop = require('./agent/loop.js');
const sessions = require('./agent/sessions.js');
const customTools = require('./agent/custom-tools.js');
const origComplete = engine.complete;
const origLoad = engine.load;
const origGet = engine.getLoaded;
engine.getLoaded = () => ({
modelId: 'fake',
friendlyId: 'qwen3.5-4b',
constant: 'QWEN3_5_4B_MULTIMODAL_Q4_K_M',
ctxSize: 8192,
});
engine.load = async () => engine.getLoaded();
let n = 0;
engine.complete = async (opts, onEvent) => {
n += 1;
if (n === 1) {
const call = { id: 'c1', name: 'get_cart', arguments: {} };
if (onEvent) onEvent({ type: 'toolCall', call });
return { text: '', toolCalls: [call] };
}
if (onEvent) onEvent({ type: 'contentDelta', delta: 'cart ok' });
return { text: 'cart ok', toolCalls: [] };
};
const cwd = path.join(tmp, 'ws');
fs.mkdirSync(cwd, { recursive: true });
const session = sessions.create({
origin: 'http://127.0.0.1:4173',
cwd,
model: 'qwen3.5-4b',
title: 'custom',
});
customTools.register(session.id, {
name: 'get_cart',
description: 'Return cart',
parameters: { type: 'object', properties: {} },
});
const jobId = 'job_custom';
const result = await loop.runTurn({
session,
userText: 'what is in the cart',
jobId,
payload: { _origin: 'http://127.0.0.1:4173', permissionMode: 'always-approve' },
emit(_type, p) {
if (p && p.type === 'tool_request') {
loop.resolveCustomResult(jobId, p.toolCallId, { result: { items: 2 } });
}
},
});
engine.complete = origComplete;
engine.load = origLoad;
engine.getLoaded = origGet;
ok(result.ok, 'custom loop ok');
ok(n >= 2, 'custom tool then final');
ok(String(result.text || '').indexOf('cart') !== -1 || result.ok, 'got reply');
}
async function testHostWorkspaceOffLoop() {
const tmp = tmpDir('bs-hostws-');
process.env.BRIDGE_SWARM_STORAGE = tmp;
const engine = require('./qvac/engine.js');
const loop = require('./agent/loop.js');
const sessions = require('./agent/sessions.js');
const customTools = require('./agent/custom-tools.js');
const origComplete = engine.complete;
const origLoad = engine.load;
const origGet = engine.getLoaded;
engine.getLoaded = () => ({
modelId: 'fake',
friendlyId: 'qwen3.5-4b',
constant: 'QWEN3_5_4B_MULTIMODAL_Q4_K_M',
ctxSize: 8192,
});
engine.load = async () => engine.getLoaded();
let n = 0;
let firstNames = [];
engine.complete = async (opts) => {
n += 1;
if (n === 1) firstNames = (opts.tools || []).map((t) => t.name);
if (n === 1) {
const call = { id: 'c1', name: 'read_file', arguments: { path: '/etc/passwd' } };
return { text: '', toolCalls: [call] };
}
return { text: 'from container', toolCalls: [] };
};
const session = sessions.create({
origin: 'http://127.0.0.1:4173',
cwd: 'dlinux-container',
workspace: 'dlinux-container',
hostWorkspace: false,
model: 'qwen3.5-4b',
title: 'page',
});
customTools.setSession(session.id, { hostWorkspace: false });
customTools.register(session.id, {
name: 'read_file',
description: 'Container read',
parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] },
});
const jobId = 'job_hostws';
const result = await loop.runTurn({
session,
userText: 'read passwd',
jobId,
payload: { _origin: 'http://127.0.0.1:4173', permissionMode: 'always-approve' },
emit(_type, p) {
if (p && p.type === 'tool_request') {
loop.resolveCustomResult(jobId, p.toolCallId, { result: { from: 'container' } });
}
},
});
engine.complete = origComplete;
engine.load = origLoad;
engine.getLoaded = origGet;
ok(result.ok, 'page-workspace loop ok');
ok(firstNames.indexOf('read_file') !== -1, 'page read_file offered');
ok(firstNames.indexOf('run_terminal_cmd') === -1, 'host shell not offered');
ok(firstNames.indexOf('grep') === -1, 'host grep not offered');
ok(n >= 2, 'custom read_file then final');
}
function mockEngine(engine) {
const origComplete = engine.complete;
const origLoad = engine.load;
const origGet = engine.getLoaded;
engine.getLoaded = () => ({
modelId: 'fake',
friendlyId: 'qwen3.5-4b',
constant: 'QWEN3_5_4B_MULTIMODAL_Q4_K_M',
ctxSize: 8192,
});
engine.load = async () => engine.getLoaded();
return () => {
engine.complete = origComplete;
engine.load = origLoad;
engine.getLoaded = origGet;
};
}
async function testPlanWriteGateLoop() {
const tmp = tmpDir('bs-plan-');
process.env.BRIDGE_SWARM_STORAGE = tmp;
const engine = require('./qvac/engine.js');
const loop = require('./agent/loop.js');
const sessions = require('./agent/sessions.js');
const restore = mockEngine(engine);
let n = 0;
const results = [];
engine.complete = async () => {
n += 1;
if (n === 1) {
return {
text: '',
toolCalls: [
{ id: 'e', name: 'enter_plan_mode', arguments: {} },
{ id: 'w', name: 'write_file', arguments: { path: 'hello.js', contents: 'nope' } },
],
};
}
return { text: 'planned', toolCalls: [] };
};
const cwd = path.join(tmp, 'ws');
fs.mkdirSync(cwd, { recursive: true });
const session = sessions.create({
origin: 'http://127.0.0.1:4173',
cwd,
model: 'qwen3.5-4b',
title: 'plan',
});
const result = await loop.runTurn({
session,
userText: 'plan then write',
jobId: 'job_plan',
payload: { _origin: 'http://127.0.0.1:4173', permissionMode: 'always-approve' },
emit(_t, p) {
if (p && p.type === 'tool_result') results.push(String(p.result || ''));
},
});
restore();
ok(result.ok, 'plan gate loop ok');
ok(results.some((r) => /Rejected|plan mode/i.test(r)), 'write to hello.js rejected');
ok(!fs.existsSync(path.join(cwd, 'hello.js')), 'hello.js not written');
}
async function testAskUserLoop() {
const tmp = tmpDir('bs-ask-');
process.env.BRIDGE_SWARM_STORAGE = tmp;
const engine = require('./qvac/engine.js');
const loop = require('./agent/loop.js');
const sessions = require('./agent/sessions.js');
const restore = mockEngine(engine);
let n = 0;
let choiceSeen = false;
engine.complete = async (opts) => {
n += 1;
if (n === 1) {
return {
text: '',
toolCalls: [{ id: 'a1', name: 'ask_user_question', arguments: { question: 'Pick', options: ['A', 'B'] } }],
};
}
const last = (opts.history || []).filter((m) => m.role === 'tool').pop();
choiceSeen = !!(last && /A/.test(String(last.content)));
return { text: 'chose A', toolCalls: [] };
};
const session = sessions.create({
origin: 'http://127.0.0.1:4173',
cwd: path.join(tmp, 'ws'),
model: 'qwen3.5-4b',
title: 'ask',
});
fs.mkdirSync(session.cwd, { recursive: true });
const result = await loop.runTurn({
session,
userText: 'ask me',
jobId: 'job_ask',
payload: { _origin: 'http://127.0.0.1:4173', permissionMode: 'always-approve' },
emit(_t, p) {
if (p && p.type === 'ask_user') loop.resolveAsk('job_ask', p.toolCallId, 'A');
},
});
restore();
ok(result.ok, 'ask loop ok');
ok(n >= 2, 'waited then continued');
ok(choiceSeen, 'choice reached history');
}
async function testGoalPrematureLoop() {
const tmp = tmpDir('bs-goal-');
process.env.BRIDGE_SWARM_STORAGE = tmp;
const engine = require('./qvac/engine.js');
const loop = require('./agent/loop.js');
const sessions = require('./agent/sessions.js');
const restore = mockEngine(engine);
let n = 0;
let premature = false;
engine.complete = async (opts) => {
n += 1;
if (!opts.tools || !opts.tools.length) {
return { text: '{"achieved": true, "gaps": []}', toolCalls: [] };
}
if (n === 1) {
return { text: '', toolCalls: [{ id: 'g1', name: 'update_goal', arguments: { completed: true } }] };
}
const last = (opts.history || []).filter((m) => m.role === 'tool').pop();
premature = !!(last && /todos|not complete/i.test(String(last.content)));
return { text: 'still working', toolCalls: [] };
};
const cwd = path.join(tmp, 'ws');
fs.mkdirSync(cwd, { recursive: true });
const session = sessions.create({
origin: 'http://127.0.0.1:4173',
cwd,
model: 'qwen3.5-4b',
title: 'goal',
});
session.plan = [{ id: '1', content: 'write file', status: 'pending' }];
const result = await loop.runTurn({
session,
userText: 'finish the goal',
jobId: 'job_goal',
payload: {
_origin: 'http://127.0.0.1:4173',
permissionMode: 'always-approve',
goal: 'Ship hello.js',
verify: false,
},
emit() {},
});
restore();
ok(result.ok, 'goal loop ok');
ok(premature, 'update_goal rejected with open todos');
ok(n >= 2, 'continued after premature stop');
}
async function testStationarityLoop() {
const tmp = tmpDir('bs-stuck-');
process.env.BRIDGE_SWARM_STORAGE = tmp;
const engine = require('./qvac/engine.js');
const loop = require('./agent/loop.js');
const sessions = require('./agent/sessions.js');
const restore = mockEngine(engine);
engine.complete = async () => {
const calls = [];
for (let i = 0; i < 8; i++) {
calls.push({ id: 'c' + i, name: 'list_dir', arguments: { path: '.' } });
}
return { text: '', toolCalls: calls };
};
const cwd = path.join(tmp, 'ws');
fs.mkdirSync(cwd, { recursive: true });
const session = sessions.create({
origin: 'http://127.0.0.1:4173',
cwd,
model: 'qwen3.5-4b',
title: 'stuck',
});
const result = await loop.runTurn({
session,
userText: 'list forever',
jobId: 'job_stuck',
payload: { _origin: 'http://127.0.0.1:4173', permissionMode: 'always-approve' },
emit() {},
});
restore();
ok(result.reason === 'stuck', 'stuck end reason');
}
async function testSessionDestroyLoop() {
const tmp = tmpDir('bs-sess-');
process.env.BRIDGE_SWARM_STORAGE = tmp;
const sessions = require('./agent/sessions.js');
const loop = require('./agent/loop.js');
const customTools = require('./agent/custom-tools.js');
const session = sessions.create({
origin: 'http://127.0.0.1:4173',
cwd: path.join(tmp, 'ws'),
model: 'qwen3.5-4b',
title: 'gone',
});
customTools.setSession(session.id, { hostWorkspace: true });
customTools.register(session.id, {
name: 'get_cart',
description: 'cart',
parameters: { type: 'object', properties: {} },
});
ok(sessions.exists(session.id), 'session on disk');
loop.markLive(session.id);
ok(loop.isLive(session.id), 'live while open');
loop.finishLive(session.id);
ok(!loop.isLive(session.id), 'cleared after turn');
loop.forget(session.id);
customTools.clear(session.id);
ok(sessions.remove(session.id), 'removed');
ok(!sessions.exists(session.id), 'gone from disk');
ok(customTools.list(session.id).length === 0, 'tools cleared');
}
async function testParallelWritesLoop() {
const tmp = tmpDir('bs-par-');
process.env.BRIDGE_SWARM_STORAGE = tmp;
const engine = require('./qvac/engine.js');
const loop = require('./agent/loop.js');
const sessions = require('./agent/sessions.js');
const restore = mockEngine(engine);
let n = 0;
engine.complete = async () => {
n += 1;
if (n === 1) {
return {
text: '',
toolCalls: [
{ id: 'w1', name: 'write_file', arguments: { path: 'a.txt', contents: 'AAA' } },
{ id: 'w2', name: 'write_file', arguments: { path: 'b.txt', contents: 'BBB' } },
],
};
}
return { text: 'wrote both', toolCalls: [] };
};
const cwd = path.join(tmp, 'ws');
fs.mkdirSync(cwd, { recursive: true });
const sandbox = require('./agent/sandbox.js');
sandbox.setGrants({ roots: [cwd] });
const session = sessions.create({
origin: 'http://127.0.0.1:4173',
cwd,
model: 'qwen3.5-4b',
title: 'par',
});
const result = await loop.runTurn({
session,
userText: 'write two files',
jobId: 'job_par',
payload: { _origin: 'http://127.0.0.1:4173', permissionMode: 'always-approve' },
emit() {},
});
restore();
ok(result.ok, 'parallel writes ok');
ok(fs.readFileSync(path.join(cwd, 'a.txt'), 'utf8') === 'AAA', 'a.txt written');
ok(fs.readFileSync(path.join(cwd, 'b.txt'), 'utf8') === 'BBB', 'b.txt written');
}
async function testSamePathWriteOrderLoop() {
const tmp = tmpDir('bs-ord-');
process.env.BRIDGE_SWARM_STORAGE = tmp;
const engine = require('./qvac/engine.js');
const loop = require('./agent/loop.js');
const sessions = require('./agent/sessions.js');
const restore = mockEngine(engine);
let n = 0;
engine.complete = async () => {
n += 1;
if (n === 1) {
return {
text: '',
toolCalls: [
{ id: 'w1', name: 'write_file', arguments: { path: 'same.txt', contents: 'AAA' } },
{
id: 's1',
name: 'search_replace',
arguments: { path: 'same.txt', old_string: 'AAA', new_string: 'BBB' },
},
],
};
}
return { text: 'ordered', toolCalls: [] };
};
const cwd = path.join(tmp, 'ws');
fs.mkdirSync(cwd, { recursive: true });
const sandbox = require('./agent/sandbox.js');
sandbox.setGrants({ roots: [cwd] });
const session = sessions.create({
origin: 'http://127.0.0.1:4173',
cwd,
model: 'qwen3.5-4b',
title: 'ord',
});
const result = await loop.runTurn({
session,
userText: 'write then replace',
jobId: 'job_ord',
payload: { _origin: 'http://127.0.0.1:4173', permissionMode: 'always-approve' },
emit() {},
});
restore();
ok(result.ok, 'same-path writes ok');
ok(fs.readFileSync(path.join(cwd, 'same.txt'), 'utf8') === 'BBB', 'search_replace ran after write_file');
}
async function testGrepGlobLoop() {
const tmp = tmpDir('bs-grep-');
process.env.BRIDGE_SWARM_STORAGE = tmp;
const engine = require('./qvac/engine.js');
const loop = require('./agent/loop.js');
const sessions = require('./agent/sessions.js');
const restore = mockEngine(engine);
let n = 0;
let grepOut = '';
engine.complete = async (opts) => {
n += 1;
if (n === 1) {
return {
text: '',
toolCalls: [{ id: 'g1', name: 'grep', arguments: { pattern: 'needle', glob: '*.js', output_mode: 'files_with_matches' } }],
};
}
const last = (opts.history || []).filter((m) => m.role === 'tool').pop();
grepOut = String(last && last.content || '');
return { text: 'grepped', toolCalls: [] };
};
const cwd = path.join(tmp, 'ws');
fs.mkdirSync(path.join(cwd, 'src'), { recursive: true });
fs.writeFileSync(path.join(cwd, 'src', 'hit.js'), 'needle here\n');
fs.writeFileSync(path.join(cwd, 'miss.md'), 'needle here\n');
const sandbox = require('./agent/sandbox.js');
sandbox.setGrants({ roots: [cwd] });
const session = sessions.create({
origin: 'http://127.0.0.1:4173',
cwd,
model: 'qwen3.5-4b',
title: 'grep',
});
const result = await loop.runTurn({
session,
userText: 'grep js',
jobId: 'job_grep',
payload: { _origin: 'http://127.0.0.1:4173', permissionMode: 'always-approve' },
emit() {},
});
restore();
ok(result.ok, 'grep glob ok');
ok(/hit\.js/.test(grepOut), 'js hit listed');
ok(!/miss\.md/.test(grepOut), 'md excluded by glob');
}