feat(acp): surface session/update tool_call notifications at Info level (#1141)

session/update notifications carrying a ToolCall (or inline tool_call /
tool_call_update kind) were only dumped at Debug level inside the params
blob. Operators could see `security.tool_granted` (permission granted) but
had no way to tell whether the tool actually executed successfully — both
"permission granted then failed silently" and "permission granted and
succeeded" looked identical in journalctl.

Add a structured Info log emitting toolCallId, name/title, status, and a
content preview (truncated at 400 chars) whenever the notification
contains tool-call state. This is what made it possible to diagnose the
recent .goclaw/-path-deny regression — `status=failed` immediately after
`security.tool_granted` revealed the gap that the granted-only log hid.

No behavior change beyond logging volume; preview is bounded.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
codebit0andClaude Opus 4.7 authored and GitHub committed 2026-06-21 06:33:15 +07:00
1 parent 6c8ad12858
commit 05d790662a
1 file changed
+32
+32
View File
@@ -216,6 +216,38 @@ func (pp *ProcessPool) spawn(ctx context.Context, poolKey string) (*ACPProcess,
slog.Warn("acp: session/update parse failed", "error", err)
return
}
// Surface tool-call notifications at Info level so operators can
// distinguish "permission granted" (security.tool_granted) from
// "tool actually executed / produced output". Without this the
// status transitions are buried in Debug-level params dumps.
if update.ToolCall != nil {
preview := ""
for _, c := range update.ToolCall.Content {
if c.Type == "text" && c.Text != "" {
preview += c.Text
if len(preview) > 400 {
break
}
}
}
if len(preview) > 400 {
preview = preview[:400] + "...(truncated)"
}
slog.Info("acp: tool_call_update",
"sid", update.SessionID,
"id", update.ToolCall.ID,
"name", update.ToolCall.Name,
"status", update.ToolCall.Status,
"content_preview", preview)
}
if update.Update.SessionUpdate == "tool_call" || update.Update.SessionUpdate == "tool_call_update" {
slog.Info("acp: tool_call_update (inline)",
"sid", update.SessionID,
"id", update.Update.ToolCallID,
"title", update.Update.Title,
"kind", update.Update.Kind,
"status", update.Update.Status)
}
proc.dispatchUpdate(update)
}
}