diff --git a/src/features/agents/components/AgentInspectPanels.tsx b/src/features/agents/components/AgentInspectPanels.tsx index 0e8a0a96..b4d4d280 100644 --- a/src/features/agents/components/AgentInspectPanels.tsx +++ b/src/features/agents/components/AgentInspectPanels.tsx @@ -301,8 +301,6 @@ export const AgentSettingsPanel = ({ onUpdateAgentPermissions = () => {}, onDelete, canDelete = true, - onToolCallingToggle, - onThinkingTracesToggle, cronJobs, cronLoading, cronError, diff --git a/src/features/agents/operations/useAgentSettingsMutationController.ts b/src/features/agents/operations/useAgentSettingsMutationController.ts index 1c5cd380..67a57ac7 100644 --- a/src/features/agents/operations/useAgentSettingsMutationController.ts +++ b/src/features/agents/operations/useAgentSettingsMutationController.ts @@ -78,6 +78,7 @@ export type UseAgentSettingsMutationControllerParams = { }; export function useAgentSettingsMutationController(params: UseAgentSettingsMutationControllerParams) { + const { agents, loadAgents, setMobilePaneChat, status } = params; const skillsLoadRequestIdRef = useRef(0); const [settingsSkillsReport, setSettingsSkillsReport] = useState(null); const [settingsSkillsLoading, setSettingsSkillsLoading] = useState(false); @@ -384,21 +385,21 @@ export function useAgentSettingsMutationController(params: UseAgentSettingsMutat if (!restartingMutationBlock) return; if (restartingMutationBlock.kind !== "delete-agent") return; if (restartingMutationBlock.phase !== "awaiting-restart") return; - if (params.status !== "connected") return; + if (status !== "connected") return; - const deletedAgentStillPresent = params.agents.some( + const deletedAgentStillPresent = agents.some( (entry) => entry.agentId === restartingMutationBlock.agentId ); if (!deletedAgentStillPresent) { setRestartingMutationBlock(null); - params.setMobilePaneChat(); + setMobilePaneChat(); return; } let cancelled = false; const refreshAgents = async () => { try { - await params.loadAgents(); + await loadAgents(); } catch (error) { if (!isGatewayDisconnectLikeError(error)) { console.error("Failed to refresh agents while awaiting delete restart.", error); @@ -417,10 +418,10 @@ export function useAgentSettingsMutationController(params: UseAgentSettingsMutat window.clearInterval(intervalId); }; }, [ - params.agents, - params.loadAgents, - params.setMobilePaneChat, - params.status, + agents, + loadAgents, + setMobilePaneChat, + status, restartingMutationBlock, ]); diff --git a/src/features/agents/operations/useGatewayConfigSyncController.ts b/src/features/agents/operations/useGatewayConfigSyncController.ts index 4c6c5d19..61ba1ff7 100644 --- a/src/features/agents/operations/useGatewayConfigSyncController.ts +++ b/src/features/agents/operations/useGatewayConfigSyncController.ts @@ -45,46 +45,54 @@ export type GatewayConfigSyncController = { export function useGatewayConfigSyncController( params: UseGatewayConfigSyncControllerParams ): GatewayConfigSyncController { + const { + client, + status, + settingsRouteActive, + inspectSidebarAgentId, + gatewayConfigSnapshot, + setGatewayConfigSnapshot, + setGatewayModels, + setGatewayModelsError, + enqueueConfigMutation, + loadAgents, + isDisconnectLikeError, + logError: logErrorOverride, + } = params; const sandboxRepairAttemptedRef = useRef(false); - const logError = params.logError ?? defaultLogError; + const logError = logErrorOverride ?? defaultLogError; const refreshGatewayConfigSnapshot = useCallback(async () => { - if (params.status !== "connected") return null; + if (status !== "connected") return null; try { - const snapshot = await params.client.call("config.get", {}); - params.setGatewayConfigSnapshot(snapshot); + const snapshot = await client.call("config.get", {}); + setGatewayConfigSnapshot(snapshot); return snapshot; } catch (err) { - if (!params.isDisconnectLikeError(err)) { + if (!isDisconnectLikeError(err)) { logError("Failed to refresh gateway config.", err); } return null; } - }, [ - params.client, - params.isDisconnectLikeError, - params.setGatewayConfigSnapshot, - params.status, - logError, - ]); + }, [client, isDisconnectLikeError, setGatewayConfigSnapshot, status, logError]); useEffect(() => { const repairIntent = resolveSandboxRepairIntent({ - status: params.status, + status, attempted: sandboxRepairAttemptedRef.current, - snapshot: params.gatewayConfigSnapshot, + snapshot: gatewayConfigSnapshot, }); if (repairIntent.kind !== "repair") return; sandboxRepairAttemptedRef.current = true; - void params.enqueueConfigMutation({ + void enqueueConfigMutation({ kind: "repair-sandbox-tool-allowlist", label: "Repair sandbox tool access", run: async () => { for (const agentId of repairIntent.agentIds) { await updateGatewayAgentOverrides({ - client: params.client, + client, agentId, overrides: { tools: { @@ -97,41 +105,30 @@ export function useGatewayConfigSyncController( }, }); } - await params.loadAgents(); + await loadAgents(); }, }); - }, [ - params.client, - params.enqueueConfigMutation, - params.gatewayConfigSnapshot, - params.loadAgents, - params.status, - ]); + }, [client, enqueueConfigMutation, gatewayConfigSnapshot, loadAgents, status]); useEffect(() => { if ( !shouldRefreshGatewayConfigForSettingsRoute({ - status: params.status, - settingsRouteActive: params.settingsRouteActive, - inspectSidebarAgentId: params.inspectSidebarAgentId, + status, + settingsRouteActive, + inspectSidebarAgentId, }) ) { return; } void refreshGatewayConfigSnapshot(); - }, [ - params.inspectSidebarAgentId, - params.settingsRouteActive, - params.status, - refreshGatewayConfigSnapshot, - ]); + }, [inspectSidebarAgentId, settingsRouteActive, status, refreshGatewayConfigSnapshot]); useEffect(() => { - const syncIntent = resolveGatewayModelsSyncIntent({ status: params.status }); + const syncIntent = resolveGatewayModelsSyncIntent({ status }); if (syncIntent.kind === "clear") { - params.setGatewayModels([]); - params.setGatewayModelsError(null); - params.setGatewayConfigSnapshot(null); + setGatewayModels([]); + setGatewayModelsError(null); + setGatewayConfigSnapshot(null); return; } @@ -139,31 +136,28 @@ export function useGatewayConfigSyncController( const loadModels = async () => { let configSnapshot: GatewayModelPolicySnapshot | null = null; try { - configSnapshot = await params.client.call("config.get", {}); + configSnapshot = await client.call("config.get", {}); if (!cancelled) { - params.setGatewayConfigSnapshot(configSnapshot); + setGatewayConfigSnapshot(configSnapshot); } } catch (err) { - if (!params.isDisconnectLikeError(err)) { + if (!isDisconnectLikeError(err)) { logError("Failed to load gateway config.", err); } } try { - const result = await params.client.call<{ models: GatewayModelChoice[] }>( - "models.list", - {} - ); + const result = await client.call<{ models: GatewayModelChoice[] }>("models.list", {}); if (cancelled) return; const catalog = Array.isArray(result.models) ? result.models : []; - params.setGatewayModels(buildGatewayModelChoices(catalog, configSnapshot)); - params.setGatewayModelsError(null); + setGatewayModels(buildGatewayModelChoices(catalog, configSnapshot)); + setGatewayModelsError(null); } catch (err) { if (cancelled) return; const message = err instanceof Error ? err.message : "Failed to load models."; - params.setGatewayModelsError(message); - params.setGatewayModels([]); - if (!params.isDisconnectLikeError(err)) { + setGatewayModelsError(message); + setGatewayModels([]); + if (!isDisconnectLikeError(err)) { logError("Failed to load gateway models.", err); } } @@ -174,12 +168,12 @@ export function useGatewayConfigSyncController( cancelled = true; }; }, [ - params.client, - params.isDisconnectLikeError, - params.setGatewayConfigSnapshot, - params.setGatewayModels, - params.setGatewayModelsError, - params.status, + client, + isDisconnectLikeError, + setGatewayConfigSnapshot, + setGatewayModels, + setGatewayModelsError, + status, logError, ]); diff --git a/src/features/agents/operations/useRuntimeSyncController.ts b/src/features/agents/operations/useRuntimeSyncController.ts index 9aa59a42..9b48c219 100644 --- a/src/features/agents/operations/useRuntimeSyncController.ts +++ b/src/features/agents/operations/useRuntimeSyncController.ts @@ -63,16 +63,28 @@ export type RuntimeSyncController = { export function useRuntimeSyncController( params: UseRuntimeSyncControllerParams ): RuntimeSyncController { - const agentsRef = useRef(params.agents); + const { + client, + status, + agents, + focusedAgentId, + focusedAgentRunning, + dispatch, + clearRunTracking, + isDisconnectLikeError, + defaultHistoryLimit: defaultHistoryLimitOverride, + maxHistoryLimit: maxHistoryLimitOverride, + } = params; + const agentsRef = useRef(agents); const historyInFlightRef = useRef>(new Set()); const reconcileRunInFlightRef = useRef>(new Set()); - const defaultHistoryLimit = params.defaultHistoryLimit ?? RUNTIME_SYNC_DEFAULT_HISTORY_LIMIT; - const maxHistoryLimit = params.maxHistoryLimit ?? RUNTIME_SYNC_MAX_HISTORY_LIMIT; + const defaultHistoryLimit = defaultHistoryLimitOverride ?? RUNTIME_SYNC_DEFAULT_HISTORY_LIMIT; + const maxHistoryLimit = maxHistoryLimitOverride ?? RUNTIME_SYNC_MAX_HISTORY_LIMIT; useEffect(() => { - agentsRef.current = params.agents; - }, [params.agents]); + agentsRef.current = agents; + }, [agents]); const clearHistoryInFlight = useCallback((sessionKey: string) => { const key = sessionKey.trim(); @@ -86,39 +98,39 @@ export function useRuntimeSyncController( agents: snapshotAgents, maxKeys: 64, }); - if (summaryIntent.kind === "skip") return; - const activeAgents = snapshotAgents.filter((agent) => agent.sessionCreated); - try { - const [statusSummary, previewResult] = await Promise.all([ - params.client.call("status", {}), - params.client.call("sessions.preview", { - keys: summaryIntent.keys, - limit: summaryIntent.limit, - maxChars: summaryIntent.maxChars, - }), + if (summaryIntent.kind === "skip") return; + const activeAgents = snapshotAgents.filter((agent) => agent.sessionCreated); + try { + const [statusSummary, previewResult] = await Promise.all([ + client.call("status", {}), + client.call("sessions.preview", { + keys: summaryIntent.keys, + limit: summaryIntent.limit, + maxChars: summaryIntent.maxChars, + }), ]); for (const entry of buildSummarySnapshotPatches({ agents: activeAgents, statusSummary, previewResult, })) { - params.dispatch({ - type: "updateAgent", - agentId: entry.agentId, - patch: entry.patch, - }); + dispatch({ + type: "updateAgent", + agentId: entry.agentId, + patch: entry.patch, + }); + } + } catch (error) { + if (!isDisconnectLikeError(error)) { + console.error("Failed to load summary snapshot.", error); + } } - } catch (error) { - if (!params.isDisconnectLikeError(error)) { - console.error("Failed to load summary snapshot.", error); - } - } - }, [params.client, params.dispatch, params.isDisconnectLikeError]); + }, [client, dispatch, isDisconnectLikeError]); const loadAgentHistory = useCallback( async (agentId: string, options?: { limit?: number }) => { const commands = await runHistorySyncOperation({ - client: params.client, + client, agentId, requestedLimit: options?.limit, getAgent: (targetAgentId) => @@ -132,19 +144,13 @@ export function useRuntimeSyncController( }); executeHistorySyncCommands({ commands, - dispatch: params.dispatch, + dispatch, logMetric: (metric, meta) => logTranscriptDebugMetric(metric, meta), - isDisconnectLikeError: params.isDisconnectLikeError, + isDisconnectLikeError, logError: (message, error) => console.error(message, error), }); }, - [ - defaultHistoryLimit, - maxHistoryLimit, - params.client, - params.dispatch, - params.isDisconnectLikeError, - ] + [client, defaultHistoryLimit, dispatch, isDisconnectLikeError, maxHistoryLimit] ); const loadMoreAgentHistory = useCallback( @@ -161,9 +167,9 @@ export function useRuntimeSyncController( ); const reconcileRunningAgents = useCallback(async () => { - if (params.status !== "connected") return; + if (status !== "connected") return; const commands = await runAgentReconcileOperation({ - client: params.client, + client, agents: agentsRef.current, getLatestAgent: (agentId) => agentsRef.current.find((entry) => entry.agentId === agentId) ?? null, @@ -179,35 +185,28 @@ export function useRuntimeSyncController( if (!normalized) return; reconcileRunInFlightRef.current.delete(normalized); }, - isDisconnectLikeError: params.isDisconnectLikeError, + isDisconnectLikeError, }); executeAgentReconcileCommands({ commands, - dispatch: params.dispatch, - clearRunTracking: params.clearRunTracking, + dispatch, + clearRunTracking, requestHistoryRefresh: (agentId) => { void loadAgentHistory(agentId); }, logInfo: (message) => console.info(message), logWarn: (message, error) => console.warn(message, error), }); - }, [ - loadAgentHistory, - params.clearRunTracking, - params.client, - params.dispatch, - params.isDisconnectLikeError, - params.status, - ]); + }, [clearRunTracking, client, dispatch, isDisconnectLikeError, loadAgentHistory, status]); useEffect(() => { - if (params.status !== "connected") return; + if (status !== "connected") return; void loadSummarySnapshot(); - }, [loadSummarySnapshot, params.status]); + }, [loadSummarySnapshot, status]); useEffect(() => { const reconcileIntent = resolveRuntimeSyncReconcilePollingIntent({ - status: params.status, + status, }); if (reconcileIntent.kind === "stop") return; void reconcileRunningAgents(); @@ -217,23 +216,23 @@ export function useRuntimeSyncController( return () => { window.clearInterval(timer); }; - }, [params.status, reconcileRunningAgents]); + }, [status, reconcileRunningAgents]); useEffect(() => { const bootstrapAgentIds = resolveRuntimeSyncBootstrapHistoryAgentIds({ - status: params.status, - agents: params.agents, + status, + agents, }); for (const agentId of bootstrapAgentIds) { void loadAgentHistory(agentId); } - }, [loadAgentHistory, params.agents, params.status]); + }, [agents, loadAgentHistory, status]); useEffect(() => { const pollingIntent = resolveRuntimeSyncFocusedHistoryPollingIntent({ - status: params.status, - focusedAgentId: params.focusedAgentId, - focusedAgentRunning: params.focusedAgentRunning, + status, + focusedAgentId, + focusedAgentRunning, }); if (pollingIntent.kind === "stop") return; void loadAgentHistory(pollingIntent.agentId); @@ -248,10 +247,10 @@ export function useRuntimeSyncController( return () => { window.clearInterval(timer); }; - }, [loadAgentHistory, params.focusedAgentId, params.focusedAgentRunning, params.status]); + }, [focusedAgentId, focusedAgentRunning, loadAgentHistory, status]); useEffect(() => { - return params.client.onGap((info) => { + return client.onGap((info) => { const recoveryIntent = resolveRuntimeSyncGapRecoveryIntent(); console.warn(`Gateway event gap expected ${info.expected}, received ${info.received}.`); if (recoveryIntent.refreshSummarySnapshot) { @@ -261,7 +260,7 @@ export function useRuntimeSyncController( void reconcileRunningAgents(); } }); - }, [loadSummarySnapshot, params.client, reconcileRunningAgents]); + }, [client, loadSummarySnapshot, reconcileRunningAgents]); return { loadSummarySnapshot, diff --git a/src/features/agents/operations/useSettingsRouteController.ts b/src/features/agents/operations/useSettingsRouteController.ts index 331e02ed..02d99e5e 100644 --- a/src/features/agents/operations/useSettingsRouteController.ts +++ b/src/features/agents/operations/useSettingsRouteController.ts @@ -90,81 +90,101 @@ const executeSettingsRouteCommands = ( export function useSettingsRouteController( params: UseSettingsRouteControllerParams ): SettingsRouteController { + const { + settingsRouteActive, + settingsRouteAgentId, + status, + agentsLoadedOnce, + selectedAgentId, + focusedAgentId, + personalityHasUnsavedChanges, + activeTab, + inspectSidebar, + agents, + flushPendingDraft, + dispatchSelectAgent, + setInspectSidebar, + setMobilePaneChat, + setPersonalityHasUnsavedChanges, + push, + replace, + confirmDiscard, + } = params; const applyCommands = useCallback( (commands: SettingsRouteNavCommand[]) => { executeSettingsRouteCommands(commands, { - dispatchSelectAgent: params.dispatchSelectAgent, - setInspectSidebar: params.setInspectSidebar, - setMobilePaneChat: params.setMobilePaneChat, - setPersonalityHasUnsavedChanges: params.setPersonalityHasUnsavedChanges, - flushPendingDraft: params.flushPendingDraft, - push: params.push, - replace: params.replace, + dispatchSelectAgent, + setInspectSidebar, + setMobilePaneChat, + setPersonalityHasUnsavedChanges, + flushPendingDraft, + push, + replace, }); }, [ - params.dispatchSelectAgent, - params.flushPendingDraft, - params.push, - params.replace, - params.setInspectSidebar, - params.setMobilePaneChat, - params.setPersonalityHasUnsavedChanges, + dispatchSelectAgent, + flushPendingDraft, + push, + replace, + setInspectSidebar, + setMobilePaneChat, + setPersonalityHasUnsavedChanges, ] ); const handleBackToChat = useCallback(() => { const needsDiscardConfirmation = shouldConfirmDiscardPersonalityChanges({ - settingsRouteActive: params.settingsRouteActive, - activeTab: params.activeTab, - personalityHasUnsavedChanges: params.personalityHasUnsavedChanges, + settingsRouteActive, + activeTab, + personalityHasUnsavedChanges, }); - const discardConfirmed = needsDiscardConfirmation ? params.confirmDiscard() : true; + const discardConfirmed = needsDiscardConfirmation ? confirmDiscard() : true; const commands = planBackToChatCommands({ - settingsRouteActive: params.settingsRouteActive, - activeTab: params.activeTab, - personalityHasUnsavedChanges: params.personalityHasUnsavedChanges, + settingsRouteActive, + activeTab, + personalityHasUnsavedChanges, discardConfirmed, }); applyCommands(commands); }, [ + activeTab, applyCommands, - params.activeTab, - params.confirmDiscard, - params.personalityHasUnsavedChanges, - params.settingsRouteActive, + confirmDiscard, + personalityHasUnsavedChanges, + settingsRouteActive, ]); const handleSettingsRouteTabChange = useCallback( (nextTab: SettingsRouteTab) => { - const currentTab = params.inspectSidebar?.tab ?? "personality"; + const currentTab = inspectSidebar?.tab ?? "personality"; const needsDiscardConfirmation = currentTab === "personality" && nextTab !== "personality" && shouldConfirmDiscardPersonalityChanges({ - settingsRouteActive: params.settingsRouteActive, + settingsRouteActive, activeTab: currentTab, - personalityHasUnsavedChanges: params.personalityHasUnsavedChanges, + personalityHasUnsavedChanges, }); - const discardConfirmed = needsDiscardConfirmation ? params.confirmDiscard() : true; + const discardConfirmed = needsDiscardConfirmation ? confirmDiscard() : true; const commands = planSettingsTabChangeCommands({ nextTab, - currentInspectSidebar: params.inspectSidebar, - settingsRouteAgentId: params.settingsRouteAgentId, - settingsRouteActive: params.settingsRouteActive, - personalityHasUnsavedChanges: params.personalityHasUnsavedChanges, + currentInspectSidebar: inspectSidebar, + settingsRouteAgentId, + settingsRouteActive, + personalityHasUnsavedChanges, discardConfirmed, }); applyCommands(commands); }, [ applyCommands, - params.confirmDiscard, - params.inspectSidebar, - params.personalityHasUnsavedChanges, - params.settingsRouteActive, - params.settingsRouteAgentId, + confirmDiscard, + inspectSidebar, + personalityHasUnsavedChanges, + settingsRouteActive, + settingsRouteAgentId, ] ); @@ -172,79 +192,79 @@ export function useSettingsRouteController( (agentId: string) => { const commands = planOpenSettingsRouteCommands({ agentId, - currentInspectSidebar: params.inspectSidebar, - focusedAgentId: params.focusedAgentId, + currentInspectSidebar: inspectSidebar, + focusedAgentId, }); applyCommands(commands); }, - [applyCommands, params.focusedAgentId, params.inspectSidebar] + [applyCommands, focusedAgentId, inspectSidebar] ); const handleFleetSelectAgent = useCallback( (agentId: string) => { const commands = planFleetSelectCommands({ agentId, - currentInspectSidebar: params.inspectSidebar, - focusedAgentId: params.focusedAgentId, + currentInspectSidebar: inspectSidebar, + focusedAgentId, }); applyCommands(commands); }, - [applyCommands, params.focusedAgentId, params.inspectSidebar] + [applyCommands, focusedAgentId, inspectSidebar] ); useEffect(() => { - const routeAgentId = (params.settingsRouteAgentId ?? "").trim(); + const routeAgentId = (settingsRouteAgentId ?? "").trim(); const hasRouteAgent = routeAgentId - ? params.agents.some((agent) => agent.agentId === routeAgentId) + ? agents.some((agent) => agent.agentId === routeAgentId) : false; const commands = planSettingsRouteSyncCommands({ - settingsRouteActive: params.settingsRouteActive, - settingsRouteAgentId: params.settingsRouteAgentId, - status: params.status, - agentsLoadedOnce: params.agentsLoadedOnce, - selectedAgentId: params.selectedAgentId, + settingsRouteActive, + settingsRouteAgentId, + status, + agentsLoadedOnce, + selectedAgentId, hasRouteAgent, - currentInspectSidebar: params.inspectSidebar, + currentInspectSidebar: inspectSidebar, }); applyCommands(commands); }, [ + agents, + agentsLoadedOnce, applyCommands, - params.agents, - params.agentsLoadedOnce, - params.inspectSidebar, - params.selectedAgentId, - params.settingsRouteActive, - params.settingsRouteAgentId, - params.status, + inspectSidebar, + selectedAgentId, + settingsRouteActive, + settingsRouteAgentId, + status, ]); useEffect(() => { - const hasSelectedAgentInAgents = params.selectedAgentId - ? params.agents.some((agent) => agent.agentId === params.selectedAgentId) + const hasSelectedAgentInAgents = selectedAgentId + ? agents.some((agent) => agent.agentId === selectedAgentId) : false; - const hasInspectSidebarAgent = params.inspectSidebar?.agentId - ? params.agents.some((agent) => agent.agentId === params.inspectSidebar?.agentId) + const hasInspectSidebarAgent = inspectSidebar?.agentId + ? agents.some((agent) => agent.agentId === inspectSidebar?.agentId) : false; const commands = planNonRouteSelectionSyncCommands({ - settingsRouteActive: params.settingsRouteActive, - selectedAgentId: params.selectedAgentId, - focusedAgentId: params.focusedAgentId, + settingsRouteActive, + selectedAgentId, + focusedAgentId, hasSelectedAgentInAgents, - currentInspectSidebar: params.inspectSidebar, + currentInspectSidebar: inspectSidebar, hasInspectSidebarAgent, }); applyCommands(commands); }, [ + agents, applyCommands, - params.agents, - params.focusedAgentId, - params.inspectSidebar, - params.selectedAgentId, - params.settingsRouteActive, + focusedAgentId, + inspectSidebar, + selectedAgentId, + settingsRouteActive, ]); return { diff --git a/src/features/agents/state/livePatchQueue.ts b/src/features/agents/state/livePatchQueue.ts index 38e643c3..3cb7efb9 100644 --- a/src/features/agents/state/livePatchQueue.ts +++ b/src/features/agents/state/livePatchQueue.ts @@ -18,7 +18,9 @@ export const mergePendingLivePatch = ( } if (incomingRunId && !existingRunId) { - const { streamText: _dropStreamText, thinkingTrace: _dropThinkingTrace, ...rest } = existing; + const rest = { ...existing }; + delete rest.streamText; + delete rest.thinkingTrace; return { ...rest, ...incoming }; }