Repository navigation
refactor(goal)!: stop emitting the active_goal stream event - #12181
Conversation
The stream-json active_goal event was a compatibility projection of goal_state, documented as non-authoritative since QwenLM#7895 and ignored by both TUIs. It goes, with the second projection core kept for it, the LlmEventType member, the ServerGemini*ActiveGoal aliases and the ActiveGoal wire type. Headless automation reads goal_state, which is emitted as before, with or without partial messages. BREAKING CHANGE: --output-format stream-json no longer emits stream_event entries whose event.type is active_goal.
yiliang114
left a comment
There was a problem hiding this comment.
LGTM — active_goal stream event emission removed end to end (client/turn, StreamJsonOutputAdapter, OpenTUI event-adapter) with tests and headless docs updated; reviewed with @Fable5. Note this is a breaking change for any out-of-tree consumer of the event. Ship it.
qwen-code-dev-bot
left a comment
There was a problem hiding this comment.
APPROVE at 609abec.
No historical Critical on this PR, and the required CI is complete and green on this head (unit, lint & static, integration, desktop shell on both OSes, web-shell E2E smoke, OpenTUI no-flicker gate, TUI parity snapshots, classify/precheck). The review-pr bot job is still running after 96 minutes and is not a required check — the main ruleset declares none — so it is not treated as a gate here; the repository's own triage reviewer completed on this head.
Independently verified, since this removes a published stream event:
- The removal is complete across the whole monorepo, including every SDK and client package. Grepping
active_goal/ActiveGoal/projectActiveGoal/sameActiveGoalProjectiononmainleaves only two hits outside these 16 files, and both are unrelated:LegacyActiveGoal, which this PR correctly keeps for 5-b-2 and whichgoal-wire.tsstill re-exports, and a comment namingfailClosedActiveGoalTurn. goal_stateis unaffected in the way that matters. Its branch in the stream adapter emits throughemitMessageImpldirectly, so it bypasses the base finalized guard exactly as the deletedactive_goalbranch did — late Goal state changes still reach stream consumers. It is also ungated, whereasactive_goalwent through the partial-messages gate, so the surviving event is strictly more available than the one being removed.takePendingGoalEventskeeps its drain and ordering semantics:splice(0, length)still empties the queue and everygoal_stateevent is still pushed in subscription order, with only the interleaved projection gone.- Completeness is compiler-enforced rather than assumed: the
use-llm-streamswitch closes onconst unreachable: never = event, and there is noObject.keys/values/entries(LlmEventType)anywhere, so no runtime enumeration or snapshot depends on the member list. - Old cores and recorded streams degrade silently, not loudly: the OpenTUI adapter's
default: breakignores anactive_goalthat still arrives.
The breaking change is disclosed rather than buried — the ! in the title, an explicit list of the departing exports, and a user-doc line that now tells automation to read goal_state. Retiring the ServerGeminiActiveGoalEvent alias ahead of its "future major release" note is unavoidable, since an alias cannot outlive the type it aliases, and a maintainer has approved with that tradeoff named.
已核对 head 609abec:无历史 Critical,required CI 全绿,未发现新的 Critical。破坏性变更已在标题、导出清单与用户文档中交代清楚。
What this PR does
--output-format stream-jsonno longer emits theactive_goalstream event. It was a projection ofgoal_stateinto the pre-#7895 card shape, emitted after everygoal_statewhen partial messages were on. With it go the second projection core kept to decide when to emit it (projectActiveGoal,sameActiveGoalProjectionand their bookkeeping inclient.ts),LlmEventType.ActiveGoal,ServerLlmActiveGoalEventand its deprecatedServerGeminialias, theActiveGoalwire type, the headlessActiveGoalStreamEvent, and the twobreakcases the TUIs kept to ignore it.goal_stateis emitted exactly as before, with or without--include-partial-messages.Why it's needed
Step 5-b-1 of #12179.
docs/users/features/headless.mdhas documentedactive_goalas a compatibility projection withgoal_stateauthoritative since #7895 (2026-07-29); neither TUI reads it (use-llm-stream.ts,event-adapter.tsboth ignore it); nothing else in this repository consumes it. It costs one extra stream event per Goal transition and ~60 lines of projection kept in step with the canonical state.Reviewer Test Plan
How to verify
StreamJsonOutputAdapter.test.ts: aGoalStateevent with partial messages on producesgoal_stateas the only Goal stream event; without partial messages it still does. The cases that assertedactive_goalfollowed it are gone.nonInteractiveCli.test.ts:/goalin headless stream-json mode with partial messages emits['goal_state'].client-goal.test.ts: ordering assertions now anchor on thegoal_stateevents themselves (initial state,pause,turn_finished) instead of the projection that followed them.event-adapter.test.ts: an unknown event type maps to nothing (the formeractive_goalcase).LlmEventType.ActiveGoaland theActiveGoaltype no longer exist, so any remaining consumer fails to compile.Evidence (Before & After)
A real headless run on the bundled branch,
-p "/goal set …" --output-format stream-json --include-partial-messages, tallied byevent.type:No
active_goalentries; on main the same run emits one after eachgoal_state. Local: coreclient,client-goal,goal-legacy-projection,turn: 538 passed; cliStreamJsonOutputAdapter(both files),nonInteractiveCli,event-adapter,use-llm-stream: 570 passed;tsc --noEmitclean for core and cli; core builds; the JS bundle builds; ESLint and Prettier clean on every changed file.Tested on
Environment (optional)
Unit tests plus one headless stream-json run against the bundled
dist/cli.js.Risk & Scope
active_goalstops seeing it. The doc line that introduced it as a compatibility projection now says it is no longer emitted and to readgoal_state; the!in the title puts it in the release notes' breaking-changes list._meta.goalStatusprojection andgoal-legacy-projection.tsitself, which stay for the Web Shell's history card until 5-b-2 (Goal: retire the legacy card projection (ladder step 5-b) #12179).LlmEventType.ActiveGoal,ServerLlmActiveGoalEvent,ServerGeminiActiveGoalEventandActiveGoalleave the core exports;ActiveGoalStreamEventleaves the cli's headless types. Consumers readgoal_state/ServerLlmGoalStateEvent.Linked Issues
Part of #12179 (5-b-1). Ladder: #12053.
中文说明
这个 PR 做了什么
--output-format stream-json不再发出active_goal流事件。它是把goal_state投影成 #7895 之前卡片形态的产物,在开启 partial messages 时跟在每条goal_state之后。随之删除的还有 core 里为决定何时发它而保留的第二份投影(client.ts的projectActiveGoal、sameActiveGoalProjection及其簿记)、LlmEventType.ActiveGoal、ServerLlmActiveGoalEvent与其已弃用的ServerGemini别名、ActiveGoal线上类型、headless 的ActiveGoalStreamEvent,以及两个 TUI 为忽略它而保留的两处break。goal_state的发送与之前完全一致,有无--include-partial-messages都发。为什么需要
#12179 的 5-b-1。
docs/users/features/headless.md自 #7895(2026-07-29)起就把active_goal写成兼容投影、goal_state为权威;两个 TUI 都不读它(use-llm-stream.ts、event-adapter.ts均忽略);仓库内没有其他消费者。它每次 Goal 转换多发一条流事件,并让约 60 行投影代码必须与规范状态保持同步。评审测试计划
如何验证
StreamJsonOutputAdapter.test.ts:开启 partial messages 时一条GoalState事件只产生goal_state这一个 Goal 流事件;关闭时同样。原先断言active_goal跟随其后的用例已删。nonInteractiveCli.test.ts:headless stream-json 模式开启 partial messages 执行/goal,发出['goal_state']。client-goal.test.ts:顺序断言改为锚定goal_state事件本身(初始状态、pause、turn_finished),而不是跟在后面的投影。event-adapter.test.ts:未知事件类型映射为空(即原来的active_goal用例)。LlmEventType.ActiveGoal与ActiveGoal类型不复存在,任何残留消费者都会编译失败。证据(前后对比)
用本分支打包产物真实执行 headless:
-p "/goal set …" --output-format stream-json --include-partial-messages,按event.type统计见上方英文部分:goal_state3 条,没有任何active_goal;main 上同样的运行每条goal_state之后会多一条active_goal。本地:core 的client、client-goal、goal-legacy-projection、turn共 538 个通过;cli 的StreamJsonOutputAdapter(两个文件)、nonInteractiveCli、event-adapter、use-llm-stream共 570 个通过;core 与 cli 的tsc --noEmit干净;core 构建成功;JS bundle 构建成功;所有改动文件 ESLint 与 Prettier 均干净。测试平台
macOS⚠️ 未测,Windows ⚠️ 未测,Linux ✅ 已测。
环境
单元测试,外加一次针对打包产物
dist/cli.js的 headless stream-json 运行。风险与范围
active_goal的自动化脚本将不再看到它。当初引入它作为兼容投影的那行文档现已改为"不再发出,读goal_state";标题里的!会让它进入发布说明的破坏性变更列表。_meta.goalStatus投影和goal-legacy-projection.ts本身,它们为 Web Shell 的历史卡片保留到 5-b-2(Goal: retire the legacy card projection (ladder step 5-b) #12179)。LlmEventType.ActiveGoal、ServerLlmActiveGoalEvent、ServerGeminiActiveGoalEvent、ActiveGoal从 core 导出中移除;ActiveGoalStreamEvent从 cli 的 headless 类型中移除。消费者改读goal_state/ServerLlmGoalStateEvent。关联 Issue
Part of #12179(5-b-1)。阶梯:#12053。