diff --git a/AGENTS.md b/AGENTS.md index f5b8bc175..4f6e12748 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,7 +32,7 @@ CodexMonitor is a macOS Tauri app that orchestrates Codex agents across local wo - **Components**: presentational only; props in, UI out; no Tauri IPC calls. - **Hooks**: own state, side-effects, and event wiring (e.g., app-server events). - **Utils**: pure helpers live in `src/utils/` (no React hooks here). -- **Services**: all Tauri IPC goes through `src/services/tauri.ts`. +- **Services**: all Tauri IPC goes through `src/services/` (prefer `src/services/tauri.ts`; event subscriptions can live in `src/services/events.ts`). - **Types**: shared UI data types live in `src/types.ts`. - **Styles**: one CSS file per UI area in `src/styles/` (no global refactors in components). - **Backend IPC**: add new commands in `src-tauri/src/lib.rs` and mirror them in the service. diff --git a/src/features/app/hooks/useAppServerEvents.ts b/src/features/app/hooks/useAppServerEvents.ts index 8cd6aca14..d44d3e01c 100644 --- a/src/features/app/hooks/useAppServerEvents.ts +++ b/src/features/app/hooks/useAppServerEvents.ts @@ -1,6 +1,6 @@ import { useEffect } from "react"; -import { listen } from "@tauri-apps/api/event"; import type { AppServerEvent, ApprovalRequest } from "../../../types"; +import { subscribeAppServerEvents } from "../../../services/events"; type AgentDelta = { workspaceId: string; @@ -58,10 +58,10 @@ export function useAppServerEvents(handlers: AppServerEventHandlers) { useEffect(() => { let unlisten: (() => void) | null = null; let canceled = false; - listen("app-server-event", (event) => { - handlers.onAppServerEvent?.(event.payload); + subscribeAppServerEvents((payload) => { + handlers.onAppServerEvent?.(payload); - const { workspace_id, message } = event.payload; + const { workspace_id, message } = payload; const method = String(message.method ?? ""); if (method === "codex/connected") { diff --git a/src/features/terminal/hooks/useTerminalSession.ts b/src/features/terminal/hooks/useTerminalSession.ts index 1c7517beb..301e97b41 100644 --- a/src/features/terminal/hooks/useTerminalSession.ts +++ b/src/features/terminal/hooks/useTerminalSession.ts @@ -1,11 +1,11 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { RefObject } from "react"; -import { listen } from "@tauri-apps/api/event"; import { Terminal } from "@xterm/xterm"; import { FitAddon } from "@xterm/addon-fit"; import "@xterm/xterm/css/xterm.css"; import type { DebugEntry, TerminalStatus, WorkspaceInfo } from "../../../types"; import { buildErrorDebugEntry } from "../../../utils/debugEntries"; +import { subscribeTerminalOutput, type TerminalOutputEvent } from "../../../services/events"; import { openTerminalSession, resizeTerminalSession, @@ -14,12 +14,6 @@ import { const MAX_BUFFER_CHARS = 200_000; -type TerminalOutputEvent = { - workspaceId: string; - terminalId: string; - data: string; -}; - type UseTerminalSessionOptions = { activeWorkspace: WorkspaceInfo | null; activeTerminalId: string | null; @@ -122,8 +116,8 @@ export function useTerminalSession({ useEffect(() => { let unlisten: (() => void) | null = null; let canceled = false; - listen("terminal-output", (event) => { - const { workspaceId, terminalId, data } = event.payload; + subscribeTerminalOutput((payload: TerminalOutputEvent) => { + const { workspaceId, terminalId, data } = payload; const key = `${workspaceId}:${terminalId}`; const next = appendBuffer(outputBuffersRef.current.get(key), data); outputBuffersRef.current.set(key, next); diff --git a/src/services/events.ts b/src/services/events.ts new file mode 100644 index 000000000..9ebfafb7d --- /dev/null +++ b/src/services/events.ts @@ -0,0 +1,26 @@ +import { listen } from "@tauri-apps/api/event"; +import type { AppServerEvent } from "../types"; + +export type Unsubscribe = () => void; + +export type TerminalOutputEvent = { + workspaceId: string; + terminalId: string; + data: string; +}; + +export async function subscribeAppServerEvents( + onEvent: (event: AppServerEvent) => void, +): Promise { + return listen("app-server-event", (event) => { + onEvent(event.payload); + }); +} + +export async function subscribeTerminalOutput( + onEvent: (event: TerminalOutputEvent) => void, +): Promise { + return listen("terminal-output", (event) => { + onEvent(event.payload); + }); +}