diff --git a/AGENTS.md b/AGENTS.md index 88570f030..513c8b764 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -112,4 +112,6 @@ - 兜底值的单一 owner 与失败归因:当同一个业务值(工作目录、根路径、身份、超时)既被**预备阶段**(草稿 / 预览 / 校验)消费、又被**执行阶段**(提交 / 启动 / 发送)消费时,它必须由同一个 resolver 从同一个请求源解析。触发条件:两阶段各自算一遍,且其中一个带兜底(如 stdio 缺目录时退到用户主目录)而另一个不带。原生期望行为是兜底规则连同其**分类依据**(此处为传输类型)一起收进那个 resolver,调用方只传原始请求值与分类依据。**此类缺陷最隐蔽的形态是「预备阶段成功、执行阶段失败」**:草稿能建、按钮亮着、选择器认为选择合法,用户点下去才失败,且失败文案是「请稍后重试」——而重试永远不会让缺失的值长出来。因此不可重试的失败必须呈现 resolver 自己的原因(「请先选择远程目录」而非「请稍后重试」),并且**不得只靠一道守卫**:既有的选择器/命令门禁拦住的是它能判定的子集,resolver 守卫是第二道,二者断言不同契约、不可互相替代。禁止做法:为「不是真实实体」的合法哨兵值(未归类 / 全部 / 默认)返回 null 再让下游当错误;在执行阶段新写一份兜底;把不可重试失败呈现为可重试;留下第二个 resolver 并让其唯一调用点传常量参数(它退化成 Trim 包装,看起来仍是「架构层」)。验证覆盖:哨兵值 + 各传输类型的组合各跑一遍(本地兜底必须成功启动、远程必须拒绝并给出原因);**反向验证必须把缺陷种回去**并确认新增测试见红(仅新测试全绿不构成证据);删除第二个 owner 时以 fail-closed 门禁断言其文件不存在且执行阶段只出现唯一 resolver 调用;以及审计**所有**触达执行阶段的既有测试——它们可能把缺陷行为写成断言(如 `Assert.Null(cwd)`),也可能因为夹具从未配置分类依据而在守卫前 return,使 `await 某个只在下游置位的 TaskCompletionSource` 永久挂死:表现为整套测试**无任何失败输出地超时**,而单跑新测试却是秒级全绿。 - 测试进程级监听器与可释放资源隔离:当测试创建会注册进程级 listener、provider、event source 或其它全局回调的 `IDisposable` 对象时,每个用例必须在自身作用域内确定释放。原生期望行为是用例结束后全局注册恢复到进入前的状态,后续用例的采样、事件与回调只由自身夹具决定;禁止依赖串行执行、GC、终结器、平台速度、重试或固定延时清理泄漏,也禁止把多个 live listener 聚合后的全局标志误当成单个 provider 的裁决。验证覆盖同一测试类完整顺序、多轮进程级重复运行和完整程序集;诊断时用受控的共存 listener 量化污染率,并在交付前删除临时探针。 + - 入站交互的应答与展示所有权:当 agent 请求需要用户输入,而 UI 无法展示、只承载一张表单的 UI 已有未答表单或响应发送失败时,请求必须由同一 pending owner 收敛;不可展示的请求按协议允许的取消结果应答,连接仍有效时已展示表单保留到成功响应。连接仍有效时发送成功才移除 pending,失败允许重试,取消意图不得因在途发送失败而丢失;清理同时核对请求实例与连接归属,URL completion 按连接关联并忽略未知或已完成 ID。禁止静默丢弃、覆盖未答表单、按会话或复用 ID 清除新请求、用按钮状态代替协议层单次应答门控。验证覆盖真实传输响应、无 sessionId、未知必填字段、同时到达的表单、发送失败与取消竞态、断连重连和 ID 复用。 + - 短命令的管道与进程收尾:当版本探测或平台授权服务启动重定向 stdout/stderr 的命令时,共享 runner 必须并发排空两条流、限制保留输出,并在调用方取消或超时时终止仍归属该进程树的命令、限时等待退出、观察所有读取任务;调用方取消时,清理失败保留在取消异常的内层原因中。禁止只读一条流、用 Process.Dispose 代替终止、让清理异常改写取消语义,或声称退出父进程的 PID 能回收已被系统重新托管的子进程。验证使用真实管道覆盖超出容量的双流输出、调用方取消、超时、终止失败及父退出后子进程持有管道;夹具记录并回收自身 PID,不按进程名清理。 - 安装器拥有命令的 PATH 注册:当同一产品既分发 GUI 又分发命令行入口时,命令必须由**每个安装包**用该平台安装器自己的机制注册(MSIX 的 `windows.appExecutionAlias`、MSI 的 `Environment` 表、dpkg 拥有的符号链接、pkg 的 postinstall),且命令二进制必须由单一 publish 脚本产出后被各安装包嵌入。禁止做法:脚本编辑 `.bashrc` / `.zshrc` / 用户 PATH 字符串或调 `setx`(安装、升级、卸载必须同属一个所有者);每条打包链各自构建命令二进制(四份产物都能构建成功,正是它们开始漂移的形态);把注册断言写成只在打 tag 时才跑的内联脚本。验证方式:注册必须对**本次构建产出的安装物**断言(读包内清单、`Environment` / `File` 表、文件列表),断言规则要与读取产物的平台 API 分离,使其正反例能在任意平台的每次 push 上跑;能真装真卸的平台必须有安装→命令解析→卸载→残留检查全链门禁;符号链接必须断言**解析结果**而非链接文本(相对深度错误只在解析时暴露,包的文件列表看不出来);平台把辅助可执行文件放进包内哪个目录若非文档化契约,消费方必须探测候选位置并报告命中项,禁止赌一个位置。唯一例外是**该平台的包格式没有安装钩子**(macOS 拖拽 .app):此时应用自身可以拥有该注册,但必须写与该平台安装器**同一路径、同一形状**(先删再链),必须同时提供移除入口,且该逻辑要拆成可在任意平台用假根跑的脚本/服务 —— 应用与安装器各写一套是同一个双所有者缺陷。 diff --git a/BUILD_GUIDE.md b/BUILD_GUIDE.md index a83a4f9a4..5409d8833 100644 --- a/BUILD_GUIDE.md +++ b/BUILD_GUIDE.md @@ -371,7 +371,9 @@ scripts/gates/run-wasm-smoke-gates.sh Debug - 设置持久化:通过 UI 切换应用语言并修改外观、数据与存储、快捷键、ACP 和 MCP 服务器状态,验证 shell 重载后的 `x:Uid` 与 singleton ViewModel 文案,并在刷新后从可见设置页确认状态仍存在; - ACP / 平台能力边界:保存 ACP WebSocket profile,验证 WASM 不声明 `clientCapabilities.fs` / `terminal=true`,并确认受限平台不会暴露桌面文件系统入口; - Gamepad 能力边界:确认 BrowserWasm 通过浏览器 Gamepad API 投影标准手柄读数,并验证 DPad / A 键进入 Diagnostics 的 pressed 槽位与 ActiveInputs 意图事实(`MoveDown` / `Activate`)。原生焦点由键盘/XYFocus 与控件 consumer 拥有;壳层不再对轮询手柄做全局 SendInput / FocusManager 焦点 bridge,避免双路径分发; -- WASM ACP 全链路:用同一 profile 和 remote directory 从 Start 页面创建远端会话,断言 mock ACP Server 收到 `initialize`、`session/new`(`cwd` 为所选 remote path)和 `session/prompt`,并确认 agent reply 投影到 Chat UI。 +- WASM ACP 全链路:用同一 profile 和 remote directory 从 Start 页面创建远端会话,断言 mock ACP Server 收到 `initialize`、`session/new`(`cwd` 为所选 remote path)和 `session/prompt`,并确认 agent reply 投影到 Chat UI;在 `session/new` 完成前验证请求级 elicitation 收到 `cancel`,再通过真实表单填写验证必填校验、字符串与布尔值回包,以及未知必填字段禁止提交且可取消。 + +设置持久化与 ACP 全链路在刷新前,只读等待本轮配置出现在 IDBFS 的 IndexedDB 已提交记录中,再从重载后的 UI 验证;内存 `FS.readFile`、按钮恢复可用或固定等待均不代表落盘完成,gate 不代应用调用 `syncfs`。 它补充 Windows FlaUI gate,专门覆盖 WASM 浏览器里的原生 Uno 控件行为与当前构建产物的浏览器持久化链路。 diff --git a/SalmonEgg/SalmonEgg/DependencyInjection.cs b/SalmonEgg/SalmonEgg/DependencyInjection.cs index f1f7c502d..d7a418c6b 100644 --- a/SalmonEgg/SalmonEgg/DependencyInjection.cs +++ b/SalmonEgg/SalmonEgg/DependencyInjection.cs @@ -26,6 +26,7 @@ using SalmonEgg.Infrastructure.Services; using SalmonEgg.Infrastructure.Storage; using SalmonEgg.Infrastructure.Transport; +using SalmonEgg.Presentation.Core.Localization; using SalmonEgg.Presentation.Core.Mvux.Chat; using SalmonEgg.Presentation.Core.Mvux.ShellLayout; using SalmonEgg.Presentation.Core.Resources; @@ -512,6 +513,7 @@ private static void RegisterInfrastructureServices(IServiceCollection services) ? new TerminalSessionManager() : new UnsupportedTerminalSessionManager()); #endif + services.AddSingleton(); services.AddSingleton(); // ACP setup wizard. The catalog is pure data and safe everywhere; every probing, installing and @@ -942,6 +944,9 @@ private static void RegisterInfrastructureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); #endif + // Intentional exception to coding-standards §3.1: these commands operate on one machine-wide + // registration. Settings page instances share the command owner and IsBusy so their buttons + // observe the same in-flight inspect/link operation instead of maintaining separate busy state. services.AddSingleton(); services.AddSingleton(sp => new McpSettingsViewModel( diff --git a/SalmonEgg/SalmonEgg/Presentation/Views/Chat/ChatView.xaml b/SalmonEgg/SalmonEgg/Presentation/Views/Chat/ChatView.xaml index 8fbd9730b..cd9e07b8e 100644 --- a/SalmonEgg/SalmonEgg/Presentation/Views/Chat/ChatView.xaml +++ b/SalmonEgg/SalmonEgg/Presentation/Views/Chat/ChatView.xaml @@ -13,6 +13,17 @@ AllowDrop="False"> + + 12,0,12,8 + 16 + 1 + 0,0,0,12 + 12 + 16 + 4 + 6 + 8 @@ -465,14 +476,14 @@ Visibility="{x:Bind ViewModel.HasPendingElicitationRequest, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}"> - - + BorderThickness="{StaticResource ChatElicitationBorderThickness}"> + + - + - +