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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 0 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,39 +185,6 @@ Disable Meta-Agent via startup flag:
./codeactor tui --disable-agents=meta
```

## DevOps-Agent

The **DevOps-Agent** is the operations and infrastructure specialist — it handles all non-coding operational tasks by executing shell commands, inspecting the file system, and analyzing command output. When the Conductor encounters a task involving system administration, log inspection, process management, or ad-hoc shell commands, it delegates to the DevOps-Agent via `delegate_devops`.

### Capabilities

- **Shell Command Execution** (`run_bash`) — Run any bash command with foreground/background support, danger detection, and workspace boundary checks
- **File System Inspection** — `read_file`, `list_dir`, `print_dir_tree`, `search_by_regex` for browsing logs, configs, and directories
- **Self-Correction** — `thinking` tool for analyzing command failures and adjusting approach before retrying
- **Isolated Analysis** — `micro_agent` for deep reasoning on command output or generating structured reports

### Example use cases

- Check disk usage, memory, and system resources
- Find all log files modified in the last 24 hours
- Restart services or check process status
- Inspect configuration files
- Run system diagnostics and generate reports
- Execute ad-hoc shell pipelines for data processing

### Configuration

```toml
[agent]
devops_max_steps = 15 # Max LLM steps for DevOps-Agent (default: 15)
```

Disable DevOps-Agent via startup flag:

```bash
./codeactor tui --disable-agents=devops
```

## Codebase Analysis Engine

The `codeactor-codebase` is a standalone **Rust** service that provides deep code analysis capabilities. It runs as a background HTTP server managed automatically by the Go binary.
Expand Down
33 changes: 0 additions & 33 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,39 +184,6 @@ meta_retry_count = 5 # JSON 解析失败重试次数(默认 5)
./codeactor tui --disable-agents=meta
```

## DevOps-Agent(运维代理)

**DevOps-Agent** 是运维和基础设施专家——通过执行 Shell 命令、检查文件系统和分析命令输出来处理所有非编码的运维任务。当 Conductor 遇到系统管理、日志检查、进程管理或 ad-hoc shell 命令类任务时,会通过 `delegate_devops` 委派给 DevOps-Agent。

### 核心能力

- **Shell 命令执行** (`run_bash`) — 运行任意 bash 命令,支持前台/后台运行,含危险检测和工作空间边界检查
- **文件系统检查** — `read_file`、`list_dir`、`print_dir_tree`、`search_by_regex` 用于浏览日志、配置和目录
- **自我修正** — 使用 `thinking` 工具分析命令失败原因,调整策略后重试
- **独立分析** — 使用 `micro_agent` 对命令输出进行深度推理或生成结构化报告

### 示例用例

- 检查磁盘使用率、内存和系统资源
- 查找最近 24 小时内修改的所有日志文件
- 重启服务或检查进程状态
- 检查配置文件
- 运行系统诊断并生成报告
- 执行 ad-hoc shell 管道进行数据处理

### 配置

```toml
[agent]
devops_max_steps = 15 # DevOps-Agent 最大 LLM 步数(默认 15)
```

通过启动参数禁用 DevOps-Agent:

```bash
./codeactor tui --disable-agents=devops
```

## Codebase 分析引擎

`codeactor-codebase` 是一个独立的 **Rust** 服务,提供深度代码分析能力。它作为后台 HTTP 服务器运行,由 Go 二进制自动管理。
Expand Down
9 changes: 9 additions & 0 deletions codebase/src/http/handlers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,15 @@ pub(crate) fn setup_watcher(
match res {
Ok(event) => {
if event.kind.is_modify() || event.kind.is_create() || event.kind.is_remove() {
// Filter out events from .git directory
let should_process = event.paths.iter().any(|path| {
!path.components().any(|comp| comp.as_os_str() == ".git")
});

if !should_process {
return;
}

tracing::info!("File change detected: {:?}, queueing re-analysis", event.paths);
let _ = tx.send(());
}
Expand Down
23 changes: 17 additions & 6 deletions internal/agents/conductor.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,17 @@ type ConductorAgent struct {
compactEngine *compact.Engine // 上下文压缩引擎
compactConfig *compact.Config // 压缩配置
summaryEngine llm.Engine // 独立的摘要 LLM 引擎(nil 则复用主引擎)
cachedProjectContext *ProjectContextLoadResult // 缓存项目上下文文件(同一会话只加载一次)
}

// loadProjectContext 读取工作区目录下的项目上下文文件(CODEACTOR.md、CLAUDE.md、AGENTS.md),
// 将成功读取的文件内容格式化后组合返回。文件按顺序尝试,不存在或读取失败时忽略。
// 返回加载的文件列表和组合后的内容。
func (a *ConductorAgent) loadProjectContext() *ProjectContextLoadResult {
// 如果已经加载过,直接返回缓存(同一 Agent 实例会话内只加载一次)
if a.cachedProjectContext != nil {
return a.cachedProjectContext
}
result := &ProjectContextLoadResult{
LoadedFiles: []ProjectContextFile{},
}
Expand All @@ -95,6 +100,8 @@ func (a *ConductorAgent) loadProjectContext() *ProjectContextLoadResult {
}
}
result.Content = sb.String()
// 缓存加载结果,避免后续调用重复读取文件
a.cachedProjectContext = result
return result
}

Expand Down Expand Up @@ -655,13 +662,17 @@ func (a *ConductorAgent) Run(ctx context.Context, input string, mem *memory.Conv
systemPrompt += "\nUse these agents via their delegate tools for tasks matching their specializations.\n"
}

// 加载项目上下文文件(CODEACTOR.md、CLAUDE.md、AGENTS.md)并前置到 System Prompt
if loadResult := a.loadProjectContext(); loadResult != nil && loadResult.Content != "" {
// 发送上下文加载完成消息到消息通道
if a.Publisher != nil {
a.Publisher.Publish("context_loaded", loadResult, a.Name())
// 只在首次对话时加载项目上下文文件(CODEACTOR.md、CLAUDE.md、AGENTS.md),
// 同一会话的后续追问无需重复注入,避免浪费 token。
// memory 中不存储 system 消息,因此 len(mem.GetMessages()) == 0 即可判断是否为首次对话。
if mem == nil || len(mem.GetMessages()) == 0 {
if loadResult := a.loadProjectContext(); loadResult != nil && loadResult.Content != "" {
// 发送上下文加载完成消息到消息通道
if a.Publisher != nil {
a.Publisher.Publish("context_loaded", loadResult, a.Name())
}
systemPrompt = fmt.Sprintf("### Project Workspace Context\n%s\n\n", loadResult.Content) + systemPrompt
}
systemPrompt = fmt.Sprintf("### Project Workspace Context\n%s\n\n", loadResult.Content) + systemPrompt
}

messages = append(messages, llm.Message{
Expand Down
28 changes: 23 additions & 5 deletions internal/tui/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func RenderToolLine(entry *ToolEntry, anim *Anim, width int) string {
params = formatToolParams(entry.Call.Name, entry.Call.Arguments)
}

// Check for early states first
// Check for early states first — no borders
if early, ok := RenderEarlyState(entry.Status, entry.Call.Name, params); ok {
return early
}
Expand All @@ -113,26 +113,35 @@ func RenderToolLine(entry *ToolEntry, anim *Anim, width int) string {

header := RenderHeader(entry.Status, entry.Call.Name, params, errBrief)

// If still running, this should have been handled by RenderPending — fallback
// If still running, no borders — animation is in progress
if entry.Status == ToolStatusRunning {
return header
}

// Tools whose result body is just status JSON — skip body rendering,
// only show the tool name + file path in the header.
if skipBodyTools[entry.Call.Name] && entry.Status == ToolStatusSuccess {
return header
return addToolCallBorders(header, width)
}

// Render body if we have a result
if entry.Result != nil && entry.Result.Content != "" {
body := RenderResultBody(entry.Result.Content, width)
if body != "" {
return header + "\n" + body
// Border only wraps header; body goes below the border
return addToolCallBorders(header, width) + "\n" + body
}
}

return header
return addToolCallBorders(header, width)
}

// addToolCallBorders wraps header with a thin top and bottom border line.
// Body content should be appended OUTSIDE the border.
func addToolCallBorders(header string, width int) string {
topBorder := ToolCallBorderTop.Render(makeBorderLine('─', width))
bottomBorder := ToolCallBorderBottom.Render(makeBorderLine('─', width))
return topBorder + "\n" + header + "\n" + bottomBorder
}

// RenderResultBody renders the tool result content with smart detection.
Expand Down Expand Up @@ -327,6 +336,15 @@ func renderCodeLines(content string, filename string, width int) string {

// ── Content Detection ──

// makeBorderLine generates a compact border line using half-block characters.
func makeBorderLine(char rune, width int) string {
runes := make([]rune, width)
for i := range runes {
runes[i] = char
}
return string(runes)
}

func isJSON(s string) bool {
s = strings.TrimSpace(s)
if len(s) == 0 {
Expand Down
6 changes: 6 additions & 0 deletions internal/tui/styles.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ var (
HelpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("228"))
)

// ── Tool call area borders ──
var (
ToolCallBorderTop = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
ToolCallBorderBottom = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
)

// ── Separator ──
var SeparatorStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("237"))

Expand Down