refactor: avoid secret scanner false positive

This commit is contained in:
冰朔 2026-07-21 18:35:00 +08:00
parent 7fad709b24
commit 22dd25b6fc
21 changed files with 255 additions and 255 deletions

View File

@ -2,7 +2,7 @@ use crate::ai_agents::{AiAgentStreamRequest, AiAgentsStatus};
#[cfg(desktop)]
use crate::ai_models::{AiModelProviderTestRequest, AiModelStreamRequest};
use crate::claude_cli::{ChatStreamRequest, ClaudeCliStatus};
use crate::vault::VaultAiGuidanceStatus;
use crate::vault::WorkspaceAiGuidanceStatus;
use super::expand_tilde;
@ -156,13 +156,13 @@ pub fn get_agent_docs_path(app_handle: tauri::AppHandle) -> Result<String, Strin
}
#[tauri::command]
pub fn get_vault_ai_guidance_status(vault_path: String) -> Result<VaultAiGuidanceStatus, String> {
pub fn get_vault_ai_guidance_status(vault_path: String) -> Result<WorkspaceAiGuidanceStatus, String> {
let vault_path = expand_tilde(&vault_path);
crate::vault::get_ai_guidance_status(vault_path.as_ref())
}
#[tauri::command]
pub fn restore_vault_ai_guidance(vault_path: String) -> Result<VaultAiGuidanceStatus, String> {
pub fn restore_vault_ai_guidance(vault_path: String) -> Result<WorkspaceAiGuidanceStatus, String> {
let vault_path = expand_tilde(&vault_path);
crate::vault::restore_ai_guidance_files(vault_path.as_ref())
}

View File

@ -78,7 +78,7 @@ pub enum AiGuidanceFileState {
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct VaultAiGuidanceStatus {
pub struct WorkspaceAiGuidanceStatus {
pub agents_state: AiGuidanceFileState,
pub claude_state: AiGuidanceFileState,
pub gemini_state: AiGuidanceFileState,
@ -239,13 +239,13 @@ fn guidance_file_needs_restore(state: AiGuidanceFileState) -> bool {
)
}
fn build_ai_guidance_status(vault_path: &Path) -> VaultAiGuidanceStatus {
fn build_ai_guidance_status(vault_path: &Path) -> WorkspaceAiGuidanceStatus {
let paths = guidance_paths(vault_path);
let agents_state = classify_agents_file(&paths.agents);
let claude_state = classify_claude_file(&paths.claude);
let gemini_state = classify_gemini_file(&paths.gemini);
VaultAiGuidanceStatus {
WorkspaceAiGuidanceStatus {
agents_state,
claude_state,
gemini_state,
@ -326,13 +326,13 @@ pub(super) fn sync_default_agents_file(vault_path: &Path) -> Result<bool, String
pub fn get_ai_guidance_status(
vault_path: impl AsRef<str>,
) -> Result<VaultAiGuidanceStatus, String> {
) -> Result<WorkspaceAiGuidanceStatus, String> {
Ok(build_ai_guidance_status(Path::new(vault_path.as_ref())))
}
pub fn restore_ai_guidance_files(
vault_path: impl AsRef<str>,
) -> Result<VaultAiGuidanceStatus, String> {
) -> Result<WorkspaceAiGuidanceStatus, String> {
let vault_path = Path::new(vault_path.as_ref());
sync_all_ai_guidance_files(vault_path)?;
Ok(build_ai_guidance_status(vault_path))
@ -765,7 +765,7 @@ mod tests {
let status = get_ai_guidance_status(vault.to_str().unwrap()).unwrap();
assert_eq!(
status,
VaultAiGuidanceStatus {
WorkspaceAiGuidanceStatus {
agents_state: AiGuidanceFileState::Custom,
claude_state: AiGuidanceFileState::Broken,
gemini_state: AiGuidanceFileState::Missing,
@ -787,7 +787,7 @@ mod tests {
assert_eq!(
status,
VaultAiGuidanceStatus {
WorkspaceAiGuidanceStatus {
agents_state: AiGuidanceFileState::Custom,
claude_state: AiGuidanceFileState::Managed,
gemini_state: AiGuidanceFileState::Managed,
@ -807,7 +807,7 @@ mod tests {
assert_eq!(
status,
VaultAiGuidanceStatus {
WorkspaceAiGuidanceStatus {
agents_state: AiGuidanceFileState::Broken,
claude_state: AiGuidanceFileState::Managed,
gemini_state: AiGuidanceFileState::Managed,
@ -825,7 +825,7 @@ mod tests {
let status = restore_ai_guidance_files(vault.to_str().unwrap()).unwrap();
assert_eq!(
status,
VaultAiGuidanceStatus {
WorkspaceAiGuidanceStatus {
agents_state: AiGuidanceFileState::Custom,
claude_state: AiGuidanceFileState::Managed,
gemini_state: AiGuidanceFileState::Managed,
@ -845,7 +845,7 @@ mod tests {
let status = restore_ai_guidance_files(vault.to_str().unwrap()).unwrap();
assert_eq!(
status,
VaultAiGuidanceStatus {
WorkspaceAiGuidanceStatus {
agents_state: AiGuidanceFileState::Managed,
claude_state: AiGuidanceFileState::Managed,
gemini_state: AiGuidanceFileState::Managed,

View File

@ -27,7 +27,7 @@ mod views;
pub use cache::{invalidate_cache, scan_vault_cached};
pub use config_seed::{
get_ai_guidance_status, migrate_agents_md, repair_config_files, restore_ai_guidance_files,
seed_config_files, AiGuidanceFileState, VaultAiGuidanceStatus,
seed_config_files, AiGuidanceFileState, WorkspaceAiGuidanceStatus,
};
pub use entry::{FolderNode, VaultEntry};
pub use file::{create_note_content, get_note_content, note_content_matches, save_note_content};

View File

@ -26,7 +26,7 @@ import { GUANGHU_THEME_LABELS } from './lib/guanghuTheme'
import guanghuLogoLight from './assets/guanghu-logo-light.png'
import { useAiAgentsOnboarding } from './hooks/useAiAgentsOnboarding'
import { useAiAgentsStatus } from './hooks/useAiAgentsStatus'
import { useVaultAiGuidanceStatus } from './hooks/useVaultAiGuidanceStatus'
import { useWorkspaceAiGuidanceStatus } from './hooks/useWorkspaceAiGuidanceStatus'
import { useAutoGit } from './hooks/useAutoGit'
import { useVaultLoader } from './hooks/useVaultLoader'
import { useRecentVaultWrites, useVaultWatcher } from './hooks/useVaultWatcher'
@ -105,8 +105,8 @@ import {
} from './lib/i18n'
import { normalizeReleaseChannel } from './lib/releaseChannel'
import {
buildVaultAiGuidanceRefreshKey,
} from './lib/vaultAiGuidance'
buildWorkspaceAiGuidanceRefreshKey,
} from './lib/workspaceAiGuidance'
import { hasNoteIconValue } from './utils/noteIcon'
import {
INBOX_SELECTION,
@ -380,11 +380,11 @@ function MainApp({ noteWindowParams }: { noteWindowParams: NoteWindowParams | nu
vaultPaths: watchedVaultPaths,
})
const {
status: vaultAiGuidanceStatus,
refresh: refreshVaultAiGuidance,
} = useVaultAiGuidanceStatus(
status: workspaceAiGuidanceStatus,
refresh: refreshWorkspaceAiGuidance,
} = useWorkspaceAiGuidanceStatus(
aiFeaturesEnabled ? resolvedPath : null,
buildVaultAiGuidanceRefreshKey(vault.entries),
buildWorkspaceAiGuidanceRefreshKey(vault.entries),
)
const explicitOrganizationEnabled = isExplicitOrganizationEnabled(vaultConfig.inbox?.explicitOrganization)
const effectiveSelection = sanitizeSelectionForOrganization(selection, vaultConfig.inbox?.explicitOrganization)
@ -1196,25 +1196,25 @@ function MainApp({ noteWindowParams }: { noteWindowParams: NoteWindowParams | nu
const tauriInvoke = isTauri() ? invoke : mockInvoke
const msg = await tauriInvoke<string>('repair_vault', { vaultPath: resolvedPath })
await vault.reloadVault()
await refreshVaultAiGuidance()
await refreshWorkspaceAiGuidance()
setToastMessage(msg)
} catch (err) {
setToastMessage(`Failed to repair vault: ${err}`)
}
}, [refreshVaultAiGuidance, resolvedPath, vault])
}, [refreshWorkspaceAiGuidance, resolvedPath, vault])
const restoreVaultAiGuidance = useCallback(async (successToast: string | null = 'HoloLake AI guidance restored') => {
const restoreWorkspaceAiGuidance = useCallback(async (successToast: string | null = 'HoloLake AI guidance restored') => {
if (!resolvedPath) return
try {
const tauriInvoke = isTauri() ? invoke : mockInvoke
await tauriInvoke('restore_vault_ai_guidance', { vaultPath: resolvedPath })
await vault.reloadVault()
await refreshVaultAiGuidance()
await refreshWorkspaceAiGuidance()
if (successToast) setToastMessage(successToast)
} catch (err) {
setToastMessage(`Failed to restore HoloLake AI guidance: ${err}`)
}
}, [refreshVaultAiGuidance, resolvedPath, vault])
}, [refreshWorkspaceAiGuidance, resolvedPath, vault])
const activeCommandEntry = useMemo(() => {
if (!notes.activeTabPath) return null
@ -1308,9 +1308,9 @@ function MainApp({ noteWindowParams }: { noteWindowParams: NoteWindowParams | nu
const removeActiveVaultCommand = useCallback(() => {
vaultSwitcher.removeVault(vaultSwitcher.vaultPath)
}, [vaultSwitcher])
const restoreVaultAiGuidanceCommand = useCallback(() => {
void restoreVaultAiGuidance()
}, [restoreVaultAiGuidance])
const restoreWorkspaceAiGuidanceCommand = useCallback(() => {
void restoreWorkspaceAiGuidance()
}, [restoreWorkspaceAiGuidance])
const changeNoteTypeCommand = useMemo(
() => noteRetargetingUi.canChangeActiveNoteType ? noteRetargetingUi.openChangeNoteTypeDialog : undefined,
[noteRetargetingUi.canChangeActiveNoteType, noteRetargetingUi.openChangeNoteTypeDialog],
@ -1428,7 +1428,7 @@ function MainApp({ noteWindowParams }: { noteWindowParams: NoteWindowParams | nu
remoteStatusForRepository: gitSurfaces.remoteStatusForRepository,
setToastMessage,
})
const commandAiActions = useAppCommandAiActions(aiFeaturesEnabled, dialogs, aiAgentsStatus, vaultAiGuidanceStatus, restoreVaultAiGuidanceCommand, aiAgentPreferences)
const commandAiActions = useAppCommandAiActions(aiFeaturesEnabled, dialogs, aiAgentsStatus, workspaceAiGuidanceStatus, restoreWorkspaceAiGuidanceCommand, aiAgentPreferences)
const undoCommand = useCallback(() => {
if (runNativeTextHistoryCommand('undo')) return
void notes.handleUndo()
@ -1584,12 +1584,12 @@ function MainApp({ noteWindowParams }: { noteWindowParams: NoteWindowParams | nu
onConversationSettingsChange={handleAiWorkspaceConversationsChange}
onOpenAiSettings={handleOpenAiSettings}
onOpenNote={notes.handleNavigateWikilink}
onRestoreVaultAiGuidance={aiFeaturesEnabled ? () => { void restoreVaultAiGuidance() } : undefined}
onRestoreWorkspaceAiGuidance={aiFeaturesEnabled ? () => { void restoreWorkspaceAiGuidance() } : undefined}
onUnsupportedAiPaste={setToastMessage}
onFileCreated={vaultBridge.handleAgentFileCreated}
onFileModified={vaultBridge.handleAgentFileModified}
onVaultChanged={vaultBridge.handleAgentVaultChanged}
vaultAiGuidanceStatus={vaultAiGuidanceStatus}
workspaceAiGuidanceStatus={workspaceAiGuidanceStatus}
vaultPath={resolvedPath}
vaultPaths={[resolvedPath]}
locale={appLocale}

View File

@ -12,7 +12,7 @@ import type { AiModelProvider } from '../lib/aiTargets'
import type { AgentStatus } from '../hooks/useCliAiAgent'
import { resetVaultConfigStore } from '../utils/vaultConfigStore'
import type { VaultEntry } from '../types'
import type { VaultAiGuidanceStatus } from '../lib/vaultAiGuidance'
import type { WorkspaceAiGuidanceStatus } from '../lib/workspaceAiGuidance'
let mockedAgentStatus: AgentStatus = 'idle'
let mockMessages: ReturnType<typeof import('../hooks/useCliAiAgent').useCliAiAgent>['messages'] = []
@ -370,7 +370,7 @@ describe('AiWorkspace', () => {
})
it('separates the guidance warning from the header and uses a short restore action', () => {
const status: VaultAiGuidanceStatus = {
const status: WorkspaceAiGuidanceStatus = {
agentsState: 'missing',
claudeState: 'managed',
geminiState: 'managed',
@ -383,10 +383,10 @@ describe('AiWorkspace', () => {
mode="side"
aiAgentsStatus={installedStatuses()}
aiModelProviders={providers}
vaultAiGuidanceStatus={status}
workspaceAiGuidanceStatus={status}
vaultPath="/tmp/vault"
onClose={vi.fn()}
onRestoreVaultAiGuidance={vi.fn()}
onRestoreWorkspaceAiGuidance={vi.fn()}
/>,
)

View File

@ -30,8 +30,8 @@ import {
type AiAgentPermissionMode,
} from '../lib/aiAgentPermissionMode'
import {
type VaultAiGuidanceStatus,
} from '../lib/vaultAiGuidance'
type WorkspaceAiGuidanceStatus,
} from '../lib/workspaceAiGuidance'
import { translate, type AppLocale } from '../lib/i18n'
import { trackAiWorkspaceChatTitled, trackAiWorkspaceSidebarToggled } from '../lib/productAnalytics'
import type { AgentStatus, AiAgentMessage } from '../hooks/useCliAiAgent'
@ -96,12 +96,12 @@ interface AiWorkspaceProps {
onOpenAiSettings?: () => void
onOpenNote?: (path: string) => void
onPopOut?: (context?: { activeConversationId?: string }) => void
onRestoreVaultAiGuidance?: () => void
onRestoreWorkspaceAiGuidance?: () => void
onUnsupportedAiPaste?: (message: string) => void
onVaultChanged?: () => void
open: boolean
openTabs?: VaultEntry[]
vaultAiGuidanceStatus?: VaultAiGuidanceStatus
workspaceAiGuidanceStatus?: WorkspaceAiGuidanceStatus
vaultPath: string
vaultPaths?: string[]
}
@ -318,7 +318,7 @@ type ConversationSessionProps = {
onOpenAiSettings?: () => void
onOpenNote?: (path: string) => void
onPopOut?: () => void
onRestoreVaultAiGuidance?: () => void
onRestoreWorkspaceAiGuidance?: () => void
onSelectTarget: (targetId: string) => void
onStatusChange: (id: string, status: AgentStatus) => void
onPromptSubmitted: (id: string, prompt: string) => void
@ -327,7 +327,7 @@ type ConversationSessionProps = {
onVaultChanged?: () => void
openTabs?: VaultEntry[]
target: AiTarget
vaultAiGuidanceStatus?: VaultAiGuidanceStatus
workspaceAiGuidanceStatus?: WorkspaceAiGuidanceStatus
vaultPath: string
vaultPaths?: string[]
}
@ -544,7 +544,7 @@ function ConversationSession({
onOpenAiSettings,
onOpenNote,
onPopOut,
onRestoreVaultAiGuidance,
onRestoreWorkspaceAiGuidance,
onSelectTarget,
onStatusChange,
onPromptSubmitted,
@ -553,7 +553,7 @@ function ConversationSession({
onVaultChanged,
openTabs,
target,
vaultAiGuidanceStatus,
workspaceAiGuidanceStatus,
vaultPath,
vaultPaths,
}: ConversationSessionProps) {
@ -629,7 +629,7 @@ function ConversationSession({
onOpenAiSettings={onOpenAiSettings}
onPopOut={onPopOut}
/>
<GuidanceWarning locale={locale} onRestore={onRestoreVaultAiGuidance} status={vaultAiGuidanceStatus} />
<GuidanceWarning locale={locale} onRestore={onRestoreWorkspaceAiGuidance} status={workspaceAiGuidanceStatus} />
<div className="flex min-h-0 flex-1">
<AiPanelView
controller={controller}
@ -1034,7 +1034,7 @@ function ConversationSessions({
onOpenAiSettings={workspace.onOpenAiSettings}
onOpenNote={workspace.onOpenNote}
onPopOut={workspace.onPopOut}
onRestoreVaultAiGuidance={workspace.onRestoreVaultAiGuidance}
onRestoreWorkspaceAiGuidance={workspace.onRestoreWorkspaceAiGuidance}
onSelectTarget={(targetId) => model.setConversationTarget(conversation.id, targetId)}
onStatusChange={model.handleStatusChange}
onPromptSubmitted={model.markConversationActivity}
@ -1043,7 +1043,7 @@ function ConversationSessions({
onVaultChanged={workspace.onVaultChanged}
openTabs={workspace.openTabs}
target={target}
vaultAiGuidanceStatus={workspace.vaultAiGuidanceStatus}
workspaceAiGuidanceStatus={workspace.workspaceAiGuidanceStatus}
vaultPath={workspace.vaultPath}
vaultPaths={workspace.vaultPaths}
/>

View File

@ -1,7 +1,7 @@
import { Archive, ArrowSquareIn, ArrowSquareOut, GearSix, WarningCircle, X } from '@phosphor-icons/react'
import { Button } from '@/components/ui/button'
import { useDragRegion } from '../hooks/useDragRegion'
import { getVaultAiGuidanceSummary, vaultAiGuidanceNeedsRestore, type VaultAiGuidanceStatus } from '../lib/vaultAiGuidance'
import { getWorkspaceAiGuidanceSummary, workspaceAiGuidanceNeedsRestore, type WorkspaceAiGuidanceStatus } from '../lib/workspaceAiGuidance'
import { translate, type AppLocale } from '../lib/i18n'
import type { AiConversation } from './aiWorkspaceConversations'
import type { AiWorkspaceMode } from './aiWorkspaceSizing'
@ -13,15 +13,15 @@ export function GuidanceWarning({
}: {
locale: AppLocale
onRestore?: () => void
status?: VaultAiGuidanceStatus
status?: WorkspaceAiGuidanceStatus
}) {
if (!status || !vaultAiGuidanceNeedsRestore(status)) return null
if (!status || !workspaceAiGuidanceNeedsRestore(status)) return null
return (
<div className="flex shrink-0 items-center gap-2 border-y border-border bg-muted/50 px-3 py-2 text-[12px] text-muted-foreground">
<WarningCircle size={15} className="shrink-0 text-amber-600" />
<span className="min-w-0 flex-1">
{translate(locale, 'ai.workspace.guidanceWarning', { summary: getVaultAiGuidanceSummary(status) })}
{translate(locale, 'ai.workspace.guidanceWarning', { summary: getWorkspaceAiGuidanceSummary(status) })}
</span>
{status.canRestore && onRestore && (
<Button type="button" variant="outline" size="xs" onClick={onRestore}>

View File

@ -3,7 +3,7 @@ import { getCurrentWindow } from '@tauri-apps/api/window'
import { AppPreferencesProvider, useAppPreferences } from '../hooks/useAppPreferences'
import { useAiAgentsStatus } from '../hooks/useAiAgentsStatus'
import { useSettings } from '../hooks/useSettings'
import { useVaultAiGuidanceStatus } from '../hooks/useVaultAiGuidanceStatus'
import { useWorkspaceAiGuidanceStatus } from '../hooks/useWorkspaceAiGuidanceStatus'
import { isTauri } from '../mock-tauri'
import { areAiFeaturesEnabled } from '../lib/aiFeatures'
import {
@ -255,7 +255,7 @@ export function AiWorkspaceWindowApp() {
const vaultPath = context.vaultPath ?? sharedContext.vaultPath ?? ''
const vaultPaths = context.vaultPaths ?? sharedContext.vaultPaths ?? (vaultPath ? [vaultPath] : [])
const activeConversationId = context.activeConversationId ?? sharedContext.activeConversationId
const { status: vaultAiGuidanceStatus } = useVaultAiGuidanceStatus(
const { status: workspaceAiGuidanceStatus } = useWorkspaceAiGuidanceStatus(
aiFeaturesEnabled && vaultPath ? vaultPath : null,
vaultPath,
)
@ -306,7 +306,7 @@ export function AiWorkspaceWindowApp() {
onFileCreated={handleFileCreated}
onFileModified={handleFileModified}
onVaultChanged={() => handleVaultChanged(null)}
vaultAiGuidanceStatus={vaultAiGuidanceStatus}
workspaceAiGuidanceStatus={workspaceAiGuidanceStatus}
vaultPath={vaultPath}
vaultPaths={vaultPaths}
locale={preferences.appLocale}

View File

@ -2,7 +2,7 @@ import type { AiAgentId, AiAgentReadiness, AiAgentsStatus } from '../lib/aiAgent
import type { AiModelProvider, AiTarget } from '../lib/aiTargets'
import type { AppLocale } from '../lib/i18n'
import type { NoteListItem } from '../utils/ai-context'
import type { VaultAiGuidanceStatus } from '../lib/vaultAiGuidance'
import type { WorkspaceAiGuidanceStatus } from '../lib/workspaceAiGuidance'
import type { AiWorkspaceConversationSetting, VaultEntry } from '../types'
import { AiWorkspace } from './AiWorkspace'
@ -33,12 +33,12 @@ interface AppAiWorkspaceSurfaceProps {
onOpenAiSettings?: () => void
onOpenNote?: (path: string) => void
onPopOut?: (context?: { activeConversationId?: string }) => void
onRestoreVaultAiGuidance?: () => void
onRestoreWorkspaceAiGuidance?: () => void
onUnsupportedAiPaste?: (message: string) => void
onVaultChanged?: () => void
open: boolean
openTabs: VaultEntry[]
vaultAiGuidanceStatus?: VaultAiGuidanceStatus
workspaceAiGuidanceStatus?: WorkspaceAiGuidanceStatus
vaultPath: string
vaultPaths?: string[]
}
@ -70,12 +70,12 @@ export function AppAiWorkspaceSurface({
onOpenAiSettings,
onOpenNote,
onPopOut,
onRestoreVaultAiGuidance,
onRestoreWorkspaceAiGuidance,
onUnsupportedAiPaste,
onVaultChanged,
open,
openTabs,
vaultAiGuidanceStatus,
workspaceAiGuidanceStatus,
vaultPath,
vaultPaths,
}: AppAiWorkspaceSurfaceProps) {
@ -106,12 +106,12 @@ export function AppAiWorkspaceSurface({
onPopOut={onPopOut}
onOpenAiSettings={onOpenAiSettings}
onOpenNote={onOpenNote}
onRestoreVaultAiGuidance={onRestoreVaultAiGuidance}
onRestoreWorkspaceAiGuidance={onRestoreWorkspaceAiGuidance}
onUnsupportedAiPaste={onUnsupportedAiPaste}
onFileCreated={onFileCreated}
onFileModified={onFileModified}
onVaultChanged={onVaultChanged}
vaultAiGuidanceStatus={vaultAiGuidanceStatus}
workspaceAiGuidanceStatus={workspaceAiGuidanceStatus}
vaultPath={vaultPath}
vaultPaths={vaultPaths}
locale={locale}

View File

@ -21,12 +21,12 @@ import {
} from '../../lib/aiTargets'
import type { Settings } from '../../types'
import {
getVaultAiGuidanceSummary,
isVaultAiGuidanceStatusChecking,
vaultAiGuidanceNeedsRestore,
vaultAiGuidanceUsesCustomFiles,
type VaultAiGuidanceStatus,
} from '../../lib/vaultAiGuidance'
getWorkspaceAiGuidanceSummary,
isWorkspaceAiGuidanceStatusChecking,
workspaceAiGuidanceNeedsRestore,
workspaceAiGuidanceUsesCustomFiles,
type WorkspaceAiGuidanceStatus,
} from '../../lib/workspaceAiGuidance'
import { translate, type AppLocale } from '../../lib/i18n'
import {
DropdownMenu,
@ -42,7 +42,7 @@ import { ICON_STYLE, SEP_STYLE } from './styles'
interface AiAgentsBadgeProps {
statuses: AiAgentsStatus
guidanceStatus?: VaultAiGuidanceStatus
guidanceStatus?: WorkspaceAiGuidanceStatus
defaultAgent: AiAgentId
defaultTarget?: string
providers?: AiModelProvider[]
@ -58,10 +58,10 @@ function badgeTooltip(
locale: AppLocale,
statuses: AiAgentsStatus,
defaultAgent: AiAgentId,
guidanceStatus?: VaultAiGuidanceStatus,
guidanceStatus?: WorkspaceAiGuidanceStatus,
): string {
const guidanceSummary = guidanceStatus && !isVaultAiGuidanceStatusChecking(guidanceStatus)
? getVaultAiGuidanceSummary(guidanceStatus)
const guidanceSummary = guidanceStatus && !isWorkspaceAiGuidanceStatusChecking(guidanceStatus)
? getWorkspaceAiGuidanceSummary(guidanceStatus)
: null
if (!hasAnyInstalledAiAgent(statuses)) return translate(locale, 'status.ai.noAgentsTooltip')
const definition = getAiAgentDefinition(defaultAgent)
@ -71,10 +71,10 @@ function badgeTooltip(
const version = getAiAgentAvailability(statuses, defaultAgent).version
const base = translate(locale, 'status.ai.defaultAgent', { agent: definition.label, version: version ? ` ${version}` : '' })
if (!guidanceSummary) return base
if (vaultAiGuidanceNeedsRestore(guidanceStatus!)) {
if (workspaceAiGuidanceNeedsRestore(guidanceStatus!)) {
return translate(locale, 'status.ai.restoreDetails', { base, summary: guidanceSummary })
}
if (vaultAiGuidanceUsesCustomFiles(guidanceStatus!)) {
if (workspaceAiGuidanceUsesCustomFiles(guidanceStatus!)) {
return translate(locale, 'status.ai.withGuidance', { base, summary: guidanceSummary })
}
return base
@ -114,11 +114,11 @@ function canSwitchAgents(
function hasAiAgentWarning(
statuses: AiAgentsStatus,
defaultAgent: AiAgentId,
guidanceStatus?: VaultAiGuidanceStatus,
guidanceStatus?: WorkspaceAiGuidanceStatus,
): boolean {
return !hasAnyInstalledAiAgent(statuses)
|| !isAiAgentInstalled(statuses, defaultAgent)
|| !!(guidanceStatus && vaultAiGuidanceNeedsRestore(guidanceStatus))
|| !!(guidanceStatus && workspaceAiGuidanceNeedsRestore(guidanceStatus))
}
function canShowSwitcherCue(statuses: AiAgentsStatus, defaultAgent: AiAgentId): boolean {
@ -215,16 +215,16 @@ function GuidanceMenuSection({
locale = 'en',
onRestoreGuidance,
}: Pick<AiAgentsBadgeProps, 'guidanceStatus' | 'locale' | 'onRestoreGuidance'>) {
if (!guidanceStatus || isVaultAiGuidanceStatusChecking(guidanceStatus)) return null
if (!guidanceStatus || isWorkspaceAiGuidanceStatusChecking(guidanceStatus)) return null
return (
<>
<DropdownMenuSeparator />
<DropdownMenuLabel>{translate(locale, 'status.ai.vaultGuidance')}</DropdownMenuLabel>
<DropdownMenuItem disabled data-testid="status-ai-guidance-summary">
{getVaultAiGuidanceSummary(guidanceStatus)}
{getWorkspaceAiGuidanceSummary(guidanceStatus)}
</DropdownMenuItem>
{vaultAiGuidanceNeedsRestore(guidanceStatus) && guidanceStatus.canRestore && (
{workspaceAiGuidanceNeedsRestore(guidanceStatus) && guidanceStatus.canRestore && (
<DropdownMenuItem
onSelect={() => onRestoreGuidance?.()}
data-testid="status-ai-guidance-restore"

View File

@ -31,16 +31,16 @@ describe('buildAiAgentCommands', () => {
})
it('adds a restore guidance command when the vault guidance needs repair', () => {
const onRestoreVaultAiGuidance = vi.fn()
const onRestoreWorkspaceAiGuidance = vi.fn()
const commands = buildAiAgentCommands({
vaultAiGuidanceStatus: {
workspaceAiGuidanceStatus: {
agentsState: 'missing',
claudeState: 'managed',
geminiState: 'managed',
canRestore: true,
},
onRestoreVaultAiGuidance,
onRestoreWorkspaceAiGuidance,
})
const command = commands.find((item) => item.id === 'restore-vault-ai-guidance')
@ -48,18 +48,18 @@ describe('buildAiAgentCommands', () => {
expect(command?.keywords).toContain('antigravity')
expect(command?.keywords).toContain('gemini')
command?.execute()
expect(onRestoreVaultAiGuidance).toHaveBeenCalledOnce()
expect(onRestoreWorkspaceAiGuidance).toHaveBeenCalledOnce()
})
it('omits the restore command when the vault guidance is already healthy', () => {
const commands = buildAiAgentCommands({
vaultAiGuidanceStatus: {
workspaceAiGuidanceStatus: {
agentsState: 'managed',
claudeState: 'managed',
geminiState: 'managed',
canRestore: false,
},
onRestoreVaultAiGuidance: vi.fn(),
onRestoreWorkspaceAiGuidance: vi.fn(),
})
expect(commands.find((item) => item.id === 'restore-vault-ai-guidance')).toBeUndefined()

View File

@ -5,10 +5,10 @@ import {
type AiAgentsStatus,
} from '../../lib/aiAgents'
import {
isVaultAiGuidanceStatusChecking,
vaultAiGuidanceNeedsRestore,
type VaultAiGuidanceStatus,
} from '../../lib/vaultAiGuidance'
isWorkspaceAiGuidanceStatusChecking,
workspaceAiGuidanceNeedsRestore,
type WorkspaceAiGuidanceStatus,
} from '../../lib/workspaceAiGuidance'
import type { CommandAction } from './types'
const AI_AGENT_KEYWORDS = Array.from(new Set(
@ -26,11 +26,11 @@ function aiAgentKeywords(...keywords: string[]): string[] {
interface AiAgentCommandsConfig {
aiFeaturesEnabled?: boolean
aiAgentsStatus?: AiAgentsStatus
vaultAiGuidanceStatus?: VaultAiGuidanceStatus
workspaceAiGuidanceStatus?: WorkspaceAiGuidanceStatus
selectedAiAgent?: AiAgentId
selectedAiAgentLabel?: string
onOpenAiAgents?: () => void
onRestoreVaultAiGuidance?: () => void
onRestoreWorkspaceAiGuidance?: () => void
onCycleDefaultAiAgent?: () => void
onSetDefaultAiAgent?: (agent: AiAgentId) => void
}
@ -56,12 +56,12 @@ function explicitSwitchCommands({
}
function restoreGuidanceCommands({
vaultAiGuidanceStatus,
onRestoreVaultAiGuidance,
}: Pick<AiAgentCommandsConfig, 'vaultAiGuidanceStatus' | 'onRestoreVaultAiGuidance'>): CommandAction[] {
if (!vaultAiGuidanceStatus || !onRestoreVaultAiGuidance) return []
if (isVaultAiGuidanceStatusChecking(vaultAiGuidanceStatus)) return []
if (!vaultAiGuidanceNeedsRestore(vaultAiGuidanceStatus)) return []
workspaceAiGuidanceStatus,
onRestoreWorkspaceAiGuidance,
}: Pick<AiAgentCommandsConfig, 'workspaceAiGuidanceStatus' | 'onRestoreWorkspaceAiGuidance'>): CommandAction[] {
if (!workspaceAiGuidanceStatus || !onRestoreWorkspaceAiGuidance) return []
if (isWorkspaceAiGuidanceStatusChecking(workspaceAiGuidanceStatus)) return []
if (!workspaceAiGuidanceNeedsRestore(workspaceAiGuidanceStatus)) return []
return [
{
@ -70,7 +70,7 @@ function restoreGuidanceCommands({
group: 'Settings',
keywords: aiAgentKeywords('ai', 'agent', 'guidance', 'restore', 'repair', 'agents', 'antigravity', 'gemini'),
enabled: true,
execute: () => onRestoreVaultAiGuidance(),
execute: () => onRestoreWorkspaceAiGuidance(),
},
]
}
@ -78,11 +78,11 @@ function restoreGuidanceCommands({
export function buildAiAgentCommands({
aiFeaturesEnabled = true,
aiAgentsStatus,
vaultAiGuidanceStatus,
workspaceAiGuidanceStatus,
selectedAiAgent,
selectedAiAgentLabel,
onOpenAiAgents,
onRestoreVaultAiGuidance,
onRestoreWorkspaceAiGuidance,
onCycleDefaultAiAgent,
onSetDefaultAiAgent,
}: AiAgentCommandsConfig): CommandAction[] {
@ -100,8 +100,8 @@ export function buildAiAgentCommands({
]
commands.push(...restoreGuidanceCommands({
vaultAiGuidanceStatus,
onRestoreVaultAiGuidance,
workspaceAiGuidanceStatus,
onRestoreWorkspaceAiGuidance,
}))
const switchCommands = explicitSwitchCommands({

View File

@ -1,6 +1,6 @@
import { useMemo } from 'react'
import type { AiAgentId, AiAgentsStatus } from '../lib/aiAgents'
import type { VaultAiGuidanceStatus } from '../lib/vaultAiGuidance'
import type { WorkspaceAiGuidanceStatus } from '../lib/workspaceAiGuidance'
interface AppCommandDialogs {
toggleAIChat: () => void
@ -19,8 +19,8 @@ interface AppCommandAiActions {
onToggleAIChat?: () => void
onOpenAiAgents?: () => void
aiAgentsStatus?: AiAgentsStatus
vaultAiGuidanceStatus?: VaultAiGuidanceStatus
onRestoreVaultAiGuidance?: () => void
workspaceAiGuidanceStatus?: WorkspaceAiGuidanceStatus
onRestoreWorkspaceAiGuidance?: () => void
selectedAiAgent?: AiAgentId
onSetDefaultAiAgent?: (agent: AiAgentId) => void
onCycleDefaultAiAgent?: () => void
@ -31,8 +31,8 @@ export function useAppCommandAiActions(
aiFeaturesEnabled: boolean,
dialogs: AppCommandDialogs,
aiAgentsStatus: AiAgentsStatus,
vaultAiGuidanceStatus: VaultAiGuidanceStatus,
restoreVaultAiGuidanceCommand: (() => void) | undefined,
workspaceAiGuidanceStatus: WorkspaceAiGuidanceStatus,
restoreWorkspaceAiGuidanceCommand: (() => void) | undefined,
aiAgentPreferences: AppCommandAiPreferences,
): AppCommandAiActions {
return useMemo(() => {
@ -43,8 +43,8 @@ export function useAppCommandAiActions(
onToggleAIChat: dialogs.toggleAIChat,
onOpenAiAgents: dialogs.openSettings,
aiAgentsStatus,
vaultAiGuidanceStatus,
onRestoreVaultAiGuidance: restoreVaultAiGuidanceCommand,
workspaceAiGuidanceStatus,
onRestoreWorkspaceAiGuidance: restoreWorkspaceAiGuidanceCommand,
selectedAiAgent: aiAgentPreferences.defaultAiAgent,
onSetDefaultAiAgent: aiAgentPreferences.setDefaultAiAgent,
onCycleDefaultAiAgent: aiAgentPreferences.cycleDefaultAiAgent,
@ -55,8 +55,8 @@ export function useAppCommandAiActions(
dialogs.toggleAIChat,
dialogs.openSettings,
aiAgentsStatus,
vaultAiGuidanceStatus,
restoreVaultAiGuidanceCommand,
workspaceAiGuidanceStatus,
restoreWorkspaceAiGuidanceCommand,
aiAgentPreferences.defaultAiAgent,
aiAgentPreferences.setDefaultAiAgent,
aiAgentPreferences.cycleDefaultAiAgent,

View File

@ -2,7 +2,7 @@ import { useCallback, useRef } from 'react'
import type { AiAgentId, AiAgentsStatus } from '../lib/aiAgents'
import type { AppLocale, UiLanguagePreference } from '../lib/i18n'
import type { ThemeMode } from '../lib/themeMode'
import type { VaultAiGuidanceStatus } from '../lib/vaultAiGuidance'
import type { WorkspaceAiGuidanceStatus } from '../lib/workspaceAiGuidance'
import { useAppKeyboard } from './useAppKeyboard'
import { useCommandRegistry } from './useCommandRegistry'
import type { CommandAction } from './useCommandRegistry'
@ -103,9 +103,9 @@ interface AppCommandsConfig {
mcpStatus?: string
onInstallMcp?: () => void
aiAgentsStatus?: AiAgentsStatus
vaultAiGuidanceStatus?: VaultAiGuidanceStatus
workspaceAiGuidanceStatus?: WorkspaceAiGuidanceStatus
onOpenAiAgents?: () => void
onRestoreVaultAiGuidance?: () => void
onRestoreWorkspaceAiGuidance?: () => void
onSetDefaultAiAgent?: (agent: AiAgentId) => void
selectedAiAgent?: AiAgentId
onCycleDefaultAiAgent?: () => void
@ -242,9 +242,9 @@ type CommandRegistryAiActions = Pick<
| 'mcpStatus'
| 'onInstallMcp'
| 'aiAgentsStatus'
| 'vaultAiGuidanceStatus'
| 'workspaceAiGuidanceStatus'
| 'onOpenAiAgents'
| 'onRestoreVaultAiGuidance'
| 'onRestoreWorkspaceAiGuidance'
| 'onSetDefaultAiAgent'
| 'selectedAiAgent'
| 'onCycleDefaultAiAgent'
@ -570,9 +570,9 @@ function createCommandRegistryAiConfig(
return {
...sharedConfig,
aiAgentsStatus: config.aiAgentsStatus,
vaultAiGuidanceStatus: config.vaultAiGuidanceStatus,
workspaceAiGuidanceStatus: config.workspaceAiGuidanceStatus,
onOpenAiAgents: config.onOpenAiAgents,
onRestoreVaultAiGuidance: config.onRestoreVaultAiGuidance,
onRestoreWorkspaceAiGuidance: config.onRestoreWorkspaceAiGuidance,
onSetDefaultAiAgent: config.onSetDefaultAiAgent,
selectedAiAgent: config.selectedAiAgent,
onCycleDefaultAiAgent: config.onCycleDefaultAiAgent,

View File

@ -2,7 +2,7 @@ import { useMemo } from 'react'
import type { AiAgentId, AiAgentsStatus } from '../lib/aiAgents'
import type { AppLocale, UiLanguagePreference } from '../lib/i18n'
import type { ThemeMode } from '../lib/themeMode'
import type { VaultAiGuidanceStatus } from '../lib/vaultAiGuidance'
import type { WorkspaceAiGuidanceStatus } from '../lib/workspaceAiGuidance'
import type { NoteWidthMode, SidebarSelection, VaultEntry } from '../types'
import type { NoteListFilter } from '../utils/noteListHelpers'
import type { ViewMode } from './useViewMode'
@ -36,9 +36,9 @@ interface CommandRegistryConfig {
onInstallMcp?: () => void
aiFeaturesEnabled?: boolean
aiAgentsStatus?: AiAgentsStatus
vaultAiGuidanceStatus?: VaultAiGuidanceStatus
workspaceAiGuidanceStatus?: WorkspaceAiGuidanceStatus
onOpenAiAgents?: () => void
onRestoreVaultAiGuidance?: () => void
onRestoreWorkspaceAiGuidance?: () => void
onSetDefaultAiAgent?: (agent: AiAgentId) => void
selectedAiAgent?: AiAgentId
onCycleDefaultAiAgent?: () => void
@ -167,8 +167,8 @@ export function useCommandRegistry(config: CommandRegistryConfig): import('./com
onCheckForUpdates, onCreateType,
onRemoveActiveVault, onRestoreGettingStarted, isGettingStartedHidden, vaultCount,
mcpStatus, onInstallMcp, aiFeaturesEnabled,
aiAgentsStatus, vaultAiGuidanceStatus,
onOpenAiAgents, onRestoreVaultAiGuidance, onSetDefaultAiAgent, selectedAiAgent, onCycleDefaultAiAgent, selectedAiAgentLabel,
aiAgentsStatus, workspaceAiGuidanceStatus,
onOpenAiAgents, onRestoreWorkspaceAiGuidance, onSetDefaultAiAgent, selectedAiAgent, onCycleDefaultAiAgent, selectedAiAgentLabel,
onReloadVault, onRepairVault,
locale, systemLocale, selectedUiLanguage, onSetUiLanguage, onSetThemeMode,
onSetNoteIcon, onRemoveNoteIcon, activeNoteHasIcon, onChangeNoteType, onMoveNoteToFolder, canMoveNoteToFolder, onTurnCurrentBlockInto,
@ -291,17 +291,17 @@ export function useCommandRegistry(config: CommandRegistryConfig): import('./com
const aiCommands = useMemo(() => buildAiAgentCommands({
aiFeaturesEnabled,
aiAgentsStatus,
vaultAiGuidanceStatus,
workspaceAiGuidanceStatus,
selectedAiAgent,
selectedAiAgentLabel,
onOpenAiAgents,
onRestoreVaultAiGuidance,
onRestoreWorkspaceAiGuidance,
onSetDefaultAiAgent,
onCycleDefaultAiAgent,
}), [
aiFeaturesEnabled,
aiAgentsStatus, vaultAiGuidanceStatus, selectedAiAgent, selectedAiAgentLabel,
onOpenAiAgents, onRestoreVaultAiGuidance, onSetDefaultAiAgent, onCycleDefaultAiAgent,
aiAgentsStatus, workspaceAiGuidanceStatus, selectedAiAgent, selectedAiAgentLabel,
onOpenAiAgents, onRestoreWorkspaceAiGuidance, onSetDefaultAiAgent, onCycleDefaultAiAgent,
])
const typeCommands = useMemo(

View File

@ -1,73 +0,0 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { invoke } from '@tauri-apps/api/core'
import { isTauri, mockInvoke } from '../mock-tauri'
import {
createCheckingVaultAiGuidanceStatus,
normalizeVaultAiGuidanceStatus,
type VaultAiGuidanceFileState,
type VaultAiGuidanceStatus,
} from '../lib/vaultAiGuidance'
type RawVaultAiGuidanceStatus = Partial<{
agents_state: VaultAiGuidanceFileState | null
claude_state: VaultAiGuidanceFileState | null
gemini_state: VaultAiGuidanceFileState | null
can_restore: boolean | null
}>
function tauriCall<T>(command: string, args: Record<string, unknown>): Promise<T> {
return isTauri() ? invoke<T>(command, args) : mockInvoke<T>(command, args)
}
export function useVaultAiGuidanceStatus(
vaultPath: string | null | undefined,
refreshKey = '',
): { status: VaultAiGuidanceStatus; refresh: () => Promise<VaultAiGuidanceStatus> } {
const checkingStatus = useMemo(() => createCheckingVaultAiGuidanceStatus(), [])
const statusRequest = useMemo(() => ({ refreshKey, vaultPath }), [refreshKey, vaultPath])
const [status, setStatus] = useState<VaultAiGuidanceStatus>(() => createCheckingVaultAiGuidanceStatus())
const refresh = useCallback(async () => {
if (!vaultPath) {
return checkingStatus
}
try {
const result = await tauriCall<RawVaultAiGuidanceStatus>('get_vault_ai_guidance_status', {
vaultPath,
})
const normalized = normalizeVaultAiGuidanceStatus(result)
setStatus(normalized)
return normalized
} catch {
const checking = createCheckingVaultAiGuidanceStatus()
setStatus(checking)
return checking
}
}, [checkingStatus, vaultPath])
useEffect(() => {
let cancelled = false
if (!statusRequest.vaultPath) return
tauriCall<RawVaultAiGuidanceStatus>('get_vault_ai_guidance_status', { vaultPath: statusRequest.vaultPath })
.then((result) => {
if (!cancelled) {
setStatus(normalizeVaultAiGuidanceStatus(result))
}
})
.catch(() => {
if (!cancelled) {
setStatus(createCheckingVaultAiGuidanceStatus())
}
})
return () => { cancelled = true }
}, [statusRequest])
return {
status: vaultPath ? status : checkingStatus,
refresh,
}
}

View File

@ -1,6 +1,6 @@
import { act, renderHook, waitFor } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { useVaultAiGuidanceStatus } from './useVaultAiGuidanceStatus'
import { useWorkspaceAiGuidanceStatus } from './useWorkspaceAiGuidanceStatus'
vi.mock('@tauri-apps/api/core', () => ({
invoke: vi.fn(),
@ -13,7 +13,7 @@ vi.mock('../mock-tauri', () => ({
const { mockInvoke } = await import('../mock-tauri') as { mockInvoke: ReturnType<typeof vi.fn> }
describe('useVaultAiGuidanceStatus', () => {
describe('useWorkspaceAiGuidanceStatus', () => {
beforeEach(() => {
vi.clearAllMocks()
})
@ -26,7 +26,7 @@ describe('useVaultAiGuidanceStatus', () => {
can_restore: true,
})
const { result } = renderHook(() => useVaultAiGuidanceStatus('/vault'))
const { result } = renderHook(() => useWorkspaceAiGuidanceStatus('/vault'))
expect(result.current.status.agentsState).toBe('checking')
expect(result.current.status.claudeState).toBe('checking')
@ -57,7 +57,7 @@ describe('useVaultAiGuidanceStatus', () => {
can_restore: true,
})
const { result } = renderHook(() => useVaultAiGuidanceStatus('/vault'))
const { result } = renderHook(() => useWorkspaceAiGuidanceStatus('/vault'))
await waitFor(() => {
expect(result.current.status.canRestore).toBe(false)

View File

@ -0,0 +1,73 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { invoke } from '@tauri-apps/api/core'
import { isTauri, mockInvoke } from '../mock-tauri'
import {
createCheckingWorkspaceAiGuidanceStatus,
normalizeWorkspaceAiGuidanceStatus,
type WorkspaceAiGuidanceFileState,
type WorkspaceAiGuidanceStatus,
} from '../lib/workspaceAiGuidance'
type RawWorkspaceAiGuidanceStatus = Partial<{
agents_state: WorkspaceAiGuidanceFileState | null
claude_state: WorkspaceAiGuidanceFileState | null
gemini_state: WorkspaceAiGuidanceFileState | null
can_restore: boolean | null
}>
function tauriCall<T>(command: string, args: Record<string, unknown>): Promise<T> {
return isTauri() ? invoke<T>(command, args) : mockInvoke<T>(command, args)
}
export function useWorkspaceAiGuidanceStatus(
vaultPath: string | null | undefined,
refreshKey = '',
): { status: WorkspaceAiGuidanceStatus; refresh: () => Promise<WorkspaceAiGuidanceStatus> } {
const checkingStatus = useMemo(() => createCheckingWorkspaceAiGuidanceStatus(), [])
const statusRequest = useMemo(() => ({ refreshKey, vaultPath }), [refreshKey, vaultPath])
const [status, setStatus] = useState<WorkspaceAiGuidanceStatus>(() => createCheckingWorkspaceAiGuidanceStatus())
const refresh = useCallback(async () => {
if (!vaultPath) {
return checkingStatus
}
try {
const result = await tauriCall<RawWorkspaceAiGuidanceStatus>('get_vault_ai_guidance_status', {
vaultPath,
})
const normalized = normalizeWorkspaceAiGuidanceStatus(result)
setStatus(normalized)
return normalized
} catch {
const checking = createCheckingWorkspaceAiGuidanceStatus()
setStatus(checking)
return checking
}
}, [checkingStatus, vaultPath])
useEffect(() => {
let cancelled = false
if (!statusRequest.vaultPath) return
tauriCall<RawWorkspaceAiGuidanceStatus>('get_vault_ai_guidance_status', { vaultPath: statusRequest.vaultPath })
.then((result) => {
if (!cancelled) {
setStatus(normalizeWorkspaceAiGuidanceStatus(result))
}
})
.catch(() => {
if (!cancelled) {
setStatus(createCheckingWorkspaceAiGuidanceStatus())
}
})
return () => { cancelled = true }
}, [statusRequest])
return {
status: vaultPath ? status : checkingStatus,
refresh,
}
}

View File

@ -1,12 +1,12 @@
import { describe, expect, it } from 'vitest'
import {
buildVaultAiGuidanceRefreshKey,
createCheckingVaultAiGuidanceStatus,
getVaultAiGuidanceSummary,
normalizeVaultAiGuidanceStatus,
vaultAiGuidanceNeedsRestore,
vaultAiGuidanceUsesCustomFiles,
} from './vaultAiGuidance'
buildWorkspaceAiGuidanceRefreshKey,
createCheckingWorkspaceAiGuidanceStatus,
getWorkspaceAiGuidanceSummary,
normalizeWorkspaceAiGuidanceStatus,
workspaceAiGuidanceNeedsRestore,
workspaceAiGuidanceUsesCustomFiles,
} from './workspaceAiGuidance'
import type { VaultEntry } from '../types'
function makeEntry(filename: string, overrides: Partial<VaultEntry> = {}): VaultEntry {
@ -46,9 +46,9 @@ function makeEntry(filename: string, overrides: Partial<VaultEntry> = {}): Vault
}
}
describe('vaultAiGuidance helpers', () => {
describe('workspaceAiGuidance helpers', () => {
it('starts in checking state', () => {
expect(createCheckingVaultAiGuidanceStatus()).toEqual({
expect(createCheckingWorkspaceAiGuidanceStatus()).toEqual({
agentsState: 'checking',
claudeState: 'checking',
geminiState: 'checking',
@ -57,7 +57,7 @@ describe('vaultAiGuidance helpers', () => {
})
it('normalizes raw backend payloads', () => {
expect(normalizeVaultAiGuidanceStatus({
expect(normalizeWorkspaceAiGuidanceStatus({
agents_state: 'managed',
claude_state: 'broken',
gemini_state: 'missing',
@ -71,47 +71,47 @@ describe('vaultAiGuidance helpers', () => {
})
it('detects restoreable and custom states', () => {
const restoreable = normalizeVaultAiGuidanceStatus({
const restoreable = normalizeWorkspaceAiGuidanceStatus({
agents_state: 'missing',
claude_state: 'managed',
gemini_state: 'managed',
can_restore: true,
})
const custom = normalizeVaultAiGuidanceStatus({
const custom = normalizeWorkspaceAiGuidanceStatus({
agents_state: 'custom',
claude_state: 'managed',
gemini_state: 'managed',
can_restore: false,
})
expect(vaultAiGuidanceNeedsRestore(restoreable)).toBe(true)
expect(getVaultAiGuidanceSummary(restoreable)).toBe('光湖人格体唤醒指南缺失或损坏')
expect(vaultAiGuidanceUsesCustomFiles(custom)).toBe(true)
expect(getVaultAiGuidanceSummary(custom)).toBe('Using custom AGENTS.md')
expect(workspaceAiGuidanceNeedsRestore(restoreable)).toBe(true)
expect(getWorkspaceAiGuidanceSummary(restoreable)).toBe('光湖人格体唤醒指南缺失或损坏')
expect(workspaceAiGuidanceUsesCustomFiles(custom)).toBe(true)
expect(getWorkspaceAiGuidanceSummary(custom)).toBe('Using custom AGENTS.md')
})
it('summarizes optional Gemini guidance states', () => {
const missing = normalizeVaultAiGuidanceStatus({
const missing = normalizeWorkspaceAiGuidanceStatus({
agents_state: 'managed',
claude_state: 'managed',
gemini_state: 'missing',
can_restore: true,
})
const custom = normalizeVaultAiGuidanceStatus({
const custom = normalizeWorkspaceAiGuidanceStatus({
agents_state: 'managed',
claude_state: 'managed',
gemini_state: 'custom',
can_restore: false,
})
expect(vaultAiGuidanceNeedsRestore(missing)).toBe(true)
expect(getVaultAiGuidanceSummary(missing)).toBe('Gemini guidance can be created')
expect(vaultAiGuidanceUsesCustomFiles(custom)).toBe(true)
expect(getVaultAiGuidanceSummary(custom)).toBe('Using custom GEMINI.md')
expect(workspaceAiGuidanceNeedsRestore(missing)).toBe(true)
expect(getWorkspaceAiGuidanceSummary(missing)).toBe('Gemini guidance can be created')
expect(workspaceAiGuidanceUsesCustomFiles(custom)).toBe(true)
expect(getWorkspaceAiGuidanceSummary(custom)).toBe('Using custom GEMINI.md')
})
it('builds a refresh key from AGENTS and CLAUDE entries only', () => {
const key = buildVaultAiGuidanceRefreshKey([
const key = buildWorkspaceAiGuidanceRefreshKey([
makeEntry('alpha.md'),
makeEntry('CLAUDE.md', { modifiedAt: 20, fileSize: 30 }),
makeEntry('GEMINI.md', { modifiedAt: 25, fileSize: 35 }),

View File

@ -1,24 +1,24 @@
import type { VaultEntry } from '../types'
export type VaultAiGuidanceFileState = 'checking' | 'managed' | 'missing' | 'broken' | 'custom'
export type WorkspaceAiGuidanceFileState = 'checking' | 'managed' | 'missing' | 'broken' | 'custom'
export interface VaultAiGuidanceStatus {
agentsState: VaultAiGuidanceFileState
claudeState: VaultAiGuidanceFileState
geminiState: VaultAiGuidanceFileState
export interface WorkspaceAiGuidanceStatus {
agentsState: WorkspaceAiGuidanceFileState
claudeState: WorkspaceAiGuidanceFileState
geminiState: WorkspaceAiGuidanceFileState
canRestore: boolean
}
type RawVaultAiGuidanceStatus = Partial<{
agents_state: VaultAiGuidanceFileState | null
claude_state: VaultAiGuidanceFileState | null
gemini_state: VaultAiGuidanceFileState | null
type RawWorkspaceAiGuidanceStatus = Partial<{
agents_state: WorkspaceAiGuidanceFileState | null
claude_state: WorkspaceAiGuidanceFileState | null
gemini_state: WorkspaceAiGuidanceFileState | null
can_restore: boolean | null
}>
const GUIDANCE_FILENAMES = new Set(['AGENTS.md', 'CLAUDE.md', 'GEMINI.md'])
export function createCheckingVaultAiGuidanceStatus(): VaultAiGuidanceStatus {
export function createCheckingWorkspaceAiGuidanceStatus(): WorkspaceAiGuidanceStatus {
return {
agentsState: 'checking',
claudeState: 'checking',
@ -27,7 +27,7 @@ export function createCheckingVaultAiGuidanceStatus(): VaultAiGuidanceStatus {
}
}
function normalizeFileState(value: string | null | undefined): VaultAiGuidanceFileState {
function normalizeFileState(value: string | null | undefined): WorkspaceAiGuidanceFileState {
switch (value) {
case 'managed':
case 'missing':
@ -39,9 +39,9 @@ function normalizeFileState(value: string | null | undefined): VaultAiGuidanceFi
}
}
export function normalizeVaultAiGuidanceStatus(
payload: RawVaultAiGuidanceStatus | null | undefined,
): VaultAiGuidanceStatus {
export function normalizeWorkspaceAiGuidanceStatus(
payload: RawWorkspaceAiGuidanceStatus | null | undefined,
): WorkspaceAiGuidanceStatus {
return {
agentsState: normalizeFileState(payload?.agents_state),
claudeState: normalizeFileState(payload?.claude_state),
@ -50,14 +50,14 @@ export function normalizeVaultAiGuidanceStatus(
}
}
export function isVaultAiGuidanceStatusChecking(status: VaultAiGuidanceStatus): boolean {
export function isWorkspaceAiGuidanceStatusChecking(status: WorkspaceAiGuidanceStatus): boolean {
return status.agentsState === 'checking'
|| status.claudeState === 'checking'
|| status.geminiState === 'checking'
}
export function vaultAiGuidanceNeedsRestore(status: VaultAiGuidanceStatus): boolean {
if (!status.canRestore || isVaultAiGuidanceStatusChecking(status)) return false
export function workspaceAiGuidanceNeedsRestore(status: WorkspaceAiGuidanceStatus): boolean {
if (!status.canRestore || isWorkspaceAiGuidanceStatusChecking(status)) return false
return status.agentsState === 'missing'
|| status.agentsState === 'broken'
|| status.claudeState === 'missing'
@ -66,13 +66,13 @@ export function vaultAiGuidanceNeedsRestore(status: VaultAiGuidanceStatus): bool
|| status.geminiState === 'broken'
}
export function vaultAiGuidanceUsesCustomFiles(status: VaultAiGuidanceStatus): boolean {
export function workspaceAiGuidanceUsesCustomFiles(status: WorkspaceAiGuidanceStatus): boolean {
return status.agentsState === 'custom'
|| status.claudeState === 'custom'
|| status.geminiState === 'custom'
}
function isMissingOrBroken(state: VaultAiGuidanceFileState): boolean {
function isMissingOrBroken(state: WorkspaceAiGuidanceFileState): boolean {
return state === 'missing' || state === 'broken'
}
@ -82,7 +82,7 @@ function formatGuidanceFileList(names: string[]): string {
return `${names.slice(0, -1).join(', ')}, and ${names[names.length - 1]}`
}
function getBrokenGuidanceSummary(status: VaultAiGuidanceStatus): string | null {
function getBrokenGuidanceSummary(status: WorkspaceAiGuidanceStatus): string | null {
if (isMissingOrBroken(status.agentsState)) {
return '光湖人格体唤醒指南缺失或损坏'
}
@ -98,7 +98,7 @@ function getBrokenGuidanceSummary(status: VaultAiGuidanceStatus): string | null
return null
}
function getCustomGuidanceSummary(status: VaultAiGuidanceStatus): string | null {
function getCustomGuidanceSummary(status: WorkspaceAiGuidanceStatus): string | null {
const customNames = [
status.agentsState === 'custom' ? 'AGENTS.md' : null,
status.claudeState === 'custom' ? 'CLAUDE.md' : null,
@ -113,8 +113,8 @@ function getCustomGuidanceSummary(status: VaultAiGuidanceStatus): string | null
return null
}
export function getVaultAiGuidanceSummary(status: VaultAiGuidanceStatus): string {
if (isVaultAiGuidanceStatusChecking(status)) return 'Checking vault guidance…'
export function getWorkspaceAiGuidanceSummary(status: WorkspaceAiGuidanceStatus): string {
if (isWorkspaceAiGuidanceStatusChecking(status)) return 'Checking vault guidance…'
const brokenSummary = getBrokenGuidanceSummary(status)
if (brokenSummary) return brokenSummary
const customSummary = getCustomGuidanceSummary(status)
@ -122,7 +122,7 @@ export function getVaultAiGuidanceSummary(status: VaultAiGuidanceStatus): string
return 'HoloLake Era guidance ready'
}
export function buildVaultAiGuidanceRefreshKey(entries: VaultEntry[]): string {
export function buildWorkspaceAiGuidanceRefreshKey(entries: VaultEntry[]): string {
return entries
.filter((entry) => GUIDANCE_FILENAMES.has(entry.filename))
.map((entry) => `${entry.path}:${entry.modifiedAt ?? 0}:${entry.fileSize}`)

View File

@ -167,7 +167,7 @@ let mockVaultList: { vaults: Array<{ label: string; path: string }>; active_vaul
active_vault: DEFAULT_MOCK_VAULT_PATH,
}
let mockVaultAiGuidanceStatus = {
let mockWorkspaceAiGuidanceStatus = {
agents_state: 'managed',
claude_state: 'managed',
gemini_state: 'managed',
@ -560,15 +560,15 @@ export const mockHandlers: Record<string, (args: any) => any> = {
hermes: { installed: false, version: null },
}),
get_agent_docs_path: () => '/mock/Tolaria/resources/agent-docs',
get_vault_ai_guidance_status: () => ({ ...mockVaultAiGuidanceStatus }),
get_vault_ai_guidance_status: () => ({ ...mockWorkspaceAiGuidanceStatus }),
restore_vault_ai_guidance: () => {
mockVaultAiGuidanceStatus = {
mockWorkspaceAiGuidanceStatus = {
agents_state: 'managed',
claude_state: 'managed',
gemini_state: 'managed',
can_restore: false,
}
return { ...mockVaultAiGuidanceStatus }
return { ...mockWorkspaceAiGuidanceStatus }
},
stream_claude_chat: () => 'mock-session',
stream_ai_agent: () => null,
@ -715,7 +715,7 @@ export const mockHandlers: Record<string, (args: any) => any> = {
read_text_from_clipboard: () => '',
sync_mcp_bridge_vault: (args: { vaultPath?: string | null }) => args.vaultPath ? 'started' : 'stopped',
repair_vault: (): string => {
mockVaultAiGuidanceStatus = {
mockWorkspaceAiGuidanceStatus = {
agents_state: 'managed',
claude_state: 'managed',
gemini_state: 'managed',