跳转至

Tui chat cn

TUI Chat 是 Jianmu 的终端交互应用,基于 Textual 构建,提供面向 Agent 的聊天、审批和运行观察界面。它采用“薄 UI、厚适配层”的组织方式:界面层主要负责渲染与输入收集,运行时编排、挂起恢复、审批协调和事件投影则集中在 apps/tui_chat/ 的适配层中。本文重点说明它的结构、运行路径和与 Jianmu 内核的衔接方式。

架构分层总览

TUI Chat 的架构可划分为四个正交层次:入口路由层 负责分发 CLI 命令与 TUI 启动;UI 组合层(Textual App + Widgets)定义布局绑定与事件路由;适配编排层(Runner + Factory + Projector)将 Jianmu 运行时事件转化为 UI 可消费的快照;能力支撑层(配置、会话、技能、审批、文件、转录等模块)提供独立的功能单元。

graph TD
    subgraph 入口路由
        MAIN[__main__.py] --> CLI[cli.py / 非UI命令]
        MAIN --> APP[app.py / Textual TUI]
    end

    subgraph UI组合层
        APP --> WIDGETS[15个 Textual Widgets]
        APP --> SCREENS[4个 Modal Screens]
    end

    subgraph 适配编排层
        APP --> RUNNER[TUIAgentRunner]
        RUNNER --> FACTORY[TUIAgentFactory]
        RUNNER --> PROJECTOR[TUIRuntimeProjector]
        FACTORY --> JIANMU_AGENT[Agent / SkillNode]
        PROJECTOR --> SNAPSHOT[RunSnapshot]
    end

    subgraph 能力支撑层
        SESSIONS[SessionStore] --> APP
        CONFIG[TUIConfig] --> APP
        APPROVALS[ApprovalCoordinator] --> APP
        SKILLS[SkillsCatalog] --> APP
        TOOLS[ToolAssembly] --> FACTORY
        TRACES[TraceSink] --> RUNNER
    end

    JIANMU_AGENT --> REACTIVE[ReactiveRunner]
    JIANMU_AGENT --> EVENTBUS[RuntimeEventBus]
    EVENTBUS -->|subscribe| PROJECTOR

入口路由:CLI 双通道与依赖检查

__main__.py 实现了双通道入口逻辑。当用户传入 -h/--help 或模型管理类子命令(add-model、list-models、set-default-model、smoke-test、probe-openai)时,直接路由到 cli.py 的无 UI 命令行处理器;否则进入 Textual TUI 通道。

在进入 TUI 通道前,ensure_textual_installed() 会检查 textual 和 rich 两个可选依赖是否已安装,若缺失则抛出清晰的 RuntimeError 提示用户安装。这一设计使得 tui_chat 可以作为可选应用独立于框架核心存在——用户只有在需要终端界面时才需要安装额外依赖。

CLI 子命令系统通过 argparse 构建,涵盖以下无 UI 操作:

命令 功能 是否需要 Textual
add-model 添加或替换模型配置(--name/--provider/--model/--base-url/--api-key-env) 否
list-models 列出已配置模型及默认标记 否
set-default-model 设置默认模型 否
smoke-test 运行一次非 UI 运行时冒烟测试 否
probe-openai 直接探测 OpenAI 兼容端点连通性 否
export-session 将会话导出为 Markdown 否
trace-list 列出最近 trace 文件摘要 否
trace-view 从 trace 文件或 run id 渲染紧凑 waterfall 否
inspect 启动本地 loopback request inspector 查看器 否

其中 smoke-test 很实用:它在无 UI 环境下走通 TUIAgentRunner 的初始化、提交和关闭流程,输出 outcome、status、attempted_model、事件数量、挂起状态和 token 用量,适合用于 CI 校验和问题排查。

UI 组合层:Textual App 的布局、绑定与事件路由

JianmuTUIApp 继承自 textual.app.App,是整个终端界面的根容器。它通过 compose() 方法声明式定义布局层级,通过 BINDINGS 注册全局键盘快捷键,通过 @on 装饰器和 @work 装饰器实现事件驱动的异步任务调度。

双栏布局与组件编排

界面采用经典的左右分栏结构:

┌─────────────────────────────────────────────────┐
│ HeaderBar (标题栏)                                │
├──────────────────────┬──────────────────────────┤
│ MessageHistory       │ TodoPanel                │
│ (对话历史/3fr)        │ (任务进度)                │
│                      │                          │
│ StreamingBar         │ FilePanel                │
│ (流式输出指示)         │ (文件变更)                │
│                      │                          │
│ CommandList          │ CodePanel                │
│ (命令帮助)            │ (代码编辑/1fr)             │
├──────────────────────┴──────────────────────────┤
│ ApprovalBar / AskUserBar (挂起交互栏)             │
│ StatusBar (状态栏)                                │
│ InputBox (输入框 / dock: bottom)                  │
└─────────────────────────────────────────────────┘

左侧占用 3fr(3 份弹性空间),作为主要的对话交互区;右侧占用 1fr,展示辅助信息面板。ApprovalBar 和 AskUserBar 默认隐藏(display: none),仅在运行时挂起时通过 -visible CSS class 切换显隐。

键盘绑定与动作系统

全局 BINDINGS 定义了 6 个核心键盘操作:

绑定 动作方法 功能
ctrl+c action_copy_or_quit 优先复制对话到剪贴板,若无内容则退出
ctrl+q action_quit 直接退出应用
ctrl+s action_save_code_panel 保存代码面板中当前文件
escape action_escape 关闭弹窗 / 取消等待输入 / 取消活跃 worker
a action_approve 批准当前待审批请求
r action_reject 拒绝当前待审批请求

action_escape 采用三段式处理:先尝试关闭 Modal Screen,再尝试取消 awaiting_user_input 状态,最后取消所有活跃 worker,也就是中断当前 agent 运行。action_copy_or_quit 则利用 last_visible_transcript 缓存来避免意外退出。

事件驱动的消息流

Textual 的消息机制被充分利用来实现组件间解耦通信。以下是核心消息类型及其流向:

sequenceDiagram
    participant Input as InputBox
    participant App as JianmuTUIApp
    participant Runner as TUIAgentRunner
    participant Bus as RuntimeEventBus
    participant Widgets as UI Widgets

    Input->>App: CommandSubmitted(text)
    alt 斜杠命令
        App->>App: parse_command → _handle_command
    else 普通对话
        App->>Runner: submit(text)
        Runner->>Bus: subscribe(projector.apply_event)
        Bus-->>App: text.delta → StreamingBar
        Bus-->>App: reply.completed → final_answer
        Bus-->>App: tool.call.* → transcript
        Bus-->>App: approval.requested → ApprovalBar
        Runner-->>App: RunSnapshot
        App->>Widgets: _apply_snapshot
    end

CommandSubmitted 消息由 InputBox 在用户按下回车后发出,携带着原始文本。App.on_command_submitted 首先尝试 parse_command 解析斜杠命令,失败则视为普通 prompt 进入 handle_prompt 异步工作流。

@work(exclusive=True) 的并发控制

所有可能触发 agent 运行的 handler 均标记为 @work(exclusive=True)——这确保同一时刻只有一个异步 worker 在执行。当用户在 agent 运行期间输入新内容时,新 worker 会排队等待而非并发执行,避免了状态竞争。action_escape 中的 self.workers.cancel_all() 则提供紧急中断能力。

适配编排层:从运行时事件到 UI 快照

适配编排层是 TUI Chat 的架构核心,由三个类协同完成 Jianmu 运行时到终端 UI 的映射:TUIAgentFactory 负责组装运行时对象图,TUIAgentRunner 负责编排运行生命周期,TUIRuntimeProjector 负责将运行时事件流投影为 UI 可消费的 RunSnapshot。

TUIAgentFactory:运行时对象图组装

TUIAgentFactory.create() 是运行时对象图的工厂方法,它按依赖顺序组装以下组件:

graph LR
    CONFIG[TUIConfig + ProjectConfig] --> MC[ModelClient.resolve]
    CONFIG --> TOOLS[build_tool_profile + MCPToolProvider]
    TOOLS --> TOOLSET[ToolSet]
    MC --> NODE[SkillNode]
    TOOLSET --> NODE
    CONFIG --> CHK[FileCheckpointer]

    BUS[RuntimeEventBus] --> CTX[RunContext]
    APPROVALS[ApprovalCoordinator] --> CTX
    CONSTRAINTS[Constraints] --> CTX
    MC --> CTX

    NODE --> AGENT[Agent]
    STATEMGR[StateManager/ChatState] --> AGENT
    CTX --> AGENT

关键设计决策:

  1. 模型解析优先级:TUIAgentFactory._resolve_model_client() 优先使用 TUI 配置的 ModelEntry(含 provider、base_url、api_key_env),回退到 ModelClient.resolve(preference=["openai", "litellm"]) 的自动检测逻辑。当 ModelEntry.api_key_env 存在时,工厂会将对应环境变量的值注入 API_KEY / OPENAI_API_KEY,实现模型凭据的按名隔离。

  2. 工具装配:TUI 现在使用统一的 workspace 工具集。除基础四件套(Calculator、FileRead、FileWrite、Bash)外,ListDir、GlobSearch、GrepSearch、FileInfo、StrReplace、ApplyPatch 等编码辅助工具也会始终可用。MCP 工具通过 MCPToolProvider 动态接入并与内建工具集合并。旧 session 中的 tool_profile 元数据会保留用于兼容,但不再改变运行行为。

TUI 里的 read_file 还是一个 app-local 版本:当 grep_search 命中候选位置后,会优先引导模型使用 around_line 加上较小的 before / after 窗口读取局部片段,而不是直接整文件读取。投影层也会把 grep 结果转换成 suggested_read_file_calls,并进一步收紧模型可见的 read_file 返回上限。

  1. 约束配置:TUI 启用 Permission V2,并向 core 提交纯 guard.permission 配置。项目中的旧 tool_policy / tool_rules 会在 TUI host 层迁移成 V2 rules,再参与运行时构建。write_file、str_replace、apply_patch、mkdir、move_path 统一声明为 edit,bash 声明为 bash。所有内建文件系统写操作以及 shell 命令都需要审批;只读文件工具仍可直接执行。

TUIAgentRunner:生命周期编排与挂起恢复

TUIAgentRunner 是适配编排层的门面,封装了 agent 的完整生命周期。

初始化采用延迟模式——构造时仅记录 session 元数据和创建 TUIRuntimeProjector/TUIAgentFactory,实际的 agent 构建在首次 initialize() 时发生。initialize() 支持 force=True 参数以触发完全重建(先关闭旧 bundle 再创建新的),这在模型配置变更或 MCP 重载时使用。

核心运行方法 submit() 和 resume() 共享一致的执行模板:

  1. ensure_ready() 确保 agent 已初始化
  2. projector.begin_run() 创建新的 RunSnapshot
  3. 应用挂起历史消息(_apply_pending_history)
  4. 调用 agent.run_until_suspend() 并传入 SuspensionMode.YIELD——这使运行时在遇到挂起条件时立即返回而非阻塞等待
  5. _capture_result() 将 RunResult 投影为 RunSnapshot
  6. _persist_session_if_needed() 持久化会话

max_ticks=50 限制了单次运行的 tick 上限,防止失控循环。reset_tree=True 确保每次运行从干净的行为树开始,reset_data=False 则保留状态数据的连续性。

普通 prompt 提交和 approval resume 现在还会把 event_driven_checkpoint=True 传给 run_until_suspend()。也就是说,底层 runner 只会在 checkpointable state 或 tree status 真正变化时保存 checkpoint,而不会在每个 RUNNING tick 上重复写入。TUI 仍然保留 file-backed checkpoint,但长工具阶段不会再刷出大量相同 checkpoint。

审批回复统一使用 Core 的 once / always / reject contract。always 会在 workspace scope 下保存精确 action/resource allow rule,并自动解决 其他已被新规则满足的 pending 请求。拒绝也会恢复原运行:工具不会执行, 拒绝结果作为 tool observation 返回模型,模型可以调整方案,而不是由 TUI 提前终止整个任务。

审批栏会展示工具提供的结构化 action、resources、精确持久化范围和限长 diff preview。持久授权需要二次明确确认,并可通过 /permissions 查看和撤销;configured deny 始终优先。

TUIRuntimeProjector:事件到快照的投影引擎

TUIRuntimeProjector 通过订阅 RuntimeEventBus,将 Jianmu 的运行时事件流转化为结构化的 RunSnapshot。它是整个适配层中事件语义最密集的组件:

事件类型 投影行为
text.delta 累积追加到 snapshot.streaming_text
reply.completed 设置 snapshot.final_answer
execution.suspended 设置 snapshot.suspension 和 pending_interaction
approval.requested 插入 system 角色 transcript 消息
tool.call.started 插入 tool_use 类型 transcript 消息
tool.call.completed 插入 tool_result 类型 transcript 消息(ok=true)
tool.call.failed 插入 tool_result 类型 transcript 消息(ok=false)

apply_result() 在运行结束后执行"终态快照"合并——从 RunResult、StateManager、工具诊断、MCP 状态等多个来源聚合最终状态。其中 _build_diagnostic() 方法在 final_answer 为空且运行失败时生成诊断信息,帮助用户理解失败原因(如 "Agent run failed before a reply was produced. model=xxx model_calls=1 last_event=model.call.failed")。

会话持久化:SessionStore 与 SessionRecord

会话系统是整个 TUI 应用的状态骨架。SessionStore 以 JSONL 文件格式将会话持久化到磁盘,SessionRecord 封装单个会话的元数据、消息列表和挂起状态。

存储格式

每个会话存储为一个 .jsonl 文件,第一行为 header(含 _type: "metadata" 标记、SessionMetadata 数据和 suspension 快照),后续每行为一条 normalize_message() 处理后的消息记录。这种格式兼具人类可读性和逐行追加的可扩展性:

{"_type": "metadata", "metadata": {"session_id": "s_abc123...", ...}, "suspension": null}
{"role": "user", "content": [{"type": "text", "text": "帮我分析..."}]}
{"role": "assistant", "content": [{"type": "text", "text": "好的,让我..."}]}

SessionStore.list() 仅解析每个文件的 header 行以构建会话列表,避免全量加载所有消息——这对于会话数量增长后的性能至关重要。resolve_id() 支持精确匹配和前缀匹配,允许用户通过简短前缀快速恢复会话。

会话生命周期

stateDiagram-v2
    [*] --> Ephemeral: 新建空会话(session_id="")
    Ephemeral --> Active: 首次消息触发 _ensure_session
    Active --> Active: submit/resume 后 _persist_session_if_needed
    Active --> Suspended: 运行时挂起(approval/ask_user)
    Suspended --> Active: 用户响应后 resume
    Active --> Saved: 落盘到 JSONL
    Saved --> Active: /sessions 选择后 _swap_session
    Active --> [*]: /exit 或 /quit

_ensure_session() 在首次交互时触发,分配 session_id 和 thread_id(两者使用相同的 s_ 前缀随机 ID),创建时间戳,并重建 runner。_swap_session() 是会话切换的核心——它先保存当前会话(隐含在 _persist_session_if_needed 中),再加载目标会话的全部消息到 UI 并恢复挂起状态。

终端交互能力全景

Slash 命令系统

TUI Chat 实现了一组内置命令和动态发现的技能命令。命令解析由 parse_command() 完成——以 / 开头,空格分隔命令名和参数。load_available_commands() 将内置命令与 SkillsCatalog 发现的技能合并排序。/compact 会立即总结较早的上下文并持久化 active model-history projection;它不等待下一条 prompt,也不会伪造一轮对话消息。

实时过滤是提升可用性的关键设计:当用户在输入框中输入 / 开头的内容时,on_input_changed 事件触发 show_filtered() 方法,在 CommandList 面板中实时展示匹配的命令。这使用户无需记忆完整命令名即可快速发现功能。

审批与人机交互挂起

审批流由 ApprovalCoordinator 作为桥接层实现。它构建一个绑定自身回调的 ApprovalManager 实例注入 RunContext——当运行时工具调用触发审批时,回调将审批请求存入 pending 字典并返回 PENDING 状态,导致 agent.run_until_suspend() 以 SuspensionMode.YIELD 立即返回。

UI 层检测到 snapshot.suspension.reason == "approval_pending" 后显示 ApprovalBar,用户通过键盘 a(批准)或 r(拒绝)触发 action_approve/action_reject,进而调用 runner.resolve_approval() 将决策写入 ApprovalCoordinator 并恢复运行。

ask_user 挂起遵循相同的模式,但额外支持结构化问卷:AskUserBar 解析 payload.questions 列表,为每个问题显示标签,收集用户回答后通过 ResumePayloadSubmitted 消息回传。

流式输出与工具调用可视化

流式输出通过 StreamingBar(一个简单的 Static widget)实现——每次 text.delta 事件触发时累积更新文本,reply.completed 后转为正式的 assistant 消息写入 MessageHistory。

工具调用通过 RichLog.write() 以 Markdown 格式渲染: - Tool call: - Tool call: \tool_name` input: `...`- **Tool result**:- Tool result (ok/error): `tool_name` output: ...`

CodePanel 进一步将文件写入工具的效果可视化为可编辑的代码面板——collect_file_effects_from_event() 从 tool.call.* 事件的 args_preview.path 中提取文件路径,build_file_entries() 合并现有条目(保留 dirty 标记),最终在 CodePanel 中以 ListView + TextArea(带行号和语法高亮)展示。

辅助面板:Todo 与 File Effects

TodoPanel 从当前会话消息和运行时事件中推导进度摘要——最后一条用户消息作为 "Current task",工具调用计数作为 "Tools started/completed"。FilePanel 展示 RunSnapshot.file_effects 中的文件读写记录。两个面板在每次 _apply_snapshot() 时通过 _refresh_side_panels() 更新。

四个 Modal Screen 扩展了功能深度而不增加主界面复杂度:

Screen 触发方式 功能
SessionPickerScreen /sessions 或 /resume 浏览、恢复、删除已保存会话
ModelManagerScreen /model 添加/替换/设默认/删除模型配置
FirstRunWizardScreen 自动(无模型配置时) 首次引导配置模型
DebugScreen /debug 或 /trace 查看 trace 事件摘要和诊断信息

所有 Modal Screen 均绑定 escape 键关闭,遵循 Textual 的 ModalScreen 约定。

模型配置管理

TUIConfig 是 TUI 应用的独立配置模型(区别于框架层的 jianmu.yaml),持久化在 ~/.jianmu_tui/config.yaml。它管理:

  • 模型列表 (models: list[ModelEntry]):每个条目包含 name/provider/model/base_url/api_key_env
  • 默认模型 (default_model):当前会话默认使用的模型名称
  • 技能目录 (skills_dir):覆盖框架的 skills 搜索路径
  • 目录路径 (session_dir/checkpoint_dir/trace_dir):各自默认指向 ~/.jianmu_tui/ 下对应子目录
  • MCP 路径 (mcp_config_path):MCP 服务器配置文件路径
  • 并行探索上限 (max_parallel_tasks):TUI 专属的 parallel_tasks 工具上限,默认允许 2 个只读 explore 子任务并发
  • 上下文压缩 (context_compaction):默认启用,按估算 100,000 token 自动触发。recent tail 同时受 turn 数(默认 2)和 token 预算约束(默认取触发阈值的 25%,并限制在 2,000–8,000 token),keep_recent_messages 仅作为消息数硬上限;摘要输入/输出上限和超时仍可配置。/compact 会立即执行同一条 durable compaction 路径;如果保留 tail 后没有可摘要区间,则改为总结完整历史。

模型配置的运行时生效通过 _rebuild_runner() 实现——每次添加、删除或切换默认模型后,runner 被完全重建,新模型立即生效。模型凭据的隔离通过 ModelEntry.api_key_env 实现:不同模型可指定不同的环境变量名作为 API Key 来源,构建时由 TUIAgentFactory._resolve_model_client() 按名注入。

技能发现与显式调用

TUI Chat 通过 resolve_skills_catalog() 获取 SkillsCatalog 实例,优先使用 TUI 配置的 skills_dir,回退到 SkillsCatalog.resolve_runtime() 的全局发现。发现的技能被转化为 SkillCommand 并合并到 slash 命令列表中。

显式技能调用通过 /skill_name prompt 语法触发:_handle_command 在匹配到技能命令后调用 _run_skill_prompt(),该方法设置 session.metadata.selected_skill 并重建 runner(SkillNode 的 enabled_skills 参数被限定为该技能),然后按标准 prompt 流程运行。

MCP 生命周期管理

MCP(Model Context Protocol)工具通过 MCPToolProvider 动态接入。load_mcp_status() 封装了 provider 的初始化和工具集合并逻辑——成功加载的 MCP 工具以 mcp: 前缀注入 diagnostics,失败的以 mcp:error: 记录。

/mcp reload 命令触发 _reload_mcp(),调用 runner.reload_mcp() → runner.rebuild() 完成工具重载和 agent 重建。/mcp(无参数)显示当前 MCP 状态:配置文件路径、已连接服务器列表、已注册工具列表。

转录与导出

转录系统围绕"结构化内容部件"模型构建,定义了 5 种部件类型:

部件类型 结构 用途
text {"type": "text", "text": "..."} 纯文本消息
thinking {"type": "thinking", "thinking": "..."} 模型思考过程
tool_use {"type": "tool_use", "name": "...", "input": ...} 工具调用请求
tool_result {"type": "tool_result", "name": "...", "content": "...", "ok": bool} 工具调用结果
system {"type": "system", "text": "..."} 系统通知/审批提示

normalize_parts() 负责将各种格式(字符串、列表、遗留格式)统一为部件列表;normalize_message() 确保每条消息的 role 和 content 字段符合存储规范。Markdown 导出由 render_message_markdown() 逐条渲染后通过 export_transcript_markdown() 写入文件。

/copy 命令利用 transcript_to_text() 生成纯文本并通过 Textual 的 copy_to_clipboard() 写入系统剪贴板,/export 命令生成 Markdown 文件到 ~/.jianmu_tui/exports/。

Trace 可观测性

TraceSink 将每次运行的 versioned event envelope 以 JSONL 格式写入 ~/.jianmu_tui/traces/{session_id}_{run_id}.jsonl。submit/resume 使用不同的 TUI run id,resume 还记录 parent_run_id;trace-view 因而可以重建 run → step → model/tool/child span,未闭合 span 标记为 aborted。写盘边界会先统一 sanitize 事件 payload;默认情况下 llm.request.messages 只保存结构、计数和长度元数据,不保存完整 prompt 内容。usage_from_events() 聚合所有 model.call.completed 事件中的 token 用量,生成 UsageSnapshot。

/debug 或 /trace 命令展示当前会话的 trace 摘要,包括 session/model/usage/trace_path/mcp_servers/mcp_tools/tool_diagnostics 等诊断信息。无 UI 的 trace-view 可以从同一 JSONL 文件渲染紧凑 waterfall。设置 JIANMU_TUI_INSPECTOR=1 会启用 normalized request capture,并在 TUI 进程内启动仅绑定 loopback 的实时 SSE viewer(默认端口 8765,可用 JIANMU_TUI_INSPECTOR_PORT 修改);jianmu-tui inspect 仍可脱离 Textual 查看已脱敏导出。inspector/broadcast 错误只做 best-effort 降级,不会终止 Agent 主路径。

上下文、审批与并行 Explore Task

TUIAgentFactory 会根据 session 的 workspace root 注入 PromptRuntimeContext,因此 AGENTS.md 等 workspace bootstrap 文件通过 Jianmu 既有 prompt builder 解析。TUI 自有 hooks 都以 HookFailurePolicy.IGNORE 注册,并在 callback 内提供本地 fallback,覆盖 tool observation projection、外部文件变更提示、非破坏性 working-view compaction 和可选 normalized request capture。手动与自动压缩都通过独立的无工具、非流式内部模型调用总结 middle,失败时降级为确定性摘要。active summary 和消息边界随 session 持久化,而 UI transcript、canonical runtime history、导出和 checkpoint 仍保留完整原消息。

审批仍由 Core ApprovalManager 管理,但 TUI callback 增加 request-scoped one-shot decision 和 workspace-scoped exact persistent grant,持久规则保存在 ~/.jianmu_tui/permissions.sqlite。审批栏展示完整格式化参数、命中 rule/risk 信息(若存在),并提供仅本次允许、对当前 workspace 永久允许同一精确动作、拒绝三种选择。持久规则可通过 /permissions 列出或撤销。

Core ToolExecutor 现在也支持原生 multi-tool batching:每次具体调用得到带原因的 ParallelDecision,相邻安全调用并发执行,不安全调用形成串行 batch;完成事件按真实 wall-clock 顺序发出,canonical tool observation 仍按原始 tool-call 顺序提交。shell、文件写入和持久 REPL 默认串行,参数感知工具可以显式放行只读调用。统一 TUI 工具集包含 parallel_tasks,用于显式 fan-out 多个只读 child exploration。

对于 parallel_tasks,Trace 和 Inspector 事件流会在子任务执行前通过 tui.child_task.scheduling 报告调度决策,其中包括请求与实际并发度、调度模式以及 Provider 并发限制。每个子任务随后依次发出 tui.child_task.started,再发出 tui.child_task.completed、tui.child_task.failed 或 tui.child_task.cancelled。失败子任务会作为该子任务的失败结果返回,不会被静默地显示为成功完成。

与框架层的关系

大多数产品行为仍留在 TUI 层。原生 multi-tool 并发是有意开放的窄 Core 例外:Core 只提供 ParallelDecision 和相邻 concurrent/sequential batch,审批、压缩、inspector 与 UI policy 仍在 apps/tui_chat/。与框架层的关键对接点:

框架层模块 对接方式 TUI 适配层
jianmu.Agent 构造 + run_until_suspend() TUIAgentFactory / TUIAgentRunner
jianmu.engine.events.RuntimeEventBus subscribe(projector.apply_event) TUIRuntimeProjector
jianmu.engine.interaction.RunResult apply_result() 投影 TUIRuntimeProjector
jianmu.engine.state.StateManager 共享 ChatState 实例 TUIAgentBundle
jianmu.engine.deps.PromptRuntimeContext workspace bootstrap 与 skills context apps.tui_chat.context
jianmu.hooks.HookManager TUI 自有 best-effort transform hooks apps.tui_chat.context / inspector capture
jianmu.guard.approval.ApprovalManager 自定义 callback ApprovalCoordinator
jianmu.node.SkillNode 配置构造 TUIAgentFactory
jianmu.model.ModelClient resolve() 工厂方法 TUIAgentFactory._resolve_model_client()
jianmu.tool.builtin.* 直接实例化 ToolAssembly / build_builtin_toolset()
jianmu.mcp.provider.MCPToolProvider initialize() + ToolSet.from_providers() build_tool_profile()

这种"薄壳 + 厚适配层"架构使得 TUI 应用可以独立演进而不阻塞框架层发展,同时框架层的任何 API 变更只影响明确的适配点。

阅读指引

本文档覆盖了 TUI Chat 的完整架构与实现细节。建议按以下路径继续深入: