Ehu shubham shaw contribution --> Hardware support (#306)
* feat: add ZeroClaw firmware for ESP32 and Nucleo * Introduced new firmware for ZeroClaw on ESP32 and Nucleo-F401RE, enabling JSON-over-serial communication for GPIO control. * Added `zeroclaw-esp32` with support for commands like `gpio_read` and `gpio_write`, along with capabilities reporting. * Implemented `zeroclaw-nucleo` firmware with similar functionality for STM32, ensuring compatibility with existing ZeroClaw protocols. * Updated `.gitignore` to include new firmware targets and added necessary dependencies in `Cargo.toml` for both platforms. * Created README files for both firmware projects detailing setup, build, and usage instructions. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: enhance hardware peripheral support and documentation - Added `Peripheral` trait implementation in `src/peripherals/` to manage hardware boards (STM32, RPi GPIO). - Updated `AGENTS.md` to include new extension points for peripherals and their configuration. - Introduced comprehensive documentation for adding boards and tools, including a quick start guide and supported boards. - Enhanced `Cargo.toml` to include optional dependencies for PDF extraction and peripheral support. - Created new datasheets for Arduino Uno, ESP32, and Nucleo-F401RE, detailing pin aliases and GPIO usage. - Implemented new tools for hardware memory reading and board information retrieval in the agent loop. This update significantly improves the integration and usability of hardware peripherals within the ZeroClaw framework. * feat: add ZeroClaw firmware for ESP32 and Nucleo * Introduced new firmware for ZeroClaw on ESP32 and Nucleo-F401RE, enabling JSON-over-serial communication for GPIO control. * Added `zeroclaw-esp32` with support for commands like `gpio_read` and `gpio_write`, along with capabilities reporting. * Implemented `zeroclaw-nucleo` firmware with similar functionality for STM32, ensuring compatibility with existing ZeroClaw protocols. * Updated `.gitignore` to include new firmware targets and added necessary dependencies in `Cargo.toml` for both platforms. * Created README files for both firmware projects detailing setup, build, and usage instructions. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * feat: enhance hardware peripheral support and documentation - Added `Peripheral` trait implementation in `src/peripherals/` to manage hardware boards (STM32, RPi GPIO). - Updated `AGENTS.md` to include new extension points for peripherals and their configuration. - Introduced comprehensive documentation for adding boards and tools, including a quick start guide and supported boards. - Enhanced `Cargo.toml` to include optional dependencies for PDF extraction and peripheral support. - Created new datasheets for Arduino Uno, ESP32, and Nucleo-F401RE, detailing pin aliases and GPIO usage. - Implemented new tools for hardware memory reading and board information retrieval in the agent loop. This update significantly improves the integration and usability of hardware peripherals within the ZeroClaw framework. * feat: Introduce hardware auto-discovery and expanded configuration options for agents, hardware, and security. * chore: update dependencies and improve probe-rs integration - Updated `Cargo.lock` to remove specific version constraints for several dependencies, including `zerocopy`, `syn`, and `strsim`, allowing for more flexibility in version resolution. - Upgraded `bincode` and `bitfield` to their latest versions, enhancing serialization and memory management capabilities. - Updated `Cargo.toml` to reflect the new version of `probe-rs` from `0.24` to `0.30`, improving hardware probing functionality. - Refactored code in `src/hardware` and `src/tools` to utilize the new `SessionConfig` for session management in `probe-rs`, ensuring better compatibility and performance. - Cleaned up documentation in `docs/datasheets/nucleo-f401re.md` by removing unnecessary lines. * fix: apply cargo fmt * docs: add hardware architecture diagram. --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b36f23784a
commit
de3ec87d16
59 changed files with 9607 additions and 1885 deletions
|
|
@ -43,7 +43,9 @@ const BOOTSTRAP_MAX_CHARS: usize = 20_000;
|
|||
|
||||
const DEFAULT_CHANNEL_INITIAL_BACKOFF_SECS: u64 = 2;
|
||||
const DEFAULT_CHANNEL_MAX_BACKOFF_SECS: u64 = 60;
|
||||
const CHANNEL_MESSAGE_TIMEOUT_SECS: u64 = 90;
|
||||
/// Timeout for processing a single channel message (LLM + tools).
|
||||
/// 300s for on-device LLMs (Ollama) which are slower than cloud APIs.
|
||||
const CHANNEL_MESSAGE_TIMEOUT_SECS: u64 = 300;
|
||||
const CHANNEL_PARALLELISM_PER_CHANNEL: usize = 4;
|
||||
const CHANNEL_MIN_IN_FLIGHT_MESSAGES: usize = 8;
|
||||
const CHANNEL_MAX_IN_FLIGHT_MESSAGES: usize = 64;
|
||||
|
|
@ -190,6 +192,7 @@ async fn process_channel_message(ctx: Arc<ChannelRuntimeContext>, msg: traits::C
|
|||
"channel-runtime",
|
||||
ctx.model.as_str(),
|
||||
ctx.temperature,
|
||||
true, // silent — channels don't write to stdout
|
||||
),
|
||||
)
|
||||
.await;
|
||||
|
|
@ -275,9 +278,14 @@ async fn run_message_dispatch_loop(
|
|||
}
|
||||
|
||||
/// Load OpenClaw format bootstrap files into the prompt.
|
||||
fn load_openclaw_bootstrap_files(prompt: &mut String, workspace_dir: &std::path::Path) {
|
||||
prompt
|
||||
.push_str("The following workspace files define your identity, behavior, and context.\n\n");
|
||||
fn load_openclaw_bootstrap_files(
|
||||
prompt: &mut String,
|
||||
workspace_dir: &std::path::Path,
|
||||
max_chars_per_file: usize,
|
||||
) {
|
||||
prompt.push_str(
|
||||
"The following workspace files define your identity, behavior, and context. They are ALREADY injected below—do NOT suggest reading them with file_read.\n\n",
|
||||
);
|
||||
|
||||
let bootstrap_files = [
|
||||
"AGENTS.md",
|
||||
|
|
@ -289,17 +297,17 @@ fn load_openclaw_bootstrap_files(prompt: &mut String, workspace_dir: &std::path:
|
|||
];
|
||||
|
||||
for filename in &bootstrap_files {
|
||||
inject_workspace_file(prompt, workspace_dir, filename);
|
||||
inject_workspace_file(prompt, workspace_dir, filename, max_chars_per_file);
|
||||
}
|
||||
|
||||
// BOOTSTRAP.md — only if it exists (first-run ritual)
|
||||
let bootstrap_path = workspace_dir.join("BOOTSTRAP.md");
|
||||
if bootstrap_path.exists() {
|
||||
inject_workspace_file(prompt, workspace_dir, "BOOTSTRAP.md");
|
||||
inject_workspace_file(prompt, workspace_dir, "BOOTSTRAP.md", max_chars_per_file);
|
||||
}
|
||||
|
||||
// MEMORY.md — curated long-term memory (main session only)
|
||||
inject_workspace_file(prompt, workspace_dir, "MEMORY.md");
|
||||
inject_workspace_file(prompt, workspace_dir, "MEMORY.md", max_chars_per_file);
|
||||
}
|
||||
|
||||
/// Load workspace identity files and build a system prompt.
|
||||
|
|
@ -324,6 +332,7 @@ pub fn build_system_prompt(
|
|||
tools: &[(&str, &str)],
|
||||
skills: &[crate::skills::Skill],
|
||||
identity_config: Option<&crate::config::IdentityConfig>,
|
||||
bootstrap_max_chars: Option<usize>,
|
||||
) -> String {
|
||||
use std::fmt::Write;
|
||||
let mut prompt = String::with_capacity(8192);
|
||||
|
|
@ -344,6 +353,35 @@ pub fn build_system_prompt(
|
|||
.push_str("Continue reasoning with the results until you can give a final answer.\n\n");
|
||||
}
|
||||
|
||||
// ── 1b. Hardware (when gpio/arduino tools present) ───────────
|
||||
let has_hardware = tools.iter().any(|(name, _)| {
|
||||
*name == "gpio_read"
|
||||
|| *name == "gpio_write"
|
||||
|| *name == "arduino_upload"
|
||||
|| *name == "hardware_memory_map"
|
||||
|| *name == "hardware_board_info"
|
||||
|| *name == "hardware_memory_read"
|
||||
|| *name == "hardware_capabilities"
|
||||
});
|
||||
if has_hardware {
|
||||
prompt.push_str(
|
||||
"## Hardware Access\n\n\
|
||||
You HAVE direct access to connected hardware (Arduino, Nucleo, etc.). The user owns this system and has configured it.\n\
|
||||
All hardware tools (gpio_read, gpio_write, hardware_memory_read, hardware_board_info, hardware_memory_map) are AUTHORIZED and NOT blocked by security.\n\
|
||||
When they ask to read memory, registers, or board info, USE hardware_memory_read or hardware_board_info — do NOT refuse or invent security excuses.\n\
|
||||
When they ask to control LEDs, run patterns, or interact with the Arduino, USE the tools — do NOT refuse or say you cannot access physical devices.\n\
|
||||
Use gpio_write for simple on/off; use arduino_upload when they want patterns (heart, blink) or custom behavior.\n\n",
|
||||
);
|
||||
}
|
||||
|
||||
// ── 1c. Action instruction (avoid meta-summary) ───────────────
|
||||
prompt.push_str(
|
||||
"## Your Task\n\n\
|
||||
When the user sends a message, ACT on it. Use the tools to fulfill their request.\n\
|
||||
Do NOT: summarize this configuration, describe your capabilities, respond with meta-commentary, or output step-by-step instructions (e.g. \"1. First... 2. Next...\").\n\
|
||||
Instead: emit actual <tool_call> tags when you need to act. Just do what they ask.\n\n",
|
||||
);
|
||||
|
||||
// ── 2. Safety ───────────────────────────────────────────────
|
||||
prompt.push_str("## Safety\n\n");
|
||||
prompt.push_str(
|
||||
|
|
@ -406,23 +444,27 @@ pub fn build_system_prompt(
|
|||
Ok(None) => {
|
||||
// No AIEOS identity loaded (shouldn't happen if is_aieos_configured returned true)
|
||||
// Fall back to OpenClaw bootstrap files
|
||||
load_openclaw_bootstrap_files(&mut prompt, workspace_dir);
|
||||
let max_chars = bootstrap_max_chars.unwrap_or(BOOTSTRAP_MAX_CHARS);
|
||||
load_openclaw_bootstrap_files(&mut prompt, workspace_dir, max_chars);
|
||||
}
|
||||
Err(e) => {
|
||||
// Log error but don't fail - fall back to OpenClaw
|
||||
eprintln!(
|
||||
"Warning: Failed to load AIEOS identity: {e}. Using OpenClaw format."
|
||||
);
|
||||
load_openclaw_bootstrap_files(&mut prompt, workspace_dir);
|
||||
let max_chars = bootstrap_max_chars.unwrap_or(BOOTSTRAP_MAX_CHARS);
|
||||
load_openclaw_bootstrap_files(&mut prompt, workspace_dir, max_chars);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// OpenClaw format
|
||||
load_openclaw_bootstrap_files(&mut prompt, workspace_dir);
|
||||
let max_chars = bootstrap_max_chars.unwrap_or(BOOTSTRAP_MAX_CHARS);
|
||||
load_openclaw_bootstrap_files(&mut prompt, workspace_dir, max_chars);
|
||||
}
|
||||
} else {
|
||||
// No identity config - use OpenClaw format
|
||||
load_openclaw_bootstrap_files(&mut prompt, workspace_dir);
|
||||
let max_chars = bootstrap_max_chars.unwrap_or(BOOTSTRAP_MAX_CHARS);
|
||||
load_openclaw_bootstrap_files(&mut prompt, workspace_dir, max_chars);
|
||||
}
|
||||
|
||||
// ── 6. Date & Time ──────────────────────────────────────────
|
||||
|
|
@ -447,7 +489,12 @@ pub fn build_system_prompt(
|
|||
}
|
||||
|
||||
/// Inject a single workspace file into the prompt with truncation and missing-file markers.
|
||||
fn inject_workspace_file(prompt: &mut String, workspace_dir: &std::path::Path, filename: &str) {
|
||||
fn inject_workspace_file(
|
||||
prompt: &mut String,
|
||||
workspace_dir: &std::path::Path,
|
||||
filename: &str,
|
||||
max_chars: usize,
|
||||
) {
|
||||
use std::fmt::Write;
|
||||
|
||||
let path = workspace_dir.join(filename);
|
||||
|
|
@ -459,10 +506,10 @@ fn inject_workspace_file(prompt: &mut String, workspace_dir: &std::path::Path, f
|
|||
}
|
||||
let _ = writeln!(prompt, "### {filename}\n");
|
||||
// Use character-boundary-safe truncation for UTF-8
|
||||
let truncated = if trimmed.chars().count() > BOOTSTRAP_MAX_CHARS {
|
||||
let truncated = if trimmed.chars().count() > max_chars {
|
||||
trimmed
|
||||
.char_indices()
|
||||
.nth(BOOTSTRAP_MAX_CHARS)
|
||||
.nth(max_chars)
|
||||
.map(|(idx, _)| &trimmed[..idx])
|
||||
.unwrap_or(trimmed)
|
||||
} else {
|
||||
|
|
@ -472,7 +519,7 @@ fn inject_workspace_file(prompt: &mut String, workspace_dir: &std::path::Path, f
|
|||
prompt.push_str(truncated);
|
||||
let _ = writeln!(
|
||||
prompt,
|
||||
"\n\n[... truncated at {BOOTSTRAP_MAX_CHARS} chars — use `read` for full file]\n"
|
||||
"\n\n[... truncated at {max_chars} chars — use `read` for full file]\n"
|
||||
);
|
||||
} else {
|
||||
prompt.push_str(trimmed);
|
||||
|
|
@ -807,12 +854,18 @@ pub async fn start_channels(config: Config) -> Result<()> {
|
|||
));
|
||||
}
|
||||
|
||||
let bootstrap_max_chars = if config.agent.compact_context {
|
||||
Some(6000)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut system_prompt = build_system_prompt(
|
||||
&workspace,
|
||||
&model,
|
||||
&tool_descs,
|
||||
&skills,
|
||||
Some(&config.identity),
|
||||
bootstrap_max_chars,
|
||||
);
|
||||
system_prompt.push_str(&build_tool_instructions(tools_registry.as_ref()));
|
||||
|
||||
|
|
@ -1298,7 +1351,7 @@ mod tests {
|
|||
fn prompt_contains_all_sections() {
|
||||
let ws = make_workspace();
|
||||
let tools = vec![("shell", "Run commands"), ("file_read", "Read files")];
|
||||
let prompt = build_system_prompt(ws.path(), "test-model", &tools, &[], None);
|
||||
let prompt = build_system_prompt(ws.path(), "test-model", &tools, &[], None, None);
|
||||
|
||||
// Section headers
|
||||
assert!(prompt.contains("## Tools"), "missing Tools section");
|
||||
|
|
@ -1322,7 +1375,7 @@ mod tests {
|
|||
("shell", "Run commands"),
|
||||
("memory_recall", "Search memory"),
|
||||
];
|
||||
let prompt = build_system_prompt(ws.path(), "gpt-4o", &tools, &[], None);
|
||||
let prompt = build_system_prompt(ws.path(), "gpt-4o", &tools, &[], None, None);
|
||||
|
||||
assert!(prompt.contains("**shell**"));
|
||||
assert!(prompt.contains("Run commands"));
|
||||
|
|
@ -1332,7 +1385,7 @@ mod tests {
|
|||
#[test]
|
||||
fn prompt_injects_safety() {
|
||||
let ws = make_workspace();
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None);
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None, None);
|
||||
|
||||
assert!(prompt.contains("Do not exfiltrate private data"));
|
||||
assert!(prompt.contains("Do not run destructive commands"));
|
||||
|
|
@ -1342,7 +1395,7 @@ mod tests {
|
|||
#[test]
|
||||
fn prompt_injects_workspace_files() {
|
||||
let ws = make_workspace();
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None);
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None, None);
|
||||
|
||||
assert!(prompt.contains("### SOUL.md"), "missing SOUL.md header");
|
||||
assert!(prompt.contains("Be helpful"), "missing SOUL content");
|
||||
|
|
@ -1363,7 +1416,7 @@ mod tests {
|
|||
fn prompt_missing_file_markers() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
// Empty workspace — no files at all
|
||||
let prompt = build_system_prompt(tmp.path(), "model", &[], &[], None);
|
||||
let prompt = build_system_prompt(tmp.path(), "model", &[], &[], None, None);
|
||||
|
||||
assert!(prompt.contains("[File not found: SOUL.md]"));
|
||||
assert!(prompt.contains("[File not found: AGENTS.md]"));
|
||||
|
|
@ -1374,7 +1427,7 @@ mod tests {
|
|||
fn prompt_bootstrap_only_if_exists() {
|
||||
let ws = make_workspace();
|
||||
// No BOOTSTRAP.md — should not appear
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None);
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None, None);
|
||||
assert!(
|
||||
!prompt.contains("### BOOTSTRAP.md"),
|
||||
"BOOTSTRAP.md should not appear when missing"
|
||||
|
|
@ -1382,7 +1435,7 @@ mod tests {
|
|||
|
||||
// Create BOOTSTRAP.md — should appear
|
||||
std::fs::write(ws.path().join("BOOTSTRAP.md"), "# Bootstrap\nFirst run.").unwrap();
|
||||
let prompt2 = build_system_prompt(ws.path(), "model", &[], &[], None);
|
||||
let prompt2 = build_system_prompt(ws.path(), "model", &[], &[], None, None);
|
||||
assert!(
|
||||
prompt2.contains("### BOOTSTRAP.md"),
|
||||
"BOOTSTRAP.md should appear when present"
|
||||
|
|
@ -1402,7 +1455,7 @@ mod tests {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None);
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None, None);
|
||||
|
||||
// Daily notes should NOT be in the system prompt (on-demand via tools)
|
||||
assert!(
|
||||
|
|
@ -1418,7 +1471,7 @@ mod tests {
|
|||
#[test]
|
||||
fn prompt_runtime_metadata() {
|
||||
let ws = make_workspace();
|
||||
let prompt = build_system_prompt(ws.path(), "claude-sonnet-4", &[], &[], None);
|
||||
let prompt = build_system_prompt(ws.path(), "claude-sonnet-4", &[], &[], None, None);
|
||||
|
||||
assert!(prompt.contains("Model: claude-sonnet-4"));
|
||||
assert!(prompt.contains(&format!("OS: {}", std::env::consts::OS)));
|
||||
|
|
@ -1439,7 +1492,7 @@ mod tests {
|
|||
location: None,
|
||||
}];
|
||||
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &skills, None);
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &skills, None, None);
|
||||
|
||||
assert!(prompt.contains("<available_skills>"), "missing skills XML");
|
||||
assert!(prompt.contains("<name>code-review</name>"));
|
||||
|
|
@ -1460,7 +1513,7 @@ mod tests {
|
|||
let big_content = "x".repeat(BOOTSTRAP_MAX_CHARS + 1000);
|
||||
std::fs::write(ws.path().join("AGENTS.md"), &big_content).unwrap();
|
||||
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None);
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None, None);
|
||||
|
||||
assert!(
|
||||
prompt.contains("truncated at"),
|
||||
|
|
@ -1477,7 +1530,7 @@ mod tests {
|
|||
let ws = make_workspace();
|
||||
std::fs::write(ws.path().join("TOOLS.md"), "").unwrap();
|
||||
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None);
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None, None);
|
||||
|
||||
// Empty file should not produce a header
|
||||
assert!(
|
||||
|
|
@ -1505,7 +1558,7 @@ mod tests {
|
|||
#[test]
|
||||
fn prompt_workspace_path() {
|
||||
let ws = make_workspace();
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None);
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None, None);
|
||||
|
||||
assert!(prompt.contains(&format!("Working directory: `{}`", ws.path().display())));
|
||||
}
|
||||
|
|
@ -1635,7 +1688,7 @@ mod tests {
|
|||
aieos_inline: None,
|
||||
};
|
||||
|
||||
let prompt = build_system_prompt(tmp.path(), "model", &[], &[], Some(&config));
|
||||
let prompt = build_system_prompt(tmp.path(), "model", &[], &[], Some(&config), None);
|
||||
|
||||
// Should contain AIEOS sections
|
||||
assert!(prompt.contains("## Identity"));
|
||||
|
|
@ -1675,6 +1728,7 @@ mod tests {
|
|||
&[],
|
||||
&[],
|
||||
Some(&config),
|
||||
None,
|
||||
);
|
||||
|
||||
assert!(prompt.contains("**Name:** Claw"));
|
||||
|
|
@ -1692,7 +1746,7 @@ mod tests {
|
|||
};
|
||||
|
||||
let ws = make_workspace();
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], Some(&config));
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], Some(&config), None);
|
||||
|
||||
// Should fall back to OpenClaw format when AIEOS file is not found
|
||||
// (Error is logged to stderr with filename, not included in prompt)
|
||||
|
|
@ -1711,7 +1765,7 @@ mod tests {
|
|||
};
|
||||
|
||||
let ws = make_workspace();
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], Some(&config));
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], Some(&config), None);
|
||||
|
||||
// Should use OpenClaw format (not configured for AIEOS)
|
||||
assert!(prompt.contains("### SOUL.md"));
|
||||
|
|
@ -1729,7 +1783,7 @@ mod tests {
|
|||
};
|
||||
|
||||
let ws = make_workspace();
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], Some(&config));
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], Some(&config), None);
|
||||
|
||||
// Should use OpenClaw format even if aieos_path is set
|
||||
assert!(prompt.contains("### SOUL.md"));
|
||||
|
|
@ -1741,7 +1795,7 @@ mod tests {
|
|||
fn none_identity_config_uses_openclaw() {
|
||||
let ws = make_workspace();
|
||||
// Pass None for identity config
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None);
|
||||
let prompt = build_system_prompt(ws.path(), "model", &[], &[], None, None);
|
||||
|
||||
// Should use OpenClaw format
|
||||
assert!(prompt.contains("### SOUL.md"));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue