qwen-code 会话 PR 绑定支持 Aone 平台:基于 a1 CLI 的自动绑定与状态刷新设计解析

qwen-code 会话 PR 绑定支持 Aone 平台:基于 a1 CLI 的自动绑定与状态刷新设计解析 qwen-code 会话 PR 绑定支持 Aone 平台基于 a1 CLI 的自动绑定与状态刷新设计解析【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code导读本文基于 docs/design/2026-08-27-session-pr-aone-provider.md 设计文档结合仓库内已落地的源码实现深入解析 qwen-code 如何为 Aone阿里巴巴 Aone Code / a1 CLI工作区提供与 GitHub 工作区同等的一等公民能力会话 PR 自动回填backfill与状态定时刷新refresh。读完本文你将掌握会话 PR 绑定session PR binding的 sidecar 数据模型与两条自动路径a1 CLI 的能力矩阵、错误契约与版本下限约束以及平台检测、同 PR 身份判定、视图调用预算与旋转窗口等关键设计决策背后的工程理由。背景会话 PR 绑定的数据模型与 Aone 的缺口sidecar 绑定模型qwen-code 将会话与拉取请求的关联关系持久化在会话旁的 sidecar JSON 文件sessionId.pr.json中核心实现在 packages/core/src/services/session-pr-service.ts。每条绑定记录SessionPr包含numberPR 号正整数urlhttp(s) URL作为 badge/tooltip 的直接链接目标createdAt绑定时间state可选快照取值为open/merged/closedsource绑定来源按权威度排序为create/worktree/reviewissuesPR 关闭的 issue 快照仅 GitHub 概念。sidecar 列表有硬性边界SESSION_PR_LIST_LIMIT 10超出丢弃最弱/最旧URL 长度上限SESSION_PR_URL_MAX_LENGTH 2048且必须通过isValidSessionPrUrl的形状校验http(s) scheme、无控制字符——因为读取端readSessionPrs对整份 sidecar 是 fail-closed 的一条超长或含控制字符的 URL 会让该会话的全部绑定一起失效。手动绑定是平台中立的自动路径却只有 GitHub手动绑定RESTPATCH …/session/:id/metadata路由与 ACPsession/update_metadata方法只要求正整数number加任意 http(s)url因此 Aone 的 codereview URL 今天就能手动绑上。但两条自动路径都是 GitHub-only 的回填Backfillpackages/cli/src/serve/routes/session-pr-backfill.ts 的POST /sessions/backfill-prs每工作区执行一次gh pr list --state all把工作区会话映射到 PR并通过 gh 解析 worktreepr-N约定号兜底回退到origin web URL/pull/N。在 Aone 工作区上gh面对非 GitHub remote 必然失败而兜底会伪造出一个https://gitlab.alibaba-inc.com/g/p/pull/NURL——这个页面根本不存在Aone 的 CR 页面位于 WEB 主机code.alibaba-inc.com的…/codereview/global-id路径。状态刷新State refreshpackages/cli/src/serve/server/session-pr-refresh.ts 的 5 分钟 daemon 扫描通过gh pr list --state all保持每条绑定的state快照open/merged/closed badge新鲜。Aone 绑定的编号永远不会出现在 gh 的分页结果里因此其状态在绑定那一刻被永久冻结。设计目标让 Aone 工作区获得与 GitHub 工作区相同的自动绑定与状态刷新而不是停留在只能手动绑的二等公民状态。平台事实核查a1 CLI 能力矩阵实测 a1 0.2.51设计文档记录了 2026-08-27 对 a1 0.2.51 的实测探针结果探针仓库aone/a1列出有权限、查看被拒与jspt/agentic_coding完全授权能力a1 命令关键事实列出 MRa1 repo mr list --state opened\|merged --format json [--page N] [--repo g/p]分页大小固定 20--page最大 100默认按updated_at desc排序。条目携带id全局、iid、state、sourceBranch、projectPath。列表输出中detailUrl/webUrl是空字符串。--state取值有opened含reopened条目、accepted、merged——没有closed也没有all查看单个 MRa1 repo mr view global-id [--repo g/p] --format json返回{mergeRequest: {state, detailUrl, sourceBranch, targetBranch, …}}detailUrlhttps://code.alibaba-inc.com/groupPath/codereview/global-id——这是唯一被认可的 URL 来源review provider 的composeUrl禁止自行拼装 Aone 链接因为嵌套 group 折叠是 non-injective 的状态取值list/view 的state实测观察到opened、reopened、mergedaccepted是过滤器取值已批准、未合并closed从未观察到没有任何可列出的 closed MR——需防御性映射错误信号退出码不可靠a1 可能以退出码 0或1 返回{schemaVersion:a1.error/v1,code,message,retryable,exitCode}——解析出的形状、而非退出码才是错误信号。mr list可用时mr view仍可能 403按 MR 的可见性id 语义全局id在整个平台唯一CR URL 以它为主键因此在 Aone 内一个数字就能唯一标识一个 MRAGit-FlowsourceBranch可能是 head SHA此时分支名映射自然找不到匹配transcript 的gitBranch是分支名、绝不是裸 SHA——不存在误归属风险仓库坐标--repo group/project全路径daemon 的 cwd 不是工作区所以每次调用都必须显式传--repo坐标来自工作区自身 origin 经parseRemoteUrl(...).groupPath解析这些事实直接决定了实现的三条硬约束URL 绝不自行拼装Aone 的 MR URL 只能来自mr view的detailUrl错误判断看形状、不看退出码必须解析 stdout 检查a1.error/v1schema--repo每次显式传daemon 是多工作区的cwd 没有语义。可复用的 Aone 基元与边界设计明确复用了 review 子系统中两个依赖极轻的模块都位于 packages/cli/src/commands/review/lib/remote-match.ts零依赖导入parseRemoteUrl解析 https/ssh/scp 形状为{host, owner, repo, groupPath}与isAoneCanonicalHost只认规范的 web/git 主机对code.alibaba-inc.com/gitlab.alibaba-inc.com。注意该模块还定义了isAoneHostFamily宽松的*.alibaba-inc.com后缀匹配但本特性刻意只用 canonical 谓词见下文检测一节。platform/aone-client.tsA1_MIN_VERSION0.1.90与parseA1Version/a1VersionAtLeast版本比较工具。明确不复用review 的registry.ts中detectPlatformKind——它的无信号分支会探测process.cwd()这对多工作区 daemon 是错的serve 端改为按工作区读取 origin并套用 review 对显式坐标所用的 canonical-only 谓词。同样不复用的是aone-client.ts的执行传输层execFileSync原因见下一节。设计总览按工作区、基于 origin、canonical-only 的平台检测每次 backfill 运行 / refresh 扫描都从该工作区自己的 origin解析平台在工作区 git 根执行git remote get-url origin异步execFile环境经 core 的gitEnv清洗——与本路径上所有同类 git 调用一致用 review 的parseRemoteUrl解析再经isAoneCanonicalHost门控。origin 未定义 / 不可读 / 非 canonical 主机 → 走 GitHub 路径即今天的默认行为。门控必须是canonical-only、不能放宽到*.alibaba-inc.com家族该后缀同样命名 GitHub Enterprise 实例家族匹配会把 GHE 工作区错误地切换到 a1——此前gh还能服务它从 origin 解析出 enterprise 主机而 a1 要么每次读取都失败状态永久冻结要么在真实 Aone 上存在同名仓库时服务于另一个仓库的同号 MR。工作区 origin 在 review 的语义里就是显式坐标因此适用其规则。测试在 aone-mrs.test.ts 中钉住了这些边界例如gitghe.alibaba-inc.com:team/project.git形态的 origin 必须返回undefined保持 GitHub 路径嵌套 grouphttps://gitlab.alibaba-inc.com/group/sub/project.git必须保留完整group/sub/project继承的GIT_DIR环境变量不能干扰 origin 探测env 清洗。新模块 aone-mrs.ts异步 a1 传输层与三个操作新模块 packages/cli/src/serve/server/aone-mrs.ts 承载异步 a1 传输层与绑定路径需要的三个操作配套同位测试aone-mrs.test.ts。Runner为什么不用 review 的 execFileSyncrunner 采用execFilepromisified无 shell、自动追加--format json、20s 超时A1_TIMEOUT_MS、16MB maxBufferA1_MAX_BUFFER、windowsHide。刻意不复用review 的aone-client.ts传输层因为它是execFileSync阻塞 daemon 事件循环用Atomics.wait阻塞睡眠以 3s/6s 阻塞退避重试两次、总超时 120s——单次调用最坏约 6 分钟对 5 分钟定时器和 HTTP 路由完全不可接受。daemon 的设计哲学是下次扫描/运行再重试一次失败的调用只应花一次超时的代价20s而不是六分钟。错误契约形状优先于退出码runA1Json解析 stdout 前先检查a1.error/v1形状。a1 有的错误以退出码 0 返回该对象、有的以退出码 1 把同一对象打到 stdout——解析出的形状才是错误信号。错误分层为两个消费者可捕获的异常AoneCliUnavailableErrora1 二进制缺失ENOENT、或版本低于下限AoneCommandErrora1 返回了结构化错误对象、非 JSON 输出、或解析出的内容不合法。设计文档定义的失败契约已按实现落地非 Aone 工作区检测返回undefined读取抛上述异常两个消费者各自原地降级跳过分支映射、留下未解析的号、保留绑定的最后状态——与 gh 路径的not_a_repo / cli_unavailable / failed联合类型完全一致。版本下限正向探测才记忆化首次读取前先跑a1 --versionA1_VERSION_TIMEOUT_MS 10s。只有正向探测结果按 daemon 进程记忆化a1AvailabilityVerified标志a1 缺失或过旧时会持续重查因此安装/升级 a1 无需重启 daemon 即可生效而健康的安装整个 daemon 生命周期只花一次探测而不是每次 mr list/view 都查。低于 review 子系统的A1_MIN_VERSION0.1.90→ 判定不可用错误消息给出升级指引。测试专门钉住了低于下限不记忆化连续两次读取触发两次探测与探测失败后重查的行为。导出 API 与状态映射resolveAoneWorkspaceRepo(workspaceCwd, env?) → Promise{repoPath: string} | undefined检测 坐标一次 git 调用完成undefined 非 Aone 工作区或非仓库。实现细节findGitRoot定位 git 根 →git remote get-url origin5s 超时、gitEnv清洗→parseRemoteUrl→isAoneCanonicalHost→ 取groupPath作为repoPath。listAoneMergeRequests(repoPath, {state, pages}) → PromiseArray{number, headRefName, state}顺序翻页、每页 20 条、短页即停AONE_MR_LIST_PAGE_SIZE 20--page超过 100 由平台限制。parseAoneMrListPage严格校验id必须是正整数number 必须映射自全局id绝不是项目内iid——CR URL 和mr view都以全局 id 为主键畸形条目跳过。viewAoneMergeRequest(repoPath, id) → Promise{number, url, state}url取detailUrl经parseAoneMrView严格校验见 refresh 一节。状态映射mapAoneMrStatemerged→mergedclosed→closed其余一切opened、reopened、accepted→open。sidecar 没有draft变体与 gh 的映射一致。accepted表示已批准但未合并仍按 open 处理。测试注入缝backfillWorkspaceSessionPrs与refreshWorkspaceSessionPrStates的 options 对象新增可选aoneBackendgh fetcher 保持位置参数注入不动因此所有既有测试继续编译。backend 接口就是上面的 view 函数AoneMrBackend测试注入 fakesexec 层另有setA1ExecForTest缝用于替换 a1 的 spawn 并重置版本探测记忆。Backfill 改造绝不伪造 URL视图调用有上限注文档标注已随 #9739 更新——backfill 的来源在所有平台上都只是用户键入的/review N|#N|url命令与 worktreepr-N约定transcript 分支映射在 GitHub 上被实测为噪声后移除Aone 不重新引入因此a1 repo mr list不再位于 backfill 调用路径上listAoneMergeRequests保留为 exec 层原语注入的AoneMrBackend缝只含 view。每次运行收集候选后按工作区分发GitHub 工作区→ 完全走今天的代码路径。Aone 工作区候选有/review命令或pr-Nworktree sidecar 的会话此外——仅 Aone——任何已持有 PR sidecar 的会话这样下面的遗留修复能触达 worktree sidecar 已消失的绑定。URL 解析绝不伪造。本轮新绑定的每个计划号/review号和约定号都经viewAoneMergeRequest解析上限AONE_MAX_MR_VIEW_CALLS_PER_RUN 25个唯一号与 refresh 扫描共用同一常量超出的计入unresolved下一轮重试。实现中viewCache按号缓存、viewCalls计数超过上限即停成功视图同时填充 URL 与 state 映射。/review url形态只识别/pull/NURL因此 Aone 的codereview/id链接不是形态来源裸/review id才是。Aone 工作区能看到的唯一/pull/N形态就是历史上伪造的 own-remote 形状仅在全路径匹配两段仓库 key 会折叠嵌套 group时放行且只提供号、绝不提供 URL——URL 一律来自mr view。同 PR 身份 fail-closed已有条目只有在该条目被证明是本仓库自己的 MR 时才通过守卫——要么本轮经 mr-view 验证过要么精确匹配本repoPath的 detailUrl 形状isAoneDetailUrlForRepo。该形状刻意镜像 sidecar 的写入路径updateSessionPrStates按精确的canonicalSessionPrUrl相等匹配保留 scheme/host/port只有当取回的 detailUrl 真的会落在该条目上才算 own/可刷新。所以形状必须是 a1 产出的 WEB 主机、https、无端口的拼写——任何其他拼写git 主机、http:、显式端口都是写入路径也会拒绝的这里保持外来且保留而不是白费一次 view。形状精确是因为repoPath是 origin 的完整 group path无折叠它让满 sidecar 中被重新计划的条目无需每轮花 view 预算重新验证即可裁剪——反复验证会在稳态下泄露整个预算。任何无法证明的条目保持外来并保留gh 路径保持 fail-open 默认。遗留修复Aone 化之前 backfill 持久化的伪造origin web URL/pull/N条目被检测出来isLegacyFabricatedcanonicalSessionPrUrl(url) canonicalSessionPrUrl(${remote}/pull/${number})经同一个有上限的 view 路径重新解析然后原地重写为真实detailUrl state保留createdAt。不修复的话它们永远匹配不上取回的 URL——状态冻结、且每次 refresh 扫描都白花一次 view 调用。失败的mr view该号留在unresolvedremote web URL 的/pull/N兜底只适用于 GitHub。响应形状wire每工作区结果新增一个字段platform: github | aoneghAvailable仅在 GitHub 上报告。Refresh 改造旋转窗口 聚合预算 detailUrl 校验每轮收集完 pending 号非 merged 条目后按工作区分发GitHub 工作区→ 完全走今天的代码路径。Aone 工作区可刷新性过滤丢弃每个已存 URL 都 miss 本仓库 detailUrl 形状的号——外来手动绑定、等待 backfill 修复的遗留伪造条目、另一仓库的同号 MR 永远匹配不上经过验证的detailUrl为它们 view 只会每轮、永远地烧掉有上限的槽位。跨会话去重全局 id 是全平台唯一的被多个会话绑定的一个号只花一次 view 调用然后对每个唯一号执行viewAoneMergeRequest。旋转窗口视图窗口上限AONE_MAX_MR_VIEW_CALLS_PER_RUN 25且旋转——定时器为每工作区维护一个起始偏移aoneSweepOffsets每轮按该轮实际启动的视图数aoneConsumed在扫描结果中上报推进而不是按固定上限推进。按上限推进会在大集合被聚合预算截断时让被截断的尾部永远落在连续窗口之间的空隙里按实际消费数推进则让窗口无缝衔接。无论哪种情况大于上限的可刷新集合都会在连续多轮扫描中被完整刷新而不是饿死固定前缀。定时器每 tick 还会清理已移除工作区的偏移daemon 无限期运行为每个见过的 cwd 留条目会无界增长。聚合时间预算AONE_SWEEP_VIEW_BUDGET_MS 60s。单次调用超时只约束一次 view但 25 个顺序挂起的 view 会拖垮扫描间隔并经由定时器的 re-entrancy 守卫暂停所有工作区的刷新。预算耗尽后循环停止启动新的 view剩余部分按单号失败降级旋转窗口会重试。detailUrl 写前校验parseAoneMrView从 a1 stdout 取回的detailUrl在信任前必须通过 sidecar 自身的 URL 不变量——超过SESSION_PR_URL_MAX_LENGTH2048或含控制字符的 URL 被拒绝因为持久化任一种都会让readSessionPrs拒绝整份列表一条畸形 a1 回答会作废该会话的全部绑定。该视图随后降级为本轮未解析。控制字符守卫同时堵住了审计行伪造风险。写入路径成功的视图喂给既有的updateSessionPrStates写入路径以{state, url: detailUrl}为键——其 canonical-URL 身份检查继续有效因为detailUrl就是已绑 Aone URL 的 canonical 形态。单号错误403/404/超时跳过该条目扫描继续。只刷非 merged这一限制保持不变。a1 无法列出 closed MR所以 MR 被关闭的绑定保留最后状态但 reopened 的 MR 会出现在--state opened下并自愈。定时器本身的工程细节session-pr-refresh.ts间隔默认 5 分钟DEFAULT_SESSION_PR_REFRESH_INTERVAL_MS环境变量QWEN_SESSION_PR_REFRESH_MINUTES可调0 关闭空串/非法/亚分钟/溢出值均回退默认防止Number() 0静默关闭扫描或亚分钟值变成热循环首轮延迟 60s 避开启动期定时器unref()不阻碍进程退出每次 tick 有 re-entrancy 守卫防重入。环境与认证a1 不接受任何 env 参数review 先例继承process.env通过它自己的a1 auth login配置认证不存在A1_TOKEN约定。本特性新增的 git 调用与所有同类调用一致经由gitEnv(runtime.env.effectiveEnv)清洗环境后执行——避免继承的GIT_DIR/GIT_CONFIG_*把 origin 解析到另一个仓库。影响文件一览文件变更packages/cli/src/serve/server/aone-mrs.ts新增——检测、a1 runner、list/view、状态映射packages/cli/src/serve/server/aone-mrs.test.ts新增——runner 错误形状、状态映射、分页、检测packages/cli/src/serve/routes/session-pr-backfill.ts平台分发Aone 分支映射 基于 view 的 URL 解析packages/cli/src/serve/routes/session-pr-backfill.test.ts经注入 backend 的 Aone 用例packages/cli/src/serve/server/session-pr-refresh.ts平台分发基于 view 的状态刷新packages/cli/src/serve/server/session-pr-refresh.test.ts经注入 backend 的 Aone 用例packages/core完全不动。从packages/cli/src/commands/review/lib/的导入都是依赖极轻的模块remote-match.ts零导入aone-client.ts只依赖node:child_process且 serve → commands 的导入已有先例channel 模块。非目标明确不做Web Shell/prs概览面板与 PR 创建对话框workspace_github_prscapability、workspace-github-prs.ts这是独立特性有自己的 CI 汇总 / review 决策字段。从 Git 对话框在 Aone 上创建 MR。检测已关闭的 Aone MRa1 无法列出它们reopen 自愈。transcript 分支 → MR 映射#9739 已在所有平台移除该来源AGit-Flow 的 SHA head 因此无关紧要。对手动绑定、sidecar schema、bridge/ACP wire 形状的任何改动。风险与开放问题延迟大型 Aone 工作区的 backfill 串行执行最多 25 次 view 调用每次 ≤20s 超时、通常约 1s。路由是手动触发的维护操作可接受、已在文档中说明。accepted语义按open处理已批准但未合并。若 Aone 日后在 badge 中区分它再行 revisit。closed 状态字符串探针从未观察到映射是防御性的closed→closed。上限行为当可刷新的非 merged 绑定超过 25 个时扫描的旋转窗口在连续多轮中覆盖它们ceil(size/cap)单轮内窗口尾部按单号失败降级。超过上限的号也会在合并后自然退出所以旋转是双保险而非唯一出口。结语Aone 平台支持的本质是一次把平台差异收敛进一个薄层的工程检测origin canonical 主机、传输异步 exec、形状优先的错误契约、版本下限、语义全局 id、detailUrl唯一 URL 来源、AGit-Flow SHA被封装进 aone-mrs.ts 单一模块两个消费路径backfill 与 refresh只新增平台分发与降级逻辑。25 上限 旋转窗口 60s 聚合预算保证了 5 分钟定时器在任何规模下都不会失控URL 绝不拼装、身份 fail-closed、写前校验则把错误绑定和 sidecar 污染的风险降到最低——这套模式同样适用于未来接入其他代码托管平台时的参考。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考