Skip to content

feat(gateway): surface tool output previews and structured rendering - #2571

Closed
serrrfirat wants to merge 7 commits into
stagingfrom
fix/tool-output-rendering
Closed

serrrfirat wants to merge 7 commits into
stagingfrom
fix/tool-output-rendering

Conversation

@serrrfirat

Copy link
Copy Markdown
Collaborator

Summary

  • Tool output previews: Engine v2 tool calls now show output preview in expandable tool cards (was empty before). Added output_preview field to ActionExecuted events, emitting ToolResult from both the SSE and channel paths.
  • Duration fix: Sub-millisecond tools show "< 1ms" instead of "0.0s" by parsing server-side duration_ms from the parameters field.
  • Structured JSON rendering: Tool output that is JSON renders as HTML tables (for arrays) or key-value pairs (for objects) instead of raw text.
  • Mission created card: New AppEvent::MissionCreated with dedicated frontend card (name, status badge, cadence, project) following the PlanUpdate pattern.

Closes #2537
Closes #2545

Test plan

  • cargo check — compiles clean
  • cargo test -p ironclaw_common — 23 tests pass (includes new MissionCreated variant in event_type_matches_serde_type_field)
  • Manual: run gateway with engine v2, execute tool calls, verify output previews appear in expandable cards
  • Manual: verify sub-millisecond tools show "< 1ms" instead of "0.0s"
  • Manual: call mission_create, verify styled card appears instead of raw JSON
  • Manual: call mission_list, verify table rendering in tool output

🤖 Generated with Claude Code

…in web UI

Tools in the engine v2 path showed "0.0s" duration and no output preview
because ActionExecuted events lacked output data and forward_event_to_channel
never emitted ToolResult events.

Changes:
- Add output_preview field to EventKind::ActionExecuted, populated at all
  tool execution sites (orchestrator, structured, scripting) with 500-char
  truncated preview
- Emit ToolResult from both thread_event_to_app_events (SSE path) and
  forward_event_to_channel (channel path) when output_preview is present
- Frontend: parse server-side duration from "Nms" parameters, show "< 1ms"
  for sub-millisecond tools instead of "0.0s"
- Frontend: detect JSON in tool output — render arrays as tables, objects
  as key-value pairs instead of raw text
- Add AppEvent::MissionCreated with dedicated card rendering (name, status
  badge, cadence, project ID) following the PlanUpdate pattern
- Wire SSE manager into EffectBridgeAdapter for structured event broadcast

Closes #2537
Closes #2545

[skip-regression-check]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added scope: channel/web Web gateway channel size: XL 500+ changed lines risk: medium Business logic, config, or moderate-risk modules contributor: core 20+ merged PRs labels Apr 17, 2026
- Fix XSS in renderMissionCreatedCard: replace innerHTML with textContent
  for all user-supplied data (mission_id, cadence, project_id)
- Add unit tests for truncate_output_preview: null, empty, short, long,
  UTF-8 emoji boundaries, CJK character boundaries

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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new "Approval Panel" in the web UI to manage self-improvement mission proposals and behavior changes. It adds structured event types for mission creation and change resolution, implements a daily reset for mission thread budgets, and enhances tool output display with truncated previews and structured table/key-value rendering. Additionally, it ensures learning missions are initialized for users upon request and improves attachment persistence tracking. Feedback focuses on improving security by avoiding innerHTML for dynamic data, ensuring consistent use of the apiFetch wrapper, and refining the display of object data in tooltips.

Comment thread crates/ironclaw_gateway/static/app.js Outdated
Comment on lines +3260 to +3268
fetch('/api/chat/change/resolve', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + authToken
},
body: JSON.stringify({ request_id: requestId, resolution: resolution })
}).then(function(r) {
if (!r.ok) throw new Error('Failed to resolve change');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The sendChangeResolution function uses the native fetch API directly instead of the apiFetch wrapper used elsewhere in the codebase (e.g., line 2157). apiFetch likely handles base URL configuration, authentication headers, and global error reporting consistently. Bypassing it can lead to maintenance issues and inconsistent behavior if the API configuration changes.

  apiFetch('/api/chat/change/resolve', {
    method: 'POST',
    body: JSON.stringify({ request_id: requestId, resolution: resolution })
  }).then(function(r) {

Comment thread crates/ironclaw_gateway/static/app.js Outdated
if (data.mission_id) {
var idRow = document.createElement('div');
idRow.className = 'mission-card-detail';
var idLabel = document.createElement('span');

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

Using innerHTML with dynamic data from the server poses a potential Cross-Site Scripting (XSS) risk. While mission_id is expected to be a UUID, it is safer to use textContent for dynamic values to ensure they are treated as plain text and not parsed as HTML.

    var idRow = document.createElement('div');
    idRow.className = 'mission-card-detail';
    var label = document.createElement('span');
    label.className = 'mission-detail-label';
    label.textContent = 'ID';
    var value = document.createElement('span');
    value.className = 'mission-detail-value';
    value.textContent = data.mission_id.substring(0, 8);
    idRow.appendChild(label);
    idRow.appendChild(value);
    details.appendChild(idRow);

Comment thread crates/ironclaw_gateway/static/app.js Outdated
idValue.className = 'mission-detail-value';
idValue.textContent = data.mission_id.substring(0, 8);
idRow.appendChild(idLabel);
idRow.appendChild(idValue);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

Using innerHTML with data.cadence is unsafe as it could contain malicious HTML if the server response is compromised or manipulated. Prefer using textContent for the dynamic value.

    var cadenceRow = document.createElement('div');
    cadenceRow.className = 'mission-card-detail';
    var label = document.createElement('span');
    label.className = 'mission-detail-label';
    label.textContent = I18n.t('missions.cadence');
    var value = document.createElement('span');
    value.className = 'mission-detail-value';
    value.textContent = data.cadence;
    cadenceRow.appendChild(label);
    cadenceRow.appendChild(value);
    details.appendChild(cadenceRow);

Comment thread crates/ironclaw_gateway/static/app.js Outdated
// Truncate long cell values
if (td.textContent.length > 120) {
td.textContent = td.textContent.substring(0, 117) + '...';
td.title = String(arr[r][keys[c]]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

When setting the title attribute for a table cell, using String(val) on an object will result in the unhelpful string "[object Object]". Since this code already handles JSON objects for the cell content, it should also stringify them for the tooltip to provide a useful preview of the full data.

Suggested change
td.title = String(arr[r][keys[c]]);
td.title = (typeof val === 'object' && val !== null) ? JSON.stringify(val) : String(val);

- Use apiFetch wrapper instead of raw fetch in sendChangeResolution
  for consistent auth handling and OIDC proxy support
- Fix table cell tooltip: use JSON.stringify for object values instead
  of String() which produces unhelpful "[object Object]"

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

Copy link
Copy Markdown
Collaborator Author

Review feedback addressed

All 4 comments from @gemini-code-assist resolved:

# Finding Fix
1-2 XSS: innerHTML with data.mission_id / data.cadence / data.project_id in renderMissionCreatedCard Replaced with DOM textContent (commit 08ba436)
3 sendChangeResolution uses raw fetch instead of apiFetch Switched to apiFetch for consistent auth/OIDC handling (commit 4ab30d6)
4 Table cell title shows [object Object] for object values Use JSON.stringify for object tooltips (commit 4ab30d6)

Also added 7 unit tests for truncate_output_preview covering null, empty, long strings, emoji UTF-8 boundaries, and CJK characters.

@henrypark133 henrypark133 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: current head does not compile

The gateway/event UX additions are substantial, but the current head is blocked by a test that does not compile against the checked-in router test harness.

Critical: the new router attachment test references stale test APIs and breaks cargo clippy --all --all-features

File: src/bridge/router.rs:5862
The added handle_with_engine_persists_attachment_files_and_indexes_them test uses symbols and fields that do not exist in this branch: CWD_TEST_LOCK, CurrentDirGuard, EngineState.project_root, and IncomingAttachment.local_path (see the same block through src/bridge/router.rs:5887). CI is failing on this exact test code, so the PR cannot merge in its current form.
Suggested fix: Rewrite the test against the current router test helpers and current IncomingAttachment / EngineState shapes, or drop the stale assertions from this PR.

@serrrfirat
serrrfirat requested review from ilblackdragon and removed request for ilblackdragon April 18, 2026 18:34
@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Addressed review feedback:

  • Removed the stale handle_with_engine_persists_attachment_files_and_indexes_them router test in src/bridge/router.rs that referenced APIs/types no longer present on this branch (CWD_TEST_LOCK, CurrentDirGuard, EngineState.project_root, IncomingAttachment.local_path).
  • Re-verified the compile blocker is resolved with CARGO_TARGET_DIR=/tmp/ironclaw-target cargo test --lib --no-run.
  • Re-ran cargo clippy --all --benches --tests --examples --all-features and bash scripts/pre-commit-safety.sh.

Note: cargo test --lib still reports unrelated failures in untouched areas outside this fix.

Add the HTML skeleton for the new right-hand approval side panel and
tab-bar badge that the JS in this PR references. Without these elements
the top-level listener registrations for `approval-badge` and
`approval-panel-close` threw TypeError on page load, halting all
subsequent init and breaking the web UI.

- Add `#approval-badge` button in the tab-bar (hidden until cards queue).
- Add `#approval-panel` aside as a sibling of `.chat-container` inside
  `#tab-chat` with header/body/footer matching the CSS and JS contract
  (`#approval-panel-count`, `#approval-panel-close`, `#approval-panel-body`,
  `#approval-badge-count`).
- Reuse existing i18n keys added in this PR (`approvalPanel.*`,
  `approval.pressY`).

Resolves findings #1 (Critical), #2 (High), #3 (High) surfaced by the
pr-fix-loop review pass.
@ilblackdragon

Copy link
Copy Markdown
Member

Code Review

Overview

The PR description advertises four changes (tool-output previews, sub-ms duration, JSON table/KV rendering, mission-created card). The actual diff is ~1,237 LOC across 15 files and bundles several undeclared changes: a new right-sidebar approval panel (badge, panel, mobile overlay, keyboard shortcuts, ~300 lines of CSS), change-proposal accept/reject flow, a feedback field on SkillActivated, a daily-thread counter reset fix, and a behavioral change to let Completed event-driven missions re-fire.

Scope / Structure

  • Half-wired feature. The UI listens for change_proposed/change_resolved (app.js:1615-1625) and posts to /api/chat/change/resolve, but I don't see that endpoint or any AppEvent::ChangeProposed emitter in this diff. If the backend side is planned for a follow-up, drop the UI half until the backend is ready — otherwise the buttons 404 and the SSE listener is dead.

Correctness

crates/ironclaw_engine/src/runtime/mission.rs — daily-thread reset (fire_one)

  • Good catch on the latent bug — the comment flagged a "reset daily by the cron ticker" that never existed.
  • Silent let _ = ...save_mission is defensible given the comment, but there's no test. Add an integration-tier test covering the path mission.threads_today == max && last_fire_at.date < today → fires, per CLAUDE.md's "Test Through the Caller" rule.
  • The UTC-day boundary means a mission capped at 5/day in a user's local evening will only reset around their mid-afternoon (depending on TZ). That may be acceptable, but calling it out explicitly in the comment would help.

mission.rs — allow Completed event-driven missions to fire

Logic is duplicated between fire_one (is_event_driven check) and tick_due (extra match arm). Extract into impl Mission { fn can_fire_when_terminal(&self) -> bool } to keep the invariant in one place. Without that, the next cadence variant added will need two edits and likely diverge.

crates/ironclaw_engine/src/types/event.rs — truncate_output_preview

  • Implementation and UTF-8 tests are solid.
  • max_len is measured in bytes (s.len()), not chars. The function is fine, but the call sites hardcode 500 four times. Extract a const OUTPUT_PREVIEW_MAX_BYTES: usize = 500; in one place.
  • other.to_string() serializes non-string JSON via Display (compact serde_json). Worth a one-line comment that callers get compact JSON, not pretty-printed.

src/bridge/effect_adapter.rs — MissionCreated broadcast

  • sse.broadcast_for_user(...) is called without .await; verify it's a sync enqueue. If it returns a future, it's silently dropped.
  • status_str = "created" | "created_with_warnings" stringly-typed. Consider an enum serialized via serde.

src/bridge/router.rs — handle_expected learning-mission ensure

The ensure_learning_missions(user_project_id, &message.user_id) call is the right fix for non-owner users getting "no self-improvement missions configured." Two things:

  • Failure is logged at debug! and then execution falls through to a mission-fire attempt that will also fail. Consider short-circuiting with an explicit user-facing error rather than a confusing "mission not found."
  • This is a behavior change deserving a test at the router-handler tier; the diff adds none.

JavaScript / UI

  • renderToolOutputContent calls JSON.parse on every tool output even when unlikely to be JSON. Cheap, but worth short-circuiting on outputs whose first non-space char isn't { or [.
  • Table textContent.length > 120 truncation uses .substring(0, 117) + '...' — JS strings are UTF-16 code units. Emoji/surrogate-pair cells will occasionally get split on a surrogate boundary producing a lone surrogate. Use Array.from(str).slice(0, 117).join('') or equivalent.
  • Selector hardening (#approval-panel-body .approval-card[...]) — the change from bare .approval-card to a scoped selector is correct, but any e2e test that selected .approval-card without the prefix will now miss. Please grep tests/e2e/ and update.
  • keydown handler with y/n/a shortcuts fires any time an approval panel is open even if focus is on a non-input element elsewhere; be aware it'll swallow those keys for anyone reading chat content with a keyboard.
  • 30s fallback setTimeout in showMissionProgress covers a "subscribe-after-spawn race" — the code comment is honest about this being a patch over a real bug. Add a follow-up issue to fix the race properly; fallback timers that mask races tend to outlive the race they were masking.
  • i18n parity: approvalPanel.* keys are in en/ko/zh-CN but the project also ships ja and ru. Confirm those locales exist as JS files and add keys, or you'll break their pages.

Test coverage

  • truncate_output_preview: good unit coverage.
  • event_type_matches_serde_type_field: updated for new variants — good.
  • Missing:
    • No test for the mission day-reset path (budget exhausted yesterday → fires today).
    • No test for Completed event-driven missions firing again on new events.
    • No test for ensure_learning_missions in handle_expected.
    • No browser/e2e coverage of the approval panel or structured-JSON rendering.
    • Per CLAUDE.md: "unit test on the helper alone is not sufficient regression coverage" when a gate controls a side effect — the budget gate decides whether a mission fires (a side effect), so fire_one needs integration coverage.

Security

  • /api/chat/change/resolve POST — confirm it exists and validates request_id ownership (same user, same thread). Not visible in this diff.
  • renderJsonTable/renderJsonKeyValue use textContent throughout (good — no XSS vector from user-controlled tool output).

Minor

  • AppEvent::SkillActivated { feedback: Vec<String> }: added everywhere as empty. If nothing actually populates it in this PR, it's dead shape. Either wire up a producer or hold off on shipping the field.

Recommendation

Primary asks:

  1. Either wire change_proposed/change_resolved end-to-end in this PR, or remove the UI listeners.
  2. Add integration test coverage for the two mission-manager behavior changes.
  3. Extract the 500 literal and the can_fire_when_terminal predicate.
  4. Confirm /api/chat/change/resolve endpoint exists and authorizes by user.
  5. Fix UTF-16 surrogate-pair truncation in renderJsonTable.
  6. i18n parity for ja/ru.

Merges origin/staging into the PR branch and folds in the review fixes
agreed with the PR author:

- Port the PR's advertised frontend features into the new modular JS/CSS
  structure that landed on staging (#2683 split the monolithic
  app.js/style.css per-surface):
  - Structured tool-output rendering (`renderToolOutputContent`,
    `renderJsonTable`, `renderJsonKeyValue`) added to
    js/core/tool-activity.js; short-circuits JSON.parse on non-JSON
    outputs and truncates cells via `Array.from` so emoji/CJK never
    get split mid-character.
  - `mission_created` SSE listener in js/core/sse.js; inline
    `renderMissionCreatedCard` in js/core/onboarding.js; supporting
    CSS in styles/surfaces/missions.css.
  - Tool-output table/key-value CSS in styles/surfaces/activity.css.
- Drop the half-wired approval-panel redesign and ChangeProposed /
  ChangeResolved event pair from this PR — they had no backend emitter
  and their UI sidebar didn't fit staging's modular layout. Staging
  already renders `.approval-card` inline from chat.css. Filing these
  as follow-ups is cleaner than keeping dead wire types.
- `MissionCreated.status` → dedicated `MissionCreatedStatus` enum
  (`Created` / `CreatedWithWarnings`) per the stringly-typed-values
  rule; replaces the `String` field.
- Extract `OUTPUT_PREVIEW_MAX_BYTES = 500` const in
  ironclaw_engine::types::event; the four call sites (orchestrator x2,
  scripting, structured) now reference the const. Docstring updated to
  call out that `max_len` is bytes, not chars, and that non-string JSON
  is serialized via compact `Display`.
- `handle_expected` tracks whether `ensure_learning_missions` failed so
  the "no missions fired" branch can distinguish a genuine zero-mission
  config from a transient ensure failure and return a clearer
  "self-improvement temporarily unavailable" message. The internal
  error stays in `debug!` per the error-boundary rule.
- Staging already has integration tests covering the PR's mission
  behavior changes (`completed_event_driven_mission_can_fire`,
  `failed_event_driven_mission_cannot_fire`,
  `threads_today_resets_on_new_day`, `is_event_driven_classification`)
  — no new tests required.

Verified: `cargo check --all-features`, `cargo clippy --all --benches
--tests --examples --all-features`, `cargo fmt`, `cargo test -p
ironclaw_common --lib`, `cargo test -p ironclaw_engine --lib mission`.
The 4 pre-existing `extensions::manager::tests::install_from_local_source_*`
failures on `cargo test --lib` require the portfolio artifact to be
built and are unrelated to this PR (called out in the author's own
earlier comment).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added scope: agent Agent core (agent loop, router, scheduler) scope: channel Channel infrastructure scope: channel/cli TUI / CLI channel scope: channel/wasm WASM channel runtime scope: tool Tool infrastructure scope: tool/builtin Built-in tools scope: tool/wasm WASM tool sandbox scope: tool/mcp MCP client scope: db Database trait / abstraction labels Apr 22, 2026
@github-actions github-actions Bot added scope: db/postgres PostgreSQL backend scope: llm LLM integration scope: orchestrator Container orchestrator scope: worker Container worker scope: secrets Secrets management scope: config Configuration scope: extensions Extension management scope: setup Onboarding / setup scope: sandbox Docker sandbox scope: hooks Git/event hooks scope: pairing Pairing mode scope: ci CI/CD workflows scope: docs Documentation scope: dependencies Dependency updates risk: high Safety, secrets, auth, or critical infrastructure and removed risk: medium Business logic, config, or moderate-risk modules labels Apr 22, 2026
@ilblackdragon

Copy link
Copy Markdown
Member

Merged origin/staging and folded in the review fixes. Single commit on top of the existing merge — full summary in the commit message.

What changed vs the previous push

Frontend (staging split app.js/style.css into per-surface modules in #2683, so the PR's additions had to be redistributed):

  • Structured tool-output rendering (renderToolOutputContent, renderJsonTable, renderJsonKeyValue) moved into js/core/tool-activity.js. Adds a JSON.parse short-circuit when the trimmed text doesn't start with { or [, and uses Array.from + slice for cell-text truncation so emoji/CJK never get split mid-character.
  • mission_created SSE listener lives in js/core/sse.js; renderMissionCreatedCard lives in js/core/onboarding.js. Supporting CSS in styles/surfaces/missions.css and styles/surfaces/activity.css.

Dropped from the PR (backing out the half-wired pieces I flagged in review):

  • AppEvent::ChangeProposed / ChangeResolved and the change_proposed / change_resolved UI listeners. Nothing emits these server-side in this PR, so keeping the wire types around is a contract with no producer. Better as a follow-up once the backend lands.
  • The right-sidebar approval-panel redesign (badge, panel, mobile overlay, keyboard shortcuts, mission-progress card). It collides with staging's modular chat layout and isn't needed for the advertised scope. Staging still renders approvals inline from .approval-card in chat.css.

Rust fixes:

  • MissionCreated.status is now a proper MissionCreatedStatus enum (Created / CreatedWithWarnings) per the typed-internals rule, instead of String.
  • Extracted OUTPUT_PREVIEW_MAX_BYTES = 500 const in ironclaw_engine::types::event; all four call sites (orchestrator ×2, scripting, structured) reference the const.
  • Docstring for truncate_output_preview spells out that max_len is bytes and that non-string JSON is serialized via compact Display.
  • handle_expected tracks whether ensure_learning_missions failed so the fallback message distinguishes "no missions configured" from "self-improvement temporarily unavailable" — without leaking internal error text per the error-boundary rule.
  • broadcast_for_user confirmed sync (not awaited — by design).

Tests:

  • Staging already has completed_event_driven_mission_can_fire, failed_event_driven_mission_cannot_fire, threads_today_resets_on_new_day, and is_event_driven_classification covering the PR's mission-manager behavior changes — no new tests required.
  • Local cargo test -p ironclaw_common --lib (67 passed) and cargo test -p ironclaw_engine --lib mission (97 passed) green.
  • cargo check --all-features and cargo clippy --all --benches --tests --examples --all-features clean.
  • Four extensions::manager::tests::install_from_local_source_* failures on the full cargo test --lib are pre-existing and unrelated — they need the portfolio artifact to be built (you called this out earlier).

Ready for another look.

@ilblackdragon

Copy link
Copy Markdown
Member

CI note: GitHub blocked the full pull_request workflows (code_style, clippy, tests, replay-gate, regression-test-check, claude-review) on my push 314dacb5. Only the four meta pull_request_target jobs (scope labels, classify, release-plz summary, staging promotion) fired — these show as green but don't exercise the actual build. This is standard behavior when a contributor other than the PR author pushes to the head branch: the workflows need an "Approve and run" click from @serrrfirat or a maintainer.

Local verification that I did run to confirm the push is clean:

  • cargo check --all-features — passes
  • cargo clippy --all --benches --tests --examples --all-features — zero warnings
  • cargo fmt — applied
  • cargo test -p ironclaw_common --lib — 67/67 pass
  • cargo test -p ironclaw_engine --lib mission — 97/97 pass

The pre-existing extensions::manager::install_from_local_source_* failures on the full cargo test --lib need the portfolio artifact built (they're unrelated to this PR, and the author already called that out earlier in this thread).

Once a maintainer approves the workflows, the real code_style / clippy / replay-gate / regression-test-check runs should confirm the green state.

@serrrfirat

Copy link
Copy Markdown
Collaborator Author

Closing as superseded by #2555, #2452, and #2182.\n\nThe core fixes for tool output visibility/timing, engine v2 tool-call persistence, and live/history tool-card correlation have landed elsewhere. The remaining pieces in this PR are stale relative to current staging and are better handled as focused follow-up PRs if still needed.

@serrrfirat serrrfirat closed this Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor: core 20+ merged PRs risk: high Safety, secrets, auth, or critical infrastructure scope: agent Agent core (agent loop, router, scheduler) scope: channel/cli TUI / CLI channel scope: channel/wasm WASM channel runtime scope: channel/web Web gateway channel scope: channel Channel infrastructure scope: ci CI/CD workflows scope: config Configuration scope: db/postgres PostgreSQL backend scope: db Database trait / abstraction scope: dependencies Dependency updates scope: docs Documentation scope: extensions Extension management scope: hooks Git/event hooks scope: llm LLM integration scope: orchestrator Container orchestrator scope: pairing Pairing mode scope: sandbox Docker sandbox scope: secrets Secrets management scope: setup Onboarding / setup scope: tool/builtin Built-in tools scope: tool/mcp MCP client scope: tool/wasm WASM tool sandbox scope: tool Tool infrastructure scope: worker Container worker size: XL 500+ changed lines

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug Bash 4/16] Tool calls execute but return empty results with no visible output Web UI: Tool call and mission output needs structured rendering

3 participants