diff --git a/src/crates/core/src/agentic/coordination/coordinator.rs b/src/crates/core/src/agentic/coordination/coordinator.rs index d23abf1f..758d8efb 100644 --- a/src/crates/core/src/agentic/coordination/coordinator.rs +++ b/src/crates/core/src/agentic/coordination/coordinator.rs @@ -17,7 +17,9 @@ use crate::agentic::image_analysis::ImageContextData; use crate::agentic::session::SessionManager; use crate::agentic::tools::pipeline::{SubagentParentInfo, ToolPipeline}; use crate::agentic::WorkspaceBinding; -use crate::service::bootstrap::is_workspace_bootstrap_pending; +use crate::service::bootstrap::{ + initialize_workspace_persona_files, is_workspace_bootstrap_pending, +}; use crate::util::errors::{BitFunError, BitFunResult}; use log::{debug, error, info, warn}; use std::path::{Path, PathBuf}; @@ -633,7 +635,11 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet workspace_path: String, ) -> BitFunResult { let workspace_root = PathBuf::from(&workspace_path); - if !is_workspace_bootstrap_pending(&workspace_root) { + // Empty or partial assistant dirs may never have run create_assistant_workspace; fill only + // missing persona stubs (never overwrite). Ensures BOOTSTRAP.md exists when appropriate. + initialize_workspace_persona_files(&workspace_root).await?; + let bootstrap_pending = is_workspace_bootstrap_pending(&workspace_root); + if !bootstrap_pending { return Ok(AssistantBootstrapEnsureOutcome::Skipped { session_id, reason: AssistantBootstrapSkipReason::BootstrapNotRequired, @@ -649,7 +655,9 @@ Update the persona files and delete BOOTSTRAP.md as soon as bootstrap is complet } }; - if self.session_manager.get_turn_count(&session_id) > 0 { + let turn_count = self.session_manager.get_turn_count(&session_id); + + if turn_count > 0 { return Ok(AssistantBootstrapEnsureOutcome::Skipped { session_id, reason: AssistantBootstrapSkipReason::SessionHasExistingTurns, diff --git a/src/crates/core/src/service/workspace/service.rs b/src/crates/core/src/service/workspace/service.rs index 6ce0c5cc..3e1be696 100644 --- a/src/crates/core/src/service/workspace/service.rs +++ b/src/crates/core/src/service/workspace/service.rs @@ -242,6 +242,7 @@ impl WorkspaceService { })?; } + // New assistant dirs get persona files at creation; coordinator also fills missing files when opening. initialize_workspace_persona_files(&path).await?; self.create_workspace(path, options).await diff --git a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx index 1b005116..45580ce3 100644 --- a/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx +++ b/src/web-ui/src/app/components/NavPanel/sections/workspaces/WorkspaceItem.tsx @@ -199,6 +199,8 @@ const WorkspaceItem: React.FC = ({ await flowChatManager.resetWorkspaceSessions(workspace.rootPath, { reinitialize: isActive, preferredMode: 'Claw', + ensureAssistantBootstrap: + isActive && workspace.workspaceKind === WorkspaceKind.Assistant, }); notificationService.success(t('nav.workspaces.workspaceReset'), { duration: 2500 }); } catch (error) { diff --git a/src/web-ui/src/flow_chat/services/FlowChatManager.ts b/src/web-ui/src/flow_chat/services/FlowChatManager.ts index 265d2ba3..a7d44d65 100644 --- a/src/web-ui/src/flow_chat/services/FlowChatManager.ts +++ b/src/web-ui/src/flow_chat/services/FlowChatManager.ts @@ -153,7 +153,12 @@ export class FlowChatManager { async resetWorkspaceSessions( workspacePath: string, - options?: { reinitialize?: boolean; preferredMode?: string } + options?: { + reinitialize?: boolean; + preferredMode?: string; + /** After reinit, ask core to run assistant bootstrap if BOOTSTRAP.md is present (e.g. workspace reset). */ + ensureAssistantBootstrap?: boolean; + } ): Promise { const removedSessionIds = this.context.flowChatStore.removeSessionsByWorkspace(workspacePath); @@ -180,6 +185,24 @@ export class FlowChatManager { if (!hasHistoricalSessions || !hasActiveWorkspaceSession) { await this.createChatSession({}, options.preferredMode); } + + if (options?.ensureAssistantBootstrap) { + const sid = this.context.flowChatStore.getState().activeSessionId; + if (sid) { + try { + const { agentAPI } = await import('@/infrastructure/api/service-api/AgentAPI'); + await agentAPI.ensureAssistantBootstrap({ + sessionId: sid, + workspacePath, + }); + } catch (error) { + log.warn('ensureAssistantBootstrap after resetWorkspaceSessions failed', { + workspacePath, + error, + }); + } + } + } } async sendMessage(