Harden architecture hot paths
This commit is contained in:
@@ -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[]> {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user