Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4dff037

Browse files
authored
feat: add help command mode with dedicated help agent (tailcallhq#475)
1 parent b8c3057 commit 4dff037

8 files changed

Lines changed: 140 additions & 6 deletions

File tree

crates/forge_app/src/template.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ use tracing::debug;
1212

1313
use crate::{EmbeddingService, EnvironmentService, Infrastructure, VectorIndex};
1414

15+
// Include README.md at compile time
16+
const README_CONTENT: &str = include_str!("../../../README.md");
17+
1518
#[derive(Embed)]
1619
#[folder = "../../templates/"]
1720
struct Templates;
@@ -61,14 +64,18 @@ impl<F: Infrastructure, T: ToolService> TemplateService for ForgeTemplateService
6164
// Sort the files alphabetically to ensure consistent ordering
6265
files.sort();
6366

67+
// Create the context with README content for all agents
6468
let ctx = SystemContext {
6569
env: Some(env),
6670
tool_information: Some(self.tool_service.usage_prompt()),
6771
tool_supported: agent.tool_supported,
6872
files,
73+
readme: README_CONTENT.to_string(),
6974
};
7075

71-
Ok(self.hb.render_template(prompt.template.as_str(), &ctx)?)
76+
// Render the template with the context
77+
let result = self.hb.render_template(prompt.template.as_str(), &ctx)?;
78+
Ok(result)
7279
}
7380

7481
async fn render_event(

crates/forge_domain/src/agent.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub struct SystemContext {
1818
pub tool_supported: bool,
1919
#[serde(skip_serializing_if = "Vec::is_empty")]
2020
pub files: Vec<String>,
21+
pub readme: String,
2122
}
2223

2324
#[derive(Debug, Display, Eq, PartialEq, Hash, Clone, Serialize, Deserialize)]

crates/forge_main/src/model.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ pub enum Command {
7777
/// Switch to "plan" mode.
7878
/// This can be triggered with the '/plan' command.
7979
Plan,
80+
/// Switch to "help" mode.
81+
/// This can be triggered with the '/help' command.
82+
Help,
8083
/// Dumps the current conversation into a json file
8184
Dump,
8285
}
@@ -96,6 +99,7 @@ impl Command {
9699
"/models".to_string(),
97100
"/act".to_string(),
98101
"/plan".to_string(),
102+
"/help".to_string(),
99103
"/dump".to_string(),
100104
]
101105
}
@@ -121,6 +125,7 @@ impl Command {
121125
"/dump" => Command::Dump,
122126
"/act" => Command::Act,
123127
"/plan" => Command::Plan,
128+
"/help" => Command::Help,
124129
text => Command::Message(text.to_string()),
125130
}
126131
}

crates/forge_main/src/state.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::input::PromptInput;
55
#[derive(Clone, Default)]
66
pub enum Mode {
77
Plan,
8+
Help,
89
#[default]
910
Act,
1011
}
@@ -13,6 +14,7 @@ impl std::fmt::Display for Mode {
1314
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1415
match self {
1516
Mode::Plan => write!(f, "PLAN"),
17+
Mode::Help => write!(f, "HELP"),
1618
Mode::Act => write!(f, "ACT"),
1719
}
1820
}

crates/forge_main/src/ui.rs

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::state::{Mode, UIState};
2020
// Event type constants moved to UI layer
2121
pub const EVENT_USER_TASK_INIT: &str = "user_task_init";
2222
pub const EVENT_USER_TASK_UPDATE: &str = "user_task_update";
23+
pub const EVENT_USER_HELP_QUERY: &str = "user_help_query";
2324
pub const EVENT_TITLE: &str = "title";
2425

2526
lazy_static! {
@@ -43,21 +44,28 @@ impl<F: API> UI<F> {
4344
self.state.mode = mode;
4445

4546
// Show message that mode changed
46-
let mode = self.state.mode.to_string();
47+
let mode_str = self.state.mode.to_string();
4748

4849
// Set the mode variable in the conversation if a conversation exists
4950
let conversation_id = self.init_conversation().await?;
5051
self.api
5152
.set_variable(
5253
&conversation_id,
5354
"mode".to_string(),
54-
Value::from(mode.as_str()),
55+
Value::from(mode_str.as_str()),
5556
)
5657
.await?;
5758

59+
// Print a mode-specific message
60+
let mode_message = match self.state.mode {
61+
Mode::Act => "mode - executes commands and makes file changes",
62+
Mode::Plan => "mode - plans actions without making changes",
63+
Mode::Help => "mode - answers questions (type /act or /plan to switch back)",
64+
};
65+
5866
CONSOLE.write(
59-
TitleFormat::success(&mode)
60-
.sub_title("mode activated")
67+
TitleFormat::success(&mode_str)
68+
.sub_title(mode_message)
6169
.format(),
6270
)?;
6371

@@ -71,6 +79,9 @@ impl<F: API> UI<F> {
7179
fn create_task_update_event(content: impl ToString) -> Event {
7280
Event::new(EVENT_USER_TASK_UPDATE, content)
7381
}
82+
fn create_user_help_query_event(content: impl ToString) -> Event {
83+
Event::new(EVENT_USER_HELP_QUERY, content)
84+
}
7485

7586
pub fn init(cli: Cli, api: Arc<F>) -> Result<Self> {
7687
// Parse CLI arguments first to get flags
@@ -129,7 +140,10 @@ impl<F: API> UI<F> {
129140
continue;
130141
}
131142
Command::Message(ref content) => {
132-
let chat_result = self.chat(content.clone()).await;
143+
let chat_result = match self.state.mode {
144+
Mode::Help => self.help_chat(content.clone()).await,
145+
_ => self.chat(content.clone()).await,
146+
};
133147
if let Err(err) = chat_result {
134148
CONSOLE.writeln(TitleFormat::failed(format!("{:?}", err)).format())?;
135149
}
@@ -150,6 +164,13 @@ impl<F: API> UI<F> {
150164
input = self.console.prompt(prompt_input).await?;
151165
continue;
152166
}
167+
Command::Help => {
168+
self.handle_mode_change(Mode::Help).await?;
169+
170+
let prompt_input = Some((&self.state).into());
171+
input = self.console.prompt(prompt_input).await?;
172+
continue;
173+
}
153174
Command::Exit => {
154175
break;
155176
}
@@ -311,5 +332,21 @@ impl<F: API> UI<F> {
311332
}
312333
}
313334
Ok(())
335+
} // Handle help chat in HELP mode
336+
async fn help_chat(&mut self, content: String) -> Result<()> {
337+
let conversation_id = self.init_conversation().await?;
338+
339+
// Create a help query event
340+
let event = Self::create_user_help_query_event(content.clone());
341+
342+
// Create the chat request with the help query event
343+
let chat = ChatRequest::new(event, conversation_id);
344+
345+
tokio::spawn(TRACKER.dispatch(EventKind::Prompt(content)));
346+
347+
match self.api.chat(chat).await {
348+
Ok(mut stream) => self.handle_chat_stream(&mut stream).await,
349+
Err(err) => Err(err),
350+
}
314351
}
315352
}

forge.default.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ agents:
1515
system_prompt: "{{> system-prompt-title-generator.hbs }}"
1616
user_prompt: <technical_content>{{event.value}}</technical_content>
1717

18+
- id: help_agent
19+
model: google/gemini-2.0-flash-thinking-exp:free
20+
tools:
21+
- tool_forge_fs_read
22+
- tool_forge_fs_create
23+
subscribe:
24+
- user_help_query
25+
system_prompt: |
26+
{{> system-prompt-help.hbs }}
27+
user_prompt: <query>{{event.value}}</query>
28+
1829
- id: software-engineer
1930
model: *advanced_model
2031
tool_supported: true

forge.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ agents:
1515
system_prompt: "{{> system-prompt-title-generator.hbs }}"
1616
user_prompt: <technical_content>{{event.value}}</technical_content>
1717

18+
- id: help_agent
19+
model: google/gemini-2.0-flash-thinking-exp:free
20+
tools:
21+
- tool_forge_fs_read
22+
- tool_forge_fs_create
23+
subscribe:
24+
- user_help_query
25+
system_prompt: |
26+
{{> system-prompt-help.hbs }}
27+
user_prompt: <user_query>{{event.value}}</user_query>
28+
1829
- id: software-engineer
1930
model: *advanced_model
2031
tool_supported: true

templates/system-prompt-help.hbs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
2+
3+
You are a specialized assistant for the Forge CLI tool, designed to help users become better at using Forge. Your responses should be concise, accurate, and optimized for command-line interface (CLI) environments.
4+
5+
<forge_documentation>
6+
{{readme}}
7+
</forge_documentation>
8+
9+
Here's some system information that may be relevant to your responses:
10+
11+
<system_info>
12+
<operating_system>{{env.os}}</operating_system>
13+
<current_working_directory>{{env.cwd}}</current_working_directory>
14+
<default_shell>{{env.shell}}</default_shell>
15+
<home_directory>{{env.home}}</home_directory>
16+
</system_info>
17+
18+
INSTRUCTIONS:
19+
20+
1. Analyze the user's query wrapped in `<user_query>` about Forge CLI.
21+
2. Provide accurate information based strictly on the `<forge_documentation>`.
22+
3. Explain concepts clearly and concisely, optimized for terminal reading.
23+
4. Include practical, realistic examples of Forge commands.
24+
5. Only answer questions about Forge CLI. Politely redirect unrelated queries.
25+
6. Provide context on why Forge features are useful and when to use them.
26+
27+
OUTPUT FORMATTING:
28+
29+
- Keep line lengths under 80 characters when possible.
30+
- Use plain text formatting only (no Markdown).
31+
- Format section headers with full-width dashed lines and ALL CAPS:
32+
33+
SECTION TITLE
34+
35+
- Use "*" for bullet points with two spaces of indentation.
36+
- For numbered lists, use "1.", "2.", etc. with two spaces after the period.
37+
- Format command examples with proper spacing and indentation.
38+
39+
RESPONSE PROCESS
40+
41+
Provide your response using this structure:
42+
43+
SUMMARY
44+
45+
[Brief, 1-2 sentence overview of the answer]
46+
47+
48+
DETAILED EXPLANATION
49+
50+
[Main content of your response, broken into subsections if needed]
51+
52+
EXAMPLE USAGE
53+
54+
[Practical example(s) of using Forge CLI for the discussed topic]
55+
56+
ADDITIONAL TIPS
57+
58+
[Optional: Suggest a related feature that can help]
59+
60+
Remember to keep your responses concise and CLI-optimized while maintaining accuracy and helpfulness.

0 commit comments

Comments
 (0)