feat: implement model persistence with per-session overrides
- Add session_config SQLite table for per-session settings - Update routing to support session override → agent config → global default resolution chain - Upgrade WebChat SessionBridge from NativeAgent to AgentOrchestrator - Add /model, /local, /cloud commands to Telegram adapter - Add /model command to WebChat gateway handlers - Clear session overrides on /reset command - Pass memoryStore and config through to SessionBridge - Add comprehensive tests for all new functionality Fixes model persistence bug where TUI model changes didn't affect WebChat/Telegram sessions. Now: - TUI /model sets global default (persists across restarts, affects all new sessions) - WebChat/Telegram /model sets session override (only that conversation, cleared on /reset) - WebChat sessions gain AgentOrchestrator features (delegation, compaction, memory)
This commit is contained in:
+1
-1
@@ -120,7 +120,7 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
|
||||
|
||||
const gateway = createGateway({
|
||||
config, sessionManager, modelRouter, systemPrompt, toolRegistry, toolExecutor,
|
||||
channelRegistry, pairingManager, lifecycle,
|
||||
channelRegistry, pairingManager, lifecycle, memoryStore,
|
||||
getChannelAgents: () => channelAgents,
|
||||
});
|
||||
|
||||
|
||||
+55
-1
@@ -58,9 +58,18 @@ export function createMessageRouter(deps: {
|
||||
if (!entry) {
|
||||
const session = deps.sessionManager.getSession(channel, senderId);
|
||||
|
||||
// Read per-session model tier override (persisted in SQLite)
|
||||
const sessionTierOverride = session.getConfig('modelTier') as ModelTier | undefined;
|
||||
|
||||
// Resolution chain: metadata (cron) → session override → agent config → global default
|
||||
const effectiveTier = tierFromMetadata
|
||||
?? sessionTierOverride
|
||||
?? agentConfig?.modelTier
|
||||
?? deps.config.agents.primary_tier
|
||||
?? 'default';
|
||||
|
||||
// Use agent config overrides where available, falling back to global config
|
||||
const effectiveSystemPrompt = agentConfig?.systemPrompt ?? deps.systemPrompt;
|
||||
const effectiveTier = tierFromMetadata ?? agentConfig?.modelTier ?? deps.config.agents.primary_tier ?? 'default';
|
||||
const effectiveProvider = deps.config.models.default.provider;
|
||||
|
||||
const delegationConfig: DelegationConfig = {
|
||||
@@ -166,6 +175,46 @@ export function createMessageRouter(deps: {
|
||||
if (msg.metadata?.isCommand) {
|
||||
if (msg.metadata.command === 'reset') {
|
||||
agent.reset();
|
||||
// Clear per-session config overrides
|
||||
const session = deps.sessionManager.getSession(msg.channel, msg.senderId);
|
||||
session.deleteConfig('modelTier');
|
||||
return;
|
||||
}
|
||||
if (msg.metadata.command === 'model') {
|
||||
const modelArg = msg.metadata.commandArgs as string | undefined;
|
||||
const session = deps.sessionManager.getSession(msg.channel, msg.senderId);
|
||||
|
||||
if (!modelArg) {
|
||||
// Show current model tier
|
||||
const currentTier = agent.getModelTier();
|
||||
const sessionOverride = session.getConfig('modelTier');
|
||||
const available = deps.modelRouter.getAvailableTiers();
|
||||
const labels = deps.modelRouter.getAllLabels();
|
||||
const lines = [`Active tier: ${currentTier}${sessionOverride ? ' (session override)' : ''}`];
|
||||
for (const tier of available) {
|
||||
const label = labels[tier] ?? 'unknown';
|
||||
const marker = tier === currentTier ? ' ←' : '';
|
||||
lines.push(` ${tier}: ${label}${marker}`);
|
||||
}
|
||||
await reply({ text: lines.join('\n'), replyTo: msg.id });
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate tier
|
||||
const validTiers = deps.modelRouter.getAvailableTiers();
|
||||
if (!validTiers.includes(modelArg as ModelTier)) {
|
||||
await reply({ text: `Model tier not available: ${modelArg}`, replyTo: msg.id });
|
||||
return;
|
||||
}
|
||||
|
||||
// Persist to session config
|
||||
session.setConfig('modelTier', modelArg);
|
||||
|
||||
// Update the orchestrator's agent tier
|
||||
agent.setModelTier(modelArg as ModelTier);
|
||||
|
||||
const label = deps.modelRouter.getLabel(modelArg as ModelTier);
|
||||
await reply({ text: `Switched to model: ${modelArg} (${label})`, replyTo: msg.id });
|
||||
return;
|
||||
}
|
||||
if (msg.metadata.command === 'compact') {
|
||||
@@ -215,8 +264,13 @@ export function createMessageRouter(deps: {
|
||||
try {
|
||||
// Determine if the active model supports native audio input
|
||||
let effectiveTier: string = deps.config.agents.primary_tier ?? 'default';
|
||||
const session = deps.sessionManager.getSession(msg.channel, msg.senderId);
|
||||
const sessionTierOverride = session.getConfig('modelTier');
|
||||
|
||||
if (msg.metadata?.modelTier) {
|
||||
effectiveTier = msg.metadata.modelTier as string;
|
||||
} else if (sessionTierOverride) {
|
||||
effectiveTier = sessionTierOverride;
|
||||
} else if (deps.agentRouter && deps.agentConfigRegistry) {
|
||||
const agentName = deps.agentRouter.resolve(msg.channel, msg.senderId);
|
||||
if (agentName) {
|
||||
|
||||
@@ -13,6 +13,7 @@ import { SkillRegistry, SkillInstaller, loadAllSkills } from '../skills/index.js
|
||||
import { assembleSystemPrompt } from '../prompt/index.js';
|
||||
import { resolve } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import type { MemoryStore } from '../memory/store.js';
|
||||
|
||||
// ── Skills ──────────────────────────────────────────────────────
|
||||
|
||||
@@ -121,6 +122,7 @@ export interface GatewayDeps {
|
||||
pairingManager?: PairingManager;
|
||||
lifecycle: Lifecycle;
|
||||
getChannelAgents: () => Map<string, { orchestrator: AgentOrchestrator; collector: OutboundAttachmentCollector }> | null;
|
||||
memoryStore?: MemoryStore;
|
||||
}
|
||||
|
||||
export function createGateway(deps: GatewayDeps): GatewayServer {
|
||||
@@ -144,6 +146,7 @@ export function createGateway(deps: GatewayDeps): GatewayServer {
|
||||
config,
|
||||
channelRegistry,
|
||||
pairingManager,
|
||||
memoryStore: deps.memoryStore,
|
||||
restart: async () => {
|
||||
console.log('Restart requested via gateway');
|
||||
await lifecycle.shutdown();
|
||||
|
||||
Reference in New Issue
Block a user