Files
openhuman/app/src/features/daemon/store.ts
T
Steven EnamakelandGitHub acc6246e59 Refactor core-polled app state and screen intelligence status (#464)
* refactor(accessibility): remove device control and predictive input features from accessibility settings

- Updated accessibility-related components and tests to eliminate device control and predictive input features.
- Adjusted AccessibilityPanel and ScreenIntelligencePanel to reflect the removal of these features.
- Modified related tests to ensure consistency with the updated accessibility status structure.
- Cleaned up accessibility session parameters and state management to focus solely on screen monitoring.

* refactor(accessibility): streamline featureOverrides state initialization

- Simplified the initialization of featureOverrides state in AccessibilityPanel and ScreenIntelligencePanel components for better readability.
- Consolidated parameter definitions in startAccessibilitySession to enhance clarity and maintainability.
- Removed unnecessary re-exports in the screen_intelligence engine module to clean up the codebase.

* chore(dependencies): update OpenHuman to version 0.51.19 in Cargo.lock

* chore(dependencies): update OpenHuman version to 0.51.19 in Cargo.lock

* feat(restart): implement core process restart functionality

- Added a new `SystemRestartRequested` event to the `DomainEvent` enum to handle restart requests.
- Introduced a `RestartSubscriber` that listens for restart events and manages the process respawn.
- Created a `service_restart` function to publish restart requests via the event bus.
- Updated service schemas to include a new `restart` controller with parameters for source and reason.
- Enhanced documentation to reflect changes in behavior and added necessary code comments.

* feat(accessibility): add last restart summary to Screen Intelligence Panel

- Introduced `lastRestartSummary` to the accessibility state and updated relevant components to display the last successful core restart information.
- Modified `PermissionsSection` and `ScreenIntelligencePanel` to include the new summary.
- Updated tests to validate the display of the last restart summary and ensure proper state management during core restarts.
- Refactored accessibility slice to handle the new restart summary in state updates.

* feat(core): enhance startup process with restart delay and subscriber registration

- Added a call to apply startup restart delay from environment variables in `run_core_from_args`.
- Updated the `bootstrap_skill_runtime` function to register a `RestartSubscriber` for handling restart requests, ensuring consistent respawn logic across triggers.
- Introduced a new `core_process` field in the `AccessibilityEngine` to track the core process status, including its PID and start time.
- Implemented a helper function to capture the core process start time using `OnceLock` for efficient initialization.

* feat(screen-intelligence): refactor accessibility state management and UI components

- Replaced direct Redux state access with a new `useScreenIntelligenceState` hook across multiple components, including `AccessibilityPanel`, `ScreenIntelligencePanel`, and their respective subcomponents.
- Streamlined permission and session handling by consolidating related functions and removing unnecessary dispatch calls.
- Updated tests to mock the new state management approach, ensuring consistent behavior and validation of UI elements.
- Removed the `SessionAndVisionSection` component to simplify the structure and improve maintainability.
- Introduced a new API file for screen intelligence to encapsulate related functionality and improve code organization.

* refactor(tests): clean up and optimize test files for accessibility and screen intelligence panels

- Removed redundant imports and streamlined the structure of test files for `AccessibilityPanel` and `ScreenIntelligencePanel`.
- Consolidated core process state initialization in test mocks for better readability.
- Updated dependency imports and ensured consistent mocking of state management hooks across tests.
- Enhanced the `ScreenPermissionsStep` component by improving the dependency array in the useEffect hook for better performance.

* refactor(store): remove unused authentication and user management code

- Deleted the `UserProvider`, `authSlice`, `authSelectors`, `userSlice`, `teamSlice`, and related test files to streamline the codebase.
- This cleanup enhances maintainability by removing legacy code that is no longer in use.
- Updated the store configuration to reflect the removal of these slices and ensure proper state management.

* refactor(webhooks): reorganize types and remove legacy state management

- Moved `TunnelRegistration` and `WebhookActivityEntry` types to a new `types.ts` file for better organization.
- Updated imports in `TunnelList` and `WebhookActivity` components to reference the new types location.
- Refactored `useWebhooks` hook to eliminate Redux state management in favor of local state, enhancing performance and reducing complexity.
- Removed unused `aiSlice`, `inviteSlice`, and `webhooksSlice` along with their associated tests to streamline the codebase.

* refactor(daemon): migrate state management from Redux to a custom store

- Introduced a new `store.ts` file to manage daemon state, replacing the previous Redux slice.
- Updated components and hooks to utilize the new state management approach, enhancing performance and reducing complexity.
- Removed the legacy `daemonSlice` and associated Redux logic, streamlining the codebase.
- Adjusted imports in various components and hooks to reference the new store structure.

* refactor(screen-intelligence): integrate core state management and enhance status handling

- Replaced direct state management in `useScreenIntelligenceState` with a new core state approach, utilizing `useCoreState` for improved performance and consistency.
- Updated status fetching and permission handling to leverage the core state snapshot, streamlining the logic and reducing redundant API calls.
- Introduced a new `CoreRuntimeSnapshot` interface to encapsulate runtime statuses, including screen intelligence, local AI, autocomplete, and service states.
- Adjusted related components and hooks to align with the new state management structure, enhancing maintainability and readability.
- Updated tests to validate the new runtime state structure and ensure proper functionality across the application.

* refactor(components): reorganize imports and streamline function formatting

- Moved the import of `Tunnel` and `tunnelsApi` in `TunnelList.tsx` for better organization.
- Reformatted function definitions in `store.ts`, `useDaemonHealth.ts`, `useDaemonLifecycle.ts`, `useWebhooks.ts` for improved readability.
- Cleaned up the structure of test files in `coreRpcClient.test.ts` by consolidating object properties for clarity.
- These changes enhance code maintainability and readability across the application.

* test(screen-intelligence): fix duplicate hook imports

* fix(tests): update ScreenIntelligenceDebugPanel test to use baseState for refresh status and vision calls

* refactor(invites): simplify error message rendering in Invites component

- Consolidated the conditional rendering of the load error message in the Invites component for improved readability.
- This change enhances the clarity of the code without altering functionality.

* refactor(daemon): streamline state management and function definitions

- Removed the `healthTimeoutId` from the `DaemonUserState` interface and related functions to simplify state management.
- Converted several functions in `store.ts` to arrow function syntax for consistency and improved readability.
- Updated the `Invites` component to handle asynchronous loading and error states more effectively, ensuring that in-flight requests are properly managed.
- Refactored the `CoreStateProvider` to enhance the refresh logic and prevent multiple simultaneous refreshes.
- Introduced a new `register_domain_subscribers` function in `jsonrpc.rs` to centralize event bus subscriber registration, improving code organization and maintainability.

* fix: add debug logging, atomic restart guard, and idempotent subscriber registration

- CoreStateProvider: add namespaced debug logger for polling failure diagnostics
- service/bus.rs: add AtomicBool gate to prevent duplicate restart spawns
- service/bus.rs: use OnceLock for idempotent RestartSubscriber registration
- Invites.tsx: add debug log in loadInviteCodes catch block

* style: apply prettier formatting to CoreStateProvider

* fix: sanitize error logging, serialize refresh, and demote restart logs

- CoreStateProvider: sanitize error objects in poll failure logs to avoid
  leaking tokens/headers
- CoreStateProvider: move in-flight guard into refresh() via shared promise
  so all callers (poll, updateLocalState, storeSessionToken) are serialized
- CoreStateProvider: log refreshTeams errors instead of swallowing them
- service/bus.rs: demote duplicate-restart log to debug, omit reason from
  log output to avoid free-form text emission

* style: apply cargo fmt to service/bus.rs
2026-04-09 15:51:27 -07:00

147 lines
4.2 KiB
TypeScript

import { useSyncExternalStore } from 'react';
export type DaemonStatus = 'starting' | 'stopping' | 'running' | 'error' | 'disconnected';
export type ComponentStatus = 'ok' | 'error' | 'starting';
export interface ComponentHealth {
status: ComponentStatus;
updated_at: string;
last_ok?: string;
last_error?: string;
restart_count: number;
}
export interface HealthSnapshot {
pid: number;
updated_at: string;
uptime_seconds: number;
components: Record<string, ComponentHealth>;
}
export interface DaemonUserState {
status: DaemonStatus;
healthSnapshot: HealthSnapshot | null;
components: {
gateway?: ComponentHealth;
channels?: ComponentHealth;
heartbeat?: ComponentHealth;
scheduler?: ComponentHealth;
};
lastHealthUpdate: string | null;
connectionAttempts: number;
autoStartEnabled: boolean;
isRecovering: boolean;
}
interface DaemonState {
byUser: Record<string, DaemonUserState>;
}
const initialUserState: DaemonUserState = {
status: 'disconnected',
healthSnapshot: null,
components: {},
lastHealthUpdate: null,
connectionAttempts: 0,
autoStartEnabled: false,
isRecovering: false,
};
let daemonState: DaemonState = { byUser: {} };
const listeners = new Set<() => void>();
const emitChange = (): void => {
for (const listener of listeners) {
listener();
}
};
const currentUserState = (userId: string): DaemonUserState =>
daemonState.byUser[userId] ?? initialUserState;
const updateUserState = (
userId: string,
updater: (current: DaemonUserState) => DaemonUserState
): void => {
daemonState = {
...daemonState,
byUser: { ...daemonState.byUser, [userId]: updater(currentUserState(userId)) },
};
emitChange();
};
export const subscribeDaemonStore = (listener: () => void): (() => void) => {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
};
export const getDaemonUserState = (userId?: string): DaemonUserState =>
currentUserState(userId || '__pending__');
export const useDaemonUserState = (userId?: string): DaemonUserState =>
useSyncExternalStore(
subscribeDaemonStore,
() => getDaemonUserState(userId),
() => getDaemonUserState(userId)
);
export const updateHealthSnapshot = (userId: string, healthSnapshot: HealthSnapshot): void => {
updateUserState(userId, current => {
const componentStatuses = Object.values(healthSnapshot.components).map(
component => component.status
);
let status = current.status;
if (componentStatuses.length === 0) {
status = 'disconnected';
} else if (componentStatuses.every(componentStatus => componentStatus === 'ok')) {
status = 'running';
} else if (componentStatuses.some(componentStatus => componentStatus === 'error')) {
status = 'error';
} else if (componentStatuses.some(componentStatus => componentStatus === 'starting')) {
status = 'starting';
}
return {
...current,
status,
healthSnapshot,
components: healthSnapshot.components,
lastHealthUpdate: new Date().toISOString(),
isRecovering: status === 'running' ? false : current.isRecovering,
connectionAttempts: status === 'running' ? 0 : current.connectionAttempts,
};
});
};
export const setDaemonStatus = (userId: string, status: DaemonStatus): void => {
updateUserState(userId, current => ({
...current,
status,
healthSnapshot: status === 'disconnected' ? null : current.healthSnapshot,
components: status === 'disconnected' ? {} : current.components,
lastHealthUpdate: status === 'disconnected' ? null : current.lastHealthUpdate,
}));
};
export const incrementConnectionAttempts = (userId: string): void => {
updateUserState(userId, current => ({
...current,
connectionAttempts: current.connectionAttempts + 1,
}));
};
export const resetConnectionAttempts = (userId: string): void => {
updateUserState(userId, current => ({ ...current, connectionAttempts: 0 }));
};
export const setAutoStartEnabled = (userId: string, enabled: boolean): void => {
updateUserState(userId, current => ({ ...current, autoStartEnabled: enabled }));
};
export const setIsRecovering = (userId: string, isRecovering: boolean): void => {
updateUserState(userId, current => ({ ...current, isRecovering }));
};