diff --git a/app/src/pages/Conversations.tsx b/app/src/pages/Conversations.tsx index 5ff0f849e..dfa4a5fb1 100644 --- a/app/src/pages/Conversations.tsx +++ b/app/src/pages/Conversations.tsx @@ -293,16 +293,22 @@ const Conversations = ({ variant = 'page', composer = 'text' }: ConversationsPro // return; // } const threadStateForSelect = store.getState().thread; - if (data.threads.length > 0) { + // Worker/subagent threads are hidden from the conversation list + // (see tinyhumansai/openhuman#1624). Match the sidebar filter here so + // initial/resume selection can't auto-pick a hidden thread and leave + // the UI showing a thread that isn't in the list. + const visibleThreads = data.threads.filter(t => !t.parentThreadId); + if (visibleThreads.length > 0) { // Prefer the thread the user was last viewing (persisted across // reloads via redux-persist on the `thread` slice). Only fall // through to "most recent" if that thread no longer exists - // server-side (deleted, purged, or different user). + // server-side (deleted, purged, or different user) — or is now + // hidden because it's a worker thread. const persistedId = threadStateForSelect.selectedThreadId; const resumeId = - persistedId && data.threads.some(t => t.id === persistedId) + persistedId && visibleThreads.some(t => t.id === persistedId) ? persistedId - : data.threads[0].id; + : visibleThreads[0].id; dispatch(setSelectedThread(resumeId)); void dispatch(loadThreadMessages(resumeId)); } else { @@ -940,6 +946,10 @@ const Conversations = ({ variant = 'page', composer = 'text' }: ConversationsPro const filteredThreads = useMemo(() => { const base = threads.filter(t => { + // Hide worker/subagent threads from the conversation list. They are + // currently surfaced inline inside the parent thread via WorkerThreadRefCard. + // A dedicated showcase is tracked in tinyhumansai/openhuman#1624. + if (t.parentThreadId) return false; if (selectedLabel === 'all') return true; return t.labels?.includes(selectedLabel); }); diff --git a/src/openhuman/tools/impl/agent/spawn_subagent.rs b/src/openhuman/tools/impl/agent/spawn_subagent.rs index d0eeb20cd..ae5aaa284 100644 --- a/src/openhuman/tools/impl/agent/spawn_subagent.rs +++ b/src/openhuman/tools/impl/agent/spawn_subagent.rs @@ -128,7 +128,7 @@ impl Tool for SpawnSubagentTool { }, "dedicated_thread": { "type": "boolean", - "description": "Default `false`. Set `true` ONLY for long, complex sub-tasks where the parent thread should not be flooded with sub-agent output. The sub-agent's prompt and final summary land in a fresh worker-labeled thread the user can open from the thread list, and the parent receives a compact reference (worker thread id + brief summary) instead of the full transcript. Worker threads cannot themselves spawn another worker (sub-agents never see this tool), so this is a one-level-deep escape hatch." + "description": "Temporarily disabled (see tinyhumansai/openhuman#1624). Passing `true` causes this tool to return an explicit error. Omit the field or pass `false` until the worker-thread UI surface lands." } } }) @@ -166,10 +166,25 @@ impl Tool for SpawnSubagentTool { .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()); - let dedicated_thread = args + // Worker-thread spawning is temporarily disabled until a proper UI + // showcase lands (see tinyhumansai/openhuman#1624). Return an + // explicit error when callers request a dedicated thread so the + // behaviour is observable rather than silently downgraded. + let dedicated_thread_requested = args .get("dedicated_thread") .and_then(|v| v.as_bool()) .unwrap_or(false); + if dedicated_thread_requested { + log::debug!( + "[spawn_subagent] dedicated_thread requested but temporarily \ + disabled (see tinyhumansai/openhuman#1624); rejecting call" + ); + return Ok(ToolResult::error( + "spawn_subagent: `dedicated_thread` is temporarily disabled \ + (see tinyhumansai/openhuman#1624); retry without it.", + )); + } + let dedicated_thread = false; // ── Validation ───────────────────────────────────────────────── if agent_id.is_empty() { diff --git a/src/openhuman/tools/orchestrator_tools.rs b/src/openhuman/tools/orchestrator_tools.rs index 77e162861..3a94f2055 100644 --- a/src/openhuman/tools/orchestrator_tools.rs +++ b/src/openhuman/tools/orchestrator_tools.rs @@ -29,7 +29,11 @@ use crate::openhuman::agent::harness::definition::{ }; use crate::openhuman::context::prompt::ConnectedIntegration; -use super::{ArchetypeDelegationTool, SkillDelegationTool, SpawnWorkerThreadTool, Tool}; +// SpawnWorkerThreadTool import kept commented while the worker-thread spawn is +// temporarily disabled (see tinyhumansai/openhuman#1624). +#[allow(unused_imports)] +use super::SpawnWorkerThreadTool; +use super::{ArchetypeDelegationTool, SkillDelegationTool, Tool}; /// Synthesise the delegation tool list for an agent based on its /// declarative `subagents` field. @@ -66,9 +70,12 @@ pub fn collect_orchestrator_tools( let mut tools: Vec> = Vec::new(); // Orchestrator-only tool: spawn_worker_thread. - if definition.id == "orchestrator" { - tools.push(Box::new(SpawnWorkerThreadTool::new())); - } + // Temporarily disabled — worker threads do not yet have a proper UI + // showcase (see tinyhumansai/openhuman#1624). Re-enable once the + // dedicated worker-thread surface lands. + // if definition.id == "orchestrator" { + // tools.push(Box::new(SpawnWorkerThreadTool::new())); + // } for entry in &definition.subagents { match entry { @@ -298,7 +305,9 @@ mod tests { assert_eq!( names, vec![ - "spawn_worker_thread", // orchestrator-only, prepended in collect_orchestrator_tools + // `spawn_worker_thread` is temporarily disabled — see + // tinyhumansai/openhuman#1624. Re-add the leading entry when + // the registration in `collect_orchestrator_tools` is restored. "research", // researcher's delegate_name override "delegate_archivist", // archivist has no delegate_name → default "delegate_gmail", @@ -329,10 +338,8 @@ mod tests { let reg = registry_with_targets(); let tools = collect_orchestrator_tools(&orch, ®, &[]); let names: Vec<&str> = tools.iter().map(|t| t.name()).collect(); - assert_eq!( - names, - vec!["spawn_worker_thread", "research", "delegate_archivist"] - ); + // `spawn_worker_thread` is temporarily disabled — see #1624. + assert_eq!(names, vec!["research", "delegate_archivist"]); } /// An AgentId entry that points at an id not present in the registry @@ -348,7 +355,8 @@ mod tests { let reg = registry_with_targets(); let tools = collect_orchestrator_tools(&orch, ®, &[]); let names: Vec<&str> = tools.iter().map(|t| t.name()).collect(); - assert_eq!(names, vec!["spawn_worker_thread", "research"]); + // `spawn_worker_thread` is temporarily disabled — see #1624. + assert_eq!(names, vec!["research"]); } /// An empty `subagents` list should produce zero tools — regular