Files
Illia Polosukhin eb75a62a97 feat(debug-panel): expand Activity tab coverage with CodeAct + warnings (#2850)
* feat(debug-panel): expand Activity tab coverage with CodeAct + warnings

The Activity tab was missing most event types: CodeAct runs showed only
lossy chat summaries, WARN/ERROR logs only landed in server stdout, and
tool entries hid their parameters on success.

- Emit AppEvent::CodeExecuted (verbose-only) with raw code, stdout, and
  return value from the engine orchestrator so observers see what the
  model actually wrote.
- Bridge WARN/ERROR tracing into AppEvent::Warning via
  spawn_warning_bridge, scoped by owner_id in multi-tenant mode to
  prevent cross-tenant log bleed.
- Backfill params_summary on ActionExecuted/ActionFailed events from
  structured + scripting executors so the Activity tab shows tool args
  immediately (not just on failure) without waiting for tool_completed.
- Wire debug-panel.js to render code_executed, warning, gate_required,
  gate_resolved, approval_needed, skill_activated, plan_update,
  thread_state_changed, child/mission_thread_spawned, onboarding_state,
  image_generated, suggestions, and the full sandbox-job event family.
- Extract shared on(name, handler) wrapper to dedupe ~25 copies of the
  JSON-parse + reconnect-counter housekeeping and keep lastEventTime
  bookkeeping consistent across listeners.
- Add i18n strings (en/ko/zh-CN) and CSS icon colors for the new
  activity types.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(debug-panel): address review feedback on activity-trace PR

- summarize_params generic fallback: skip sensitive-looking parameter
  keys (token/secret/password/api_key/auth/credential/bearer) so MCP
  and unknown-tool calls can't surface secret values into
  ActionExecuted events or debug-panel SSE. Adds two regression tests.
- Cap CodeExecuted code/stdout at 8_000 chars (tail-last) before
  emission so a step that prints a large blob can't bloat persisted
  thread events. Matches the existing scripting OUTPUT_TRUNCATE_LEN.
- await_thread_outcome: skip broadcasting verbose-only AppEvents when
  no debug subscriber is connected — mirrors the send_status gate
  and keeps CodeExecuted off the shared SSE broadcast buffer for
  normal browser tabs.
- spawn_warning_bridge: same short-circuit on has_verbose_receivers.
- debug-panel.js: introduce GATE_RESOLUTION_STATUS so `expired`
  (a failure path from router.rs) no longer renders as a green
  success badge; shared STATUS_TO_ACTIVITY map is kept for jobs/
  plans/onboarding where `success` is the right default.
- debug-panel.js: migrate the remaining legacy listeners to the
  shared on() wrapper so lastEventTime / totalEventsReceived
  bookkeeping stays consistent across every activity listener.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(debug-panel): address PR #2850 follow-up review on leak / tenant scoping

- Warning bridge (`src/channels/web/mod.rs`): disable entirely in
  multi-tenant mode. The `tracing` layer captures log context at the
  global subscriber scope, not at request scope, so scoping the bridge
  to the gateway `owner_id` misroutes tenant A's WARN/ERROR log lines
  to the admin account (and prevents tenant A from ever seeing them).
  Per-request provenance would need threading through every `warn!` /
  `error!` call site — out of scope for this PR — so the safe move is
  to keep the bridge off until that lands.
- `summarize_params` (`crates/ironclaw_engine/src/types/event.rs`):
  strip URL query strings / fragments / userinfo for `http` and
  `web_fetch`, and redact auth-bearing flag values (`-H`, `--header`,
  `-u`, `--user`, `--token`, `--api-key`, `--password`, `--auth`,
  `--bearer`) plus embedded URL query strings inside `shell` commands.
  Signed URLs, inline `Authorization: Bearer …` headers, and query-
  string API keys no longer reach `ToolCompleted.parameters` on the
  debug SSE stream. Six regression tests added.
- `CodeExecuted` redaction (`src/bridge/router.rs`): apply the leak
  detector to `code` / `stdout` / `return_value` at the bridge
  boundary before SSE broadcast. The engine crate has no dependency on
  `ironclaw_safety`, so scrubbing lives here. Adds
  `SafetyLayer::leak_detector()` and `EffectBridgeAdapter::safety()`
  accessors. Handles both `Redact` and `Block`-action matches
  (scan_and_clean's `redacted_content` is `None` for Block-only
  matches, which would have passed bearer tokens / API keys through
  unchanged). Regression test covers string and nested-JSON cases.

* fix(debug-panel): address PR #2850 Copilot follow-up review

- `src/channels/web/log_layer.rs`: annotate `spawn_warning_bridge`'s
  `sse.broadcast_for_user` / `sse.broadcast` sites with
  `// projection-exempt: log source, WARN/ERROR tracing bridge →
  AppEvent::Warning` so the PROJECTION safety check (#9 in
  `scripts/pre-commit-safety.sh`) recognises the tracing
  `LogBroadcaster` as a typed source log. Added a comment block
  explaining why the source-log category isn't yet in
  `.claude/rules/gateway-events.md`'s table.
- `crates/ironclaw_engine/src/executor/orchestrator.rs`: replace
  `tail_chars` (O(n) via `chars().count()`) with a local
  `tail_utf8_bytes` helper for the `CodeExecuted` emission path. Byte
  based so it stays O(1) + ≤3-byte UTF-8 boundary walk for arbitrarily
  large `code`/`stdout`. Also add `bounded_return_value` so a CodeAct
  snippet returning a 50 MB JSON value doesn't bloat persisted thread
  events — strings are tail-truncated; structured values that
  serialize past 8 KiB are dropped to `None` (rather than truncated
  into unparseable JSON). Seven regression tests cover ASCII / emoji
  boundary / null / small struct / oversized struct / large-string
  paths.

`tail_chars` is kept unchanged for its existing callers, whose inputs
are already bounded (`OUTPUT_TRUNCATE_LEN`, 500-char error slices).

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 13:48:02 +09:00
..