# 《WaLiSSH - AI Shell 智能终端》第2-11节:UI 对接&迭代功能
作者:小傅哥
博客:https://bugstack.cn (opens new window)
视频:https://t.zsxq.com/OXlKJ (opens new window)
沉淀、分享、成长,让自己和他人都能有所收获!😄
大家好,我是技术UP主小傅哥。
好嘞,到这可以开始对接UI了,让整个功能以产品的形态展示。对话过程中,让用户看到;

- Agent 当前处于分析、工具执行还是生成阶段;
- 文本如何逐步生成;
- 调用了什么工具,传入了什么参数,结果是成功还是失败;
- 当前执行到第几步,是否还会继续;
- 连接中断、用户停止或后端异常时,页面如何正常结束。
与此同时,再优化2个场景:
- 上下文污染:每轮工具执行后,环境信息、工具摘要都被追加到用户消息尾部。下次打开会话,模型看到的"用户消息"其实是"用户原文 + 上次的环境快照 + 上次的工具输出",又长又乱还浪费 token。
- 意图标签太粗糙:早期实现只把一个枚举名(如
DIAGNOSE)塞进 Prompt,低置信度时反而误导模型。
这一节的视频会先演示使用效果。
# 一、本章诉求
在本分支 2-11-ai-case-ReAct-ui 的开发中,我们解决了以下核心问题:
| 问题 | 现象 | 解法 |
|---|---|---|
| 子 Agent 事件不可见 | dispatchSubAgents 派发的子 Runner 事件不进入父事件流 | AgentEventPublisher 三级路由 + 子工具合成事件 |
| SSH 工具事件丢失 | executeCommand 的 FunctionResponse 不一定以标准形式进入父事件流 | SshExecuteAdkTool 合成 tool_call / tool_result 事件 |
| 同一工具事件重复 | ADK 可能对同一 FunctionCall / Response 重复投递 | Controller 端按 toolCallId 去重 |
| 上下文膨胀 | 动态 Prompt 后缀原样写入 Session,跨轮重复累积 | PromptEnvelope 定界用户原文,Session 只保存原文 |
| Prompt 前缀不稳定 | 所有动态上下文堆在用户消息后,缓存命中率低 | ContextPlacement 分层:稳定前缀 / 动态尾部 |
| 意图标签误导 | 低置信度枚举名照样渲染,模型被误导 | 结构化意图提示:置信度 + 候选 + 实体 + 使用原则 |
用户消息含 --- 被误切 | 旧实现用 \n---\n 分隔动态后缀,用户 Markdown 内容被切断 | PromptEnvelope 用 XML 风格标签定界,不依赖内容特征 |
没有一次或者一直完美的东西,只有不断优化的目标和诉求。
# 二、总体架构
先从全局视角看这一节做了什么:
用户发送消息 + terminalSessionId
│
▼
┌─────────────────────────────────────────────────────┐
│ Controller.chatStream() │
│ │
│ 1. 创建 SseEmitter + 心跳 │
│ 2. 注册 session / terminal 监听器 │
│ 3. 订阅 chatService.handleMessageStream() │
│ 4. 逐事件转换 → JSON SSE │
│ 5. 收到嵌套事件 → 同一转换逻辑处理 │
│ 6. finally: 注销监听器 + 清理 ThreadLocal │
└────────────────────┬────────────────────────────────┘
│
┌───────────┼───────────────┐
▼ ▼ ▼
主 Runner 子 Agent Runner SSH 工具
Event 流 (独立 Runner) executeCommand
│ │ │
│ ▼ ▼
│ AgentEventPublisher(三级路由)
│ invocation → session → terminal
│ │ │
└───────────┴───────┬───────┘
▼
Controller 事件桥接层
│
▼
JSON SSE → 前端 UI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

