feat(webchat): add personal assistant mode controls in settings

This commit is contained in:
William Valentin
2026-02-18 12:04:37 -08:00
parent a8bb9f23ac
commit 43b9324c14
5 changed files with 257 additions and 2 deletions
+46
View File
@@ -163,6 +163,52 @@ const PATCHABLE_KEYS: Record<string, (config: Config, value: unknown) => boolean
config.server.nodes.push.enabled = value;
return true;
},
'automation.delivery_mode': (config, value) => {
if (value !== 'shared_session' && value !== 'isolated_job' && value !== 'announce') {return false;}
config.automation ??= {} as Config['automation'];
config.automation.delivery_mode = value;
return true;
},
'automation.daily_briefing.enabled': (config, value) => {
if (typeof value !== 'boolean') {return false;}
config.automation ??= {} as Config['automation'];
config.automation.daily_briefing ??= {} as Config['automation']['daily_briefing'];
config.automation.daily_briefing.enabled = value;
return true;
},
'memory.daily_log.enabled': (config, value) => {
if (typeof value !== 'boolean') {return false;}
config.memory ??= {} as Config['memory'];
config.memory.daily_log ??= {} as Config['memory']['daily_log'];
config.memory.daily_log.enabled = value;
return true;
},
'memory.proactive_extract.enabled': (config, value) => {
if (typeof value !== 'boolean') {return false;}
config.memory ??= {} as Config['memory'];
config.memory.proactive_extract ??= {} as Config['memory']['proactive_extract'];
config.memory.proactive_extract.enabled = value;
return true;
},
'memory.proactive_extract.min_tool_calls': (config, value) => {
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0 || value > 50) {return false;}
config.memory ??= {} as Config['memory'];
config.memory.proactive_extract ??= {} as Config['memory']['proactive_extract'];
config.memory.proactive_extract.min_tool_calls = Math.floor(value);
return true;
},
'tts.enabled': (config, value) => {
if (typeof value !== 'boolean') {return false;}
config.tts ??= {} as Config['tts'];
config.tts.enabled = value;
return true;
},
'tts.enabled_channels': (config, value) => {
if (!Array.isArray(value) || !value.every((v) => typeof v === 'string' && v.trim().length > 0)) {return false;}
config.tts ??= {} as Config['tts'];
config.tts.enabled_channels = value as string[];
return true;
},
};
export function createConfigHandlers(deps: ConfigHandlerDeps) {
+37 -2
View File
@@ -1143,13 +1143,34 @@ describe('config handlers', () => {
'server.queue.debounce_ms': 100,
'server.nodes.location.enabled': true,
'server.nodes.push.enabled': true,
'automation.delivery_mode': 'announce',
'automation.daily_briefing.enabled': true,
'memory.daily_log.enabled': true,
'memory.proactive_extract.enabled': true,
'memory.proactive_extract.min_tool_calls': 2,
'tts.enabled': true,
'tts.enabled_channels': ['telegram', 'discord'],
},
},
};
const result = await handlers['config.patch'](req) as GatewayResponse;
const r = result.result as { applied: string[]; rejected: string[]; persisted: boolean };
expect(r.applied).toEqual(['hooks.confirm', 'hooks.log', 'server.queue.mode', 'server.queue.debounce_ms', 'server.nodes.location.enabled', 'server.nodes.push.enabled']);
expect(r.applied).toEqual([
'hooks.confirm',
'hooks.log',
'server.queue.mode',
'server.queue.debounce_ms',
'server.nodes.location.enabled',
'server.nodes.push.enabled',
'automation.delivery_mode',
'automation.daily_briefing.enabled',
'memory.daily_log.enabled',
'memory.proactive_extract.enabled',
'memory.proactive_extract.min_tool_calls',
'tts.enabled',
'tts.enabled_channels',
]);
expect(r.rejected).toEqual([]);
expect(r.persisted).toBe(false);
// Verify the config was actually mutated
@@ -1159,6 +1180,13 @@ describe('config handlers', () => {
expect(config.server.queue.debounce_ms).toBe(100);
expect(config.server.nodes.location.enabled).toBe(true);
expect(config.server.nodes.push.enabled).toBe(true);
expect(getPath(config, 'automation', 'delivery_mode')).toBe('announce');
expect(getPath(config, 'automation', 'daily_briefing', 'enabled')).toBe(true);
expect(getPath(config, 'memory', 'daily_log', 'enabled')).toBe(true);
expect(getPath(config, 'memory', 'proactive_extract', 'enabled')).toBe(true);
expect(getPath(config, 'memory', 'proactive_extract', 'min_tool_calls')).toBe(2);
expect(getPath(config, 'tts', 'enabled')).toBe(true);
expect(getPath(config, 'tts', 'enabled_channels')).toEqual(['telegram', 'discord']);
});
it('config.patch rejects unknown keys', async () => {
@@ -1192,6 +1220,8 @@ describe('config handlers', () => {
patches: {
'hooks.confirm': 'not-an-array',
'server.queue.cap': 0,
'memory.proactive_extract.min_tool_calls': 99,
'tts.enabled_channels': [1, 2, 3],
},
},
};
@@ -1199,7 +1229,12 @@ describe('config handlers', () => {
const r = result.result as { applied: string[]; rejected: string[]; persisted: boolean };
expect(r.applied).toEqual([]);
expect(r.rejected).toEqual(['hooks.confirm', 'server.queue.cap']);
expect(r.rejected).toEqual([
'hooks.confirm',
'server.queue.cap',
'memory.proactive_extract.min_tool_calls',
'tts.enabled_channels',
]);
expect(r.persisted).toBe(false);
});