Windows 桌面 AI 助理。Agent 循环用 pi.net 的 Pi.CodingAgent, 界面用 TKCode.Arc,交互参考 WorkBuddy:左侧任务与空间, 中间对话,输入框里带模型、权限和工作区选择。
- 流式对话:DeepSeek / OpenAI / Anthropic / 任意 OpenAI 兼容端点(Ollama、LM Studio、vLLM、One-API)。
- 真的会动手:自建 8 个日常办公 / 编码工具 + CodingAgent 内置的 read / grep / find / ls / edit。
- 技能与 MCP:工作区
.pi/skills的SKILL.md按需加载,技能页可从文件夹或 zip 导入;.pi/mcp.json的 MCP 服务端启动时连接。 - @ 直接引用文件:输入框打
@按名字搜当前工作区的文件——文件名精确 > 前缀 > 包含 > 路径 > 字符子序列, 自动跳过node_modules/.git这类目录;选中即引用,发送时才读内容,不用先把文件导入到哪里。 - 粘贴图片:输入框里 Ctrl+V 直接贴图(截图也行),缩略图挂在输入框上方,和文字一起发;
图片落在数据目录的
images/下,会话 JSON 只记路径,存档不会被 base64 撑大。 - 长会话能自己压缩:CodingAgent 的上下文压缩与自动重试,不必手动清空。
- 三档权限:只读 / 工作区 / 完全访问;执行命令会在对话里弹授权卡。
- 工具过程可见:每次调用压成一行(「执行命令:dotnet build」+ 耗时),点开才看参数和结果; 连续十几次调用也只是一列短行,不会把对话淹掉。
- 会话留在本机:一个会话一个 JSON 文件,第一句话一发出就落盘(不用等回复跑完)。 侧栏「任务」列全部任务、按今天 / 昨天 / 最近 7 天分组,「空间」只是按目录再归一次类—— 归了空间的任务在「任务」里照样看得见。
- 工作区一点就换:输入框下面那条点开是「搜索 + 工作区列表」,当前那个打勾;
新建工作空间、打开本地文件夹、不使用工作空间都在这一个浮层里,首页右上角是同一个。
新建任务默认是普通任务,上一次选的空间不跟过来;还没发第一条时挑的空间当场生效,
发过话就钉在那条任务上——中途换目录,前面引用过的文件就对不上了。
普通任务的文件落在
~/.avaloniaassistant(像.claude/.codex那样藏在用户目录里, 第一次用到时才建),不再把整个用户目录当工作区。 - 窗口记着上次的样子:位置、尺寸、侧栏宽度(可拖动分隔条)、界面缩放(90%–125%,Ctrl+滚轮 / Ctrl+0) 和最后看的那条任务,关掉再开直接接上;设置里可选「启动时:首页 / 上次会话」。
- 时间与引用看得清:跨天的消息之间插日期分隔,时间悬停给完整日期; 引用文件是带大小、行数、截断状态的标签,点一下在资源管理器中定位。
- 快捷键有张表:Ctrl+/ 打开;Ctrl+N 新建任务、Ctrl+K 搜任务、Ctrl+F 会话内查找; 审批卡支持 Ctrl+Enter 允许一次、Ctrl+Shift+Enter 本会话允许、Esc 拒绝。
- Markdown:标题、列表、表格、行内代码、代码块高亮。
- 语音输入:NAudio 录音 +
/audio/transcriptions转写(端点需支持)。 - 技能页:卡片式浏览,按分类筛选、关键词搜索,开关与导入都持久化。
- MCP 页:列出
mcp.json里的服务端,并真的起一次进程握手——已连接会给出工具数, 起不来会给出原因(Windows 上npx少了cmd /c包装在这里就能看见),每张卡可重试。 整段粘贴标准 JSON(mcpServers/ VS Code 的servers写法)就能一次导入多个,也能从现成的mcp.json文件读进来;只加一个 stdio 服务端时用表单。 - 导出会话:对话页右上角一键存成 Markdown,工具调用、失败轮次、引用文件都在里面。
| 会话 | 设置 |
|---|---|
![]() |
![]() |
界面用 TKCode.Arc,Agent 循环用 pi.net,其余选型都是围着这两个补的。
| 层 | 框架 | 引用包 | 版本 |
|---|---|---|---|
| UI 框架 | TKCode.Arc(AtomUI 生态) | TKCode.Arc、TKCode.Arc.Fonts |
0.3.0-preview.3 |
| Agent 框架 | pi.net(Pi 的 .NET 移植) | Pi.CodingAgent(传递引入 Pi.Agent / Pi.Ai / Pi.Mcp 等) |
1.4.2 |
- TKCode.Arc 负责界面:控件、主题、图标字体全部来自 Arc,入口在
Program.BuildAvaloniaApp里一次装好——WithArcFonts()加UseAtomUI(b => b.UseDesktopControls().UseArc()), 页面、样式、VM 都在AvaloniaAssistant.UI。 - pi.net 负责 Agent:
Sdk.CreateAgentSessionAsync装配出AgentSession,技能、MCP、 上下文压缩、自动重试都由它带着;模型出入仍由本项目自己的 provider 设置决定, 通过ProviderConfigInput.StreamSimple直连Pi.Ai的流式实现,不读~/.pi/agent/auth.json。 - 权限没有让给框架:CodingAgent 的
BeforeToolCall只服务扩展的tool_call事件, 本项目在它外面串了一层三档权限与授权卡片(PiAgentRuntime.InstallPermissionHook)。 只读模式下连edit都不会出现在工具集里。 - 两边只在一个地方碰面:
AvaloniaAssistant.Desktop这个组合根。UI不引用Pi.*,Agent不引用 Avalonia,换掉其中任何一个都不用动另一边。
两个包都直接走 NuGet,不依赖本机参考仓库路径。Arc 包与本地源码的命名空间差异
(AtomUICommunity vs AIDotNet)及踩坑记录见 docs/research.md。
| 层 | 选型 | 版本 |
|---|---|---|
| 运行时 | .NET | 10.0(net10.0) |
| 基础 UI | Avalonia | 12.1.4 |
| 控件库 | AtomUI.Desktop.Controls(Arc 依赖) | 6.2.8 |
| MVVM | CommunityToolkit.Mvvm | 8.4.0 |
| 语音 | NAudio | 2.2.1 |
要求:Windows 10/11,.NET 10 SDK(在 10.0.401 上验证)。
dotnet restore
dotnet run --project src/AvaloniaAssistant.Desktop首次启动进入首页,点右上角「设置」或提示条里的「去设置」:
- 进「模型设置」配一套:名称(可留空,留空就显示模型名)、Base URL、API Key、模型名;
- 服务商预设只负责带默认值(DeepSeek / OpenAI / Anthropic / 自定义),协议按端点选 Chat Completions / Responses / Anthropic Messages;
- 需要多个渠道就点左边「+ 添加模型」继续加:每套有各自的地址、Key 和协议,互不影响;
- 保存后,输入框下方那颗胶囊里随时切换当前模型。列表里只有你配过的那几套。
预设带出的默认值:
| 预设 | Base URL | 默认模型 | 协议 | 该厂商其它常用模型 |
|---|---|---|---|---|
| DeepSeek | https://api.deepseek.com/v1 |
deepseek-flash |
Chat Completions | deepseek-v4-pro |
| OpenAI | https://api.openai.com/v1 |
gpt-6.1-sol |
Chat Completions | gpt-6-astra(需申请)、gpt-6-luna |
| Anthropic | https://api.anthropic.com |
claude-sonnet-5-5 |
Anthropic Messages | claude-opus-5-5、claude-haiku-5-5 |
| 自定义 | http://localhost:11434/v1 |
qwen3.8:27b |
Chat Completions | qwen3.8-flash(云端 API 名) |
默认走「够用且便宜」的那一档:DeepSeek 用 Flash、Anthropic 用 Sonnet、本地用 27B。要更强就换成
deepseek-v4-pro或claude-opus-5-5,Token 消耗也会跟着上去。模型名填的是厂商 API 的
model字段,不是 App 里的展示名,而且写法各家不一样: DeepSeekdeepseek-flash用连字符,Ollama 的 tag 用冒号(qwen3.8:27b),填错直接 404。 旧的deepseek-chat/deepseek-reasoner已于 2026-07-24 下线,deepseek-v4-flash也已退役 (请求会被转到deepseek-flash,但别再写了)。公司网关、代理这类自建端点选「自定义」:预设里的模型名只是该厂商的示例, App 不会替你猜端点上有哪些模型。旧版单套配置(settings.json 里的扁平 provider/apiKey/...) 会在首次启动时自动搬成一套模型,地址和预设对不上时标为「自定义」。
自定义端点必须是 OpenAI 兼容的
/chat/completions。本地 LLM 不再走 LLamaSharp, 用 Ollama / LM Studio 暴露一个兼容端点即可。
dotnet publish src/AvaloniaAssistant.Desktop -c Release -r win-x64 --self-contained false产物在 src/AvaloniaAssistant.Desktop/bin/Release/net10.0/win-x64/publish/。
Avalonia-Assistant/
├── Avalonia.Assistant.slnx
├── Directory.Build.props # net10.0 / 语言版本 / 依赖版本变量
├── Directory.Packages.props # 集中包版本
├── src/
│ ├── AvaloniaAssistant.Abstractions/ # 契约与 DTO(零依赖)
│ │ ├── IAssistantRuntime.cs # 运行时、授权、会话、设置、语音接口
│ │ ├── AgentContracts.cs # 事件、模型、工具描述、运行请求
│ │ ├── ChatSession.cs # 会话 / 消息 / 工具调用 DTO
│ │ └── AssistantSettings.cs # 设置、provider 预设、权限
│ ├── AvaloniaAssistant.Agent/ # 唯一认识 pi.net 的项目
│ │ ├── PiAgentRuntime.cs # 会话缓存、事件映射、权限钩子、abort
│ │ ├── CodingSessionFactory.cs # ModelRuntime + Sdk 装配(技能 / MCP / 内置扩展)
│ │ ├── CapabilityScanner.cs # 扫 .pi/skills 与 mcp.json,供界面展示
│ │ ├── ToolAccess.cs # 内置 / MCP 工具的安全属性判定
│ │ ├── PiStreamFactory.cs # 测试可替换的 OpenAI / Anthropic 流式入口
│ │ ├── AgentToolRegistry.cs # 自建工具清单 → ToolDefinition
│ │ └── Tools/ # 5 个自建工具(读 / 列 / 搜走 CodingAgent 内置)
│ ├── AvaloniaAssistant.Storage/ # settings.json / sessions/*.json / 技能导入
│ ├── AvaloniaAssistant.Speech/ # NAudio 录音 + 转写
│ ├── AvaloniaAssistant.UI/ # 窗口、页面、VM、样式
│ │ ├── Views/ # MainWindow / Sidebar / Home / Chat / Tools / Mcp / Library / Settings
│ │ ├── ViewModels/ # 命令、会话状态、消息 VM
│ │ └── Styles/ # Tokens.axaml / AppStyles.axaml
│ └── AvaloniaAssistant.Desktop/ # 入口、DI、Arc 初始化
├── tests/AvaloniaAssistant.Agent.Tests/ # 31 个测试
└── docs/
├── research.md # 两个参考项目的结构与集成方式
├── migration.md # 旧功能逐项盘点与取舍
├── architecture.md # 分层、时序、权限、扩展点
└── images/ # README 截图
UI ──► Abstractions ◄── Agent ──► Pi.CodingAgent ──► Pi.Agent / Pi.Ai / Pi.Mcp
▲ ▲
Storage Speech
UI不引用任何Pi.*,只消费IAssistantRuntime、AgentStreamEvent与AgentCapabilities;Agent不引用 Avalonia,工具执行结果只返回文本;Desktop是唯一同时认识两边的组合根。
想换 Agent 框架:实现 IAssistantRuntime 并在 Program.cs 替换注册。
想换 UI:Agent / Storage 不变。详见 docs/architecture.md。
两者都按 CodingAgent 的目录约定发现,不需要写代码:
<工作区>/.pi/
├── skills/<名称>/SKILL.md # 项目级技能,frontmatter 写 name / description
└── mcp.json # 项目级 MCP 服务端
~/.pi/agent/
├── skills/<名称>/SKILL.md # 全局技能
└── mcp.json # 全局 MCP 服务端
- 技能的正文由内置
read工具按需加载,模型只先看到名字和描述;/skill:<名称>也能显式触发。 - MCP 服务端必须写
"exposure": "direct",否则工具只留给 codemode,模型直接调不到。 - Windows 上手写
mcp.json时,npx/npm这类命令必须用cmd /c包一层:
{
"mcpServers": {
"filesystem": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@modelcontextprotocol/server-filesystem", "D:/work"],
"exposure": "direct"
}
}
}原因是 MCP 的 stdio 传输用 UseShellExecute = false 直接起进程,而 CreateProcess 不认
.cmd / .bat 垫片:写 "command": "npx"(或 npx.cmd)时服务端进程根本起不来,
表现是「配置在、工具永远不出现」,且没有明显报错。通过 MCP 页导入(粘贴 JSON 或填表单)时,
CapabilityInstaller 会自动补这层包装,不用自己写;直接手写文件则要自己写 cmd /c。
macOS / Linux 直接写 npx。
- 项目级
.pi属于「需要信任」的配置(技能、MCP、AGENTS.md都会进上下文)。 桌面助理默认信任你选的工作区,settings.json里把TrustWorkspace设为false可以关掉, 关掉后只读全局~/.pi/agent。 - 技能页列出扫到的技能,MCP 页列出服务端并显示这一次真实握手的结果(配置在 ≠ 连上了); 加完文件点「重新扫描」,新配置在下一个会话生效。
- 技能页的「导入文件夹」选一个直接含
SKILL.md的目录;「导入 zip」选一个.zip技能包。 包里可以带一层外层目录,也可以把SKILL.md放在压缩包根上(用包名当技能名); 一个包只装一个技能,重名会拒绝,装完自动让会话重建。校验与解压在Storage/CapabilityInstaller。 - MCP 页的「导入 JSON」认三种写法:
{"mcpServers": {...}}、VS Code 的{"servers": {...}}(含外面再套一层mcp)、以及直接一层「名称 → 配置」。字段原样保留(env/headers/url/cwd都不动),只做三件事:缺exposure补direct、npx这类命令补 Windows 启动层、 校验能起得来;同名默认跳过,勾「覆盖同名」才替换。JSON 里的尾逗号和注释会被容忍。
工具分两拨,但同一件事只留一个实现:读文件、列目录、搜内容交给 CodingAgent 的内置工具,
自建工具(AvaloniaAssistant.Agent/Tools)只补它没有的能力,一共 5 个。
系统信息、进程管理、在浏览器打开这类低频动作用 run_command 或系统自带工具做,
少一个工具就少一次选错。
| 工具 | 说明 | 权限 |
|---|---|---|
write_file |
写工作区文件(含追加、自动建父目录) | 工作区可写 / 只读拦截 |
open_path |
用系统程序打开文件/文件夹 | 只读 |
run_command |
执行 shell 命令(60s 超时) | 需确认 |
fetch_url |
抓网页正文 | 只读 |
current_time |
当前时间与时区 | 只读 |
CodingAgent 提供的内置工具(tools allowlist 里固定启用):
| 工具 | 说明 | 权限 |
|---|---|---|
read |
按行读文件,技能正文靠它加载 | 只读 |
grep |
正则搜内容,遵循 .gitignore |
只读 |
find |
glob 找文件,遵循 .gitignore |
只读 |
ls |
列目录 | 只读 |
edit |
最小替换改文件 | 需确认 / 只读模式不启用 |
write |
新建或整写文件 | 需确认 / 仅完全访问 |
mcp__<server>__<tool> |
MCP 服务端暴露的工具 | 需确认 / 只读模式拦截 |
写文件为什么还留 write_file:内置 write 只在「完全访问」启用,默认权限下新建文件走 write_file,
它带工作区沙箱、支持追加,且在工作区内写入不弹确认;改已有文件的单行才用 edit。
路径默认限制在当前工作区;要越界需要切到「完全访问」。
open_path 不会直接启动 .exe / .bat / .ps1 等可执行文件;需要运行时走 run_command 并确认。
dotnet test tests/AvaloniaAssistant.Agent.Tests覆盖:
- Faux 模型驱动的完整 agent 循环与工具调用;
- 只读模式拦截、授权拒绝、缺 API Key;
- 切换权限后会话重建,只读模式立即生效;
- 项目级技能发现(真实
SKILL.md→ system prompt); - MCP 端到端:测试里起一个 Node stdio 服务端,断言
mcp__demo__echo进入可调用工具集; - 本机 TCP 模拟 OpenAI SSE 端点,验证真实 HTTP 流式与工具声明;
- 文件工具的目录 / 读取 / 越界写入;
- 技能导入:文件夹与 zip(外层目录、根级
SKILL.md、缺清单、多技能、重名)各走一遍; - settings / session 的 JSON 往返。
用于 UI 回归和文档配图,不需要 API Key:
dotnet run --project src/AvaloniaAssistant.Desktop -- --screenshot out.png --page home
dotnet run --project src/AvaloniaAssistant.Desktop -- --screenshot out.png --page chat
dotnet run --project src/AvaloniaAssistant.Desktop -- --screenshot out.png --page settings
dotnet run --project src/AvaloniaAssistant.Desktop -- --screenshot out.png --page tools--page chat 会加载一份内存里的示例会话(不写入 sessions/),用来检查消息、工具卡、表格渲染。
弹层(Popup / Flyout)挂在另一个 TopLevel 上,截主窗口是看不到的,单独有两个入口:
# 把工作区浮层摆进一个无边框窗口再截,检查列表 / 新建 / 搜索三种版式
dotnet run --project src/AvaloniaAssistant.Desktop -- --screenshot out.png --workspace-picker
dotnet run --project src/AvaloniaAssistant.Desktop -- --screenshot out.png --workspace-picker --workspace-picker-create
dotnet run --project src/AvaloniaAssistant.Desktop -- --screenshot out.png --workspace-picker --workspace-picker-query banana
# 在主窗口里真的打开浮层,让弹层自己出图(含 Flyout 外壳和真实定位)
dotnet run --project src/AvaloniaAssistant.Desktop -- --screenshot out.png --workspace-flyout新增的几处界面也走同一套验收参数:
# 快捷键总览
dotnet run --project src/AvaloniaAssistant.Desktop -- --screenshot out.png --shortcuts
# 跨天消息:日期分隔与完整时间(--scroll-top 看最早一条)
dotnet run --project src/AvaloniaAssistant.Desktop -- --screenshot out.png --page chat --chat-dates --scroll-top
# 界面缩放 125%,设置页滚到底
dotnet run --project src/AvaloniaAssistant.Desktop -- --screenshot out.png --page settings --scroll-bottom --scale 1.25
# 授权卡版式;--press-escape 会真的发一次 Esc,验证「Esc 拒绝」
dotnet run --project src/AvaloniaAssistant.Desktop -- --screenshot out.png --page chat --approval-demo --press-escape --scroll-bottom
# 悬停提示的皮:短提示 / 中等长度 / 长路径折行三种样例
dotnet run --project src/AvaloniaAssistant.Desktop -- --screenshot out.png --tooltips
# 隔离数据目录:设置/会话都写到这里,验收启动恢复时不碰真实数据
dotnet run --project src/AvaloniaAssistant.Desktop -- --data-dir .tmp/verify还提供一个开发用的自动发送参数,用来跑真实的端到端链路:
dotnet run --project src/AvaloniaAssistant.Desktop -- --run "用三句话介绍你自己" --screenshot out.png它会加载 %APPDATA%\AvaloniaAssistant\settings.json,走完整的 Agent → HTTP → UI → 会话落盘流程。
%APPDATA%\AvaloniaAssistant\
├── settings.json
├── sessions\<session-id>.json
└── Workspace\ # 默认工作区
删除 sessions\ 下的文件即可清空对应会话。设置页有「打开数据目录」。
- API Key 明文保存在
settings.json,目前没有接 DPAPI;不要在共享机器上使用生产密钥。 - 语音转写依赖端点支持
/audio/transcriptions;DeepSeek 官方端点不支持,会给出错误提示。 - MCP 工具要写
exposure: "direct":不写就只有 codemode / tool_search 能碰到,模型直接调不到。 - 会话存档仍是本项目自己的 JSON:CodingAgent 的会话跑在内存里,压缩结果不跨进程保存;
重启后由
sessions/*.json的历史重建上下文,压缩收益会重新累计。 - 上下文窗口读本地模型目录:从
~/.pi/agent/models-store.json取当前模型的contextWindow, 水位条和自动压缩用同一个值;目录里没有这个模型时退回 128k,界面上会标*并说明是估算。 - 图片要模型支持视觉:不支持的模型由 pi.net 降级成占位符,不会报错,但也看不到图。
- @ 只按文件名和路径匹配:不是全文检索,搜文件里的内容要等后面的索引;二进制文件可以引用但没有正文。
- 没有定时任务 / 专家市场:导航里暂未提供入口,避免假按钮;扩展点见架构文档。
- Windows 优先:代码里对 macOS/Linux 的路径和 shell 做了分支,但只在 Windows 上验证过。
MIT,见 LICENSE。



