Skip to content

Commit c3413f1

Browse files
os-zhuangclaude
andauthored
fix(app-shell): hydrated server history renders Completed + unified 'Assistant' label everywhere (#1657)
Two gaps found in live staging verification of the unified assistant: 1. Stateless tool parts spun forever: server conversations persist ModelMessage content whose tool-call entries carry NO UI state; contentToParts passes them through verbatim, partToolState's default returned undefined, and the chip rendered 'Running' eternally. In hydrated history the turn has ended — stateless ≡ completed. 2. The server-side data_chat agent now says 'Assistant' (builtin upsert), but three client spots still hardcoded 'Data Assistant' / '数据助手' (AiChatPage i18n default, en/zh locale catalogs, ConsoleFloatingChatbot fallback map) and overrode it. Now 'Assistant' / '智能助手'. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 9765756 commit c3413f1

5 files changed

Lines changed: 61 additions & 6 deletions

File tree

packages/app-shell/src/console/ai/AiChatPage.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,18 @@ function partToolState(part: HydratedUIMessagePart): ChatbotEnhancedToolInvocati
8383
case 'output-denied':
8484
return state;
8585
default:
86-
return undefined;
86+
// No state at all: server-side conversations persist ModelMessage
87+
// `tool-call` content entries, which carry no UI state — contentToParts
88+
// passes them through as `tool-call` parts verbatim. In hydrated
89+
// history that turn has ended too, so stateless ≡ completed; returning
90+
// undefined here leaves the invocation state-less and the chip renders
91+
// "Running" forever (the live-verified gap left by the first fix).
92+
return 'output-available';
8793
}
8894
}
8995

90-
function hydratedMessagesToChatMessages(messages: HydratedUIMessage[]): ChatMessage[] {
96+
/** Exported for tests — maps persisted/cached history to renderable messages. */
97+
export function hydratedMessagesToChatMessages(messages: HydratedUIMessage[]): ChatMessage[] {
9198
return messages.map((message) => {
9299
const toolInvocations: ChatbotEnhancedToolInvocation[] = [];
93100
const content = message.parts
@@ -130,7 +137,7 @@ function firstUserMessageText(messages: HydratedUIMessage[]): string | undefined
130137
}
131138

132139
const PLATFORM_AGENT_LABEL_KEYS: Record<string, { key: string; defaultValue: string }> = {
133-
data_chat: { key: 'console.ai.agentLabels.dataChat', defaultValue: 'Data Assistant' },
140+
data_chat: { key: 'console.ai.agentLabels.dataChat', defaultValue: 'Assistant' },
134141
metadata_assistant: { key: 'console.ai.agentLabels.metadataAssistant', defaultValue: 'Metadata Assistant' },
135142
};
136143

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
import { describe, it, expect } from 'vitest';
4+
import { hydratedMessagesToChatMessages } from '../AiChatPage';
5+
import type { HydratedUIMessage } from '../../../hooks/useChatConversation';
6+
7+
function assistantWith(parts: HydratedUIMessage['parts']): HydratedUIMessage[] {
8+
return [{ id: 'm1', role: 'assistant', parts }];
9+
}
10+
11+
describe('AiChatPage hydration — tool invocation states', () => {
12+
it('promotes STATELESS tool parts to output-available (server ModelMessage tool-call entries)', () => {
13+
// Server conversations persist ModelMessage content: `tool-call` entries
14+
// carry toolName/toolCallId but NO UI state. Hydrated history has ended,
15+
// so stateless must render Completed — not an eternal "Running" chip.
16+
const [msg] = hydratedMessagesToChatMessages(
17+
assistantWith([
18+
{ type: 'tool-call', toolCallId: 't1', toolName: 'propose_blueprint' },
19+
]),
20+
);
21+
expect(msg.toolInvocations).toEqual([
22+
{ toolCallId: 't1', toolName: 'propose_blueprint', state: 'output-available' },
23+
]);
24+
});
25+
26+
it('promotes dangling mid-stream states to output-available', () => {
27+
const [msg] = hydratedMessagesToChatMessages(
28+
assistantWith([
29+
{ type: 'tool-add_field', toolCallId: 't1', toolName: 'add_field', state: 'input-available' },
30+
{ type: 'tool-create_metadata', toolCallId: 't2', toolName: 'create_metadata', state: 'input-streaming' },
31+
]),
32+
);
33+
expect(msg.toolInvocations?.map((t) => t.state)).toEqual(['output-available', 'output-available']);
34+
});
35+
36+
it('preserves genuine terminal states', () => {
37+
const [msg] = hydratedMessagesToChatMessages(
38+
assistantWith([
39+
{ type: 'tool-add_field', toolCallId: 't1', toolName: 'add_field', state: 'output-error', errorText: 'boom' },
40+
{ type: 'tool-verify_build', toolCallId: 't2', toolName: 'verify_build', state: 'output-denied' },
41+
]),
42+
);
43+
expect(msg.toolInvocations).toEqual([
44+
{ toolCallId: 't1', toolName: 'add_field', state: 'output-error', errorText: 'boom' },
45+
{ toolCallId: 't2', toolName: 'verify_build', state: 'output-denied' },
46+
]);
47+
});
48+
});

packages/app-shell/src/layout/ConsoleFloatingChatbot.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ import { getRuntimeConfig } from '../runtime-config';
5151
* to whatever label the backend provides.
5252
*/
5353
const PLATFORM_AGENT_LABELS: Record<string, { zh: string; en: string }> = {
54-
data_chat: { zh: '数据助手', en: 'Data Assistant' },
54+
data_chat: { zh: '智能助手', en: 'Assistant' },
5555
metadata_assistant: { zh: '元数据开发助手', en: 'Metadata Assistant' },
5656
};
5757

packages/i18n/src/locales/en.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1029,7 +1029,7 @@ const en = {
10291029
hoursAgo: '{{count}}h ago',
10301030
daysAgo: '{{count}}d ago',
10311031
agentLabels: {
1032-
dataChat: 'Data Assistant',
1032+
dataChat: 'Assistant',
10331033
metadataAssistant: 'Metadata Assistant',
10341034
},
10351035
suggestions: {

packages/i18n/src/locales/zh.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1029,7 +1029,7 @@ const zh = {
10291029
hoursAgo: '{{count}} 小时前',
10301030
daysAgo: '{{count}} 天前',
10311031
agentLabels: {
1032-
dataChat: '数据助手',
1032+
dataChat: '智能助手',
10331033
metadataAssistant: '元数据开发助手',
10341034
},
10351035
suggestions: {

0 commit comments

Comments
 (0)