Skip to content

refactor(goal)!: stop emitting the active_goal stream event - #12181

Merged
qqqys merged 1 commit into
QwenLM:mainfrom
qqqys:refactor/goal-retire-active-goal-event
Sep 18, 2026
Merged

qqqys merged 1 commit into
QwenLM:mainfrom
qqqys:refactor/goal-retire-active-goal-event

Conversation

@qqqys

@qqqys qqqys commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

--output-format stream-json no longer emits the active_goal stream event. It was a projection of goal_state into the pre-#7895 card shape, emitted after every goal_state when partial messages were on. With it go the second projection core kept to decide when to emit it (projectActiveGoal, sameActiveGoalProjection and their bookkeeping in client.ts), LlmEventType.ActiveGoal, ServerLlmActiveGoalEvent and its deprecated ServerGemini alias, the ActiveGoal wire type, the headless ActiveGoalStreamEvent, and the two break cases the TUIs kept to ignore it. goal_state is emitted exactly as before, with or without --include-partial-messages.

Why it's needed

Step 5-b-1 of #12179. docs/users/features/headless.md has documented active_goal as a compatibility projection with goal_state authoritative since #7895 (2026-07-29); neither TUI reads it (use-llm-stream.ts, event-adapter.ts both 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: a GoalState event with partial messages on produces goal_state as the only Goal stream event; without partial messages it still does. The cases that asserted active_goal followed it are gone.
  • nonInteractiveCli.test.ts: /goal in headless stream-json mode with partial messages emits ['goal_state'].
  • client-goal.test.ts: ordering assertions now anchor on the goal_state events 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 former active_goal case).
  • Type-level: LlmEventType.ActiveGoal and the ActiveGoal type 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 by event.type:

178 content_block_delta
 21 content_block_start
 21 content_block_stop
  3 goal_state
  4 message_start
  4 message_stop

No active_goal entries; on main the same run emits one after each goal_state. Local: core client, client-goal, goal-legacy-projection, turn: 538 passed; cli StreamJsonOutputAdapter (both files), nonInteractiveCli, event-adapter, use-llm-stream: 570 passed; tsc --noEmit clean for core and cli; core builds; the JS bundle builds; ESLint and Prettier clean on every changed file.

Tested on

OS Status
🍏 macOS ⚠️
🪟 Windows ⚠️
🐧 Linux ✅

Environment (optional)

Unit tests plus one headless stream-json run against the bundled dist/cli.js.

Risk & Scope

  • Main risk or tradeoff: automation that still filtered stream-json for active_goal stops seeing it. The doc line that introduced it as a compatibility projection now says it is no longer emitted and to read goal_state; the ! in the title puts it in the release notes' breaking-changes list.
  • Not validated / out of scope: the ACP _meta.goalStatus projection and goal-legacy-projection.ts itself, which stay for the Web Shell's history card until 5-b-2 (Goal: retire the legacy card projection (ladder step 5-b) #12179).
  • Breaking changes / migration notes: LlmEventType.ActiveGoal, ServerLlmActiveGoalEvent, ServerGeminiActiveGoalEvent and ActiveGoal leave the core exports; ActiveGoalStreamEvent leaves the cli's headless types. Consumers read goal_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_state 3 条,没有任何 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 运行。

风险与范围

  • 主要风险或取舍:仍在从 stream-json 里过滤 active_goal 的自动化脚本将不再看到它。当初引入它作为兼容投影的那行文档现已改为"不再发出,读 goal_state";标题里的 ! 会让它进入发布说明的破坏性变更列表。
  • 未验证 / 范围外:ACP 的 _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。

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 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@qqqys
qqqys enabled auto-merge September 18, 2026 11:52

@qwen-code-dev-bot qwen-code-dev-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 / sameActiveGoalProjection on main leaves only two hits outside these 16 files, and both are unrelated: LegacyActiveGoal, which this PR correctly keeps for 5-b-2 and which goal-wire.ts still re-exports, and a comment naming failClosedActiveGoalTurn.
  • goal_state is unaffected in the way that matters. Its branch in the stream adapter emits through emitMessageImpl directly, so it bypasses the base finalized guard exactly as the deleted active_goal branch did — late Goal state changes still reach stream consumers. It is also ungated, whereas active_goal went through the partial-messages gate, so the surviving event is strictly more available than the one being removed.
  • takePendingGoalEvents keeps its drain and ordering semantics: splice(0, length) still empties the queue and every goal_state event is still pushed in subscription order, with only the interleaved projection gone.
  • Completeness is compiler-enforced rather than assumed: the use-llm-stream switch closes on const unreachable: never = event, and there is no Object.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: break ignores an active_goal that 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。破坏性变更已在标题、导出清单与用户文档中交代清楚。

@qqqys
qqqys added this pull request to the merge queue Sep 18, 2026
Merged via the queue into QwenLM:main with commit ab47e38 Sep 18, 2026
111 of 112 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants