mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-27 04:56:20 +02:00
MUL-3136 fix(openclaw): parse config path from last non-empty line of CLI output
Fix OpenClaw config discovery when `openclaw config file` prints Doctor warning UI before the actual config path. The daemon now uses the last non-empty stdout line as the path while preserving the existing tilde expansion, absolute-path validation, stat checks, and fail-closed behavior. Tests: go test ./internal/daemon/execenv
This commit is contained in:
@@ -351,7 +351,17 @@ func openclawActiveConfigPath(bin string, timeout time.Duration) (string, bool,
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
path := strings.TrimSpace(out)
|
||||
// OpenClaw may print terminal UI borders (e.g., Doctor warnings) before
|
||||
// the actual path. The path is always the last non-empty line.
|
||||
lines := strings.Split(strings.TrimSpace(out), "\n")
|
||||
path := ""
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
trimmed := strings.TrimSpace(lines[i])
|
||||
if trimmed != "" {
|
||||
path = trimmed
|
||||
break
|
||||
}
|
||||
}
|
||||
if path == "" {
|
||||
return "", false, fmt.Errorf("`openclaw config file` returned empty output")
|
||||
}
|
||||
|
||||
@@ -349,6 +349,59 @@ func TestPrepareOpenclawConfigExpandsTilde(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestPrepareOpenclawConfigParsesPathFromUITerminalOutput — regression test
|
||||
// for the case where `openclaw config file` prints terminal UI borders
|
||||
// (e.g., Doctor warnings) before the actual path. The path is always the
|
||||
// last non-empty line.
|
||||
func TestPrepareOpenclawConfigParsesPathFromUITerminalOutput(t *testing.T) {
|
||||
envRoot := t.TempDir()
|
||||
workDir := filepath.Join(envRoot, "workdir")
|
||||
if err := os.MkdirAll(workDir, 0o755); err != nil {
|
||||
t.Fatalf("mkdir workdir: %v", err)
|
||||
}
|
||||
|
||||
userConfigDir := t.TempDir()
|
||||
userConfigPath := filepath.Join(userConfigDir, "openclaw.json")
|
||||
if err := os.WriteFile(userConfigPath, []byte(`{}`), 0o600); err != nil {
|
||||
t.Fatalf("write user cfg: %v", err)
|
||||
}
|
||||
|
||||
// Simulate OpenClaw's output with UI borders (Doctor warnings)
|
||||
stdoutWithUI := `│
|
||||
◇ Doctor warnings ──────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ - Left plugin install index in place because shared SQLite state has │
|
||||
│ conflicting plugin install metadata for: qqbot │
|
||||
│ │
|
||||
├────────────────────────────────────────────────────────────────────────╯
|
||||
[state-migrations] Legacy state migration warnings:
|
||||
- Left plugin install index in place because shared SQLite state has conflicting plugin install metadata for: qqbot
|
||||
│
|
||||
◇ Doctor warnings ──────────────────────────────────────────────────────╮
|
||||
│ │
|
||||
│ - Left plugin install index in place because shared SQLite state has │
|
||||
│ conflicting plugin install metadata for: qqbot │
|
||||
│ │
|
||||
├────────────────────────────────────────────────────────────────────────╯
|
||||
` + userConfigPath + "\n"
|
||||
|
||||
stub := installOpenclawStub(t, map[string]openclawResponse{
|
||||
"config file": {stdout: stdoutWithUI},
|
||||
"config get agents.list --json": {stdout: "null"},
|
||||
})
|
||||
|
||||
result, err := prepareOpenclawConfig(envRoot, workDir, OpenclawConfigPrep{OpenclawBin: stub.bin})
|
||||
if err != nil {
|
||||
t.Fatalf("prepareOpenclawConfig: %v", err)
|
||||
}
|
||||
|
||||
got := mustReadJSON(t, result.ConfigPath)
|
||||
include := got["$include"].([]any)
|
||||
if include[0] != userConfigPath {
|
||||
t.Errorf("$include[0] = %v, want %q (path must be extracted from last non-empty line)", include[0], userConfigPath)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPrepareOpenclawConfigWrapperLoadableUnderIncludeConfinement is the
|
||||
// regression test for the Elon include-confinement blocker. OpenClaw
|
||||
// resolves `$include` only inside the wrapper file's own directory unless
|
||||
|
||||
Reference in New Issue
Block a user