Repository navigation
[PR] Pi: opt-in structured acp_delegate notification envelope for third-party UIs - #5790
ranxianglei wants to merge 5 commits into
Conversation
📦 Built Package ArtifactBranch: Latest build — install this exact versionnpm install -g billion-context@0.0.9267-pr.5790.3d363fbThis exact version is immutable and reproducible. The rolling Recent builds (commit → version)
Download artifact
tar xzf billion-context-pr5790.tgz
npm install -g packageInstall instructions are refreshed on each push. |
(ai: vllm-qwen/qwen3.8-27b) 查重: 不重复 —— 本条是带 按规范处理:不重做,而是 ① 拉分支核对代码 ② 查 CI 状态 ③ 出结构化结论(八问)。 标签: 开始验证,完成后回复结论。 |
The PR bumped pi-subagents/package.json 0.2.3 -> 0.2.4 but did not regenerate the root package-lock.json, which still recorded the file: link at 0.2.3. The lock-sync drift guard (#2704/#3782) regenerates the root lock and fails on any residual diff, so the check went red. Regenerating updates exactly one line: the billion-context-pi-subagents version record 0.2.3 -> 0.2.4. Kernel lock verified clean (the PR does not touch kernel/).
(ai: vllm-qwen/qwen3.8-27b) lock-sync 已修复并推回分支。 分支 七问:
当前状态:新 head 已在 CI 上开跑,目前无任何失败项,剩下的是较慢的 test 分片(与本改动无关); |
… path The opt-in structured envelope (customType billion-context:delegate-notification) switched delivery from sendUserMessage to sendMessage, but pi's sendCustomMessage only starts a turn on an IDLE host when triggerTurn is set — without it an idle-tier notice sat dormant until the next activity instead of waking the model like the plain path does (#5750 acceptance: wake-up behavior intact). Pass {deliverAs:"steer", triggerTurn:true}: streaming delivery is unchanged (steer either way), idle hosts gain parity with the legacy plain message. Also restore per-carrier identity in the unified send-failure logs: the refactor folded four carriers' error/warn events into notify-send-error/notify-no-channel without saying which tier failed — add a `carrier` field ("turn-end" | "idle-batch" | "inject-result" | "inline-usage"). Tests: pin triggerTurn on every structured dispatch; add coverage for the usage-note carrier (kind:"usage" envelope on, plain path off) which was missing.
…onfig home The structured-notify knob works de facto through the piSubagentsAdapter spread passthrough, but the host schema (PiSubagentsFileConfig) did not declare it and CONFIGURATION.md never mentioned it — and the PR description pointed users at the deprecated ~/.pi/acp.json delegate.* fallback, which is ignored whenever the canonical pi.subagents section exists. Declare the field in the host schema (precedent: notifyIfRead is declared in both schemas) and document it in both jsonc examples. No runtime change.
(ai: vllm-qwen/qwen3.8-27b) PR #5790 审核完成:拉分支逐行核对了全部 6 个文件的 diff,并对结论做了定向验证(不是只看描述)。发现 4 个问题(1 个行为 bug + 1 个配置面缺陷 + 日志上下文丢失 + 测试缺口),已全部修掉并推回 PR 分支( 八问:
验证证据:本地定向套件 131/131(原 129 + 新增 2)、 |
The new opt-in key (#5750) was declared in CONFIGURATION.md/zh and both config types, but the standalone-facing pi-subagents READMEs (example block + key table, en/zh) never listed it.
ranxianglei
left a comment
There was a problem hiding this comment.
注:GitHub API 禁止 approve 自己的 PR(token 即作者),故以正式 review(COMMENT)提交,判定如下。
(ai: vllm-qwen/qwen3.8-27b)
APPROVE — 独立复审 head d8f2dadfe 全量 diff(含任务快照 7562aac0b 之后新增的 e8d8a4206、d8f2dadfe 两个提交)。
方向核对(对 #5750)
issue 要的是「持久化的类型化 custom message + 版本化 details」,owner 已在 issue 线程拍板 C 方案(opt-in 开关默认 off,先给精确规格后 owner 回「c」签核)。PR 逐条按批准规格实现:键名/默认值/4 个通知点/启动期来源捕获/能力门控回退,全部一致。修的是投递层没有结构化信封这个根因,不是调措辞。
关键验证(逐项独立做过,不依赖 PR 描述)
- Wire 安全:对照钉死宿主版 @earendil-works/pi-coding-agent@0.83.0 真实代码——
convertToLlm对role:"custom"只把content以role:"user"出网,details/customType/display被丢弃(dist/core/messages.js:75-96)→ 模型可见字节与 legacysendUserMessage路径完全一致,前缀缓存不受影响。 - 持久化/重载:
CustomMessageEntry把{customType, content, details?, display}写进会话 JSONL(session-manager.d.ts:97-103)→ live 与 reload/resume 暴露相同元数据,满足验收项。 - 唤醒一致性:宿主
sendCustomMessage在 idle 宿主上只有带triggerTurn才起新 turn(agent-session.js:1058-1093),而sendUserMessage恒触发 turn——e8d8a4206补上triggerTurn:true正是修掉首个 head 的真实回归(idle 宿主上通知会休眠到下次活动),已对照宿主源码确认修法正确且只影响 idle 分支(streaming 时两路都 steer)。 - 配置流:canonical home
pi.subagents.notifyStructured(src/config.ts 有类型声明,adapter spread 透传)与 acp.json fallback 收敛到同一处resolveDelegate的cfg.notifyStructured === true——仅字面量true启用,无 env 面。 - 溯源捕获:
runs.set全仓唯一创建点(runDelegate),originToolCallId/sourceSessionId 在启动时捕获;缺省即缺省、从不伪造,满足「missing origins stay visible」。
回归审查(diff 触碰路径的既有行为逐项结论)
- 默认 off(全部现有安装):4 个通知点逐字节走原
sendUserMessage(text,{deliverAs:"steer"})—— 保持(有测试钉住 sendMessage 不被触碰)。 - injectResult cheap no-op gate:仅在 on+sendMessage 时放宽;off 时判定式不变 —— 保持。
- 恢复语义(发送失败 → undelivered → 挂到下次通知/tool result):dispatcher 失败返回 false,恢复标记只在 sent=true 提交 —— 保持。
- 日志事件名 notify-turn-end-error/notify-batch-error/inject-error → 统一 notify-send-error(+carrier 字段):内部日志,非用户可见/非 wire —— 有意变更,无外部影响。
- 版本号 0.2.3→0.2.4 + 两处 lockfile 同步:Pi-Subagents Boundary 要求;嵌套 lock 在 PR 前 stale 于 0.2.2,现收敛 —— 顺手修正,方向正确。
- 缓存:模型可见字节不变(details 出网前剥离)—— 未破坏。
测试有效性
delegate-structured-notify.test.ts 9 个用例均为真断言、驱动宿主生命周期表面:同测内 A/B 钉住「content 与 legacy 消息逐字节一致」;能力门控双向断言;batch 列全 run + 溯源在场/缺省两态;wake parity {deliverAs:"steer", triggerTurn:true} deepEqual 钉死;usage note kind:"usage" 与 terminal 可区分(去重友好)。这些回归发生时抓得住。既有 3 个通知套件不变通过。
行为变更披露(旧值→新值)
新增配置 delegate.notifyStructured(boolean,默认 false):off = 现状零变化;on 时投递信道由 sendUserMessage 切为 sendMessage(typed custom message;TUI 按 custom-message 样式渲染而非 user 气泡样式——纯外观差异、仅 opt-in 用户),模型可见文本与 steer/idle-wake 语义不变。PR 正文 Config surface 节已披露,owner 签核在 #5750 线程。
CI:评审时 test shards 仍在跑(无失败项);最终绿以 head acad6f9 的运行为准。合并权在 owner。
|
(ai: vllm-qwen/qwen3.8-27b) 已顺手修:pi-subagents/README.md 与 README.zh-CN.md 的示例块和键值表补上 |
(ai: vllm-qwen/qwen3.8-27b) 审核完成,判定 APPROVE:已独立对照 pi 0.83.0 宿主源码逐行复核(wire 安全 / 持久化重载 / 空闲唤醒 parity / 双配置面流 / 溯源捕获), |
(ai: vllm-qwen/qwen3.8-27b) 收到——这条是我自己刚发的「已顺手修」通知经上游同步回显,无新增请求。状态不变:分支 head |
ranxianglei
left a comment
There was a problem hiding this comment.
(ai: vllm-qwen/qwen3.8-27b)
APPROVE(新 head acad6f93c)——对本轮唯一增量做收口确认:
- delta
d8f2dadfe..acad6f93c= 仅 2 个文件、4 行纯文档(pi-subagents/README.md + README.zh-CN.md 的示例块与键值表补列delegate.notifyStructured,即上一轮「已顺手修」commit)。无代码、无配置、无锁文件变更;不影响任何测试路径。 - CI(镜像缓存 @ acad6f9):全部守卫/门禁绿——static-gates、kernel-guard、pi-subagents-guard、config-surface-guard、lock-sync、docs-sync、docs-coverage、version-guard、lane-guard、lifecycle-ledger-guard、unused-exports、test-import-graph、mysql-contract 均 success;test 分片 4/10 已绿、0 失败、其余在跑(文档 delta 不可能改变其结果);e2e 各 lane skipped 属正常。
- 实质性内容(wire 安全 / 持久化重载 / 空闲唤醒 parity / 双配置面流 / 溯源捕获 / 回归清单)已在上一轮正式 review 中逐项独立核对过(对照 pi 0.83.0 宿主源码),结论不变。
判定维持 APPROVE,无阻塞项;合并动作留给你。(GitHub API 禁止 approve 自己的 PR,故仍以 review-COMMENT 形式提交。)
(ai: vllm-qwen/qwen3.8-27b) 新 head |
(ai: vllm-qwen/qwen3.8-27b) CI 已全绿:最终 head 补充一条透明性说明:我推完两个修复 commit 后,另一条并行会话在同一 PR 分支上追加了 收工判定:可以合并——合并动作按规矩留给你(PR #5790)。 |
(ai: vllm-qwen/qwen3.8-27b) 更正上一条里的笔误:分支相对 master 共 5 个 commit(不是 4)—— |
Deliver delegate completion/failure notices as a typed custom message (customType billion-context:delegate-notification) whose versioned details carry stable run identity plus launch provenance (originToolCallId/sourceSessionId), while content stays byte-identical to the legacy steering user message. The model sees the same text either way; pi's convertToLlm drops details so they never reach upstream. Gated behind delegate.notifyStructured (default false -> plain path untouched); falls back to the plain path automatically when the host lacks ExtensionAPI.sendMessage. Lets third-party UIs (e.g. pi-tidy #59) tie a notice to its launching turn without parsing prose. Refs #5750
The PR bumped pi-subagents/package.json 0.2.3 -> 0.2.4 but did not regenerate the root package-lock.json, which still recorded the file: link at 0.2.3. The lock-sync drift guard (#2704/#3782) regenerates the root lock and fails on any residual diff, so the check went red. Regenerating updates exactly one line: the billion-context-pi-subagents version record 0.2.3 -> 0.2.4. Kernel lock verified clean (the PR does not touch kernel/).
… path The opt-in structured envelope (customType billion-context:delegate-notification) switched delivery from sendUserMessage to sendMessage, but pi's sendCustomMessage only starts a turn on an IDLE host when triggerTurn is set — without it an idle-tier notice sat dormant until the next activity instead of waking the model like the plain path does (#5750 acceptance: wake-up behavior intact). Pass {deliverAs:"steer", triggerTurn:true}: streaming delivery is unchanged (steer either way), idle hosts gain parity with the legacy plain message. Also restore per-carrier identity in the unified send-failure logs: the refactor folded four carriers' error/warn events into notify-send-error/notify-no-channel without saying which tier failed — add a `carrier` field ("turn-end" | "idle-batch" | "inject-result" | "inline-usage"). Tests: pin triggerTurn on every structured dispatch; add coverage for the usage-note carrier (kind:"usage" envelope on, plain path off) which was missing.
…onfig home The structured-notify knob works de facto through the piSubagentsAdapter spread passthrough, but the host schema (PiSubagentsFileConfig) did not declare it and CONFIGURATION.md never mentioned it — and the PR description pointed users at the deprecated ~/.pi/acp.json delegate.* fallback, which is ignored whenever the canonical pi.subagents section exists. Declare the field in the host schema (precedent: notifyIfRead is declared in both schemas) and document it in both jsonc examples. No runtime change.
The new opt-in key (#5750) was declared in CONFIGURATION.md/zh and both config types, but the standalone-facing pi-subagents READMEs (example block + key table, en/zh) never listed it.
acad6f9 to
31c7824
Compare
(ai: vllm-qwen/qwen3.8-27b) Merge conflict resolved and pushed. Rebased the 5 PR commits onto current What happened: master's doc reorg (#5920) replaced the root Verified locally on the rebased head: lock-sync idempotency exit 0 (root lock = exactly one line, 七问:
Verdict: conflict cleared — the branch strictly leads master by 5 commits and merges cleanly; awaiting CI on the new head. |
ranxianglei
left a comment
There was a problem hiding this comment.
(ai: vllm-qwen/qwen3.8-27b)
APPROVE(新 head 31c7824cd,CI 全绿收口)
本轮相对上一轮(acad6f93c)的分支变化 = rebase 到最新 master,无新 PR 内容:
- PR 自身提交不变:仍是同样的 5 个逻辑提交(feat 主体 / root-lock 同步 / idle-wake parity fix / canonical config home 文档 / README 补列),只是换了 hash。对 master 的净增量仍是 11 个文件 +391/-66,与已审版本一致。
- 语义标记逐项复核通过(rebase 后新 head 上实测):
pi-subagents/src/config.ts:198严格门cfg.notifyStructured === true、默认 false;delegate-tool.ts:333结构化路径{deliverAs:"steer", triggerTurn:true}、carrier 参数保留在两个失败日志事件里;测试文件 9 个用例全在;docs/configuration.md:919+docs/configuration.zh-CN.md:913新键在文档搬家(#5920 → docs/)后的规范位置就位;pi-subagents 版本 0.2.4。 - CI(镜像缓存 @ 31c7824):30/30 全绿——10 个 test 分片(ubuntu/windows × node 22/24)+ static-gates ×2 + gate ×2 + mysql-contract 全部 success,e2e 各 lane skipped 属正常,0 failed 0 pending。
- 实质性审查结论(wire 安全 / 持久化重载 / 空闲唤醒 parity / 双配置面收敛 / 单点溯源捕获 / 回归清单 / 缓存不受影响)见前两轮正式 review,全部维持不变。
判定:APPROVE,无任何阻塞项,可由你合并。(GitHub API 禁止 approve 自己的 PR,仍以 review-COMMENT 形式提交。)
(ai: vllm-qwen/qwen3.8-27b) head |
(ai: vllm-qwen/qwen3.8-27b) 本轮(head
|
|
该PR已按根因归类纳入 [storage] 根因汇总 #6267,由汇总单统一跟踪处理;本单不再单独跟进,故关闭。如认为归类有误或问题仍未解决,请直接回复,会重新打开。 |
|
状态更正(自动化脚本误操作): 本单此前被「纳入根因聚合」流程误关闭, 现重新打开。机器人工作已停, 本单由根因聚合线程统一跟踪, 根因修复落地后请手动关闭或在本单回复「close」。 |
|
重新打开:此前被归类聚合脚本误关。按 owner 口径,纳入根因聚合的 issue 只停机器人、不关闭,由根因聚合线程统一跟踪,根因修复落地后统一关闭。 |
|
(更正)此前按"清理"口径误关闭;按 owner 口径纠正:被根因聚合纳入的 issue 只停机器人、不关闭。本 issue 重新打开,机器人工作已停。 |
What & why
Today every Pi
acp_delegatecompletion/failure notice reaches the host as a plain steering user message whose wording third-party consumers can only identify by regex-parsing prose (the fragile part). That makes reliable turn-folding (pi-tidy #59) and any other UI integration brittle: a late result can't be tied to the turn that launched it.This adds an opt-in typed envelope. When enabled, each notice is delivered as a custom message (
customType: billion-context:delegate-notification) whose versioneddetailscarry stable run identity plus launch provenance, whilecontentstays byte-identical to the legacy plain message. The model sees the same text either way; pi'sconvertToLlm()dropsdetails, so nothing new ever reaches the upstream wire and prefix caching is unaffected.Envelope shape (version 1)
originToolCallId— captured at launch, so a late notification still ties back to the turn that launched it (the core requirement; no script parsing needed).sourceSessionId— main-session provenance for nested/codemode runs; best-effort, omitted when the host exposes none.kind: "usage"so they're distinguishable from terminal notices (dedup-friendly). Absent provenance stays absent (never fabricated).Config surface (new field)
pi.subagents.notifyStructured(boolean, defaultfalse) — canonical home: thepi.subagentssection of~/.config/billion-context/billion-context.json(declared in the host schema, documented in CONFIGURATION.md). The deprecated~/.pi/acp.jsondelegate.*fallback reads it too, but only while thepi.subagentssection is absent — same read path as every otherdelegate.*key.x-bili-pluginheader (wrong layer — this is a pi-host extension concern, not the proxy wire); always-on (rejected — would change default behavior for every install; owner chose opt-in); env-var-only (rejected — this is a durable user preference, belongs in config per Env Var Discipline).false⇒ zero behavior change for every existing install. Enabling is additive; a host withoutExtensionAPI.sendMessagefalls back to the plain path automatically (capability gate), so older pi versions are safe.Behavior change disclosure
{deliverAs: "steer"}. No regression possible on the default.sendUserMessagetosendMessage(typed custom message). Model-visible bytes are unchanged (samecontent, same steer). This is the intended, opt-in change.Verification
tsc --noEmitclean;tsupbuild succeeds.tests/delegate-structured-notify.test.ts(default-off parity, structured dispatch + content byte-identity, capability fallback, batch lists every run, provenance carried / cleanly absent, idle-tier + injectResult paths) plus the existingdelegate-turn-end-notify/delegate-settle-notify/delegate-toolsuites, unchanged.Closes #5750