From 023ffc330a8d6ef068c5308e3a4e353f831e3e9a Mon Sep 17 00:00:00 2001 From: lick <2188718831@qq.com> Date: Mon, 28 Sep 2026 19:23:12 +0800 Subject: [PATCH] fix(macos): prevent Agent panel resource crash and clarify sidebar icon --- ...026-09-25-shared-acp-agent-conversation.md | 3 + docs/development/platform-parity-matrix.csv | 4 +- docs/development/platform-parity-matrix.md | 4 +- macos/Resources/IDEAIcons/NOTICE.txt | 3 + .../IDEAIcons/toolwindows/toolWindowAgent.svg | 7 ++ .../Lithe/Views/Agent/AgentPanelStyle.swift | 30 ++++++- .../WorkbenchModuleUIComposition.swift | 2 +- .../AgentBrandIconResourceTests.swift | 78 +++++++++++++++++++ shared/platform-feature-matrix.json | 11 ++- 9 files changed, 130 insertions(+), 12 deletions(-) create mode 100644 macos/Resources/IDEAIcons/toolwindows/toolWindowAgent.svg create mode 100644 macos/Tests/LitheTests/AgentBrandIconResourceTests.swift diff --git a/.agents/notes/implemented/architecture/2026-09-25-shared-acp-agent-conversation.md b/.agents/notes/implemented/architecture/2026-09-25-shared-acp-agent-conversation.md index 8942b1c3f..494c07eb7 100644 --- a/.agents/notes/implemented/architecture/2026-09-25-shared-acp-agent-conversation.md +++ b/.agents/notes/implemented/architecture/2026-09-25-shared-acp-agent-conversation.md @@ -17,6 +17,8 @@ Agent 对话默认关闭,打开某个项目的 Agent 面板时才启动本机 ## 决策 +- **侧栏入口图标**:Agent 使用 Lithe 原创的“对话气泡 + 星光”线性 SVG,由工作台 renderer 显式绑定,避免落入通用模块图标而让用户误认成插件管理。图标作为固定源资源随应用打包,复用现有模板着色与选中态;不下载、不生成运行时文件,不影响安装包的只读边界。面板内 Codex/Claude 标志仍表示具体供应商。 +- **安装包图标解析**:Agent 品牌图标从应用的 `Contents/Resources/Lithe_Lithe.bundle` 只读加载;资源包或图标缺失时使用默认标志。不要在已安装的 `.app` 中直接调用 SwiftPM(Swift 包管理器)生成的 `Bundle.module`:它只查应用旁的资源包和编译目录,找不到会直接终止进程,开发机器残留的构建资源还会掩盖问题。开发和测试入口保留 SwiftPM 回退;安装版不使用编译目录,也不通过启动时复制资源来修补发行包,避免改变代码签名和 Sparkle 增量更新基线。 - **共享实现**:`rust/lithe-agent-host` 使用官方 `agent-client-protocol` SDK。一个 `AgentHandle` 对应一个项目的 Agent 进程和 ACP 连接,负责初始化、网关登录、会话新建/列出/加载、消息、权限、取消和进程树清理。Mac 通过 Rust Core C ABI(`lithe_agent_open_json`、`lithe_agent_send_json`、`lithe_agent_close`)调用;Windows 以后直接依赖同一个 crate。命令和事件的 JSON 形状由 `shared/fixtures/agent/acp-events-v1.json` 固定。 - **按需启动,跟着项目走**:`LitheAgentConversationModule` 是内置可选模块,默认禁用。每个项目有自己的模块运行时,所以会话天然属于项目。切换标签或窗口不会结束任何会话,后台项目的这一轮会继续跑完;只有关闭项目、关闭功能或退出应用时才停止 Agent。后台项目的会话在等待权限时,项目标签上会显示提醒点。 - **API Key 模式显式登录**:初始化时声明 `auth._meta.gateway = true`,然后只用 `gateway` 方式登录,把服务商地址和 `Authorization: Bearer ` 放进 `authenticate` 请求,经 stdio 传给 Agent。Key 不进命令行参数、环境变量或文件,Lithe 也不设置 `APP_SERVER_LOGS`。Agent 不提供 `gateway` 登录时直接报错,不会退而使用它的账号登录。Codex 使用 Responses,Claude 使用 Anthropic Messages;二者均不静默回退到账户订阅。 @@ -114,6 +116,7 @@ npm 的进度选项只面向终端,HTTP 日志通常在请求完成后才输 ## 验证 +- `./.agents/skills/write-stable-tests/scripts/test-stability-macos.sh -- --filter AgentBrandIconResourceTests`:临时安装包布局覆盖 Codex/Claude 图标加载、资源包和图标缺失时安全回退、缓存隔离,比较读取前后的文件清单与内容,确认不改写发行资源。 - 订阅新增测试覆盖旧配置兼容、显式登录、已有账号、认证通知顺序、登录取消和超时、账号变更、额度真实窗口/缺失值/多 bucket、仅查询不发送 prompt,以及临时失败保留旧值。Linux 已运行 Agent Host 的逐测试计时套件;macOS Swift 编译、真实账号登录及深浅主题/窄宽布局仍须在目标环境验证,不将代码存在等同于运行验证。 - 真实账号验收:先用 API Key 对话,空闲切换 Codex 订阅,确认请求使用本机账号;未登录时确认打开面板不会启动浏览器,点击登录与取消正确;上下文右侧额度每分钟更新,断网后灰显,换账号后旧值清除,关闭项目不残留探测进程。Claude 不出现订阅入口。Windows 已有共享协议和 host,但订阅选择和额度 UI 待接入。 diff --git a/docs/development/platform-parity-matrix.csv b/docs/development/platform-parity-matrix.csv index facdfba4c..46f8d0a81 100644 --- a/docs/development/platform-parity-matrix.csv +++ b/docs/development/platform-parity-matrix.csv @@ -1,8 +1,8 @@ id,area,feature group,capability,macOS implementation,macOS verification,Windows implementation,Windows verification,owner,verification,notes,macOS evidence,Windows evidence -agent-entry-discoverability,AI,Agent 对话,新安装及模块关闭时仍显示 Agent 侧栏入口,面板立即提示关闭或未配置原因并提供设置入口,启停后入口不重复或消失,已实现,待验证,未实现,不适用,Agent,使用无本机模块配置的 macOS 测试账户启动并打开项目,确认 Agent 入口可见、默认开关关闭、面板显示设置指引;开启再关闭后入口和面板保留且不重复。确认读取入口不调用模块 factory、关闭状态无 Agent 连接或进程。Windows 尚无 Agent 对话 UI。,,macos/Sources/Lithe/Application/Features/AgentConversationEntryPolicy.swift; macos/Sources/Lithe/Views/Agent/AgentConversationView.swift; macos/Tests/LitheTests/AgentConversationEntryPolicyTests.swift,windows/tauri/src/features +agent-entry-discoverability,AI,Agent 对话,新安装及模块关闭时仍显示 Agent 侧栏入口,面板立即提示关闭或未配置原因并提供设置入口,启停后入口不重复或消失,已实现,待验证,未实现,不适用,Agent,使用无本机模块配置的 macOS 测试账户启动并打开项目,确认 Agent 入口可见、默认开关关闭、面板显示设置指引;开启再关闭后入口和面板保留且不重复。确认读取入口不调用模块 factory、关闭状态无 Agent 连接或进程。Windows 尚无 Agent 对话 UI。 Agent 侧栏入口使用对话气泡加星光的专用线性图标;在深浅主题、选中与未选中状态下检查可辨识性。,,macos/Sources/Lithe/Application/Features/AgentConversationEntryPolicy.swift; macos/Sources/Lithe/Views/Agent/AgentConversationView.swift; macos/Tests/LitheTests/AgentConversationEntryPolicyTests.swift; macos/Resources/IDEAIcons/toolwindows/toolWindowAgent.svg; macos/Sources/Lithe/Views/Workbench/WorkbenchModuleUIComposition.swift,windows/tauri/src/features agent-conversation-history,AI,Agent 对话,CC GUI 风格历史页:标题与会话 ID 搜索、更新时间、复制 ID、刷新与恢复、收藏与本地重命名、单条与批量移除确认及恢复、筛选、多选批量管理及 Markdown 导出,已实现,待验证,未实现,不适用,Agent,macOS:在深浅主题与窄宽面板查看历史页,验证标题/ID 搜索、空结果、返回保留草稿、刷新失败不丢列表、打开历史恢复会话;验证收藏/重命名重启后保留且不同项目/Agent 隔离;筛选和全选只操作可见项,单条与批量移除先确认,取消不修改记录,确认只移除提示时选中的会话,移除后可恢复且不改上游文件;单个与批量 Markdown 导出等待历史回放完成、保持原选中标签,取消/失败不写部分记录;部分回放后失败再导出必须重新加载并等待完成,断连后的未完整回放不可导出,完整快照断连后仍可导出;关闭项目释放导出等待。消息数量只显示已加载的用户与 Agent 消息,未知总数不伪造。Windows 页面待实现。,,macos/Sources/Lithe/Views/Agent/AgentHistoryView.swift; macos/Sources/LitheAgentConversationModule/Application/AgentHistoryFeatureModel.swift; macos/Sources/Lithe/Platform/MacOS/Agent/MacAgentHistoryAdapters.swift; macos/Tests/LitheTests/AgentHistoryTests.swift,rust/lithe-agent-host/src/lib.rs agent-context-usage,AI,Agent 对话,Agent 上下文用量:输入框圆环百分比、悬停已用/容量 token,未使用时零占位、会话隔离与压缩后更新,已实现,待验证,部分实现,待验证,Agent,macOS:真实 Agent 对话后检查输入框顶部圆环百分比;分别悬停圆环与百分比,立即在上方显示已用/容量 token,移开或切换面板后关闭浮层,窄面板浮层不越界;首次、Agent 未上报或已上报零使用量时显示 0%,悬停仅显示“上下文: 0.0%”,内部缺失状态不伪造 token 或容量;切换会话/Agent、新建会话不串用量,压缩后用量可降低,不累加计费用量;模型确认切换和断连后清除旧容量,重新加载或上报后更新;窄宽面板及深浅主题检查布局和辅助功能。Windows 共享 host 可转发 usage_update,页面待实现。,,macos/Sources/LitheAgentConversationModule/Application/AgentContextUsage.swift; macos/Sources/Lithe/Views/Agent/AgentContextUsageView.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift; macos/Tests/LitheTests/AgentConversationPresentationTests.swift,rust/lithe-agent-host/src/lib.rs; shared/fixtures/agent/acp-events-v1.json -agent-acp-conversation,AI,Agent 对话,可选 ACP Agent 对话:API Key 网关登录、项目会话与历史恢复、模型/权限/思考配置、不依赖凭据的本机默认模型同步、旧默认模型按上游推荐修复与品牌模型图标、CC GUI 风格可搜索模型与权限弹出面板、工具详情与权限、停止确认和进程清理,已实现,待验证,部分实现,待验证,Agent,macOS + Codex + API Key:验证发消息前的上游配置选项和确认;无自定义服务商或 API Key 时确认仍显示本机顶层默认模型、保留密钥缺失错误;修改本机默认模型后重连,连续新建空会话确认不再使用旧导入模型;配置不在上游目录中的旧模型时确认新会话使用目录内的上游推荐值、拒绝与超时不显示假成功且 Codex 模型菜单显示 OpenAI 标志;验证工具输入/输出/文件/变更、权限拒绝、停止确认及超时重连、历史加载失败保留记录;临时项目跑通读文件→修改→执行测试→追问,复用文档观察和脏缓冲区保存保护。检查模型搜索(名称/ID/分组、空结果)、品牌图标/选中状态、无模型说明、权限说明、思考/速度子菜单、确认/失败与会话切换关闭弹出面板;检查项目切换、退出清理,以及 CC GUI 风格空态、搜索、标签、可调整输入区和配置菜单在窄宽面板及深浅主题下的布局。Windows 与 Claude 端到端仍待验证。,,macos/Sources/LitheAgentConversationModule; macos/Sources/Lithe/Views/Agent; rust/lithe-agent-host,rust/lithe-agent-host +agent-acp-conversation,AI,Agent 对话,可选 ACP Agent 对话:API Key 网关登录、项目会话与历史恢复、模型/权限/思考配置、不依赖凭据的本机默认模型同步、旧默认模型按上游推荐修复与品牌模型图标、CC GUI 风格可搜索模型与权限弹出面板、工具详情与权限、停止确认和进程清理,已实现,待验证,部分实现,待验证,Agent,macOS + Codex + API Key:验证发消息前的上游配置选项和确认;无自定义服务商或 API Key 时确认仍显示本机顶层默认模型、保留密钥缺失错误;修改本机默认模型后重连,连续新建空会话确认不再使用旧导入模型;配置不在上游目录中的旧模型时确认新会话使用目录内的上游推荐值、拒绝与超时不显示假成功且 Codex 模型菜单显示 OpenAI 标志;验证工具输入/输出/文件/变更、权限拒绝、停止确认及超时重连、历史加载失败保留记录;临时项目跑通读文件→修改→执行测试→追问,复用文档观察和脏缓冲区保存保护。检查模型搜索(名称/ID/分组、空结果)、品牌图标/选中状态、无模型说明、权限说明、思考/速度子菜单、确认/失败与会话切换关闭弹出面板;检查项目切换、退出清理,以及 CC GUI 风格空态、搜索、标签、可调整输入区和配置菜单在窄宽面板及深浅主题下的布局。Windows 与 Claude 端到端仍待验证。 安装包环境下打开 Agent 面板,确认 Codex/Claude 图标从 Contents/Resources 加载且不依赖构建目录;资源包或图标缺失时显示默认图标而不闪退,读取前后包内文件清单和内容不变。,,macos/Sources/LitheAgentConversationModule; macos/Sources/Lithe/Views/Agent; rust/lithe-agent-host; macos/Tests/LitheTests/AgentBrandIconResourceTests.swift,rust/lithe-agent-host agent-file-references,AI,Agent 对话,从 Finder 或项目树拖入文件到 Agent 输入框,文件选择器、可移除标签、多文件去重和数量限制、文件引用随消息发送及失败保留草稿,已实现,待验证,部分实现,待验证,Agent,macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 原生拖放和 Claude 端到端待验证。,,macos/Sources/Lithe/Views/Agent/AgentComposerView.swift; macos/Sources/Lithe/Views/Agent/AgentFileReferenceList.swift; macos/Sources/LitheAgentConversationModule/Application/AgentFileReference.swift; macos/Tests/LitheTests/AgentFileReferenceTests.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift; rust/lithe-agent-host/src/tests.rs,rust/lithe-agent-host/src/prompt.rs; shared/fixtures/agent/acp-events-v1.json agent-management,AI,Agent 对话,Agent 管理:面板内设置、Node.js/npm 检测、预检清单、ACP 适配器与 Agent CLI 一键安装或升级、一键获取本机 CLI 配置、实时下载数据量/速度/耗时与等待提示、按 CLI 安装来源更新、验证实际升级结果并区分成功警告与失败,已实现,待验证,部分实现,待验证,Agent,在 macOS 打开 Agent 面板右上角的设置:确认显示 Node.js 与 npm 版本,Node 缺失或版本过低时只提示;一键安装、取消安装、更新与卸载 Codex 适配器;CLI 过旧时确认显示 npm/Homebrew/原生来源并通过原安装器升级,npm 环境不匹配或未知来源时只显示手动指引,升级后确认实际 PATH 中的版本达到要求;模拟安装器先下载失败再重试成功但返回非零,确认版本确实提升时显示成功与折叠警告日志,版本未变/仍旧/丢失、取消和超时继续按原语义处理;确认下载数据量、速度、耗时实时更新且未知总量不显示百分比,取消/失败/完成后进度清除;一键获取本机 Codex 配置后在 Agent 面板对话,且提交信息所用服务商不变;Windows 待接入设置界面。,,macos/Sources/Lithe/Views/Agent/AgentPanelSettingsView.swift; macos/Sources/Lithe/Application/Features/AgentManagementFeatureModel.swift; rust/lithe-agent-host/src/install.rs; rust/lithe-core/src/agent/mod.rs; rust/lithe-agent-host/src/npm-progress.mjs; rust/lithe-agent-host/src/cli_update.rs; macos/Tests/LitheTests/AgentManagementFeatureModelTests.swift,rust/lithe-core/src/agent/mod.rs agent-provider-configuration,AI,Agent 对话,Agent 供应商管理:读取本机配置、自定义 Codex/Claude 配置文本、添加与编辑、安全保存密钥、启用与重连、确认删除及取消关联,已实现,待验证,部分实现,待验证,Agent,macOS:验证本机 Codex/Claude 配置读取、刷新与取消关联;添加自定义 TOML/auth JSON 或 Claude settings JSON,检查格式化、输入直引号不被自动替换、弯引号 TOML 错误在保存按钮上方始终可见、非法配置和缺失密钥、取消不保存、编辑保持 ID、删除确认及安全存储失败不丢失配置。在一个窗口保存并等待连接关闭期间,由另一窗口删除或修改同一供应商,确认旧保存失败且不恢复/覆盖供应商、密钥或 Agent 关联。检查窄宽面板、深浅主题、中英文本。空闲切换后确认请求到所选地址和模型;未发送空会话切换或意外退出后重新创建,不向旧 ID 加载,首条消息与文件只发送一次;已有消息、上游列出或成功加载的会话继续恢复,历史加载失败保留记录;正在响应、加载、权限待处理或配置确认时拒绝切换。提交服务商选择不被添加/启用改变。CLI 文件与 bundle 不被写入。Windows 编辑器及 Claude 真实请求待验证。,,macos/Sources/Lithe/Views/Agent/AgentProviderConfigurationView.swift; macos/Sources/Lithe/Platform/MacOS/UI/MacConfigurationTextEditor.swift; macos/Sources/Lithe/Application/Features/AgentProviderConfiguration.swift; macos/Sources/Lithe/Models/AppModel/AppModel+AgentProviders.swift; macos/Tests/LitheTests/AgentProviderConfigurationTests.swift; macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift; macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift,rust/lithe-core/src/ai/agent_configuration.rs; shared/fixtures/agent/provider-configuration-v1.json diff --git a/docs/development/platform-parity-matrix.md b/docs/development/platform-parity-matrix.md index 2c9bb9b60..2e1c35bc8 100644 --- a/docs/development/platform-parity-matrix.md +++ b/docs/development/platform-parity-matrix.md @@ -34,10 +34,10 @@ | 功能组 | 能力点 | macOS | Windows | 负责人 | 验证方式 | 备注 | | --- | --- | --- | --- | --- | --- | --- | -| Agent 对话 | **新安装及模块关闭时仍显示 Agent 侧栏入口,面板立即提示关闭或未配置原因并提供设置入口,启停后入口不重复或消失**
agent-entry-discoverability | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Application/Features/AgentConversationEntryPolicy.swift`、`macos/Sources/Lithe/Views/Agent/AgentConversationView.swift`、`macos/Tests/LitheTests/AgentConversationEntryPolicyTests.swift` | ❌ 未实现
— 不适用
`windows/tauri/src/features` | Agent | 使用无本机模块配置的 macOS 测试账户启动并打开项目,确认 Agent 入口可见、默认开关关闭、面板显示设置指引;开启再关闭后入口和面板保留且不重复。确认读取入口不调用模块 factory、关闭状态无 Agent 连接或进程。Windows 尚无 Agent 对话 UI。 | | +| Agent 对话 | **新安装及模块关闭时仍显示 Agent 侧栏入口,面板立即提示关闭或未配置原因并提供设置入口,启停后入口不重复或消失**
agent-entry-discoverability | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Application/Features/AgentConversationEntryPolicy.swift`、`macos/Sources/Lithe/Views/Agent/AgentConversationView.swift`、`macos/Tests/LitheTests/AgentConversationEntryPolicyTests.swift`、`macos/Resources/IDEAIcons/toolwindows/toolWindowAgent.svg`、`macos/Sources/Lithe/Views/Workbench/WorkbenchModuleUIComposition.swift` | ❌ 未实现
— 不适用
`windows/tauri/src/features` | Agent | 使用无本机模块配置的 macOS 测试账户启动并打开项目,确认 Agent 入口可见、默认开关关闭、面板显示设置指引;开启再关闭后入口和面板保留且不重复。确认读取入口不调用模块 factory、关闭状态无 Agent 连接或进程。Windows 尚无 Agent 对话 UI。 Agent 侧栏入口使用对话气泡加星光的专用线性图标;在深浅主题、选中与未选中状态下检查可辨识性。 | | | Agent 对话 | **CC GUI 风格历史页:标题与会话 ID 搜索、更新时间、复制 ID、刷新与恢复、收藏与本地重命名、单条与批量移除确认及恢复、筛选、多选批量管理及 Markdown 导出**
agent-conversation-history | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentHistoryView.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentHistoryFeatureModel.swift`、`macos/Sources/Lithe/Platform/MacOS/Agent/MacAgentHistoryAdapters.swift`、`macos/Tests/LitheTests/AgentHistoryTests.swift` | ❌ 未实现
— 不适用
`rust/lithe-agent-host/src/lib.rs` | Agent | macOS:在深浅主题与窄宽面板查看历史页,验证标题/ID 搜索、空结果、返回保留草稿、刷新失败不丢列表、打开历史恢复会话;验证收藏/重命名重启后保留且不同项目/Agent 隔离;筛选和全选只操作可见项,单条与批量移除先确认,取消不修改记录,确认只移除提示时选中的会话,移除后可恢复且不改上游文件;单个与批量 Markdown 导出等待历史回放完成、保持原选中标签,取消/失败不写部分记录;部分回放后失败再导出必须重新加载并等待完成,断连后的未完整回放不可导出,完整快照断连后仍可导出;关闭项目释放导出等待。消息数量只显示已加载的用户与 Agent 消息,未知总数不伪造。Windows 页面待实现。 | | | Agent 对话 | **Agent 上下文用量:输入框圆环百分比、悬停已用/容量 token,未使用时零占位、会话隔离与压缩后更新**
agent-context-usage | ✅ 已实现
🔍 待验证
`macos/Sources/LitheAgentConversationModule/Application/AgentContextUsage.swift`、`macos/Sources/Lithe/Views/Agent/AgentContextUsageView.swift`、`macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift`、`macos/Tests/LitheTests/AgentConversationPresentationTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-agent-host/src/lib.rs`、`shared/fixtures/agent/acp-events-v1.json` | Agent | macOS:真实 Agent 对话后检查输入框顶部圆环百分比;分别悬停圆环与百分比,立即在上方显示已用/容量 token,移开或切换面板后关闭浮层,窄面板浮层不越界;首次、Agent 未上报或已上报零使用量时显示 0%,悬停仅显示“上下文: 0.0%”,内部缺失状态不伪造 token 或容量;切换会话/Agent、新建会话不串用量,压缩后用量可降低,不累加计费用量;模型确认切换和断连后清除旧容量,重新加载或上报后更新;窄宽面板及深浅主题检查布局和辅助功能。Windows 共享 host 可转发 usage_update,页面待实现。 | | -| Agent 对话 | **可选 ACP Agent 对话:API Key 网关登录、项目会话与历史恢复、模型/权限/思考配置、不依赖凭据的本机默认模型同步、旧默认模型按上游推荐修复与品牌模型图标、CC GUI 风格可搜索模型与权限弹出面板、工具详情与权限、停止确认和进程清理**
agent-acp-conversation | ✅ 已实现
🔍 待验证
`macos/Sources/LitheAgentConversationModule`、`macos/Sources/Lithe/Views/Agent`、`rust/lithe-agent-host` | 🟡 部分实现
🔍 待验证
`rust/lithe-agent-host` | Agent | macOS + Codex + API Key:验证发消息前的上游配置选项和确认;无自定义服务商或 API Key 时确认仍显示本机顶层默认模型、保留密钥缺失错误;修改本机默认模型后重连,连续新建空会话确认不再使用旧导入模型;配置不在上游目录中的旧模型时确认新会话使用目录内的上游推荐值、拒绝与超时不显示假成功且 Codex 模型菜单显示 OpenAI 标志;验证工具输入/输出/文件/变更、权限拒绝、停止确认及超时重连、历史加载失败保留记录;临时项目跑通读文件→修改→执行测试→追问,复用文档观察和脏缓冲区保存保护。检查模型搜索(名称/ID/分组、空结果)、品牌图标/选中状态、无模型说明、权限说明、思考/速度子菜单、确认/失败与会话切换关闭弹出面板;检查项目切换、退出清理,以及 CC GUI 风格空态、搜索、标签、可调整输入区和配置菜单在窄宽面板及深浅主题下的布局。Windows 与 Claude 端到端仍待验证。 | | +| Agent 对话 | **可选 ACP Agent 对话:API Key 网关登录、项目会话与历史恢复、模型/权限/思考配置、不依赖凭据的本机默认模型同步、旧默认模型按上游推荐修复与品牌模型图标、CC GUI 风格可搜索模型与权限弹出面板、工具详情与权限、停止确认和进程清理**
agent-acp-conversation | ✅ 已实现
🔍 待验证
`macos/Sources/LitheAgentConversationModule`、`macos/Sources/Lithe/Views/Agent`、`rust/lithe-agent-host`、`macos/Tests/LitheTests/AgentBrandIconResourceTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-agent-host` | Agent | macOS + Codex + API Key:验证发消息前的上游配置选项和确认;无自定义服务商或 API Key 时确认仍显示本机顶层默认模型、保留密钥缺失错误;修改本机默认模型后重连,连续新建空会话确认不再使用旧导入模型;配置不在上游目录中的旧模型时确认新会话使用目录内的上游推荐值、拒绝与超时不显示假成功且 Codex 模型菜单显示 OpenAI 标志;验证工具输入/输出/文件/变更、权限拒绝、停止确认及超时重连、历史加载失败保留记录;临时项目跑通读文件→修改→执行测试→追问,复用文档观察和脏缓冲区保存保护。检查模型搜索(名称/ID/分组、空结果)、品牌图标/选中状态、无模型说明、权限说明、思考/速度子菜单、确认/失败与会话切换关闭弹出面板;检查项目切换、退出清理,以及 CC GUI 风格空态、搜索、标签、可调整输入区和配置菜单在窄宽面板及深浅主题下的布局。Windows 与 Claude 端到端仍待验证。 安装包环境下打开 Agent 面板,确认 Codex/Claude 图标从 Contents/Resources 加载且不依赖构建目录;资源包或图标缺失时显示默认图标而不闪退,读取前后包内文件清单和内容不变。 | | | Agent 对话 | **从 Finder 或项目树拖入文件到 Agent 输入框,文件选择器、可移除标签、多文件去重和数量限制、文件引用随消息发送及失败保留草稿**
agent-file-references | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentComposerView.swift`、`macos/Sources/Lithe/Views/Agent/AgentFileReferenceList.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentFileReference.swift`、`macos/Tests/LitheTests/AgentFileReferenceTests.swift`、`macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift`、`rust/lithe-agent-host/src/tests.rs` | 🟡 部分实现
🔍 待验证
`rust/lithe-agent-host/src/prompt.rs`、`shared/fixtures/agent/acp-events-v1.json` | Agent | macOS:从 Finder、项目树拖入临时文件(多文件、中文/空格名)到输入区与上下文栏,检查高亮和可移除标签、去重、数量限制、会话切换和发送失败保留草稿;用隔离项目验证文字+文件与纯文件发送、历史加载后引用正确送到原会话、Agent 读取与权限流程。图片按文件引用处理,无多模态上传。Windows 原生拖放和 Claude 端到端待验证。 | | | Agent 对话 | **Agent 管理:面板内设置、Node.js/npm 检测、预检清单、ACP 适配器与 Agent CLI 一键安装或升级、一键获取本机 CLI 配置、实时下载数据量/速度/耗时与等待提示、按 CLI 安装来源更新、验证实际升级结果并区分成功警告与失败**
agent-management | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentPanelSettingsView.swift`、`macos/Sources/Lithe/Application/Features/AgentManagementFeatureModel.swift`、`rust/lithe-agent-host/src/install.rs`、`rust/lithe-core/src/agent/mod.rs`、`rust/lithe-agent-host/src/npm-progress.mjs`、`rust/lithe-agent-host/src/cli_update.rs`、`macos/Tests/LitheTests/AgentManagementFeatureModelTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-core/src/agent/mod.rs` | Agent | 在 macOS 打开 Agent 面板右上角的设置:确认显示 Node.js 与 npm 版本,Node 缺失或版本过低时只提示;一键安装、取消安装、更新与卸载 Codex 适配器;CLI 过旧时确认显示 npm/Homebrew/原生来源并通过原安装器升级,npm 环境不匹配或未知来源时只显示手动指引,升级后确认实际 PATH 中的版本达到要求;模拟安装器先下载失败再重试成功但返回非零,确认版本确实提升时显示成功与折叠警告日志,版本未变/仍旧/丢失、取消和超时继续按原语义处理;确认下载数据量、速度、耗时实时更新且未知总量不显示百分比,取消/失败/完成后进度清除;一键获取本机 Codex 配置后在 Agent 面板对话,且提交信息所用服务商不变;Windows 待接入设置界面。 | | | Agent 对话 | **Agent 供应商管理:读取本机配置、自定义 Codex/Claude 配置文本、添加与编辑、安全保存密钥、启用与重连、确认删除及取消关联**
agent-provider-configuration | ✅ 已实现
🔍 待验证
`macos/Sources/Lithe/Views/Agent/AgentProviderConfigurationView.swift`、`macos/Sources/Lithe/Platform/MacOS/UI/MacConfigurationTextEditor.swift`、`macos/Sources/Lithe/Application/Features/AgentProviderConfiguration.swift`、`macos/Sources/Lithe/Models/AppModel/AppModel+AgentProviders.swift`、`macos/Tests/LitheTests/AgentProviderConfigurationTests.swift`、`macos/Sources/LitheAgentConversationModule/Application/AgentConnectionModel.swift`、`macos/Tests/LitheTests/AgentConversationFeatureModelTests.swift` | 🟡 部分实现
🔍 待验证
`rust/lithe-core/src/ai/agent_configuration.rs`、`shared/fixtures/agent/provider-configuration-v1.json` | Agent | macOS:验证本机 Codex/Claude 配置读取、刷新与取消关联;添加自定义 TOML/auth JSON 或 Claude settings JSON,检查格式化、输入直引号不被自动替换、弯引号 TOML 错误在保存按钮上方始终可见、非法配置和缺失密钥、取消不保存、编辑保持 ID、删除确认及安全存储失败不丢失配置。在一个窗口保存并等待连接关闭期间,由另一窗口删除或修改同一供应商,确认旧保存失败且不恢复/覆盖供应商、密钥或 Agent 关联。检查窄宽面板、深浅主题、中英文本。空闲切换后确认请求到所选地址和模型;未发送空会话切换或意外退出后重新创建,不向旧 ID 加载,首条消息与文件只发送一次;已有消息、上游列出或成功加载的会话继续恢复,历史加载失败保留记录;正在响应、加载、权限待处理或配置确认时拒绝切换。提交服务商选择不被添加/启用改变。CLI 文件与 bundle 不被写入。Windows 编辑器及 Claude 真实请求待验证。 | | diff --git a/macos/Resources/IDEAIcons/NOTICE.txt b/macos/Resources/IDEAIcons/NOTICE.txt index f1efcf430..5bb5ba097 100644 --- a/macos/Resources/IDEAIcons/NOTICE.txt +++ b/macos/Resources/IDEAIcons/NOTICE.txt @@ -13,3 +13,6 @@ the Community icon set. The source project and these resources are distributed under the Apache License 2.0. Lithe does not include JetBrains product logos or external third-party plugin icons. See LICENSE-APACHE-2.0.txt for the license text. + +Exception: toolwindows/toolWindowAgent.svg is an original Lithe icon, not an +imported JetBrains asset. It follows the Lithe repository license. diff --git a/macos/Resources/IDEAIcons/toolwindows/toolWindowAgent.svg b/macos/Resources/IDEAIcons/toolwindows/toolWindowAgent.svg new file mode 100644 index 000000000..404946fea --- /dev/null +++ b/macos/Resources/IDEAIcons/toolwindows/toolWindowAgent.svg @@ -0,0 +1,7 @@ + + + + + diff --git a/macos/Sources/Lithe/Views/Agent/AgentPanelStyle.swift b/macos/Sources/Lithe/Views/Agent/AgentPanelStyle.swift index e5a46cf66..f01612380 100644 --- a/macos/Sources/Lithe/Views/Agent/AgentPanelStyle.swift +++ b/macos/Sources/Lithe/Views/Agent/AgentPanelStyle.swift @@ -56,21 +56,26 @@ struct AgentBrandIcon: View { @MainActor enum AgentBrandIconLoader { private struct CacheKey: Hashable { + let bundleURL: URL let filename: String let size: Int } private static var images: [CacheKey: NSImage] = [:] - static func image(name: String?, size: CGFloat = 64) -> NSImage? { + static func image( + name: String?, size: CGFloat = 64, + resourceBundle: Bundle? = resolveResourceBundle() + ) -> NSImage? { let filename: String switch name?.lowercased() { case "codex": filename = "openai" case "claude", "claude code": filename = "claude" default: return nil } - let key = CacheKey(filename: filename, size: max(1, Int(size.rounded()))) + guard let resourceBundle else { return nil } + let key = CacheKey(bundleURL: resourceBundle.bundleURL, filename: filename, size: max(1, Int(size.rounded()))) if let image = images[key] { return image } - guard let url = Bundle.module.url(forResource: filename, withExtension: "svg", subdirectory: "AgentIcons"), + guard let url = resourceBundle.url(forResource: filename, withExtension: "svg", subdirectory: "AgentIcons"), let image = NSImage(contentsOf: url) else { return nil } image.isTemplate = true // Native Menu labels read NSImage.size rather than the SwiftUI frame. @@ -78,6 +83,25 @@ enum AgentBrandIconLoader { images[key] = image return image } + + nonisolated static func resolveResourceBundle( + mainBundle: Bundle = .main, + developmentBundle: () -> Bundle = { Bundle.module } + ) -> Bundle? { + // Packaged assets are read-only inputs. SwiftPM's generated accessor looks + // beside the app or in the build tree, not in Contents/Resources. + let packagedURL = mainBundle.resourceURL? + .appendingPathComponent("Lithe_Lithe.bundle", isDirectory: true) + if let packagedURL, let bundle = Bundle(url: packagedURL) { + return bundle + } + // A damaged installed app must use the fallback glyph, never the fatal + // SwiftPM accessor or an unrelated development machine's build output. + guard mainBundle.bundleURL.pathExtension != "app" else { return nil } + let adjacentURL = mainBundle.bundleURL + .appendingPathComponent("Lithe_Lithe.bundle", isDirectory: true) + return Bundle(url: adjacentURL) ?? developmentBundle() + } } struct AgentToolbarButtonStyle: ButtonStyle { diff --git a/macos/Sources/Lithe/Views/Workbench/WorkbenchModuleUIComposition.swift b/macos/Sources/Lithe/Views/Workbench/WorkbenchModuleUIComposition.swift index 105aa507c..585b590f8 100644 --- a/macos/Sources/Lithe/Views/Workbench/WorkbenchModuleUIComposition.swift +++ b/macos/Sources/Lithe/Views/Workbench/WorkbenchModuleUIComposition.swift @@ -32,7 +32,7 @@ enum WorkbenchModuleUIComposition { renderers: [ .init( id: "agent.conversation", - ideaAssetPath: nil, + ideaAssetPath: "toolwindows/toolWindowAgent.svg", isVisible: { $0.workspaceURL != nil }, isSelected: { $0.workbenchFeature.isVisible(.agent) }, // The panel renders its full layout even before the optional diff --git a/macos/Tests/LitheTests/AgentBrandIconResourceTests.swift b/macos/Tests/LitheTests/AgentBrandIconResourceTests.swift new file mode 100644 index 000000000..4e85e96d6 --- /dev/null +++ b/macos/Tests/LitheTests/AgentBrandIconResourceTests.swift @@ -0,0 +1,78 @@ +import AppKit +import Testing +@testable import Lithe + +@MainActor +@Suite("Agent brand icon resources") +struct AgentBrandIconResourceTests { + @Test + func installedAppLoadsBothMarksWithoutDevelopmentResources() throws { + let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + defer { try? FileManager.default.removeItem(at: root) } + let app = try makeApp(at: root) + let resources = try #require(app.resourceURL) + let packagedURL = resources.appendingPathComponent("Lithe_Lithe.bundle") + try FileManager.default.createDirectory(at: packagedURL, withIntermediateDirectories: true) + let source = try #require(AgentBrandIconLoader.resolveResourceBundle()?.resourceURL) + try FileManager.default.copyItem( + at: source.appendingPathComponent("AgentIcons"), + to: packagedURL.appendingPathComponent("AgentIcons") + ) + let before = try contents(at: resources) + let bundle = try #require(AgentBrandIconLoader.resolveResourceBundle(mainBundle: app) { + Issue.record("An installed app must not access the SwiftPM development fallback") + return Bundle.main + }) + #expect(bundle.bundleURL.standardizedFileURL == packagedURL.standardizedFileURL) + for name in ["Codex", "Claude"] { + let image = try #require(AgentBrandIconLoader.image(name: name, size: 16, resourceBundle: bundle)) + #expect(image.isTemplate) + #expect(image.size == NSSize(width: 16, height: 16)) + } + // Reading icons must preserve the signed release/update baseline. + #expect(try contents(at: resources) == before) + } + + @Test + func missingInstalledResourcesUseFallbackEvenAfterCacheIsWarm() throws { + let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + defer { try? FileManager.default.removeItem(at: root) } + let app = try makeApp(at: root) + _ = try #require(AgentBrandIconLoader.image(name: "Codex")) + let bundle = AgentBrandIconLoader.resolveResourceBundle(mainBundle: app) { + Issue.record("Missing installed resources must not call the fatal SwiftPM accessor") + return Bundle.main + } + #expect(bundle == nil) + #expect(AgentBrandIconLoader.image(name: "Codex", resourceBundle: bundle) == nil) + let emptyURL = try #require(app.resourceURL).appendingPathComponent("Empty.bundle") + try FileManager.default.createDirectory(at: emptyURL, withIntermediateDirectories: true) + let emptyBundle = try #require(Bundle(url: emptyURL)) + #expect(AgentBrandIconLoader.image(name: "Codex", resourceBundle: emptyBundle) == nil) + } + + private func makeApp(at root: URL) throws -> Bundle { + let appURL = root.appendingPathComponent("Fixture.app") + let contents = appURL.appendingPathComponent("Contents") + try FileManager.default.createDirectory( + at: contents.appendingPathComponent("Resources"), withIntermediateDirectories: true + ) + let info = try PropertyListSerialization.data( + fromPropertyList: ["CFBundleIdentifier": "test.agent-icons", "CFBundlePackageType": "APPL"], + format: .xml, options: 0 + ) + try info.write(to: contents.appendingPathComponent("Info.plist")) + return try #require(Bundle(url: appURL)) + } + + private func contents(at root: URL) throws -> [String: Data] { + let paths = try FileManager.default.subpathsOfDirectory(atPath: root.path).sorted() + var result: [String: Data] = [:] + for path in paths { + let url = root.appendingPathComponent(path) + let values = try url.resourceValues(forKeys: [.isRegularFileKey]) + result[path] = values.isRegularFile == true ? try Data(contentsOf: url) : Data() + } + return result + } +} diff --git a/shared/platform-feature-matrix.json b/shared/platform-feature-matrix.json index df4308d1d..18b534c04 100644 --- a/shared/platform-feature-matrix.json +++ b/shared/platform-feature-matrix.json @@ -61,7 +61,9 @@ "evidence": [ "macos/Sources/Lithe/Application/Features/AgentConversationEntryPolicy.swift", "macos/Sources/Lithe/Views/Agent/AgentConversationView.swift", - "macos/Tests/LitheTests/AgentConversationEntryPolicyTests.swift" + "macos/Tests/LitheTests/AgentConversationEntryPolicyTests.swift", + "macos/Resources/IDEAIcons/toolwindows/toolWindowAgent.svg", + "macos/Sources/Lithe/Views/Workbench/WorkbenchModuleUIComposition.swift" ] }, "windows": { @@ -72,7 +74,7 @@ ] }, "owner": "Agent", - "verification": "使用无本机模块配置的 macOS 测试账户启动并打开项目,确认 Agent 入口可见、默认开关关闭、面板显示设置指引;开启再关闭后入口和面板保留且不重复。确认读取入口不调用模块 factory、关闭状态无 Agent 连接或进程。Windows 尚无 Agent 对话 UI。" + "verification": "使用无本机模块配置的 macOS 测试账户启动并打开项目,确认 Agent 入口可见、默认开关关闭、面板显示设置指引;开启再关闭后入口和面板保留且不重复。确认读取入口不调用模块 factory、关闭状态无 Agent 连接或进程。Windows 尚无 Agent 对话 UI。 Agent 侧栏入口使用对话气泡加星光的专用线性图标;在深浅主题、选中与未选中状态下检查可辨识性。" }, { "id": "agent-conversation-history", @@ -134,7 +136,8 @@ "evidence": [ "macos/Sources/LitheAgentConversationModule", "macos/Sources/Lithe/Views/Agent", - "rust/lithe-agent-host" + "rust/lithe-agent-host", + "macos/Tests/LitheTests/AgentBrandIconResourceTests.swift" ], "implementationStatus": "implemented", "verificationStatus": "pending" @@ -147,7 +150,7 @@ "verificationStatus": "pending" }, "owner": "Agent", - "verification": "macOS + Codex + API Key:验证发消息前的上游配置选项和确认;无自定义服务商或 API Key 时确认仍显示本机顶层默认模型、保留密钥缺失错误;修改本机默认模型后重连,连续新建空会话确认不再使用旧导入模型;配置不在上游目录中的旧模型时确认新会话使用目录内的上游推荐值、拒绝与超时不显示假成功且 Codex 模型菜单显示 OpenAI 标志;验证工具输入/输出/文件/变更、权限拒绝、停止确认及超时重连、历史加载失败保留记录;临时项目跑通读文件→修改→执行测试→追问,复用文档观察和脏缓冲区保存保护。检查模型搜索(名称/ID/分组、空结果)、品牌图标/选中状态、无模型说明、权限说明、思考/速度子菜单、确认/失败与会话切换关闭弹出面板;检查项目切换、退出清理,以及 CC GUI 风格空态、搜索、标签、可调整输入区和配置菜单在窄宽面板及深浅主题下的布局。Windows 与 Claude 端到端仍待验证。" + "verification": "macOS + Codex + API Key:验证发消息前的上游配置选项和确认;无自定义服务商或 API Key 时确认仍显示本机顶层默认模型、保留密钥缺失错误;修改本机默认模型后重连,连续新建空会话确认不再使用旧导入模型;配置不在上游目录中的旧模型时确认新会话使用目录内的上游推荐值、拒绝与超时不显示假成功且 Codex 模型菜单显示 OpenAI 标志;验证工具输入/输出/文件/变更、权限拒绝、停止确认及超时重连、历史加载失败保留记录;临时项目跑通读文件→修改→执行测试→追问,复用文档观察和脏缓冲区保存保护。检查模型搜索(名称/ID/分组、空结果)、品牌图标/选中状态、无模型说明、权限说明、思考/速度子菜单、确认/失败与会话切换关闭弹出面板;检查项目切换、退出清理,以及 CC GUI 风格空态、搜索、标签、可调整输入区和配置菜单在窄宽面板及深浅主题下的布局。Windows 与 Claude 端到端仍待验证。 安装包环境下打开 Agent 面板,确认 Codex/Claude 图标从 Contents/Resources 加载且不依赖构建目录;资源包或图标缺失时显示默认图标而不闪退,读取前后包内文件清单和内容不变。" }, { "id": "agent-file-references",