Skip to content

Commit e739119

Browse files
Copilotyiwang
andcommitted
Add tool factory to allow heartbeat to use CLI tools
Co-authored-by: yiwang <142937+yiwang@users.noreply.github.com>
1 parent 11818a6 commit e739119

3 files changed

Lines changed: 46 additions & 3 deletions

File tree

crates/cli/src/cli/daemon.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,17 @@ async fn run_daemon_services(config: &Config, agent_id: &str) -> Result<()> {
164164
config.heartbeat.interval
165165
);
166166
Some(tokio::spawn(async move {
167-
match HeartbeatRunner::new_with_gate(
167+
// Create tool factory that provides CLI tools to heartbeat
168+
let tool_factory: localgpt_core::heartbeat::ToolFactory =
169+
Box::new(|config: &localgpt_core::config::Config| {
170+
crate::tools::create_cli_tools(config)
171+
});
172+
173+
match HeartbeatRunner::new_with_gate_and_tools(
168174
&heartbeat_config,
169175
&heartbeat_agent_id,
170176
Some(heartbeat_gate),
177+
Some(tool_factory),
171178
) {
172179
Ok(runner) => {
173180
if let Err(e) = runner.run().await {
@@ -444,7 +451,14 @@ async fn show_status() -> Result<()> {
444451

445452
async fn run_heartbeat_once(agent_id: &str) -> Result<()> {
446453
let config = Config::load()?;
447-
let runner = HeartbeatRunner::new_with_agent(&config, agent_id)?;
454+
455+
// Create tool factory to provide CLI tools
456+
let tool_factory: localgpt_core::heartbeat::ToolFactory =
457+
Box::new(|config: &localgpt_core::config::Config| {
458+
crate::tools::create_cli_tools(config)
459+
});
460+
461+
let runner = HeartbeatRunner::new_with_gate_and_tools(&config, agent_id, None, Some(tool_factory))?;
448462

449463
println!("Running heartbeat (agent: {})...", agent_id);
450464
let result = runner.run_once().await?;

crates/core/src/heartbeat/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ mod events;
22
mod runner;
33

44
pub use events::{HeartbeatEvent, HeartbeatStatus, emit_heartbeat_event, get_last_heartbeat_event};
5-
pub use runner::HeartbeatRunner;
5+
pub use runner::{HeartbeatRunner, ToolFactory};

crates/core/src/heartbeat/runner.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@ use tracing::{debug, info, warn};
1111
use super::events::{HeartbeatEvent, HeartbeatStatus, emit_heartbeat_event, now_ms};
1212
use crate::agent::{
1313
Agent, AgentConfig, HEARTBEAT_OK_TOKEN, SessionStore, build_heartbeat_prompt, is_heartbeat_ok,
14+
tools::Tool,
1415
};
1516
use crate::concurrency::{TurnGate, WorkspaceLock};
1617
use crate::config::{Config, parse_duration, parse_time};
1718
use crate::memory::MemoryManager;
1819

20+
/// Factory function type for creating additional tools for the heartbeat agent.
21+
/// This allows the caller (e.g., CLI daemon) to inject dangerous tools like bash, file I/O.
22+
pub type ToolFactory = Box<dyn Fn(&Config) -> Result<Vec<Box<dyn Tool>>> + Send + Sync>;
23+
1924
pub struct HeartbeatRunner {
2025
config: Config,
2126
interval: Duration,
@@ -28,6 +33,8 @@ pub struct HeartbeatRunner {
2833
turn_gate: Option<TurnGate>,
2934
/// Cross-process workspace lock
3035
workspace_lock: WorkspaceLock,
36+
/// Optional tool factory for injecting additional tools (e.g., CLI tools from daemon)
37+
tool_factory: Option<ToolFactory>,
3138
}
3239

3340
impl HeartbeatRunner {
@@ -49,6 +56,20 @@ impl HeartbeatRunner {
4956
config: &Config,
5057
agent_id: &str,
5158
turn_gate: Option<TurnGate>,
59+
) -> Result<Self> {
60+
Self::new_with_gate_and_tools(config, agent_id, turn_gate, None)
61+
}
62+
63+
/// Create a new HeartbeatRunner with optional TurnGate and tool factory.
64+
///
65+
/// The tool_factory allows injecting additional tools (e.g., bash, file I/O from CLI)
66+
/// into the heartbeat agent. This enables the heartbeat to perform filesystem operations
67+
/// and execute commands when running in a CLI/daemon context.
68+
pub fn new_with_gate_and_tools(
69+
config: &Config,
70+
agent_id: &str,
71+
turn_gate: Option<TurnGate>,
72+
tool_factory: Option<ToolFactory>,
5273
) -> Result<Self> {
5374
let interval = parse_duration(&config.heartbeat.interval)
5475
.map_err(|e| anyhow::anyhow!("Invalid heartbeat interval: {}", e))?;
@@ -82,6 +103,7 @@ impl HeartbeatRunner {
82103
memory,
83104
turn_gate,
84105
workspace_lock,
106+
tool_factory,
85107
})
86108
}
87109

@@ -319,6 +341,13 @@ impl HeartbeatRunner {
319341
};
320342

321343
let mut agent = Agent::new(agent_config, &self.config, self.memory.clone()).await?;
344+
345+
// Extend agent with additional tools from factory if provided (e.g., CLI tools from daemon)
346+
if let Some(ref factory) = self.tool_factory {
347+
let extra_tools = factory(&self.config)?;
348+
agent.extend_tools(extra_tools);
349+
}
350+
322351
agent.new_session().await?;
323352

324353
info!(name: "Heartbeat", "Running HEARTBEAT.md");

0 commit comments

Comments
 (0)