From cb76828e4327ce8d568602012215af40993fe140 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Wed, 26 Aug 2026 10:24:47 +0800 Subject: [PATCH] =?UTF-8?q?fix(cmd/files/webui):=20shell=20=E8=AF=AD?= =?UTF-8?q?=E4=B9=89=E4=BF=AE=E5=A4=8D=20+=20=E6=A0=B9=E6=B2=99=E7=AE=B1?= =?UTF-8?q?=E8=AF=AF=E5=88=A4=20+=20=E4=B8=8A=E4=BC=A0=E6=B3=A8=E5=85=A5?= =?UTF-8?q?=E8=B5=B0=20interrupt=20+=20UI=20=E5=8C=BA=E5=88=86=E9=99=84?= =?UTF-8?q?=E4=BB=B6=E6=9D=A5=E6=BA=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. cmd_run 改经 /bin/bash -c 执行完整 shell 语法 旧实现 shellUnquote 拆词后直接 exec:'pwd; ls /' 变成执行名为 'pwd;' 的程序(exit -1)、heredoc 被截断、管道/命令替换全部失效—— agent 多次反馈命令解析奇怪即此。危险命令拦截(kill homed 等)保留。 2. files 沙箱根目录判断修复 pathWithinSandbox 在 base='/' 时 prefix 变 '//',所有绝对路径误判 逃逸(生产实锤:files.dir=/ 下 files_read/write/ls 全部报 outside sandbox)。根沙箱直接放行。 3. webui 文件上传注入改走 interrupt(system 角色) 文件元信息不再混入用户消息气泡;用户附言作为正常消息先行注入, 文件说明紧随其后以 no_memory interrupt 补充——对齐 terminal_watch/ timer 工具提醒模式,聊天流保持干净。 4. 前端附件卡片按 role 区分来源 user=右侧+『你发送的』标签+accent 底色;assistant=左侧+『小宅发送的』。 📌 emoji 按钮换为 SVG 图标,前端 emoji 清零。 --- cmd/homed/main.go | 10 +- internal/plugins/cmd/plugin.go | 24 +- internal/plugins/files/plugin.go | 6 + internal/plugins/webui/dashboard.html | 529 ++++++++++++++++---------- internal/plugins/webui/handler.go | 50 ++- 5 files changed, 394 insertions(+), 225 deletions(-) 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);