23 KiB
7-40 Page AI MNote skill library and context tool contract v1
创建时间:2026-05-29
状态:
PROCESSOwner:Page AI skill/tool capability surface + MNote host-side context provider
上位依据:
design/07-ai/done/7-39-page-ai-agent-selector-context-authorization-settings-v1.mddesign/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.mddesign/07-ai/process/7-38-page-ai-sidebar-runtime-owner-split-v1.mddesign/07-ai/done/7-15-page-ai-acp-agent-runtime-unified-layer-v1.mddesign/07-ai/reference/7-12-page-ai-hermes-tool-routing-and-review-surface-v1.md参考代码:
reference-code/PilotDeck/src/context/prompt/PromptAssembler.tsreference-code/PilotDeck/src/context/extension/ExtensionResolver.tsreference-code/PilotDeck/src/tool/builtin/readSkill.tsreference-code/PilotDeck/src/context/input/InputProcessor.ts关键口径:Hermes 和 Reasonix 本身已经是完整 agent,拥有自己的记忆、会话隔离、agent loop、工具调度和运行时状态。MNote 不建设第三套 agent runtime,不把 MNote 上下文、工具说明或页面正文强行注入每条用户 prompt。MNote 要提供一个专用 skill library 和结构化 context tools,让 agent 在需要时自行选择读取。
1. 背景与问题
7-39 已经完成第一轮 Page AI UI 收口:
sidebar-page-ai-runtime.js已有PAGE_AI_AGENT_REGISTRY。PAGE_AI_CONTEXT_REF_REGISTRY已包含current_page / selection / active_editor / file / folder / changed_files。- 发送
/api/hermes/client/runs时 payload 已包含agentId、contextRefs、allowedRoots、editorTarget、runTargetSnapshot。 - SQLite directory grants 和 user preferences 已作为授权与偏好控制面。
但当前暴露出一个更关键的系统问题:
- 简单聊天请求,例如“收到请回复收到”,仍可能触发大量
mnote_doc_fetch等工具调用。 - 当前实现倾向把 MNote page context、tool guidance、runTargetSnapshot 等内容拼成大段 instructions 注入上游 prompt。
- 这种方式会诱导 agent 把所有请求都理解成 MNote 文档任务,也会增加 token、工具误调用和失败噪音。
- UI 上 contextRefs 与授权区域已经可勾选,但后台语义仍混杂:勾选范围被当成“必须塞给模型的上下文”,而不是“允许 agent 按需读取的能力边界”。
因此本稿替代原先以 AgentRunEnvelope 为中心的方案。AgentRunEnvelope 仍保留为 audit / permission / receipt 的宿主侧结构,但不再是默认 prompt 注入中心。
2. PilotDeck 对照结论
2.1 可借鉴模型
PilotDeck 的 PromptAssembler 采用分层上下文:
ExtensionResolver.listSkills()只返回 skill 名称、描述和 namespace。formatSkills()只生成<available-skills>摘要,并明确提示Use the read_skill tool to load the full content of any skill listed below.- skill 正文不默认进入每轮 prompt。
- slash command 由
InputProcessor识别后作为用户意图进入 agent loop,不把所有 command body 默认注入。 - MCP instructions 也是单独
<mcp-instructions>块,不与用户原文混写。
这些模型适合 MNote:
- Page AI 只暴露“MNote 有哪些能力”。
- 详细技能说明通过
mnote_skill.read或 agent 原生 skill 读取。 - 当前页、选区、文件夹、changed files 通过 context tools 按需读取。
- 用户 prompt 保持干净,避免把普通聊天污染成文档任务。
2.2 只作参考实现
PilotDeck 的完整 RouterRuntime、Model Canonical Protocol、AgentLoop、MemoryResolver 和 CompactionEngine 不直接移植。MNote 当前已有 Hermes / Reasonix ACP runtime,不应复制第三套 agent runtime。
可参考但不直接搬运:
ExtensionResolver的只读 contribution snapshot。read_skill懒加载模式。- tool result budget / tool_result_reference 思路。
- permission runtime 的统一决策链。
2.3 不适合 MNote 的内容
- 不做 PilotDeck 式全局模型 router。
- 不做 PilotDeck 式后台 always-on agent runtime。
- 不做独立于 SQLite control-plane 的另一套权限真相。
- 不用定时轮询来刷新 Page AI、文件树或页面树。
- 不把 PilotDeck 的项目 memory dataDir 作为 MNote 默认记忆存储;MNote 若后续做记忆,应按
workspace_id + user_id落 SQLite control-plane 或明确的本地数据目录。
3. 第一结论
本阶段目标从“把 envelope 注入得更干净”改为:
- 建立 MNote 专用 skill library 合同。
- 建立 MNote context tools 合同。
- Page AI 发送时保留用户原文,不把 MNote context/tool guidance 强行拼到 prompt_blocks。
contextRefs只作为用户授权的上下文范围,agent 需要时自行调用工具读取。AgentRunEnvelope只作为宿主侧 audit / permission / tool input snapshot,不默认进入 prompt。- Chat-only agent 不挂载 MNote context tools;Hermes / Reasonix 是否暴露 MNote tools 由用户勾选的
contextRefs与授权边界决定,普通消息是否调用工具由 agent 自己判断。 - UI 继续按 7-39 的 Cline 式模型收敛:输入区只保留
+ / Agent / 上下文 / 停止 / 发送,不显示write · /path黑色授权提示。
4. 产品设计:Page AI 输入区
4.1 默认输入区
Page AI 输入区默认只显示:
[+] [Agent] [上下文] [停止] [发送]
要求:
Agent是按钮 + popover,不在输入区平铺 Hermes / Reasonix / Chat-only chip。上下文是按钮 + popover,不在输入区平铺 6 个 context chip。- 授权区域只在上下文 popover 中展示,不再在输入区显示
write · /mnt/Data1T/mnote黑色提示。 - 输入区不显示 profile、model、gateway、runtime、tool trace 等技术噪音。
4.2 Agent popover
Agent popover 显示:
选择 Agent
(x) Reasonix
适合工作区任务和文件编辑
( ) Hermes
适合 MNote 内置工具和页面操作
( ) Chat-only
只聊天,不申请文件读写能力
选择 agent 后:
- payload 显式携带
agentId。 chat_only强制不挂载 MNote context tools。- Hermes / Reasonix 可挂载 skill/tool capability,但是否读取由 agent 决定。
4.3 Context popover
Context popover 显示:
发送给 AI 的上下文权限
[x] 当前页
允许 agent 按需读取当前 Markdown 页面
[ ] 选区
当前没有选区
[x] 打开资源
允许 agent 按需读取当前打开资源
[ ] 文件
允许 agent 按需读取当前文件
[ ] 文件夹
允许 agent 在授权根内按需读取文件夹
[ ] 最近修改
允许 agent 读取上次 run 的 changed files 摘要
授权区域
write · /mnt/Data1T/mnote
[恢复默认] [完成]
语义:
- 勾选项表达“允许 agent 读取的上下文范围”,不是“强制把内容发给模型”。
current_page勾选后,只代表工具可读取当前页;页面正文不默认塞进 prompt。selection勾选且有选区时,agent 可通过 tool 读取选区文本;选区文本不默认塞进 prompt,除非后续用户明确选择“直接附加选区文本”模式。folder必须由服务端 SQLite grants 重算 allowed roots。
5. MNote Skill Library 合同
5.1 Skill registry
新增 MNote 专用 skill registry,第一阶段可以用静态文件或 Rust 静态 registry,后续可落 SQLite / filesystem。
推荐初始技能:
| skill id | 简述 | 默认 agent |
|---|---|---|
mnote-current-page |
如何读取当前页、判断 dirty/conflict、刷新页面 | Hermes / Reasonix |
mnote-selection |
如何读取和使用选区 | Hermes / Reasonix |
mnote-local-file |
如何解析 MNote target / allowed roots,并让 agent 使用自身文件能力读写本地 Markdown | Reasonix |
mnote-attachments |
如何处理 Markdown 标准链接、图片、PDF/Office 附件 | Hermes / Reasonix |
mnote-workspace-search |
如何在授权 workspace 内搜索文件和引用 | Reasonix |
mnote-agent-receipt |
agent 写入后如何报告 changed files 与刷新需求 | Hermes / Reasonix |
mnote-chat-only |
只聊天时不得调用 MNote 文件/页面工具 | Chat-only |
Skill 元数据:
{
"id": "mnote-local-file",
"title": "MNote local file editing",
"description": "Resolve MNote targets and allowed roots before using the agent runtime's native local file tools.",
"agentIds": ["reasonix", "hermes"],
"readOnly": false,
"requiresContextRefs": ["current_page", "file", "folder"],
"toolNames": ["mnote.context.snapshot", "mnote.context.resolve_target"],
"path": "skills/mnote-local-file/SKILL.md"
}
5.2 Prompt 中只出现摘要
Page AI 上游 prompt 中最多允许出现类似摘要:
<available-skills>
Use mnote_skill.read to load the full content of any skill listed below.
- mnote-current-page — Read the current MNote Markdown page when the task needs page content.
- mnote-local-file — Resolve MNote targets and then use native file tools inside allowed roots.
- mnote-attachments — Resolve and open Markdown attachment links.
</available-skills>
禁止:
- 把每个 skill 的完整正文默认塞入 prompt。
- 把当前页全文默认塞入 prompt。
- 把 runTargetSnapshot 的正文、pageXml、contextBlocks 默认塞入 prompt。
- 把工具长说明拼进用户消息。
5.3 Skill 读取工具
新增或适配工具:
{
"name": "mnote_skill.read",
"description": "Read a MNote skill by id when the task requires this MNote capability.",
"input_schema": {
"type": "object",
"properties": {
"skillId": { "type": "string" }
},
"required": ["skillId"]
}
}
返回:
{
"skillId": "mnote-local-file",
"content": "...SKILL.md...",
"tools": ["mnote_context.resolve_target", "mnote_file.read", "mnote_file.patch"],
"constraints": {
"allowedRootsRequired": true,
"mustReadBackAfterWrite": true
}
}
6. MNote Context Tool 合同
6.1 工具列表
第一阶段建议提供最小工具集:
| tool | 职责 |
|---|---|
mnote_context.snapshot |
返回本次 run 可用上下文摘要,不含正文全文 |
mnote_context.read_current_page |
在 current_page 被授权时读取当前页 Markdown |
mnote_context.read_selection |
在 selection 被授权且存在时读取选区 |
mnote_context.resolve_target |
返回当前 rootUri、relativePath、documentId、file version |
mnote_file.read |
在 allowed roots 内读取文件 |
mnote_file.patch |
在 allowed roots 内 patch 文件,写入后回读 |
mnote_receipt.changed_files |
agent 完成后提交 changed files 或读取 audit 结果 |
6.2 权限原则
- 前端
allowedRoots只作为 UI 展示与用户选择,不作为服务端真相。 - 服务端必须按当前 SQLite user session 重算 grants。
- 每次 tool 调用都按
user_id + workspace_id + contextRefs + allowedRoots判定。 chat_only不注册文件读写工具。- 未勾选
current_page时,mnote_context.read_current_page返回 blocked。 - 未勾选
folder时,mnote_file.read只能读 primary target 或当前文件。
6.3 上下文快照
mnote_context.snapshot 返回示例:
{
"schema": "mnote.context_snapshot.v1",
"runId": "page-ai-run-abc",
"agentId": "reasonix",
"workspace": {
"sourceKind": "local_folder",
"rootUri": "file:///mnt/Data1T/mnote"
},
"contextRefs": ["current_page", "active_editor"],
"primaryTarget": {
"documentId": "local-md:README.md",
"relativePath": "README.md",
"fileVersion": "mtime:size:hash"
},
"availableReads": {
"currentPage": true,
"selection": false,
"folder": false,
"changedFiles": false
}
}
注意:snapshot 是工具返回,不是默认 prompt 注入。
7. AgentRunEnvelope 与 Receipt 的新位置
AgentRunEnvelope 不废弃,但职责调整:
- 宿主侧构建,用于 audit、权限判定、工具输入和 receipt 关联。
- 可以被
mnote_context.snapshot精简返回。 - 不默认进入 ACP
prompt_blocks。 - 不作为普通聊天请求的 system instructions。
AgentRunReceipt 仍保留:
- run 完成后记录 changed files、touchesCurrentFile、shouldRefreshEditor、conflict。
- clean buffer 下 agent 写入当前
.md后触发事件驱动刷新。 - dirty buffer 下进入
external-change-conflict。 - 不通过轮询等待结果;使用 ACP events、watcher、WS/SSE 或现有浏览器事件。
8. 后端数据流
Page AI composer
-> 用户选择 Agent / contextRefs
-> sendPageAiMessage()
-> payload: agentId + contextRefs + editorTarget + runTargetSnapshot metadata
-> 服务端认证 + SQLite grants 重算
-> 构建 host-side AgentRunEnvelope
-> 判断是否挂载 MNote skill/tool capability
- chat_only: 不挂载
- simple chat: 不挂载
- Hermes/Reasonix + 文档任务: 挂载 skill summary + context tools
-> ACP run prompt_blocks 只包含用户原文
-> agent 按需调用 mnote_skill.read / mnote_context.* / mnote_file.*
-> run completed
-> AgentRunReceipt / agentAudit.changedFiles
-> 事件驱动刷新当前页、文件树、页面树或显示 conflict
9. “是否挂载能力”的判定
第一阶段采用保守规则:
暴露 MNote skill/tool capability:
- agentId 是
hermes或reasonix。 - 用户在 context popover 中勾选了
current_page / selection / active_editor / file / folder / changed_files这类 MNote 上下文能力。 - 用户 prompt 原文只用于 agent 自己推理,不由 MNote 侧按关键词分类成“普通聊天 / 文档任务”。
不挂载:
- agentId 是
chat_only。 - 用户没有勾选任何 MNote contextRef。
- 当前没有有效 workspace / rootUri / session。
能力暴露不等于工具调用。MNote 只提供边界、授权和 skill/tool 描述;是否读取当前页、文件或附件由 agent 根据任务自行选择。
10. 与当前半成品实现的差异
当前半成品中已有一些方向需要修正:
acp_stream_events已改为 prompt_blocks 只放用户原文,这是正确方向。should_skip_mnote_tool_context这类短句特判不应保留;应升级成只看 agentId / contextRefs / 授权边界的 capability attach policy。build_run_upstream_body(...).instructions仍可作为 legacy Hermes route 兼容,但不应进入 ACP prompt。toolGuidance不应变成长文本默认 instructions;应拆入 skill 正文,按需读取。AgentRunEnvelope应保留为 audit/tool input,不作为默认 prompt 文本。
11. 验收 Checklist
Batch A:冻结现状与 PilotDeck 对照
- 记录当前“收到请回复收到”触发工具调用的真实浏览器证据。证据:用户截图/反馈记录了旧 UI 下短消息出现大量工具调用;本轮用
task502-page-ai-agent-selector-context-smoke固化回归断言:短消息无 tool card,payload 不默认上传正文。 - 记录当前 ACP payload / prompt_blocks 是否包含 MNote instructions。证据:
acp_stream_events现只构造用户原文ContentBlock::Text,不再调用build_run_upstream_body(...).instructions塞入 ACP prompt。 - 对照 PilotDeck
PromptAssembler.formatSkills(),确认 MNote 只注入 skill 摘要。证据:参考reference-code/PilotDeck/src/context/prompt/PromptAssembler.ts,MNote 新增 skill registry 摘要与mnote.skill.read懒加载。 - 对照 PilotDeck
InputProcessor,确认 slash/command 不强行注入所有 command body。证据:参考reference-code/PilotDeck/src/context/input/InputProcessor.ts,本轮未把 MNote skill 正文塞入普通用户 prompt。
Batch B:Skill registry 设计与最小实现
- 新增 MNote skill metadata registry。证据:
rust/crates/mnote-web/src/hermes_tools/skill.rs。 - 新增
mnote_skill.read或等价工具。证据:mnote.skill.readmanifest + route dispatch。 - 添加
mnote-current-page、mnote-local-file、mnote-chat-only三个最小 skill 正文。证据:skills/mnote-*/SKILL.md。 - 单测覆盖 skill id lookup、未知 skill、agentId 过滤。证据:
cargo test --manifest-path rust/Cargo.toml -p mnote-web skill_lookup -- --nocapture。
Batch C:Context tools 合同
- 新增或收口
mnote_context.snapshot。证据:mnote.context.snapshotmanifest +context_tools::context_snapshot。 - 新增或收口
mnote_context.read_current_page。证据:mnote.context.read_current_pagemanifest + current_page contextRef guard。 - 新增或收口
mnote_context.resolve_target。证据:mnote.context.resolve_targetmanifest + route dispatch。 - 工具返回结构化 JSON,不把正文混入 prompt。证据:context snapshot / resolve target 只返回 JSON;读取正文必须显式调用 read_current_page。
- Rust 定点测试覆盖 contextRefs 权限。证据:
cargo test --manifest-path rust/Cargo.toml -p mnote-web context_ref_enabled -- --nocapture。
Batch D:Capability attach policy
- 实现
PageAiCapabilityPolicy或等价函数。证据:page_ai_capability_policy。 chat_only永不挂载 MNote 文件/页面工具。证据:policy 单测 + Reasonix wrapper chatLoop 使用空 ToolRegistry。- MNote 不按“收到/你好/总结当前页”等 prompt 文本强行分类能力;普通消息是否调用工具由 agent 自己判断。证据:
page_ai_capability_policy_exposes_selected_context_without_prompt_classifying、capability_policy_does_not_text_classify_ack_prompt。 - 只在 Hermes / Reasonix 且用户勾选 MNote contextRefs 时暴露 skill/tool capability;没有 contextRefs 或 Chat-only 时不挂载。证据:
page_ai_capability_policy_does_not_attach_without_context_refs、page_ai_capability_policy_never_attaches_for_chat_only。 - 单测覆盖 selected contextRefs、无 contextRefs、Chat-only、ack prompt 不文本分类四类输入。证据:
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_capability_policy -- --nocapture。
Batch E:ACP prompt 收口
- ACP
prompt_blocks只包含用户原文和用户显式附件,不包含 MNote tool guidance。证据:acp_stream_events只传input.to_string()。 - skill 摘要如果需要进入 system/context,只作为短
<available-skills>。证据:Reasonix wrapper mnoteLoop system prompt 仅保留短摘要,正文走mnote_skill_read。 AgentRunEnvelope不进入 prompt_blocks。证据:ACP path 不再使用 upstream instructions。build_run_upstream_body的 legacy instructions 不再被 ACP path 直接使用。证据:acp_stream_events不调用build_run_upstream_body。
Batch F:UI 收口
- Agent selector 改为
+旁边的 Agent 按钮 + popover。证据:task502-page-ai-agent-selector-context-smoke通过。 - Context refs 改为
+旁边的上下文按钮 + popover。证据:task502-page-ai-agent-selector-context-smoke通过。 - 输入区不显示
write · /path黑色授权提示。证据:截图tmp/task502-page-ai-agent-selector-context-smoke/01-agent-selector-context.png。 - 默认聊天面不显示 profile/model/gateway/runtime/tool trace。证据:
task502-page-ai-agent-selector-context-smoke断言通过。 - 浏览器截图验证 popover、选中态、输入区清爽度。证据:
tmp/task502-page-ai-agent-selector-context-smoke/01-agent-selector-context.png。
Batch G:Tool card 降噪
- 普通聊天不显示 MNote tool card。证据:
task502-page-ai-agent-selector-context-smoke对“收到请回复收到”断言无 tool card。 - Page AI run payload 不默认上传
pageText/pageXml/contextBlocks/selectedText/evidence正文;当前页/选区正文必须由 agent 通过 context tools 按需读取。证据:pageAiPageContextForRefs()默认裁剪正文,task502-page-ai-agent-selector-context-smoke断言通过。 - tool 调用失败时卡片默认折叠,显示简短错误和展开入口。证据:tool message 使用
<details class="wolai-page-ai-tool-details">,默认不加open。 - debug/raw trace 进入高级/调试区,不污染默认聊天。证据:trace/auditId 只在 tool
<details>内显示;默认聊天 smoke 截图无 runtime/raw trace。 - “收到请回复收到”真实浏览器测试只显示用户消息与 assistant 回复。证据:
task502-page-ai-agent-selector-context-smoke通过并生成截图。
Batch H:Receipt 与事件驱动刷新
- 保留
AgentRunReceipt/agentAudit.changedFiles。证据:local_agent_audit_event写入agentRunReceipt,cargo test --manifest-path rust/Cargo.toml -p mnote-web local_agent_audit_event_carries_agent_run_receipt -- --nocapture。 - clean buffer agent 写入后事件驱动刷新当前页。证据:前端优先消费
agentRunReceipt.refresh.touchesCurrentFile并派发mnote:page-ai-tool-write-completed,document-session-runtime.js负责 clean refresh / dirty conflict。 - dirty buffer agent 写入后进入 conflict。证据:
document-session-runtime.js的refreshDocumentSessionsFromExternalWrite对 dirty / saving / recent input 标记external-change-conflict,事件来源改为 receipt。 - 不引入定时轮询。证据:本轮只使用 ACP SSE
run.completed、浏览器 CustomEvent、现有 watcher/WS/SSE 事件,没有新增setInterval或轮询。 - changed files 与文件树/页面树刷新走 watcher、ACP event、WS/SSE 或现有事件。证据:
agentRunReceipt.changedFiles转为tree:local-folder-watch-batch,由sidebar-tree-live-apply-runtime.js事件驱动刷新 affected parents。
Batch I:验证矩阵与归档
- Rust targeted tests:skill registry、capability policy、context tool auth。证据:
page_ai_capability_policy、skill_lookup、context_ref_enabled定点测试通过。 - JS
node --check:Page AI runtime 与 smoke。证据:node --check scripts/reasonix-acp-wrapper.mjs rust/crates/mnote-web/browser/sidebar-page-ai-runtime.js rust/crates/mnote-web/browser/sidebar-tree-runtime.js scripts/task502-page-ai-agent-selector-context-smoke.js。 - 浏览器 smoke:Agent popover、Context popover、纯聊天无工具调用、payload 不默认上传正文、receipt 事件驱动刷新。证据:
node scripts/task502-page-ai-agent-selector-context-smoke.js。 - 浏览器截图:输入区不显示授权黑 chip。证据:
tmp/task502-page-ai-agent-selector-context-smoke/01-agent-selector-context.png。 git diff --check。codegraph sync .,必要时codegraph index . --force。- checklist 全部完成后移动到
design/07-ai/done/。
12. 归档条件
满足以下条件后,本稿可移动到 design/07-ai/done/:
- Page AI 已采用 MNote skill library + context tools 模型。
- ACP 用户 prompt 保持干净,不再默认注入 MNote instructions。
- 纯聊天请求不会触发 MNote doc/page/file 工具。
- 文档任务仍能通过 skill/tool 按需读取当前页或文件。
- Agent / Context UI 已收敛为输入区按钮 + popover。
- 真实浏览器截图与 smoke 证明默认聊天面没有冗余技术噪音。