Skip to content

refactor(core): centralize extension runtime refresh - #6152

Merged
wenshao merged 5 commits into
QwenLM:mainfrom
ZijianZhang989:feat/extension-runtime-refresh
Jul 5, 2026
Merged

refactor(core): centralize extension runtime refresh#6152
wenshao merged 5 commits into
QwenLM:mainfrom
ZijianZhang989:feat/extension-runtime-refresh

Conversation

@ZijianZhang989

Copy link
Copy Markdown
Collaborator

What this PR does

Extracts the body of ExtensionManager.refreshMemory() into a standalone, exported refreshExtensionRuntime() function in a new packages/core/src/extension/extension-runtime-refresh.ts module. The function takes ExtensionRuntimeRefreshConfig, a Pick<Config, 'getToolRegistry' | 'getSkillManager' | 'getSubagentManager' | 'refreshHierarchicalMemory'> view of Config, so it can run without an ExtensionManager instance. ExtensionManager.refreshMemory() now just delegates to it. This is a pure extraction: the restart-MCP-servers step, the Promise.allSettled refresh of skills/subagents, and the awaited try/catch around hierarchical memory refresh are all preserved exactly as before, with no behavior change.

Why it's needed

Part of #3696 (comprehensive hot-reload system), specifically the sub-task for a single orchestrator that refreshes subsystem caches and runtime state in a predictable order. Today that refresh logic is inlined inside ExtensionManager, which makes it unreachable for planned future call sites: a manual /reload-plugins command for external file changes, and additional refresh steps for commands, hooks, and LSP servers. This PR is step 1 of the sequenced plan in docs/plans/2026-07-01-extension-runtime-refresh-implementation.md ("Shared Extension Runtime Refresh Orchestrator"): move the current refresh sequence behind one function with no behavior change, so later PRs have a single place to attach the additional refresh steps and model-facing notifications.

Reviewer Test Plan

How to verify

Run the existing extension manager suite plus the new orchestrator test, and confirm the project still typechecks cleanly:

npx vitest run packages/core/src/extension/extensionManager.test.ts packages/core/src/extension/extension-runtime-refresh.test.ts
npm run typecheck

Both suites pass (59 tests). Manually, toggling an extension (enable/disable/install/update/uninstall) should still refresh MCP servers, skills, subagents, and hierarchical memory exactly as before — there is no observable behavior change, since refreshMemory() now only delegates to the extracted function.

Evidence (Before & After)

N/A — internal refactor, no user-visible or TUI change.

Tested on

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

Environment (optional)

Local: npm run dev on macOS, Node 22.

Risk & Scope

  • Main risk or tradeoff: none functionally — verified the extraction is behaviorally identical to the original refreshMemory() body (same Promise.allSettled semantics, same try/catch around refreshHierarchicalMemory, same restartMcpServers ordering).
  • Not validated / out of scope: this PR doesn't change MCP restart cost, and doesn't add command/hook/LSP refresh or model-facing availability notifications — those are the follow-up PRs in the linked plan doc.
  • Breaking changes / migration notes: none. refreshExtensionRuntime and ExtensionRuntimeRefreshConfig are new exports from extension-runtime-refresh.ts; they are not yet re-exported from the package barrel, since no consumer outside packages/core/src/extension/ needs them yet.

Linked Issues

Part of #3696

中文说明

这个 PR 做了什么

ExtensionManager.refreshMemory() 的方法体抽取成一个独立导出的 refreshExtensionRuntime() 函数,放进新文件 packages/core/src/extension/extension-runtime-refresh.ts。该函数接收 ExtensionRuntimeRefreshConfig,即 Pick<Config, 'getToolRegistry' | 'getSkillManager' | 'getSubagentManager' | 'refreshHierarchicalMemory'>,因此可以在没有 ExtensionManager 实例的情况下运行。ExtensionManager.refreshMemory() 现在只是委托给它。这是一次纯粹的抽取:重启 MCP 服务器的步骤、通过 Promise.allSettled 刷新 skills/subagents、以及对 hierarchical memory 刷新的 await + try/catch,行为都与之前完全一致,没有任何变化。

为什么需要

属于 #3696(全面的热重载系统)的一部分,具体是其中"统一的缓存/运行时状态刷新编排器"子任务。目前这部分刷新逻辑写死在 ExtensionManager 内部,导致未来的调用点(比如面向外部文件变更的手动 /reload-plugins 命令,以及 commands/hooks/LSP 的额外刷新步骤)无法复用它。这个 PR 是 docs/plans/2026-07-01-extension-runtime-refresh-implementation.md 中规划的第一步("共享的 Extension Runtime 刷新编排器"):把现有的刷新流程收进一个函数、不改变任何行为,这样后续 PR 就有了一个统一的地方去挂接更多刷新步骤和面向模型的通知。

Reviewer Test Plan

(测试步骤同上,此处省略以保持可读性。)

风险与范围

  • 主要风险与权衡:功能上没有风险——已验证这次抽取与原始 refreshMemory() 方法体在行为上完全一致(相同的 Promise.allSettled 语义、相同的 refreshHierarchicalMemory try/catch、相同的 restartMcpServers 执行顺序)。
  • 未验证/超出范围:这个 PR 不涉及 MCP 重启开销的优化,也没有加入 commands/hooks/LSP 刷新或面向模型的可用性变更通知——这些都是关联计划文档中后续 PR 的工作。
  • Breaking changes / 迁移说明:无。refreshExtensionRuntimeExtensionRuntimeRefreshConfigextension-runtime-refresh.ts 新增的导出,目前还没有从包的 barrel 文件中重新导出,因为暂时没有 packages/core/src/extension/ 之外的调用方需要它们。

关联 Issues

属于 #3696 的一部分

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

On direction: this is a clean, well-scoped extraction that fits squarely within the extension system improvement track (#3696). The motivation is sound — having a single orchestrator function for extension runtime refresh is a reasonable foundation for the planned follow-up work (commands, hooks, LSP, /reload-plugins). No direction concerns.

On approach: the scope is tight — one function extraction, one delegation site, one test file, plus a plan doc for the broader initiative. The 320-line plan document is substantial for what's essentially a 40-line code move, but it's explicitly referenced in the PR description as context for future PRs, so it earns its place. No scope creep, no drive-by refactors. Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

方向:这是一个范围清晰的重构,完全契合扩展系统改进方向(#3696)。动机合理——将扩展运行时刷新逻辑收进一个统一的编排函数,为后续工作(commands、hooks、LSP、/reload-plugins)打下基础。方向上没有顾虑。

方案:范围控制得当——一个函数抽取、一个委托调用点、一个测试文件,加上一份用于描述后续 PR 序列的计划文档。320 行的计划文档对于一个本质上是 40 行代码搬移的 PR 来说看起来较多,但 PR 描述中明确引用它作为后续 PR 的上下文,因此有其存在的价值。没有范围蔓延,没有顺手重构。进入代码审查 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Code Review

I compared the extracted refreshExtensionRuntime() against the original refreshMemory() line by line (using git show main:... as the baseline). The extraction is faithful — same await on restartMcpServers(), same Promise.allSettled for skills/subagents with the same skillManager?. null-guard and rejection-logging pattern, same try/catch around refreshHierarchicalMemory(). The only differences are non-behavioral: this.configconfig parameter, logger tag 'EXTENSIONS''EXTENSION_RUNTIME_REFRESH', and debug-message prefixes. The new error-handling contract comment (Tier 1/2/3) is a documentation improvement that will help future contributors adding refresh steps.

The extensionManager.ts change is minimal — one import and a one-line delegation. Grep confirms refreshMemory() is the only entry point for this logic; no other callers bypass it. The Pick<Config, ...> type is appropriately narrow (4 members).

Tests cover the five key scenarios: no-config guard, happy path, partial allSettled failure, refreshHierarchicalMemory failure (caught), and restartMcpServers failure (propagated). All correct.

No blockers. No AGENTS.md violations.

Test Results

New orchestrator tests: 5/5 pass

$ cd packages/core && npx vitest run src/extension/extension-runtime-refresh.test.ts

 RUN  v3.2.4 .../packages/core
      Coverage enabled with v8

 ✓ src/extension/extension-runtime-refresh.test.ts (5 tests) 6ms

 Test Files  1 passed (1)
      Tests  5 passed (5)
   Start at  16:15:32
   Duration  2.86s

Typecheck (core package): clean ✅ (0 errors)

Real-Scenario Testing

N/A — pure internal refactor with no user-visible or TUI change. The PR body explicitly marks Before/After as N/A. Unit tests and typecheck are the appropriate verification here.

Additionally, collaborator @wenshao independently performed a comprehensive real-binary A/B verification (detailed in their comment above) that exercised all four refresh legs with a probe extension, confirming byte-for-byte identical observables between the pre-refactor and PR binaries. That external validation reinforces the conclusion that this extraction is behavior-preserving.

中文说明

代码审查

逐行对比了抽取的 refreshExtensionRuntime() 与原始 refreshMemory()(以 git show main:... 为基线)。抽取完全忠实——restartMcpServers()await、skills/subagents 的 Promise.allSettled 及相同的 skillManager?. 空值保护与 rejection 日志模式、refreshHierarchicalMemory()try/catch 都保持一致。唯一差异是非行为性的:this.configconfig 参数、logger tag 从 'EXTENSIONS' 改为 'EXTENSION_RUNTIME_REFRESH'、以及调试日志前缀。新增的错误处理契约注释(Tier 1/2/3)是一个文档改进,有助于未来添加刷新步骤的贡献者。

extensionManager.ts 的改动很小——仅一个 import 和一行委托调用。Grep 确认 refreshMemory() 是该逻辑的唯一入口,没有其他调用方绕过它。Pick<Config, ...> 类型收窄得当(4 个成员)。

测试覆盖了五个关键场景:无 config 提前返回、正常路径、allSettled 部分失败、refreshHierarchicalMemory 失败(被捕获)、restartMcpServers 失败(向上抛出)。全部正确。

无阻塞问题。无 AGENTS.md 违规。

测试结果

新编排器测试:5/5 通过
类型检查(core 包):通过 ✅(0 错误)

真实场景测试

不适用——纯内部重构,无用户可见或 TUI 变化。PR 正文中明确标注 Before/After 为 N/A。单元测试和类型检查是此处合适的验证方式。

此外,协作人员 @wenshao 独立进行了全面的真实二进制 A/B 验证(详见其评论),使用探针扩展覆盖了全部四条刷新路径,确认重构前与 PR 二进制之间可观测项逐字节一致。该外部验证进一步证实本次抽取保持行为不变。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

This is a textbook extraction refactor: the original refreshMemory() body moved into a standalone function with no behavioral changes, the caller delegates cleanly, tests cover all the important paths, and typecheck is green. My independent proposal would have been identical: extract the refresh body into a free function parameterized by a narrow Pick<Config, ...> type, keep the original method as a thin delegate. The PR matches this exactly.

The plan document, while lengthy, serves a real purpose — it's the roadmap for a 6-PR sequence and this PR is explicitly step 1.

Two pieces of external validation reinforce the verdict: the 5 new unit tests with their tiered error-handling contract, and @wenshao's independent real-binary A/B verification exercising all four refresh legs.

One note: the extensionManager.test.ts suite has pre-existing failures unrelated to this change (visible on both the PR branch and main). Not a blocker, but worth flagging for follow-up.

Verdict: clean, ready to merge. Escalating to maintainer for final approval since this is a fork refactor PR. 🙏

中文说明

这是一个教科书式的抽取重构:原始 refreshMemory() 方法体被搬移到独立函数中,行为完全不变;调用方干净地委托;测试覆盖了所有重要路径;类型检查全部通过。我的独立方案会完全相同:将刷新方法体抽取到以窄 Pick<Config, ...> 类型参数化的自由函数中,保留原方法作为薄委托。PR 完全匹配这个方案。

计划文档虽然较长,但有实际用途——它是一个 6 个 PR 序列的路线图,本 PR 明确是第一步。

两项外部验证支持该结论:5 个新的单元测试及其分层错误处理契约,以及 @wenshao 独立的真实二进制 A/B 验证覆盖了全部四条刷新路径。

一个备注:extensionManager.test.ts 有与本改动无关的既有失败(在 PR 分支和 main 上都可见)。不是阻塞问题,但值得在后续工作中关注。

结论: 干净,可以合并。因为是 fork 重构 PR,交由维护者做最终审批。🙏

Qwen Code · qwen3.7-max

Comment thread packages/core/src/extension/extension-runtime-refresh.test.ts
Comment thread packages/core/src/extension/extensionManager.ts
@yiliang114
yiliang114 marked this pull request as draft July 2, 2026 03:19
@yiliang114
yiliang114 marked this pull request as ready for review July 2, 2026 03:19
Comment thread packages/core/src/extension/extension-runtime-refresh.test.ts

@qwen-code-ci-bot qwen-code-ci-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.

⚠️ Downgraded from Approve to Comment: CI still running.

Clean extraction — the control flow, error handling, and Promise.allSettled semantics are faithfully preserved. One test improvement suggestion below.

Comment thread packages/core/src/extension/extension-runtime-refresh.test.ts

@qwen-code-ci-bot qwen-code-ci-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.

No issues found. Clean extraction — the control flow, error handling, and Promise.allSettled semantics are faithfully preserved from the original refreshMemory() body. Typecheck and ESLint pass; all 4 new tests pass. Downgraded from Approve to Comment: CI still running.

— qwen3.7-max via Qwen Code /review

doudouOUC
doudouOUC previously approved these changes Jul 2, 2026

@doudouOUC doudouOUC 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.

Clean extraction — control flow, error handling, and Promise.allSettled semantics are faithfully preserved from the original refreshMemory() body. Build, typecheck, and all 4 new tests pass. LGTM! ✅

— qwen3.7-max via Qwen Code /review

Comment thread packages/core/src/extension/extension-runtime-refresh.test.ts
Comment thread packages/core/src/extension/extension-runtime-refresh.ts
Comment thread packages/core/src/extension/extension-runtime-refresh.ts
…onale comments

Address @wenshao's review feedback:
- Add test for restartMcpServers rejection (the only fatal error path)
- Restore key 'why' comments about allSettled and try/catch design decisions
- Logger tag change to EXTENSION_RUNTIME_REFRESH is intentional (standalone module)

@DragonnZhang DragonnZhang 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.

New review. Clean extraction of the extension runtime refresh logic from ExtensionManager.refreshMemory() into a shared refreshExtensionRuntime() function in extension-runtime-refresh.ts.

Analysis:

  • Control flow is faithfully preserved: MCP restart first (awaited, failure propagates), then Promise.allSettled for skills + subagents (failure logged but non-fatal), then refreshHierarchicalMemory (failure caught and logged).
  • The ExtensionManager.refreshMemory() body is replaced with a single delegation call — zero behavioral change.
  • 4 new tests cover: undefined config early return, happy path, refreshCache rejection resilience, and refreshHierarchicalMemory rejection.
  • The restartMcpServers rejection test correctly verifies it blocks subsequent legs (matching the sequential await before allSettled).
  • Plan document provides useful context for the phased rollout.

No high-confidence issues found. All 7 existing inline comments are resolved Suggestions from prior reviews.

Downgraded from Approve to Comment: CI still running (Test ubuntu-latest pending).

— qwen3-coder via Qwen Code /review

@wenshao wenshao 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.

No review findings. Downgraded from Approve to Comment: CI still running.

— GPT-5 via Qwen Code /review

@qwen-code-ci-bot qwen-code-ci-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.

No review findings. Clean extraction — the control flow, error handling, and Promise.allSettled semantics are faithfully preserved from the original refreshMemory() body. ESLint and typecheck pass; all 5 new tests pass with full coverage. Downgraded from Approve to Comment: CI still running.

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot qwen-code-ci-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.

Clean extraction — the control flow, error handling, and Promise.allSettled semantics are faithfully preserved from the original refreshMemory() body. Build passes, typecheck clean, all 5 new tests pass with good branch coverage (happy path, undefined config, allSettled rejection, hierarchical memory rejection, and fatal MCP rejection).

Downgraded from Approve to Comment: CI still running.

— qwen3.7-max via Qwen Code /review

Comment thread packages/core/src/extension/extension-runtime-refresh.ts
Comment thread packages/core/src/extension/extension-runtime-refresh.test.ts
@wenshao

wenshao commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao

wenshao commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

✅ Real-binary verification (merge reference)

I built the actual qwen binary from this PR's head (b4825487) and drove it in a real TUI (tmux) to confirm the refactor is behavior-preserving. Bottom line: safe to merge — every observable is identical before/after the extraction, in both the enable and disable directions.

Verification had four layers:

  1. Static — the diff is a pure code move; refreshMemory() now await refreshExtensionRuntime(this.config), and the extracted body is character-identical apart from three non-behavioral edits: the receiver (this.configconfig), the logger tag, and the debug-message prefixes (refreshMemory:refreshExtensionRuntime:).
  2. Unit + mutation — the 5 new tests + 55 extensionManager tests pass; mutation testing proves they are load-bearing.
  3. Real E2E — a purpose-built extension bundling all four refresh legs (MCP server + skill + subagent + QWEN.md) toggled in a live session.
  4. A/B equivalence — the same sequence run against a pre-refactor (inline refreshMemory) binary vs the PR (delegated) binary, byte-for-byte identical observations.

1 · Unit tests + mutation

npx vitest run extension-runtime-refresh.test.ts extensionManager.test.ts60 passed (5 + 55). npm run build compiles all packages cleanly (0 type errors).

To prove the new tests actually guard the control flow, I mutated the source and re-ran the suite:

# Mutation Targeted test Result
P1 restartMcpServers() made non-fatal (wrap in try/catch) rejects when restartMcpServers fails 🔴 fails — MCP is correctly the only fatal leg
P2 Promise.allSettledPromise.all continues when a refreshCache leg rejects (+2) 🔴 3 fail — non-cascade is load-bearing
P3 hierarchical-memory try/catch → rethrow resolves when refreshHierarchicalMemory throws 🔴 fails — swallow is load-bearing
N1 skillManager?.refreshCache()skillManager.refreshCache() (none) 🟢 all pass — coverage gap: the ?. null-guard has no test

P1 is exactly the restartMcpServers reject path added on review — confirmed load-bearing. N1 is a minor, optional nit (see notes).


2 · Real E2E — all four legs fire live (no restart)

I installed a probe extension that bundles an MCP server, a skill, a subagent and a QWEN.md, so a single toggle exercises every leg of refreshExtensionRuntime():

consent

/extensions install fires installExtension → enableExtension → refreshTools → refreshMemory → refreshExtensionRuntime. All four subsystems updated in-process, no restart:

Leg 1 — restartMcpServers(): probeServer connects (0 → 1 server).

mcp

Leg 2 — SkillManager.refreshCache(): pr6152-probe-skill appears (8 → 9 skills).

skills

Leg 3 — SubagentManager.refreshCache(): pr6152-probe-agent appears (3 → 4 agents).

agents

Leg 4 — refreshHierarchicalMemory(): the extension's QWEN.md is loaded (/contextMemory files gains the .qwen/extensions/… entry, 85 tokens).

context

Disabling it (manage dialog) runs refreshExtensionRuntime() again and tears the MCP server down live:

disable


3 · A/B equivalence — inline vs delegated

Same probe, same key sequence, on a pre-refactor binary (reverted refreshMemory to its old inline body) and the PR binary:

Observable Baseline OLD (inline refreshMemory) NEW (refreshExtensionRuntime)
/mcp after enable 0 probeServer ✓ connected probeServer ✓ connected
/skills after enable 8 9 (+probe-skill) 9 (+probe-skill)
/agents after enable 3 4 (+probe-agent) 4 (+probe-agent)
Memory files after enable 300 tok (ext QWEN.md 85) 300 tok (ext QWEN.md 85)
/mcp after disable probeServer ✗ disconnected probeServer ✗ disconnected
/skills after disable 8 8
Memory files after disable 215 tok (ext dropped) 215 tok (ext dropped)

Identical in every row. The extraction changes nothing observable.


4 · Notes (not blockers)

  • Pre-existing, out of scope: /agents manage keeps a disabled extension's agent listed until uninstall (the other three legs drop correctly). This reproduces identically on the pre-refactor binary, so it is not caused by this PR — the subagent leg is called byte-for-byte as before. Worth a separate look, but does not affect this merge.
  • Minor nit (optional): mutation N1 shows the skillManager?. null-guard is untested. A one-line test (getSkillManager() returns undefined → still resolves, other legs run) would close it. Fine to defer to PR 2.

Verdict: LGTM — pure, regression-tested extraction; no behavior change. 👍

🇨🇳 中文说明(点击展开)

✅ 真实二进制验证(合并参考)

我从本 PR 的 head(b4825487)构建了真实的 qwen 二进制,并在真实 TUI(tmux)中驱动,确认这次重构保持行为不变。结论:可以安全合并——抽取前后每一个可观测项都完全一致,enable 与 disable 两个方向都是。

验证分四层:

  1. 静态 —— 这个 diff 是纯代码搬移;refreshMemory() 现在只是 await refreshExtensionRuntime(this.config),被抽取的方法体逐字符相同,只有三处非行为性改动:receiver(this.configconfig)、logger tag、以及调试日志前缀(refreshMemory:refreshExtensionRuntime:)。
  2. 单测 + 变异 —— 5 个新测试 + 55 个 extensionManager 测试通过;变异测试证明它们是"承重"的、非空过。
  3. 真实 E2E —— 专门造了一个同时打包全部四条刷新路径(MCP server + 技能 + 子代理 + QWEN.md)的扩展,在运行中的会话里切换开关。
  4. A/B 等价 —— 同样的序列,分别跑在重构前(内联 refreshMemory)二进制PR(委托)二进制上,观测逐项完全一致。

1 · 单测 + 变异

vitest 跑两个测试文件 → 60 通过(5 + 55)。npm run build 全包编译干净(0 type error)。

为证明新测试真的守住控制流,我对源码做变异后重跑:

# 变异 命中的测试 结果
P1 restartMcpServers() 变成非致命(包 try/catch rejects when restartMcpServers fails 🔴 挂 —— MCP 确实是唯一的致命路径
P2 Promise.allSettledPromise.all continues when a refreshCache leg rejects(+2) 🔴 挂 3 个 —— "不级联"是承重的
P3 memory 的 try/catch → 改成 rethrow resolves when refreshHierarchicalMemory throws 🔴 挂 —— "吞异常"是承重的
N1 skillManager?.refreshCache()skillManager.refreshCache() (无) 🟢 全绿 —— 测试缺口?. 空值保护没有测试

P1 正是 review 阶段补的 restartMcpServers 拒绝路径——确认承重。N1 是个可选的小 nit(见备注)。

2 · 真实 E2E —— 四条路径在运行中实时触发(无需重启)

我安装的探针扩展同时打包了 MCP server、技能、子代理和 QWEN.md,因此一次切换就能覆盖 refreshExtensionRuntime() 的每条路径(见上方第①张图)。

/extensions install 触发 installExtension → enableExtension → refreshTools → refreshMemory → refreshExtensionRuntime。四个子系统全部在进程内更新,无需重启:

  • 路径 1 —— restartMcpServers()probeServer 连上(0 → 1 server)。(第②图)
  • 路径 2 —— SkillManager.refreshCache()pr6152-probe-skill 出现(8 → 9 技能)。(第③图)
  • 路径 3 —— SubagentManager.refreshCache()pr6152-probe-agent 出现(3 → 4 agents)。(第④图)
  • 路径 4 —— refreshHierarchicalMemory():扩展的 QWEN.md 被加载(/contextMemory files 多出 .qwen/extensions/… 一项,85 tokens)。(第⑤图)

在管理弹窗里禁用它会再次运行 refreshExtensionRuntime(),MCP server 被实时拆除。(第⑥图)

3 · A/B 等价 —— 内联 vs 委托

同一个探针、同一段关键序列,跑在重构前二进制(把 refreshMemory 还原成旧的内联版本)和 PR 二进制上:

可观测项 基线 旧(内联 refreshMemory 新(refreshExtensionRuntime
enable 后 /mcp 0 probeServer ✓ connected probeServer ✓ connected
enable 后 /skills 8 9(+probe-skill) 9(+probe-skill)
enable 后 /agents 3 4(+probe-agent) 4(+probe-agent)
enable 后 Memory files 300 tok(扩展 QWEN.md 85) 300 tok(扩展 QWEN.md 85)
disable 后 /mcp probeServer ✗ disconnected probeServer ✗ disconnected
disable 后 /skills 8 8
disable 后 Memory files 215 tok(扩展项掉了) 215 tok(扩展项掉了)

每一行都完全相同。 这次抽取不改变任何可观测行为。

4 · 备注(非阻塞)

  • 既有问题,超出范围: /agents manage 会把一个已禁用扩展的 agent 一直列到 uninstall 为止(另外三条路径都正确掉了)。这在重构前的二进制上同样复现,所以不是本 PR 引入的——子代理这条路径的调用与之前逐字节一致。值得单独看,但不影响本次合并。
  • 小 nit(可选): 变异 N1 说明 skillManager?. 空值保护没测试。加一行测试(getSkillManager() 返回 undefined → 仍 resolve、其它路径照跑)即可补上。留到 PR 2 也行。

结论:LGTM —— 纯粹、且有变异回归保护的抽取;无行为变化。👍

Verified on macOS · Node 22 · real qwen binary built from PR head + a pre-refactor A/B binary · probe extension exercising all four refresh legs.

Add block comment documenting the three-tier error-handling contract
(fatal/swallow/swallow) so future maintainers know which tier applies
when adding new refresh steps. Addresses review feedback on QwenLM#6152.

@qwen-code-ci-bot qwen-code-ci-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.

No review findings. Downgraded from Approve to Comment: CI still running.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao

wenshao commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-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.

This change touches core infrastructure at scale (539 lines across packages/core/src/extension/). Per the project's core module protection policy, core refactors of this size must be maintainer-initiated.

Please open an issue to discuss the design first, or coordinate with a maintainer to champion this change.

中文说明

此 PR 涉及核心基础设施的大规模改动(539 行,跨越 packages/core/src/extension/)。根据项目的核心模块保护策略,此规模的核心重构必须由维护者发起。

请先开一个 issue 讨论设计方案,或与维护者协调由他们来推动此变更。

Qwen Code · qwen3.7-max

@yiliang114
yiliang114 dismissed qwen-code-ci-bot’s stale review July 5, 2026 06:55

False positive: Stage 0 counted total PR lines instead of protected core production lines. Docs/tests should not trigger the 500-line core hard block.

@yiliang114

Copy link
Copy Markdown
Collaborator

@qwen-code /review --timeout=180

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor
_Qwen Code review request accepted. Review is queued in [workflow run](https://github.com/QwenLM/qwen-code/actions/runs/28732595815)._

@qwen-code-ci-bot qwen-code-ci-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.

No issues found. LGTM! ✅

— qwen3.7-max via Qwen Code /review

@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. Clean extraction — control flow, error-handling contract (fatal/swallow/swallow tiers), and Promise.allSettled semantics are faithfully preserved. Good test coverage across all error paths. The plan doc adds useful context for the follow-up PRs in #3696.

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.

6 participants