Skip to content

Commit a3d7a75

Browse files
committed
feat: organize tauri agent replies into structured blocks
1 parent 9871f43 commit a3d7a75

9 files changed

Lines changed: 180 additions & 13 deletions

docs/diataxis/en/explanation/development-progress-dashboard.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ Tauri-first reply rendering baseline delivered:
6767
- `src/frontend/workspace_panes.js` now mounts assistant replies through typed blocks instead of only plain text when structured payloads are present,
6868
- `src/frontend/agent_workspace.js` keeps legacy fallback behavior intact, so older `assistantMessage`-only flows still render.
6969

70+
The next real improvement beyond that baseline is now also in code:
71+
72+
- `src/learning/KnowledgeLearningPlatform.ts` no longer treats `assistantBlocks` as a thin transport wrapper around the same old answer string,
73+
- the scoped conversation reply is now organized into explicit overview / explanation / evidence summary / memory notice / action guidance sections before citations and knowledge-action affordances are appended,
74+
- which means the Tauri agent surface can now look materially different even when the underlying knowledge result set is unchanged.
75+
7076
The next gap is narrower now:
7177

7278
- broaden block coverage where future endpoints emit richer assistant payloads,

docs/diataxis/zh/explanation/development-progress-dashboard.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ Tauri-first reply rendering 基线已交付:
6767
- `src/frontend/workspace_panes.js` 现已在结构化载荷存在时通过 typed blocks 挂载 assistant reply,而不再只走纯文本,
6868
- `src/frontend/agent_workspace.js` 继续保留 legacy fallback,因此旧的 `assistantMessage`-only 路径仍然可显示。
6969

70+
在这条基线之上,当前代码又前进了一步:
71+
72+
- `src/learning/KnowledgeLearningPlatform.ts` 不再把 `assistantBlocks` 当成“同一段旧 answer 的运输包装层”,
73+
- scoped conversation reply 现在会先组织成明确的 overview / explanation / evidence summary / memory notice / action guidance,再追加 citations 与 knowledge-action affordance,
74+
- 这意味着即便底层知识命中集合没变,Tauri 中的 agent 输出也已经可以在结构上明显不同。
75+
7076
当前剩余缺口已经收窄为:
7177

7278
- 当更多端点开始返回 richer assistant payload 时,继续扩充 block 覆盖面,

docs/en/implementation_plan.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ Bring the implementation plan back in line with `main` reality at three levels:
3535
3. **P2: release-grade ANN calibration**
3636
- keep the `external_http` connector green under workload proof, then close recall/latency threshold calibration.
3737
4. **P3: Tauri-first reply/render surface expansion**
38-
- keep the shared Reader-derived runtime as the Tauri baseline and continue widening typed block usage without breaking compatibility.
38+
- keep the shared Reader-derived runtime as the Tauri baseline,
39+
- move `assistantBlocks` from a thin wrapper around the legacy answer string into a real reply-organization layer,
40+
- continue widening typed block usage without breaking compatibility.
3941
5. **P4: tutor routing and orchestration hardening**
4042
- move from active local-first routing toward a production-proven multi-provider policy.
4143
6. **P5: architecture pressure reduction**
@@ -66,6 +68,7 @@ Align the active implementation plan with current code reality:
6668
- a typed reply-rendering model in the Tauri agent workspace,
6769
- shared reuse of Reader-derived markdown/math/mermaid rendering inside the agent reply surface,
6870
- artifact-style handling for large HTML assistant outputs through sandboxed preview.
71+
- structured reply composition now splits the assistant output into overview / explanation / evidence summary / memory notice / action guidance blocks instead of emitting only one wrapped markdown answer.
6972

7073
#### Next execution order
7174

docs/en/task.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [x] Reader-side markdown/KaTeX/Mermaid hardening and Tauri debug capture tooling are now real on the current branch.
1010
- [x] The Tauri agent workspace now has a typed rich-reply baseline instead of `assistantMessage`-only text mounting.
1111
- [x] The Tauri-first plan to evolve toward shared Reader-aligned rich reply rendering is now implemented as the current baseline while preserving knowledge-point/capability compatibility.
12+
- [x] The backend reply composer now uses `assistantBlocks` as a real organization layer in Tauri: overview, explanation, evidence summary, memory notice, and next-action guidance are emitted as distinct blocks instead of only wrapping the old answer text.
1213
- [x] FR-010 is now governed by the current workflow reality instead of the removed transition-era assumption: repo-owned workflows pin `actions/setup-node@v4` to Node 24 and no longer rely on `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24`.
1314
- [~] Remote CI closure remains a live release bar rather than a static claim: `Fixrisk Operational Readiness` must stay green on `main`, while residual Node 20 deprecation annotations from marketplace actions remain documented as non-blocking external debt.
1415

docs/zh/implementation_plan.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@
3636
3. **P2:release-grade ANN 校准**
3737
- 保持 `external_http` connector 在 workload proof 下稳定,再收口 recall/latency 阈值校准。
3838
4. **P3:Tauri-first reply/render surface 扩展**
39-
- 继续以共享的 Reader-derived runtime 作为 Tauri 基线,在不破坏兼容性的前提下扩展 typed block 使用面。
39+
- 继续以共享的 Reader-derived runtime 作为 Tauri 基线,
40+
-`assistantBlocks` 从“legacy answer 的薄包装层”推进为真正的回复组织层,
41+
- 在不破坏兼容性的前提下继续扩展 typed block 使用面。
4042
5. **P4:tutor routing 与 orchestration 加固**
4143
- 从已激活的 local-first 路由推进到生产级多 provider 策略。
4244
6. **P5:架构压力缩减**
@@ -67,6 +69,7 @@
6769
- Tauri agent workspace 中的 typed reply-rendering model,
6870
- Reader render substrate 在 agent reply surface 中的共享复用,
6971
- 面向大型 HTML assistant output 的 artifact-style 隔离路径(sandboxed preview)。
72+
- assistant 输出现在按 overview / explanation / evidence summary / memory notice / action guidance 分块组织,而不再只是单个 markdown answer 的包装。
7073

7174
#### 下一步执行顺序
7275

docs/zh/task.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- [x] Reader 侧 markdown / KaTeX / Mermaid 加固与 Tauri 调试抓取工具链已在当前分支真实落地。
66
- [x] Tauri agent workspace 已具备 typed rich-reply baseline,不再只是 `assistantMessage` 纯文本挂载。
77
- [x] 这条 Tauri-first 路线已作为当前基线落地:在保持现有 knowledge-point / capability 兼容的前提下,引入了共享 Reader-aligned rich reply rendering。
8+
- [x] 后端回复组织层现在已经把 `assistantBlocks` 用成真正的 Tauri 输出结构:overview、explanation、evidence summary、memory notice、next-action guidance 已分块输出,而不再只是旧 answer 文本的包装。
89
- [x] FR-010 现已按当前工作流现实治理,而不是继续沿用已删除的过渡期假设:仓库自有工作流固定为 `actions/setup-node@v4` + Node 24,且不再依赖 `FORCE_JAVASCRIPT_ACTIONS_TO_NODE24`
910
- [~] remote CI 是否闭环仍以 `main` 上的实时 `Fixrisk Operational Readiness` 结果为准;其余 marketplace action 带来的 Node 20 弃用注解继续作为非阻塞外部债务记录。
1011

src/agent_workspace.frontend.test.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2770,11 +2770,25 @@ describe('agent workspace learning-path integration', () => {
27702770
svg: '<svg><text>Rendered Mermaid</text></svg>',
27712771
}));
27722772
(window as any).marked = {
2773-
parse: jest.fn(() => (
2774-
'<h2>Scoped Answer</h2>'
2775-
+ '<p>Inline math $E=mc^2$ and a diagram:</p>'
2776-
+ '<pre><code class="language-mermaid">graph TD;A-->B;</code></pre>'
2777-
)),
2773+
parse: jest.fn((markdown: string) => {
2774+
if (markdown.includes('## Scoped Answer')) {
2775+
return (
2776+
'<h2>Scoped Answer</h2>'
2777+
+ '<ul><li>Relevant knowledge points: <strong>1</strong></li></ul>'
2778+
);
2779+
}
2780+
if (markdown.includes('## Evidence Summary')) {
2781+
return (
2782+
'<h2>Evidence Summary</h2>'
2783+
+ '<p>Blocks Citation</p>'
2784+
);
2785+
}
2786+
return (
2787+
'<h2>Explanation</h2>'
2788+
+ '<p>Inline math $E=mc^2$ and a diagram:</p>'
2789+
+ '<pre><code class="language-mermaid">graph TD;A-->B;</code></pre>'
2790+
);
2791+
}),
27782792
};
27792793
(window as any).renderMathInElement = renderMathInElement;
27802794
(window as any).mermaid = {
@@ -2795,10 +2809,25 @@ describe('agent workspace learning-path integration', () => {
27952809
assistantMessage: 'Scoped Answer',
27962810
answer: 'Scoped Answer',
27972811
assistantBlocks: [
2812+
{
2813+
blockId: 'block_overview_1',
2814+
type: 'main_markdown',
2815+
markdown: '## Scoped Answer\n\n- Relevant knowledge points: **1**\n- Citations returned: **1**\n- Scoped memories recalled: **0**',
2816+
},
27982817
{
27992818
blockId: 'block_main_1',
28002819
type: 'main_markdown',
2801-
markdown: '## Scoped Answer\n\nInline math $E=mc^2$ and a diagram:\n\n```mermaid\ngraph TD;A-->B;\n```',
2820+
markdown: '## Explanation\n\nInline math $E=mc^2$ and a diagram:\n\n```mermaid\ngraph TD;A-->B;\n```',
2821+
},
2822+
{
2823+
blockId: 'block_evidence_1',
2824+
type: 'main_markdown',
2825+
markdown: '## Evidence Summary\n\n1. **Blocks Citation** (Knowledge_Base/optics/blocks.md:18)\n - Scoped snippet',
2826+
},
2827+
{
2828+
blockId: 'block_notice_1',
2829+
type: 'system_notice',
2830+
text: 'No scoped memory note was recalled for this turn.',
28022831
},
28032832
{
28042833
blockId: 'block_citations_1',
@@ -2884,6 +2913,9 @@ describe('agent workspace learning-path integration', () => {
28842913
const assistantNode = document.querySelector('.agent-chat-message-rendered.agent-chat-message-assistant');
28852914
expect(assistantNode).not.toBeNull();
28862915
expect(assistantNode?.querySelector('h2')?.textContent).toBe('Scoped Answer');
2916+
expect(String(assistantNode?.textContent || '')).toContain('Relevant knowledge points');
2917+
expect(String(assistantNode?.textContent || '')).toContain('No scoped memory note was recalled for this turn.');
2918+
expect(String(assistantNode?.textContent || '')).toContain('Evidence Summary');
28872919
expect(renderMathInElement).toHaveBeenCalled();
28882920
expect((window as any).mermaid.initialize).toHaveBeenCalled();
28892921
expect(mermaidRender).toHaveBeenCalled();

src/learning/KnowledgeLearningPlatform.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,6 +1458,21 @@ describe('KnowledgeLearningPlatform', () => {
14581458

14591459
expect(response.answer).toContain('吸收');
14601460
expect(response.citations.length).toBeGreaterThan(0);
1461+
expect(Array.isArray(response.assistantBlocks)).toBe(true);
1462+
expect(response.assistantBlocks?.map((block) => block.type)).toEqual(
1463+
expect.arrayContaining(['main_markdown', 'system_notice', 'citations', 'knowledge_actions'])
1464+
);
1465+
const markdownBlocks = (response.assistantBlocks || []).filter((block) => block.type === 'main_markdown');
1466+
expect(markdownBlocks.length).toBeGreaterThanOrEqual(3);
1467+
expect(
1468+
markdownBlocks.some((block) => String((block as { markdown?: string }).markdown || '').includes('## Scoped Answer'))
1469+
).toBe(true);
1470+
expect(
1471+
markdownBlocks.some((block) => String((block as { markdown?: string }).markdown || '').includes('## Explanation'))
1472+
).toBe(true);
1473+
expect(
1474+
markdownBlocks.some((block) => String((block as { markdown?: string }).markdown || '').includes('## Evidence Summary'))
1475+
).toBe(true);
14611476
expect(response.trace.usedScope.corpusId).toBe('optics');
14621477
expect(response.summary.appliedMemoryCount).toBeGreaterThan(0);
14631478

src/learning/KnowledgeLearningPlatform.ts

Lines changed: 105 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7624,18 +7624,112 @@ export class KnowledgeLearningPlatform implements KnowledgeLearningPlatformAPI {
76247624
return `${memoryPrefix}The strongest scoped match is ${leadingPoint.title}. I found ${params.knowledgePoints.length} relevant knowledge point(s) and ${params.citations.length} citation(s).\n\nKey evidence:\n${evidenceLines.join('\n')}`;
76257625
}
76267626

7627+
private buildScopedConversationOverviewMarkdown(params: {
7628+
knowledgePoints: AgentConversationKnowledgePoint[];
7629+
citations: KnowledgeCitation[];
7630+
recalledMemories: AgentConversationMemoryRecord[];
7631+
}): string {
7632+
const strongestPoint = params.knowledgePoints[0];
7633+
const lines = [
7634+
'## Scoped Answer',
7635+
'',
7636+
];
7637+
if (strongestPoint) {
7638+
lines.push(`The strongest scoped match is **${strongestPoint.title}**.`, '');
7639+
} else {
7640+
lines.push('No scoped knowledge point produced a strong match for the current request.', '');
7641+
}
7642+
lines.push(
7643+
`- Relevant knowledge points: **${params.knowledgePoints.length}**`,
7644+
`- Citations returned: **${params.citations.length}**`,
7645+
`- Scoped memories recalled: **${params.recalledMemories.length}**`
7646+
);
7647+
return lines.join('\n');
7648+
}
7649+
7650+
private buildScopedConversationEvidenceMarkdown(params: {
7651+
citations: KnowledgeCitation[];
7652+
}): string {
7653+
const evidenceLines = params.citations.slice(0, 3).map((citation, index) => (
7654+
`${index + 1}. **${citation.title}** (${citation.sourcePath}${citation.startLine ? `:${citation.startLine}` : ''})\n - ${citation.snippet}`
7655+
));
7656+
if (evidenceLines.length <= 0) {
7657+
return '## Evidence Summary\n\nNo scoped citations were returned.';
7658+
}
7659+
return [
7660+
'## Evidence Summary',
7661+
'',
7662+
...evidenceLines,
7663+
].join('\n');
7664+
}
7665+
7666+
private buildScopedConversationMemoryNotice(params: {
7667+
recalledMemories: AgentConversationMemoryRecord[];
7668+
}): string {
7669+
if (params.recalledMemories.length <= 0) {
7670+
return 'No scoped memory note was recalled for this turn.';
7671+
}
7672+
if (params.recalledMemories.length === 1) {
7673+
return '1 scoped memory note was recalled and merged into the answer context.';
7674+
}
7675+
return `${params.recalledMemories.length} scoped memory notes were recalled and merged into the answer context.`;
7676+
}
7677+
7678+
private buildScopedConversationActionGuideMarkdown(params: {
7679+
knowledgePoints: AgentConversationKnowledgePoint[];
7680+
}): string {
7681+
if (params.knowledgePoints.length <= 0) {
7682+
return '## Next Actions\n\nNo actionable scoped knowledge card is available for this turn.';
7683+
}
7684+
return [
7685+
'## Next Actions',
7686+
'',
7687+
'Use the scoped knowledge cards below to continue with focus mode or guided learning for the highest-signal nodes:',
7688+
...params.knowledgePoints.slice(0, 3).map((point) => `- ${point.title}`),
7689+
].join('\n');
7690+
}
7691+
76277692
private buildScopedConversationAssistantBlocks(params: {
76287693
answer: string;
76297694
citations: KnowledgeCitation[];
76307695
knowledgePoints: AgentConversationKnowledgePoint[];
7696+
recalledMemories: AgentConversationMemoryRecord[];
76317697
}): AgentConversationAssistantBlock[] {
7632-
const blocks: AgentConversationAssistantBlock[] = [
7633-
{
7698+
const blocks: AgentConversationAssistantBlock[] = [];
7699+
const overviewMarkdown = this.buildScopedConversationOverviewMarkdown(params);
7700+
const normalizedAnswer = String(params.answer || '').trim();
7701+
const evidenceMarkdown = this.buildScopedConversationEvidenceMarkdown(params);
7702+
const memoryNotice = this.buildScopedConversationMemoryNotice(params);
7703+
const actionGuideMarkdown = this.buildScopedConversationActionGuideMarkdown(params);
7704+
7705+
if (overviewMarkdown) {
7706+
blocks.push({
76347707
blockId: this.nextId('assistant_block'),
76357708
type: 'main_markdown',
7636-
markdown: String(params.answer || ''),
7637-
},
7638-
];
7709+
markdown: overviewMarkdown,
7710+
});
7711+
}
7712+
if (normalizedAnswer) {
7713+
blocks.push({
7714+
blockId: this.nextId('assistant_block'),
7715+
type: 'main_markdown',
7716+
markdown: `## Explanation\n\n${normalizedAnswer}`,
7717+
});
7718+
}
7719+
if (evidenceMarkdown) {
7720+
blocks.push({
7721+
blockId: this.nextId('assistant_block'),
7722+
type: 'main_markdown',
7723+
markdown: evidenceMarkdown,
7724+
});
7725+
}
7726+
if (memoryNotice) {
7727+
blocks.push({
7728+
blockId: this.nextId('assistant_block'),
7729+
type: 'system_notice',
7730+
text: memoryNotice,
7731+
});
7732+
}
76397733
if (params.citations.length > 0) {
76407734
blocks.push({
76417735
blockId: this.nextId('assistant_block'),
@@ -7645,6 +7739,11 @@ export class KnowledgeLearningPlatform implements KnowledgeLearningPlatformAPI {
76457739
});
76467740
}
76477741
if (params.knowledgePoints.length > 0) {
7742+
blocks.push({
7743+
blockId: this.nextId('assistant_block'),
7744+
type: 'main_markdown',
7745+
markdown: actionGuideMarkdown,
7746+
});
76487747
blocks.push({
76497748
blockId: this.nextId('assistant_block'),
76507749
type: 'knowledge_actions',
@@ -8061,6 +8160,7 @@ export class KnowledgeLearningPlatform implements KnowledgeLearningPlatformAPI {
80618160
answer,
80628161
citations,
80638162
knowledgePoints,
8163+
recalledMemories,
80648164
});
80658165
const response: AgentConversationResponse = {
80668166
userId,

0 commit comments

Comments
 (0)