Harden architecture hot paths

This commit is contained in:
gsxdsm
2026-04-12 15:13:27 -07:00
parent 7b78963a4c
commit a34ba41ad1
38 changed files with 1212 additions and 561 deletions

View File

@@ -343,8 +343,13 @@ export async function deleteAttachment(id: string, filename: string, projectId?:
return api<Task>(withProjectId(`/tasks/${id}/attachments/${filename}`, projectId), { method: "DELETE" });
}
export function fetchAgentLogs(taskId: string, projectId?: string): Promise<AgentLogEntry[]> {
return api<AgentLogEntry[]>(withProjectId(`/tasks/${taskId}/logs`, projectId));
export function fetchAgentLogs(taskId: string, projectId?: string, options?: { limit?: number }): Promise<AgentLogEntry[]> {
const params = new URLSearchParams();
if (options?.limit !== undefined) {
params.set("limit", String(options.limit));
}
const suffix = params.toString() ? `?${params.toString()}` : "";
return api<AgentLogEntry[]>(withProjectId(`/tasks/${taskId}/logs${suffix}`, projectId));
}
export function fetchSessionFiles(taskId: string, projectId?: string): Promise<string[]> {

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback, useRef } from "react";
import { Globe, Folder } from "lucide-react";
import { THINKING_LEVELS, GLOBAL_SETTINGS_KEYS, PROJECT_SETTINGS_KEYS, PROMPT_KEY_CATALOG } from "@fusion/core";
import { THINKING_LEVELS, PROMPT_KEY_CATALOG, isGlobalSettingsKey, isProjectSettingsKey } from "@fusion/core";
import type { Settings, GlobalSettings, ThemeMode, ColorTheme, ModelPreset, NtfyNotificationEvent } from "@fusion/core";
import { fetchSettings, updateSettings, updateGlobalSettings, fetchAuthStatus, loginProvider, logoutProvider, saveApiKey, clearApiKey, fetchModels, testNtfyNotification, fetchBackups, createBackup, exportSettings, importSettings, fetchMemory, saveMemory } from "../api";
import type { AuthProvider, ModelInfo, BackupListResponse, SettingsExportData } from "../api";
@@ -497,19 +497,17 @@ export function SettingsModal({
// updateGlobalSettings ignores project keys). This ensures fields in sections
// are persisted correctly based on their scope.
const globalKeySet = new Set<string>(GLOBAL_SETTINGS_KEYS);
const globalPatch: Partial<GlobalSettings> = {};
for (const [key, value] of Object.entries(payload)) {
if (globalKeySet.has(key)) {
if (isGlobalSettingsKey(key)) {
(globalPatch as any)[key] = value;
}
}
const projectKeySet = new Set<string>(PROJECT_SETTINGS_KEYS as readonly string[]);
const projectPatch: Partial<Settings> = {};
for (const [key, value] of Object.entries(payload)) {
if (key === "githubTokenConfigured") continue; // server-only field
if (projectKeySet.has(key)) {
if (isProjectSettingsKey(key)) {
(projectPatch as any)[key] = value;
}
}

View File

@@ -72,7 +72,7 @@ describe("useAgentLogs", () => {
expect(result.current.entries).toEqual(historicalLogs);
});
expect(mockFetchAgentLogs).toHaveBeenCalledWith("FN-001", undefined);
expect(mockFetchAgentLogs).toHaveBeenCalledWith("FN-001", undefined, { limit: 500 });
expect(MockEventSource.instances).toHaveLength(1);
expect(MockEventSource.instances[0].url).toBe("/api/tasks/FN-001/logs/stream");
});

View File

@@ -98,8 +98,8 @@ describe("useMultiAgentLogs", () => {
expect(result.current["FN-002"].entries).toEqual(logs2);
});
expect(mockFetchAgentLogs).toHaveBeenCalledWith("FN-001");
expect(mockFetchAgentLogs).toHaveBeenCalledWith("FN-002");
expect(mockFetchAgentLogs).toHaveBeenCalledWith("FN-001", undefined, { limit: 500 });
expect(mockFetchAgentLogs).toHaveBeenCalledWith("FN-002", undefined, { limit: 500 });
});
it("opens SSE EventSource for each task ID", async () => {

View File

@@ -22,7 +22,7 @@ function capLogEntries(entries: AgentLogEntry[]): AgentLogEntry[] {
* Hook that manages agent log fetching and live SSE streaming for a task.
*
* When `enabled` is true:
* 1. Fetches historical logs via GET /api/tasks/:id/logs
* 1. Fetches recent historical logs via GET /api/tasks/:id/logs?limit=500
* 2. Opens an EventSource to /api/tasks/:id/logs/stream for live updates
* 3. Merges historical + live entries in order
*
@@ -53,7 +53,7 @@ export function useAgentLogs(taskId: string | null, enabled: boolean, projectId?
setLoading(true);
try {
const historical = await fetchAgentLogs(currentTaskId, projectId);
const historical = await fetchAgentLogs(currentTaskId, projectId, { limit: MAX_LOG_ENTRIES });
if (cancelled) return;
setEntries(capLogEntries(historical));
} catch {

View File

@@ -35,7 +35,7 @@ interface InitState {
* Hook that manages agent log fetching and live SSE streaming for multiple tasks.
*
* For each task ID in the provided array:
* 1. Fetches historical logs via GET /api/tasks/:id/logs
* 1. Fetches recent historical logs via GET /api/tasks/:id/logs?limit=500
* 2. Opens an EventSource to /api/tasks/:id/logs/stream for live updates
* 3. Merges historical + live entries in order
*
@@ -195,7 +195,7 @@ export function useMultiAgentLogs(taskIds: string[]): LogStateMap {
es.addEventListener("error", handleError);
// Fetch historical logs
void fetchAgentLogs(taskId)
void fetchAgentLogs(taskId, undefined, { limit: MAX_LOG_ENTRIES })
.then((historical) => {
if (cancelled[taskId]) return;