Compare commits
14 Commits
jochen_cac
...
micn/testi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95e5a59720 | ||
|
|
04ceefd5e2 | ||
|
|
40f9ea5eb3 | ||
|
|
69ae894de8 | ||
|
|
3643dad354 | ||
|
|
14c8d066c9 | ||
|
|
b6e226df67 | ||
|
|
5b46922047 | ||
|
|
1069664e16 | ||
|
|
725f54b99b | ||
|
|
325aab6b0e | ||
|
|
7f73b664a3 | ||
|
|
8d8ddbe4b9 | ||
|
|
0466405d87 |
73
.github/workflows/ci.yml
vendored
Normal file
73
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
arch: x86_64
|
||||
- os: ubuntu-latest
|
||||
arch: aarch64
|
||||
- os: macos-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Set up QEMU (for aarch64 on Linux)
|
||||
if: matrix.arch == 'aarch64' && runner.os == 'Linux'
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
target
|
||||
key: ${{ runner.os }}-${{ matrix.arch || 'x86_64' }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
||||
|
||||
- name: Install system dependencies (Ubuntu)
|
||||
if: runner.os == 'Linux' && matrix.arch != 'aarch64'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libx11-dev libxdo-dev libxcb-shape0-dev libxcb-xfixes0-dev libxtst-dev
|
||||
|
||||
- name: Build and test (Linux aarch64)
|
||||
if: matrix.arch == 'aarch64' && runner.os == 'Linux'
|
||||
uses: uraimo/run-on-arch-action@v2
|
||||
with:
|
||||
arch: aarch64
|
||||
distro: ubuntu22.04
|
||||
install: |
|
||||
apt-get update
|
||||
apt-get install -y curl build-essential libx11-dev libxdo-dev libxcb-shape0-dev libxcb-xfixes0-dev libxtst-dev
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
run: |
|
||||
. $HOME/.cargo/env
|
||||
cargo build --workspace --exclude g3-computer-control
|
||||
cargo test --workspace --exclude g3-computer-control --lib --tests
|
||||
|
||||
- name: Build (Linux x86_64)
|
||||
if: matrix.arch != 'aarch64' && runner.os == 'Linux'
|
||||
run: cargo build --workspace --exclude g3-computer-control
|
||||
|
||||
- name: Run tests (Linux x86_64)
|
||||
if: matrix.arch != 'aarch64' && runner.os == 'Linux'
|
||||
run: cargo test --workspace --exclude g3-computer-control --lib --tests
|
||||
|
||||
- name: Build (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: cargo build --workspace
|
||||
|
||||
- name: Run tests (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: cargo test --workspace --lib --tests
|
||||
27
Cargo.lock
generated
27
Cargo.lock
generated
@@ -576,6 +576,26 @@ dependencies = [
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad"
|
||||
dependencies = [
|
||||
"const_format_proc_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format_proc_macros"
|
||||
version = "0.2.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
@@ -1427,6 +1447,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"chrono",
|
||||
"const_format",
|
||||
"futures-util",
|
||||
"g3-computer-control",
|
||||
"g3-config",
|
||||
@@ -4090,6 +4111,12 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
|
||||
@@ -22,12 +22,13 @@ use_oauth = true
|
||||
|
||||
[providers.anthropic]
|
||||
api_key = "your-anthropic-api-key"
|
||||
model = "claude-3-haiku-20240307" # Using a faster model for player
|
||||
model = "claude-sonnet-4-5"
|
||||
max_tokens = 4096
|
||||
temperature = 0.3 # Slightly higher temperature for more creative implementations
|
||||
# cache_config = "ephemeral" # Optional: Enable prompt caching
|
||||
# Options: "ephemeral", "5minute", "1hour"
|
||||
# Reduces costs and latency for repeated prompts. Uses Anthropic's prompt caching with different TTLs.
|
||||
# enable_1m_context = true # optional, more expensive
|
||||
|
||||
[agent]
|
||||
fallback_default_max_tokens = 8192
|
||||
|
||||
@@ -14,14 +14,16 @@ max_tokens = 4096 # Per-request output limit (how many tokens the model can gen
|
||||
# Note: This is different from max_context_length (total conversation history size)
|
||||
temperature = 0.1
|
||||
use_oauth = true
|
||||
# cache_config = "ephemeral" # Optional: Enable prompt caching for Claude models on Databricks
|
||||
# Options: "ephemeral", "5minute", "1hour"
|
||||
# Reduces costs and latency for repeated prompts. Uses Anthropic's prompt caching with different TTLs.
|
||||
# The cache control will be automatically applied to:
|
||||
# - The system prompt at the start of each session
|
||||
# - Assistant responses after every 10 tool calls
|
||||
# - 5minute costs $3/mtok, more details below
|
||||
# https://docs.claude.com/en/docs/build-with-claude/prompt-caching#pricing
|
||||
|
||||
[providers.anthropic]
|
||||
api_key = "your-anthropic-api-key"
|
||||
model = "claude-sonnet-4-5"
|
||||
max_tokens = 4096
|
||||
temperature = 0.3 # Slightly higher temperature for more creative implementations
|
||||
# cache_config = "ephemeral" # Optional: Enable prompt caching
|
||||
# Options: "ephemeral", "5minute", "1hour"
|
||||
# Reduces costs and latency for repeated prompts. Uses Anthropic's prompt caching with different TTLs.
|
||||
# enable_1m_context = true # optional, more expensive
|
||||
|
||||
|
||||
# Multiple OpenAI-compatible providers can be configured with custom names
|
||||
|
||||
@@ -1686,6 +1686,9 @@ async fn run_autonomous(
|
||||
turn, max_turns
|
||||
));
|
||||
|
||||
// Surface provider info for player agent
|
||||
agent.print_provider_banner("Player");
|
||||
|
||||
// Player mode: implement requirements (with coach feedback if available)
|
||||
let player_prompt = if coach_feedback.is_empty() {
|
||||
format!(
|
||||
@@ -1879,6 +1882,9 @@ async fn run_autonomous(
|
||||
let mut coach_agent =
|
||||
Agent::new_autonomous_with_readme_and_quiet(coach_config, ui_writer, None, quiet).await?;
|
||||
|
||||
// Surface provider info for coach agent
|
||||
coach_agent.print_provider_banner("Coach");
|
||||
|
||||
// Ensure coach agent is also in the workspace directory
|
||||
project.enter_workspace()?;
|
||||
|
||||
|
||||
256
crates/g3-console/src/logs.rs
Normal file
256
crates/g3-console/src/logs.rs
Normal file
@@ -0,0 +1,256 @@
|
||||
use crate::models::{InstanceStats, TurnInfo};
|
||||
use anyhow::{Context, Result};
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LogEntry {
|
||||
pub timestamp: Option<DateTime<Utc>>,
|
||||
pub role: Option<String>,
|
||||
pub content: Option<String>,
|
||||
pub tool_calls: Option<Vec<Value>>,
|
||||
pub raw: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ChatMessage {
|
||||
pub role: String,
|
||||
pub content: String,
|
||||
pub timestamp: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolCall {
|
||||
pub name: String,
|
||||
pub parameters: Value,
|
||||
pub result: Option<String>,
|
||||
pub timestamp: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
pub struct LogParser;
|
||||
|
||||
impl LogParser {
|
||||
/// Parse logs from a workspace directory
|
||||
pub fn parse_logs(workspace: &Path) -> Result<Vec<LogEntry>> {
|
||||
let logs_dir = workspace.join("logs");
|
||||
|
||||
if !logs_dir.exists() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let mut entries = Vec::new();
|
||||
|
||||
// Read all JSON log files
|
||||
for entry in fs::read_dir(&logs_dir).context("Failed to read logs directory")? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if path.extension().and_then(|s| s.to_str()) == Some("json") {
|
||||
if let Ok(content) = fs::read_to_string(&path) {
|
||||
if let Ok(json) = serde_json::from_str::<Value>(&content) {
|
||||
// Try to parse as a log session
|
||||
if let Some(messages) = json.get("messages").and_then(|m| m.as_array()) {
|
||||
for msg in messages {
|
||||
entries.push(LogEntry {
|
||||
timestamp: msg.get("timestamp")
|
||||
.and_then(|t| t.as_str())
|
||||
.and_then(|s| DateTime::parse_from_rfc3339(s).ok())
|
||||
.map(|dt| dt.with_timezone(&Utc)),
|
||||
role: msg.get("role")
|
||||
.and_then(|r| r.as_str())
|
||||
.map(String::from),
|
||||
content: msg.get("content")
|
||||
.and_then(|c| c.as_str())
|
||||
.map(String::from),
|
||||
tool_calls: msg.get("tool_calls")
|
||||
.and_then(|tc| tc.as_array())
|
||||
.map(|arr| arr.clone()),
|
||||
raw: msg.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by timestamp
|
||||
entries.sort_by(|a, b| {
|
||||
match (&a.timestamp, &b.timestamp) {
|
||||
(Some(t1), Some(t2)) => t1.cmp(t2),
|
||||
(Some(_), None) => std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||
(None, None) => std::cmp::Ordering::Equal,
|
||||
}
|
||||
});
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
/// Extract chat messages from log entries
|
||||
pub fn extract_chat_messages(entries: &[LogEntry]) -> Vec<ChatMessage> {
|
||||
entries
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
let role = entry.role.clone()?;
|
||||
let content = entry.content.clone()?;
|
||||
|
||||
Some(ChatMessage {
|
||||
role,
|
||||
content,
|
||||
timestamp: entry.timestamp,
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Extract tool calls from log entries
|
||||
pub fn extract_tool_calls(entries: &[LogEntry]) -> Vec<ToolCall> {
|
||||
let mut tool_calls = Vec::new();
|
||||
|
||||
for entry in entries {
|
||||
if let Some(calls) = &entry.tool_calls {
|
||||
for call in calls {
|
||||
if let Some(name) = call.get("name").and_then(|n| n.as_str()) {
|
||||
tool_calls.push(ToolCall {
|
||||
name: name.to_string(),
|
||||
parameters: call.get("parameters")
|
||||
.cloned()
|
||||
.unwrap_or(Value::Object(serde_json::Map::new())),
|
||||
result: call.get("result")
|
||||
.and_then(|r| r.as_str())
|
||||
.map(String::from),
|
||||
timestamp: entry.timestamp,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tool_calls
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StatsAggregator;
|
||||
|
||||
impl StatsAggregator {
|
||||
/// Aggregate statistics from log entries
|
||||
pub fn aggregate_stats(
|
||||
entries: &[LogEntry],
|
||||
start_time: DateTime<Utc>,
|
||||
is_ensemble: bool,
|
||||
) -> InstanceStats {
|
||||
let total_tokens = Self::count_tokens(entries);
|
||||
let tool_calls = Self::count_tool_calls(entries);
|
||||
let errors = Self::count_errors(entries);
|
||||
|
||||
let duration_secs = if let Some(last_entry) = entries.last() {
|
||||
if let Some(last_time) = last_entry.timestamp {
|
||||
(last_time - start_time).num_seconds().max(0) as u64
|
||||
} else {
|
||||
(Utc::now() - start_time).num_seconds().max(0) as u64
|
||||
}
|
||||
} else {
|
||||
(Utc::now() - start_time).num_seconds().max(0) as u64
|
||||
};
|
||||
|
||||
let turns = if is_ensemble {
|
||||
Some(Self::extract_turns(entries))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
InstanceStats {
|
||||
total_tokens,
|
||||
tool_calls,
|
||||
errors,
|
||||
duration_secs,
|
||||
turns,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the latest message content from log entries
|
||||
pub fn get_latest_message(entries: &[LogEntry]) -> Option<String> {
|
||||
entries
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|entry| entry.role.as_deref() == Some("assistant"))
|
||||
.and_then(|entry| entry.content.clone())
|
||||
.or_else(|| {
|
||||
entries
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|entry| entry.content.is_some())
|
||||
.and_then(|entry| entry.content.clone())
|
||||
})
|
||||
}
|
||||
|
||||
fn count_tokens(entries: &[LogEntry]) -> u64 {
|
||||
// Try to extract token counts from metadata
|
||||
entries
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
entry.raw.get("usage")
|
||||
.and_then(|u| u.get("total_tokens"))
|
||||
.and_then(|t| t.as_u64())
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn count_tool_calls(entries: &[LogEntry]) -> u64 {
|
||||
entries
|
||||
.iter()
|
||||
.filter_map(|entry| entry.tool_calls.as_ref())
|
||||
.map(|calls| calls.len() as u64)
|
||||
.sum()
|
||||
}
|
||||
|
||||
fn count_errors(entries: &[LogEntry]) -> u64 {
|
||||
entries
|
||||
.iter()
|
||||
.filter(|entry| {
|
||||
entry.raw.get("error").is_some()
|
||||
|| entry.content.as_ref().map(|c| c.to_lowercase().contains("error")).unwrap_or(false)
|
||||
})
|
||||
.count() as u64
|
||||
}
|
||||
|
||||
fn extract_turns(entries: &[LogEntry]) -> Vec<TurnInfo> {
|
||||
// Simple implementation: group consecutive assistant messages as turns
|
||||
let mut turns = Vec::new();
|
||||
let mut current_turn_start: Option<DateTime<Utc>> = None;
|
||||
let mut turn_count = 0;
|
||||
|
||||
for entry in entries {
|
||||
if entry.role.as_deref() == Some("assistant") {
|
||||
if current_turn_start.is_none() {
|
||||
current_turn_start = entry.timestamp;
|
||||
turn_count += 1;
|
||||
}
|
||||
} else if entry.role.as_deref() == Some("user") {
|
||||
if let Some(start) = current_turn_start {
|
||||
if let Some(end) = entry.timestamp {
|
||||
let duration = (end - start).num_seconds().max(0) as u64;
|
||||
turns.push(TurnInfo {
|
||||
agent: format!("agent-{}", turn_count),
|
||||
duration_secs: duration,
|
||||
status: "completed".to_string(),
|
||||
color: Self::get_turn_color(turn_count),
|
||||
});
|
||||
}
|
||||
current_turn_start = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
turns
|
||||
}
|
||||
|
||||
fn get_turn_color(turn_number: usize) -> String {
|
||||
let colors = vec!["blue", "green", "purple", "orange", "pink", "teal"];
|
||||
colors[turn_number % colors.len()].to_string()
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ use anyhow::Result;
|
||||
use chrono::{DateTime, Utc};
|
||||
use std::path::PathBuf;
|
||||
use sysinfo::{System, Pid, Process};
|
||||
use tracing::{debug, warn};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
pub struct ProcessDetector {
|
||||
system: System,
|
||||
@@ -17,7 +17,11 @@ impl ProcessDetector {
|
||||
}
|
||||
|
||||
pub fn detect_instances(&mut self) -> Result<Vec<Instance>> {
|
||||
self.system.refresh_processes();
|
||||
info!("Scanning for g3 processes...");
|
||||
// Refresh all processes to ensure we catch newly started ones
|
||||
// Using refresh_all() instead of just refresh_processes() to ensure
|
||||
// we get complete information about new processes
|
||||
self.system.refresh_all();
|
||||
let mut instances = Vec::new();
|
||||
|
||||
// Find all g3 processes
|
||||
@@ -33,7 +37,7 @@ impl ProcessDetector {
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Detected {} g3 instances", instances.len());
|
||||
info!("Detected {} g3 instances", instances.len());
|
||||
Ok(instances)
|
||||
}
|
||||
|
||||
@@ -45,24 +49,27 @@ impl ProcessDetector {
|
||||
) -> Option<Instance> {
|
||||
let cmd_str = cmd.join(" ");
|
||||
|
||||
// Exclude g3-console itself
|
||||
if cmd_str.contains("g3-console") {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Check if this is a g3 binary (more comprehensive check)
|
||||
let is_g3_binary = cmd.get(0).map(|s| {
|
||||
s.ends_with("g3") || s.ends_with("/g3") || s.contains("/target/release/g3") || s.contains("/target/debug/g3")
|
||||
(s.ends_with("g3") || s.ends_with("/g3") || s.contains("/target/release/g3") || s.contains("/target/debug/g3"))
|
||||
&& !s.contains("g3-") // Exclude other g3-* binaries
|
||||
}).unwrap_or(false);
|
||||
|
||||
// Check if this is cargo run with g3
|
||||
let is_cargo_run = cmd.get(0).map(|s| s.contains("cargo")).unwrap_or(false) && cmd.iter().any(|s| s == "run");
|
||||
// Check if this is cargo run with g3 (not g3-console or other variants)
|
||||
let is_cargo_run = cmd.get(0).map(|s| s.contains("cargo")).unwrap_or(false)
|
||||
&& cmd.iter().any(|s| s == "run")
|
||||
&& !cmd_str.contains("g3-console");
|
||||
|
||||
// Also check if any part of the command line contains g3-related patterns
|
||||
let has_g3_pattern = cmd_str.contains("g3 ")
|
||||
|| cmd_str.contains("/g3 ")
|
||||
|| cmd_str.contains("g3-")
|
||||
|| cmd_str.ends_with("g3")
|
||||
|| cmd_str.contains("--workspace") // g3-specific flag
|
||||
|| cmd_str.contains("--autonomous"); // g3-specific flag
|
||||
// Also check if command line has g3-specific flags
|
||||
let has_g3_flags = cmd_str.contains("--workspace") || cmd_str.contains("--autonomous");
|
||||
|
||||
// Accept if it's a g3 binary, cargo run with g3 patterns, or has g3-specific flags
|
||||
let is_g3_process = is_g3_binary || (is_cargo_run && has_g3_pattern) || has_g3_pattern;
|
||||
// Accept if it's a g3 binary or cargo run with g3, and has typical g3 patterns
|
||||
let is_g3_process = is_g3_binary || (is_cargo_run && has_g3_flags);
|
||||
|
||||
if !is_g3_process {
|
||||
return None;
|
||||
@@ -165,7 +172,7 @@ impl ProcessDetector {
|
||||
}
|
||||
|
||||
pub fn get_process_status(&mut self, pid: u32) -> Option<InstanceStatus> {
|
||||
self.system.refresh_processes();
|
||||
self.system.refresh_all();
|
||||
|
||||
let sysinfo_pid = Pid::from_u32(pid);
|
||||
if self.system.process(sysinfo_pid).is_some() {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<div id="app">
|
||||
<header class="header">
|
||||
<div class="header-content">
|
||||
<h1 class="header-title">G3 Console</h1>
|
||||
<h1 class="header-title">G3 Console <span id="live-indicator" class="live-indicator" title="Scanning for processes every 3 seconds">● LIVE</span></h1>
|
||||
<div class="header-actions">
|
||||
<button id="new-run-btn" class="btn btn-primary">+ New Run</button>
|
||||
<button id="theme-toggle" class="btn btn-secondary">🌙</button>
|
||||
|
||||
@@ -6,6 +6,7 @@ const router = {
|
||||
currentInstanceId: null,
|
||||
initialized: false,
|
||||
renderInProgress: false,
|
||||
REFRESH_INTERVAL_MS: 3000, // Refresh every 3 seconds for live updates
|
||||
|
||||
init() {
|
||||
console.log('[Router] init() called');
|
||||
@@ -84,6 +85,9 @@ const router = {
|
||||
this.renderInProgress = true;
|
||||
|
||||
try {
|
||||
// Flash live indicator
|
||||
this.flashLiveIndicator();
|
||||
|
||||
// Check if we already have a container for instances
|
||||
let instancesList = container.querySelector('.instances-list');
|
||||
const isInitialLoad = !instancesList;
|
||||
@@ -167,11 +171,11 @@ const router = {
|
||||
|
||||
// Schedule next refresh only if still on home route
|
||||
if (this.currentRoute === '/' || this.currentRoute === '') {
|
||||
console.log('[Router] Scheduling auto-refresh in 5 seconds');
|
||||
console.log(`[Router] Scheduling auto-refresh in ${this.REFRESH_INTERVAL_MS}ms`);
|
||||
this.refreshTimeout = setTimeout(() => {
|
||||
console.log('[Router] Auto-refresh triggered');
|
||||
this.renderHome(container);
|
||||
}, 5000);
|
||||
}, this.REFRESH_INTERVAL_MS);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Router] Error in renderHome:', error);
|
||||
@@ -187,12 +191,26 @@ const router = {
|
||||
}
|
||||
},
|
||||
|
||||
flashLiveIndicator() {
|
||||
const indicator = document.getElementById('live-indicator');
|
||||
if (indicator) {
|
||||
indicator.style.animation = 'none';
|
||||
// Force reflow
|
||||
void indicator.offsetWidth;
|
||||
indicator.style.animation = null;
|
||||
indicator.style.opacity = '1';
|
||||
}
|
||||
},
|
||||
|
||||
async renderDetail(container, id) {
|
||||
console.log('[Router] renderDetail called for', id);
|
||||
|
||||
this.currentInstanceId = id;
|
||||
|
||||
try {
|
||||
// Flash live indicator
|
||||
this.flashLiveIndicator();
|
||||
|
||||
// Check if we already have a detail view for this instance
|
||||
let detailView = container.querySelector('.detail-view');
|
||||
const isInitialLoad = !detailView || detailView.getAttribute('data-instance-id') !== id;
|
||||
|
||||
@@ -64,6 +64,22 @@ body {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.live-indicator {
|
||||
font-size: 0.625rem; /* 75% of 0.833rem */
|
||||
font-weight: 600;
|
||||
color: var(--success);
|
||||
margin-left: 0.75rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
|
||||
@@ -43,6 +43,8 @@ tree-sitter-scheme = "0.24"
|
||||
streaming-iterator = "0.1"
|
||||
walkdir = "2.4"
|
||||
|
||||
const_format = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.8"
|
||||
serial_test = "3.0"
|
||||
|
||||
@@ -4,7 +4,6 @@ pub mod project;
|
||||
pub mod task_result;
|
||||
pub mod ui_writer;
|
||||
|
||||
use std::process::exit;
|
||||
pub use task_result::TaskResult;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -21,6 +20,8 @@ mod tilde_expansion_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod error_handling_test;
|
||||
mod prompts;
|
||||
|
||||
use anyhow::Result;
|
||||
use g3_computer_control::WebDriverController;
|
||||
use g3_config::Config;
|
||||
@@ -33,6 +34,7 @@ use serde_json::json;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use prompts::{SYSTEM_PROMPT_FOR_NON_NATIVE_TOOL_USE, SYSTEM_PROMPT_FOR_NATIVE_TOOL_USE};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolCall {
|
||||
@@ -938,10 +940,34 @@ impl<W: UiWriter> Agent<W> {
|
||||
debug!("Default provider set successfully");
|
||||
|
||||
// Determine context window size based on active provider
|
||||
let context_length = Self::get_configured_context_length(&config, &providers)?;
|
||||
let mut context_warnings = Vec::new();
|
||||
let context_length =
|
||||
Self::get_configured_context_length(&config, &providers, &mut context_warnings)?;
|
||||
let mut context_window = ContextWindow::new(context_length);
|
||||
|
||||
// If README content is provided, add it as the first system message
|
||||
// Surface any context warnings to the user via UI
|
||||
for warning in context_warnings {
|
||||
ui_writer.print_context_status(&format!("⚠️ {}", warning));
|
||||
}
|
||||
|
||||
// Add system prompt as the FIRST message (before README)
|
||||
// This ensures the agent always has proper tool usage instructions
|
||||
let provider = providers.get(None)?;
|
||||
let provider_has_native_tool_calling = provider.has_native_tool_calling();
|
||||
let _ = provider; // Drop provider reference to avoid borrowing issues
|
||||
|
||||
let system_prompt = if provider_has_native_tool_calling {
|
||||
// For native tool calling providers, use a more explicit system prompt
|
||||
SYSTEM_PROMPT_FOR_NATIVE_TOOL_USE.to_string()
|
||||
} else {
|
||||
// For non-native providers (embedded models), use JSON format instructions
|
||||
SYSTEM_PROMPT_FOR_NON_NATIVE_TOOL_USE.to_string()
|
||||
};
|
||||
|
||||
let system_message = Message::new(MessageRole::System, system_prompt);
|
||||
context_window.add_message(system_message);
|
||||
|
||||
// If README content is provided, add it as a second system message (after the main system prompt)
|
||||
if let Some(readme) = readme_content {
|
||||
let readme_message = Message::new(MessageRole::System, readme);
|
||||
context_window.add_message(readme_message);
|
||||
@@ -1003,6 +1029,35 @@ impl<W: UiWriter> Agent<W> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Validate that the system prompt is the first message in the conversation history.
|
||||
/// This is a critical invariant that must be maintained for proper agent operation.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if:
|
||||
/// - The conversation history is empty
|
||||
/// - The first message is not a System message
|
||||
/// - The first message doesn't contain the system prompt markers
|
||||
fn validate_system_prompt_is_first(&self) {
|
||||
if self.context_window.conversation_history.is_empty() {
|
||||
panic!(
|
||||
"FATAL: Conversation history is empty. System prompt must be the first message."
|
||||
);
|
||||
}
|
||||
|
||||
let first_message = &self.context_window.conversation_history[0];
|
||||
|
||||
if !matches!(first_message.role, MessageRole::System) {
|
||||
panic!(
|
||||
"FATAL: First message is not a System message. Found: {:?}",
|
||||
first_message.role
|
||||
);
|
||||
}
|
||||
|
||||
if !first_message.content.contains("You are G3") {
|
||||
panic!("FATAL: First system message does not contain the system prompt. This likely means the README was added before the system prompt.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert cache config string to CacheControl enum
|
||||
fn parse_cache_control(cache_config: &str) -> Option<CacheControl> {
|
||||
match cache_config {
|
||||
@@ -1016,24 +1071,72 @@ impl<W: UiWriter> Agent<W> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_configured_context_length(config: &Config, providers: &ProviderRegistry) -> Result<u32> {
|
||||
/// Get the configured max_tokens for a provider from top-level config
|
||||
fn provider_max_tokens(config: &Config, provider_name: &str) -> Option<u32> {
|
||||
match provider_name {
|
||||
"anthropic" => config.providers.anthropic.as_ref()?.max_tokens,
|
||||
"openai" => config.providers.openai.as_ref()?.max_tokens,
|
||||
"databricks" => config.providers.databricks.as_ref()?.max_tokens,
|
||||
"embedded" => config.providers.embedded.as_ref()?.max_tokens,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve the max_tokens to use for a given provider, applying fallbacks
|
||||
fn resolve_max_tokens(&self, provider_name: &str) -> u32 {
|
||||
match provider_name {
|
||||
"databricks" => Self::provider_max_tokens(&self.config, "databricks")
|
||||
.or(Some(self.config.agent.fallback_default_max_tokens as u32))
|
||||
.unwrap_or(32000),
|
||||
other => Self::provider_max_tokens(&self.config, other)
|
||||
.or(Some(self.config.agent.fallback_default_max_tokens as u32))
|
||||
.unwrap_or(16000),
|
||||
}
|
||||
}
|
||||
|
||||
/// Print provider diagnostics through the UiWriter for visibility
|
||||
pub fn print_provider_banner(&self, role_label: &str) {
|
||||
if let Ok((provider_name, model)) = self.get_provider_info() {
|
||||
let max_tokens = self.resolve_max_tokens(&provider_name);
|
||||
let context_len = self.context_window.total_tokens;
|
||||
|
||||
let mut details = vec![
|
||||
format!("provider={}", provider_name),
|
||||
format!("model={}", model),
|
||||
format!("max_tokens={}", max_tokens),
|
||||
format!("context_window_length={}", context_len),
|
||||
];
|
||||
|
||||
if let Ok(provider) = self.providers.get(None) {
|
||||
details.push(format!(
|
||||
"native_tools={}",
|
||||
if provider.has_native_tool_calling() {
|
||||
"yes"
|
||||
} else {
|
||||
"no"
|
||||
}
|
||||
));
|
||||
if provider.supports_cache_control() {
|
||||
details.push("cache_control=yes".to_string());
|
||||
}
|
||||
}
|
||||
|
||||
self.ui_writer
|
||||
.print_context_status(&format!("{}: {}", role_label, details.join(", ")));
|
||||
}
|
||||
}
|
||||
|
||||
fn get_configured_context_length(
|
||||
config: &Config,
|
||||
providers: &ProviderRegistry,
|
||||
warnings: &mut Vec<String>,
|
||||
) -> Result<u32> {
|
||||
// First, check if there's a global max_context_length override in agent config
|
||||
if let Some(max_context_length) = config.agent.max_context_length {
|
||||
debug!("Using configured agent.max_context_length: {}", max_context_length);
|
||||
return Ok(max_context_length);
|
||||
}
|
||||
|
||||
// Get the configured max_tokens for the current provider
|
||||
fn get_provider_max_tokens(config: &Config, provider_name: &str) -> Option<u32> {
|
||||
match provider_name {
|
||||
"anthropic" => config.providers.anthropic.as_ref()?.max_tokens,
|
||||
"openai" => config.providers.openai.as_ref()?.max_tokens,
|
||||
"databricks" => config.providers.databricks.as_ref()?.max_tokens,
|
||||
"embedded" => config.providers.embedded.as_ref()?.max_tokens,
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
// Get the active provider to determine context length
|
||||
let provider = providers.get(None)?;
|
||||
let provider_name = provider.name();
|
||||
@@ -1060,25 +1163,45 @@ impl<W: UiWriter> Agent<W> {
|
||||
}
|
||||
"openai" => {
|
||||
// gpt-5 has 400k window
|
||||
get_provider_max_tokens(config, "openai").unwrap_or(400000)
|
||||
if let Some(max_tokens) = Self::provider_max_tokens(config, "openai") {
|
||||
warnings.push(format!(
|
||||
"Context length falling back to max_tokens ({}) for provider=openai",
|
||||
max_tokens
|
||||
));
|
||||
max_tokens
|
||||
} else {
|
||||
400000
|
||||
}
|
||||
}
|
||||
"anthropic" => {
|
||||
// Claude models have large context windows
|
||||
// Use configured max_tokens or fall back to default
|
||||
get_provider_max_tokens(config, "anthropic").unwrap_or(200000)
|
||||
if let Some(max_tokens) = Self::provider_max_tokens(config, "anthropic") {
|
||||
warnings.push(format!(
|
||||
"Context length falling back to max_tokens ({}) for provider=anthropic",
|
||||
max_tokens
|
||||
));
|
||||
max_tokens
|
||||
} else {
|
||||
200000
|
||||
}
|
||||
}
|
||||
"databricks" => {
|
||||
// Databricks models have varying context windows depending on the model
|
||||
// Use configured max_tokens or fall back to model-specific defaults
|
||||
get_provider_max_tokens(config, "databricks").unwrap_or_else(|| {
|
||||
if model_name.contains("claude") {
|
||||
200000 // Claude models on Databricks have large context windows
|
||||
} else if model_name.contains("llama") || model_name.contains("dbrx") {
|
||||
32768 // DBRX supports 32k context
|
||||
} else {
|
||||
16384 // Conservative default for other Databricks models
|
||||
}
|
||||
})
|
||||
if let Some(max_tokens) = Self::provider_max_tokens(config, "databricks") {
|
||||
warnings.push(format!(
|
||||
"Context length falling back to max_tokens ({}) for provider=databricks",
|
||||
max_tokens
|
||||
));
|
||||
max_tokens
|
||||
} else if model_name.contains("claude") {
|
||||
200000 // Claude models on Databricks have large context windows
|
||||
} else if model_name.contains("llama") || model_name.contains("dbrx") {
|
||||
32768 // DBRX supports 32k context
|
||||
} else {
|
||||
16384 // Conservative default for other Databricks models
|
||||
}
|
||||
}
|
||||
_ => config.agent.fallback_default_max_tokens as u32,
|
||||
};
|
||||
@@ -1178,7 +1301,7 @@ impl<W: UiWriter> Agent<W> {
|
||||
async fn execute_single_task(
|
||||
&mut self,
|
||||
description: &str,
|
||||
show_prompt: bool,
|
||||
_show_prompt: bool,
|
||||
_show_code: bool,
|
||||
show_timing: bool,
|
||||
cancellation_token: CancellationToken,
|
||||
@@ -1186,345 +1309,15 @@ impl<W: UiWriter> Agent<W> {
|
||||
// Reset the JSON tool call filter state at the start of each new task
|
||||
// This prevents the filter from staying in suppression mode between user interactions
|
||||
fixed_filter_json::reset_fixed_json_tool_state();
|
||||
|
||||
// Validate that the system prompt is the first message (critical invariant)
|
||||
self.validate_system_prompt_is_first();
|
||||
|
||||
// Generate session ID based on the initial prompt if this is a new session
|
||||
if self.session_id.is_none() {
|
||||
self.session_id = Some(self.generate_session_id(description));
|
||||
}
|
||||
|
||||
// Only add system message if this is the first interaction (empty conversation history)
|
||||
if self.context_window.conversation_history.is_empty() {
|
||||
let provider = self.providers.get(None)?;
|
||||
let provider_has_native_tool_calling = provider.has_native_tool_calling();
|
||||
let provider_name_for_system = provider.name().to_string();
|
||||
drop(provider); // Drop provider reference to avoid borrowing issues
|
||||
|
||||
let system_prompt = if provider_has_native_tool_calling {
|
||||
// For native tool calling providers, use a more explicit system prompt
|
||||
"You are G3, an AI programming agent of the same skill level as a seasoned engineer at a major technology company. You analyze given tasks and write code to achieve goals.
|
||||
|
||||
You have access to tools. When you need to accomplish a task, you MUST use the appropriate tool. Do not just describe what you would do - actually use the tools.
|
||||
|
||||
IMPORTANT: You must call tools to achieve goals. When you receive a request:
|
||||
1. Analyze and identify what needs to be done
|
||||
2. Call the appropriate tool with the required parameters
|
||||
3. Continue or complete the task based on the result
|
||||
4. If you repeatedly try something and it fails, try a different approach
|
||||
5. Call the final_output tool with a detailed summary when done.
|
||||
|
||||
For shell commands: Use the shell tool with the exact command needed. Avoid commands that produce a large amount of output, and consider piping those outputs to files. Example: If asked to list files, immediately call the shell tool with command parameter \"ls\".
|
||||
If you create temporary files for verification, place these in a subdir named 'tmp'. Do NOT pollute the current dir.
|
||||
|
||||
# Task Management with TODO Tools
|
||||
|
||||
**REQUIRED for multi-step tasks.** Use TODO tools when your task involves ANY of:
|
||||
- Multiple files to create/modify (2+)
|
||||
- Multiple distinct steps (3+)
|
||||
- Dependencies between steps
|
||||
- Testing or verification needed
|
||||
- Uncertainty about approach
|
||||
|
||||
## Workflow
|
||||
|
||||
Every multi-step task follows this pattern:
|
||||
1. **Start**: Call todo_read, then todo_write to create your plan
|
||||
2. **During**: Execute steps, then todo_read and todo_write to mark progress
|
||||
3. **End**: Call todo_read to verify all items complete
|
||||
|
||||
Note: todo_write replaces the entire todo.g3.md file, so always read first to preserve content. TODO lists persist across g3 sessions in the workspace directory.
|
||||
|
||||
## Examples
|
||||
|
||||
**Example 1: Feature Implementation**
|
||||
User asks: \"Add user authentication with tests\"
|
||||
|
||||
First action:
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
|
||||
Then create plan:
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Add user authentication\\n - [ ] Create User struct\\n - [ ] Add login endpoint\\n - [ ] Add password hashing\\n - [ ] Write unit tests\\n - [ ] Write integration tests\"}}
|
||||
|
||||
After completing User struct:
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Add user authentication\\n - [x] Create User struct\\n - [ ] Add login endpoint\\n - [ ] Add password hashing\\n - [ ] Write unit tests\\n - [ ] Write integration tests\"}}
|
||||
|
||||
**Example 2: Bug Fix**
|
||||
User asks: \"Fix the memory leak in cache module\"
|
||||
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Fix memory leak\\n - [ ] Review cache.rs\\n - [ ] Check for unclosed resources\\n - [ ] Add drop implementation\\n - [ ] Write test to verify fix\"}}
|
||||
|
||||
**Example 3: Refactoring**
|
||||
User asks: \"Refactor database layer to use async/await\"
|
||||
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Refactor to async\\n - [ ] Update function signatures\\n - [ ] Replace blocking calls\\n - [ ] Update all callers\\n - [ ] Update tests\"}}
|
||||
|
||||
## Format
|
||||
|
||||
Use markdown checkboxes:
|
||||
- \"- [ ]\" for incomplete tasks
|
||||
- \"- [x]\" for completed tasks
|
||||
- Indent with 2 spaces for subtasks
|
||||
|
||||
Keep items short, specific, and action-oriented.
|
||||
|
||||
## Benefits
|
||||
|
||||
✓ Prevents missed steps
|
||||
✓ Makes progress visible
|
||||
✓ Helps recover from interruptions
|
||||
✓ Creates better summaries
|
||||
|
||||
## When NOT to Use
|
||||
|
||||
Skip TODO tools for simple single-step tasks:
|
||||
- \"List files\" → just use shell
|
||||
- \"Read config.json\" → just use read_file
|
||||
- \"Search for functions\" → just use code_search
|
||||
|
||||
If you can complete it with 1-2 tool calls, skip TODO.
|
||||
|
||||
# Code Search Guidelines
|
||||
|
||||
IMPORTANT: When searching for code constructs (functions, classes, methods, structs, etc.), ALWAYS use `code_search` instead of shell grep/rg.
|
||||
If you create temporary files for verification, place these in a subdir named 'tmp'. Do NOT pollute the current dir.
|
||||
|
||||
# Code Search Guidelines
|
||||
|
||||
IMPORTANT: When searching for code constructs (functions, classes, methods, structs, etc.), ALWAYS use `code_search` instead of shell grep/rg.
|
||||
It's syntax-aware and finds actual code, not comments or strings. Only use shell grep for:
|
||||
- Searching non-code files (logs, markdown, text)
|
||||
- Simple string searches across all file types
|
||||
- When you need regex for text content (not code structure)
|
||||
|
||||
Common code_search query patterns:
|
||||
|
||||
**Rust:**
|
||||
- All functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"functions\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- Async functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"async_fns\", \"query\": \"(function_item (function_modifiers) name: (identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- Structs: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"structs\", \"query\": \"(struct_item name: (type_identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- Enums: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"enums\", \"query\": \"(enum_item name: (type_identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- Impl blocks: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"impls\", \"query\": \"(impl_item type: (type_identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
|
||||
**Python:**
|
||||
- Functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"functions\", \"query\": \"(function_definition name: (identifier) @name)\", \"language\": \"python\"}]}}
|
||||
- Classes: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"classes\", \"query\": \"(class_definition name: (identifier) @name)\", \"language\": \"python\"}]}}
|
||||
|
||||
**JavaScript/TypeScript:**
|
||||
- Functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"functions\", \"query\": \"(function_declaration name: (identifier) @name)\", \"language\": \"javascript\"}]}}
|
||||
- Classes: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"classes\", \"query\": \"(class_declaration name: (identifier) @name)\", \"language\": \"javascript\"}]}}
|
||||
- Arrow functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"arrow_fns\", \"query\": \"(arrow_function) @fn\", \"language\": \"javascript\"}]}}
|
||||
|
||||
**Go:**
|
||||
- Functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"functions\", \"query\": \"(function_declaration name: (identifier) @name)\", \"language\": \"go\"}]}}
|
||||
- Methods: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"methods\", \"query\": \"(method_declaration name: (field_identifier) @name)\", \"language\": \"go\"}]}}
|
||||
|
||||
**Java/C++:**
|
||||
- Classes: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"classes\", \"query\": \"(class_declaration name: (identifier) @name)\", \"language\": \"java\"}]}}
|
||||
- Methods: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"methods\", \"query\": \"(method_declaration name: (identifier) @name)\", \"language\": \"java\"}]}}
|
||||
|
||||
**Advanced features:**
|
||||
- Multiple searches: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"funcs\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\"}, {\"name\": \"structs\", \"query\": \"(struct_item name: (type_identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- With context: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"funcs\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\", \"context_lines\": 3}]}}
|
||||
- Specific paths: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"funcs\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\", \"paths\": [\"src/core\"]}]}}
|
||||
|
||||
|
||||
IMPORTANT: If the user asks you to just respond with text (like \"just say hello\" or \"tell me about X\"), do NOT use tools. Simply respond with the requested text directly. Only use tools when you need to execute commands or complete tasks that require action.
|
||||
|
||||
When taking screenshots of specific windows (like \"my Safari window\" or \"my terminal\"), ALWAYS use list_windows first to identify the correct window ID, then use take_screenshot with the window_id parameter.
|
||||
|
||||
Do not explain what you're going to do - just do it by calling the tools.
|
||||
|
||||
|
||||
# Response Guidelines
|
||||
|
||||
- Use Markdown formatting for all responses except tool calls.
|
||||
- Whenever taking actions, use the pronoun 'I'
|
||||
".to_string()
|
||||
} else {
|
||||
// For non-native providers (embedded models), use JSON format instructions
|
||||
"You are G3, a general-purpose AI agent. Your goal is to analyze and solve problems by writing code.
|
||||
|
||||
You have access to tools. When you need to accomplish a task, you MUST use the appropriate tool. Do not just describe what you would do - actually use the tools.
|
||||
|
||||
# Tool Call Format
|
||||
|
||||
When you need to execute a tool, write ONLY the JSON tool call on a new line:
|
||||
|
||||
{\"tool\": \"tool_name\", \"args\": {\"param\": \"value\"}
|
||||
|
||||
The tool will execute immediately and you'll receive the result (success or error) to continue with.
|
||||
|
||||
# Available Tools
|
||||
|
||||
Short description for providers without native calling specs:
|
||||
|
||||
- **shell**: Execute shell commands
|
||||
- Format: {\"tool\": \"shell\", \"args\": {\"command\": \"your_command_here\"}
|
||||
- Example: {\"tool\": \"shell\", \"args\": {\"command\": \"ls ~/Downloads\"}
|
||||
|
||||
- **read_file**: Read the contents of a file (supports partial reads via start/end)
|
||||
- Format: {\"tool\": \"read_file\", \"args\": {\"file_path\": \"path/to/file\", \"start\": 0, \"end\": 100}
|
||||
- Example: {\"tool\": \"read_file\", \"args\": {\"file_path\": \"src/main.rs\"}
|
||||
- Example (partial): {\"tool\": \"read_file\", \"args\": {\"file_path\": \"large.log\", \"start\": 0, \"end\": 1000}
|
||||
|
||||
- **write_file**: Write content to a file (creates or overwrites)
|
||||
- Format: {\"tool\": \"write_file\", \"args\": {\"file_path\": \"path/to/file\", \"content\": \"file content\"}
|
||||
- Example: {\"tool\": \"write_file\", \"args\": {\"file_path\": \"src/lib.rs\", \"content\": \"pub fn hello() {}\"}
|
||||
|
||||
- **str_replace**: Replace text in a file using a diff
|
||||
- Format: {\"tool\": \"str_replace\", \"args\": {\"file_path\": \"path/to/file\", \"diff\": \"--- old\\n-old text\\n+++ new\\n+new text\"}
|
||||
- Example: {\"tool\": \"str_replace\", \"args\": {\"file_path\": \"src/main.rs\", \"diff\": \"--- old\\n-old_code();\\n+++ new\\n+new_code();\"}
|
||||
|
||||
- **final_output**: Signal task completion with a detailed summary of work done in markdown format
|
||||
- Format: {\"tool\": \"final_output\", \"args\": {\"summary\": \"what_was_accomplished\"}
|
||||
|
||||
- **todo_read**: Read the entire TODO list from todo.g3.md file in workspace directory
|
||||
- Format: {\"tool\": \"todo_read\", \"args\": {}}
|
||||
- Example: {\"tool\": \"todo_read\", \"args\": {}}
|
||||
|
||||
- **todo_write**: Write or overwrite the entire todo.g3.md file (WARNING: overwrites completely, always read first)
|
||||
- Format: {\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Task 1\\n- [ ] Task 2\"}}
|
||||
- Example: {\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Implement feature\\n - [ ] Write tests\\n - [ ] Run tests\"}}
|
||||
|
||||
- **code_search**: Syntax-aware code search using tree-sitter. Supports Rust, Python, JavaScript, TypeScript.
|
||||
- Format: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"label\", \"query\": \"tree-sitter query\", \"language\": \"rust|python|javascript|typescript\", \"paths\": [\"src/\"], \"context_lines\": 0}]}}
|
||||
- Find functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"find_functions\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\", \"paths\": [\"src/\"]}]}}
|
||||
- Find async functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"find_async\", \"query\": \"(function_item (function_modifiers) name: (identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- Find structs: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"structs\", \"query\": \"(struct_item name: (type_identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- Multiple searches: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"funcs\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\"}, {\"name\": \"structs\", \"query\": \"(struct_item name: (type_identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- With context lines: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"funcs\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\", \"context_lines\": 3}]}}
|
||||
- \"context\": 3 (show surrounding lines),
|
||||
- \"json_style\": \"stream\" (for large results)
|
||||
|
||||
# Instructions
|
||||
|
||||
1. Analyze the request and break down into smaller tasks if appropriate
|
||||
2. Execute ONE tool at a time. An exception exists for when you're writing files. See below.
|
||||
3. STOP when the original request was satisfied
|
||||
4. Call the final_output tool when done
|
||||
|
||||
For reading files, prioritize use of code_search tool use with multiple search requests per call instead of read_file, if it makes sense.
|
||||
|
||||
Exception to using ONE tool at a time:
|
||||
If all you’re doing is WRITING files, and you don’t need to do anything else between each step.
|
||||
You can issue MULTIPLE write_file tool calls in a request, however you may ONLY make a SINGLE write_file call for any file in that request.
|
||||
For example you may call:
|
||||
[START OF REQUEST]
|
||||
write_file(\"helper.rs\", \"...\")
|
||||
write_file(\"file2.txt\", \"...\")
|
||||
[DONE]
|
||||
|
||||
But NOT:
|
||||
[START OF REQUEST]
|
||||
write_file(\"helper.rs\", \"...\")
|
||||
write_file(\"file2.txt\", \"...\")
|
||||
write_file(\"helper.rs\", \"...\")
|
||||
[DONE]
|
||||
|
||||
# Task Management with TODO Tools
|
||||
|
||||
**REQUIRED for multi-step tasks.** Use TODO tools when your task involves ANY of:
|
||||
- Multiple files to create/modify (2+)
|
||||
- Multiple distinct steps (3+)
|
||||
- Dependencies between steps
|
||||
- Testing or verification needed
|
||||
- Uncertainty about approach
|
||||
|
||||
## Workflow
|
||||
|
||||
Every multi-step task follows this pattern:
|
||||
1. **Start**: Call todo_read, then todo_write to create your plan
|
||||
2. **During**: Execute steps, then todo_read and todo_write to mark progress
|
||||
3. **End**: Call todo_read to verify all items complete
|
||||
|
||||
Note: todo_write replaces the entire list, so always read first to preserve content.
|
||||
|
||||
## Examples
|
||||
|
||||
**Example 1: Feature Implementation**
|
||||
User asks: \"Add user authentication with tests\"
|
||||
|
||||
First action:
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
|
||||
Then create plan:
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Add user authentication\\n - [ ] Create User struct\\n - [ ] Add login endpoint\\n - [ ] Add password hashing\\n - [ ] Write unit tests\\n - [ ] Write integration tests\"}}
|
||||
|
||||
After completing User struct:
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Add user authentication\\n - [x] Create User struct\\n - [ ] Add login endpoint\\n - [ ] Add password hashing\\n - [ ] Write unit tests\\n - [ ] Write integration tests\"}}
|
||||
|
||||
**Example 2: Bug Fix**
|
||||
User asks: \"Fix the memory leak in cache module\"
|
||||
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Fix memory leak\\n - [ ] Review cache.rs\\n - [ ] Check for unclosed resources\\n - [ ] Add drop implementation\\n - [ ] Write test to verify fix\"}}
|
||||
|
||||
**Example 3: Refactoring**
|
||||
User asks: \"Refactor database layer to use async/await\"
|
||||
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Refactor to async\\n - [ ] Update function signatures\\n - [ ] Replace blocking calls\\n - [ ] Update all callers\\n - [ ] Update tests\"}}
|
||||
|
||||
## Format
|
||||
|
||||
Use markdown checkboxes:
|
||||
- \"- [ ]\" for incomplete tasks
|
||||
- \"- [x]\" for completed tasks
|
||||
- Indent with 2 spaces for subtasks
|
||||
|
||||
Keep items short, specific, and action-oriented.
|
||||
|
||||
## Benefits
|
||||
|
||||
✓ Prevents missed steps
|
||||
✓ Makes progress visible
|
||||
✓ Helps recover from interruptions
|
||||
✓ Creates better summaries
|
||||
|
||||
## When NOT to Use
|
||||
|
||||
Skip TODO tools for simple single-step tasks:
|
||||
- \"List files\" → just use shell
|
||||
- \"Read config.json\" → just use read_file
|
||||
- \"Search for functions\" → just use code_search
|
||||
|
||||
If you can complete it with 1-2 tool calls, skip TODO.
|
||||
|
||||
|
||||
# Response Guidelines
|
||||
|
||||
- Use Markdown formatting for all responses except tool calls.
|
||||
- Whenever taking actions, use the pronoun 'I'
|
||||
|
||||
".to_string()
|
||||
};
|
||||
|
||||
if show_prompt {
|
||||
self.ui_writer.print_system_prompt(&system_prompt);
|
||||
}
|
||||
|
||||
// Add system message to context window
|
||||
let system_message = {
|
||||
// Check if we should use cache control for system message
|
||||
if let Some(cache_config) = match provider_name_for_system.as_str() {
|
||||
"anthropic" => self.config.providers.anthropic.as_ref()
|
||||
.and_then(|c| c.cache_config.as_ref())
|
||||
.and_then(|config| Self::parse_cache_control(config)),
|
||||
_ => None,
|
||||
} {
|
||||
let provider = self.providers.get(None)?;
|
||||
Message::with_cache_control_validated(MessageRole::System, system_prompt, cache_config, provider)
|
||||
} else {
|
||||
Message::new(MessageRole::System, system_prompt)
|
||||
}
|
||||
};
|
||||
|
||||
self.context_window.add_message(system_message);
|
||||
}
|
||||
|
||||
// Add user message to context window
|
||||
let user_message = Message::new(MessageRole::User, format!("Task: {}", description));
|
||||
self.context_window.add_message(user_message);
|
||||
@@ -1535,8 +1328,8 @@ If you can complete it with 1-2 tool calls, skip TODO.
|
||||
// Check if provider supports native tool calling and add tools if so
|
||||
let provider = self.providers.get(None)?;
|
||||
let provider_name = provider.name().to_string();
|
||||
let has_native_tool_calling = provider.has_native_tool_calling();
|
||||
let supports_cache_control = provider.supports_cache_control();
|
||||
let _has_native_tool_calling = provider.has_native_tool_calling();
|
||||
let _supports_cache_control = provider.supports_cache_control();
|
||||
let tools = if provider.has_native_tool_calling() {
|
||||
Some(Self::create_tool_definitions(
|
||||
self.config.webdriver.enabled,
|
||||
@@ -1546,19 +1339,10 @@ If you can complete it with 1-2 tool calls, skip TODO.
|
||||
} else {
|
||||
None
|
||||
};
|
||||
drop(provider); // Drop the provider reference to avoid borrowing issues
|
||||
let _ = provider; // Drop the provider reference to avoid borrowing issues
|
||||
|
||||
// Get max_tokens from provider configuration
|
||||
let max_tokens = match provider_name.as_str() {
|
||||
"databricks" => {
|
||||
// Use the model's maximum limit for Databricks to allow large file generation
|
||||
Some(32000)
|
||||
}
|
||||
_ => {
|
||||
// Default for other providers
|
||||
Some(16000)
|
||||
}
|
||||
};
|
||||
// Get max_tokens from provider configuration, falling back to sensible defaults
|
||||
let max_tokens = Some(self.resolve_max_tokens(&provider_name));
|
||||
|
||||
let request = CompletionRequest {
|
||||
messages,
|
||||
@@ -1915,17 +1699,21 @@ If you can complete it with 1-2 tool calls, skip TODO.
|
||||
pub fn reload_readme(&mut self) -> Result<bool> {
|
||||
info!("Manual README reload triggered");
|
||||
|
||||
// Check if the first message in conversation history is a system message with README content
|
||||
// Check if the second message in conversation history is a system message with README content
|
||||
// (The first message should always be the system prompt)
|
||||
let has_readme = self
|
||||
.context_window
|
||||
.conversation_history
|
||||
.first()
|
||||
.get(1) // Check the SECOND message (index 1)
|
||||
.map(|m| {
|
||||
matches!(m.role, MessageRole::System)
|
||||
&& (m.content.contains("Project README")
|
||||
|| m.content.contains("Agent Configuration"))
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
// Validate that the system prompt is still first
|
||||
self.validate_system_prompt_is_first();
|
||||
|
||||
if !has_readme {
|
||||
return Ok(false);
|
||||
@@ -1949,8 +1737,8 @@ If you can complete it with 1-2 tool calls, skip TODO.
|
||||
}
|
||||
|
||||
if found_any {
|
||||
// Replace the first message with the new content
|
||||
if let Some(first_msg) = self.context_window.conversation_history.first_mut() {
|
||||
// Replace the second message (README) with the new content
|
||||
if let Some(first_msg) = self.context_window.conversation_history.get_mut(1) {
|
||||
first_msg.content = combined_content;
|
||||
info!("README content reloaded successfully");
|
||||
Ok(true)
|
||||
@@ -5699,6 +5487,16 @@ mod integration_tests {
|
||||
// Implement Drop to clean up safaridriver process
|
||||
impl<W: UiWriter> Drop for Agent<W> {
|
||||
fn drop(&mut self) {
|
||||
// Validate system prompt invariant on drop (agent exit)
|
||||
// This catches any bugs where the conversation history was corrupted during execution
|
||||
if !self.context_window.conversation_history.is_empty() {
|
||||
if let Err(e) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
self.validate_system_prompt_is_first();
|
||||
})) {
|
||||
eprintln!("\n⚠️ FATAL ERROR ON EXIT: System prompt validation failed: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Try to kill safaridriver process if it's still running
|
||||
// We need to use try_lock since we can't await in Drop
|
||||
if let Ok(mut process_guard) = self.safaridriver_process.try_write() {
|
||||
|
||||
348
crates/g3-core/src/prompts.rs
Normal file
348
crates/g3-core/src/prompts.rs
Normal file
@@ -0,0 +1,348 @@
|
||||
use const_format::concatcp;
|
||||
const CODING_STYLE: &'static str = "# IMPORTANT FOR CODING:
|
||||
It is very important that you adhere to these principles when writing code. I will use a code quality tool to assess the code you have generated.
|
||||
|
||||
### Most important for coding: Specific guideline for code design:
|
||||
|
||||
- Functions and methods should be short - at most 80 lines, ideally under 40.
|
||||
- Classes should be modular and composable. They should not have more than 20 methods.
|
||||
- Do not write deeply nested (above 6 levels deep) ‘if’, ‘match’ or ‘case’ statements, rather refactor into separate logical sections or functions.
|
||||
- Code should be written such that it is maintainable and testable.
|
||||
- For Rust code write *ALL* test code into a ‘tests’ directory that is a peer to the ‘src’ of each crate, and is for testing code in that crate.
|
||||
- For Python code write *ALL* test code into a top level ‘tests’ directory.
|
||||
- Each non-trivial function should have test coverage. DO NOT WRITE TESTS FOR INDIVIDUAL FUNCTIONS / METHODS / CLASSES unless they are large and important. Instead write something
|
||||
at a higher level of abstraction, closer to an integration test.
|
||||
- Write tests in separate files, where the filename should match the main implementation and adding a “_test” suffix.
|
||||
|
||||
### Important for coding: General guidelines for code design:
|
||||
|
||||
Keep the code as simple as possible, with few if any external dependencies.
|
||||
DRY (Don’t repeat yourself) - each small piece code may only occur exactly once in the entire system.
|
||||
KISS (Keep it simple, stupid!) - keep each small piece of software simple and unnecessary complexity should be avoided.
|
||||
YAGNI (You ain’t gonna need it) - Always implement things when you actually need them never implements things before you need them.
|
||||
|
||||
Use Descriptive Names for Code Elements. - As a rule of thumb, use more descriptive names for larger scopes. e.g., name a loop counter variable “i” is good when the scope of the loop is a single line. But don’t name some class field or method parameter “i”.
|
||||
|
||||
When modifying an existing code base, do not unnecessarily refactor or modify code that is not directly relevant to the current coding task. It is fine to do so if new code calls/is called by the new functionality, or you prevent code duplication when new functionality is added.
|
||||
If possible constrain the side-effects on other pieces of code if possible, this is part of the principle of modularity.
|
||||
|
||||
### Important for coding: General advice on designing algorithms:
|
||||
|
||||
If possible, consider the \"Gang of Four\" design patterns when writing code.
|
||||
|
||||
The Gang of Four (GOF) patterns are set of 23 common software design patterns introduced in the book
|
||||
\"Design Patterns: Elements of Reusable Object-Oriented Software\".
|
||||
|
||||
These patterns categorize into three main groups:
|
||||
|
||||
1. Creational Patterns
|
||||
2. Structural Patterns
|
||||
3. Behavioral Patterns
|
||||
|
||||
These patterns provide solutions to common design problems and help make software systems more modular, flexible and maintainable. Consider using these patterns in your code design.";
|
||||
|
||||
const SYSTEM_NATIVE_TOOL_CALLS: &'static str =
|
||||
"You are G3, an AI programming agent of the same skill level as a seasoned engineer at a major technology company. You analyze given tasks and write code to achieve goals.
|
||||
|
||||
You have access to tools. When you need to accomplish a task, you MUST use the appropriate tool. Do not just describe what you would do - actually use the tools.
|
||||
|
||||
IMPORTANT: You must call tools to achieve goals. When you receive a request:
|
||||
1. Analyze and identify what needs to be done
|
||||
2. Call the appropriate tool with the required parameters
|
||||
3. Continue or complete the task based on the result
|
||||
4. If you repeatedly try something and it fails, try a different approach
|
||||
5. Call the final_output tool with a detailed summary when done.
|
||||
|
||||
For shell commands: Use the shell tool with the exact command needed. Avoid commands that produce a large amount of output, and consider piping those outputs to files. Example: If asked to list files, immediately call the shell tool with command parameter \"ls\".
|
||||
If you create temporary files for verification, place these in a subdir named 'tmp'. Do NOT pollute the current dir.
|
||||
|
||||
# Task Management with TODO Tools
|
||||
|
||||
**REQUIRED for multi-step tasks.** Use TODO tools when your task involves ANY of:
|
||||
- Multiple files to create/modify (2+)
|
||||
- Multiple distinct steps (3+)
|
||||
- Dependencies between steps
|
||||
- Testing or verification needed
|
||||
- Uncertainty about approach
|
||||
|
||||
## Workflow
|
||||
|
||||
Every multi-step task follows this pattern:
|
||||
1. **Start**: Call todo_read, then todo_write to create your plan
|
||||
2. **During**: Execute steps, then todo_read and todo_write to mark progress
|
||||
3. **End**: Call todo_read to verify all items complete
|
||||
|
||||
Note: todo_write replaces the entire todo.g3.md file, so always read first to preserve content. TODO lists persist across g3 sessions in the workspace directory.
|
||||
|
||||
## Examples
|
||||
|
||||
**Example 1: Feature Implementation**
|
||||
User asks: \"Add user authentication with tests\"
|
||||
|
||||
First action:
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
|
||||
Then create plan:
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Add user authentication\\n - [ ] Create User struct\\n - [ ] Add login endpoint\\n - [ ] Add password hashing\\n - [ ] Write unit tests\\n - [ ] Write integration tests\"}}
|
||||
|
||||
After completing User struct:
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Add user authentication\\n - [x] Create User struct\\n - [ ] Add login endpoint\\n - [ ] Add password hashing\\n - [ ] Write unit tests\\n - [ ] Write integration tests\"}}
|
||||
|
||||
**Example 2: Bug Fix**
|
||||
User asks: \"Fix the memory leak in cache module\"
|
||||
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Fix memory leak\\n - [ ] Review cache.rs\\n - [ ] Check for unclosed resources\\n - [ ] Add drop implementation\\n - [ ] Write test to verify fix\"}}
|
||||
|
||||
**Example 3: Refactoring**
|
||||
User asks: \"Refactor database layer to use async/await\"
|
||||
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Refactor to async\\n - [ ] Update function signatures\\n - [ ] Replace blocking calls\\n - [ ] Update all callers\\n - [ ] Update tests\"}}
|
||||
|
||||
## Format
|
||||
|
||||
Use markdown checkboxes:
|
||||
- \"- [ ]\" for incomplete tasks
|
||||
- \"- [x]\" for completed tasks
|
||||
- Indent with 2 spaces for subtasks
|
||||
|
||||
Keep items short, specific, and action-oriented.
|
||||
|
||||
## Benefits
|
||||
|
||||
✓ Prevents missed steps
|
||||
✓ Makes progress visible
|
||||
✓ Helps recover from interruptions
|
||||
✓ Creates better summaries
|
||||
|
||||
## When NOT to Use
|
||||
|
||||
Skip TODO tools for simple single-step tasks:
|
||||
- \"List files\" → just use shell
|
||||
- \"Read config.json\" → just use read_file
|
||||
- \"Search for functions\" → just use code_search
|
||||
|
||||
If you can complete it with 1-2 tool calls, skip TODO.
|
||||
|
||||
# Code Search Guidelines
|
||||
|
||||
IMPORTANT: When searching for code constructs (functions, classes, methods, structs, etc.), ALWAYS use `code_search` instead of shell grep/rg.
|
||||
If you create temporary files for verification, place these in a subdir named 'tmp'. Do NOT pollute the current dir.
|
||||
|
||||
# Code Search Guidelines
|
||||
|
||||
IMPORTANT: When searching for code constructs (functions, classes, methods, structs, etc.), ALWAYS use `code_search` instead of shell grep/rg.
|
||||
It's syntax-aware and finds actual code, not comments or strings. Only use shell grep for:
|
||||
- Searching non-code files (logs, markdown, text)
|
||||
- Simple string searches across all file types
|
||||
- When you need regex for text content (not code structure)
|
||||
|
||||
Common code_search query patterns:
|
||||
|
||||
**Rust:**
|
||||
- All functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"functions\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- Async functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"async_fns\", \"query\": \"(function_item (function_modifiers) name: (identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- Structs: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"structs\", \"query\": \"(struct_item name: (type_identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- Enums: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"enums\", \"query\": \"(enum_item name: (type_identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- Impl blocks: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"impls\", \"query\": \"(impl_item type: (type_identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
|
||||
**Python:**
|
||||
- Functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"functions\", \"query\": \"(function_definition name: (identifier) @name)\", \"language\": \"python\"}]}}
|
||||
- Classes: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"classes\", \"query\": \"(class_definition name: (identifier) @name)\", \"language\": \"python\"}]}}
|
||||
|
||||
**JavaScript/TypeScript:**
|
||||
- Functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"functions\", \"query\": \"(function_declaration name: (identifier) @name)\", \"language\": \"javascript\"}]}}
|
||||
- Classes: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"classes\", \"query\": \"(class_declaration name: (identifier) @name)\", \"language\": \"javascript\"}]}}
|
||||
- Arrow functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"arrow_fns\", \"query\": \"(arrow_function) @fn\", \"language\": \"javascript\"}]}}
|
||||
|
||||
**Go:**
|
||||
- Functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"functions\", \"query\": \"(function_declaration name: (identifier) @name)\", \"language\": \"go\"}]}}
|
||||
- Methods: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"methods\", \"query\": \"(method_declaration name: (field_identifier) @name)\", \"language\": \"go\"}]}}
|
||||
|
||||
**Java/C++:**
|
||||
- Classes: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"classes\", \"query\": \"(class_declaration name: (identifier) @name)\", \"language\": \"java\"}]}}
|
||||
- Methods: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"methods\", \"query\": \"(method_declaration name: (identifier) @name)\", \"language\": \"java\"}]}}
|
||||
|
||||
**Advanced features:**
|
||||
- Multiple searches: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"funcs\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\"}, {\"name\": \"structs\", \"query\": \"(struct_item name: (type_identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- With context: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"funcs\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\", \"context_lines\": 3}]}}
|
||||
- Specific paths: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"funcs\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\", \"paths\": [\"src/core\"]}]}}
|
||||
|
||||
|
||||
IMPORTANT: If the user asks you to just respond with text (like \"just say hello\" or \"tell me about X\"), do NOT use tools. Simply respond with the requested text directly. Only use tools when you need to execute commands or complete tasks that require action.
|
||||
|
||||
When taking screenshots of specific windows (like \"my Safari window\" or \"my terminal\"), ALWAYS use list_windows first to identify the correct window ID, then use take_screenshot with the window_id parameter.
|
||||
|
||||
Do not explain what you're going to do - just do it by calling the tools.
|
||||
|
||||
|
||||
# Response Guidelines
|
||||
|
||||
- Use Markdown formatting for all responses except tool calls.
|
||||
- Whenever taking actions, use the pronoun 'I'
|
||||
";
|
||||
|
||||
pub const SYSTEM_PROMPT_FOR_NATIVE_TOOL_USE: &'static str =
|
||||
concatcp!(CODING_STYLE, SYSTEM_NATIVE_TOOL_CALLS);
|
||||
|
||||
const SYSTEM_NON_NATIVE_TOOL_USE: &'static str =
|
||||
"You are G3, a general-purpose AI agent. Your goal is to analyze and solve problems by writing code.
|
||||
|
||||
You have access to tools. When you need to accomplish a task, you MUST use the appropriate tool. Do not just describe what you would do - actually use the tools.
|
||||
|
||||
# Tool Call Format
|
||||
|
||||
When you need to execute a tool, write ONLY the JSON tool call on a new line:
|
||||
|
||||
{\"tool\": \"tool_name\", \"args\": {\"param\": \"value\"}
|
||||
|
||||
The tool will execute immediately and you'll receive the result (success or error) to continue with.
|
||||
|
||||
# Available Tools
|
||||
|
||||
Short description for providers without native calling specs:
|
||||
|
||||
- **shell**: Execute shell commands
|
||||
- Format: {\"tool\": \"shell\", \"args\": {\"command\": \"your_command_here\"}
|
||||
- Example: {\"tool\": \"shell\", \"args\": {\"command\": \"ls ~/Downloads\"}
|
||||
|
||||
- **read_file**: Read the contents of a file (supports partial reads via start/end)
|
||||
- Format: {\"tool\": \"read_file\", \"args\": {\"file_path\": \"path/to/file\", \"start\": 0, \"end\": 100}
|
||||
- Example: {\"tool\": \"read_file\", \"args\": {\"file_path\": \"src/main.rs\"}
|
||||
- Example (partial): {\"tool\": \"read_file\", \"args\": {\"file_path\": \"large.log\", \"start\": 0, \"end\": 1000}
|
||||
|
||||
- **write_file**: Write content to a file (creates or overwrites)
|
||||
- Format: {\"tool\": \"write_file\", \"args\": {\"file_path\": \"path/to/file\", \"content\": \"file content\"}
|
||||
- Example: {\"tool\": \"write_file\", \"args\": {\"file_path\": \"src/lib.rs\", \"content\": \"pub fn hello() {}\"}
|
||||
|
||||
- **str_replace**: Replace text in a file using a diff
|
||||
- Format: {\"tool\": \"str_replace\", \"args\": {\"file_path\": \"path/to/file\", \"diff\": \"--- old\\n-old text\\n+++ new\\n+new text\"}
|
||||
- Example: {\"tool\": \"str_replace\", \"args\": {\"file_path\": \"src/main.rs\", \"diff\": \"--- old\\n-old_code();\\n+++ new\\n+new_code();\"}
|
||||
|
||||
- **final_output**: Signal task completion with a detailed summary of work done in markdown format
|
||||
- Format: {\"tool\": \"final_output\", \"args\": {\"summary\": \"what_was_accomplished\"}
|
||||
|
||||
- **todo_read**: Read the entire TODO list from todo.g3.md file in workspace directory
|
||||
- Format: {\"tool\": \"todo_read\", \"args\": {}}
|
||||
- Example: {\"tool\": \"todo_read\", \"args\": {}}
|
||||
|
||||
- **todo_write**: Write or overwrite the entire todo.g3.md file (WARNING: overwrites completely, always read first)
|
||||
- Format: {\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Task 1\\n- [ ] Task 2\"}}
|
||||
- Example: {\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Implement feature\\n - [ ] Write tests\\n - [ ] Run tests\"}}
|
||||
|
||||
- **code_search**: Syntax-aware code search using tree-sitter. Supports Rust, Python, JavaScript, TypeScript.
|
||||
- Format: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"label\", \"query\": \"tree-sitter query\", \"language\": \"rust|python|javascript|typescript\", \"paths\": [\"src/\"], \"context_lines\": 0}]}}
|
||||
- Find functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"find_functions\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\", \"paths\": [\"src/\"]}]}}
|
||||
- Find async functions: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"find_async\", \"query\": \"(function_item (function_modifiers) name: (identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- Find structs: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"structs\", \"query\": \"(struct_item name: (type_identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- Multiple searches: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"funcs\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\"}, {\"name\": \"structs\", \"query\": \"(struct_item name: (type_identifier) @name)\", \"language\": \"rust\"}]}}
|
||||
- With context lines: {\"tool\": \"code_search\", \"args\": {\"searches\": [{\"name\": \"funcs\", \"query\": \"(function_item name: (identifier) @name)\", \"language\": \"rust\", \"context_lines\": 3}]}}
|
||||
- \"context\": 3 (show surrounding lines),
|
||||
- \"json_style\": \"stream\" (for large results)
|
||||
|
||||
# Instructions
|
||||
|
||||
1. Analyze the request and break down into smaller tasks if appropriate
|
||||
2. Execute ONE tool at a time. An exception exists for when you're writing files. See below.
|
||||
3. STOP when the original request was satisfied
|
||||
4. Call the final_output tool when done
|
||||
|
||||
For reading files, prioritize use of code_search tool use with multiple search requests per call instead of read_file, if it makes sense.
|
||||
|
||||
Exception to using ONE tool at a time:
|
||||
If all you’re doing is WRITING files, and you don’t need to do anything else between each step.
|
||||
You can issue MULTIPLE write_file tool calls in a request, however you may ONLY make a SINGLE write_file call for any file in that request.
|
||||
For example you may call:
|
||||
[START OF REQUEST]
|
||||
write_file(\"helper.rs\", \"...\")
|
||||
write_file(\"file2.txt\", \"...\")
|
||||
[DONE]
|
||||
|
||||
But NOT:
|
||||
[START OF REQUEST]
|
||||
write_file(\"helper.rs\", \"...\")
|
||||
write_file(\"file2.txt\", \"...\")
|
||||
write_file(\"helper.rs\", \"...\")
|
||||
[DONE]
|
||||
|
||||
# Task Management with TODO Tools
|
||||
|
||||
**REQUIRED for multi-step tasks.** Use TODO tools when your task involves ANY of:
|
||||
- Multiple files to create/modify (2+)
|
||||
- Multiple distinct steps (3+)
|
||||
- Dependencies between steps
|
||||
- Testing or verification needed
|
||||
- Uncertainty about approach
|
||||
|
||||
## Workflow
|
||||
|
||||
Every multi-step task follows this pattern:
|
||||
1. **Start**: Call todo_read, then todo_write to create your plan
|
||||
2. **During**: Execute steps, then todo_read and todo_write to mark progress
|
||||
3. **End**: Call todo_read to verify all items complete
|
||||
|
||||
Note: todo_write replaces the entire list, so always read first to preserve content.
|
||||
|
||||
## Examples
|
||||
|
||||
**Example 1: Feature Implementation**
|
||||
User asks: \"Add user authentication with tests\"
|
||||
|
||||
First action:
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
|
||||
Then create plan:
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Add user authentication\\n - [ ] Create User struct\\n - [ ] Add login endpoint\\n - [ ] Add password hashing\\n - [ ] Write unit tests\\n - [ ] Write integration tests\"}}
|
||||
|
||||
After completing User struct:
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Add user authentication\\n - [x] Create User struct\\n - [ ] Add login endpoint\\n - [ ] Add password hashing\\n - [ ] Write unit tests\\n - [ ] Write integration tests\"}}
|
||||
|
||||
**Example 2: Bug Fix**
|
||||
User asks: \"Fix the memory leak in cache module\"
|
||||
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Fix memory leak\\n - [ ] Review cache.rs\\n - [ ] Check for unclosed resources\\n - [ ] Add drop implementation\\n - [ ] Write test to verify fix\"}}
|
||||
|
||||
**Example 3: Refactoring**
|
||||
User asks: \"Refactor database layer to use async/await\"
|
||||
|
||||
{\"tool\": \"todo_read\", \"args\": {}}
|
||||
{\"tool\": \"todo_write\", \"args\": {\"content\": \"- [ ] Refactor to async\\n - [ ] Update function signatures\\n - [ ] Replace blocking calls\\n - [ ] Update all callers\\n - [ ] Update tests\"}}
|
||||
|
||||
## Format
|
||||
|
||||
Use markdown checkboxes:
|
||||
- \"- [ ]\" for incomplete tasks
|
||||
- \"- [x]\" for completed tasks
|
||||
- Indent with 2 spaces for subtasks
|
||||
|
||||
Keep items short, specific, and action-oriented.
|
||||
|
||||
## Benefits
|
||||
|
||||
✓ Prevents missed steps
|
||||
✓ Makes progress visible
|
||||
✓ Helps recover from interruptions
|
||||
✓ Creates better summaries
|
||||
|
||||
## When NOT to Use
|
||||
|
||||
Skip TODO tools for simple single-step tasks:
|
||||
- \"List files\" → just use shell
|
||||
- \"Read config.json\" → just use read_file
|
||||
- \"Search for functions\" → just use code_search
|
||||
|
||||
If you can complete it with 1-2 tool calls, skip TODO.
|
||||
|
||||
|
||||
# Response Guidelines
|
||||
|
||||
- Use Markdown formatting for all responses except tool calls.
|
||||
- Whenever taking actions, use the pronoun 'I'
|
||||
";
|
||||
|
||||
pub const SYSTEM_PROMPT_FOR_NON_NATIVE_TOOL_USE: &'static str =
|
||||
concatcp!(CODING_STYLE, SYSTEM_NON_NATIVE_TOOL_USE);
|
||||
Reference in New Issue
Block a user