From 22435ed631385205a860157373ef52ce730f91ac Mon Sep 17 00:00:00 2001 From: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Date: Sun, 29 Mar 2026 21:08:38 -0700 Subject: [PATCH] Fix false service gate blocking when service is already running (#64) * Update CLAUDE.md to clarify pull request workflow and enhance documentation - Added a note to emphasize the importance of opening pull requests against the upstream repository instead of a fork's default remote, ensuring better collaboration and code management. - Improved the overall clarity of the Git workflow section, contributing to a more streamlined development process. * Refactor REPL session handling and remove deprecated chat methods - Removed the `repl_session_chat` functionality from the schema and related files, streamlining the REPL session management. - Introduced a new `start_chat` method in the web channel provider for handling chat interactions, enhancing the chat system's architecture. - Updated socket handling to improve event emission with alias support, ensuring better communication across the application. - Enhanced error handling and logging for chat operations, providing clearer feedback during interactions. - Consolidated chat-related logic into the new web channel module, improving maintainability and organization. * Refactor apiClient to improve token management and eliminate circular dependencies - Renamed `_getToken` to `authTokenGetterRef` for clarity and to avoid clashing with transpiled private method names. - Updated the `setStoreForApiClient` function to use the new `authTokenGetterRef` variable, ensuring the `apiClient` remains free of direct imports from `store/index`. - Enhanced the `ApiClient` class by renaming the `getToken` method to `resolveAuthToken`, improving readability and consistency in token retrieval logic. - Adjusted comments in `apiClient.ts` and `store/index.ts` to reflect the changes and clarify the purpose of the lazy store accessor. * Enhance API client integration with store for improved token management - Introduced `setStoreForApiClient` in `main.tsx` to bind the Redux store's auth token for API requests, ensuring seamless token access. - Updated comments in `apiClient.ts` to clarify the lazy token accessor's purpose and prevent circular dependencies. - Removed the previous direct call to `setStoreForApiClient` from `store/index.ts`, centralizing the setup in `main.tsx` for better organization. - Enhanced test setup to include the new store binding, ensuring consistent token retrieval during testing. * Refactor web channel event handling and enhance chat functionality - Updated `WebChatSseEvent` structure to include new fields: `tool_name`, `skill_id`, `args`, `output`, `success`, and `round`, improving the granularity of event data. - Refactored event emission logic in `emit_web_chat_event` to utilize the new fields, ensuring more informative payloads for tool calls and results. - Enhanced the `start_chat` and `cancel_chat` functions to accommodate the new event structure, improving chat session management. - Introduced a new function `publish_tool_events_from_history` to streamline the emission of tool call and result events from chat history, enhancing the overall chat experience. * Refactor web channel integration and enhance controller management - Updated references from the deprecated `web_channel` module to the new `channels::providers::web` structure, improving code organization and maintainability. - Refactored the `build_registered_controllers` and `build_declared_controller_schemas` functions to utilize the new web channel controller methods, ensuring consistency across the application. - Removed the obsolete `web_channel` module and its associated files, streamlining the codebase and enhancing clarity in the channel management system. - Introduced new functions for managing web channel events and schemas, improving the overall architecture of the web channel integration. * Refactor main.tsx and test setup for improved organization - Reordered imports in `main.tsx` to enhance clarity and maintainability, ensuring that `setStoreForApiClient` is called with the correct store reference. - Removed unnecessary whitespace in `setup.ts`, streamlining the test setup file for better readability. * Fix service gate false blocking when service is running * Support soft-pass daemon gate and harden macOS service detection * Extend list-files fallback trigger phrases in agent loop * Replace list-files fallback with tool-call repair retry * Log full system prompt and drop tool-call repair flow --- CLAUDE.md | 1 + app/schema.json | 26 - app/src-tauri/src/commands/openhuman.rs | 24 +- .../components/daemon/ServiceBlockingGate.tsx | 26 +- .../__tests__/ServiceBlockingGate.test.tsx | 60 ++ app/src/main.tsx | 4 + app/src/services/apiClient.ts | 18 +- app/src/store/index.ts | 4 - src/core/all.rs | 7 +- src/core/jsonrpc.rs | 2 +- src/core/socketio.rs | 96 ++- src/openhuman/agent/agent.rs | 52 +- src/openhuman/agent/schemas.rs | 34 - src/openhuman/channels/mod.rs | 1 + src/openhuman/channels/providers/mod.rs | 1 + src/openhuman/channels/providers/web.rs | 604 ++++++++++++++++++ src/openhuman/local_ai/ops.rs | 54 -- src/openhuman/local_ai/schemas.rs | 34 - src/openhuman/mod.rs | 1 - src/openhuman/service/macos.rs | 61 +- src/openhuman/web_channel/events.rs | 31 - src/openhuman/web_channel/mod.rs | 9 - src/openhuman/web_channel/ops.rs | 236 ------- src/openhuman/web_channel/schemas.rs | 148 ----- 24 files changed, 846 insertions(+), 688 deletions(-) create mode 100644 src/openhuman/channels/providers/web.rs delete mode 100644 src/openhuman/web_channel/events.rs delete mode 100644 src/openhuman/web_channel/mod.rs delete mode 100644 src/openhuman/web_channel/ops.rs delete mode 100644 src/openhuman/web_channel/schemas.rs diff --git a/CLAUDE.md b/CLAUDE.md index 5557ea98a..7cdf9a915 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -270,6 +270,7 @@ Skills runtime uses **QuickJS** (`rquickjs`) in **`src/openhuman/skills/`** (e.g ## Git workflow +- **Open pull requests on upstream** — Always create PRs against **[tinyhumansai/openhuman](https://github.com/tinyhumansai/openhuman)** ([pull requests](https://github.com/tinyhumansai/openhuman/pulls)), not only a fork’s default remote, unless the workflow explicitly says otherwise. - **Public repo**; push to your working branch; PRs target **`main`**. - Use [`.github/pull_request_template.md`](.github/pull_request_template.md); AI-generated PR text should follow its sections and checklist. diff --git a/app/schema.json b/app/schema.json index d29d7c4c6..18ebede9e 100644 --- a/app/schema.json +++ b/app/schema.json @@ -136,19 +136,6 @@ { "comment": "Agent response payload.", "name": "response", "required": true, "ty": "Json" } ] }, - { - "description": "Send a message through REPL agent session.", - "function": "repl_session_chat", - "inputs": [ - { "comment": "REPL session id.", "name": "session_id", "required": true, "ty": "String" }, - { "comment": "User message.", "name": "message", "required": true, "ty": "String" } - ], - "method": "openhuman.agent_repl_session_chat", - "namespace": "agent", - "outputs": [ - { "comment": "Session chat response.", "name": "response", "required": true, "ty": "Json" } - ] - }, { "description": "Terminate REPL session.", "function": "repl_session_end", @@ -1285,19 +1272,6 @@ { "comment": "Agent response payload.", "name": "response", "required": true, "ty": "Json" } ] }, - { - "description": "Send a message through REPL agent session.", - "function": "agent_repl_session_chat", - "inputs": [ - { "comment": "REPL session id.", "name": "session_id", "required": true, "ty": "String" }, - { "comment": "User message.", "name": "message", "required": true, "ty": "String" } - ], - "method": "openhuman.local_ai_agent_repl_session_chat", - "namespace": "local_ai", - "outputs": [ - { "comment": "Session chat response.", "name": "response", "required": true, "ty": "Json" } - ] - }, { "description": "Terminate REPL session.", "function": "agent_repl_session_end", diff --git a/app/src-tauri/src/commands/openhuman.rs b/app/src-tauri/src/commands/openhuman.rs index 6007405a3..8c2d9ad31 100644 --- a/app/src-tauri/src/commands/openhuman.rs +++ b/app/src-tauri/src/commands/openhuman.rs @@ -104,6 +104,12 @@ struct WebChatSseEvent { full_response: Option, message: Option, error_type: Option, + tool_name: Option, + skill_id: Option, + args: Option, + output: Option, + success: Option, + round: Option, } fn core_events_url() -> String { @@ -168,10 +174,10 @@ fn emit_web_chat_event(app: &AppHandle, event: WebChatSseEvent) { "tool_call" => { let payload = serde_json::json!({ "thread_id": event.thread_id, - "tool_name": "unknown", - "skill_id": "web_channel", - "args": {}, - "round": 0, + "tool_name": event.tool_name.unwrap_or_else(|| "unknown".to_string()), + "skill_id": event.skill_id.unwrap_or_else(|| "web_channel".to_string()), + "args": event.args.unwrap_or_else(|| serde_json::json!({})), + "round": event.round.unwrap_or(0), "request_id": event.request_id, }); let _ = app.emit("chat:tool_call", payload); @@ -179,11 +185,11 @@ fn emit_web_chat_event(app: &AppHandle, event: WebChatSseEvent) { "tool_result" => { let payload = serde_json::json!({ "thread_id": event.thread_id, - "tool_name": "unknown", - "skill_id": "web_channel", - "output": "", - "success": true, - "round": 0, + "tool_name": event.tool_name.unwrap_or_else(|| "unknown".to_string()), + "skill_id": event.skill_id.unwrap_or_else(|| "web_channel".to_string()), + "output": event.output.unwrap_or_default(), + "success": event.success.unwrap_or(true), + "round": event.round.unwrap_or(0), "request_id": event.request_id, }); let _ = app.emit("chat:tool_result", payload); diff --git a/app/src/components/daemon/ServiceBlockingGate.tsx b/app/src/components/daemon/ServiceBlockingGate.tsx index e88095127..24eb6cdc7 100644 --- a/app/src/components/daemon/ServiceBlockingGate.tsx +++ b/app/src/components/daemon/ServiceBlockingGate.tsx @@ -22,6 +22,9 @@ type RefreshOptions = { showChecking?: boolean; clearError?: boolean }; const normalizeServiceState = (state: ServiceState | undefined): string => { if (!state) return 'Unknown'; if (typeof state === 'string') return state; + if ('Running' in state) return 'Running'; + if ('Stopped' in state) return 'Stopped'; + if ('NotInstalled' in state) return 'NotInstalled'; if ('Unknown' in state) return `Unknown(${state.Unknown})`; return 'Unknown'; }; @@ -50,26 +53,35 @@ const ServiceBlockingGate = ({ children }: ServiceBlockingGateProps) => { console.info('[ServiceBlockingGate] Refreshing service + agent status'); try { - const [service, agent] = await Promise.all([ + const [serviceResult, agentResult] = await Promise.allSettled([ openhumanServiceStatus(), openhumanAgentServerStatus(), ]); - const serviceState = service?.result?.state; - const normalized = normalizeServiceState(serviceState); + const normalized = + serviceResult.status === 'fulfilled' + ? normalizeServiceState(serviceResult.value?.result?.state) + : 'Unknown'; const serviceRunning = normalized === 'Running'; - const agentIsRunning = !!agent?.result?.running; + const agentIsRunning = + agentResult.status === 'fulfilled' ? !!agentResult.value?.result?.running : false; + const gateReady = serviceRunning || agentIsRunning; + + if (serviceResult.status !== 'fulfilled' && !agentIsRunning) { + throw serviceResult.reason; + } setServiceStateText(prev => (prev === normalized ? prev : normalized)); setAgentRunning(prev => (prev === agentIsRunning ? prev : agentIsRunning)); setGateStatus(prev => { - const next = serviceRunning && agentIsRunning ? 'ready' : 'blocked'; + const next = gateReady ? 'ready' : 'blocked'; return prev === next ? prev : next; }); setError(prev => (prev ? null : prev)); console.info('[ServiceBlockingGate] Status refreshed', { serviceState: normalized, agentRunning: agentIsRunning, - nextGateStatus: serviceRunning && agentIsRunning ? 'ready' : 'blocked', + nextGateStatus: gateReady ? 'ready' : 'blocked', + passMode: serviceRunning ? 'hard(service)' : agentIsRunning ? 'soft(agent)' : 'blocked', }); } catch (err) { setServiceStateText('Unknown'); @@ -183,7 +195,7 @@ const ServiceBlockingGate = ({ children }: ServiceBlockingGateProps) => {