mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-26 12:35:35 +02:00
fix(agent): handle braces in stderr log lines before openclaw JSON result
processOutput() used strings.Index(raw, "{") to find the JSON start,
but error lines like `raw_params={"command":"..."}` contain braces that
get matched first, causing JSON parsing to fail and the entire raw
stderr (including internal metadata) to be returned as the agent comment.
Now tries each '{' position until one successfully unmarshals as a valid
openclawResult, skipping braces embedded in log/error lines.
This commit is contained in:
@@ -129,16 +129,31 @@ type openclawEventResult struct {
|
||||
|
||||
// processOutput reads the JSON output from openclaw --json stderr and returns
|
||||
// the parsed result. OpenClaw writes its JSON result to stderr, which may also
|
||||
// contain non-JSON log lines. We extract the JSON object by finding the first '{'.
|
||||
// contain non-JSON log lines. We find the result JSON by trying each '{' until
|
||||
// one successfully unmarshals as an openclawResult with payloads.
|
||||
func (b *openclawBackend) processOutput(r io.Reader, ch chan<- Message) openclawEventResult {
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return openclawEventResult{status: "failed", errMsg: fmt.Sprintf("read stderr: %v", err)}
|
||||
}
|
||||
|
||||
// Log non-JSON lines and find the JSON object
|
||||
raw := string(data)
|
||||
jsonStart := strings.Index(raw, "{")
|
||||
|
||||
// Try each '{' position until we find valid openclawResult JSON.
|
||||
// Earlier '{' chars may appear in log/error lines (e.g. raw_params={...}).
|
||||
var result openclawResult
|
||||
jsonStart := -1
|
||||
for i := 0; i < len(raw); i++ {
|
||||
if raw[i] != '{' {
|
||||
continue
|
||||
}
|
||||
if err := json.Unmarshal([]byte(raw[i:]), &result); err == nil && result.Payloads != nil {
|
||||
jsonStart = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Log non-JSON lines before the result
|
||||
if jsonStart > 0 {
|
||||
for _, line := range strings.Split(raw[:jsonStart], "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
@@ -148,13 +163,7 @@ func (b *openclawBackend) processOutput(r io.Reader, ch chan<- Message) openclaw
|
||||
}
|
||||
}
|
||||
|
||||
var result openclawResult
|
||||
jsonData := raw
|
||||
if jsonStart >= 0 {
|
||||
jsonData = raw[jsonStart:]
|
||||
}
|
||||
if err := json.Unmarshal([]byte(jsonData), &result); err != nil {
|
||||
// If we can't parse JSON, return raw output as-is
|
||||
if jsonStart < 0 {
|
||||
trimmed := strings.TrimSpace(raw)
|
||||
if trimmed != "" {
|
||||
b.cfg.Logger.Debug("[openclaw:stderr] " + trimmed)
|
||||
|
||||
@@ -203,6 +203,32 @@ func TestOpenclawProcessOutputReadError(t *testing.T) {
|
||||
close(ch)
|
||||
}
|
||||
|
||||
func TestOpenclawProcessOutputWithBracesInLogLines(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
b := &openclawBackend{cfg: Config{Logger: slog.Default()}}
|
||||
ch := make(chan Message, 256)
|
||||
|
||||
result := openclawResult{
|
||||
Payloads: []openclawPayload{{Text: "Final answer"}},
|
||||
Meta: openclawMeta{DurationMs: 500},
|
||||
}
|
||||
data, _ := json.Marshal(result)
|
||||
// Simulate error line containing braces before the real JSON (the exact bug scenario)
|
||||
input := `[tools] exec failed: complex interpreter invocation detected. raw_params={"command":"echo hello"}` + "\n" + string(data)
|
||||
|
||||
res := b.processOutput(strings.NewReader(input), ch)
|
||||
|
||||
if res.status != "completed" {
|
||||
t.Errorf("status: got %q, want %q", res.status, "completed")
|
||||
}
|
||||
if res.output != "Final answer" {
|
||||
t.Errorf("output: got %q, want %q", res.output, "Final answer")
|
||||
}
|
||||
|
||||
close(ch)
|
||||
}
|
||||
|
||||
// ── openclawInt64 tests ──
|
||||
|
||||
func TestOpenclawInt64Float(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user