feat: implement automatic TOOLS.md cache synchronization with bundled-only loading

- Remove GitHub remote loading to eliminate 404 errors in development
- Implement automatic cache invalidation when TOOLS.md is updated
- Add file watcher system for real-time cache refresh
- Create useToolsUpdates hook for reactive UI components
- Ensure AI Config page immediately reflects updated tool data
- Auto-dispatch tools-updated events for seamless UI synchronization

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
cyrus
2026-03-06 16:09:36 +05:30
co-authored by Claude
parent 35662f051d
commit c00ba2f045
9 changed files with 2175 additions and 1993 deletions
+1975 -1975
View File
File diff suppressed because it is too large Load Diff
+1 -1
Submodule skills updated: 94f605d148...88c9c8c8d1
@@ -1,5 +1,6 @@
import { useState, useEffect } from 'react';
import { loadAIConfig, refreshSoul, refreshTools, refreshAll } from '../../../lib/ai/loader';
import { useToolsUpdates } from '../../../hooks/useToolsUpdates';
import type { AIConfig } from '../../../lib/ai/types';
import type { SoulConfig } from '../../../lib/ai/soul/types';
import type { ToolsConfig } from '../../../lib/ai/tools/types';
@@ -13,10 +14,21 @@ const AIPanel = () => {
const [refreshingComponent, setRefreshingComponent] = useState<'soul' | 'tools' | 'all' | null>(null);
const [error, setError] = useState<string>('');
// Listen for tools updates and refresh AI config
const toolsUpdateTimestamp = useToolsUpdates();
useEffect(() => {
loadAIPreview();
}, []);
// Auto-refresh when tools are updated
useEffect(() => {
if (toolsUpdateTimestamp > 0) {
console.log('🔄 AIPanel: Tools updated detected, refreshing AI config...');
loadAIPreview();
}
}, [toolsUpdateTimestamp]);
const loadAIPreview = async () => {
setLoading(true);
setError('');
+49
View File
@@ -0,0 +1,49 @@
/**
* React hook to listen for tools updates and trigger re-renders
*
* Components using this hook will automatically re-render when TOOLS.md
* is updated and the cache is refreshed.
*/
import { useEffect, useState } from 'react';
interface ToolsUpdateEvent {
timestamp: number;
}
/**
* Hook to listen for tools updates
* @returns timestamp of last tools update (triggers re-renders)
*/
export function useToolsUpdates(): number {
const [lastUpdate, setLastUpdate] = useState<number>(0);
useEffect(() => {
const handleToolsUpdate = (event: CustomEvent<ToolsUpdateEvent>) => {
console.log('🔔 Tools update detected, triggering component re-render');
setLastUpdate(event.detail.timestamp);
};
// Listen for tools-updated events
window.addEventListener('tools-updated', handleToolsUpdate as EventListener);
return () => {
window.removeEventListener('tools-updated', handleToolsUpdate as EventListener);
};
}, []);
return lastUpdate;
}
/**
* Hook to get a callback that forces tools refresh
* @returns function to manually trigger tools refresh
*/
export function useForceToolsRefresh(): () => Promise<void> {
const forceRefresh = async () => {
const { forceToolsCacheRefresh } = await import('../lib/tools/file-watcher');
return forceToolsCacheRefresh();
};
return forceRefresh;
}
+6 -17
View File
@@ -10,7 +10,7 @@ import type {
ToolsCacheEntry
} from './types';
const TOOLS_GITHUB_URL = 'https://raw.githubusercontent.com/alphahumanxyz/alphahuman/refs/heads/main/ai/TOOLS.md';
// GitHub URL removed - always use bundled file updated by auto-update system
const TOOLS_CACHE_KEY = 'alphahuman.tools.cache';
const TOOLS_CACHE_TTL = 1000 * 60 * 30; // 30 minutes
const CACHE_VERSION = '1.0.0';
@@ -18,11 +18,10 @@ const CACHE_VERSION = '1.0.0';
let cachedToolsConfig: ToolsConfig | null = null;
/**
* Load TOOLS.md with caching and fallback strategy:
* Load TOOLS.md with caching strategy:
* 1. Try in-memory cache
* 2. Try localStorage cache (with TTL)
* 3. Try GitHub remote
* 4. Fallback to bundled TOOLS.md
* 3. Use bundled TOOLS.md (always fresh from auto-updates)
*/
export async function loadTools(): Promise<ToolsConfig> {
// 1. Memory cache
@@ -47,19 +46,9 @@ export async function loadTools(): Promise<ToolsConfig> {
// Ignore cache errors
}
let raw: string;
let isDefault = false;
try {
// 3. GitHub remote
const response = await fetch(TOOLS_GITHUB_URL);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
raw = await response.text();
} catch {
// 4. Fallback to bundled
raw = toolsMd;
isDefault = true;
}
// 3. Always use bundled TOOLS.md (updated by auto-update system)
const raw = toolsMd;
const isDefault = false; // Not a fallback since it's the auto-generated file
const config = parseTools(raw, isDefault);
+13
View File
@@ -6,6 +6,8 @@
*/
import { invoke } from '@tauri-apps/api/core';
import { clearToolsCache } from '../ai/tools/loader';
import { forceToolsCacheRefresh } from './file-watcher';
// Prevent excessive updates - limit to once per 10 seconds
let lastUpdateTime = 0;
@@ -92,6 +94,17 @@ export async function updateToolsDocumentation(): Promise<void> {
}
console.log('✅ SUCCESS: TOOLS.md updated successfully!');
// Clear tools cache and force immediate refresh with new data
console.log('🗑️ Clearing tools cache and refreshing with new data...');
await forceToolsCacheRefresh();
// Manually dispatch tools-updated event to ensure UI updates
console.log('📡 Dispatching tools-updated event for UI components...');
window.dispatchEvent(new CustomEvent('tools-updated', {
detail: { timestamp: Date.now() }
}));
console.log(`📄 Final result: ${toolsResponse.length} tools from ${Object.keys(toolsBySkill).length} skills`);
console.log('=== AUTO-UPDATE TOOLS.md COMPLETE ===');
} catch (error) {
+109
View File
@@ -0,0 +1,109 @@
/**
* File watcher for TOOLS.md to automatically invalidate cache when updated
*
* This module sets up automatic cache invalidation whenever the bundled TOOLS.md
* file is updated, ensuring the UI reflects the latest tool data immediately.
*/
import { clearToolsCache } from '../ai/tools/loader';
import { clearAICache, loadAIConfig } from '../ai/loader';
// Track file modification time to detect changes
let lastModifiedTime: number | null = null;
let watcherInterval: NodeJS.Timeout | null = null;
/**
* Start watching TOOLS.md file for changes
* When changes are detected, automatically clear cache and reload
*/
export function startToolsFileWatcher(): void {
if (watcherInterval) {
console.log('🔍 TOOLS.md file watcher already running');
return;
}
console.log('🔍 Starting TOOLS.md file watcher...');
// Check for file changes every 2 seconds
watcherInterval = setInterval(checkForToolsChanges, 2000);
}
/**
* Stop the file watcher
*/
export function stopToolsFileWatcher(): void {
if (watcherInterval) {
clearInterval(watcherInterval);
watcherInterval = null;
console.log('🔍 TOOLS.md file watcher stopped');
}
}
/**
* Check if TOOLS.md file has been modified and trigger cache refresh
*/
async function checkForToolsChanges(): Promise<void> {
try {
// Check if the bundled TOOLS.md has changed by fetching from public directory
const response = await fetch('/ai/TOOLS.md');
if (!response.ok) return;
const content = await response.text();
const contentHash = simpleHash(content);
// Check if content has changed
if (lastModifiedTime === null) {
lastModifiedTime = contentHash;
return;
}
if (lastModifiedTime !== contentHash) {
console.log('📝 TOOLS.md file changed detected - refreshing cache...');
lastModifiedTime = contentHash;
// Clear cache and trigger reload
clearToolsCache();
clearAICache();
// Preload the new tools and AI config data
try {
await loadAIConfig();
console.log('✅ AI configuration cache refreshed successfully');
// Dispatch custom event for components to react to
window.dispatchEvent(new CustomEvent('tools-updated', {
detail: { timestamp: Date.now() }
}));
} catch (error) {
console.error('❌ Failed to reload AI config after file change:', error);
}
}
} catch (error) {
// Silently ignore errors (file not found, network issues, etc.)
// This prevents console spam during development
}
}
/**
* Simple hash function for content change detection
*/
function simpleHash(str: string): number {
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return hash;
}
/**
* Force a cache refresh (useful for manual triggers)
*/
export function forceToolsCacheRefresh(): Promise<void> {
console.log('🔄 Forcing tools cache refresh...');
clearToolsCache();
clearAICache();
lastModifiedTime = null; // Reset to trigger next check
return loadAIConfig();
}
+4
View File
@@ -8,6 +8,7 @@ import './index.css';
import './polyfills';
import { initSentry } from './services/analytics';
import { setupDesktopDeepLinkListener } from './utils/desktopDeepLinkListener';
import { startToolsFileWatcher } from './lib/tools/file-watcher';
// Initialize Sentry early (before React renders)
initSentry();
@@ -17,6 +18,9 @@ setupDesktopDeepLinkListener().catch(err => {
console.error('[DeepLink] setup error:', err);
});
// Start file watcher for automatic TOOLS.md cache invalidation
startToolsFileWatcher();
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<App />
+6
View File
@@ -15,6 +15,7 @@ import { useIntelligenceStats } from '../hooks/useIntelligenceStats';
import { deriveConnectionStatus, useSkillConnectionStatus } from '../lib/skills/hooks';
import { skillManager } from '../lib/skills/manager';
import { updateToolsDocumentation } from '../lib/tools/auto-update';
import { forceToolsCacheRefresh } from '../lib/tools/file-watcher';
import type { SkillConnectionStatus, SkillHostConnectionState } from '../lib/skills/types';
import { useAppSelector } from '../store/hooks';
import { IS_DEV } from '../utils/config';
@@ -277,6 +278,11 @@ export default function Intelligence() {
console.log('🚀 Intelligence Page: Calling updateToolsDocumentation...');
await updateToolsDocumentation();
console.log('✅ Intelligence Page: TOOLS.md update completed successfully');
// Force cache refresh to immediately reflect changes in UI
console.log('🔄 Intelligence Page: Forcing tools cache refresh...');
await forceToolsCacheRefresh();
console.log('✅ Intelligence Page: Tools cache refreshed successfully');
} catch (error) {
console.error('❌ Intelligence Page: Failed to update TOOLS.md:', error);
}