import assert from 'node:assert/strict' import { renderToStaticMarkup } from 'react-dom/server' import type { ChatMessage } from '../src/shared/types' import { projectQuestionAnswers } from '../src/renderer/src/workbuddy/conversation/question-projection' import { buildCompletedFoldModel } from '../src/renderer/src/workbuddy/conversation/completed-fold' import { CompletedFold } from '../src/renderer/src/workbuddy/conversation/CompletedFold' import { StreamingFooter, resolveRuntimeStatusText, isLoadingTipPaused } from '../src/renderer/src/workbuddy/conversation/StreamingFooter' import MessageBubble from '../src/renderer/src/components/MessageBubble' const message: ChatMessage = { id:'trace', role:'assistant', content:'A B', createdAt:1, status:'streaming', parts:[ {type:'text',text:'A'}, {type:'reasoning',id:'r1',text:'thought one'}, {type:'tool',toolId:'t1'}, {type:'text',text:'B'}, {type:'reasoning',id:'r2',text:'thought two'}, {type:'tool',toolId:'t2'} ], tools: ['t1','t2'].map(id=>({id,name:'Read',args:{file_path:id},argsRaw:'{}',status:'ok'})) } const model=buildCompletedFoldModel(message.parts!,{turnFinished:false})! assert.deepEqual(model.summaryGroups.map(group=>[group.startIndex,group.shouldFold]),[[1,true],[4,false]]) const live=renderToStaticMarkup( ) assert.equal((live.match(/cr-live-event-feed/g)||[]).length,1) assert.match(live,/data-activity-tool-id="t1"/) assert.match(live.replace(/<[^>]*>/g,''),/thought two/) assert.equal((live.match(/data-tool-name="Read"/g)||[]).length,2) const expanded=renderToStaticMarkup({p.type}}/> ) assert.deepEqual([...expanded.matchAll(/data-index="(\d+)"/g)].map(m=>Number(m[1])),[0,1,2,3,4,5]) const question: ChatMessage={...message,parts:[{type:'tool',toolId:'q'},{type:'text',text:'after'}],tools:[{id:'q',name:'AskUserQuestion',args:{},argsRaw:'{}',status:'ok'}],interactions:[{kind:'question',id:'i',runId:'r',toolCallId:'q',title:'Q',status:'answered',questions:[],answer:{questions:[{id:'q1',question:'Choose',answers:['yes']}]}}]} const projection=projectQuestionAnswers(question) assert.equal(projection.unpositioned.length,0) assert.deepEqual(projection.tools[0].metadata?.questionAnswer,question.interactions![0].kind==='question'&&question.interactions![0].answer) assert.equal(question.tools![0].metadata,undefined,'projection must not mutate persisted source') assert.equal(projectQuestionAnswers({...question,tools:[],parts:[{type:'text',text:'old'}]}).unpositioned.length,1) assert.equal(projectQuestionAnswers({...question,interactions:[...question.interactions!,...question.interactions!]}).tools.length,1) assert.equal(resolveRuntimeStatusText('准备项目',{source:'agent',phase:'tool',toolName:'Read'}),'准备项目') assert.equal(resolveRuntimeStatusText(null,{source:'interaction',phase:'waiting-answer'}),'等待回答') assert.equal(resolveRuntimeStatusText(null,{source:'agent',phase:'finishing'}),'正在收尾') assert.equal(renderToStaticMarkup(),'') assert.match(renderToStaticMarkup(),/等待授权/) console.log('alignment-conversation: local groups, original expanded order, question projection and truthful status passed') assert.equal(isLoadingTipPaused({hovered:false,focused:true}),true) assert.equal(isLoadingTipPaused({hovered:true,focused:false}),true) assert.equal(isLoadingTipPaused({hovered:false,focused:false}),false) const expandedGrouped=renderToStaticMarkup(
{children}
} renderUnit={(p,i)=>{p.type}}/> ) assert.deepEqual([...expandedGrouped.matchAll(/data-index="(\d+)"/g)].map(m=>Number(m[1])),[0,1,2,3,4,5]) assert.deepEqual([...expandedGrouped.matchAll(/data-local-group="(\d+)"/g)].map(m=>Number(m[1])),[1,4], 'opening completion must preserve independently controlled local summaries') // A01: exercise the real native name through MessageBubble, not just its projection. const nativeQuestion = {...question, status: 'done' as const, tools: question.tools!.map(tool => ({...tool, output: 'RAW SDK ANSWER'}))} for (const name of ['AskUserQuestion', 'askuserquestion', 'ask_user_question', 'AskFollowupQuestion', 'ask-followup-question']) { const fixture = {...nativeQuestion, tools: nativeQuestion.tools.map(tool => ({...tool, name}))} const before = JSON.stringify(fixture) const markup = renderToStaticMarkup() assert.match(markup, /ask-user-question--completed/, `${name} routes to dedicated question card`) assert.doesNotMatch(markup, /RAW SDK ANSWER/, `${name} suppresses raw SDK answer`) assert.equal(JSON.stringify(fixture), before, 'rendering does not rewrite recorded names or metadata') } const ordinary = renderToStaticMarkup() assert.doesNotMatch(ordinary, /ask-user-question--completed/) assert.match(ordinary, /RAW SDK ANSWER/, 'ordinary tools retain their raw output') console.log('A01 native MessageBubble identity aliases, immutable inputs and ordinary raw output passed') const importedNative = {...nativeQuestion, interactions: [], tools: nativeQuestion.tools.map(tool => ({...tool, args: {questions: [{id: 'q1', question: 'Imported native question'}]}, output: 'q1 -> imported answer', metadata: {importedHistory: true} }))} const importedMarkup = renderToStaticMarkup() assert.match(importedMarkup, /ask-user-question--completed/) assert.equal(importedNative.tools[0].name, 'AskUserQuestion', 'historical display adaptation does not rewrite stored name') const duplicateMarkup = renderToStaticMarkup() assert.equal((duplicateMarkup.match(/ask-user-question--completed/g) || []).length, 1, 'linked answers have one dedicated card') const legacyNative = {...nativeQuestion, parts: undefined, tools: nativeQuestion.tools.map(tool => ({...tool, metadata: {questionAnswer: {questions: [{id: 'legacy', question: 'Unknown-position question', answers: ['legacy answer']}]}}})), interactions: []} const legacyNativeMarkup = renderToStaticMarkup() assert.match(legacyNativeMarkup, /历史问答(原始位置不可用)/) assert.equal((legacyNativeMarkup.match(/Unknown-position question/g) || []).length, 1) assert.doesNotMatch(legacyNativeMarkup, /RAW SDK ANSWER/) console.log('A01 imported native card, linked dedup and unknown-position compatibility passed')