158 lines
10 KiB
JavaScript
158 lines
10 KiB
JavaScript
// Row 65 (#1558, REQ-PLUG-2, Q156): the launcher takes a launch's route from the instance's
|
|
// harness, model and thinking vars and its harness from the plug-in loader. A stub host answers
|
|
// the ops, as in launch-limit-checks: mayLaunch records the route's provider, model and account,
|
|
// and authorizeLaunch stops the launch before anything spawns. The fixture modules live in a
|
|
// scratch plug-in tree; no core file names them.
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {readFileSync} from 'node:fs';
|
|
import {createLauncher} from '../src/launcher.mjs';
|
|
import {initializeControl} from '../../processing-control/src/state.mjs';
|
|
import {fakeHarnesses,fixture,tmp,withoutHostCredentials} from './helpers.mjs';
|
|
import {launchLogFile,SESSION_CREDENTIALS} from '../../seat/src/session.mjs';
|
|
|
|
const PI=JSON.stringify(new URL('../../plugins/harness/pi/index.mjs',import.meta.url).href);
|
|
// down fails its preflight; sparse takes one thinking level; metered spends launch.account; broken
|
|
// lacks the kind's exports; throws throws from its preflight. Each is the pi module otherwise.
|
|
const MODULES={
|
|
down:`export * from ${PI};\nexport const preflight = () => ({ code: "fixture-down", message: "fixture preflight fails" });\n`,
|
|
sparse:`export * from ${PI};\nexport const levels = () => ["low"];\n`,
|
|
metered:`export * from ${PI};\nexport const countsAccount = () => true;\n`,
|
|
broken:'export const interfaceVersion = 1;\nexport const adapter = () => "/nowhere";\n',
|
|
throws:`export * from ${PI};\nexport function preflight() { throw new Error("fixture preflight threw"); }\n`,
|
|
};
|
|
const ON=['pi','claude-code','down','sparse','metered','broken','throws','absent'];
|
|
|
|
// vars are the coder's whole agent vars; edit changes the rest of the file.
|
|
async function setup(t,vars,{on=ON,edit=doc=>doc,env:more={},modelAdmission=null}={}){
|
|
const root=tmp(t),f=fixture(root,'acme',doc=>{doc.roles.coder.vars=vars;doc.plugins={...doc.plugins,harness:on};return edit(doc);}),ops=[];
|
|
initializeControl(f.dataRoot);
|
|
withoutHostCredentials(f.env,{piKey:true});
|
|
Object.assign(f.env,more);
|
|
const host={business:'acme',beforeClose:()=>{},op:async x=>{
|
|
ops.push(x);
|
|
if(x.op==='identity')return {business:'acme',role:'pm'};
|
|
if(x.op==='mayLaunch')return {allow:true};
|
|
if(x.op==='modelAdmission' && modelAdmission) return modelAdmission;
|
|
if(x.op==='authorizeLaunch')throw Object.assign(Error('fixture authorization stop'),{code:'fixture-policy'});
|
|
return {};
|
|
}};
|
|
const makeLoader=await fakeHarnesses(t,{pi:null,'claude-code':null},write=>{for(const [name,body] of Object.entries(MODULES))write('harness',name,body);write('model-provider','cliproxy',`export * from ${JSON.stringify(new URL('../../plugins/model-provider/cliproxy/index.mjs',import.meta.url).href)};`);});
|
|
// The system config's execution keys are set and must never reach a route.
|
|
const system={...JSON.parse(readFileSync(f.config,'utf8'))};
|
|
system.execution={...system.execution,adapter:'pi',model:'glm-5.3-flash'};
|
|
const launcher=createLauncher({host,system,env:f.env,makeLoader,bwrap:'/usr/bin/true',log:()=>{}});
|
|
const events=()=>readFileSync(launchLogFile(f.dataRoot,'acme'),'utf8').trim().split('\n').map(l=>JSON.parse(l));
|
|
const launch=()=>launcher.fromSession('fixture-cap','coder');
|
|
const may=()=>ops.filter(x=>x.op==='mayLaunch').map(({provider,model,account})=>({provider,model,account}));
|
|
return {launch,ops,may,last:()=>events().at(-1)};
|
|
}
|
|
const route=(harness,model,thinking)=>({harness,model,thinking});
|
|
const fallback=(harness,model,thinking)=>({'fallback.harness':harness,'fallback.model':model,'fallback.thinking':thinking});
|
|
const SONNET={provider:'zai',model:'claude-sonnet-5-5',account:null}, FLASH={provider:'zai',model:'glm-5.3-flash',account:null};
|
|
|
|
test('row 65 a passing primary is the route: its provider and model reach mayLaunch, and a stated fallback is never used',async t=>{
|
|
const s=await setup(t,{...route('pi','claude-sonnet-5-5','high'),...fallback('down','glm-5.3-flash','low')});
|
|
assert.equal((await s.launch()).error,'fixture-policy');
|
|
assert.deepEqual(s.may(),[SONNET]);
|
|
});
|
|
|
|
test('row 65 a failed primary preflight takes the stated fallback, and the limit checks see the fallback route',async t=>{
|
|
const s=await setup(t,{...route('down','claude-sonnet-5-5','high'),...fallback('pi','glm-5.3-flash','low')});
|
|
assert.equal((await s.launch()).error,'fixture-policy');
|
|
assert.deepEqual(s.may(),[FLASH]);
|
|
});
|
|
|
|
test('row 65 a failed primary with no fallback refuses with the preflight code and never asks mayLaunch',async t=>{
|
|
const s=await setup(t,route('down','claude-sonnet-5-5','high'));
|
|
assert.deepEqual(await s.launch(),{ok:false,error:'fixture-down'});
|
|
assert.equal(s.last().why,'fixture preflight fails');
|
|
assert.deepEqual(s.may(),[]);
|
|
});
|
|
|
|
test('row 65 a fallback whose own preflight fails refuses with its code and names both reasons',async t=>{
|
|
const s=await setup(t,{...route('down','claude-sonnet-5-5','high'),...fallback('down','glm-5.3-flash','low')});
|
|
assert.deepEqual(await s.launch(),{ok:false,error:'fixture-down'});
|
|
assert.equal(s.last().why,'fallback down: fixture preflight fails (primary down: fixture preflight fails)');
|
|
assert.deepEqual(s.may(),[]);
|
|
});
|
|
|
|
test('row 65 the fallback route spends launch.account when its module counts the account',async t=>{
|
|
const env=Object.fromEntries(SESSION_CREDENTIALS.flat().map(k=>[k,'']));
|
|
Object.assign(env,{ANTHROPIC_AUTH_TOKEN:'stand-in',ANTHROPIC_BASE_URL:'http://127.0.0.1:9'});
|
|
const s=await setup(t,{...route('down','claude-sonnet-5-5','high'),...fallback('metered','glm-5.3-flash','low')},{env,edit:doc=>{doc.launch.account={name:'proxy-login',credential:'ANTHROPIC_AUTH_TOKEN+ANTHROPIC_BASE_URL'};return doc;}});
|
|
assert.equal((await s.launch()).error,'fixture-policy');
|
|
assert.deepEqual(s.may(),[{...FLASH,account:'proxy-login'}]);
|
|
});
|
|
|
|
test('row 65 a missing harness, model or thinking var refuses no-route naming it, and the system execution keys never fill it',async t=>{
|
|
for(const missing of ['harness','model','thinking']){
|
|
const vars=route('pi','claude-sonnet-5-5','high');delete vars[missing];
|
|
const s=await setup(t,vars);
|
|
assert.deepEqual(await s.launch(),{ok:false,error:'no-route'},missing);
|
|
assert.equal(s.last().why,`instance coder: no ${missing} var`);
|
|
assert.deepEqual(s.may(),[],missing);
|
|
}
|
|
});
|
|
|
|
test('row 65 a harness that is not switched on refuses harness-off, primary or fallback, and an off primary never falls back',async t=>{
|
|
const cases=[
|
|
[route('pi','claude-sonnet-5-5','high'),'pi'],
|
|
[{...route('pi','claude-sonnet-5-5','high'),...fallback('down','glm-5.3-flash','low')},'down'],
|
|
[{...route('down','claude-sonnet-5-5','high'),...fallback('pi','glm-5.3-flash','low')},'down'],
|
|
];
|
|
for(const [vars,off] of cases){
|
|
const s=await setup(t,vars,{on:ON.filter(n=>n!==off)});
|
|
assert.deepEqual(await s.launch(),{ok:false,error:'harness-off'},off);
|
|
assert.equal(s.last().why,`harness ${off} isn't switched on in plugins.harness`);
|
|
assert.deepEqual(s.may(),[],off);
|
|
}
|
|
// A file with no plugins.harness at all refuses the same way.
|
|
const s=await setup(t,route('pi','claude-sonnet-5-5','high'),{on:undefined,edit:doc=>{delete doc.plugins.harness;return doc;}});
|
|
assert.deepEqual(await s.launch(),{ok:false,error:'harness-off'});
|
|
});
|
|
|
|
test('row 65 the loader refusing a module refuses harness-refused and names it; a module that throws refuses harness-failed',async t=>{
|
|
for(const [name,reason] of [['broken',/^harness broken: missing-export: /],['absent',/^harness absent: /]]){
|
|
const s=await setup(t,route(name,'claude-sonnet-5-5','high'));
|
|
assert.deepEqual(await s.launch(),{ok:false,error:'harness-refused'},name);
|
|
assert.match(s.last().why,reason);
|
|
}
|
|
const s=await setup(t,route('throws','claude-sonnet-5-5','high'));
|
|
assert.deepEqual(await s.launch(),{ok:false,error:'harness-failed'});
|
|
assert.equal(s.last().why,'harness throws preflight: fixture preflight threw');
|
|
});
|
|
|
|
test('row 65 a thinking level the route harness does not apply refuses thinking-unsupported, on the primary or a fallback taken',async t=>{
|
|
// A refused fallback names the primary's failure too, so the reason it was tried stays visible.
|
|
for(const [vars,why] of [
|
|
[route('sparse','claude-sonnet-5-5','high'),'harness sparse takes thinking low, not high'],
|
|
[{...route('down','claude-sonnet-5-5','high'),...fallback('sparse','glm-5.3-flash','high')},'harness sparse takes thinking low, not high (primary down: fixture preflight fails)'],
|
|
]){
|
|
const s=await setup(t,vars);
|
|
assert.deepEqual(await s.launch(),{ok:false,error:'thinking-unsupported'});
|
|
assert.equal(s.last().why,why);
|
|
assert.deepEqual(s.may(),[]);
|
|
}
|
|
// A fallback the loader refuses names both too.
|
|
const refused=await setup(t,{...route('down','claude-sonnet-5-5','high'),...fallback('broken','glm-5.3-flash','high')});
|
|
assert.deepEqual(await refused.launch(),{ok:false,error:'harness-refused'});
|
|
assert.match(refused.last().why,/^harness broken: missing-export: .* \(primary down: fixture preflight fails\)$/);
|
|
// claude-code has no off: its module's own levels refuse it.
|
|
const s=await setup(t,route('claude-code','claude-sonnet-5-5','off'));
|
|
assert.deepEqual(await s.launch(),{ok:false,error:'thinking-unsupported'});
|
|
assert.equal(s.last().why,'harness claude-code takes thinking low, medium, high, xhigh, max, not off');
|
|
});
|
|
test('module-backed unavailable selection refuses before run creation',async t=>{
|
|
const s=await setup(t,route('pi','zai-fixture-id','low'),{edit:doc=>{doc.plugins['model-provider']=['cliproxy'];doc.launch.models['zai-fixture-id']={provider:'zai',model:'fixture-id',module:'cliproxy',upstream:'zai'};return doc;}});
|
|
assert.deepEqual(await s.launch(),{ok:false,error:'model-provider-unavailable'});
|
|
assert.equal(s.ops.some(op=>op.op==='modelAdmission'),true);
|
|
assert.deepEqual(s.may(),[{provider:'zai',model:'fixture-id',account:null}]);
|
|
});
|
|
|
|
test('a proxy uncertainty refusal survives launcher admission without creating a run',async t=>{
|
|
const s=await setup(t,route('pi','zai-fixture-id','low'),{modelAdmission:{allow:false,code:'proxy-uncertain'},edit:doc=>{doc.plugins['model-provider']=['cliproxy'];doc.launch.models['zai-fixture-id']={provider:'zai',model:'fixture-id',module:'cliproxy',upstream:'zai'};return doc;}});
|
|
assert.deepEqual(await s.launch(),{ok:false,error:'proxy-uncertain'});
|
|
assert.equal(s.last().code,'proxy-uncertain');
|
|
});
|