# dsh-learn-everything — SPEC 单一事实来源:本插件的目标、已锁定决策、领域模型、seam 落法、wire 契约与验收标准。实现以本文为准;与实现冲突时先改本文再改代码。 ## 1. 定位 **Learning Everything** 是一个 DSH 外部插件(独立 cordis 插件包)。开启后进入**学习模式(Learning Mode)**:用户提问或学习新概念时,模型按**费曼学习法**四步教学(讲解 → 复述 → 判定 → 回讲),教学内容以**结构化 Lesson + 富 HTML 卡片**呈现,并用**卡片选项**让用户答题验证理解。 ## 2. 目标与非目标 ### 目标 - 会话级 `/learn on|off` 切换;开启后注入费曼教学引导,关闭后恢复默认行为。 - 模型通过 `teach` 工具产出结构化教学卡片,Web 客户端渲染为富 HTML(标题、摘要、小节、代码、mermaid 图、类比框、可选 raw HTML)。 - 通过现有 `ask_user_question` 工具向用户出卡片题,模型在下一步自行判定对错并讲解。 - 每个模型可见输入都可在会话日志中重建(model-visible ⟺ logged)。 ### 非目标(v1) - 不做跨会话长期记忆(掌握度、答题历史、当前学习主题)。 - 不做专属 quiz 工具(判定逻辑放在 prompt 层,软约束)。 - 不改动 mainline core(不加新 card kind、不改 agent-loop、不加 session event 类型之外的新事件族)。 - 不做设置页(无用户可配置项之外的持久设置)。 ## 3. 已锁定决策 | # | 决策 | 落地 | |---|---|---| | D1 | 保留 `/learn on|off` 会话级切换 | 镜像 plan mode:`learning/mode` log-only session event + `/learn` 命令;`learning:policy` 段仅在 active 时注入 | | D2 | 结构化为主 + 可选 sanitized raw HTML 逃生舱 | `Lesson` 结构化 schema;`contentHtml` 字段经 DOMPurify 白名单净化后渲染 | | D3 | 复用 `ask_user_question`,模型下一步自行判定 | 不新建 quiz 工具;判定逻辑在 `learning:policy` 引导里,软约束 | | D4 | 富卡片渲染:修订为 keyed `tool.call.toolview`(原决策为 conversation node) | 见 §3.1 | | D5 | 每次会话独立、无长期记忆 | 学习状态仅本会话 logged;resume/fork 按日志折叠恢复 | | D6 | mermaid 客户端渲染成图(M3 决策:引入 vs 保留源码块) | 引入 mermaid.js 并整体打进 client bundle(插件 bundle 路由只服务单文件);约 3.3MB raw / 900KB gzip,opt-in 插件接受该体积;动态 import 只推迟模块执行,不推迟下载/解析 | ### 3.1 D4 修订说明(conversation node → toolview) 最初决策是 conversation node(`ConversationNodeDefinition`)渲染 Lesson 卡片。脚手架阶段深入 client 侧后发现:内置 `tool-call` 节点会对**每个**工具调用渲染一张卡片,conversation node 是**叠加式**的,会给 `teach` 调用渲染出两张卡(通用工具卡 + Lesson 卡)。 而 `ui-tool` 的 `tool.call.toolview` 是官方 **keyed per-tool 视图**槽位(`ui-skill` 即业务方注册范例):按 wire 工具名注册后**替换**该工具的通用卡,未注册名回落通用卡。它同样不动 core、同样纯函数渲染,且无重复卡片问题。因此 v1 采用 toolview 方案;conversation node 保留为未来需求(如独立的"学习面板/模式指示器")的备选方案。 ## 4. 领域模型 | 术语 | 含义 | |---|---| | **Learning mode(学习模式)** | 会话级引导状态(软引导,同 plan mode);active 时注入 `learning:policy` 段并允许 `teach` 执行 | | **Lesson(课程)** | `teach` 工具一次调用产出的结构化教学内容,客户端确定性渲染为富 HTML | | **Teach-back(复述)** | 费曼第 2 步:模型经 `ask_user_question` 请用户用自己的话复述概念 | ## 5. 架构总览 ``` host (server) client (browser) ┌─────────────────────────────┐ ┌──────────────────────────┐ │ dsh-learn-everything 行 │ │ dsh-learn-everything │ │ LearningModeController │ │ ./client bundle │ │ ├─ learning/mode 事件 │ session/ │ LessonToolView │ │ ├─ /learn on|off 命令 │ event 流 │ └─ tool.call.toolview │ │ ├─ learning:policy 段 │ ───────────► │ keyed 'teach' │ │ └─ teach 工具 │ tool/call │ (替换通用工具卡) │ │ └─ (模型) │ 日志 │ project + sanitize │ │ ask_user_question(现有) │ │ (纯函数投影) │ └─────────────────────────────┘ └──────────────────────────┘ ``` - Host:一个全局插件行(`cordis.patch.yml` insert),挂载 controller、工具、命令与 prompt 段。 - Client:同一插件的 browser bundle(`dsh.client` 清单 + ModuleLoader),注册 keyed 工具视图。 - 教学闭环不经过专属服务:模型 → `teach`(出卡)→ `ask_user_question`(复述/答题)→ 模型自行判定 → 按需再次 `teach`。 ## 6. Seam 落法 ### 6.1 模式状态与 `/learn` 命令 镜像 plan-mode 的 logged-state 模式(`packages/plan/plan-mode`): - `learning/mode`(`{ active: boolean }`)为 log-only、whole-value-replace session event;`foldLearningMode(events, end?)` 纯函数折叠恢复,resume/fork/compaction 无需 live mirror。 - `/learn [on|off]` 经 `ctx.commands` 注册;裸 `/learn` 视为 on。 - 用户选择在 turn 内保持 pending,由 `agent/pre-step` listener 在 step 被接受后追加(含失败重试语义),并在模式翻转时注入一条 plugin notice("The user switched this session to learning mode.")。 - 关闭时不删除工具:`teach` 保持注册(request tool catalog 稳定),模式外执行报错(同 `exit_plan_mode` 语义)。 ### 6.2 `learning:policy` prompt 段 - 注册为 `ctx.systemPrompt.section({ name: 'learning:policy', order: 50 })`,active 时渲染配置的引导文本,inactive 渲染空串。 - 默认引导 = 仓库内置的费曼四步教学文案(`src/guidance.ts`,单一事实来源),部署可通过 `Config.section` 覆盖。`Config.section` 校验:非字符串/空白/未知键 → 加载即失败。 ### 6.3 `teach` 工具(Consumer 角色) - 注册于 `ctx.tools`;wire 名 `teach`(`src/constants.ts`,与 client key 共用同一常量)。 - Args = `Lesson` 结构化 schema(title/summary/sections[heading/prose/code?/diagram?/analogy?]/contentHtml?),由 `defineTool` 的 ParameterSchemaSpec 校验;`sections` 非空与"仅学习模式内可调用"在 `execute` 内强制。 - Canonical 结果 `{ ok: true }`;`output.render` 返回固定推进文案(引导模型继续费曼闭环)。 - `presentCall`/`presentResult` 为通用卡(headless/非 Web UI 兜底);富卡片由 client toolview 承担。 ### 6.4 卡片渲染(client toolview) - `src/client/index.ts`:`ctx.slots.inject('tool.call.toolview', ...)` 注册 keyed `teach` 视图,替换通用工具卡。 - `LessonToolView`:从 frozen `block.argsRaw`(`tool/call` 日志中的 JSON 字符串)经 `projectLesson` 纯函数投影为 `Lesson`,投影失败返回 null(保持通用卡兜底路径);渲染标题、摘要、小节、代码块、mermaid 图、类比框、净化后的 `contentHtml`。 - 代码块复用 shell 的 shiki `CodeBlock`(`@deepseek-ai/dsh-client-ui-primitives`,平台静态模块):语法高亮 + 语言横幅 + 复制按钮,未知语言回落纯文本;懒加载语法就绪后自动重渲。 - mermaid 图经 `src/client/mermaid.ts` 惰性单例渲染为 SVG:pending/error 态显示源码块(确定性 replay),ready 态替换为 SVG;渲染串行化(`mermaid.render` 非并发安全)。体积见 D6。 - 样式:插件加载时注入单一 `