refactor: P0-P3 fixes, C1 cleanup, architecture diagrams, go.work upgrade

- P0-1: ProviderError type + ReportStatus for precise 401/403 detection
- P0-2: Remove -config flag from deploy/homeagent.service
- P2-1: 5s debounce on context.go Save()
- P2-2→C1: Delete output_set_channel entirely
- P2-3: Extract mediaDataURL/mediaChat helpers
- P2-4: Dedup defaultSources var
- P3: Delete dead packages (embed/tokenizer/container/snapshot)
- P3: Delete dead functions (messagesToMap, RunStageAll)
- CL: Update .gitignore, docs, Makefile, gojieba removal
- Config: Delete config/config.yaml, update docs
- Arch: Remove EmitOutputTo from emitResponse
- CL-1: go.work 1.19→1.21
- Docs: Add Mermaid architecture diagrams to README
- Docs: Add kernel-rebuild requires plugin-rebuild note to PLUGIN_DEV.md
This commit is contained in:
root
2026-07-12 11:42:56 +08:00
parent dad62516a8
commit 1f1233b823
89 changed files with 1764 additions and 6578 deletions

View File

@ -12,6 +12,34 @@ import (
sdk "gitcode.com/JianFeeeee/HomeAgent/internal/sdk"
)
// shellUnquote 拆解命令字符串,处理单引号/双引号包裹的参数
func shellUnquote(s string) []string {
var args []string
var cur strings.Builder
inSingle := false
inDouble := false
for i := 0; i < len(s); i++ {
c := s[i]
switch {
case c == '\'' && !inDouble:
inSingle = !inSingle
case c == '"' && !inSingle:
inDouble = !inDouble
case (c == ' ' || c == '\t') && !inSingle && !inDouble:
if cur.Len() > 0 {
args = append(args, cur.String())
cur.Reset()
}
default:
cur.WriteByte(c)
}
}
if cur.Len() > 0 {
args = append(args, cur.String())
}
return args
}
func init() {
plugin.RegisterFactory("cmd", func(name string, config map[string]interface{}) (sdk.Plugin, error) {
return New(name), nil
@ -79,7 +107,11 @@ func (p *Plugin) Start(s *sdk.PluginSDK) error {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
cmd := exec.CommandContext(ctx, "sh", "-c", command)
parts := shellUnquote(command)
if len(parts) == 0 {
return map[string]interface{}{"error": "command is required"}, nil
}
cmd := exec.CommandContext(ctx, parts[0], parts[1:]...)
if workdir != "" {
cmd.Dir = workdir
}

View File

@ -81,8 +81,9 @@ func TestCmdRunWithStderr(t *testing.T) {
}
handler := tc.handlers["cmd_run"]
// ls with a nonexistent path writes to stderr and returns non-zero exit code
result, err := handler(map[string]interface{}{
"command": "echo out && echo err >&2 && exit 1",
"command": "ls /tmp/cmd_test_nonexistent_xxxxx",
})
if err != nil {
t.Fatal(err)
@ -95,14 +96,11 @@ func TestCmdRunWithStderr(t *testing.T) {
if resp["status"] != "ok" {
t.Fatalf("expected status ok, got %v", resp["status"])
}
if resp["stdout"] != "out" {
t.Fatalf("expected stdout 'out', got %v", resp["stdout"])
if stderr, ok := resp["stderr"].(string); !ok || stderr == "" {
t.Fatalf("expected stderr output, got %q", stderr)
}
if resp["stderr"] != "err" {
t.Fatalf("expected stderr 'err', got %v", resp["stderr"])
}
if resp["exit_code"].(float64) != 1 {
t.Fatalf("expected exit code 1, got %v", resp["exit_code"])
if exitCode, ok := resp["exit_code"].(float64); !ok || exitCode == 0 {
t.Fatalf("expected non-zero exit code, got %v", exitCode)
}
}
@ -197,7 +195,7 @@ func TestCmdRunNonZeroExit(t *testing.T) {
handler := tc.handlers["cmd_run"]
result, err := handler(map[string]interface{}{
"command": "exit 42",
"command": "false",
})
if err != nil {
t.Fatal(err)
@ -210,8 +208,8 @@ func TestCmdRunNonZeroExit(t *testing.T) {
if resp["status"] != "ok" {
t.Fatalf("expected status ok, got %v", resp["status"])
}
if resp["exit_code"].(float64) != 42 {
t.Fatalf("expected exit code 42, got %v", resp["exit_code"])
if resp["exit_code"].(float64) != 1 {
t.Fatalf("expected exit code 1, got %v", resp["exit_code"])
}
}