fix(saas): deep audit round industry template system - critical fixes
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

C1: Use backend createAgentFromTemplate API + tools forwarding
C3: seed source='builtin' instead of 'custom'
C4: immutable clone data handling (return fresh from store) + spread)
H3: assignTemplate error propagation (try/catch)
H4: input validation for name/fields
H5: assign_template account existence check
H6: remove dead route get_full_template
H7: model fallback gpt-4o-mini (hardcoded constant)
H8: logout clears template state
H9: console.warn -> structured logger
C2: restoreSession fetches assignedTemplate
This commit is contained in:
iven
2026-04-03 19:45:25 +08:00
parent 0857a1f608
commit edecd4c81f
5 changed files with 62 additions and 802 deletions

View File

@@ -7,8 +7,12 @@
import { create } from 'zustand';
import type { GatewayClient } from '../lib/gateway-client';
import type { AgentTemplateFull } from '../lib/saas-client';
import { saasClient } from '../lib/saas-client';
import { useChatStore } from './chatStore';
import { useConversationStore } from './chat/conversationStore';
import { createLogger } from '../lib/logger';
const log = createLogger('AgentStore');
// === Types ===
@@ -134,7 +138,7 @@ export const useAgentStore = create<AgentStore>((set, get) => ({
loadClones: async () => {
const client = getClient();
if (!client) {
console.warn('[AgentStore] Client not initialized, skipping loadClones');
log.warn('[AgentStore] Client not initialized, skipping loadClones');
return;
}
@@ -163,7 +167,7 @@ export const useAgentStore = create<AgentStore>((set, get) => ({
createClone: async (opts: CloneCreateOptions) => {
const client = getClient();
if (!client) {
console.warn('[AgentStore] Client not initialized');
log.warn('[AgentStore] Client not initialized');
return undefined;
}
@@ -181,76 +185,79 @@ export const useAgentStore = create<AgentStore>((set, get) => ({
createFromTemplate: async (template: AgentTemplateFull) => {
const client = getClient();
if (!client) return undefined;
if (!client) {
set({ error: 'Client not initialized' });
return undefined;
}
set({ isLoading: true, error: null });
try {
// Step 1: Call backend to get server-processed config (tools merge, model fallback)
const config = await saasClient.createAgentFromTemplate(template.id);
// Step 2: Create clone with merged data from backend
const result = await client.createClone({
name: template.name,
emoji: template.emoji,
personality: template.personality,
name: config.name,
emoji: config.emoji,
personality: config.personality,
scenarios: template.scenarios,
communicationStyle: template.communication_style,
model: template.model,
communicationStyle: config.communication_style,
model: config.model,
});
const cloneId = result?.clone?.id;
if (cloneId) {
// Persist SOUL.md via identity system
if (template.soul_content) {
if (config.soul_content) {
try {
const { intelligenceClient } = await import('../lib/intelligence-client');
await intelligenceClient.identity.updateFile(cloneId, 'soul', template.soul_content);
await intelligenceClient.identity.updateFile(cloneId, 'soul', config.soul_content);
} catch (e) {
console.warn('Failed to persist soul_content:', e);
log.warn('Failed to persist soul_content:', e);
}
}
// Persist system_prompt via identity system
if (template.system_prompt) {
if (config.system_prompt) {
try {
const { intelligenceClient } = await import('../lib/intelligence-client');
await intelligenceClient.identity.updateFile(cloneId, 'system', template.system_prompt);
await intelligenceClient.identity.updateFile(cloneId, 'system', config.system_prompt);
} catch (e) {
console.warn('Failed to persist system_prompt:', e);
log.warn('Failed to persist system_prompt:', e);
}
}
// Persist temperature / max_tokens if supported
if (template.temperature != null || template.max_tokens != null) {
try {
await client.updateClone(cloneId, {
temperature: template.temperature,
maxTokens: template.max_tokens,
});
} catch (e) {
console.warn('Failed to persist temperature/max_tokens:', e);
}
}
// Persist temperature / max_tokens / tools / source_template_id / welcomeMessage / quickCommands
const metadata: Record<string, unknown> = {};
if (config.temperature != null) metadata.temperature = config.temperature;
if (config.max_tokens != null) metadata.maxTokens = config.max_tokens;
if (config.tools?.length) metadata.tools = config.tools;
metadata.source_template_id = template.id;
if (config.welcome_message) metadata.welcomeMessage = config.welcome_message;
if (config.quick_commands?.length) metadata.quickCommands = config.quick_commands;
// Persist welcome_message + quick_commands as clone metadata
if (template.welcome_message || (template.quick_commands && template.quick_commands.length > 0)) {
if (Object.keys(metadata).length > 0) {
try {
await client.updateClone(cloneId, {
welcomeMessage: template.welcome_message || '',
quickCommands: template.quick_commands || [],
});
await client.updateClone(cloneId, metadata);
} catch (e) {
console.warn('Failed to persist welcome_message/quick_commands:', e);
log.warn('Failed to persist clone metadata:', e);
}
}
}
await get().loadClones();
// Merge template welcome/quick data into the returned clone for immediate use
const createdClone = result?.clone as Record<string, unknown> | undefined;
if (createdClone) {
if (template.welcome_message) createdClone.welcomeMessage = template.welcome_message;
if (template.quick_commands?.length) createdClone.quickCommands = template.quick_commands;
// Return a fresh clone from the store (immutable — no in-place mutation)
const freshClone = get().clones.find((c) => c.id === cloneId);
if (freshClone) {
return {
...freshClone,
...(config.welcome_message ? { welcomeMessage: config.welcome_message } : {}),
...(config.quick_commands?.length ? { quickCommands: config.quick_commands } : {}),
};
}
return createdClone as Clone | undefined;
return result?.clone as Clone | undefined;
} catch (error) {
set({ error: String(error) });
return undefined;
@@ -262,7 +269,7 @@ export const useAgentStore = create<AgentStore>((set, get) => ({
updateClone: async (id: string, updates: Partial<Clone>) => {
const client = getClient();
if (!client) {
console.warn('[AgentStore] Client not initialized');
log.warn('[AgentStore] Client not initialized');
return undefined;
}
@@ -281,7 +288,7 @@ export const useAgentStore = create<AgentStore>((set, get) => ({
deleteClone: async (id: string) => {
const client = getClient();
if (!client) {
console.warn('[AgentStore] Client not initialized');
log.warn('[AgentStore] Client not initialized');
return;
}
@@ -325,7 +332,7 @@ export const useAgentStore = create<AgentStore>((set, get) => ({
loadPluginStatus: async () => {
const client = getClient();
if (!client) {
console.warn('[AgentStore] Client not initialized, skipping loadPluginStatus');
log.warn('[AgentStore] Client not initialized, skipping loadPluginStatus');
return;
}

View File

@@ -397,8 +397,10 @@ export const useSaaSStore = create<SaaSStore>((set, get) => {
authToken: null,
connectionMode: 'tauri',
availableModels: [],
availableTemplates: [],
assignedTemplate: null,
error: null,
totpRequired: false,
toTopRequired: false,
totpSetupData: null,
});
},
@@ -720,6 +722,8 @@ export const useSaaSStore = create<SaaSStore>((set, get) => {
connectionMode: loadConnectionMode() === 'saas' ? 'saas' : 'tauri',
});
get().fetchAvailableModels().catch(() => {});
get().fetchAvailableTemplates().catch(() => {});
get().fetchAssignedTemplate().catch(() => {});
get().syncConfigFromSaaS().then(() => {
get().pushConfigToSaaS().catch(() => {});
}).catch(() => {});