From b161bb569a2c136403b7018028b54447a64ee575 Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Fri, 26 Jun 2026 20:56:15 +0800 Subject: [PATCH 01/27] fix(reborn): surface specific failure summaries for loop-exit categories (#5289) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Terminal failures that reach the WebUI projection via the normal loop-exit path carry a category from `LoopFailureKind::as_str()` (e.g. `capability_protocol_error`). `reborn_failure_summary_for_category` only mapped the driver-error and scheduler categories plus three loop kinds, so the rest — `capability_protocol_error`, `model_error`, `invalid_model_output`, `checkpoint_*`, `transcript_write_failed`, `driver_bug`, `policy_denied`, `compaction_unavailable`, and `driver_protocol_violation` — degraded to the generic "The run failed before producing a reply." The LLM failure explainer, fed only that generic fallback, then paraphrased it into the vague "driver protocol error" the user saw, masking the real tool failure. Map each loop-exit category to a specific, honest, user-facing summary. This also improves the explainer's input, since the fallback it receives now describes the actual failure stage. Regression coverage: - unit: `reborn_failure_summary_describes_capability_protocol_error` and `reborn_failure_summary_maps_loop_failure_categories_specifically` (no loop-exit category degrades to the generic fallback). - caller-level: `webui_event_stream_projects_capability_protocol_error_summary` drives the category through the projection with no explainer wired and asserts the specific summary reaches the run-status item. --- .../src/failure_summary.rs | 65 +++++++++++++++++++ .../projection/tests/failure_explanation.rs | 18 +++++ 2 files changed, 83 insertions(+) diff --git a/crates/ironclaw_reborn_composition/src/failure_summary.rs b/crates/ironclaw_reborn_composition/src/failure_summary.rs index f554c39579b..7694e99d7ff 100644 --- a/crates/ironclaw_reborn_composition/src/failure_summary.rs +++ b/crates/ironclaw_reborn_composition/src/failure_summary.rs @@ -37,6 +37,34 @@ pub fn reborn_failure_summary_for_category(category: Option<&str>) -> &'static s "iteration_limit" => { "The run stopped after reaching its iteration limit before producing a reply." } + // Categories below come from `LoopFailureKind::as_str()` via the normal + // loop-exit path (`ironclaw_turns::loop_exit`), not the driver-error + // path above. They were previously unmapped and degraded to the generic + // fallback, which masked the real failure (issue #5289: a tool failure + // surfaced to the user as a vague "driver protocol error"). + "capability_protocol_error" => { + "The run stopped because a tool returned a response it could not process." + } + "model_error" => "The run stopped because the model could not complete the request.", + "context_build_failed" => { + "The run failed while preparing the conversation context for the model." + } + "invalid_model_output" => { + "The run stopped because the model returned a response that could not be parsed." + } + "checkpoint_rejected" => "The run failed while saving a progress checkpoint.", + "checkpoint_unavailable" => { + "The run could not resume because its saved progress was unavailable." + } + "transcript_write_failed" => "The run failed while recording its transcript.", + "driver_bug" => "The run stopped because of an internal error in the agent runtime.", + "policy_denied" => "The run stopped because an action it attempted was not permitted.", + "compaction_unavailable" => { + "The run stopped because it could not free up context space to continue." + } + "driver_protocol_violation" => { + "The run produced an invalid result and stopped before replying." + } "unknown_failure" => "The run failed for an unknown reason.", _ => "The run failed before producing a reply.", } @@ -128,6 +156,43 @@ mod tests { } } + // Regression guard for issue #5289: categories emitted by + // `LoopFailureKind::as_str()` through the normal loop-exit path must map to + // specific, honest summaries instead of degrading to the generic fallback + // (which the LLM failure explainer then paraphrased into a vague "driver + // protocol error" that masked the real tool failure). + #[test] + fn reborn_failure_summary_describes_capability_protocol_error() { + assert_eq!( + reborn_failure_summary_for_category(Some("capability_protocol_error")), + "The run stopped because a tool returned a response it could not process." + ); + } + + #[test] + fn reborn_failure_summary_maps_loop_failure_categories_specifically() { + let generic = reborn_failure_summary_for_category(None); + for category in [ + "capability_protocol_error", + "model_error", + "context_build_failed", + "invalid_model_output", + "checkpoint_rejected", + "checkpoint_unavailable", + "transcript_write_failed", + "driver_bug", + "policy_denied", + "compaction_unavailable", + "driver_protocol_violation", + ] { + let summary = reborn_failure_summary_for_category(Some(category)); + assert_ne!( + summary, generic, + "{category} still degrades to the generic failure summary" + ); + } + } + // Regression guard: the old, never-produced category strings must no longer // be specially cased — they now fall through to the generic summary. #[test] diff --git a/crates/ironclaw_reborn_composition/src/projection/tests/failure_explanation.rs b/crates/ironclaw_reborn_composition/src/projection/tests/failure_explanation.rs index 846c2556d39..ef6ce7a9efa 100644 --- a/crates/ironclaw_reborn_composition/src/projection/tests/failure_explanation.rs +++ b/crates/ironclaw_reborn_composition/src/projection/tests/failure_explanation.rs @@ -53,6 +53,24 @@ async fn webui_event_stream_projects_scheduler_executor_panic_summary() { .await; } +// Regression for issue #5289: a terminal capability failure reaches the +// projection as the `capability_protocol_error` category (from +// `LoopFailureKind::as_str()`). Before the fix it was unmapped in +// `reborn_failure_summary_for_category`, so the fallback degraded to the +// generic "The run failed before producing a reply." and the LLM explainer +// paraphrased it into a vague "driver protocol error" that hid the real tool +// failure. With no explainer wired, the projection must now carry the specific +// fallback summary end-to-end. +#[tokio::test] +async fn webui_event_stream_projects_capability_protocol_error_summary() { + assert_failed_run_status_summary( + "webui-events-capability-protocol-thread", + "capability_protocol_error", + "The run stopped because a tool returned a response it could not process.", + ) + .await; +} + async fn assert_failed_run_status_summary( thread_id: &str, failure_category: &str, From 97f50243bba605921817d6ce2ee3ad8a4b436d71 Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Fri, 26 Jun 2026 22:43:21 +0800 Subject: [PATCH 02/27] fix(reborn): surface capability failure detail in per-tool UI preview (#5289) A failed capability (e.g. the `json` builtin returning `invalid_input`) showed only the bare error-kind string in the WebUI Activity panel's per-tool Error tab. The rich `CapabilityFailureDetail::InvalidInput` field issues reached the model transcript but never the display-preview path the UI renders: failures don't call `write_capability_result`, so no preview record was staged and the projection fell back to `failed_capability_display_preview`, which only renders the kind. Stage a failure display-preview record (approach B), so the existing rich projection path surfaces the detail: - ironclaw_loop_support: `LoopCapabilityResultWriter` gains a default no-op `stage_capability_failure_preview`. `runtime_outcome_to_loop` renders a bounded, host-authored summary from the InvalidInput issues (`capability_failure_display_summary`) and stages it on the Failed arm. Only schema-derived fields (path/code/expected) are rendered; `received` (raw tool input) is deliberately omitted. - ironclaw_reborn_composition: implement the writer hook on `LocalDevCapabilityIo` and `ProductLiveCapabilityIo`; add `CapabilityDisplayPreviewStore::record_failure_preview`, which mirrors the success path (title/input pulled from the staged input) and stores the rendered summary as the output, no result ref. - frontend (history-messages.js): `toolCardFromPreview` now prefers the backend `output_summary`/`output_preview` over the bare error kind for the Error tab. Bundle rebuilt (static/dist/app.js). Regression coverage: - unit: `capability_failure_display_summary_renders_invalid_input_issues` (+ asserts `received` never leaks) and `_is_none_for_non_invalid_input`. - caller-level: `capability_display_preview_uses_staged_failure_summary_over_bare_kind` drives the projection chokepoint and asserts the detailed summary reaches the preview view instead of "tool failed: ". Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/capability_port.rs | 146 +++++++++++++++++- .../src/failure_summary.rs | 14 +- .../src/product_live_adapters.rs | 15 ++ .../src/projection/display_preview.rs | 50 ++++++ .../src/projection/tests/display_preview.rs | 64 ++++++++ .../projection/tests/failure_explanation.rs | 6 +- .../src/runtime/local_dev.rs | 15 ++ .../static/dist/app.js | 2 +- .../js/pages/chat/lib/history-messages.js | 8 +- 9 files changed, 306 insertions(+), 14 deletions(-) diff --git a/crates/ironclaw_loop_support/src/capability_port.rs b/crates/ironclaw_loop_support/src/capability_port.rs index 218d1dd0d0c..c0555aec83a 100644 --- a/crates/ironclaw_loop_support/src/capability_port.rs +++ b/crates/ironclaw_loop_support/src/capability_port.rs @@ -260,6 +260,89 @@ pub trait LoopCapabilityResultWriter: Send + Sync { _input_ref: &CapabilityInputRef, ) { } + + /// Stage a display preview for a FAILED capability invocation so the UI can + /// render the specific failure detail (e.g. invalid-input field issues) + /// instead of only the bare error kind. `summary` is a bounded, + /// host-authored string (see `capability_failure_display_summary`). + /// Default no-op: only writers that own a display-preview store implement + /// it. Mirrors `record_running_invocation`'s opt-in shape. + fn stage_capability_failure_preview( + &self, + _run_context: &LoopRunContext, + _invocation_id: InvocationId, + _capability_id: &CapabilityId, + _summary: &str, + ) { + } +} + +/// Maximum number of input issues rendered into a failure display preview. +const CAPABILITY_FAILURE_PREVIEW_MAX_ISSUES: usize = 5; +/// Byte budget for the rendered failure summary. Stays well under the display +/// preview's own `CAPABILITY_DISPLAY_SUMMARY_MAX_BYTES` (2 KiB) cap. +const CAPABILITY_FAILURE_PREVIEW_MAX_BYTES: usize = 1024; + +/// Render a bounded, host-authored display summary for a failed capability. +/// +/// Returns `Some(..)` only for `InvalidInput` failures, where the per-field +/// `issues` carry actionable detail the model-visible observation already +/// surfaces to the LLM but which never reached the UI's per-tool preview. +/// Other failure kinds return `None` so the projection keeps its existing +/// `tool failed: ` fallback. +/// +/// Only schema-derived fields (`path`, `code`, `expected`) are interpolated. +/// The `received` value is deliberately omitted: it echoes raw tool input and +/// must not be rendered into a display surface. +fn capability_failure_display_summary(failure: &CapabilityFailure) -> Option { + let Some(CapabilityFailureDetail::InvalidInput { issues }) = failure.detail.as_ref() else { + return None; + }; + if issues.is_empty() { + return None; + } + let rendered = issues + .iter() + .take(CAPABILITY_FAILURE_PREVIEW_MAX_ISSUES) + .map(render_capability_input_issue) + .collect::>() + .join("; "); + let mut summary = format!("Invalid input: {rendered}"); + if issues.len() > CAPABILITY_FAILURE_PREVIEW_MAX_ISSUES { + let extra = issues.len() - CAPABILITY_FAILURE_PREVIEW_MAX_ISSUES; + summary.push_str(&format!(" (+{extra} more)")); + } + Some(truncate_on_char_boundary( + summary, + CAPABILITY_FAILURE_PREVIEW_MAX_BYTES, + )) +} + +fn render_capability_input_issue(issue: &CapabilityInputIssue) -> String { + let code = match issue.code { + CapabilityInputIssueCode::MissingRequired => "missing required field", + CapabilityInputIssueCode::UnexpectedField => "unexpected field", + CapabilityInputIssueCode::TypeMismatch => "type mismatch", + CapabilityInputIssueCode::InvalidValue => "invalid value", + }; + match issue.expected.as_deref() { + Some(expected) if !expected.is_empty() => { + format!("{} — {code} (expected {expected})", issue.path) + } + _ => format!("{} — {code}", issue.path), + } +} + +fn truncate_on_char_boundary(mut value: String, max_bytes: usize) -> String { + if value.len() <= max_bytes { + return value; + } + let mut end = max_bytes; + while end > 0 && !value.is_char_boundary(end) { + end -= 1; + } + value.truncate(end); + value } pub struct CapabilityResultWrite<'a> { @@ -2435,7 +2518,25 @@ async fn runtime_outcome_to_loop( safe_summary: "capability spawned background work".to_string(), }) } - RuntimeCapabilityOutcome::Failed(failure) => runtime_failure_to_loop(failure)?, + RuntimeCapabilityOutcome::Failed(failure) => { + let capability_id = failure.capability_id.clone(); + let outcome = runtime_failure_to_loop(failure)?; + // Surface actionable failure detail (e.g. invalid-input field + // issues) to the per-tool UI preview by staging a display-preview + // record. Without this the projection falls back to the bare error + // kind. The model-visible observation is unaffected. + if let CapabilityOutcome::Failed(ref cap_failure) = outcome + && let Some(summary) = capability_failure_display_summary(cap_failure) + { + result_writer.stage_capability_failure_preview( + run_context, + conversion.invocation_id, + &capability_id, + &summary, + ); + } + outcome + } RuntimeCapabilityOutcome::Unknown(unknown) => { CapabilityOutcome::Failed(CapabilityFailure { error_kind: capability_failure_kind(unknown.kind)?, @@ -3140,6 +3241,49 @@ mod tests { )); } + #[test] + fn capability_failure_display_summary_renders_invalid_input_issues() { + let failure = CapabilityFailure { + error_kind: CapabilityFailureKind::InvalidInput, + safe_summary: "tool input failed validation".to_string(), + detail: Some(CapabilityFailureDetail::InvalidInput { + issues: vec![ + CapabilityInputIssue { + path: "schedule.kind".to_string(), + code: CapabilityInputIssueCode::MissingRequired, + expected: Some("cron or once".to_string()), + received: Some("super-secret-raw-value".to_string()), + schema_path: None, + }, + CapabilityInputIssue { + path: "schedule.timezone".to_string(), + code: CapabilityInputIssueCode::InvalidValue, + expected: None, + received: None, + schema_path: None, + }, + ], + }), + }; + let summary = + capability_failure_display_summary(&failure).expect("invalid input renders a summary"); + assert!(summary.starts_with("Invalid input:")); + assert!(summary.contains("schedule.kind — missing required field (expected cron or once)")); + assert!(summary.contains("schedule.timezone — invalid value")); + // `received` echoes raw tool input and must never reach a display surface. + assert!(!summary.contains("super-secret-raw-value")); + } + + #[test] + fn capability_failure_display_summary_is_none_for_non_invalid_input() { + let failure = CapabilityFailure { + error_kind: CapabilityFailureKind::Backend, + safe_summary: "capability backend failed".to_string(), + detail: None, + }; + assert!(capability_failure_display_summary(&failure).is_none()); + } + #[test] fn runtime_failure_to_loop_keeps_recoverable_failures_out_of_tool_error_path() { let capability_id = CapabilityId::new("demo.echo").expect("valid capability id"); diff --git a/crates/ironclaw_reborn_composition/src/failure_summary.rs b/crates/ironclaw_reborn_composition/src/failure_summary.rs index 7694e99d7ff..b36fa19bd2a 100644 --- a/crates/ironclaw_reborn_composition/src/failure_summary.rs +++ b/crates/ironclaw_reborn_composition/src/failure_summary.rs @@ -40,8 +40,8 @@ pub fn reborn_failure_summary_for_category(category: Option<&str>) -> &'static s // Categories below come from `LoopFailureKind::as_str()` via the normal // loop-exit path (`ironclaw_turns::loop_exit`), not the driver-error // path above. They were previously unmapped and degraded to the generic - // fallback, which masked the real failure (issue #5289: a tool failure - // surfaced to the user as a vague "driver protocol error"). + // fallback, which masked the real failure (a tool failure surfaced to + // the user as a vague "driver protocol error"). "capability_protocol_error" => { "The run stopped because a tool returned a response it could not process." } @@ -156,11 +156,11 @@ mod tests { } } - // Regression guard for issue #5289: categories emitted by - // `LoopFailureKind::as_str()` through the normal loop-exit path must map to - // specific, honest summaries instead of degrading to the generic fallback - // (which the LLM failure explainer then paraphrased into a vague "driver - // protocol error" that masked the real tool failure). + // Regression guard: categories emitted by `LoopFailureKind::as_str()` + // through the normal loop-exit path must map to specific, honest summaries + // instead of degrading to the generic fallback (which the LLM failure + // explainer then paraphrased into a vague "driver protocol error" that + // masked the real tool failure). #[test] fn reborn_failure_summary_describes_capability_protocol_error() { assert_eq!( diff --git a/crates/ironclaw_reborn_composition/src/product_live_adapters.rs b/crates/ironclaw_reborn_composition/src/product_live_adapters.rs index 237254fa0df..8a863f0831b 100644 --- a/crates/ironclaw_reborn_composition/src/product_live_adapters.rs +++ b/crates/ironclaw_reborn_composition/src/product_live_adapters.rs @@ -317,6 +317,21 @@ impl LoopCapabilityResultWriter for ProductLiveCapabilityIo { .record_running_invocation(invocation_id, input_ref); } + fn stage_capability_failure_preview( + &self, + run_context: &LoopRunContext, + invocation_id: InvocationId, + capability_id: &CapabilityId, + summary: &str, + ) { + self.display_previews.record_failure_preview( + &run_context.run_id.to_string(), + invocation_id, + capability_id, + summary, + ); + } + async fn update_capability_result( &self, run_context: &LoopRunContext, diff --git a/crates/ironclaw_reborn_composition/src/projection/display_preview.rs b/crates/ironclaw_reborn_composition/src/projection/display_preview.rs index b87ef355d4c..ca2700253d0 100644 --- a/crates/ironclaw_reborn_composition/src/projection/display_preview.rs +++ b/crates/ironclaw_reborn_composition/src/projection/display_preview.rs @@ -256,6 +256,56 @@ impl CapabilityDisplayPreviewStore { .push(invocation_id); } + /// Stage a display preview for a FAILED invocation, carrying a + /// host-authored failure summary (e.g. rendered invalid-input issues) as + /// the output so the per-tool UI card shows the actionable detail instead + /// of only the bare error kind. Title/subtitle/input are pulled from the + /// pending input recorded at registration, mirroring the success path in + /// `record_result_with_preview`. No result ref is staged: the model-visible + /// result is written separately through the transcript port. + pub(crate) fn record_failure_preview( + &self, + run_id: &str, + invocation_id: InvocationId, + capability_id: &CapabilityId, + summary: &str, + ) { + let input = { + let mut pending = self.lock_pending_inputs(); + let input_ref = pending + .input_ref_by_invocation + .remove(&invocation_id.to_string()); + input_ref.and_then(|input_ref| pending.by_ref.remove(&input_ref)) + }; + let title = input + .as_ref() + .map(|input| input.title.clone()) + .unwrap_or_else(|| safe_capability_title(capability_id.as_str()).to_string()); + let bounded = bounded_display_text(summary, CAPABILITY_DISPLAY_SUMMARY_MAX_BYTES); + let record = CapabilityDisplayPreviewRecord { + timeline_message_id: None, + title, + subtitle: input.as_ref().and_then(|input| input.subtitle.clone()), + input_summary: input.as_ref().and_then(|input| input.input_summary.clone()), + output_summary: Some(bounded.text.clone()), + output_preview: Some(bounded.text), + output_kind: Some("text".to_string()), + output_bytes: None, + result_ref: None, + truncated: bounded.truncated || input.as_ref().is_some_and(|input| input.truncated), + }; + let mut completed = self.lock_completed_previews(); + let invocation_id = invocation_id.to_string(); + completed + .by_invocation + .insert(invocation_id.clone(), record); + completed + .invocations_by_run + .entry(run_id.to_string()) + .or_default() + .push(invocation_id); + } + pub(crate) fn prune_run(&self, run_id: &str) { let mut pending = self.lock_pending_inputs(); if let Some(input_refs) = pending.refs_by_run.remove(run_id) { diff --git a/crates/ironclaw_reborn_composition/src/projection/tests/display_preview.rs b/crates/ironclaw_reborn_composition/src/projection/tests/display_preview.rs index 0b878eac76e..7de4d3ca6c0 100644 --- a/crates/ironclaw_reborn_composition/src/projection/tests/display_preview.rs +++ b/crates/ironclaw_reborn_composition/src/projection/tests/display_preview.rs @@ -1090,6 +1090,70 @@ async fn capability_display_preview_falls_back_for_failed_tool_without_result() ); } +// Regression: when a failed capability stages a failure preview carrying the +// rendered invalid-input detail, the projection surfaces that detail in +// `output_summary` instead of degrading to the bare "tool failed: " +// fallback. This is the chokepoint the per-tool UI Error tab reads. +#[tokio::test] +async fn capability_display_preview_uses_staged_failure_summary_over_bare_kind() { + let run_id = TurnRunId::new(); + let invocation_id = InvocationId::from_uuid(run_id.as_uuid()); + let capability = CapabilityId::new("builtin.json").unwrap(); + let input_ref = preview_input_ref("failure-detail"); + let store = CapabilityDisplayPreviewStore::default(); + store.record_input( + &run_id.to_string(), + &input_ref, + "json", + &serde_json::json!({ "value": "{" }), + ); + store.record_running_invocation(invocation_id, &input_ref); + + store.record_failure_preview( + &run_id.to_string(), + invocation_id, + &capability, + "Invalid input: value — missing required field (expected object)", + ); + + let preview = store + .preview(&CapabilityActivityProjection { + invocation_id, + run_id: Some(invocation_id), + capability_id: capability, + thread_id: Some(ThreadId::new("webui-preview-thread").unwrap()), + status: ironclaw_event_projections::CapabilityActivityStatus::Failed, + provider: None, + runtime: None, + process_id: None, + output_bytes: None, + error_kind: Some("invalid_input".to_string()), + first_cursor: ironclaw_events::EventCursor::new(1), + last_cursor: ironclaw_events::EventCursor::new(1), + updated_at: chrono::Utc::now(), + }) + .await + .unwrap() + .unwrap(); + + // The bare kind is still on `error_kind`, but the human-facing summary now + // carries the actionable detail, not "tool failed: invalid_input". + assert_eq!(preview.error_kind.as_deref(), Some("invalid_input")); + assert_eq!( + preview.output_summary.as_deref(), + Some("Invalid input: value — missing required field (expected object)") + ); + assert!( + !preview + .output_summary + .as_deref() + .unwrap() + .contains("tool failed") + ); + // Title comes from the staged input, not just the capability id. + assert_eq!(preview.title, "json"); +} + #[tokio::test] async fn capability_display_preview_store_preserves_long_line_counts() { let run_id = TurnRunId::new(); diff --git a/crates/ironclaw_reborn_composition/src/projection/tests/failure_explanation.rs b/crates/ironclaw_reborn_composition/src/projection/tests/failure_explanation.rs index ef6ce7a9efa..c3240d7e3a1 100644 --- a/crates/ironclaw_reborn_composition/src/projection/tests/failure_explanation.rs +++ b/crates/ironclaw_reborn_composition/src/projection/tests/failure_explanation.rs @@ -53,9 +53,9 @@ async fn webui_event_stream_projects_scheduler_executor_panic_summary() { .await; } -// Regression for issue #5289: a terminal capability failure reaches the -// projection as the `capability_protocol_error` category (from -// `LoopFailureKind::as_str()`). Before the fix it was unmapped in +// Regression: a terminal capability failure reaches the projection as the +// `capability_protocol_error` category (from `LoopFailureKind::as_str()`). +// Before the fix it was unmapped in // `reborn_failure_summary_for_category`, so the fallback degraded to the // generic "The run failed before producing a reply." and the LLM explainer // paraphrased it into a vague "driver protocol error" that hid the real tool diff --git a/crates/ironclaw_reborn_composition/src/runtime/local_dev.rs b/crates/ironclaw_reborn_composition/src/runtime/local_dev.rs index 9d0389abb9b..06fafc573bb 100644 --- a/crates/ironclaw_reborn_composition/src/runtime/local_dev.rs +++ b/crates/ironclaw_reborn_composition/src/runtime/local_dev.rs @@ -640,6 +640,21 @@ impl LoopCapabilityResultWriter for LocalDevCapabilityIo { .record_running_invocation(invocation_id, input_ref); } + fn stage_capability_failure_preview( + &self, + run_context: &LoopRunContext, + invocation_id: InvocationId, + capability_id: &CapabilityId, + summary: &str, + ) { + self.display_previews.record_failure_preview( + &run_context.run_id.to_string(), + invocation_id, + capability_id, + summary, + ); + } + async fn update_capability_result( &self, run_context: &LoopRunContext, diff --git a/crates/ironclaw_webui_v2_static/static/dist/app.js b/crates/ironclaw_webui_v2_static/static/dist/app.js index bf768361094..59c51e43bd3 100644 --- a/crates/ironclaw_webui_v2_static/static/dist/app.js +++ b/crates/ironclaw_webui_v2_static/static/dist/app.js @@ -8,7 +8,7 @@ Error generating stack: `+a.message+` `+a.stack}}function ma(e){switch(typeof e){case"bigint":case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function yy(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function JR(e){var t=yy(e)?"checked":"value",a=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),n=""+e[t];if(!e.hasOwnProperty(t)&&typeof a<"u"&&typeof a.get=="function"&&typeof a.set=="function"){var r=a.get,s=a.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return r.call(this)},set:function(i){n=""+i,s.call(this,i)}}),Object.defineProperty(e,t,{enumerable:a.enumerable}),{getValue:function(){return n},setValue:function(i){n=""+i},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function vu(e){e._valueTracker||(e._valueTracker=JR(e))}function by(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var a=t.getValue(),n="";return e&&(n=yy(e)?e.checked?"true":"false":e.value),e=n,e!==a?(t.setValue(e),!0):!1}function gu(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}var XR=/[\n"\\]/g;function ha(e){return e.replace(XR,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function _m(e,t,a,n,r,s,i,o){e.name="",i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"?e.type=i:e.removeAttribute("type"),t!=null?i==="number"?(t===0&&e.value===""||e.value!=t)&&(e.value=""+ma(t)):e.value!==""+ma(t)&&(e.value=""+ma(t)):i!=="submit"&&i!=="reset"||e.removeAttribute("value"),t!=null?km(e,i,ma(t)):a!=null?km(e,i,ma(a)):n!=null&&e.removeAttribute("value"),r==null&&s!=null&&(e.defaultChecked=!!s),r!=null&&(e.checked=r&&typeof r!="function"&&typeof r!="symbol"),o!=null&&typeof o!="function"&&typeof o!="symbol"&&typeof o!="boolean"?e.name=""+ma(o):e.removeAttribute("name")}function xy(e,t,a,n,r,s,i,o){if(s!=null&&typeof s!="function"&&typeof s!="symbol"&&typeof s!="boolean"&&(e.type=s),t!=null||a!=null){if(!(s!=="submit"&&s!=="reset"||t!=null))return;a=a!=null?""+ma(a):"",t=t!=null?""+ma(t):a,o||t===e.value||(e.value=t),e.defaultValue=t}n=n??r,n=typeof n!="function"&&typeof n!="symbol"&&!!n,e.checked=o?e.checked:!!n,e.defaultChecked=!!n,i!=null&&typeof i!="function"&&typeof i!="symbol"&&typeof i!="boolean"&&(e.name=i)}function km(e,t,a){t==="number"&&gu(e.ownerDocument)===e||e.defaultValue===""+a||(e.defaultValue=""+a)}function Ss(e,t,a,n){if(e=e.options,t){t={};for(var r=0;r"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Cm=!1;if(gn)try{rs={},Object.defineProperty(rs,"passive",{get:function(){Cm=!0}}),window.addEventListener("test",rs,rs),window.removeEventListener("test",rs,rs)}catch{Cm=!1}var rs,Hn=null,xf=null,tu=null;function _y(){if(tu)return tu;var e,t=xf,a=t.length,n,r="value"in Hn?Hn.value:Hn.textContent,s=r.length;for(e=0;e=Yi),Gv=" ",Yv=!1;function Ry(e,t){switch(e){case"keyup":return _C.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Cy(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ps=!1;function RC(e,t){switch(e){case"compositionend":return Cy(t);case"keypress":return t.which!==32?null:(Yv=!0,Gv);case"textInput":return e=t.data,e===Gv&&Yv?null:e;default:return null}}function CC(e,t){if(ps)return e==="compositionend"||!wf&&Ry(e,t)?(e=_y(),tu=xf=Hn=null,ps=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:a,offset:t-e};e=n}e:{for(;a;){if(a.nextSibling){a=a.nextSibling;break e}a=a.parentNode}a=void 0}a=Wv(a)}}function Dy(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?Dy(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function My(e){e=e!=null&&e.ownerDocument!=null&&e.ownerDocument.defaultView!=null?e.ownerDocument.defaultView:window;for(var t=gu(e.document);t instanceof e.HTMLIFrameElement;){try{var a=typeof t.contentWindow.location.href=="string"}catch{a=!1}if(a)e=t.contentWindow;else break;t=gu(e.document)}return t}function Sf(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}var PC=gn&&"documentMode"in document&&11>=document.documentMode,hs=null,Em=null,Xi=null,Tm=!1;function tg(e,t,a){var n=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;Tm||hs==null||hs!==gu(n)||(n=hs,"selectionStart"in n&&Sf(n)?n={start:n.selectionStart,end:n.selectionEnd}:(n=(n.ownerDocument&&n.ownerDocument.defaultView||window).getSelection(),n={anchorNode:n.anchorNode,anchorOffset:n.anchorOffset,focusNode:n.focusNode,focusOffset:n.focusOffset}),Xi&&fo(Xi,n)||(Xi=n,n=Lu(Em,"onSelect"),0>=i,r-=i,dn=1<<32-Xt(t)+r|a<s?s:8;var i=te.T,o={};te.T=o,Kf(e,!1,t,a);try{var u=r(),c=te.S;if(c!==null&&c(o,u),u!==null&&typeof u=="object"&&typeof u.then=="function"){var d=HC(u,n);no(e,t,d,Zt(e))}else no(e,t,n,Zt(e))}catch(m){no(e,t,{then:function(){},status:"rejected",reason:m},Zt())}finally{ge.p=s,te.T=i}}function JC(){}function Im(e,t,a,n){if(e.tag!==5)throw Error(U(476));var r=yb(e).queue;gb(e,r,t,wr,a===null?JC:function(){return bb(e),a(n)})}function yb(e){var t=e.memoizedState;if(t!==null)return t;t={memoizedState:wr,baseState:wr,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:yn,lastRenderedState:wr},next:null};var a={};return t.next={memoizedState:a,baseState:a,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:yn,lastRenderedState:a},next:null},e.memoizedState=t,e=e.alternate,e!==null&&(e.memoizedState=t),t}function bb(e){var t=yb(e).next.queue;no(e,t,{},Zt())}function If(){return wt(xo)}function xb(){return Xe().memoizedState}function $b(){return Xe().memoizedState}function XC(e){for(var t=e.return;t!==null;){switch(t.tag){case 24:case 3:var a=Zt();e=Yn(a);var n=Jn(t,e,a);n!==null&&(Wt(n,t,a),eo(n,t,a)),t={cache:Ef()},e.payload=t;return}t=t.return}}function ZC(e,t,a){var n=Zt();a={lane:n,revertLane:0,action:a,hasEagerState:!1,eagerState:null,next:null},Xu(e)?Sb(t,a):(a=_f(e,t,a,n),a!==null&&(Wt(a,e,n),Nb(a,t,n)))}function wb(e,t,a){var n=Zt();no(e,t,a,n)}function no(e,t,a,n){var r={lane:n,revertLane:0,action:a,hasEagerState:!1,eagerState:null,next:null};if(Xu(e))Sb(t,r);else{var s=e.alternate;if(e.lanes===0&&(s===null||s.lanes===0)&&(s=t.lastRenderedReducer,s!==null))try{var i=t.lastRenderedState,o=s(i,a);if(r.hasEagerState=!0,r.eagerState=o,ea(o,i))return Vu(e,t,r,0),Re===null&&Qu(),!1}catch{}finally{}if(a=_f(e,t,r,n),a!==null)return Wt(a,e,n),Nb(a,t,n),!0}return!1}function Kf(e,t,a,n){if(n={lane:2,revertLane:Zf(),action:n,hasEagerState:!1,eagerState:null,next:null},Xu(e)){if(t)throw Error(U(479))}else t=_f(e,a,n,2),t!==null&&Wt(t,e,2)}function Xu(e){var t=e.alternate;return e===se||t!==null&&t===se}function Sb(e,t){ks=Su=!0;var a=e.pending;a===null?t.next=t:(t.next=a.next,a.next=t),e.pending=t}function Nb(e,t,a){if((a&4194048)!==0){var n=t.lanes;n&=e.pendingLanes,a|=n,t.lanes=a,py(e,a)}}var _u={readContext:wt,use:Yu,useCallback:Qe,useContext:Qe,useEffect:Qe,useImperativeHandle:Qe,useLayoutEffect:Qe,useInsertionEffect:Qe,useMemo:Qe,useReducer:Qe,useRef:Qe,useState:Qe,useDebugValue:Qe,useDeferredValue:Qe,useTransition:Qe,useSyncExternalStore:Qe,useId:Qe,useHostTransitionStatus:Qe,useFormState:Qe,useActionState:Qe,useOptimistic:Qe,useMemoCache:Qe,useCacheRefresh:Qe},_b={readContext:wt,use:Yu,useCallback:function(e,t){return Lt().memoizedState=[e,t===void 0?null:t],e},useContext:wt,useEffect:gg,useImperativeHandle:function(e,t,a){a=a!=null?a.concat([e]):null,iu(4194308,4,mb.bind(null,t,e),a)},useLayoutEffect:function(e,t){return iu(4194308,4,e,t)},useInsertionEffect:function(e,t){iu(4,2,e,t)},useMemo:function(e,t){var a=Lt();t=t===void 0?null:t;var n=e();if(Ar){Kn(!0);try{e()}finally{Kn(!1)}}return a.memoizedState=[n,t],n},useReducer:function(e,t,a){var n=Lt();if(a!==void 0){var r=a(t);if(Ar){Kn(!0);try{a(t)}finally{Kn(!1)}}}else r=t;return n.memoizedState=n.baseState=r,e={pending:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:r},n.queue=e,e=e.dispatch=ZC.bind(null,se,e),[n.memoizedState,e]},useRef:function(e){var t=Lt();return e={current:e},t.memoizedState=e},useState:function(e){e=zm(e);var t=e.queue,a=wb.bind(null,se,t);return t.dispatch=a,[e.memoizedState,a]},useDebugValue:zf,useDeferredValue:function(e,t){var a=Lt();return qf(a,e,t)},useTransition:function(){var e=zm(!1);return e=gb.bind(null,se,e.queue,!0,!1),Lt().memoizedState=e,[!1,e]},useSyncExternalStore:function(e,t,a){var n=se,r=Lt();if(ve){if(a===void 0)throw Error(U(407));a=a()}else{if(a=t(),Re===null)throw Error(U(349));(fe&124)!==0||Zy(n,t,a)}r.memoizedState=a;var s={value:a,getSnapshot:t};return r.queue=s,gg(eb.bind(null,n,s,e),[e]),n.flags|=2048,Ls(9,Ju(),Wy.bind(null,n,s,a,t),null),a},useId:function(){var e=Lt(),t=Re.identifierPrefix;if(ve){var a=mn,n=dn;a=(n&~(1<<32-Xt(n)-1)).toString(32)+a,t="\xAB"+t+"R"+a,a=Nu++,0T?(O=_,_=null):O=_.sibling;var D=f(g,_,b[T],$);if(D===null){_===null&&(_=O);break}e&&_&&D.alternate===null&&t(g,_),v=s(D,v,T),E===null?S=D:E.sibling=D,E=D,_=O}if(T===b.length)return a(g,_),ve&&xr(g,T),S;if(_===null){for(;TT?(O=_,_=null):O=_.sibling;var P=f(g,_,D.value,$);if(P===null){_===null&&(_=O);break}e&&_&&P.alternate===null&&t(g,_),v=s(P,v,T),E===null?S=P:E.sibling=P,E=P,_=O}if(D.done)return a(g,_),ve&&xr(g,T),S;if(_===null){for(;!D.done;T++,D=b.next())D=m(g,D.value,$),D!==null&&(v=s(D,v,T),E===null?S=D:E.sibling=D,E=D);return ve&&xr(g,T),S}for(_=n(_);!D.done;T++,D=b.next())D=p(_,g,T,D.value,$),D!==null&&(e&&D.alternate!==null&&_.delete(D.key===null?T:D.key),v=s(D,v,T),E===null?S=D:E.sibling=D,E=D);return e&&_.forEach(function(R){return t(g,R)}),ve&&xr(g,T),S}function w(g,v,b,$){if(typeof b=="object"&&b!==null&&b.type===cs&&b.key===null&&(b=b.props.children),typeof b=="object"&&b!==null){switch(b.$$typeof){case Pl:e:{for(var S=b.key;v!==null;){if(v.key===S){if(S=b.type,S===cs){if(v.tag===7){a(g,v.sibling),$=r(v,b.props.children),$.return=g,g=$;break e}}else if(v.elementType===S||typeof S=="object"&&S!==null&&S.$$typeof===jn&&yg(S)===v.type){a(g,v.sibling),$=r(v,b.props),Bi($,b),$.return=g,g=$;break e}a(g,v);break}else t(g,v);v=v.sibling}b.type===cs?($=Sr(b.props.children,g.mode,$,b.key),$.return=g,g=$):($=nu(b.type,b.key,b.props,null,g.mode,$),Bi($,b),$.return=g,g=$)}return i(g);case Hi:e:{for(S=b.key;v!==null;){if(v.key===S)if(v.tag===4&&v.stateNode.containerInfo===b.containerInfo&&v.stateNode.implementation===b.implementation){a(g,v.sibling),$=r(v,b.children||[]),$.return=g,g=$;break e}else{a(g,v);break}else t(g,v);v=v.sibling}$=Xd(b,g.mode,$),$.return=g,g=$}return i(g);case jn:return S=b._init,b=S(b._payload),w(g,v,b,$)}if(Qi(b))return x(g,v,b,$);if(Ui(b)){if(S=Ui(b),typeof S!="function")throw Error(U(150));return b=S.call(b),y(g,v,b,$)}if(typeof b.then=="function")return w(g,v,Hl(b),$);if(b.$$typeof===cn)return w(g,v,Il(g,b),$);Ql(g,b)}return typeof b=="string"&&b!==""||typeof b=="number"||typeof b=="bigint"?(b=""+b,v!==null&&v.tag===6?(a(g,v.sibling),$=r(v,b),$.return=g,g=$):(a(g,v),$=Jd(b,g.mode,$),$.return=g,g=$),i(g)):a(g,v)}return function(g,v,b,$){try{vo=0;var S=w(g,v,b,$);return Cs=null,S}catch(_){if(_===Mo||_===Gu)throw _;var E=Yt(29,_,null,g.mode);return E.lanes=$,E.return=g,E}finally{}}}var Ps=Rb(!0),Cb=Rb(!1),ya=Qa(null),Ha=null;function zn(e){var t=e.alternate;Oe(nt,nt.current&1),Oe(ya,e),Ha===null&&(t===null||Os.current!==null||t.memoizedState!==null)&&(Ha=e)}function Eb(e){if(e.tag===22){if(Oe(nt,nt.current),Oe(ya,e),Ha===null){var t=e.alternate;t!==null&&t.memoizedState!==null&&(Ha=e)}}else qn(e)}function qn(){Oe(nt,nt.current),Oe(ya,ya.current)}function pn(e){mt(ya),Ha===e&&(Ha=null),mt(nt)}var nt=Qa(0);function ku(e){for(var t=e;t!==null;){if(t.tag===13){var a=t.memoizedState;if(a!==null&&(a=a.dehydrated,a===null||a.data==="$?"||of(a)))return t}else if(t.tag===19&&t.memoizedProps.revealOrder!==void 0){if((t.flags&128)!==0)return t}else if(t.child!==null){t.child.return=t,t=t.child;continue}if(t===e)break;for(;t.sibling===null;){if(t.return===null||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}function em(e,t,a,n){t=e.memoizedState,a=a(n,t),a=a==null?t:Te({},t,a),e.memoizedState=a,e.lanes===0&&(e.updateQueue.baseState=a)}var Km={enqueueSetState:function(e,t,a){e=e._reactInternals;var n=Zt(),r=Yn(n);r.payload=t,a!=null&&(r.callback=a),t=Jn(e,r,n),t!==null&&(Wt(t,e,n),eo(t,e,n))},enqueueReplaceState:function(e,t,a){e=e._reactInternals;var n=Zt(),r=Yn(n);r.tag=1,r.payload=t,a!=null&&(r.callback=a),t=Jn(e,r,n),t!==null&&(Wt(t,e,n),eo(t,e,n))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var a=Zt(),n=Yn(a);n.tag=2,t!=null&&(n.callback=t),t=Jn(e,n,a),t!==null&&(Wt(t,e,a),eo(t,e,a))}};function bg(e,t,a,n,r,s,i){return e=e.stateNode,typeof e.shouldComponentUpdate=="function"?e.shouldComponentUpdate(n,s,i):t.prototype&&t.prototype.isPureReactComponent?!fo(a,n)||!fo(r,s):!0}function xg(e,t,a,n){e=t.state,typeof t.componentWillReceiveProps=="function"&&t.componentWillReceiveProps(a,n),typeof t.UNSAFE_componentWillReceiveProps=="function"&&t.UNSAFE_componentWillReceiveProps(a,n),t.state!==e&&Km.enqueueReplaceState(t,t.state,null)}function Dr(e,t){var a=t;if("ref"in t){a={};for(var n in t)n!=="ref"&&(a[n]=t[n])}if(e=e.defaultProps){a===t&&(a=Te({},a));for(var r in e)a[r]===void 0&&(a[r]=e[r])}return a}var Ru=typeof reportError=="function"?reportError:function(e){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var t=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof e=="object"&&e!==null&&typeof e.message=="string"?String(e.message):String(e),error:e});if(!window.dispatchEvent(t))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",e);return}console.error(e)};function Tb(e){Ru(e)}function Ab(e){console.error(e)}function Db(e){Ru(e)}function Cu(e,t){try{var a=e.onUncaughtError;a(t.value,{componentStack:t.stack})}catch(n){setTimeout(function(){throw n})}}function $g(e,t,a){try{var n=e.onCaughtError;n(a.value,{componentStack:a.stack,errorBoundary:t.tag===1?t.stateNode:null})}catch(r){setTimeout(function(){throw r})}}function Hm(e,t,a){return a=Yn(a),a.tag=3,a.payload={element:null},a.callback=function(){Cu(e,t)},a}function Mb(e){return e=Yn(e),e.tag=3,e}function Ob(e,t,a,n){var r=a.type.getDerivedStateFromError;if(typeof r=="function"){var s=n.value;e.payload=function(){return r(s)},e.callback=function(){$g(t,a,n)}}var i=a.stateNode;i!==null&&typeof i.componentDidCatch=="function"&&(e.callback=function(){$g(t,a,n),typeof r!="function"&&(Xn===null?Xn=new Set([this]):Xn.add(this));var o=n.stack;this.componentDidCatch(n.value,{componentStack:o!==null?o:""})})}function eE(e,t,a,n,r){if(a.flags|=32768,n!==null&&typeof n=="object"&&typeof n.then=="function"){if(t=a.alternate,t!==null&&Ao(t,a,r,!0),a=ya.current,a!==null){switch(a.tag){case 13:return Ha===null?Wm():a.alternate===null&&qe===0&&(qe=3),a.flags&=-257,a.flags|=65536,a.lanes=r,n===Um?a.flags|=16384:(t=a.updateQueue,t===null?a.updateQueue=new Set([n]):t.add(n),dm(e,n,r)),!1;case 22:return a.flags|=65536,n===Um?a.flags|=16384:(t=a.updateQueue,t===null?(t={transitions:null,markerInstances:null,retryQueue:new Set([n])},a.updateQueue=t):(a=t.retryQueue,a===null?t.retryQueue=new Set([n]):a.add(n)),dm(e,n,r)),!1}throw Error(U(435,a.tag))}return dm(e,n,r),Wm(),!1}if(ve)return t=ya.current,t!==null?((t.flags&65536)===0&&(t.flags|=256),t.flags|=65536,t.lanes=r,n!==Dm&&(e=Error(U(422),{cause:n}),po(va(e,a)))):(n!==Dm&&(t=Error(U(423),{cause:n}),po(va(t,a))),e=e.current.alternate,e.flags|=65536,r&=-r,e.lanes|=r,n=va(n,a),r=Hm(e.stateNode,n,r),Zd(e,r),qe!==4&&(qe=2)),!1;var s=Error(U(520),{cause:n});if(s=va(s,a),io===null?io=[s]:io.push(s),qe!==4&&(qe=2),t===null)return!0;n=va(n,a),a=t;do{switch(a.tag){case 3:return a.flags|=65536,e=r&-r,a.lanes|=e,e=Hm(a.stateNode,n,e),Zd(a,e),!1;case 1:if(t=a.type,s=a.stateNode,(a.flags&128)===0&&(typeof t.getDerivedStateFromError=="function"||s!==null&&typeof s.componentDidCatch=="function"&&(Xn===null||!Xn.has(s))))return a.flags|=65536,r&=-r,a.lanes|=r,r=Mb(r),Ob(r,e,a,n),Zd(a,r),!1}a=a.return}while(a!==null);return!1}var Lb=Error(U(461)),dt=!1;function vt(e,t,a,n){t.child=e===null?Cb(t,null,a,n):Ps(t,e.child,a,n)}function wg(e,t,a,n,r){a=a.render;var s=t.ref;if("ref"in n){var i={};for(var o in n)o!=="ref"&&(i[o]=n[o])}else i=n;return Tr(t),n=Of(e,t,a,i,s,r),o=Lf(),e!==null&&!dt?(Pf(e,t,r),bn(e,t,r)):(ve&&o&&Rf(t),t.flags|=1,vt(e,t,n,r),t.child)}function Sg(e,t,a,n,r){if(e===null){var s=a.type;return typeof s=="function"&&!kf(s)&&s.defaultProps===void 0&&a.compare===null?(t.tag=15,t.type=s,Pb(e,t,s,n,r)):(e=nu(a.type,null,n,t,t.mode,r),e.ref=t.ref,e.return=t,t.child=e)}if(s=e.child,!Hf(e,r)){var i=s.memoizedProps;if(a=a.compare,a=a!==null?a:fo,a(i,n)&&e.ref===t.ref)return bn(e,t,r)}return t.flags|=1,e=hn(s,n),e.ref=t.ref,e.return=t,t.child=e}function Pb(e,t,a,n,r){if(e!==null){var s=e.memoizedProps;if(fo(s,n)&&e.ref===t.ref)if(dt=!1,t.pendingProps=n=s,Hf(e,r))(e.flags&131072)!==0&&(dt=!0);else return t.lanes=e.lanes,bn(e,t,r)}return Qm(e,t,a,n,r)}function Ub(e,t,a){var n=t.pendingProps,r=n.children,s=e!==null?e.memoizedState:null;if(n.mode==="hidden"){if((t.flags&128)!==0){if(n=s!==null?s.baseLanes|a:a,e!==null){for(r=t.child=e.child,s=0;r!==null;)s=s|r.lanes|r.childLanes,r=r.sibling;t.childLanes=s&~n}else t.childLanes=0,t.child=null;return Ng(e,t,n,a)}if((a&536870912)!==0)t.memoizedState={baseLanes:0,cachePool:null},e!==null&&ru(t,s!==null?s.cachePool:null),s!==null?dg(t,s):Bm(),Eb(t);else return t.lanes=t.childLanes=536870912,Ng(e,t,s!==null?s.baseLanes|a:a,a)}else s!==null?(ru(t,s.cachePool),dg(t,s),qn(t),t.memoizedState=null):(e!==null&&ru(t,null),Bm(),qn(t));return vt(e,t,r,a),t.child}function Ng(e,t,a,n){var r=Tf();return r=r===null?null:{parent:at._currentValue,pool:r},t.memoizedState={baseLanes:a,cachePool:r},e!==null&&ru(t,null),Bm(),Eb(t),e!==null&&Ao(e,t,n,!0),null}function ou(e,t){var a=t.ref;if(a===null)e!==null&&e.ref!==null&&(t.flags|=4194816);else{if(typeof a!="function"&&typeof a!="object")throw Error(U(284));(e===null||e.ref!==a)&&(t.flags|=4194816)}}function Qm(e,t,a,n,r){return Tr(t),a=Of(e,t,a,n,void 0,r),n=Lf(),e!==null&&!dt?(Pf(e,t,r),bn(e,t,r)):(ve&&n&&Rf(t),t.flags|=1,vt(e,t,a,r),t.child)}function _g(e,t,a,n,r,s){return Tr(t),t.updateQueue=null,a=Jy(t,n,a,r),Yy(e),n=Lf(),e!==null&&!dt?(Pf(e,t,s),bn(e,t,s)):(ve&&n&&Rf(t),t.flags|=1,vt(e,t,a,s),t.child)}function kg(e,t,a,n,r){if(Tr(t),t.stateNode===null){var s=ys,i=a.contextType;typeof i=="object"&&i!==null&&(s=wt(i)),s=new a(n,s),t.memoizedState=s.state!==null&&s.state!==void 0?s.state:null,s.updater=Km,t.stateNode=s,s._reactInternals=t,s=t.stateNode,s.props=n,s.state=t.memoizedState,s.refs={},Af(t),i=a.contextType,s.context=typeof i=="object"&&i!==null?wt(i):ys,s.state=t.memoizedState,i=a.getDerivedStateFromProps,typeof i=="function"&&(em(t,a,i,n),s.state=t.memoizedState),typeof a.getDerivedStateFromProps=="function"||typeof s.getSnapshotBeforeUpdate=="function"||typeof s.UNSAFE_componentWillMount!="function"&&typeof s.componentWillMount!="function"||(i=s.state,typeof s.componentWillMount=="function"&&s.componentWillMount(),typeof s.UNSAFE_componentWillMount=="function"&&s.UNSAFE_componentWillMount(),i!==s.state&&Km.enqueueReplaceState(s,s.state,null),ao(t,n,s,r),to(),s.state=t.memoizedState),typeof s.componentDidMount=="function"&&(t.flags|=4194308),n=!0}else if(e===null){s=t.stateNode;var o=t.memoizedProps,u=Dr(a,o);s.props=u;var c=s.context,d=a.contextType;i=ys,typeof d=="object"&&d!==null&&(i=wt(d));var m=a.getDerivedStateFromProps;d=typeof m=="function"||typeof s.getSnapshotBeforeUpdate=="function",o=t.pendingProps!==o,d||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(o||c!==i)&&xg(t,s,n,i),Fn=!1;var f=t.memoizedState;s.state=f,ao(t,n,s,r),to(),c=t.memoizedState,o||f!==c||Fn?(typeof m=="function"&&(em(t,a,m,n),c=t.memoizedState),(u=Fn||bg(t,a,u,n,f,c,i))?(d||typeof s.UNSAFE_componentWillMount!="function"&&typeof s.componentWillMount!="function"||(typeof s.componentWillMount=="function"&&s.componentWillMount(),typeof s.UNSAFE_componentWillMount=="function"&&s.UNSAFE_componentWillMount()),typeof s.componentDidMount=="function"&&(t.flags|=4194308)):(typeof s.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=n,t.memoizedState=c),s.props=n,s.state=c,s.context=i,n=u):(typeof s.componentDidMount=="function"&&(t.flags|=4194308),n=!1)}else{s=t.stateNode,jm(e,t),i=t.memoizedProps,d=Dr(a,i),s.props=d,m=t.pendingProps,f=s.context,c=a.contextType,u=ys,typeof c=="object"&&c!==null&&(u=wt(c)),o=a.getDerivedStateFromProps,(c=typeof o=="function"||typeof s.getSnapshotBeforeUpdate=="function")||typeof s.UNSAFE_componentWillReceiveProps!="function"&&typeof s.componentWillReceiveProps!="function"||(i!==m||f!==u)&&xg(t,s,n,u),Fn=!1,f=t.memoizedState,s.state=f,ao(t,n,s,r),to();var p=t.memoizedState;i!==m||f!==p||Fn||e!==null&&e.dependencies!==null&&$u(e.dependencies)?(typeof o=="function"&&(em(t,a,o,n),p=t.memoizedState),(d=Fn||bg(t,a,d,n,f,p,u)||e!==null&&e.dependencies!==null&&$u(e.dependencies))?(c||typeof s.UNSAFE_componentWillUpdate!="function"&&typeof s.componentWillUpdate!="function"||(typeof s.componentWillUpdate=="function"&&s.componentWillUpdate(n,p,u),typeof s.UNSAFE_componentWillUpdate=="function"&&s.UNSAFE_componentWillUpdate(n,p,u)),typeof s.componentDidUpdate=="function"&&(t.flags|=4),typeof s.getSnapshotBeforeUpdate=="function"&&(t.flags|=1024)):(typeof s.componentDidUpdate!="function"||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=1024),t.memoizedProps=n,t.memoizedState=p),s.props=n,s.state=p,s.context=u,n=d):(typeof s.componentDidUpdate!="function"||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=4),typeof s.getSnapshotBeforeUpdate!="function"||i===e.memoizedProps&&f===e.memoizedState||(t.flags|=1024),n=!1)}return s=n,ou(e,t),n=(t.flags&128)!==0,s||n?(s=t.stateNode,a=n&&typeof a.getDerivedStateFromError!="function"?null:s.render(),t.flags|=1,e!==null&&n?(t.child=Ps(t,e.child,null,r),t.child=Ps(t,null,a,r)):vt(e,t,a,r),t.memoizedState=s.state,e=t.child):e=bn(e,t,r),e}function Rg(e,t,a,n){return To(),t.flags|=256,vt(e,t,a,n),t.child}var tm={dehydrated:null,treeContext:null,retryLane:0,hydrationErrors:null};function am(e){return{baseLanes:e,cachePool:Ky()}}function nm(e,t,a){return e=e!==null?e.childLanes&~a:0,t&&(e|=ga),e}function jb(e,t,a){var n=t.pendingProps,r=!1,s=(t.flags&128)!==0,i;if((i=s)||(i=e!==null&&e.memoizedState===null?!1:(nt.current&2)!==0),i&&(r=!0,t.flags&=-129),i=(t.flags&32)!==0,t.flags&=-33,e===null){if(ve){if(r?zn(t):qn(t),ve){var o=ze,u;if(u=o){e:{for(u=o,o=za;u.nodeType!==8;){if(!o){o=null;break e}if(u=Ca(u.nextSibling),u===null){o=null;break e}}o=u}o!==null?(t.memoizedState={dehydrated:o,treeContext:Nr!==null?{id:dn,overflow:mn}:null,retryLane:536870912,hydrationErrors:null},u=Yt(18,null,null,0),u.stateNode=o,u.return=t,t.child=u,Ct=t,ze=null,u=!0):u=!1}u||Er(t)}if(o=t.memoizedState,o!==null&&(o=o.dehydrated,o!==null))return of(o)?t.lanes=32:t.lanes=536870912,null;pn(t)}return o=n.children,n=n.fallback,r?(qn(t),r=t.mode,o=Eu({mode:"hidden",children:o},r),n=Sr(n,r,a,null),o.return=t,n.return=t,o.sibling=n,t.child=o,r=t.child,r.memoizedState=am(a),r.childLanes=nm(e,i,a),t.memoizedState=tm,n):(zn(t),Vm(t,o))}if(u=e.memoizedState,u!==null&&(o=u.dehydrated,o!==null)){if(s)t.flags&256?(zn(t),t.flags&=-257,t=rm(e,t,a)):t.memoizedState!==null?(qn(t),t.child=e.child,t.flags|=128,t=null):(qn(t),r=n.fallback,o=t.mode,n=Eu({mode:"visible",children:n.children},o),r=Sr(r,o,a,null),r.flags|=2,n.return=t,r.return=t,n.sibling=r,t.child=n,Ps(t,e.child,null,a),n=t.child,n.memoizedState=am(a),n.childLanes=nm(e,i,a),t.memoizedState=tm,t=r);else if(zn(t),of(o)){if(i=o.nextSibling&&o.nextSibling.dataset,i)var c=i.dgst;i=c,n=Error(U(419)),n.stack="",n.digest=i,po({value:n,source:null,stack:null}),t=rm(e,t,a)}else if(dt||Ao(e,t,a,!1),i=(a&e.childLanes)!==0,dt||i){if(i=Re,i!==null&&(n=a&-a,n=(n&42)!==0?1:hf(n),n=(n&(i.suspendedLanes|a))!==0?0:n,n!==0&&n!==u.retryLane))throw u.retryLane=n,Is(e,n),Wt(i,e,n),Lb;o.data==="$?"||Wm(),t=rm(e,t,a)}else o.data==="$?"?(t.flags|=192,t.child=e.child,t=null):(e=u.treeContext,ze=Ca(o.nextSibling),Ct=t,ve=!0,_r=null,za=!1,e!==null&&(fa[pa++]=dn,fa[pa++]=mn,fa[pa++]=Nr,dn=e.id,mn=e.overflow,Nr=t),t=Vm(t,n.children),t.flags|=4096);return t}return r?(qn(t),r=n.fallback,o=t.mode,u=e.child,c=u.sibling,n=hn(u,{mode:"hidden",children:n.children}),n.subtreeFlags=u.subtreeFlags&65011712,c!==null?r=hn(c,r):(r=Sr(r,o,a,null),r.flags|=2),r.return=t,n.return=t,n.sibling=r,t.child=n,n=r,r=t.child,o=e.child.memoizedState,o===null?o=am(a):(u=o.cachePool,u!==null?(c=at._currentValue,u=u.parent!==c?{parent:c,pool:c}:u):u=Ky(),o={baseLanes:o.baseLanes|a,cachePool:u}),r.memoizedState=o,r.childLanes=nm(e,i,a),t.memoizedState=tm,n):(zn(t),a=e.child,e=a.sibling,a=hn(a,{mode:"visible",children:n.children}),a.return=t,a.sibling=null,e!==null&&(i=t.deletions,i===null?(t.deletions=[e],t.flags|=16):i.push(e)),t.child=a,t.memoizedState=null,a)}function Vm(e,t){return t=Eu({mode:"visible",children:t},e.mode),t.return=e,e.child=t}function Eu(e,t){return e=Yt(22,e,null,t),e.lanes=0,e.stateNode={_visibility:1,_pendingMarkers:null,_retryCache:null,_transitions:null},e}function rm(e,t,a){return Ps(t,e.child,null,a),e=Vm(t,t.pendingProps.children),e.flags|=2,t.memoizedState=null,e}function Cg(e,t,a){e.lanes|=t;var n=e.alternate;n!==null&&(n.lanes|=t),Om(e.return,t,a)}function sm(e,t,a,n,r){var s=e.memoizedState;s===null?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:n,tail:a,tailMode:r}:(s.isBackwards=t,s.rendering=null,s.renderingStartTime=0,s.last=n,s.tail=a,s.tailMode=r)}function Fb(e,t,a){var n=t.pendingProps,r=n.revealOrder,s=n.tail;if(vt(e,t,n.children,a),n=nt.current,(n&2)!==0)n=n&1|2,t.flags|=128;else{if(e!==null&&(e.flags&128)!==0)e:for(e=t.child;e!==null;){if(e.tag===13)e.memoizedState!==null&&Cg(e,a,t);else if(e.tag===19)Cg(e,a,t);else if(e.child!==null){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;e.sibling===null;){if(e.return===null||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}n&=1}switch(Oe(nt,n),r){case"forwards":for(a=t.child,r=null;a!==null;)e=a.alternate,e!==null&&ku(e)===null&&(r=a),a=a.sibling;a=r,a===null?(r=t.child,t.child=null):(r=a.sibling,a.sibling=null),sm(t,!1,r,a,s);break;case"backwards":for(a=null,r=t.child,t.child=null;r!==null;){if(e=r.alternate,e!==null&&ku(e)===null){t.child=r;break}e=r.sibling,r.sibling=a,a=r,r=e}sm(t,!0,a,null,s);break;case"together":sm(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function bn(e,t,a){if(e!==null&&(t.dependencies=e.dependencies),rr|=t.lanes,(a&t.childLanes)===0)if(e!==null){if(Ao(e,t,a,!1),(a&t.childLanes)===0)return null}else return null;if(e!==null&&t.child!==e.child)throw Error(U(153));if(t.child!==null){for(e=t.child,a=hn(e,e.pendingProps),t.child=a,a.return=t;e.sibling!==null;)e=e.sibling,a=a.sibling=hn(e,e.pendingProps),a.return=t;a.sibling=null}return t.child}function Hf(e,t){return(e.lanes&t)!==0?!0:(e=e.dependencies,!!(e!==null&&$u(e)))}function tE(e,t,a){switch(t.tag){case 3:fu(t,t.stateNode.containerInfo),Bn(t,at,e.memoizedState.cache),To();break;case 27:case 5:wm(t);break;case 4:fu(t,t.stateNode.containerInfo);break;case 10:Bn(t,t.type,t.memoizedProps.value);break;case 13:var n=t.memoizedState;if(n!==null)return n.dehydrated!==null?(zn(t),t.flags|=128,null):(a&t.child.childLanes)!==0?jb(e,t,a):(zn(t),e=bn(e,t,a),e!==null?e.sibling:null);zn(t);break;case 19:var r=(e.flags&128)!==0;if(n=(a&t.childLanes)!==0,n||(Ao(e,t,a,!1),n=(a&t.childLanes)!==0),r){if(n)return Fb(e,t,a);t.flags|=128}if(r=t.memoizedState,r!==null&&(r.rendering=null,r.tail=null,r.lastEffect=null),Oe(nt,nt.current),n)break;return null;case 22:case 23:return t.lanes=0,Ub(e,t,a);case 24:Bn(t,at,e.memoizedState.cache)}return bn(e,t,a)}function Bb(e,t,a){if(e!==null)if(e.memoizedProps!==t.pendingProps)dt=!0;else{if(!Hf(e,a)&&(t.flags&128)===0)return dt=!1,tE(e,t,a);dt=(e.flags&131072)!==0}else dt=!1,ve&&(t.flags&1048576)!==0&&qy(t,xu,t.index);switch(t.lanes=0,t.tag){case 16:e:{e=t.pendingProps;var n=t.elementType,r=n._init;if(n=r(n._payload),t.type=n,typeof n=="function")kf(n)?(e=Dr(n,e),t.tag=1,t=kg(null,t,n,e,a)):(t.tag=0,t=Qm(null,t,n,e,a));else{if(n!=null){if(r=n.$$typeof,r===mf){t.tag=11,t=wg(null,t,n,e,a);break e}else if(r===ff){t.tag=14,t=Sg(null,t,n,e,a);break e}}throw t=xm(n)||n,Error(U(306,t,""))}}return t;case 0:return Qm(e,t,t.type,t.pendingProps,a);case 1:return n=t.type,r=Dr(n,t.pendingProps),kg(e,t,n,r,a);case 3:e:{if(fu(t,t.stateNode.containerInfo),e===null)throw Error(U(387));n=t.pendingProps;var s=t.memoizedState;r=s.element,jm(e,t),ao(t,n,null,a);var i=t.memoizedState;if(n=i.cache,Bn(t,at,n),n!==s.cache&&Lm(t,[at],a,!0),to(),n=i.element,s.isDehydrated)if(s={element:n,isDehydrated:!1,cache:i.cache},t.updateQueue.baseState=s,t.memoizedState=s,t.flags&256){t=Rg(e,t,n,a);break e}else if(n!==r){r=va(Error(U(424)),t),po(r),t=Rg(e,t,n,a);break e}else{switch(e=t.stateNode.containerInfo,e.nodeType){case 9:e=e.body;break;default:e=e.nodeName==="HTML"?e.ownerDocument.body:e}for(ze=Ca(e.firstChild),Ct=t,ve=!0,_r=null,za=!0,a=Cb(t,null,n,a),t.child=a;a;)a.flags=a.flags&-3|4096,a=a.sibling}else{if(To(),n===r){t=bn(e,t,a);break e}vt(e,t,n,a)}t=t.child}return t;case 26:return ou(e,t),e===null?(a=Vg(t.type,null,t.pendingProps,null))?t.memoizedState=a:ve||(a=t.type,e=t.pendingProps,n=Pu(Gn.current).createElement(a),n[$t]=t,n[jt]=e,yt(n,a,e),ct(n),t.stateNode=n):t.memoizedState=Vg(t.type,e.memoizedProps,t.pendingProps,e.memoizedState),null;case 27:return wm(t),e===null&&ve&&(n=t.stateNode=k0(t.type,t.pendingProps,Gn.current),Ct=t,za=!0,r=ze,ir(t.type)?(lf=r,ze=Ca(n.firstChild)):ze=r),vt(e,t,t.pendingProps.children,a),ou(e,t),e===null&&(t.flags|=4194304),t.child;case 5:return e===null&&ve&&((r=n=ze)&&(n=RE(n,t.type,t.pendingProps,za),n!==null?(t.stateNode=n,Ct=t,ze=Ca(n.firstChild),za=!1,r=!0):r=!1),r||Er(t)),wm(t),r=t.type,s=t.pendingProps,i=e!==null?e.memoizedProps:null,n=s.children,rf(r,s)?n=null:i!==null&&rf(r,i)&&(t.flags|=32),t.memoizedState!==null&&(r=Of(e,t,VC,null,null,a),xo._currentValue=r),ou(e,t),vt(e,t,n,a),t.child;case 6:return e===null&&ve&&((e=a=ze)&&(a=CE(a,t.pendingProps,za),a!==null?(t.stateNode=a,Ct=t,ze=null,e=!0):e=!1),e||Er(t)),null;case 13:return jb(e,t,a);case 4:return fu(t,t.stateNode.containerInfo),n=t.pendingProps,e===null?t.child=Ps(t,null,n,a):vt(e,t,n,a),t.child;case 11:return wg(e,t,t.type,t.pendingProps,a);case 7:return vt(e,t,t.pendingProps,a),t.child;case 8:return vt(e,t,t.pendingProps.children,a),t.child;case 12:return vt(e,t,t.pendingProps.children,a),t.child;case 10:return n=t.pendingProps,Bn(t,t.type,n.value),vt(e,t,n.children,a),t.child;case 9:return r=t.type._context,n=t.pendingProps.children,Tr(t),r=wt(r),n=n(r),t.flags|=1,vt(e,t,n,a),t.child;case 14:return Sg(e,t,t.type,t.pendingProps,a);case 15:return Pb(e,t,t.type,t.pendingProps,a);case 19:return Fb(e,t,a);case 31:return n=t.pendingProps,a=t.mode,n={mode:n.mode,children:n.children},e===null?(a=Eu(n,a),a.ref=t.ref,t.child=a,a.return=t,t=a):(a=hn(e.child,n),a.ref=t.ref,t.child=a,a.return=t,t=a),t;case 22:return Ub(e,t,a);case 24:return Tr(t),n=wt(at),e===null?(r=Tf(),r===null&&(r=Re,s=Ef(),r.pooledCache=s,s.refCount++,s!==null&&(r.pooledCacheLanes|=a),r=s),t.memoizedState={parent:n,cache:r},Af(t),Bn(t,at,r)):((e.lanes&a)!==0&&(jm(e,t),ao(t,null,null,a),to()),r=e.memoizedState,s=t.memoizedState,r.parent!==n?(r={parent:n,cache:n},t.memoizedState=r,t.lanes===0&&(t.memoizedState=t.updateQueue.baseState=r),Bn(t,at,n)):(n=s.cache,Bn(t,at,n),n!==r.cache&&Lm(t,[at],a,!0))),vt(e,t,t.pendingProps.children,a),t.child;case 29:throw t.pendingProps}throw Error(U(156,t.tag))}function on(e){e.flags|=4}function Eg(e,t){if(t.type!=="stylesheet"||(t.state.loading&4)!==0)e.flags&=-16777217;else if(e.flags|=16777216,!E0(t)){if(t=ya.current,t!==null&&((fe&4194048)===fe?Ha!==null:(fe&62914560)!==fe&&(fe&536870912)===0||t!==Ha))throw Wi=Um,Hy;e.flags|=8192}}function Vl(e,t){t!==null&&(e.flags|=4),e.flags&16384&&(t=e.tag!==22?my():536870912,e.lanes|=t,Us|=t)}function zi(e,t){if(!ve)switch(e.tailMode){case"hidden":t=e.tail;for(var a=null;t!==null;)t.alternate!==null&&(a=t),t=t.sibling;a===null?e.tail=null:a.sibling=null;break;case"collapsed":a=e.tail;for(var n=null;a!==null;)a.alternate!==null&&(n=a),a=a.sibling;n===null?t||e.tail===null?e.tail=null:e.tail.sibling=null:n.sibling=null}}function je(e){var t=e.alternate!==null&&e.alternate.child===e.child,a=0,n=0;if(t)for(var r=e.child;r!==null;)a|=r.lanes|r.childLanes,n|=r.subtreeFlags&65011712,n|=r.flags&65011712,r.return=e,r=r.sibling;else for(r=e.child;r!==null;)a|=r.lanes|r.childLanes,n|=r.subtreeFlags,n|=r.flags,r.return=e,r=r.sibling;return e.subtreeFlags|=n,e.childLanes=a,t}function aE(e,t,a){var n=t.pendingProps;switch(Cf(t),t.tag){case 31:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return je(t),null;case 1:return je(t),null;case 3:return a=t.stateNode,n=null,e!==null&&(n=e.memoizedState.cache),t.memoizedState.cache!==n&&(t.flags|=2048),vn(at),Ts(),a.pendingContext&&(a.context=a.pendingContext,a.pendingContext=null),(e===null||e.child===null)&&(Fi(t)?on(t):e===null||e.memoizedState.isDehydrated&&(t.flags&256)===0||(t.flags|=1024,sg())),je(t),null;case 26:return a=t.memoizedState,e===null?(on(t),a!==null?(je(t),Eg(t,a)):(je(t),t.flags&=-16777217)):a?a!==e.memoizedState?(on(t),je(t),Eg(t,a)):(je(t),t.flags&=-16777217):(e.memoizedProps!==n&&on(t),je(t),t.flags&=-16777217),null;case 27:pu(t),a=Gn.current;var r=t.type;if(e!==null&&t.stateNode!=null)e.memoizedProps!==n&&on(t);else{if(!n){if(t.stateNode===null)throw Error(U(166));return je(t),null}e=Ia.current,Fi(t)?ng(t,e):(e=k0(r,n,a),t.stateNode=e,on(t))}return je(t),null;case 5:if(pu(t),a=t.type,e!==null&&t.stateNode!=null)e.memoizedProps!==n&&on(t);else{if(!n){if(t.stateNode===null)throw Error(U(166));return je(t),null}if(e=Ia.current,Fi(t))ng(t,e);else{switch(r=Pu(Gn.current),e){case 1:e=r.createElementNS("http://www.w3.org/2000/svg",a);break;case 2:e=r.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;default:switch(a){case"svg":e=r.createElementNS("http://www.w3.org/2000/svg",a);break;case"math":e=r.createElementNS("http://www.w3.org/1998/Math/MathML",a);break;case"script":e=r.createElement("div"),e.innerHTML="".to_string(), + code: CapabilityInputIssueCode::InvalidValue, + expected: Some("safe".to_string()), + received: None, + schema_path: None, + }], + }), + }; + + assert_eq!( + capability_failure_display_summary(&failure).as_deref(), + Some("input schema validation failed") + ); + } + #[test] fn capability_failure_display_summary_is_none_for_generic_placeholder() { let failure = CapabilityFailure { diff --git a/crates/ironclaw_reborn_composition/src/projection/tests/runtime_stream.rs b/crates/ironclaw_reborn_composition/src/projection/tests/runtime_stream.rs index 2f2a7820dd8..9a3df5228b4 100644 --- a/crates/ironclaw_reborn_composition/src/projection/tests/runtime_stream.rs +++ b/crates/ironclaw_reborn_composition/src/projection/tests/runtime_stream.rs @@ -2,6 +2,7 @@ use super::*; use ironclaw_first_party_extension_ports::{ SkillActivationMode, SkillActivationObservedEvent, SkillActivationRequest, }; +use ironclaw_host_api::INPUT_ENCODE_HUMAN_SUMMARY; use ironclaw_product_adapters::{ PROJECTION_SKILL_ACTIVATION_MAX_ITEMS, PROJECTION_SKILL_FEEDBACK_MAX_BYTES, PROJECTION_SKILL_NAME_MAX_BYTES, ProductWorkSummaryPhase, @@ -88,7 +89,7 @@ async fn runtime_capability_activity_failure_carries_error_detail() { &invocation_id.to_string(), invocation_id, &capability_id, - "the tool input could not be encoded", + INPUT_ENCODE_HUMAN_SUMMARY, ); let payload = runtime_payload_from_candidate( @@ -126,7 +127,7 @@ async fn runtime_capability_activity_failure_carries_error_detail() { assert_eq!(activity.error_kind.as_deref(), Some("invalid_input")); assert_eq!( activity.error_detail.as_deref(), - Some("the tool input could not be encoded") + Some(INPUT_ENCODE_HUMAN_SUMMARY) ); } diff --git a/crates/ironclaw_threads/src/tool_result_reference.rs b/crates/ironclaw_threads/src/tool_result_reference.rs index be1ca970987..65fd191f898 100644 --- a/crates/ironclaw_threads/src/tool_result_reference.rs +++ b/crates/ironclaw_threads/src/tool_result_reference.rs @@ -1,4 +1,4 @@ -use ironclaw_host_api::{CapabilityId, ProviderToolName}; +use ironclaw_host_api::{CapabilityId, INPUT_ENCODE_HUMAN_SUMMARY, ProviderToolName}; use ironclaw_safety::{ validate_optional_provider_metadata_text, validate_provider_arguments, validate_provider_identity, validate_provider_token, validate_provider_tool_name, @@ -18,7 +18,6 @@ const MODEL_OBSERVATION_REPAIRS_MAX: usize = 16; const MODEL_OBSERVATION_INPUT_ISSUES_MAX: usize = 16; const MODEL_OBSERVATION_TEXT_MAX_BYTES: usize = 512; const RAW_PAYLOAD_OR_PATH_DELIMITERS: [char; 9] = ['{', '}', '[', ']', '`', '<', '>', '/', '\\']; -const TOOL_INPUT_COULD_NOT_BE_ENCODED_SUMMARY: &str = "the tool input could not be encoded"; const SENSITIVE_SUMMARY_MARKERS: [&str; 18] = [ "access token", "api key", @@ -351,7 +350,7 @@ fn validate_tool_result_safe_summary(value: String) -> Result { "tool result summary must not contain raw payload or path delimiters".to_string(), ); } - if value == TOOL_INPUT_COULD_NOT_BE_ENCODED_SUMMARY { + if value == INPUT_ENCODE_HUMAN_SUMMARY { return Ok(value); } @@ -832,8 +831,8 @@ mod tests { use ironclaw_host_api::{CapabilityId, ProviderToolName}; use super::{ - ProviderToolCallReferenceEnvelope, TOOL_INPUT_COULD_NOT_BE_ENCODED_SUMMARY, - ToolResultReferenceEnvelope, ToolResultSafeSummary, + INPUT_ENCODE_HUMAN_SUMMARY, ProviderToolCallReferenceEnvelope, ToolResultReferenceEnvelope, + ToolResultSafeSummary, }; #[test] @@ -857,9 +856,9 @@ mod tests { #[test] fn safe_summary_accepts_fixed_input_encode_summary() { - let summary = ToolResultSafeSummary::new(TOOL_INPUT_COULD_NOT_BE_ENCODED_SUMMARY) + let summary = ToolResultSafeSummary::new(INPUT_ENCODE_HUMAN_SUMMARY) .expect("fixed host-authored input encode summary is safe"); - assert_eq!(summary.as_str(), TOOL_INPUT_COULD_NOT_BE_ENCODED_SUMMARY); + assert_eq!(summary.as_str(), INPUT_ENCODE_HUMAN_SUMMARY); assert!(ToolResultSafeSummary::new("tool input contained raw payload").is_err()); } diff --git a/crates/ironclaw_turns/src/run_profile/host.rs b/crates/ironclaw_turns/src/run_profile/host.rs index 78a9f5cf7e3..b668e489cd1 100644 --- a/crates/ironclaw_turns/src/run_profile/host.rs +++ b/crates/ironclaw_turns/src/run_profile/host.rs @@ -6,8 +6,9 @@ use std::{ use async_trait::async_trait; use chrono::{DateTime, Utc}; use ironclaw_host_api::{ - ApprovalRequestId, CapabilityId, CorrelationId, ExtensionId, HostApiError, ProviderToolName, - ResourceEstimate, RuntimeCredentialAuthRequirement, RuntimeKind, ThreadId, + ApprovalRequestId, CapabilityId, CorrelationId, ExtensionId, HostApiError, + INPUT_ENCODE_HUMAN_SUMMARY, ProviderToolName, ResourceEstimate, + RuntimeCredentialAuthRequirement, RuntimeKind, ThreadId, }; use serde::{Deserialize, Deserializer, Serialize}; use thiserror::Error; @@ -143,11 +144,9 @@ fn validate_loop_safe_identifier( Ok(value) } -const TOOL_INPUT_COULD_NOT_BE_ENCODED_SUMMARY: &str = "the tool input could not be encoded"; - fn validate_loop_safe_summary(value: String) -> Result { let value = validate_bounded_loop_string(value, "loop safe summary", 512)?; - if value == TOOL_INPUT_COULD_NOT_BE_ENCODED_SUMMARY { + if value == INPUT_ENCODE_HUMAN_SUMMARY { return Ok(value); } if value.chars().any(|character| { @@ -413,16 +412,22 @@ impl LoopSafeSummary { validate_loop_safe_summary(value.into()).map(Self) } + /// Build a display-safe capability failure summary, replacing unsafe input + /// with a fixed redaction marker. pub fn capability_failure_summary(value: impl Into) -> Self { Self::new(value).unwrap_or_else(|_| Self::tool_failure_details_redacted()) } + /// Fixed summary used when a capability failure detail was intentionally + /// redacted before reaching a user-visible or model-visible boundary. pub fn tool_failure_details_redacted() -> Self { Self("the tool failure details were redacted".to_string()) } + /// Fixed fallback for input-encoding failures when no narrower safe detail + /// is available. pub fn tool_input_could_not_be_encoded() -> Self { - Self(TOOL_INPUT_COULD_NOT_BE_ENCODED_SUMMARY.to_string()) + Self(INPUT_ENCODE_HUMAN_SUMMARY.to_string()) } pub fn model_gateway_failed() -> Self { @@ -2533,9 +2538,9 @@ mod tests { #[test] fn loop_safe_summary_accepts_fixed_input_encode_summary() { - let summary = LoopSafeSummary::new(TOOL_INPUT_COULD_NOT_BE_ENCODED_SUMMARY) + let summary = LoopSafeSummary::new(INPUT_ENCODE_HUMAN_SUMMARY) .expect("fixed host-authored input encode summary is safe"); - assert_eq!(summary.as_str(), TOOL_INPUT_COULD_NOT_BE_ENCODED_SUMMARY); + assert_eq!(summary.as_str(), INPUT_ENCODE_HUMAN_SUMMARY); let raw_input_summary = LoopSafeSummary::new("tool input contained raw payload"); assert!( diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/history-messages.js b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/history-messages.js index 9cc7420f8a6..9723e720977 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/history-messages.js +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/history-messages.js @@ -186,6 +186,17 @@ export function toolCardFromPreview(preview) { const failed = preview.status === "failed" || preview.status === "killed"; const errorKind = preview.error_kind || null; const activityOrder = numericActivityOrder(preview.activity_order); + const hasFailureDetail = !!( + preview.output_summary || + preview.output_preview || + preview.result_ref + ); + const toolError = + preview.output_summary || + preview.output_preview || + toolErrorText(errorKind) || + preview.result_ref || + null; return { invocationId: preview.invocation_id, callId: preview.invocation_id, @@ -204,13 +215,8 @@ export function toolCardFromPreview(preview) { // detail like invalid-input field issues) over the bare error kind, so the // Error tab shows "Invalid input: field — missing required field" rather // than just "invalid_input". Falls back to the kind when no summary exists. - toolError: failed - ? preview.output_summary || - preview.output_preview || - toolErrorText(errorKind) || - preview.result_ref || - null - : null, + toolError: failed ? toolError : null, + toolErrorIsBareKind: failed && !!errorKind && !hasFailureDetail, toolErrorKind: errorKind, toolDurationMs: null, updatedAt: preview.updated_at || null, @@ -233,6 +239,7 @@ export function toolCardFromPreview(preview) { export function toolCardFromActivity(activity) { const activityOrder = numericActivityOrder(activity.activity_order); const errorKind = activity.error_kind || null; + const hasErrorDetail = !!activity.error_detail; return { invocationId: activity.invocation_id, callId: activity.invocation_id, @@ -246,6 +253,7 @@ export function toolCardFromActivity(activity) { // over the bare error kind, so a live failed tool card shows the real // reason. Falls back to the kind when no detail is present. toolError: activity.error_detail || toolErrorText(errorKind), + toolErrorIsBareKind: !!errorKind && !hasErrorDetail, toolErrorKind: errorKind, toolDurationMs: null, updatedAt: activity.updated_at || null, diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/tool-activity-state.js b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/tool-activity-state.js index fe1ba2b9462..6a7248b2d7f 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/tool-activity-state.js +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/tool-activity-state.js @@ -87,6 +87,7 @@ function toolCardFromGate(gate, overrides = {}) { toolResultPreview: null, toolError: overrides.toolError || null, toolErrorKind: overrides.toolErrorKind || null, + toolErrorIsBareKind: !!overrides.toolErrorKind && !overrides.toolError, toolDurationMs: null, updatedAt: overrides.updatedAt || new Date().toISOString(), resultRef: null, @@ -149,6 +150,7 @@ function mergeToolActivity(current, incoming) { toolStatus: keepCurrentTerminal ? current.toolStatus : incoming.toolStatus, toolError: mergedToolError(current, incoming), toolErrorKind: incoming.toolErrorKind || current.toolErrorKind || null, + toolErrorIsBareKind: mergedToolErrorIsBareKind(current, incoming), updatedAt: keepCurrentTerminal ? current.updatedAt || incoming.updatedAt : incoming.updatedAt || current.updatedAt, @@ -180,14 +182,22 @@ function mergedToolError(current, incoming) { const incomingError = incoming.toolError || null; const currentError = current.toolError || null; if (!incomingError) return currentError; - const incomingIsBareKind = - !!incoming.toolErrorKind && incomingError === incoming.toolErrorKind; - if (incomingIsBareKind && currentError && currentError !== incomingError) { + if (incoming.toolErrorIsBareKind && currentError && currentError !== incomingError) { return currentError; } return incomingError; } +function mergedToolErrorIsBareKind(current, incoming) { + const incomingError = incoming.toolError || null; + const currentError = current.toolError || null; + if (incoming.toolErrorIsBareKind && currentError && currentError !== incomingError) { + return !!current.toolErrorIsBareKind; + } + if (incomingError) return !!incoming.toolErrorIsBareKind; + return !!current.toolErrorIsBareKind; +} + function mergedActivityOrder(current, incoming) { return Number.isFinite(incoming.activityOrder) ? incoming.activityOrder @@ -220,5 +230,6 @@ function normalizeToolCard(card) { return { ...card, toolName: normalizedName || card.toolName || "tool", + toolErrorIsBareKind: Boolean(card.toolErrorIsBareKind), }; } diff --git a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/tool-activity-state.test.mjs b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/tool-activity-state.test.mjs index 0dbeacd63f3..061ddcf55dc 100644 --- a/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/tool-activity-state.test.mjs +++ b/crates/ironclaw_webui_v2_static/static/js/pages/chat/lib/tool-activity-state.test.mjs @@ -419,6 +419,45 @@ test("tool activity state keeps preview failure detail when a bare-kind activity assert.equal(messages[0].toolErrorKind, "invalid_input"); }); +test("tool activity state uses bare-kind flag instead of string equality", () => { + const stateRef = { current: createToolActivityState() }; + let messages = []; + const setMessages = (updater) => { + messages = typeof updater === "function" ? updater(messages) : updater; + }; + + upsertToolActivityMessage( + setMessages, + { + invocationId: "invocation-json", + capabilityId: "builtin.json", + toolName: "json", + toolStatus: "error", + toolError: "the tool input could not be encoded", + toolErrorKind: "invalid_input", + toolErrorIsBareKind: false, + }, + stateRef, + ); + + upsertToolActivityMessage( + setMessages, + { + invocationId: "invocation-json", + capabilityId: "builtin.json", + toolName: "json", + toolStatus: "error", + toolError: "invalid_input", + toolErrorKind: "invalidInput", + toolErrorIsBareKind: true, + }, + stateRef, + ); + + assert.equal(messages.length, 1); + assert.equal(messages[0].toolError, "the tool input could not be encoded"); +}); + test("tool activity state applies durable projection order to live activity", () => { const runId = "run-projection-order"; const stateRef = { current: createToolActivityState() }; From f3dbfa2c6b2e0bff695f7be1ef5672a446f623bc Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:17:25 +0800 Subject: [PATCH 24/27] fix: redact sensitive capability issue fields --- .../src/capability_port.rs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/crates/ironclaw_loop_support/src/capability_port.rs b/crates/ironclaw_loop_support/src/capability_port.rs index d1cac3d88b0..f14191935a6 100644 --- a/crates/ironclaw_loop_support/src/capability_port.rs +++ b/crates/ironclaw_loop_support/src/capability_port.rs @@ -381,6 +381,7 @@ fn capability_input_issue_display_text(value: &str) -> Option { '{' | '}' | '[' | ']' | '`' | '<' | '>' | '/' | '\\' ) }) + || contains_capability_input_issue_sensitive_marker(trimmed) { return None; } @@ -393,6 +394,51 @@ fn capability_input_issue_display_text(value: &str) -> Option { ) } +fn contains_capability_input_issue_sensitive_marker(value: &str) -> bool { + let lower = value.to_ascii_lowercase(); + for forbidden in [ + "access token", + "access_token", + "api key", + "api_key", + "apikey", + "authorization", + "bearer", + "password", + "passwd", + "secret", + "tool input", + "tool_input", + ] { + if lower.contains(forbidden) { + return true; + } + } + lower + .split(|character: char| { + !character.is_ascii_alphanumeric() && !matches!(character, '-' | '_' | '.') + }) + .any(|token| { + [ + "sk-", + "sk-ant-", + "ghp_", + "github_pat_", + "gho_", + "ghu_", + "ghs_", + "ghr_", + "glpat-", + "gcp-", + "ya29.", + "aiza", + ] + .iter() + .any(|prefix| token.starts_with(prefix)) + || (token.len() >= 16 && (token.starts_with("akia") || token.starts_with("asia"))) + }) +} + pub struct CapabilityResultWrite<'a> { pub run_context: &'a LoopRunContext, pub input_ref: &'a CapabilityInputRef, @@ -3419,6 +3465,28 @@ mod tests { ); } + #[test] + fn capability_failure_display_summary_skips_sensitive_input_issue_fields() { + let failure = CapabilityFailure { + error_kind: CapabilityFailureKind::InvalidInput, + safe_summary: "input schema validation failed".to_string(), + detail: Some(CapabilityFailureDetail::InvalidInput { + issues: vec![CapabilityInputIssue { + path: "secret_api_key".to_string(), + code: CapabilityInputIssueCode::TypeMismatch, + expected: Some("password string".to_string()), + received: None, + schema_path: None, + }], + }), + }; + + assert_eq!( + capability_failure_display_summary(&failure).as_deref(), + Some("input schema validation failed") + ); + } + #[test] fn capability_failure_display_summary_is_none_for_generic_placeholder() { let failure = CapabilityFailure { From 64af484efdb57d97dac6fb2fe794399de3259f84 Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:27:01 +0800 Subject: [PATCH 25/27] fix: document projected capability error details --- crates/ironclaw_event_projections/CLAUDE.md | 7 ++++ crates/ironclaw_event_projections/src/lib.rs | 6 ++++ .../src/runtime_projection.rs | 19 ++++++++-- .../tests/replay_projection_contract.rs | 36 +++++++++++++++++++ crates/ironclaw_events/src/runtime_event.rs | 6 +++- 5 files changed, 71 insertions(+), 3 deletions(-) diff --git a/crates/ironclaw_event_projections/CLAUDE.md b/crates/ironclaw_event_projections/CLAUDE.md index 868d29739a2..ae3971ac2a5 100644 --- a/crates/ironclaw_event_projections/CLAUDE.md +++ b/crates/ironclaw_event_projections/CLAUDE.md @@ -10,6 +10,13 @@ This crate is above `ironclaw_events` and below product adapters. Keep it: - non-mutating: projection failures must not mutate durable logs or kernel state; - backend-independent: do not depend on JSONL/PostgreSQL/libSQL adapter crates directly. +The one allowed product-display exception is `CapabilityActivityProjection.error_detail`: +it may carry only the sanitized `RuntimeEvent.error_summary` value after replay +re-runs `ironclaw_events::sanitize_error_summary`. This field is still not a +general backend-detail channel; raw tool input/output, host paths, secrets, and +provider messages that fail the runtime-event sanitizer must remain collapsed to +the fixed safe summaries. + Current slices: - replay-derived `ThreadTimeline` and `RunStatusProjection` over `DurableEventLog`; diff --git a/crates/ironclaw_event_projections/src/lib.rs b/crates/ironclaw_event_projections/src/lib.rs index 77c66436ffd..a375c397c04 100644 --- a/crates/ironclaw_event_projections/src/lib.rs +++ b/crates/ironclaw_event_projections/src/lib.rs @@ -299,6 +299,12 @@ pub struct CapabilityActivityProjection { pub process_id: Option, pub output_bytes: Option, pub error_kind: Option, + /// Sanitized display detail derived from `RuntimeEvent.error_summary`. + /// + /// This intentionally uses the product-facing `error_detail` wire name: + /// consumers render it as optional per-tool failure detail, not as the + /// durable event's source summary field. Projection replay re-runs the + /// runtime-event sanitizer before populating this field. #[serde(default, skip_serializing_if = "Option::is_none")] pub error_detail: Option, #[serde(default)] diff --git a/crates/ironclaw_event_projections/src/runtime_projection.rs b/crates/ironclaw_event_projections/src/runtime_projection.rs index d5614ddb3e9..939c4de8e18 100644 --- a/crates/ironclaw_event_projections/src/runtime_projection.rs +++ b/crates/ironclaw_event_projections/src/runtime_projection.rs @@ -272,7 +272,7 @@ fn apply_capability_activity_event( return; }; let sanitized_error_kind = event.error_kind.clone().map(sanitize_error_kind); - let sanitized_error_summary = event.error_summary.clone().and_then(sanitize_error_summary); + let sanitized_error_summary = projection_error_detail(event); let activity = activities .entry(event.scope.invocation_id) .or_insert_with(|| capability_activity_projection_for_entry(entry, status)); @@ -327,13 +327,28 @@ fn capability_activity_projection_for_entry( process_id: event.process_id, output_bytes: event.output_bytes, error_kind: event.error_kind.clone().map(sanitize_error_kind), - error_detail: event.error_summary.clone().and_then(sanitize_error_summary), + error_detail: projection_error_detail(event), first_cursor: entry.cursor, last_cursor: entry.cursor, updated_at: event.timestamp, } } +fn projection_error_detail(event: &RuntimeEvent) -> Option { + // Product-facing projections are a second channel boundary after the + // durable runtime log. Re-run the sanitizer here so direct in-memory + // construction, legacy replay payloads, or future event producers cannot + // surface backend-authored detail strings unless they satisfy the + // redacted display-summary contract. The projection field is named + // `error_detail` intentionally: product/WebUI consumers render it as + // optional per-tool failure detail, while the durable event keeps the + // source field name `error_summary`. + event + .error_summary + .as_deref() + .and_then(sanitize_error_summary) +} + fn capability_activity_status_for_event( kind: RuntimeEventKind, current_status: Option, diff --git a/crates/ironclaw_event_projections/tests/replay_projection_contract.rs b/crates/ironclaw_event_projections/tests/replay_projection_contract.rs index 06bead6848a..3d797ac8d14 100644 --- a/crates/ironclaw_event_projections/tests/replay_projection_contract.rs +++ b/crates/ironclaw_event_projections/tests/replay_projection_contract.rs @@ -1411,6 +1411,42 @@ async fn replay_projection_capability_activity_stays_metadata_only() { ); } +#[tokio::test] +async fn replay_projection_preserves_safe_capability_error_detail() { + let log = Arc::new(InMemoryDurableEventLog::new()); + let service = ReplayEventProjectionService::new(Arc::clone(&log)); + let scope = scope_for_thread(ThreadId::new("thread-tool-activity-detail").unwrap()); + let detail = "json parsing failed: unexpected comma at line 4"; + + log.append( + RuntimeEvent::capability_activity_failed( + scope.clone(), + CapabilityId::new("builtin.json").unwrap(), + None, + None, + "invalid_input", + ) + .with_error_summary(detail), + ) + .await + .unwrap(); + + let snapshot = service + .snapshot(ProjectionRequest { + scope: ProjectionScope::from_resource_scope(&scope), + after: None, + limit: 16, + }) + .await + .unwrap(); + + assert_eq!(snapshot.capability_activities.len(), 1); + let activity = &snapshot.capability_activities[0]; + assert_eq!(activity.status, CapabilityActivityStatus::Failed); + assert_eq!(activity.error_kind.as_deref(), Some("invalid_input")); + assert_eq!(activity.error_detail.as_deref(), Some(detail)); +} + #[tokio::test] async fn replay_projection_keeps_model_completed_running_until_reply_finalized() { let log = Arc::new(InMemoryDurableEventLog::new()); diff --git a/crates/ironclaw_events/src/runtime_event.rs b/crates/ironclaw_events/src/runtime_event.rs index 033b6c898d1..40a0f1f959f 100644 --- a/crates/ironclaw_events/src/runtime_event.rs +++ b/crates/ironclaw_events/src/runtime_event.rs @@ -1,6 +1,7 @@ use chrono::Utc; use ironclaw_host_api::{ - CapabilityId, ExtensionId, InvocationId, ProcessId, ResourceScope, RuntimeKind, Timestamp, + CapabilityId, ExtensionId, INPUT_ENCODE_HUMAN_SUMMARY, InvocationId, ProcessId, ResourceScope, + RuntimeKind, Timestamp, }; use serde::{Deserialize, Serialize}; use uuid::Uuid; @@ -833,6 +834,9 @@ fn sanitize_error_summary_str(value: &str) -> Option { if trimmed.is_empty() { return None; } + if trimmed == INPUT_ENCODE_HUMAN_SUMMARY { + return Some(INPUT_ENCODE_HUMAN_SUMMARY.to_string()); + } let lower = trimmed.to_ascii_lowercase(); if is_workspace_file_error_summary(trimmed, &lower) { return Some(WORKSPACE_FILE_ERROR_SUMMARY.to_string()); From 933e1681d5a90af48ee5d56eac8bd1cffa3c2a1a Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:30:20 +0800 Subject: [PATCH 26/27] fix: catch sensitive issue marker variants --- .../src/capability_port.rs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/crates/ironclaw_loop_support/src/capability_port.rs b/crates/ironclaw_loop_support/src/capability_port.rs index f14191935a6..45645207b71 100644 --- a/crates/ironclaw_loop_support/src/capability_port.rs +++ b/crates/ironclaw_loop_support/src/capability_port.rs @@ -396,6 +396,25 @@ fn capability_input_issue_display_text(value: &str) -> Option { fn contains_capability_input_issue_sensitive_marker(value: &str) -> bool { let lower = value.to_ascii_lowercase(); + let normalized = lower + .chars() + .filter(|character| character.is_ascii_alphanumeric()) + .collect::(); + for forbidden in [ + "accesstoken", + "apikey", + "authtoken", + "authorization", + "bearer", + "password", + "passwd", + "secret", + "toolinput", + ] { + if normalized.contains(forbidden) { + return true; + } + } for forbidden in [ "access token", "access_token", @@ -3487,6 +3506,19 @@ mod tests { ); } + #[test] + fn capability_input_issue_display_text_rejects_sensitive_marker_variants() { + for value in [ + "x-api-key", + "accessToken", + "auth_token", + "toolInput", + "secret_api_key", + ] { + assert_eq!(capability_input_issue_display_text(value), None, "{value}"); + } + } + #[test] fn capability_failure_display_summary_is_none_for_generic_placeholder() { let failure = CapabilityFailure { From 4915a1696e8a13430e057fe40e8ad6513e9a3e25 Mon Sep 17 00:00:00 2001 From: italic-jinxin <106428113+italic-jinxin@users.noreply.github.com> Date: Wed, 1 Jul 2026 17:41:48 +0800 Subject: [PATCH 27/27] docs: clarify projected error detail boundary --- crates/ironclaw_event_projections/CLAUDE.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/crates/ironclaw_event_projections/CLAUDE.md b/crates/ironclaw_event_projections/CLAUDE.md index ae3971ac2a5..1ce1ce986f5 100644 --- a/crates/ironclaw_event_projections/CLAUDE.md +++ b/crates/ironclaw_event_projections/CLAUDE.md @@ -17,6 +17,18 @@ general backend-detail channel; raw tool input/output, host paths, secrets, and provider messages that fail the runtime-event sanitizer must remain collapsed to the fixed safe summaries. +Sanitization ownership for this exception is: + +- runtime producers should pass only host-authored summaries into + `RuntimeEvent::with_error_summary`; +- `ironclaw_events` owns durable-log sanitization at construction, + serialization, and deserialization boundaries; +- `ironclaw_event_projections` must re-run the same sanitizer when deriving + `error_detail`, because product projections are a separate user-facing + boundary; +- product workflow and WebUI layers must treat `error_detail` as already + display-bounded and must not recover or append raw backend detail. + Current slices: - replay-derived `ThreadTimeline` and `RunStatusProjection` over `DurableEventLog`;