From 6192d3bb571f24fffdc5fc0d7d87566dffccdf0c Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sun, 26 Jul 2026 01:59:54 -0500 Subject: [PATCH] fix(desktop): stop painting memory writes as errors A memory tool call rendered destructive-red with an alert glyph and, when expanded, a raw dump of the whole args + result payload. Two causes. The red came from toolStatus treating any error text as a failure: the memory store rejects an over-budget batch so the agent can retry a smaller one, which is routine bookkeeping, not something the user acts on. Those now land on the existing amber warning tier. The payload dump came from memory having no case in toolSubtitle or toolDetailText, so both fell through to the generic stringify-everything fallback. Both now surface just the human-readable line. --- .../assistant-ui/tool/fallback-model/index.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/components/assistant-ui/tool/fallback-model/index.ts b/apps/desktop/src/components/assistant-ui/tool/fallback-model/index.ts index 8437dfee4239..a718358efee3 100644 --- a/apps/desktop/src/components/assistant-ui/tool/fallback-model/index.ts +++ b/apps/desktop/src/components/assistant-ui/tool/fallback-model/index.ts @@ -180,6 +180,10 @@ const TOOL_META: Record = { icon: 'files', tone: 'file' }, + memory: { + icon: 'brain', + tone: 'agent' + }, patch: { icon: 'edit', tone: 'file' }, read_file: { icon: 'file', tone: 'file' }, search_files: { @@ -688,7 +692,15 @@ function toolStatus(part: ToolPart, resultRecord: Record): Tool return 'running' } - return toolErrorText(part, resultRecord) ? 'error' : 'success' + if (!toolErrorText(part, resultRecord)) { + return 'success' + } + + // A rejected memory write is a budget negotiation, not a failure: the store + // refuses an over-limit batch and the agent immediately retries a smaller + // one. Painting the row destructive-red puts an alarm next to routine + // bookkeeping the user never has to act on. Amber says "noted" instead. + return part.toolName === 'memory' ? 'warning' : 'error' } function durationLabel(resultRecord: Record): string | undefined { @@ -1009,6 +1021,13 @@ function toolSubtitle( return url ? hostnameOf(url) : 'Fetched webpage' } + if (toolName === 'memory') { + // The raw payload is bookkeeping the user never needs: usage counters, a + // note telling the model not to retry, and the full operations array. The + // human-readable line is the only part worth showing. + return firstStringField(resultRecord, ['message', 'error']) + } + if (toolName === 'cronjob') { return cronjobSubtitle(argsRecord, resultRecord) } @@ -1091,6 +1110,12 @@ function toolDetailText( } } + if (part.toolName === 'memory') { + // Same reasoning as toolSubtitle: without this the generic fallback dumps + // the whole args + result payload into the expanded row. + return firstStringField(resultRecord, ['message', 'error']) + } + if (isFileEditTool(part.toolName)) { if (inlineDiffFromResult(part.result)) { return ''