diff --git a/cmd/homed/main.go b/cmd/homed/main.go index ee97f70..dfb4067 100644 --- a/cmd/homed/main.go +++ b/cmd/homed/main.go @@ -396,9 +396,17 @@ func main() { - transcribe_audio — 转写用户上传的音频 - ocr_image — 识别图片中的文字 +命令与文件操作策略: +- cmd_run 经完整 shell(bash)执行,支持管道、分号、&&、命令替换、heredoc、重定向。 +- 多步交互式程序(vim/top/ssh 会话、需要持续输入的进程)用 terminal_create 创建终端, + terminal_write 发送输入、terminal_read 读输出——不要用 cmd_run 硬等交互程序退出。 +- 写文件优先 files_write(原子+留档),生成多行内容时可用 heredoc 或 files_write, + 不要用 echo 拼接长文本。 +- 读用户发来的文件用 files_read;向 webui 回传图片/文件用 output_send__webui(type=image/file)。 + 当用户上传图片或音频时,系统会自动附着媒体内容。如果模型不支持直接处理多媒体,请使用上述工具。 -回复你的真实想法,用自然语言与用户交流。` +回复你的真实想法,用自然语言与用户交流。不要在回复中使用 emoji 表情。` sysPrompt := cfgReg.GetString("core.agent.system_prompt", defaultPrompt) if sysPrompt == "" { sysPrompt = defaultPrompt diff --git a/internal/plugins/cmd/plugin.go b/internal/plugins/cmd/plugin.go index 0be0445..42f2d0b 100644 --- a/internal/plugins/cmd/plugin.go +++ b/internal/plugins/cmd/plugin.go @@ -159,18 +159,20 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() - // Windows 上必须经 cmd.exe /c 执行(chcp 65001 预置为 UTF-8 输出), - // 直接 exec 会把整条命令当成一个程序路径导致所有命令失败。 + // Linux/Unix 经 /bin/sh -c 执行完整 shell 语法:管道、分号、&&、 + // 命令替换、heredoc、重定向全部可用。旧实现 shellUnquote 拆词后 + // 直接 exec,导致 pwd; ls / 变成执行名为 "pwd;" 的程序(exit -1)、 + // heredoc 被截断——agent 多次反馈"命令解析奇怪"即此。 var cmd *exec.Cmd if isWindows { execCmd := "chcp 65001>nul & " + command cmd = exec.CommandContext(ctx, "cmd.exe", "/d", "/c", execCmd) } else { - parts := shellUnquote(command) - if len(parts) == 0 { - return map[string]interface{}{"error": "command is required"}, nil + shell := "/bin/sh" + if _, err := os.Stat("/bin/bash"); err == nil { + shell = "/bin/bash" // bash 支持更完整的语法(数组、[[ ]] 等) } - cmd = exec.CommandContext(ctx, parts[0], parts[1:]...) + cmd = exec.CommandContext(ctx, shell, "-c", command) } if workdir != "" { cmd.Dir = workdir @@ -210,11 +212,11 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error { p.recordCmd(rec) return map[string]interface{}{ - "status": "ok", - "stdout": rec.Stdout, - "stderr": rec.Stderr, - "exit_code": exitCode, - "command": command, + "status": "ok", + "stdout": rec.Stdout, + "stderr": rec.Stderr, + "exit_code": exitCode, + "command": command, }, nil }) diff --git a/internal/plugins/files/plugin.go b/internal/plugins/files/plugin.go index abed5f6..c014873 100644 --- a/internal/plugins/files/plugin.go +++ b/internal/plugins/files/plugin.go @@ -201,6 +201,12 @@ func pathWithinSandbox(abs, base string) bool { if equalFoldPath(abs, base) { return true } + // 根沙箱(Linux "/")表示整机可访问:Clean("") 会返回 ".", + // 而 prefix 变成 "//" 导致所有绝对路径误判逃逸(生产实锤:files.dir=/ 时 + // 全部 files_read/write 报 path outside sandbox)。根目录直接放行。 + if base == string(filepath.Separator) { + return true + } // 卷根沙箱(C:\、D:\ 等)表示整机可访问 if isWindowsBuild && len(base) == 3 && base[1] == ':' && base[2] == '\\' { return true diff --git a/internal/plugins/webui/dashboard.html b/internal/plugins/webui/dashboard.html index 5ad8ed5..c2db98d 100644 --- a/internal/plugins/webui/dashboard.html +++ b/internal/plugins/webui/dashboard.html @@ -27,7 +27,7 @@ }, 8000);