Skip to content

feat(assistant): surface confirmed tool results via a host renderer (fix lost API key)#179

Merged
Tjemmmic merged 2 commits into
mainfrom
feat/assistant-confirmed-result-renderer
Jul 9, 2026
Merged

feat(assistant): surface confirmed tool results via a host renderer (fix lost API key)#179
Tjemmmic merged 2 commits into
mainfrom
feat/assistant-confirmed-result-renderer

Conversation

@Tjemmmic

@Tjemmmic Tjemmmic commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Problem

When the platform assistant creates an API key, the user never gets the secret. The server returns it once in the confirm response's output.key, but the client dropped it: on confirm, describeOutcome("create_api_key") read only the prefix, discarded output.key, and told the user to "copy it from the API Keys page" — impossible, since a key's secret is shown only once at creation and is never re-displayed there. useAssistantChat.confirm() also discarded the whole output.

Solution

Add a generic seam for rendering a confirmed tool's result, then let the host draw it:

  • useAssistantChat.confirm() retains a clean-success tool's output on the resolving status message as a new ConfirmedResult { name, output, args }.
  • adaptTranscript surfaces it (keyed by the system message id) and AssistantTranscript renders it through a new host prop renderConfirmedResult, threaded AssistantDock → AssistantPanel → transcript exactly like toolRenderers.
  • Unlike toolRenderers (collapsed tool-chip detail), the confirmed-result card renders expanded, so a one-time secret is visible without a click.
  • Corrected the create_api_key status line — no more "copy it from the API Keys page".

The package stays domain-agnostic about the result UI: the host (platform-web) owns the actual reveal card.

Security

The retained output may carry a one-time secret. It lives only in the in-memory transcript: the transcript is deliberately never cached (persistence.ts stores only threadId/model), reload rehydrates only user/assistant text, and a chat request (ChatRequest) carries only the new message — so the secret is never persisted and never sent back to the model. Shown once, gone on reload, matching the dedicated create-key page. A confirmed result never becomes a tool-role message fed to the model.

Tests

New/updated unit tests: corrected describeOutcome text (no "API Keys page", secret never in the status line); confirm() retains output on the resolved message while keeping the secret out of every message's text; adaptTranscript surfaces the result; AssistantTranscript renders the host card end-to-end and degrades to just the status line when no renderer is supplied. Full assistant suite: 254/254 green.

Consumer

products/platform/web (tangle-network/agent-dev-container) consumes renderConfirmedResult to show the one-time API-key reveal card, resolving agent-dev-container#3352. That consumer PR must pin the version this releases as.

…fix lost API key

A confirmed create_api_key returns its one-time secret in output.key, but the
client dropped it: describeOutcome read only the prefix and told the user to
"copy it from the API Keys page" — impossible, since a key's secret is shown
only once at creation. Retain a confirmed tool's output on the resolving status
message and render it prominently via a new host `renderConfirmedResult` slot
(threaded through AssistantDock/Panel/transcript, mirroring toolRenderers), so a
host can show a one-time reveal card. Correct the create_api_key status text.

The secret lives only in the in-memory transcript (never cached, never sent back
to the model), so it is shown once and gone on reload — matching the dedicated
create-key page. Unlike toolRenderers' collapsed detail, the result card is
expanded so a one-time secret is visible without a click.
@Tjemmmic

This comment has been minimized.

renderConfirmedResult returns null for a tool the host doesn't render a card
for; the transcript still wrapped that null in `<div class="mt-3">`, leaving an
empty spacer under every other confirmed tool's status line. Compute the node
first and only wrap when it is non-null.
@Tjemmmic

Tjemmmic commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

🤖 AI Code Review (ensemble)

Summary

The PR successfully threads confirmed tool outputs (like one-time API keys) through the assistant transcript via a host-supplied renderer, keeping the visible status text free of secrets. The implementation is solid and well-tested, but there is a minor rendering contract bug where valid falsy React nodes are dropped due to truthiness checks.

Issues Found

1 total — 0 P1 (blocking) · 1 P2 (should fix) · 0 P3 (nice to have)

⚠️ P2 — Valid falsy React nodes from renderConfirmedResult are dropped

  • File: src/assistant/transcript.tsx:L307-L313
  • Problem: renderConfirmedResult is typed to return ReactNode, where 0 or an empty string ("") are valid renderable outputs. The code uses confirmedNode ? ... : null, causing these valid values to be treated as absent, which breaks the render prop contract and silently hides host-provided output.
  • Fix: Check explicitly for non-rendered values instead of generic truthiness:
const confirmedCard =
  confirmedNode !== null && confirmedNode !== undefined && confirmedNode !== false ? (
    <div className="mt-3">{confirmedNode}</div>
  ) : null;

✅ APPROVE

The PR introduces a clean, well-tested mechanism for surfacing one-time secrets without leaking them into the transcript text, resolving the lost API key issue. There are no P1 issues and only one minor P2 rendering contract bug. Under the decision rules, this is mergeable, though the P2 should be addressed to fully uphold the render prop contract for edge cases.

Quick Reference

  • P2: Valid falsy React nodes from renderConfirmedResult are dropped

Synthesized by Sokuza AI from multiple independent reviewers

@Tjemmmic Tjemmmic marked this pull request as ready for review July 9, 2026 19:55
@Tjemmmic Tjemmmic merged commit e3e15f2 into main Jul 9, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant