Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/crates/core/src/agentic/agents/claw_mode.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Claw Mode

use super::Agent;
use super::{Agent, RequestContextPolicy};
use async_trait::async_trait;
pub struct ClawMode {
default_tools: Vec<String>,
Expand Down Expand Up @@ -67,6 +67,10 @@ impl Agent for ClawMode {
self.default_tools.clone()
}

fn request_context_policy(&self) -> RequestContextPolicy {
RequestContextPolicy::full_without_layout()
}

fn is_readonly(&self) -> bool {
false
}
Expand Down
26 changes: 0 additions & 26 deletions src/crates/core/src/agentic/agents/debug_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,32 +315,6 @@ impl Agent for DebugMode {
language_templates.len()
);

let project_layout = prompt_components.get_project_layout();
prompt_list.push(format!(
r##"# Current Workspace File Structure
<project_layout>
Below is a snapshot of the current workspace's file structure.

{project_layout}
</project_layout>

"##
));

if let Some(project_context) = prompt_components.get_project_context(None).await {
prompt_list.push(format!(
"# Current Workspace Context\n{project_context}\n\n"
));
}

if let Some(rules_prompt) = prompt_components.load_ai_rules().await {
prompt_list.push(rules_prompt);
}

if let Some(memory_prompt) = prompt_components.load_ai_memories().await {
prompt_list.push(memory_prompt);
}

let session_rule = self.build_session_level_rule(&debug_config, workspace_path);
prompt_list.push(session_rule);

Expand Down
6 changes: 5 additions & 1 deletion src/crates/core/src/agentic/agents/explore_agent.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::Agent;
use super::{Agent, RequestContextPolicy};
use async_trait::async_trait;
pub struct ExploreAgent {
default_tools: Vec<String>,
Expand Down Expand Up @@ -49,6 +49,10 @@ impl Agent for ExploreAgent {
self.default_tools.clone()
}

fn request_context_policy(&self) -> RequestContextPolicy {
RequestContextPolicy::instructions_only()
}

fn is_readonly(&self) -> bool {
true
}
Expand Down
6 changes: 5 additions & 1 deletion src/crates/core/src/agentic/agents/file_finder_agent.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::Agent;
use super::{Agent, RequestContextPolicy};
use async_trait::async_trait;

pub struct FileFinderAgent {
Expand Down Expand Up @@ -58,6 +58,10 @@ Examples:
self.default_tools.clone()
}

fn request_context_policy(&self) -> RequestContextPolicy {
RequestContextPolicy::instructions_only()
}

fn is_readonly(&self) -> bool {
true
}
Expand Down
9 changes: 8 additions & 1 deletion src/crates/core/src/agentic/agents/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ pub use generate_doc_agent::GenerateDocAgent;
pub use init_agent::InitAgent;
pub use plan_mode::PlanMode;
pub use team_mode::TeamMode;
pub use prompt_builder::{PromptBuilder, PromptBuilderContext, RemoteExecutionHints};
pub use prompt_builder::{
PromptBuilder, PromptBuilderContext, RemoteExecutionHints, RequestContextPolicy,
RequestContextSection,
};
pub use registry::{
get_agent_registry, AgentCategory, AgentInfo, AgentRegistry, CustomSubagentConfig,
CustomSubagentDetail, SubAgentSource,
Expand Down Expand Up @@ -68,6 +71,10 @@ pub trait Agent: Send + Sync + 'static {
None // by default, no system reminder
}

fn request_context_policy(&self) -> RequestContextPolicy {
RequestContextPolicy::default()
}

/// Build the system prompt for this agent
async fn build_prompt(&self, context: &PromptBuilderContext) -> BitFunResult<String> {
let prompt_components = PromptBuilder::new(context.clone());
Expand Down
6 changes: 5 additions & 1 deletion src/crates/core/src/agentic/agents/plan_mode.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Plan Mode

use super::Agent;
use super::{Agent, RequestContextPolicy};
use async_trait::async_trait;
pub struct PlanMode {
default_tools: Vec<String>,
Expand Down Expand Up @@ -57,6 +57,10 @@ impl Agent for PlanMode {
self.default_tools.clone()
}

fn request_context_policy(&self) -> RequestContextPolicy {
RequestContextPolicy::instructions_and_layout()
}

fn is_readonly(&self) -> bool {
// only modify plan file, not modify project code
true
Expand Down
2 changes: 2 additions & 0 deletions src/crates/core/src/agentic/agents/prompt_builder/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
mod request_context;
mod prompt_builder_impl;

pub use prompt_builder_impl::{PromptBuilder, PromptBuilderContext, RemoteExecutionHints};
pub use request_context::{RequestContextPolicy, RequestContextSection};
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
//! System prompts module providing main dialogue and agent dialogue prompts
use super::request_context::{RequestContextPolicy, RequestContextSection};
use crate::infrastructure::try_get_path_manager_arc;
use crate::service::agent_memory::build_workspace_agent_memory_prompt;
use crate::service::agent_memory::{
build_workspace_agent_memory_prompt, build_workspace_instruction_files_context,
build_workspace_memory_files_context,
};
use crate::service::ai_memory::AIMemoryManager;
use crate::service::ai_rules::get_global_ai_rules_service;
use crate::service::bootstrap::build_workspace_persona_prompt;
use crate::service::config::get_app_language_code;
use crate::service::config::global::GlobalConfigManager;
use crate::service::filesystem::get_formatted_directory_listing;
use crate::service::project_context::ProjectContextService;
use crate::util::errors::{BitFunError, BitFunResult};
use log::{debug, warn};
use std::path::Path;

/// Placeholder constants
const PLACEHOLDER_PERSONA: &str = "{PERSONA}";
const PLACEHOLDER_ENV_INFO: &str = "{ENV_INFO}";
const PLACEHOLDER_PROJECT_LAYOUT: &str = "{PROJECT_LAYOUT}";
// PROJECT_CONTEXT_FILES needs configuration parsing
// const PLACEHOLDER_PROJECT_CONTEXT_FILES: &str = "{PROJECT_CONTEXT_FILES}";
const PLACEHOLDER_RULES: &str = "{RULES}";
const PLACEHOLDER_MEMORIES: &str = "{MEMORIES}";
const PLACEHOLDER_LANGUAGE_PREFERENCE: &str = "{LANGUAGE_PREFERENCE}";
const PLACEHOLDER_AGENT_MEMORY: &str = "{AGENT_MEMORY}";
const PLACEHOLDER_CLAW_WORKSPACE: &str = "{CLAW_WORKSPACE}";
Expand Down Expand Up @@ -186,36 +184,6 @@ impl PromptBuilder {
project_layout
}

/// Get user-provided project information files
/// These files (e.g., AGENTS.md, CLAUDE.md) are provided by users to describe project architecture, conventions, and guidelines
///
/// Parameters:
/// - filter: Optional filter, supports `include=category1,category2` or `exclude=category1`
pub async fn get_project_context(&self, filter: Option<&str>) -> Option<String> {
if self.context.remote_execution.is_some() {
return None;
}

let service = ProjectContextService::new();
let workspace = Path::new(&self.context.workspace_path);

match service.build_context_prompt(workspace, filter).await {
Ok(prompt) if !prompt.is_empty() => {
let result = format!(
r#"# Project Context
The following are project documentation that describe the project's architecture, conventions, and guidelines, etc.

{}

"#,
prompt
);
Some(result)
}
_ => None,
}
}

/// Load AI memories from disk and format as prompt
pub async fn load_ai_memories(&self) -> Option<String> {
let path_manager = match try_get_path_manager_arc() {
Expand Down Expand Up @@ -244,6 +212,73 @@ The following are project documentation that describe the project's architecture
}
}

pub async fn build_request_context_reminder(
&self,
policy: &RequestContextPolicy,
) -> Option<String> {
let mut sections = Vec::new();
let mut instruction_sections = Vec::new();
let mut override_sections = Vec::new();
let mut trailing_sections = Vec::new();

if self.context.remote_execution.is_none() {
let workspace = Path::new(&self.context.workspace_path);
if policy.includes(RequestContextSection::WorkspaceInstructions) {
match build_workspace_instruction_files_context(workspace).await {
Ok(Some(prompt)) => instruction_sections.push(prompt),
Ok(None) => {}
Err(e) => warn!(
"Failed to build workspace instruction context: path={} error={}",
workspace.display(),
e
),
}
}
if policy.includes(RequestContextSection::WorkspaceMemoryFiles) {
match build_workspace_memory_files_context(workspace).await {
Ok(Some(prompt)) => override_sections.push(prompt),
Ok(None) => {}
Err(e) => warn!(
"Failed to build workspace memory context: path={} error={}",
workspace.display(),
e
),
}
}
}

if policy.includes(RequestContextSection::AIRules) {
if let Some(rules_prompt) = self.load_ai_rules().await {
override_sections.push(rules_prompt);
}
}

if policy.includes(RequestContextSection::AIMemories) {
if let Some(memory_prompt) = self.load_ai_memories().await {
override_sections.push(memory_prompt);
}
}

if policy.includes(RequestContextSection::ProjectLayout) {
trailing_sections.push(self.get_project_layout());
}

sections.extend(instruction_sections);

if policy.has_override_sections() && !override_sections.is_empty() {
sections.push("Codebase and user instructions are shown below. Be sure to adhere to these instructions. IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.".to_string());
sections.extend(override_sections);
}

sections.extend(trailing_sections);

if sections.is_empty() {
None
} else {
Some(sections.join("\n\n"))
}
}

/// Load AI rules from disk and format as prompt
pub async fn load_ai_rules(&self) -> Option<String> {
let rules_service = match get_global_ai_rules_service().await {
Expand Down Expand Up @@ -342,12 +377,8 @@ Do not read from, modify, create, move, or delete files outside this workspace u
/// - `{PERSONA}` - Workspace persona files (BOOTSTRAP.md, SOUL.md, USER.md, IDENTITY.md)
/// - `{LANGUAGE_PREFERENCE}` - User language preference (read from global config)
/// - `{ENV_INFO}` - Environment information
/// - `{PROJECT_LAYOUT}` - Project file layout
/// - `{PROJECT_CONTEXT_FILES}` - Project context files (AGENTS.md, CLAUDE.md, etc.)
/// - `{AGENT_MEMORY}` - Agent memory instructions + auto-loaded memory index
/// - `{CLAW_WORKSPACE}` - Claw-specific workspace ownership and boundary rules
/// - `{RULES}` - AI rules
/// - `{MEMORIES}` - AI memories
/// - `{VISUAL_MODE}` - Visual mode instruction (Mermaid diagrams, read from global config)
///
/// If a placeholder is not in the template, corresponding content will not be added
Expand Down Expand Up @@ -394,47 +425,6 @@ Do not read from, modify, create, move, or delete files outside this workspace u
result = result.replace(PLACEHOLDER_ENV_INFO, &env_info);
}

// Replace {PROJECT_LAYOUT}
if result.contains(PLACEHOLDER_PROJECT_LAYOUT) {
let project_layout = self.get_project_layout();
result = result.replace(PLACEHOLDER_PROJECT_LAYOUT, &project_layout);
}

// Replace {PROJECT_CONTEXT_FILES}
// Supported syntax:
// - {PROJECT_CONTEXT_FILES} - Include all enabled documents
// - {PROJECT_CONTEXT_FILES:include=general,design} - Only include specified categories
// - {PROJECT_CONTEXT_FILES:exclude=review} - Exclude specified categories
while let Some(start) = result.find("{PROJECT_CONTEXT_FILES") {
let start_pos = start;
// Find placeholder end position
let end_pos = result[start_pos..]
.find('}')
.map(|p| start_pos + p + 1)
.unwrap_or(result.len());

// Extract complete placeholder
let placeholder = &result[start_pos..end_pos];

// Parse filter
let filter = if let Some(colon_pos) = placeholder.find(':') {
// Has filter: {PROJECT_CONTEXT_FILES:include=xxx} or {PROJECT_CONTEXT_FILES:exclude=xxx}
let filter_str = &placeholder[colon_pos + 1..placeholder.len() - 1];
Some(filter_str.trim().to_string())
} else {
// No filter
None
};

let filter_ref = filter.as_deref();
let project_context = self
.get_project_context(filter_ref)
.await
.unwrap_or_default();

result = result.replace(placeholder, &project_context);
}

// Replace {AGENT_MEMORY}
if result.contains(PLACEHOLDER_AGENT_MEMORY) {
let agent_memory = if self.context.remote_execution.is_some() {
Expand All @@ -457,18 +447,6 @@ Do not read from, modify, create, move, or delete files outside this workspace u
result = result.replace(PLACEHOLDER_AGENT_MEMORY, &agent_memory);
}

// Replace {RULES}
if result.contains(PLACEHOLDER_RULES) {
let rules = self.load_ai_rules().await.unwrap_or_default();
result = result.replace(PLACEHOLDER_RULES, &rules);
}

// Replace {MEMORIES}
if result.contains(PLACEHOLDER_MEMORIES) {
let memories = self.load_ai_memories().await.unwrap_or_default();
result = result.replace(PLACEHOLDER_MEMORIES, &memories);
}

// Replace {VISUAL_MODE}
if result.contains(PLACEHOLDER_VISUAL_MODE) {
let visual_mode = self.get_visual_mode_instruction().await;
Expand Down
Loading
Loading