这是一个按 Anna 本地开发模型实现的 Mini Notes App。UI 只能在
anna-app dev 提供的 iframe Runtime 中工作,笔记通过
anna.storage.get/set 保存,总结通过以下链路完成:
Anna App iframe
-> AnnaAppRuntime.connect()
-> anna.storage.get/set
-> anna.tools.invoke(...)
-> Notes Summarizer Executa
-> reverse JSON-RPC sampling/createMessage
-> host LLM 或 --mock-sampling fixture
-> summary 返回 UI
项目不需要 Anna 账号、anna-app login、LLM API key 或云端数据库。
.
├── src/
│ ├── components/ # Vue 输入和笔记列表组件
│ └── services/
│ ├── annaRuntime.ts # AnnaAppRuntime.connect / tool identity
│ └── noteStorage.ts # anna.storage.get/set 唯一存储边界
├── public/anna-tool-ids.js # 本地 tool_id,发布时由 Anna 覆盖
├── bundle/ # pnpm build 生成的 Anna UI bundle
├── executas/notes-summarizer-python/
│ ├── executa.json # 本地 Executa 发现配置
│ ├── pyproject.toml # Python entrypoint
│ └── notes_summarizer_plugin.py # v2 双向 JSON-RPC + sampling
├── fixtures/sampling-summary.jsonl # --mock-sampling 离线响应
├── scripts/
│ ├── protocol-smoke.mjs # initialize/describe/invoke 协议测试
│ ├── run-mock-sampling.mjs # anna-app executa dev mock 测试
│ ├── package-executa.mjs # 当前平台二进制归档
│ ├── smoke-binary.mjs # 二进制 describe 冒烟测试
│ └── verify-executa-archive.mjs # 最终归档结构与 manifest 校验
├── .github/workflows/
│ └── release-executa.yml # 三平台 GitHub Release workflow
├── manifest.json # Anna App schema 2 manifest
└── app.json # bundled_executas 映射
建议环境:
- Node.js 22+
- pnpm 10+
- Python 3.10+
- uv
- Windows 打包需要
7z
安装前端和 Anna CLI 依赖:
pnpm install@anna-ai/cli 已放在 devDependencies 中,不需要全局安装
anna-app。
构建 Vue 静态 bundle:
pnpm build产物为 bundle/index.html、bundle/assets/* 和
bundle/anna-tool-ids.js。manifest.json#ui.bundle.entry 指向
index.html,Anna harness 会以 bundle/ 作为静态根目录。
运行类型检查和 Anna manifest 严格校验:
pnpm typecheck
pnpm validatepnpm validate 等价于:
pnpm exec anna-app validate --strict启动无账号、无 LLM 的本地 harness:
pnpm dev该命令执行:
anna-app dev --no-llm在 dashboard 中完成以下检查:
- 新增笔记后输入框清空,列表显示内容和添加顺序。
- 删除笔记后列表立即更新。
- RPC log 中能看到
storage.get和storage.set。 - 点击
Summarize后 RPC log 中能看到tools.invoke。
--no-llm 会关闭 harness 的 LLM/sampling 能力,因此点击
Summarize 后预期出现类似错误:
[-32603] harness started with --no-llm
这表示 UI 已经成功走到
anna.tools.invoke -> Executa -> sampling/createMessage,只是当前
harness 主动禁用了采样,不代表 Executa sampling 实现失败。
manifest.json#ui.host_api.llm 声明 complete,因为当前本地 runtime
会把 Executa 的 reverse sampling 桥接为 host.llm.complete 后再经过
App ACL。前端源码没有调用 anna.llm.complete,唯一总结入口仍是
anna.tools.invoke。
项目没有 localStorage、IndexedDB 或前端规则摘要兜底。直接运行
pnpm preview 只能查看未连接状态,不能创建、删除或总结笔记。
存储边界位于 src/services/noteStorage.ts:
await anna.storage.get({ key: STORAGE_KEY });
await anna.storage.set({ key: STORAGE_KEY, value: cleanNotes });新增和删除都调用同一个 saveNotes。总结前还会重新调用
loadNotes,确保传给 Tool 的内容来自 Anna storage。
本地 anna-app dev 使用 legacy in-memory runtime_state;它用于
无登录 harness 调试,不承诺刷新外层 dashboard 或重启 dev 后仍保留。
生产环境中同一 Host API 会映射到 APS KV。
使用官方 anna-app executa dev --mock-sampling 路径测试后端:
pnpm rpc:invoke等价核心命令为:
pnpm exec anna-app executa dev \
--dir executas/notes-summarizer-python \
--mock-sampling fixtures/sampling-summary.jsonl \
--no-agent --no-image --no-upload \
--invoke summarize \
--args '{"notes":[{"order":1,"content":"修复登录 bug"},{"order":2,"content":"明天跟客户 follow up"},{"order":3,"content":"准备 Workshop 提纲"}]}' \
--jsonWindows PowerShell 不便携地处理上述 JSON 引号,因此
pnpm rpc:invoke 通过 Node 参数数组启动同一条 CLI 命令。
输出中的 model: "mock-mini-notes" 和 fixture 文本证明结果来自
mock sampling response。Tool 内没有规则总结函数。
查看 describe 裸 manifest:
pnpm rpc:describe完整测试 initialize、describe、invoke、health 和 shutdown:
pnpm test:protocolscripts/protocol-smoke.mjs 会直接启动 Python 进程并扮演 host:
- 发送 protocol v2
initialize。 - 验证
capabilities.sampling和client_capabilities.sampling。 - 发送
describe并验证 tool identity 与llm.sample。 - 发送
invoke。 - 捕获 Tool 发出的 reverse
sampling/createMessage。 - 验证 prompt 包含 notes,metadata 包含
executa_invoke_id=manual-smoke-invoke。 - 回写 sampling response,再验证最终 summary 使用该 response。
成功时会显示可审计证据:
[evidence] sampling/createMessage metadata={"executa_invoke_id":"manual-smoke-invoke",...}
[ok] initialize, describe, invoke, health and shutdown passed
不能把 invoke JSON 单独管道给进程后立即关闭 stdin,因为正确的
sampling Tool 会等待 host 对 reverse RPC 的响应。协议脚本保留同一个
stdin reader,同时处理 host 请求和 reverse RPC response。
notes_summarizer_plugin.py 满足以下约束:
- stdin 每行读取一个 JSON-RPC 2.0 frame。
- stdout 只输出 JSON-RPC,且每次写入后 flush。
- 日志只写 stderr。
- 进程持续读取直到 EOF。
invoke在线程池中运行,stdin reader 可继续接收 sampling response。initialize协商 protocol2.0并声明 sampling capability。describe返回裸 manifest,参数使用 Annaparameters[]形态。- manifest 声明
host_capabilities: ["llm.sample"]。 sampling/createMessagemetadata 携带当前invoke_id。
身份映射保持一致:
app.json bundled handle: notes-summarizer
manifest required_executas: bundled:notes-summarizer
manifest host_api.tools: required:bundled:notes-summarizer
frontend handle: notes-summarizer
dev / describe tool_id: tool-test-notes-summarizer-12345678
执行:
pnpm package:executa脚本会:
- 识别本机平台和架构。
- 用
uv run --with pyinstaller构建独立二进制。 - 对二进制发送
describe做 smoke test。 - 生成 archive root
manifest.json。 - 将二进制放入
bin/并声明 entrypoint 和0o755权限。 - macOS 生成
.tar.gz,Windows 生成.zip。 - 从最终压缩包重新读取并校验 root manifest、平台 entrypoint 和权限。
支持的平台 key:
darwin-arm64darwin-x86_64windows-x86_64
Windows 输出示例:
dist/executa/notes-summarizer-windows-x86_64.zip
归档结构:
manifest.json
bin/
└── tool-test-notes-summarizer-12345678.exe
检查 Windows archive:
7z l dist\executa\notes-summarizer-windows-x86_64.zip
7z e -so dist\executa\notes-summarizer-windows-x86_64.zip manifest.jsonworkflow 位于 .github/workflows/release-executa.yml。
触发方式:
- 推送
notes-summarizer-v*tag。 - 在 Actions 页面运行
workflow_dispatch并填写 release tag。
构建矩阵使用当前 GitHub runner labels:
macos-15->darwin-arm64macos-15-intel->darwin-x86_64windows-latest->windows-x86_64
每个平台都会安装依赖、构建并校验 App、运行 reverse sampling 协议
测试、打包当前平台二进制,并再次对二进制发送 describe。最后一个
release job 会把三个文件上传为同一个 GitHub Release assets:
notes-summarizer-darwin-arm64.tar.gz
notes-summarizer-darwin-x86_64.tar.gz
notes-summarizer-windows-x86_64.zip
Actions artifact 只用于 job 间传递,最终交付目标是 GitHub Release。
bundle/是 iframe 加载的工程化前端产物。manifest.json声明 bundle、view、Host API ACL 和所需 Executa。app.json#bundled_executas把 handle 映射到本地 Executa 目录。anna.storage.*在本地使用 runtime_state,在生产映射到 APS KV。anna.tools.invoke把 UI 请求路由到 Executa 的 stdioinvoke。- Executa 的
sampling/createMessage借用 host LLM,不持有 API key。 - binary archive 把同一个 stdio Tool 打包为无需 Python 的发布产物。
Mini Notes 不直接调用外部业务 API,也不收集账号或分析数据。笔记只通过
Anna storage Host API 保存;点击总结时,当前笔记仅通过
anna.tools.invoke -> Executa -> host sampling 链路交给 Anna host
处理。本地无登录 harness 使用进程内的 legacy runtime state。