这是本节的多页打印视图。 .
开发指南
最近更新:
- 1: 开发环境与运行
- 2: 架构与模块边界
- 3: API 与执行协议
- 4: 扩展 Tools 与 Integrations
- 5: 测试与贡献约定
1 - 开发环境与运行
最近更新:
前提:.NET 10 SDK、Node.js 24、pnpm 12.5.1(版本由 src/clients/package.json 的 packageManager 指定)和 Git。只有构建容器镜像时才需要 Docker Buildx。以下应用命令在 AGW 仓库执行,文档站本身不依赖这些工具链。
后端
在 http://localhost:30816/setup 初始化。热重载可将 dotnet run 改为 dotnet watch。
客户端
另开终端,从仓库根进入 workspace:
打开 http://localhost:3001。Desktop 使用 pnpm dev:desktop,其独立 renderer 运行在 3000,不需要同时运行 Web。Mobile 使用 pnpm dev:mobile,或 pnpm android:mobile / pnpm ios:mobile;原生工程由 Expo CNG 生成。
启动后应该看到什么
保留后端终端运行,再启动需要的客户端。Web 页面能打开后,先确认 Server 初始化和登录,再按第一次对话配置模型。仅看到前端页面还不能说明后端连接正常。
后端默认端口是 30816,Web 开发端口是 3001,Desktop 界面开发端口是 3000。端口被占用时先确认是否已有开发进程,不要误把其他服务当成 AGW。手机上的 localhost 指手机本身,真机连接需使用手机可访问的电脑地址。
验证
确认后端初始化完成,客户端能连接并运行一条简单消息。移动真机需要可达的后端地址;外部 CLI 需在执行进程环境下可用。
运行站点需要 Hugo Extended 与 Go,运行检查脚本 scripts/check-site.py 还需要 Python 3,命令见 site/README.md。不要将站点加入客户端 Turborepo,也不要让 Web/Desktop 消费站点产物。
实现与参考
2 - 架构与模块边界
最近更新:
本页帮助开发者判断一项改动应放在哪个模块、哪一层。AGW 按业务拆分代码,例如 Agents、Projects 和 Jobs;它们可以共享一个部署进程和数据库,但各自负责自己的数据和操作。
先完成源码运行,再选择一个具体功能沿请求追踪,通常比一次阅读所有项目更容易理解。
后端组织
AGW 是模块化单体。Agw.Host 提供共享 Hosting,Control Plane、Data Plane 和 Standalone 组合所需模块。业务模块遵守 Api → Application → Domain ← Infrastructure,只创建实际需要的层。
flowchart LR
API[Api] --> APP[Application]
APP --> DOMAIN[Domain]
INFRA[Infrastructure] --> DOMAIN| 层 | 负责什么 | 阅读时关注什么 |
|---|---|---|
| Api | 接收请求并返回响应 | 路由、输入和返回值 |
| Application | 完成一次业务操作 | 身份检查、查询、事务和调用顺序 |
| Domain | 保存业务数据并表达规则 | Entity、Behavior 和 DomainService |
| Infrastructure | 连接数据库和外部系统 | 持久化、外部服务适配和具体实现 |
Domain 中的 Entity 保存状态。单个 Aggregate 的业务规则由 Behavior 处理;需要其他 Aggregate 信息的规则由 DomainService 处理。Application 读取数据、协调调用并保存变更。普通增删改查由 Application 直接完成。
数据所有权
每张表只有一个负责它的模块。即使多个模块共用实体类型和数据库,也要通过所属模块提供的接口访问数据,不能直接查询或修改其他模块的表。
拥有数据表的模块在 Application/Persistence 中声明自己的持久化接口 I<Module>DbContext,目前共有九个:Agents、Auth、Integrations、Jobs、Projects、Providers、Settings、Skills 和 Tools;Files、Setup 和 A2A 不拥有数据表,也没有这类接口。同一次请求中,这些接口由同一个 AgwDbContext 实例实现。这样既能共用数据库连接和事务,又能限制模块可见的数据范围。跨模块调用使用 Contracts 中的公开约定,必要的跨模块事务放在经过批准的 Infrastructure 适配器中。
Agw.Agents.Execution → Agw.Agents 单向依赖,两程序集属于同一 Agents 模块。Agentflow 的选择性 DDD 不扩展到普通 CRUD 模块。
以修改 Agentflow 为例
请求先进入 Api,再由 Application 检查用户能否访问该流程,并加载流程及全部节点和连线。Policy 判断新图是否符合规则,返回 Decision;Behavior 将有效变更应用到已加载的对象,最后由 Application 保存。
因此,修改连线规则时应查看 Agentflow 的 Policy 和 Topology;调整权限、加载或保存顺序时应查看 Application;修改数据库实现时再进入 Infrastructure。这个分工让业务规则与网络、数据库细节分开,也便于分别测试。
客户端
Web 与 Desktop 各自拥有路由壳和构建,业务包位于 src/clients/packages。chat-core 负责消息语义,chat-runtime 负责执行连接和状态,chat 负责 DOM 呈现。Mobile 通过 chat-native 与 RN-safe 包接入,不直接依赖 DOM 包。
增加功能前确认所属模块与公开入口;修改边界后运行 pnpm test:boundaries 和后端架构测试 dotnet test tests/Agw.Architecture.Tests。
实现与参考
3 - API 与执行协议
最近更新:
AGW 的管理操作和任务执行使用不同接口。创建或查询配置使用普通 HTTP JSON API;持续接收 Agent 回复和状态使用 SignalR 执行连接;对接其他 Agent 系统时可以使用 A2A。
接入前,先准备可访问的开发 Server 和有效的认证身份,例如 API Key 或浏览器登录会话。具体参数以运行实例的 OpenAPI 及所属模块 Contracts 中的类型定义为准,避免照抄与运行版本不一致的请求。
协议边界
| 接口 | 用途与约定 |
|---|---|
| 管理 JSON API | Bens.Results ApiResult 封装,客户端 typed helpers 解包 |
/api/hubs/exec | SignalR 执行命令、状态和事件;仅 Data Plane 和 Standalone 映射,只接受 WebSocket 传输,官方客户端以 skipNegotiation: true 直接连接 |
/api/agents/permission-capabilities | 查询目标支持的权限能力 |
/api/auth/oidc/providers | 查询已启用的登录提供商,登录界面据此显示按钮 |
/api/auth/oidc/login | 跳转到提供商完成验证,client 取 web 或 desktop |
/api/auth/desktop/exchange | Desktop 用一次性代码和自身校验值换取 API Key |
| A2A | 协议专用响应,仅 Data Plane 和 Standalone 映射 |
/openapi/* | OpenAPI 接口描述,与 Scalar API 参考页一起只在 Development 环境中由 Control Plane 或 Standalone 提供 |
第三方登录相关的路由由 Control Plane 和 Standalone 提供。Desktop 换取到的 API Key 与手动创建的 API Key 用法相同,按创建者身份访问资源。
接入流程
- 自动化使用 API Key,通过
Authorization: Bearer请求头发送;浏览器使用 Cookie 发送 POST、PUT、DELETE 等修改请求时,还要按现有客户端流程处理 CSRF 防护,防止其他网站借用登录状态发起操作。 - 通过管理 API 获取当前用户可访问的资源,保留其稳定标识。
- 启动执行前查询目标权限能力,按现有执行协议发起命令并订阅事件。
- 断线重连时恢复会话/执行状态,不把连接断开当作任务完成。
新增接口默认通过 query/body 传标识,遵守仓库规则。具体接口的路由和参数以当前 OpenAPI 与 Contracts 为准。
客户端应如何处理结果
管理 JSON API 使用 Bens.Results 统一响应格式。仓库的 @agw/api 已提供类型化辅助方法来提取业务数据,调用方应复用这些方法,并分别处理请求失败和业务错误。
执行连接会持续返回事件。客户端需要保留会话与执行标识,展示工具活动和等待输入状态,并在重连后查询实际进度。收到部分文字不代表执行结束,连接断开也不代表执行已经取消。协议消息及顺序见执行协议说明。
Agent 的结构化响应字段
responseSchema 保存 Agent 配置的 JSON Schema 原文,出现在完整的 Agent 响应中:GET /api/agents/{id}、GET /api/agents/paged、POST /api/agents、PUT /api/agents/{id} 和 PUT /api/agents/enabled。选择器使用的 GET /api/agents 不包含该字段。
两类响应都包含 resultFormat,取值为 markdown 或 json,由是否配置 Schema 推导。客户端据此决定最终结果按 Markdown 还是 JSON 显示,无需自行解析 Schema 内容。
更新时,字段缺失表示保持原值,null 或只包含空白的字符串表示清空,其他字符串表示替换。服务端要求内容是合法 JSON 且根节点为对象,否则返回参数错误;Pi Agent 设置任何 Schema 都会返回参数错误。Schema 只作为文本保存和传递。
契约变更
DTO(请求和响应的数据类型)放在所属模块的 Contracts 中。预期的业务错误使用 AgwException 和稳定的七位 ErrorCode,再由 API 或协议入口转换成响应。WebSocket、OAuth 跳转、A2A 和静态文件使用各自的协议格式。
后端接口定义更新后,先把 Development 环境的 OpenAPI 文档导出到 src/clients/packages/api/openapi.json,再从 src/clients 运行 pnpm gen:api,最后验证调用方。gen:api 只转换这个本地文件,不会自动从 Server 获取最新文档。不要手写修改生成的 openapi.d.ts,也不要向日志输出实际的 API Key。
实现与参考
4 - 扩展 Tools 与 Integrations
最近更新:
扩展前先确定要增加什么:一个具体操作可以写成 Tool,一组任务说明和专属工具可以通过 Skill 提供,需要用户连接外部账号的能力则适合 Integration。
先阅读模块边界,确认能力由哪个模块负责。本页说明放置代码、注册能力和验证调用的顺序;具体声明写法可参考文末的工具示例。
工具扩展路径
- 手写的
IAgwTool、IContextualTool和IToolBlock实现放在Agw.Tools,全局目录只扫描这个程序集中的手写工具。业务工具放在所属模块的Application/Tools,以 Attribute 容器声明或通过 Skill 提供,DTO 放Contracts/Tools。 - 引用
Agw.Tools.Abstractions,需要 Attribute 声明时将Agw.Tools.Generators作为 Analyzer 引用。 - 显式声明权限、参数说明和返回类型。独立工具及使用 Attribute 声明的工具容器不能保存会话状态;状态放在 Provider、会话对象或所属存储中。
- 在所属模块注册所需服务与生成声明。选择通过 Skill 提供,或显式加入全局目录。
- 验证工具发现、参数、权限、项目绑定和错误映射。
生成器输出元数据、JSON Schema 和直接调用委托。不要加入运行时反射扫描兜底。Skill 工具有两个来源:IAgentSkillRegistration.Tools 提供手写的 IProjectScopedAgwTool 实例,ToolTypes 提供 Attribute 容器类型(由 IAgwToolSet<T> 生成)。它们在执行时绑定 Project,不因注册生成模块就自动进入全局目录。
集成扩展路径
IPluginCatalog 拥有 Plugin、Connector、认证和能力源定义。定义是代码/内容资产;用户设置是 PluginInstallation,可选账号或端点是 Connection。不要将它们合成一张全局配置表。
先增加目录定义和必要的工具来源,再验证用户完成设置、账号进入 Ready 状态、绑定 Agent 和实际调用的全过程。凭据由 Infrastructure 加密保存并在调用时读取;读取和执行都要检查账号归属。通过 HTTP/SSE 发送凭据时使用 HTTPS。
选择全局工具还是 Skill 专属工具
如果工具是可单独选用的通用操作,可显式加入全局目录。如果它只服务于某个 Skill,就通过该 Skill 注册,使说明和工具一起提供给 Agent。例如,agw-job 的任务管理工具属于 Jobs 模块,并随 Skill 提供。
工具成功编译后,还要确认 Agent 实际能发现它。若目录中没有出现,应先检查生成声明和注册位置;若调用时失败,再检查 Project 绑定、权限及参数。编译通过并不等于已经完成运行时接入。
方式一:通过接口定义 Tool
适合一个类负责一个可独立调用的操作。实现 IAgwTool,声明稳定名称、分类、Plan 可用性和权限,并实现唯一必需的成员 ToAITool()。仓库工具的写法是把操作放在 Execute 方法中,再在 ToAITool() 中用 AgwAIFunctionFactory.CreateParameterObjectFunction 包装成模型可调用的函数;这个工厂是 Agw.Tools 的内部类型,所以示例放在 Agw.Tools 中。下面是一个不读写外部资源的最小示例:
IAgwTool 继承元数据接口 IAgwToolMeta,ToAITool() 把执行方法包装成模型可调用的函数。参数 DTO 和方法上的 Description 帮助模型理解何时调用、如何填参。实际异步 I/O 应参考仓库工具使用异步方法并传递 CancellationToken;业务 DTO 放在所属模块的 Contracts/Tools。
接入与使用
- 将实现放到
Agw.Tools/Impl/Tools。全局目录只扫描Agw.Tools程序集中的手写工具,放在业务模块中的IAgwTool不会被发现;业务模块改用方式二的 Attribute 容器,或把IProjectScopedAgwTool实例放进 Skill 的IAgentSkillRegistration.Tools。声明对象保持无状态,不在字段中保存当前 Project、用户或会话数据。 - 在
src/server/Agw.Shared/Tooling/ToolValueObject.cs中把工具名称加入ToolDefinitionNames及其All列表,并加入具体ToolDefinition、[JsonDerivedType]名称映射和空的或实际的 Options 类型,保持定义与实现一一对应。名称没有登记时,注册会报错 “does not have a registered ToolDefinition”。 - 在所属模块 DI 入口登记依赖,确认工具出现在
/api/tools;工具绑定到 Agent 或 Project 后,才供该执行目标使用。 - 在 Chat 让 Agent 调用该操作,检查输入和输出。直接调用 C# 方法适合单元测试,但不能证明运行时权限和 Project 绑定已接通。
有 Project 或运行时目录依赖的独立工具,应参考 IContextualTool.MaterializeAsync(接口位于 Agw.Tools/Contracts/Abstractions,与其他手写工具一样只从 Agw.Tools 程序集发现),把已校验的上下文绑定到本次贡献的函数中。不要让模型通过一个随意填写的 Project ID 决定资源归属。
方式二:通过 Attribute 定义 Tool
适合把一个服务中的多个操作声明为工具,也适合随 Skill 提供的业务能力。先添加抽象项目和生成器引用,路径按项目位置调整:
下面与接口示例实现同一个 echo 操作,二选一使用,不要同时注册同名工具:
示例容器是普通的 sealed class,其中的方法都是静态方法;后面的 IAgwToolSet<TextTools> 要求类型参数是非静态类,所以容器不能声明为 static class。AgwToolContainer 批量选择类型直接声明的 public 普通方法;AgwTool 指定名称和权限;AgwToolIgnore 排除辅助方法。没有显式名称时,默认使用方法名并移除末尾的 Async。容器及每个操作都应明确声明或继承权限;AllowInPlanMode 是独立设置。
实例容器使用显式构造函数注入,并在 DI 中注册实例类型。静态方法可通过标记 [AgwToolService] 的参数取得服务。服务参数与 CancellationToken 不进入模型可填写的参数 Schema;每次调用都有独立的异步 DI scope。
生成、注册与选用
编译器生成元数据、输入输出 Schema 和直接调用委托。以程序集 My.Module 为例,在模块组合入口登记生成模块;需要全局选用时再选择容器类型:
上述代码片段需要生成声明命名空间及所属注册扩展的引用。只包含静态方法的容器不必登记实例;包含实例方法的容器还需 services.AddScoped<YourToolContainer>()。生成模块只登记声明,不会自动公开全部工具。全局工具仍要完成具体 ToolDefinition 和 JSON 多态映射。
若工具只服务于 Skill,让 Skill 注册类使用 partial 并实现 IAgwToolSet<TextTools>,生成器补齐 ToolTypes;同时按 IAgentSkillRegistration 完成 Id、说明和创建逻辑。在所属模块的 DI 入口用 services.AddSingleton<IAgentSkillRegistration, YourSkillRegistration>() 注册 Skill,并登记生成模块和实例容器,写法参考 Jobs 模块的 JobManagementSkillRegistration。将该 Skill 绑定到 Agent/Project 后,其工具才参与运行时组合。手写的 Skill 工具实现 IProjectScopedAgwTool,放进 IAgentSkillRegistration.Tools,不应额外放进全局目录。
生成失败时先处理编译诊断:不支持的签名是 AGWTOOL001,无效声明是 AGWTOOL002。不要用运行时反射绕过诊断。完整的实例容器和 Skill 示例见 工具抽象说明。
ToolBlock:定义共享状态的一组工具
当多个操作需要维护同一份状态,例如添加 Todo、完成 Todo 和查看待办列表,应把它们作为一个 ToolBlock 整组选用。Attribute 容器只是声明方式,本身不提供会话状态隔离。
下面是仓库的 TodoToolBlock 完整实现:
Todo 的状态与生命周期
Descriptor.Members声明五个成员、各自权限和 Plan 可用性。成员不能单独注册成全局工具。MaterializeAsync创建本次能力组合使用的AgwTodoProvider,放进ToolContribution.ContextProviders,同时提供循环检查器。AgwTodoProvider把AgwTodoState保存到当前AgentSession.StateBag,并通过StateKeys声明状态键。调用时从当前 session 读取、修改并保存,不能用 static 列表或共享单例保存所有人的 Todo。- 同一 session 的后续调用可继续使用已有 Todo;不同 session 的状态分开。持久化恢复依赖外层会话保存与恢复流程,不能仅因为 Provider 中有字段就认为状态可以恢复。
TodoCompletionLoopEvaluator检查待办是否完成;与 Mode 一起启用时只在 Execute 阶段参与检查。自定义 ToolBlock 不一定需要循环检查器,按具体任务添加。
开发自己的有状态 ToolBlock
- 定义数据对象和状态范围:回合、会话或项目长期存储。会话状态参考
AgwTodoState,项目长期状态参考 Project Memory。 - 在
Agw.Shared/Tooling/ToolValueObject.cs中把名称加入ToolBlockDefinitionNames及其All列表,增加具体ToolBlockDefinition、Options 和[JsonDerivedType]名称映射,再在ToolBlockNames中加入引用该常量的运行时名称。启动时的覆盖检查会拒绝缺少定义或缺少实现的 ToolBlock。 - 实现
IToolBlock,一次声明全部成员,明确每个成员的权限与allowInPlanMode。 - 在
MaterializeAsync创建 Provider,将函数和状态操作绑定到当前上下文;把生命周期交给ToolContribution,不要缓存跨用户的 Provider 或 scoped 服务。 - 在目录和定义解析流程接通整组选用,再测试:新增、完成、移除、不同 session 隔离、保存恢复、Plan 限制与审批。
参考 Todo Provider 和 Todo 状态,不要只复制描述符而遗漏状态的读取与保存。
Plugin:从目录定义到实际调用
这里的 Plugin 是 AGW 集成目录中的代码与内容定义。当前内建示例是 GitHub;新增 Plugin 需要修改并构建服务端,不是上传任意插件包即可运行。界面用 Available integrations 展示目录,用 Configured integrations 展示用户配置的账号或端点。
| 开发对象 | 定义内容 |
|---|---|
PluginDefinition | 稳定 Id、版本、显示名、Connector 和可选 Skill 内容 |
ConnectorDefinition | 服务或协议变体,例如 GitHub Cloud |
AuthSchemeDefinition | 认证方式、用户配置字段及 OAuth 流程设置 |
CapabilitySourceDefinition | 工具由内部 C# Provider 创建,或从 MCP 服务取得 |
PluginInstallation | 当前用户的安装设置,例如 OAuth Client ID/Secret |
Connection | 当前用户配置的具体账号、凭据和 Ready 状态,绑定时使用 ConnectionId |
定义目录与认证
下面是现有 GitHub 目录实现,可作为开发新集成时的完整结构参考。OAuth 地址、scope 和字段需按目标服务的协议设置,不能照搬 GitHub 的值:
新增定义时保持 Plugin、Connector、认证方式和能力源的 Id 稳定,并让 PluginCatalogValidator 验证整个目录。InstallationFields 用于每用户的安装配置;账号级字段应放到相应认证方案的连接字段中。秘密字段使用 Secret 类型,不能把真实凭据写入目录定义。
实现能力源
Native:定义中的 Provider = "github" 对应 IConnectionNativeCapabilityProvider.Provider。实现 CreateTools(ConnectionNativeCapabilityContext),用已解析的 ConnectionId、Alias 和 ProjectId 创建工具。工具名称通常为 {alias}__{operation},防止多个账号的工具混淆。
参考 GitHubConnectionNativeCapabilityProvider:生成函数绑定账号和 Project,实际调用时创建 scope、解析 IGitHubConnectionInvoker;Invoker 再执行账号归属、Ready 状态与凭据检查。不得让模型指定任意 ConnectionId,或在单例 Provider 中缓存账号密钥。为新增操作接入当前能力源的权限元数据与执行审批流程。
MCP:使用 McpCapabilitySourceDefinition,选择 stdio、HTTP 或 SSE Transport,再通过 CredentialBindings 把安装字段、连接字段或 OAuth Token 注入所需环境变量或 HTTP Header。经过网络注入凭据时使用 HTTPS;字段引用必须与认证定义匹配。MCP 路径复用连接授权与运行时校验,不是直接把未验证的 URL 交给 Agent。
注册、内容与测试
- 在 Integrations 的 DI 入口维护目录注册;Native Provider 注册为
IConnectionNativeCapabilityProvider,调用服务按其生命周期登记,例如 GitHub 的 scoped Invoker。 - 若附带 Skill,将内容放入 Plugin 内容目录,并通过
PluginSkillDefinition.ContentPath指向SKILL.md。它提供使用说明,不自动执行第三方脚本;同时确认构建产物包含内容文件。 - 在 Available integrations 找到定义,为测试用户配置安装字段和账号。完成认证后确认 Ready,再绑定 Agent 或 Project。
- 验证一次读取和一次受控写入,检查工具名称、参数、权限及错误处理。测试使用真实实现,不使用 mock 或 fake 实现;需要真实账号或 OAuth 授权的测试不放进默认测试套件。
- 覆盖跨用户 ConnectionId、未就绪账号、失效凭据、目录字段错误、同名工具和配置变更。修改用户安装设置只应影响该用户的账号连接。
当前没有远程 Marketplace 的下载、签名与自动升级机制。完整调用链参考 GitHub Native Provider、GitHub Invoker 和 能力源定义。
验证
至少覆盖成功调用、非法参数、权限不足、外来 Connection 和未就绪 Connection。保持编译期诊断有效。测试使用真实实现,不使用 mock 或 fake 实现;依赖真实账号或外部 CLI 的测试需要显式开启,不放进默认测试套件。
实现与参考
5 - 测试与贡献约定
最近更新:
前提:依赖已安装。修改前阅读根目录 AGENTS.md 和 docs/human/ 中的相关规则,保留无关的本地改动。
后端检查
从仓库根运行:
测试项目使用 xUnit v3,并通过根目录 global.json 使用 Microsoft.Testing.Platform 运行。定位问题时先运行相关测试,例如 dotnet test tests/Agw.Files.Tests。单元和组合测试使用真实实现和纯选项辅助方法,不使用 mock 或 fake 实现。构造 CodexAIAgent 或 ClaudeCodeAIAgent 时会探测 CLI,这类测试作为真实 CLI 测试运行,需要显式启用并确认可执行文件可用,不放进默认测试套件。
修改错误码或异常规则后运行 dotnet test tests/Agw.Shared.Tests;修改模块依赖后运行后端架构测试 dotnet test tests/Agw.Architecture.Tests。
持久化执行的 PostgreSQL 测试(租约保护、事件顺序、活动执行升级和调度容量)通过 AGW_TEST_POSTGRES_CONNECTION_STRING 连接独立的测试实例,测试用户需要创建数据库的权限;Redis 事件投影测试使用 AGW_TEST_REDIS_CONNECTION_STRING。没有设置这些变量时,对应测试会跳过。CI 使用 PostgreSQL 18 运行 PostgreSQL 测试,并检查 TRX 结果,确认必需的测试全部执行成功;完整命令见开发文档。
改动登录相关代码时,运行 dotnet test tests/Agw.Auth.Tests。这些测试默认使用受控的提供商和各自独立的 SQLite 数据库。需要在 PostgreSQL 上验证时,把 AGW_TEST_OIDC_POSTGRES 设为测试服务器的管理连接字符串,该账号需要能够创建数据库;不要指向生产服务器。Desktop 主进程的登录与凭据存储测试使用 pnpm --filter @agw/desktop test。
客户端检查
从 src/clients 运行:
使用 oxlint/oxfmt,不是 ESLint/Prettier。修改包边界后必须通过 pnpm test:boundaries;改变 API 后先把 Development 环境的 OpenAPI 文档导出到 src/clients/packages/api/openapi.json,再运行 pnpm gen:api 重新生成 typed client,并验证调用方。
组件渲染测试通过共享的 @agw/test-harness 建立 DOM 环境;需要 API 响应时,用其中的 startApiServer 启动真实的本地 HTTP 服务,让组件走自身的请求路径。Web 的浏览器测试先运行 pnpm --filter @agw/web exec playwright install chromium,再运行 pnpm --filter @agw/web test:e2e;Playwright 会在 127.0.0.1:3101 启动独立的 Web 服务,不需要后端。
数据与提交
模型变更需要配套 SQLite 与 PostgreSQL 迁移,但只有明确授权后才生成或应用。生成时分别以 src/server/Agw.Migrations.Sqlite 和 src/server/Agw.Migrations.Postgres 为迁移项目、src/server/Agw.Standalone.Host 为启动项目,并在命令末尾传入 -- --provider sqlite 或 -- --provider postgres,完整命令见开发文档。dotnet tool restore 只安装 CSharpier,dotnet ef 需要另行安装。NoForeignKeyModelDiffer 禁止生成数据库外键,引用验证和清理由应用层/基础设施负责。
C# 使用显式构造函数,禁止 primary constructor;日期使用 DateTimeOffset。遵守根目录 AGENTS.md。提交需显式授权,并使用 Conventional Commits。
按改动选择检查
| 改动 | 至少确认 |
|---|---|
| 修复后端行为 | 相关项目构建通过,能重现原问题的验证通过 |
| 修改接口或 DTO | 导出 OpenAPI 文档并重新生成 API 类型,检查调用方及错误处理 |
| 调整模块或包依赖 | 后端架构测试或客户端边界检查通过 |
| 修改错误码 | tests/Agw.Shared.Tests 通过 |
| 修改界面 | 检查实际操作、不同屏幕宽度和相关测试;涉及 Web 浏览器行为时运行 test:e2e |
| 修改本站文档 | Hugo 严格构建、链接与中英文对应检查通过,页面显示正常 |
验证失败时保留错误信息,修复后重跑受影响的检查。提交说明应写清改了什么、如何验证,以及是否影响数据库或部署。
完成标准
缺陷修复应有可复现验证,行为改动覆盖相关成功和失败路径。文档站只改 site 时运行其 Hugo、链接和浏览器检查,无需为纯文档改动启动模型服务或执行数据库初始化。