Compare commits

...

3 Commits

Author SHA1 Message Date
iven
f7edc59abb fix(auth): 修复重启后无法对话 — restoreSession 优先验证 SaaS token
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
根因: 心跳降级将 'tauri' 持久化到 localStorage,重启后盲信该值。
修复: token refresh 成功时强制恢复 'saas' 模式;connectionMode 携带时间戳。
2026-04-11 12:32:20 +08:00
iven
be01127098 fix(autonomy): hand_trigger 从 null 映射改为 handAutoTrigger 字段
根因: autonomy-manager.ts:268 将 hand_trigger 硬编码为 null,
导致任何自主权级别都无法自动触发 Hand。
新增 handAutoTrigger 字段,autonomous 级别默认 true。
UI 增加对应开关。
2026-04-11 12:32:19 +08:00
iven
33c1bd3866 fix(memory): memory_search 空查询时默认 min_similarity=0.0 触发表扫描
根因: FTS5 空查询返回 0 条,而 memory_stats 因设 min_similarity=Some(0.0)
走表扫描才正确计数。统一空查询行为。
2026-04-11 12:32:18 +08:00
5 changed files with 55 additions and 6 deletions

View File

@@ -158,10 +158,18 @@ pub async fn memory_search(
// Build search query
let query = options.query.unwrap_or_default();
// When query is empty, use min_similarity=0.0 to trigger table scan
// (FTS5 requires non-empty query; without this, empty query returns 0 results)
let min_similarity = if query.trim().is_empty() {
Some(0.0)
} else {
options.min_importance.map(|i| (i as f32) / 10.0)
};
let find_options = zclaw_growth::FindOptions {
scope,
limit: options.limit.or(Some(50)),
min_similarity: options.min_importance.map(|i| (i as f32) / 10.0),
min_similarity,
};
let entries = zclaw_growth::VikingStorage::find(storage.as_ref(), &query, find_options).await

View File

@@ -381,6 +381,15 @@ export function AutonomyConfig({ className = '', onConfigChange }: AutonomyConfi
})
}
/>
<ActionToggle
label="自动触发 Hand 能力"
enabled={config.allowedActions.handAutoTrigger}
onChange={(enabled) =>
updateConfig({
allowedActions: { ...config.allowedActions, handAutoTrigger: enabled },
})
}
/>
</div>
</div>

View File

@@ -48,6 +48,7 @@ export interface AutonomyConfig {
selfModification: boolean;
autoCompaction: boolean;
autoReflection: boolean;
handAutoTrigger: boolean;
};
approvalThreshold: {
importanceMax: number; // Auto-approve if importance <= this (default: 5)
@@ -112,6 +113,7 @@ export const DEFAULT_AUTONOMY_CONFIGS: Record<AutonomyLevel, AutonomyConfig> = {
selfModification: false,
autoCompaction: false,
autoReflection: false,
handAutoTrigger: false,
},
approvalThreshold: {
importanceMax: 0,
@@ -129,6 +131,7 @@ export const DEFAULT_AUTONOMY_CONFIGS: Record<AutonomyLevel, AutonomyConfig> = {
selfModification: false,
autoCompaction: true,
autoReflection: true,
handAutoTrigger: false,
},
approvalThreshold: {
importanceMax: 5,
@@ -146,6 +149,7 @@ export const DEFAULT_AUTONOMY_CONFIGS: Record<AutonomyLevel, AutonomyConfig> = {
selfModification: false, // Always require approval for self-modification
autoCompaction: true,
autoReflection: true,
handAutoTrigger: true,
},
approvalThreshold: {
importanceMax: 7,
@@ -265,7 +269,7 @@ export class AutonomyManager {
skill_uninstall: 'skillAutoInstall',
config_change: null,
workflow_trigger: 'autoCompaction',
hand_trigger: null,
hand_trigger: 'handAutoTrigger',
llm_call: 'autoReflection',
reflection_run: 'autoReflection',
compaction_run: 'autoCompaction',

View File

@@ -157,16 +157,41 @@ export async function clearSaaSSession(): Promise<void> {
}
/**
* Persist the connection mode to localStorage.
* Persist the connection mode to localStorage with timestamp.
*/
export function saveConnectionMode(mode: string): void {
localStorage.setItem(SAASMODE_KEY, mode);
const data = JSON.stringify({ mode, timestamp: Date.now() });
localStorage.setItem(SAASMODE_KEY, data);
}
/**
* Load the connection mode from localStorage.
* Handles both new JSON format and legacy plain string format.
* Returns null if not set.
*/
export function loadConnectionMode(): string | null {
return localStorage.getItem(SAASMODE_KEY);
const raw = localStorage.getItem(SAASMODE_KEY);
if (!raw) return null;
try {
const parsed = JSON.parse(raw);
if (typeof parsed === 'string') return parsed; // legacy format
return parsed.mode ?? null;
} catch {
return raw; // legacy format (plain string)
}
}
/**
* Load the timestamp of the persisted connection mode.
* Returns null if not set or format is legacy.
*/
export function loadConnectionModeTimestamp(): number | null {
const raw = localStorage.getItem(SAASMODE_KEY);
if (!raw) return null;
try {
const parsed = JSON.parse(raw);
return parsed.timestamp ?? null;
} catch {
return null;
}
}

View File

@@ -818,8 +818,11 @@ export const useSaaSStore = create<SaaSStore>((set, get) => {
account,
saasUrl: restored.saasUrl,
authToken: newToken,
connectionMode: loadConnectionMode() === 'saas' ? 'saas' : 'tauri',
// If token refresh succeeded, always restore to 'saas' mode
// regardless of what was persisted (heartbeat may have degraded to 'tauri')
connectionMode: 'saas',
});
saveConnectionMode('saas');
get().fetchAvailableModels().catch(() => {});
get().fetchAvailableTemplates().catch(() => {});
get().fetchAssignedTemplate().catch(() => {});