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) => {