feat(councils-ui): add on-demand council conversations panel and model config controls
This commit is contained in:
@@ -10,6 +10,47 @@ export interface ConfigHandlerDeps {
|
||||
modelRouter?: ModelRouter;
|
||||
}
|
||||
|
||||
function ensureCouncilsConfig(config: Config): NonNullable<Config['councils']> {
|
||||
config.councils ??= {
|
||||
enabled: false,
|
||||
defaults: {
|
||||
max_rounds: 2,
|
||||
ideas_per_round: 6,
|
||||
top_ideas_for_bridge: 3,
|
||||
bridge_packet_max_chars: 2500,
|
||||
bridge_field_max_bullets: 6,
|
||||
bridge_entry_max_chars: 300,
|
||||
novelty_delta_threshold: 10,
|
||||
repetition_threshold: 70,
|
||||
},
|
||||
strict_grounding: false,
|
||||
strict_meta_validation: true,
|
||||
groups: {
|
||||
D: {
|
||||
arbiter_agent: 'council_d_arbiter',
|
||||
freethinker_agent: 'council_d_freethinker',
|
||||
model_tier: 'complex',
|
||||
group_prompt_prefix: 'Optimize for feasibility and speed-to-test. Prefer boring-but-true.',
|
||||
novelty_bias: 'low',
|
||||
risk_tolerance: 'low',
|
||||
forbidden_approaches: ['moonshots', 'handwavy AI claims', 'unverified assumptions'],
|
||||
},
|
||||
P: {
|
||||
arbiter_agent: 'council_p_arbiter',
|
||||
freethinker_agent: 'council_p_freethinker',
|
||||
model_tier: 'complex',
|
||||
group_prompt_prefix: 'Optimize for reframing and non-obvious leverage. Weird is fine; label speculation.',
|
||||
novelty_bias: 'high',
|
||||
risk_tolerance: 'high',
|
||||
forbidden_approaches: ['incremental tweaks', 'obvious best practices', 'purely conventional solutions'],
|
||||
},
|
||||
},
|
||||
meta_arbiter_agent: 'council_meta_arbiter',
|
||||
meta_model_tier: 'complex',
|
||||
};
|
||||
return config.councils;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redact sensitive values from config before returning.
|
||||
* Replaces API keys, tokens, passwords, and other credentials with "***".
|
||||
@@ -316,6 +357,87 @@ const PATCHABLE_KEYS: Record<string, (config: Config, value: unknown) => boolean
|
||||
config.agents.background_models.complex_reasoning.fallback_tier = value;
|
||||
return true;
|
||||
},
|
||||
'councils.enabled': (config, value) => {
|
||||
if (typeof value !== 'boolean') {return false;}
|
||||
const councils = ensureCouncilsConfig(config);
|
||||
councils.enabled = value;
|
||||
return true;
|
||||
},
|
||||
'councils.defaults.max_rounds': (config, value) => {
|
||||
if (typeof value !== 'number' || !Number.isFinite(value) || value < 1 || value > 6) {return false;}
|
||||
const councils = ensureCouncilsConfig(config);
|
||||
councils.defaults.max_rounds = Math.floor(value);
|
||||
return true;
|
||||
},
|
||||
'councils.groups.D.model_tier': (config, value) => {
|
||||
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
|
||||
const councils = ensureCouncilsConfig(config);
|
||||
councils.groups.D.model_tier = value;
|
||||
return true;
|
||||
},
|
||||
'councils.groups.P.model_tier': (config, value) => {
|
||||
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
|
||||
const councils = ensureCouncilsConfig(config);
|
||||
councils.groups.P.model_tier = value;
|
||||
return true;
|
||||
},
|
||||
'councils.meta_model_tier': (config, value) => {
|
||||
if (value !== 'fast' && value !== 'default' && value !== 'complex' && value !== 'local') {return false;}
|
||||
const councils = ensureCouncilsConfig(config);
|
||||
councils.meta_model_tier = value;
|
||||
return true;
|
||||
},
|
||||
'councils.groups.D.arbiter_agent': (config, value) => {
|
||||
if (typeof value !== 'string' || value.trim().length === 0) {return false;}
|
||||
const councils = ensureCouncilsConfig(config);
|
||||
councils.groups.D.arbiter_agent = value.trim();
|
||||
return true;
|
||||
},
|
||||
'councils.groups.D.freethinker_agent': (config, value) => {
|
||||
if (typeof value !== 'string' || value.trim().length === 0) {return false;}
|
||||
const councils = ensureCouncilsConfig(config);
|
||||
councils.groups.D.freethinker_agent = value.trim();
|
||||
return true;
|
||||
},
|
||||
'councils.groups.D.grounder_agent': (config, value) => {
|
||||
if (typeof value !== 'string') {return false;}
|
||||
const next = value.trim();
|
||||
const councils = ensureCouncilsConfig(config);
|
||||
councils.groups.D.grounder_agent = next.length > 0 ? next : undefined;
|
||||
return true;
|
||||
},
|
||||
'councils.groups.P.arbiter_agent': (config, value) => {
|
||||
if (typeof value !== 'string' || value.trim().length === 0) {return false;}
|
||||
const councils = ensureCouncilsConfig(config);
|
||||
councils.groups.P.arbiter_agent = value.trim();
|
||||
return true;
|
||||
},
|
||||
'councils.groups.P.freethinker_agent': (config, value) => {
|
||||
if (typeof value !== 'string' || value.trim().length === 0) {return false;}
|
||||
const councils = ensureCouncilsConfig(config);
|
||||
councils.groups.P.freethinker_agent = value.trim();
|
||||
return true;
|
||||
},
|
||||
'councils.groups.P.grounder_agent': (config, value) => {
|
||||
if (typeof value !== 'string') {return false;}
|
||||
const next = value.trim();
|
||||
const councils = ensureCouncilsConfig(config);
|
||||
councils.groups.P.grounder_agent = next.length > 0 ? next : undefined;
|
||||
return true;
|
||||
},
|
||||
'councils.meta_arbiter_agent': (config, value) => {
|
||||
if (typeof value !== 'string' || value.trim().length === 0) {return false;}
|
||||
const councils = ensureCouncilsConfig(config);
|
||||
councils.meta_arbiter_agent = value.trim();
|
||||
return true;
|
||||
},
|
||||
'councils.scaffold_path': (config, value) => {
|
||||
if (typeof value !== 'string') {return false;}
|
||||
const next = value.trim();
|
||||
const councils = ensureCouncilsConfig(config);
|
||||
councils.scaffold_path = next.length > 0 ? next : undefined;
|
||||
return true;
|
||||
},
|
||||
'models.default.provider': (config, value) => {
|
||||
if (!MODEL_PROVIDERS.includes(String(value) as ModelProvider)) {return false;}
|
||||
config.models.default.provider = value as ModelProvider;
|
||||
|
||||
@@ -1234,6 +1234,52 @@ describe('config handlers', () => {
|
||||
expect(getPath(config, 'tts', 'enabled_channels')).toEqual(['telegram', 'discord']);
|
||||
});
|
||||
|
||||
it('config.patch applies councils model and routing patches', async () => {
|
||||
const config = makeConfig();
|
||||
const handlers = createConfigHandlers({ config: asConfigValue(config) });
|
||||
const req: GatewayRequest = {
|
||||
id: 22,
|
||||
method: 'config.patch',
|
||||
params: {
|
||||
patches: {
|
||||
'councils.enabled': true,
|
||||
'councils.defaults.max_rounds': 3,
|
||||
'councils.groups.D.model_tier': 'complex',
|
||||
'councils.groups.P.model_tier': 'fast',
|
||||
'councils.meta_model_tier': 'default',
|
||||
'councils.groups.D.arbiter_agent': 'd_arbiter',
|
||||
'councils.groups.D.freethinker_agent': 'd_ft',
|
||||
'councils.groups.P.arbiter_agent': 'p_arbiter',
|
||||
'councils.groups.P.freethinker_agent': 'p_ft',
|
||||
'councils.meta_arbiter_agent': 'meta_arbiter',
|
||||
'councils.scaffold_path': 'docs/councils/ai-council-production-scaffold.json',
|
||||
},
|
||||
},
|
||||
};
|
||||
const result = await handlers['config.patch'](req) as GatewayResponse;
|
||||
const r = result.result as { applied: string[]; rejected: string[]; persisted: boolean };
|
||||
|
||||
expect(r.applied).toEqual([
|
||||
'councils.enabled',
|
||||
'councils.defaults.max_rounds',
|
||||
'councils.groups.D.model_tier',
|
||||
'councils.groups.P.model_tier',
|
||||
'councils.meta_model_tier',
|
||||
'councils.groups.D.arbiter_agent',
|
||||
'councils.groups.D.freethinker_agent',
|
||||
'councils.groups.P.arbiter_agent',
|
||||
'councils.groups.P.freethinker_agent',
|
||||
'councils.meta_arbiter_agent',
|
||||
'councils.scaffold_path',
|
||||
]);
|
||||
expect(r.rejected).toEqual([]);
|
||||
expect(getPath(config, 'councils', 'enabled')).toBe(true);
|
||||
expect(getPath(config, 'councils', 'defaults', 'max_rounds')).toBe(3);
|
||||
expect(getPath(config, 'councils', 'groups', 'P', 'model_tier')).toBe('fast');
|
||||
expect(getPath(config, 'councils', 'meta_model_tier')).toBe('default');
|
||||
expect(getPath(config, 'councils', 'meta_arbiter_agent')).toBe('meta_arbiter');
|
||||
});
|
||||
|
||||
it('config.patch rejects unknown keys', async () => {
|
||||
const config = makeConfig();
|
||||
const handlers = createConfigHandlers({ config: asConfigValue(config) });
|
||||
|
||||
Reference in New Issue
Block a user