feat(external-context): Publish the Mem0 Extension package - #10653
Conversation
E2E test reportValidated the packaged Extension through the same npm installation boundary administrators will use:
Additional verification: 49 Extension tests passed; 102 focused release/version tests passed with one existing host-specific skip; repository build, typecheck, and lint passed before the final rebase; the post-rebase focused tests and npm publish dry-run passed again. |
23c2f04 to
cc6a742
Compare
|
Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. 中文请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。 |
|
Thanks for the PR! Re-run on a moved head — the branch took a fix round ( Template looks good ✓
Moving on to code review. 🔍 中文说明感谢贡献!本次是在 head 变化后的 re-run——分支经历了一轮修复( 模板完整 ✓
进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not explored to full depth (tool budget reached): "agent 6b": live execution of scripts/tests/{get-release-version,package-scripts,release-workflow}.test.js and integrations/external-context-mem0/src/manifest.test.ts (veri….
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Test Plan (not a blocker): 102 tests pass — this review observed 49 passed.
中文说明
已审查。 建议见行内评论。
未探索到全部深度(达到工具调用预算):"agent 6b":live execution of scripts/tests/{get-release-version,package-scripts,release-workflow}.test.js and integrations/external-context-mem0/src/manifest.test.ts (veri…。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
Test Plan(非阻断):102 tests pass — this review observed 49 passed。
— qwen3.8-max via Qwen Code /review (v0.22.3)
Verification report — head
|
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 98 passed · 0 failed · 98 total Flakiness gate: ✅ 4 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:98 通过 · 0 失败 · 98 总计 抖动门:✅ 4 changed test file(s) x 5 identical rounds, no divergence Verification reportPR #10653 Deep Verification — feat(external-context): Publish the Mem0 Extension packageVerdict: 中文摘要
Scope
Central claim + A/BHead cells run at
One environment nuance found while building this oracle: npm 10.9.8 does not return EPRIVATE for a private workspace package — it silently no-ops with exit 0 (see Findings F2). Version-guard A/B (shared published-version guard)Real
Precondition checked: the base script already carries Version-alignment A/B (
|
| Arm | package.json | qwen-extension.json |
|---|---|---|
| head | 9.9.9-verify.0 | 9.9.9-verify.0 (aligned) |
| base | 9.9.9-verify.0 | 0.1.0 (left behind) — the flip |
Followed by the staging replay (witness: logs 05-gitadd.log): the head git add line (extracted verbatim from release.yml) stages the bumped manifest; the base line, run on the same tree, leaves it unstaged. The glob integrations/*/qwen-extension.json matches both integration manifests; the sibling external-context manifest is unmodified, so staging it is a no-op.
Publish-step replay (gate + flags)
The step's run: block was extracted from release.yml by a YAML parser and executed under bash --noprofile --norc with a recording npm stub. Witness: 02-publish-step-replay.png.
if:expression pinned verbatim, then evaluated as a truth table: runs only forgithub.repository == 'QwenLM/qwen-code'andvars.…ENABLED == 'true'(case-sensitive; unset/''/false/TRUEall skip; fork repos skip even with the var set).- Dry run →
publish --provenance --access public --tag=preview --dry-run(no registry probe). - Real run, version absent → probe
view @qwen-code/external-context-mem0@<ver> version, thenpublish --provenance --access public --tag=latest. - Real run, version present →
::notice::… already published; skipping, exit 0, no publish call. - Ordering: mem0 step index < audio-capture < qwen-code, and after
Build Bundle and Prepare Package(sodist/main.jsexists at publish time —scripts/build.jsbuildsintegrations/external-context-mem0on the non---cli-onlypath the release job uses). - Base arm: no mem0 step exists; base
git addline lacks the manifest glob.
E2E: registry install + runtime boot
Witness: 06-install-and-runtime-e2e.png; logs under logs/e2e/ and logs/runtime/.
- The head tarball was served by a loopback stub npm registry; the real CLI (
packages/cli/dist/index.js extensions install @qwen-code/external-context-mem0, isolatedHOME+QWEN_HOME) fetched the packument and tarball (request log asserted), installed, andextensions listprintedMem0 External Context (0.22.3)withSource: @qwen-code/external-context-mem0 (Type: npm). The installed manifest version is 0.22.3 — this PR's manifest bump makes that true now, and theversion.jsalignment step keeps it true for every future release (without them, the list would show the manifest's stale0.1.0). - The installed bundle was booted as a real MCP stdio server with admin-owned instance/dialect files and a loopback provider:
initializeandtools/list(exactlycontext_search) succeed;context_searchissued exactlyGET /memories/search?query=team+conventions&app_id=verify-app&top_k=5with the credential on the dialect-boundx-api-keyheader, and returned the normalized item; a running process kept its startup dialect after the file changed, a restart picked up the new path; an invalid dialect exited 1 with a bounded message leaking neither paths nor credential values.
Mutation matrix (vacuity)
Each PR hunk reverted in the head worktree; the PR's own tests were required to go red on the intended assertion. Witness: 05-mutation-matrix.png.
| Mutant | Suite(s) | Result |
|---|---|---|
| M0 unmutated control | all | green (2 + 301 tests) |
M1 private: true restored, repository removed |
manifest.test.ts | red on is self-contained and exposes only context_search |
M2 mem0 dropped from PUBLISHED_PACKAGES |
get-release-version.test.js | red on pins the full published-package set |
| M3 mem0 publish step removed from release.yml | release-workflow + package-scripts | red on all three step-related tests |
| M4 version.js alignment step removed | package-scripts.test.js | red on keeps the Mem0 Extension manifest aligned with release versions |
M5 positive control (pre-existing audio-capture entry dropped) |
get-release-version.test.js | red — the harness can fail |
Postcondition: worktree pristine after all reverts. All four hunks are load-bearing; no survivors.
Corrections
None — no prior round and no inaccurate bot claims to correct.
Findings
F1 — Suggestion (pre-existing, surfaced by this PR's purpose): the MCP server identifies itself as 0.1.0 forever.
integrations/external-context-mem0/src/mcp.ts:22 hardcodes version: '0.1.0' in new McpServer(...). After this PR, package.json and qwen-extension.json track the Qwen Code release version, but the initialize response's serverInfo.version (observed live: "version":"0.1.0" from the built bundle) stays stale. Cosmetic/observability only — nothing gates on it — but it is the one version surface the alignment work does not reach. Not introduced by this PR (the line predates it); a follow-up could inject the version at build or startup. No code change requested here.
F2 — Informational (npm semantics, no change requested): publishing a private workspace package is a silent exit-0 skip.
Measured on npm 10.9.8: npm publish [--dry-run] on a package still marked private inside a workspace prints Skipping workspace …, marked as private and exits 0 — no EPRIVATE, no failure. Consequence: a copy of this PR's publish-step pattern pointed at a not-yet-unprivated package would pass CI while publishing nothing. This PR is not exposed (the step is additionally gated by the repo var, and the package is unprivated in the same diff), but the skip is invisible unless someone reads the log line — worth knowing for future package additions.
Not covered
- Real npm.org publish and trusted-publisher setup — intentionally left to the release administrator by the PR; the gate keeps the step disabled until
NPM_EXTERNAL_CONTEXT_MEM0_TRUSTED_PUBLISHING_ENABLEDis set. The disabled-gate path and the enabled-step semantics are replayed, not a live publish. - Trial merge into current
main— this sandbox has no network/token to fetchorigin; verified against the PR's base only. The base is the merge-base tip, so the A/B is exact for what lands unlessmainhas moved (not observable here). - yamllint —
pip3: Permission deniedin this container; actionlint (live-controlled) and shellcheck ran clean instead. - Repo-wide test suite / repo-wide lint — targeted gates only: extension suite 49/49; PR-touched script suites + size ratchet 301 passed, 0 skipped (the container has GNU realpath, so the host-specific skip the test plan mentions did not apply);
tsc --noEmit, eslint (live-controlled by a planted unused-var), prettier, actionlint all clean; size baseline57772 release.ymlmatches the file's byte size exactly. - Harness-infrastructure incident (self-inflicted, resolved): the first tarball round was built in a worktree, which has no
node_modules; esbuild silently resolved the hoistedajv@6instead of the package's nestedajv@8, producing a bundle that crashed at startup (import_ajv.Ajv is not a constructor). Diagnosed, rebuilt/packed from the main tree (which carries the lockfile layout fromnpm ci, exactly as the publish job'snpm ci --ignore-scriptsreproduces it), and all downstream cells re-ran green against the correct artifact. Not a PR defect — recorded so thenode_modules-less-worktree trap is visible. - Windows behavior — the author tested macOS only; this round ran Linux E2E end to end, which covers the untested-by-author lane for install/run.
- Live provider services / Auto Recall / write operations — out of the PR's declared scope; not exercised.
Methodology
Ran inside the CI verify container (node v22.23.2, npm 10.9.8, same node:22-bookworm runtime as the triage lanes). Working tree = refs/pull/10653/merge at depth 2; scratch worktrees at tmp/base-tree (HEAD^1) and tmp/head-tree (HEAD), removed after use. Harnesses live in the artifact dir as harness-01…09*.mjs (+ stub dirs) with raw logs under logs/; each prints [PASS]/[FAIL] lines and an ASSERTIONS pass=N fail=M footer, and the 98 totals in assertions.json are the sum of those footers from the final runs. Techniques: npm pack/publish --dry-run on both arms; step bodies extracted from release.yml by a YAML parser and executed against a recording npm stub; the push-time guard run as a real process under PATH-stubbed npm/git/gh; scripts/version.js replayed with an npm emulator that applies npm version bumps and skips npm install; staging replayed via the extracted git add line; a loopback HTTP stub registry + the built CLI for the install E2E (with HOME/QWEN_HOME pinned after discovering QWEN_HOME wins over $HOME in Storage.getGlobalQwenDir()); and a loopback memory provider speaking the GET dialect for the runtime-boot wire assertions. Evidence images in evidence/ were rendered by scripts/verify-capture.mjs.
Flakiness gate log
rounds=5 files=4 skipped=0
file integrations/external-context-mem0/src/manifest.test.ts: (cd integrations/external-context-mem0) npx --no-install vitest run ./src/manifest.test.ts
file scripts/tests/get-release-version.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/get-release-version.test.js
file scripts/tests/package-scripts.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/package-scripts.test.js
file scripts/tests/release-workflow.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/release-workflow.test.js
per-file results (P=pass F=fail I=infra-exit, one letter per run):
integrations/external-context-mem0/src/manifest.test.ts: PPPPP
scripts/tests/get-release-version.test.js: PPPPP
scripts/tests/package-scripts.test.js: PPPPP
scripts/tests/release-workflow.test.js: PPPPP
verdict: pass
summary: 4 changed test file(s) x 5 identical rounds, no divergence
--- per-invocation detail (full copy in the artifact) ---
round 1 · integrations/external-context-mem0/src/manifest.test.ts: P (exit 0)
round 1 · scripts/tests/get-release-version.test.js: P (exit 0)
round 1 · scripts/tests/package-scripts.test.js: P (exit 0)
round 1 · scripts/tests/release-workflow.test.js: P (exit 0)
round 2 · integrations/external-context-mem0/src/manifest.test.ts: P (exit 0)
round 2 · scripts/tests/get-release-version.test.js: P (exit 0)
round 2 · scripts/tests/package-scripts.test.js: P (exit 0)
round 2 · scripts/tests/release-workflow.test.js: P (exit 0)
round 3 · integrations/external-context-mem0/src/manifest.test.ts: P (exit 0)
round 3 · scripts/tests/get-release-version.test.js: P (exit 0)
round 3 · scripts/tests/package-scripts.test.js: P (exit 0)
round 3 · scripts/tests/release-workflow.test.js: P (exit 0)
round 4 · integrations/external-context-mem0/src/manifest.test.ts: P (exit 0)
round 4 · scripts/tests/get-release-version.test.js: P (exit 0)
round 4 · scripts/tests/package-scripts.test.js: P (exit 0)
round 4 · scripts/tests/release-workflow.test.js: P (exit 0)
round 5 · integrations/external-context-mem0/src/manifest.test.ts: P (exit 0)
round 5 · scripts/tests/get-release-version.test.js: P (exit 0)
round 5 · scripts/tests/package-scripts.test.js: P (exit 0)
round 5 · scripts/tests/release-workflow.test.js: P (exit 0)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
Code reviewRe-reviewed the full diff at the new head
No merge-blocking findings. The three items from the maintainer verification (bootstrap-ordering doc sentence, the MCP identity still advertising Testing
CI is fully settled on the reviewed head — zero pending What the evidence does and does not cover:
Real-scenario testingN/A for this run — unattended CI path, and the PR has no TUI surface. Live install-path behavior is covered by the maintainer's local verification cited above. 中文说明代码审查:已在新 head
无阻断合并的问题。维护者验证中的三项(bootstrap 顺序的文档句子、MCP 身份仍自报 测试证据:被审 head 上 CI 已完全收敛—— 证据覆盖范围说明:
真实场景测试:本次运行不适用——无人值守 CI 路径,且 PR 无 TUI 面。安装路径的实际行为由上文引用的维护者本地验证覆盖。 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 4/5 — clean at every stage, and the policy hold from the last run is resolved by a CODEOWNER's sign-off; the point off a clean 5 is the three non-blocking follow-ups recorded below, one of which must land before the bootstrap is performed. Stepping back: my independent proposal for this problem and the PR's approach are still the same plan, and the implementation remains the conservative version of it — publishing stays dark behind a repository variable until the one-time bootstrap, the already-published guard and the step's own Three non-blocking follow-ups recorded in this thread (per the five-round rule, deferred rather than widening the PR): the design doc needs one sentence pinning the bootstrap ordering (bootstrap publishes an already-shipped release version, then the repository variable flips) — this must land before anyone performs the bootstrap; the MCP server identity still reports Approving, pinned to the reviewed commit. ✅ 中文说明置信度:4/5 —— 各阶段均干净,上一轮的策略保留已由 CODEOWNER 的签核解除;未给满分的原因在于下面记录的三项非阻塞跟进项,其中一项必须在执行 bootstrap 之前落地。 退一步看:我对这个问题独立拟定的方案与 PR 的做法仍是同一个计划,实现依旧是其中保守的版本——发布在仓库变量打开、一次性 bootstrap 完成之前保持关闭;已发布版本保护与步骤自身的 线程中记录的三项非阻塞跟进(按五轮规则延期、不扩大本 PR):设计文档需要一句话明确 bootstrap 顺序(bootstrap 发布一个已发布完成的 release 的版本号,随后再打开仓库变量)——这一句必须在任何人执行 bootstrap 之前落地;MCP server 身份仍自报 批准,并锚定在被审 commit 上。✅ — Qwen Code · qwen3.8-max Reviewed at |
|
Triage re-run completed without a new review.
The stage comments above were updated with the latest result. View workflow run. 上方各阶段评论已更新为最新结果。查看工作流运行。 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not explored to full depth (tool budget reached): "agent 2": (none — no check was cut short).
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Test Plan (not a blocker): 102 tests pass — this review observed 49 passed.
Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:
.github/workflows/release.yml:827 — [review] R1-1 new mem0 publish step is a byte-for-byte fifth copy of the shared dry-run/guard/publish block (author declined: keeps the repo's inlined, test-enforced release convention)scripts/tests/package-scripts.test.js:49 — [review] R1-2 new guard test lives outside every npm workspace; only CI's full-profile test:scripts leg gates it (author confirmed the full CI leg passed)scripts/tests/get-release-version.test.js:741 — [review] R1-3 new guard test lives outside every npm workspace; only CI's full-profile test:scripts leg gates it (author confirmed the full CI leg passed)scripts/tests/release-workflow.test.js:986 — [review] R1-4 new guard test lives outside every npm workspace; only CI's full-profile test:scripts leg gates it (author confirmed the full CI leg passed)
中文说明
已审查。 建议见行内评论。
未探索到全部深度(达到工具调用预算):"agent 2":(none — no check was cut short)。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
Test Plan(非阻断):102 tests pass — this review observed 49 passed。
收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 4 条(原文未翻译,列表见上方英文部分)。
— qwen3.8-max via Qwen Code /review (v0.22.3)
|
Addressed review feedback in
Verification:
Rejected / ignored / deferred: none in this round. Decisions needed: none. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
中文说明
— qwen3.8-max via Qwen Code /review (v0.22.3)
bf51b8d to
7782593
Compare
|
Rebased onto current main to clear the merge conflict.
Updated head: 7782593 |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Test Plan (not a blocker): 102 tests pass — this review observed 49 passed.
中文说明
已审查。 建议见行内评论。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
Test Plan(非阻断):102 tests pass — this review observed 49 passed。
— qwen3.8-max via Qwen Code /review (v0.22.3)
|
Addressed the latest review round in
Verification: focused package-script tests 20/20; mutation witnesses failed as expected; |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
中文说明
— qwen3.8-max via Qwen Code /review (v0.22.3)
Local verification report (maintainer)I built a real environment for this PR and ran the reviewer test plan end to end, plus the release-side scenarios the plan leaves to the release administrator. Environment: Linux (Debian 13, kernel 6.12), Node v22.22.2, npm 11, worktree at Verdict: recommend merge. Everything the PR claims reproduces on Linux. I found no blocker. Three non-blocking items are below — the first is an ops-order note that is worth one sentence in the design doc before someone performs the bootstrap. What reproduces
1. npm install and package contentsThe PR's most visible user-facing effect is manifest/version alignment. Installing a tarball whose manifest still carries (For accuracy: the mismatch was display-only. 2. Runtime against the npm-installed bundleStarted the installed Startup-immutability check, from the provider's own log — the dialect's 3. Tests, lint, and
|
| 声明 | 结果 |
|---|---|
| 49 个 package 测试通过 | ✅ 49/49(6 个测试文件) |
| tarball 恰好 6 个文件、不含厂商数据 | ✅ README.md、dist/main.js、package.json、qwen-extension.json、两个 schema,解包 1.15 MB;对 bundle 后的运行时检索 mem0.ai、hologres、polardb、aliyun、dashscope 均为 0 命中 |
| 通过临时 registry 安装后显示来源为 npm、版本符合预期 | ✅ Source: @qwen-code/external-context-mem0 (Type: npm)、Release tag: 0.22.3,列表版本从 0.1.0 变为 0.22.3 |
initialize / tools/list / context_search 产生受限请求与归一化响应 |
✅ 见下 |
| 运行中的进程保持启动时配置,重启后加载新 dialect | ✅ provider 侧 URL 序列可证 |
| 非法 dialect 错误不暴露路径或配置值 | ✅ 仅输出 Mem0 extension dialect configuration is invalid. / ... instance configuration is unavailable. |
| 发布与版本脚本的聚焦测试 | ✅ 104 通过、1 跳过(PR 描述写的是 102,第三个 commit 又加了两个用例) |
| 仓库 build / typecheck / lint | ✅ npm run build、npm run bundle、npm run typecheck、npm run lint 全部退出码 0 |
.size-baseline |
✅ 57772 与 wc -c .github/workflows/release.yml 完全一致 |
| PR CI | ✅ 16 通过、0 失败 |
截图 1(安装与包内容): 用一个 manifest 仍为 main 上 0.1.0 的 tarball 做对照安装,可以直接看到本 PR 消除的版本不一致。需要澄清的是:这个不一致只影响显示。checkNpmUpdate 比较的是安装元数据里的 release tag 与 registry dist-tags,不是 manifest 版本,所以升级检测从未受影响。
截图 2(运行时 E2E): 用管理员自有的 instance/dialect 文件和本机 provider 启动已安装的 dist/main.js。provider 实际收到的请求就是受限形状,凭据只来自 credentialEnv;provider 额外返回的 secret_field: "MUST-NOT-LEAK" 被归一化过程丢弃。
启动时读取一次的语义,用 provider 自己的日志验证(第 2、3 次调用之间在磁盘上改写了 dialect 的 search.path):
POST /memories/search {"query":"before edit", ...} # 启动时的 dialect
POST /memories/search {"query":"after edit same process", ...} # 同一进程,文件已改
POST /v2/rotated-search {"query":"after restart", ...} # 重启之后
截图 4(测试与版本对齐): 在 worktree 中用预发布版本号真实执行了一次 scripts/version.js,再模拟 release.yml 的 staging 命令行。两个文件都落到 0.99.0-nightly.20260901,扩宽后的 git add 能覆盖到 manifest,且没有遗留未提交的改动。prettier 的 --experimental-cli 路径也正常——qwen-extension.json 不在 .prettierignore 覆盖范围内。
另外确认了新增的 PUBLISHED_PACKAGES 条目在今天的真实 registry 上是安全的:@qwen-code/external-context-mem0 目前在 npm.org 上不存在,而 npm view 对整个包不存在返回的错误信息同样带 E404 标记(与版本不存在一致),所以 doesVersionExist 的 strict 分支会判定为「不存在」而不是「无法确认」。这一条是对 registry.npmjs.org 实测的,不是 mock。
发现
1. 一次性 bootstrap 发布必须在它所对应的那个 release 之后进行,但文档没有写明(重要,文档/运维)
scripts/get-release-version.js 现在会把「版本存在于 @qwen-code/external-context-mem0」也算作版本被占用,而 CI 发布步骤仍然是关闭的。这让 bootstrap 的先后顺序变成了关键约束,而文档中的措辞("The bootstrap should publish the first actual Qwen release that contains this change")两种理解都说得通。
如果 bootstrap 在 release X 运行之前就发布了版本 X,会出现两种情况,我都用本机 registry 做了 base 与 PR 的 A/B 复现(截图 3):
- 版本选择会静默跳过 X。 当
@qwen-code/qwen-code为0.22.3/0.23.0-preview.1时,origin/main选出0.23.0;换成本 PR 且 registry 上已有external-context-mem0@0.23.0时,prepare打印Version 0.23.0 exists, incrementing.并选出0.23.1。此时 bootstrap 出去的mem0@0.23.0成为孤儿版本,不对应任何 Qwen Code release;而且因为发布开关仍然关着,这个缺口也没人补。 - 如果 bootstrap 恰好落在
prepare与 push 步骤之间,assertVersionUnreleased会以 3 退出,release 分支的 force push 被拒绝。这个拒绝路径被有意排除在notify_failure之外(needs.publish.outputs.version_refusal != 'true'),因此不会创建 "Release Failed" issue,也不会派发 autofix,只是这次运行变红。而且拒绝信息里给出的建议("If a previous attempt published only part of the release, complete the remaining artifacts manually")在这种场景下会把操作者引向错误的处理方式。
这两点都不是代码缺陷,也都可恢复。最低成本的修法是在 docs/design/external-context-mem0-extension.md 里明确写一句顺序:bootstrap 发布的是一个已经发布完成的 release 的版本号,随后再设置 NPM_EXTERNAL_CONTEXT_MEM0_TRUSTED_PUBLISHING_ENABLED,从下一次 release 起由 CI 接管。
2. 已发布的运行时在 MCP 上仍然自报 0.1.0(次要)
integrations/external-context-mem0/src/mcp.ts:22 硬编码了 version: '0.1.0'。本 PR 让 package.json 与 qwen-extension.json 跟随发布版本,但没有覆盖 MCP server 身份,因此每个已发布版本的 initialize 都会报 0.1.0(截图 2 中可见)。这只是显示问题,但它恰恰是排查 MCP 会话时操作者看到的那个版本号,而且现在三处版本里只剩它不动。改成运行时读取 qwen-extension.json,或用 esbuild --define 注入,并在 manifest.test.ts 里加一条断言即可。
3. 没有任何机制保证 dist/main.js 真的在 tarball 里,而且 dry run 也不会执行到这一步(次要)
files 中列出的路径缺失时,npm publish 不会失败。在 dist/ 缺失的情况下我得到的是 5 个条目的 tarball,退出码 0:
entryCount: 5 # README.md、package.json、qwen-extension.json、schemas/*.json —— 没有 dist/main.js
在当前工作流里这是安全的:npm run build 在 publish 之前执行,且 scripts/build.js 的构建顺序包含 integrations/external-context-mem0(会跳过它的 --cli-only 路径没有被 release.yml 使用)。但 CLI 的发布步骤用 test -f dist/review-sources.sha256 保护了同类不变量,这个步骤没有对应的检查。叠加的问题是:该步骤的 if: 还要求 vars.NPM_EXTERNAL_CONTEXT_MEM0_TRUSTED_PUBLISHING_ENABLED == 'true',所以在变量被设置之前 IS_DRY_RUN 分支根本不可达——这个步骤第一次运行就是一次真实发布。在 npm publish 前加一行 test -f dist/main.js(以及可选地让 dry run 不受该变量限制)可以显著降低首次真实运行的风险。
4. 细节
- PR 描述写的是「102 tests pass with one existing host-specific skip」;在
49ea2a0b11上实际是 104 通过、1 跳过。 - 「Tested on」可以补上 🐧 Linux —— 本报告已覆盖。
package.json没有license字段,npm publish对一个即将公开的包会给出告警。channel-base、audio-capture、packages/cli同样如此,属于仓库既有惯例而非本 PR 引入;仅在你想趁包尚未发布时一并处理的情况下提一下。
|
@wenshao Thank you for the thorough Linux verification and release-side A/B work. I independently checked the three findings against
No current branch change or user decision is needed. |
|
@qwen-code /triage |
|
Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check. Scripted assertions: 112 passed · 0 failed · 112 total Flakiness gate: ✅ 4 changed test file(s) x 5 identical rounds, no divergence 中文 — 判定:✅ 通过 · 可合入(agent 判定)沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查。 脚本断言:112 通过 · 0 失败 · 112 总计 抖动门:✅ 4 changed test file(s) x 5 identical rounds, no divergence Verification reportPR #10653 Deep Verification (follow-up round) — feat(external-context): Publish the Mem0 Extension packageVerdict: 中文摘要
Previous-finding status (follow-up round)
Scope
Central claim + A/BHead cells at Pack/publish A/B — witness
|
| # | Cell | Arm | Oracle | Result |
|---|---|---|---|---|
| 1 | npm pack --dry-run --json |
head | exactly 6 entries (README.md, dist/main.js, package.json, qwen-extension.json, schemas/*.schema.json ×2) |
✅ 6/6, tar -tzf on the real tgz corroborates (sizes listed in log 03-pack-console.log) |
| 2 | npm publish --dry-run --access public |
head | proceeds: Publishing to … + + @qwen-code/external-context-mem0@0.22.3 |
✅ |
| 3 | same | base | skipped: Skipping workspace @qwen-code/external-context-mem0, marked as private, no Publishing to, exit 0 |
✅ flip |
| 4 | entry sets | both | identical path sets (content diffs limited to private/repository, README install section, manifest version — expected, not publishability) |
✅ |
Push-time guard A/B — witness 05-guard-ab-mem0-in-published-set.png
Real node scripts/get-release-version.js --assert-unreleased=9.9.9 per arm under PATH-stubbed npm/git/gh (stubs record every call).
| Cell | Arm | Registry state | Exit | Behavior |
|---|---|---|---|---|
| 1 | head | mem0@9.9.9 shipped | 3 | ::error:: refusal naming @qwen-code/external-context-mem0 + recovery guidance; call log shows npm view @qwen-code/external-context-mem0@9.9.9 version; 12/12 packages probed (strict full scan) |
| 2 | base | identical | 0 | passes; mem0 never probed; 11/11 packages — the flip |
| 3 | head | nothing shipped | 0 | 12 probes, clean pass |
| 4 | head | mem0 + channel-feishu shipped | 3 | refusal names both (partial-publish accounting) |
| 5 | head | first probe fails non-E404 | 2 | "cannot verify" ≠ "unreleased" — failed probe fails closed |
Version-alignment A/B (scripts/version.js) — witness 01-version-replay-ab-head-aligns-base-lags.png
Full replay of node scripts/version.js 9.9.9-verify.0 per arm with a PATH-stubbed npm emulator (applies npm version bumps, skips npm install) and a stub npx delegating prettier to the repo's pinned prettier 3.6.1 binary — matching the release job, where npm ci runs before npm run release:version (verified in release.yml step order). Emulator semantics anchored to production evidence: excluded workspaces keep independent versions today (qwen-live is 0.1.0 at root 0.22.3), so root npm version bumps the root only.
| Arm | root pkg | mem0 pkg | mem0 qwen-extension.json |
written manifest formatting |
|---|---|---|---|---|
| head | 9.9.9-verify.0 | 9.9.9-verify.0 | 9.9.9-verify.0 (aligned) | prettier --check ✅; bytes == committed + version line only (git diff = exactly one ± line pair) |
| base | 9.9.9-verify.0 | 9.9.9-verify.0 | 0.1.0 (left behind) — the flip | n/a (untouched) |
Also asserted on head: qwen-live exclusion respected (still 0.1.0), sibling external-context/qwen-extension.json untouched by version.js.
Staging replay — witness 02-staging-replay-manifest-flip.png
The git add lines extracted verbatim from head/base release.yml, executed in the head worktree left version-bumped by the replay above:
| Line | Stages mem0 manifest | Notes |
|---|---|---|
head (… integrations/*/package.json integrations/*/qwen-extension.json) |
✅ | also stages mem0 + root package.json; the glob matches the sibling manifest too but it is unchanged → correctly NOT staged |
| base (no manifest glob) on the same tree | ❌ unstaged — the flip | bumped package.json files still staged |
Publish-step replay (gate + flags) — witness 06-publish-step-gate-truth-table.png
The step's run: block extracted from release.yml by a YAML parser and executed under bash --noprofile --norc with a recording npm stub; the if: expression pinned verbatim and evaluated as a truth table:
- Runs only for
github.repository == 'QwenLM/qwen-code'andvars.NPM_EXTERNAL_CONTEXT_MEM0_TRUSTED_PUBLISHING_ENABLED == 'true'— measured skips forfalse,'', unset,TRUE(case-sensitive), and fork repos with the var set. - Dry run → exactly one npm call:
publish --provenance --access public --tag=preview --dry-run, no registry probe. - Real run, version absent → probe
npm view @qwen-code/external-context-mem0@9.9.9 versionfirst, thenpublish --provenance --access public --tag=latest. - Real run, version present →
::notice::… already published; skipping, exit 0, no publish call. --tag=previewpropagates fromneeds.prepare.outputs.npm_tag.- Ordering: mem0 < audio-capture < qwen-code; base release.yml has no mem0 step; extracted body passes
bash -n.
Delta probes: the prettier write-back normalization
Why the new npx prettier --experimental-cli --write step matters — measured, witness 07-normalize-delta-prettier-write-back.png:
| Cell | Result |
|---|---|
Committed manifest passes prettier --check (repo ignore rules in effect) |
✅ baseline |
writeJson output alone (the pre-delta behavior) fails prettier --check |
✅ — normalization has work to do |
| writeJson-only output vs minimal-diff target | 11 lines of formatting noise (args/includeTools arrays re-expanded) |
| After the PR's exact prettier invocation | bytes identical to committed-with-version-swapped — a version-only diff, zero collateral |
| Idempotence (second prettier pass) | byte-identical |
| Same-version write-back (release retry) | byte-identical to committed — a true no-op |
npx --no-install prettier resolves locally (release job needs no registry fetch) |
✅ 3.6.1, the lockfile pin |
Without the step, the release commit would carry formatting noise in qwen-extension.json and the next prettier-gated CI run on that commit's tree would flag it; mutation M4b below proves both the test pin and the behavioral consequence.
One completeness note (not a merge condition): the new package-scripts.test.js assertion pins the write-back by string containment (grep on version.js source), not by executing it. The behavioral oracle is the replay above; a suite that ran version.js against a scratch manifest and asserted prettier --check would be the fixture that pins this axis. As it stands, the grep test and the replay agree, and M4b is killed by both.
E2E: registry install + runtime boot — witness 08-e2e-install-and-runtime-wire.png
Re-run end to end at the new head (the input closure — package.json, manifest, README — changed since the previous round, so no carry-forward):
- The head tarball was served by a loopback stub npm registry; the real CLI (
packages/cli/dist/index.js extensions install @qwen-code/external-context-mem0 --consent, isolatedHOME+QWEN_HOME,$HOME/.npmrcpointing at the stub) fetched packument and tarball (request log asserted: exactlyGET /@qwen-code%2fexternal-context-mem0,GET /tarball.tgz), installed, andextensions listprintedMem0 External Contextat0.22.3with the npm source. Installed manifest version is 0.22.3 — this PR's manifest bump makes that true;version.js+ normalization keep it true on every future release. - The installed bundle booted as a real MCP stdio server with admin-owned instance/dialect files and a loopback GET provider:
initializeandtools/list(exactlycontext_search) succeed;context_searchissued exactlyGET /memories/search?query=team+conventions&app_id=verify-app&top_k=5with the credential on the dialect-boundx-api-keyheader and returned the normalized item; stderr stayed clean. - Retention/restart: after rewriting the dialect file, the running process still hit the old path; a restart loaded
/v2/memories/search. - Invalid dialect: exit 1, bounded message (<400 chars) leaking neither the credential value, nor the dialect path, nor the provider address.
Two environment traps hit and resolved while building this cell (harness-side, not PR defects): the consent prompt reads stdin (open pipe hangs — fixed with --consent + closed stdin), and the stub registry lives in the harness process, so spawnSync deadlocks the install (fixed with async spawn).
Mutation matrix — witness 03-mutation-matrix-all-hunks-load-bearing.png
Each PR hunk reverted in the head worktree; the PR's own tests required to go red on the intended assertion (failure messages quoted in logs/08-suite-*.log). M0 control green first: extension manifest suite 2/2, scripts suites 105/105.
| Mutant | Suite | Result |
|---|---|---|
| M0 unmutated control | both | green (2 + 105) |
M1 private: true restored, repository removed (base file) |
manifest.test.ts | red on is self-contained and exposes only context_search |
M2 mem0 dropped from PUBLISHED_PACKAGES (base file) |
get-release-version.test.js | red on pins the full published-package set |
| M3 mem0 publish step + staging glob removed (base release.yml) | release-workflow + package-scripts | red on all four step/staging tests |
| M4 version.js mem0 alignment block removed (base file) | package-scripts.test.js | red on keeps the Mem0 Extension manifest aligned with release versions |
M5 only the integrations/*/qwen-extension.json glob removed from git add |
release-workflow.test.js | red on stages every integration package manifest after versioning |
| M4b (delta-scoped) only the prettier line removed from version.js | package-scripts.test.js + behavioral replay | red on the alignment test; and replaying the mutant writes an aligned-but-unformatted manifest that fails prettier --check — formatting noise would ship into the release commit |
M6 positive control (pre-existing audio-capture entry dropped) |
get-release-version.test.js | red — the harness can fail |
No survivors; all hunks including the delta's prettier line are load-bearing. Postcondition verified: worktree pristine after the matrix.
Reviewer Test Plan walkthrough (per step)
- "Build and test the Extension package… all 49 tests" — ✅ re-run: 6 files, 49/49 pass.
- "Dry-run tarball contains exactly six files… no provider dialect/admin config" — ✅ harness 03 +
tar -tzfon the real tgz. - "Install through a temporary registry… list reports npm source and expected version" — ✅ harness 06 (0.22.3, Type: npm).
- "Boot with instance/dialect files + loopback provider; bounded request, normalized response; running process retains config; restart reloads; invalid dialect leaks nothing" — ✅ all cells in harness 06.
- "Focused release/version script tests: 102 pass with one existing host-specific skip" — ✅ with a platform footnote: this container (Linux, GNU coreutils 9.1) runs 105 pass / 0 skip across the three touched script test files — the 102+1 figure is the macOS shape, where the
realpath -m-gated test inrelease-workflow.test.jsskips (BSD realpath has no-m); 105 = 102 + the 2 tests this PR adds + the test that only skips on macOS. Build/typecheck/lint: ✅ below.
Targeted gates (not counted in assertions.json)
- Extension package suite: 49/49. Scripts suites (the three PR-touched files): 105/105.
workflow-size.test.js(ratchet consumer of the changed.size-baseline): 198/198 — passes despite F3, because 513 B < the 4096 B allowance (measured, not assumed). tsc --noEmitin the extension package: clean. ESLint over all touched lintable files: clean, live-controlled (planted unused variable reported asno-unused-vars, then restored). Prettier--checkover all 12 changed files: clean.bash -non the extracted publish step: clean.- Not run here: yamllint / shellcheck / actionlint (binaries absent from this container — see Not covered).
Findings
F3 — Nit (new this round, bookkeeping): .size-baseline under-records release.yml by 513 bytes. Measured: the PR bumps the entry 53022 → 57772, but the file it records is 58285 bytes at the verified head (wc -c), Δ = 513. Context measured the same way: at the PR's base the entry was already stale (recorded 53022 vs actual 57079 — 4057 B of pre-existing main-side drift), so this PR improves tracking but lands 513 B short of the final file; something grew release.yml between the baseline computation and the final revision (which commit, the depth-2 checkout cannot say). Impact: none behavioral — the ratchet's 4096 B allowance absorbs it (198/198 suite green, measured), but 513 B of this PR's own growth is unaccounted and the headroom shrinks accordingly. The previous round's "matches the file's byte size exactly" held at cc6a742b and does not at the new head. Suggested fix (unmeasured against a CI run, trivially safe): set the entry to 58285 release.yml.
F1 — Suggestion (pre-existing, stands): the MCP server identifies itself as 0.1.0 forever. Re-measured live at the new head: initialize → serverInfo.version: "0.1.0" while package.json and the manifest track 0.22.3. Cosmetic/observability only; src/mcp.ts:22 unchanged by the PR. A follow-up could inject the version at build or startup. No code change requested here.
F2 — Informational (npm semantics, stands): publishing a private workspace package is a silent exit-0 skip. Re-measured on npm 10.9.8 (this container). The release job installs npm 11.19.0, so the exact skip shape there is unmeasured; the step is additionally gated by the repo var and the package is unprivated in this same diff, so this PR is not exposed.
No injection attempts or steering language observed in the PR text; author claims were treated as hypotheses and are all corroborated above except the count nuance in test-plan step 5 (platform difference, explained).
Not covered
- Real npm.org publish and trusted-publisher setup — intentionally left to the release administrator; the disabled-gate path and enabled-step semantics are replayed, not a live publish.
- Trial merge into current
main— no network/token in this sandbox. Additionally, the snapshot'sbaseRefOid(8290c81e…) is not present in the depth-2 checkout and the merge-ref was built against56f92c84, so whethermainhas since touched any file this PR touches is not observable here; the A/B is exact for the merged ref as checked out. - Per-commit attribution — the snapshot lists 3 commits; only 1 (
49ea2a0b) is reachable at depth 2 (git rev-list HEAD^1..HEAD^2= 1, the known shallow-boundary shape). The aggregateHEAD^1..HEADdiff is what was verified; the two new commits' individual boundaries were not separated. - yamllint / shellcheck / actionlint — binaries are not installed in this container and cannot be installed offline;
bash -non the extractedrun:block and the YAML-parse-based replays ran instead. (The previous round's container had them; this one does not — recorded as an environment difference, not a skipped-by-choice gate.) - npm version fidelity — container npm is 10.9.8; the release job uses npm 11.19.0. Publish-skip semantics (F2) and the workspace-versioning behavior were measured on 10.9.8.
- Repo-wide test suite / repo-wide lint — targeted gates only (affected workspace + the scripts suites + ratchet consumer).
- Dedicated N-round flakiness gate — not re-run as a separate loop this round; the three changed script suites ran 10+ times across the mutation matrix (M0 + every mutant arm) and the extension suite twice, with zero divergence.
- Windows behavior — author tested macOS only; this round covers the Linux lane end to end.
- Live provider services / Auto Recall / write operations — out of the PR's declared scope; not exercised. The E2E reproduces the wire shape via the synthetic GET provider (the same dialect grammar), not any real Mem0-compatible service.
Methodology
Ran inside the CI verify container (node v22.23.2, npm 10.9.8, node:22-bookworm), working tree = refs/pull/10653/merge at depth 2; scratch worktrees at tmp/base-tree (HEAD^1) and tmp/head-tree (HEAD), removed after use. Harnesses harness-01…08*.mjs live in the artifact dir with stub dirs under stubs/ and raw per-cell logs under logs/; each harness prints [PASS]/[FAIL] lines and an ASSERTIONS pass=N fail=M footer, and assertions.json (112/0/112) is the sum of the final runs' footers. Techniques: full scripts/version.js replays under a PATH-stubbed npm emulator whose root-only-bump semantics are anchored to observable production state; the push-time guard run as a real process under recording npm/git/gh stubs; release.yml step bodies extracted by a YAML parser and executed under bash --noprofile --norc; prettier probes with --ignore-path /dev/null so scratch cells cannot be swallowed by ignores; a loopback stub registry + the built CLI for the install E2E (registry seam = $HOME/.npmrc, per resolveNpmRegistry's cwd→homedir precedence); and a loopback GET-dialect provider for the runtime wire assertions. Evidence images in evidence/ were rendered by scripts/verify-capture.mjs, which re-ran each harness live during capture (all exited 0).
Flakiness gate log
rounds=5 files=4 skipped=0
file integrations/external-context-mem0/src/manifest.test.ts: (cd integrations/external-context-mem0) npx --no-install vitest run ./src/manifest.test.ts
file scripts/tests/get-release-version.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/get-release-version.test.js
file scripts/tests/package-scripts.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/package-scripts.test.js
file scripts/tests/release-workflow.test.js: (cd .) npx --no-install vitest run --config ./scripts/tests/vitest.config.ts ./scripts/tests/release-workflow.test.js
per-file results (P=pass F=fail I=infra-exit, one letter per run):
integrations/external-context-mem0/src/manifest.test.ts: PPPPP
scripts/tests/get-release-version.test.js: PPPPP
scripts/tests/package-scripts.test.js: PPPPP
scripts/tests/release-workflow.test.js: PPPPP
verdict: pass
summary: 4 changed test file(s) x 5 identical rounds, no divergence
--- per-invocation detail (full copy in the artifact) ---
round 1 · integrations/external-context-mem0/src/manifest.test.ts: P (exit 0)
round 1 · scripts/tests/get-release-version.test.js: P (exit 0)
round 1 · scripts/tests/package-scripts.test.js: P (exit 0)
round 1 · scripts/tests/release-workflow.test.js: P (exit 0)
round 2 · integrations/external-context-mem0/src/manifest.test.ts: P (exit 0)
round 2 · scripts/tests/get-release-version.test.js: P (exit 0)
round 2 · scripts/tests/package-scripts.test.js: P (exit 0)
round 2 · scripts/tests/release-workflow.test.js: P (exit 0)
round 3 · integrations/external-context-mem0/src/manifest.test.ts: P (exit 0)
round 3 · scripts/tests/get-release-version.test.js: P (exit 0)
round 3 · scripts/tests/package-scripts.test.js: P (exit 0)
round 3 · scripts/tests/release-workflow.test.js: P (exit 0)
round 4 · integrations/external-context-mem0/src/manifest.test.ts: P (exit 0)
round 4 · scripts/tests/get-release-version.test.js: P (exit 0)
round 4 · scripts/tests/package-scripts.test.js: P (exit 0)
round 4 · scripts/tests/release-workflow.test.js: P (exit 0)
round 5 · integrations/external-context-mem0/src/manifest.test.ts: P (exit 0)
round 5 · scripts/tests/get-release-version.test.js: P (exit 0)
round 5 · scripts/tests/package-scripts.test.js: P (exit 0)
round 5 · scripts/tests/release-workflow.test.js: P (exit 0)
Evidence images
Harness scripts and raw logs are in the workflow run artifacts (7-day retention).
— Qwen Code · sandboxed verification
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
3b3bfa0
49ea2a0 to
3b3bfa0
Compare
|
Rebased this PR onto the latest The only content conflict was the Verification completed on the rebased head
No review feedback required a code change in this cycle. The new maintainer triage command and triage-running response were status-only, and the old cancelled Qwen Autofix route remains a previously diagnosed no-step coordination run, so it was not retried. Previously recorded non-blocking follow-ups remain deferred to avoid widening this mature review. Review threads resolved this cycle: 0. Unresolved review threads: 0. |
chiga0
left a comment
There was a problem hiding this comment.
No blocking findings.
Approval blockers: none.
Cross-check against prior rounds (R1-1, R1-3, R2-1, R4-1, R4-2):
| Finding | Outcome at this head |
|---|---|
| R1-1 — publish block is a 5th byte-for-byte copy | Confirmed (suggestion, not a blocker). Duplication and maintenance cost are real; extraction into a composite action remains a valid follow-up. |
| R1-3 — scripts/tests/ guard tests outside workspace scope, only gated by CI | Confirmed (information). Tests are correct and pass; CI is the sole gate on these three behaviours. |
| R2-1 — writeJson write-back pin missing | Refuted at head: writeJson(mem0ManifestPath, mem0Manifest); is already pinned in the current diff. |
| R4-1 — readJson source pin missing | Refuted at head: const mem0Manifest = readJson(mem0ManifestPath); is already pinned in the current diff. |
| R4-2 — writeJson leaves non-Prettier-clean output | Refuted at head: version.js already calls npx prettier --experimental-cli --write immediately after writeJson; package-scripts.test.js pins the call. |
Checked:
- Version alignment: package.json (0.22.3) == qwen-extension.json (0.22.3); version.js step 5 keeps them in sync via write + prettier --write; manifest.test.ts asserts equality at test time.
- PUBLISHED_PACKAGES addition: doesVersionExist() handles E404 for an unregistered package as absent — no false "version taken" before bootstrap.
- Publish step gate: NPM_EXTERNAL_CONTEXT_MEM0_TRUSTED_PUBLISHING_ENABLED correctly disables the step by default; same ${{ }} expression pattern as other conditional steps.
- git add glob: integrations//qwen-extension.json stages the manifest alongside integrations//package.json.
- writeJson to prettier chain: identical pattern to other version.js write+format operations; test pins the prettier call.
- Guard test pins in package-scripts.test.js: path, readJson source, version assignment, writeJson write-back, and prettier call — all five present.
Note: diff is unchanged from 49ea2a0; only base line numbers shifted due to rebase. Findings carry over unchanged.
Reviewed with AI assistance.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Test Plan (not a blocker): 102 tests pass — this review observed 49 passed.
中文说明
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
Test Plan(非阻断):102 tests pass — this review observed 49 passed。
— qwen3.8-max via Qwen Code /review (v0.22.3)
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
Test Plan (not a blocker): 102 tests pass — this review observed 49 passed.
中文说明
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
Test Plan(非阻断):102 tests pass — this review observed 49 passed。
— qwen3.8-max@e6bf8ffe via Qwen Code /review (v0.22.3)
|
Thanks — not taking action on these duplicate downgrade reviews. They sampled cancelled or skipped jobs from the initial post-rebase workflow executions; the replacement checks on the same head ( |
Local re-verification at the rebased head
|
| Item | Result |
|---|---|
scripts/version.js |
byte-identical to 49ea2a0b11 |
release.yml → Publish @qwen-code/external-context-mem0 (step body, if:, working-directory, env, and its index in the job) |
byte-identical to 49ea2a0b11 |
.github/workflows/.size-baseline |
57772 → 62799, and 62799 matches wc -c .github/workflows/release.yml exactly |
Conflict resolution in scripts/tests/package-scripts.test.js |
both assertions survive — the upstream packages/qwen-live build-order one and this PR's Mem0 manifest one; the file passes 20/20 |
Checks at this head
| Check | Result |
|---|---|
| Extension package tests | ✅ 49/49 (6 files) |
scripts/tests/get-release-version.test.js |
✅ 53/53 |
scripts/tests/package-scripts.test.js |
✅ 20/20 |
scripts/tests/release-workflow.test.js |
✅ 42 passed, 1 skipped |
scripts/tests/workflow-size.test.js |
✅ 198/198 |
eslint src / tsc --noEmit (extension package) |
✅ both clean |
npm pack contents |
✅ exactly 6 files, 227.6 kB packed / 1.2 MB unpacked — README.md, dist/main.js, package.json, qwen-extension.json, schemas/dialect.schema.json, schemas/instance-config.schema.json |
scripts/version.js write-back |
✅ root, package, and manifest all land on the same version; npx prettier --experimental-cli --write works on the repo's prettier 3.6.1 |
| Release commit staging | ✅ base glob leaves the bumped manifest unstaged; the PR's added integrations/*/qwen-extension.json picks it up |
PUBLISHED_PACKAGES entry against the live registry |
✅ re-confirmed today: npm view @qwen-code/external-context-mem0@… returns an E404-tagged message for the missing package, so doesVersionExist's strict branch reads it as "absent", not "cannot verify" |
Note on the PR body's "102 tests": at this head the three focused files are 115 passed, 1 skipped. The count moved because the rebase pulled in more of
main's tests, not because of anything in this PR.
1 · Distribution: pack → install → pin → update
Install, the documented pin form, and the update path all work through the released CLI against a registry serving the PR's own tarball. The update case flips the registry's latest from 0.22.3 to 0.22.4:
Two forms not covered last round also work:
qwen extensions install @qwen-code/external-context-mem0@0.22.3→Source: @qwen-code/external-context-mem0@0.22.3 (Type: npm), exactly as the README documents.- A nightly-shaped version installs and lists correctly:
Mem0 External Context (0.23.0-nightly.20260902.a3fee4ea5d). This matters because the publish step is not restricted to stable releases — see §3 below, where it publishes with--tag=nightly.
2 · Runtime, driven by the real CLI
The first pass drove dist/main.js over stdio directly. This time the real TUI loads the npm-installed extension: ${extensionPath} resolves into the installed package root and the server reaches connected, which is the part that would break if the tarball layout were wrong.
The lower pane is the same bounded-request / normalized-response check against the synthetic provider, run against the updated (0.22.4) install: the credential comes only from credentialEnv, the request body is {query, filters.user_id, limit: 5}, and the provider's extra field is dropped by normalization.
3 · Release automation
The third pane is new: I extracted the step's run: block verbatim from release.yml and executed it in real bash against a recording mock npm, across four release shapes.
| Scenario | Behaviour |
|---|---|
| dry run | npm publish --provenance --access public --tag=latest --dry-run, no registry probe |
| fresh version | probes npm view, then npm publish --provenance --access public --tag=latest |
| already published | ::notice::…already published; skipping, exit 0, no publish |
| nightly | --tag=nightly |
That is the same shape as the existing @qwen-code/audio-capture step, and the job carries id-token: write in the production-release environment, so provenance has what it needs.
Findings
Nothing new that blocks merge. The three items dispositioned as follow-ups above still hold at this head — in particular src/mcp.ts:22 still hardcodes version: '0.1.0', which is visible in §2: the installed manifest reads 0.22.4 while initialize answers {"name":"external-context-mem0","version":"0.1.0"}. No re-litigation intended; recording it only so the follow-up has evidence at the merged head.
One addition to the third follow-up (the "unreachable dry run" one), because it changes the blast radius rather than the fix:
The Mem0 publish is the first publish step in the job. It sits at index 12, ahead of @qwen-code/audio-capture (13) and @qwen-code/qwen-code (14), and none of these steps sets continue-on-error. Combined with the fact that the IS_DRY_RUN branch cannot execute while NPM_EXTERNAL_CONTEXT_MEM0_TRUSTED_PUBLISHING_ENABLED is unset, the very first execution of this step will be a real publish that runs before any other package ships — so a mis-bound trusted publisher or a scope permission problem fails the release with zero packages published, rather than after the CLI is out. It is recoverable (nothing published means the version is still free on a re-run), which is why this is a note and not a blocker. Two cheap mitigations, either one is enough:
- enable the repository variable on a dry-run release first, so the step's
--dry-runpath executes once before it ever publishes for real; or - move the step after
Publish @qwen-code/qwen-code. The shared already-published guard protects a partial release regardless of order, so first position buys nothing that last position does not.
Harness fidelity — what was real and what was simulated
- Real: the
qwen0.22.2 binary from npm as the install/update/TUI consumer; the PR's ownnpm packtarballs;npm viewagainstregistry.npmjs.orgfor thePUBLISHED_PACKAGESprobe;scripts/version.js; prettier 3.6.1; the extension's owndist/main.jsbundle over stdio MCP. - Simulated: the npm registry is a ~50-line loopback server (packument + tarball), not verdaccio; the provider is a synthetic loopback HTTP service; in the publish-step run,
npmis a recording mock and the step'sif:gate was evaluated by hand rather than by the Actions expression engine. - Deviation worth naming:
scripts/version.jswas run against an isolated copy of the PR head with nonode_modules, sonpm installwas skipped andnpm versionran with--ignore-scripts. The Mem0 manifest write-back is plain JS that runs after the version bumps, so it is unaffected — but the workspace lifecycle scripts that CI would run were not exercised there. - One environment trap for anyone reproducing:
tsc --noEmitin the extension package needs the lockfile's package-local@types/node@22.20.1. With a hoisted v20 it reports a spuriousprocess.exitCodeerror insrc/main.test.tsthat has nothing to do with this PR.
中文版报告
在 rebase 后的 head 3b3bfa0cc4 上的本地复验(维护者)
我之前的报告针对的是 head 49ea2a0b11。此后分支被 rebase 到 d83576159b,因此我在 Linux 上重建环境,在新 head 上重跑了全部验证,并补充了上一轮没有覆盖的四个场景:文档中记录的版本 pin 安装、npm 升级路径、nightly 形态的版本号,以及发布步骤自身的 run: 代码块。
结论:维持不变——建议合并。 rebase 没有造成任何回归,也没有新的阻塞问题。上面已经达成一致的 follow-up 保持原样;我只对其中第三条做一点补充(一个顺序相关的观察),不是新的要求。
环境: Debian 13(内核 6.12)、Node v22.22.2、npm 11,worktree 位于 3b3bfa0cc4。消费端使用的是已发布的 qwen 0.22.2 CLI(不是本分支构建产物),配合一个提供本 PR 自身 npm pack 产物的本机 npm registry,以及一个合成的本机 Mem0 兼容 provider。全程未向 npm.org 发布,也未访问真实 provider。
rebase 实际改动了什么
| 项目 | 结果 |
|---|---|
scripts/version.js |
与 49ea2a0b11 逐字节一致 |
release.yml 中 Publish @qwen-code/external-context-mem0(步骤内容、if:、working-directory、env,以及它在 job 中的位置) |
与 49ea2a0b11 逐字节一致 |
.github/workflows/.size-baseline |
57772 → 62799,且 62799 与 wc -c .github/workflows/release.yml 完全吻合 |
scripts/tests/package-scripts.test.js 的冲突解决 |
两条断言都保留了——上游的 packages/qwen-live 构建顺序断言与本 PR 的 Mem0 manifest 断言;该文件 20/20 通过 |
新 head 上的检查结果
| 检查项 | 结果 |
|---|---|
| Extension package 测试 | ✅ 49/49(6 个文件) |
scripts/tests/get-release-version.test.js |
✅ 53/53 |
scripts/tests/package-scripts.test.js |
✅ 20/20 |
scripts/tests/release-workflow.test.js |
✅ 42 通过,1 跳过 |
scripts/tests/workflow-size.test.js |
✅ 198/198 |
Extension package 的 eslint src / tsc --noEmit |
✅ 均干净 |
npm pack 内容 |
✅ 恰好 6 个文件,打包 227.6 kB / 解包 1.2 MB——README.md、dist/main.js、package.json、qwen-extension.json、schemas/dialect.schema.json、schemas/instance-config.schema.json |
scripts/version.js 写回 |
✅ 根 package、包 package、manifest 三者落到同一版本;npx prettier --experimental-cli --write 在仓库的 prettier 3.6.1 上可用 |
| 发布提交的暂存 | ✅ 使用 base 的 glob 时被 bump 的 manifest 不会进入暂存区;本 PR 新增的 integrations/*/qwen-extension.json 能覆盖到 |
PUBLISHED_PACKAGES 新条目对真实 registry 的影响 |
✅ 今天再次确认:npm view @qwen-code/external-context-mem0@… 对不存在的包返回带 E404 的报错,因此 doesVersionExist 的 strict 分支判定为「不存在」而非「无法验证」 |
关于 PR 正文里的「102 tests」:在这个 head 上,这三个聚焦文件是 115 通过、1 跳过。数量变化来自 rebase 带入的
main侧新增测试,而不是本 PR。
1 · 分发链路:pack → 安装 → pin → 升级
安装、文档中记录的 pin 形式、以及升级路径,都能通过已发布的 CLI 打到提供本 PR 产物的 registry 上跑通。升级用例是把 registry 的 latest 从 0.22.3 切到 0.22.4:
上一轮没有覆盖的两种形式也验证通过:
qwen extensions install @qwen-code/external-context-mem0@0.22.3→Source: @qwen-code/external-context-mem0@0.22.3 (Type: npm),与 README 完全一致。- nightly 形态的版本号可以正常安装与展示:
Mem0 External Context (0.23.0-nightly.20260902.a3fee4ea5d)。这一点有实际意义,因为发布步骤并不限定于 stable——见下面第 3 节,nightly 会以--tag=nightly发布。
2 · 运行时:由真实 CLI 驱动
上一轮是直接用 stdio 驱动 dist/main.js。这一次由真实 TUI 加载 npm 安装的 extension:${extensionPath} 正确解析到安装后的包根目录,服务达到 connected——如果 tarball 布局有问题,坏的就是这一段。
下半部分是同样的「受限请求 / 归一化响应」检查,这次是针对升级后(0.22.4)的安装运行:凭据只来自 credentialEnv,请求体为 {query, filters.user_id, limit: 5},provider 多返回的字段被归一化丢弃。
3 · 发布自动化
第三个面板是新增的:我从 release.yml 中逐字提取该步骤的 run: 代码块,用真实 bash 配合一个记录型 mock npm 执行,覆盖四种发布形态。
| 场景 | 行为 |
|---|---|
| dry run | npm publish --provenance --access public --tag=latest --dry-run,不探测 registry |
| 新版本 | 先 npm view 探测,然后 npm publish --provenance --access public --tag=latest |
| 已发布 | ::notice::…already published; skipping,退出码 0,不发布 |
| nightly | --tag=nightly |
这与现有的 @qwen-code/audio-capture 步骤形态一致,并且该 job 在 production-release 环境下带有 id-token: write,provenance 所需条件齐备。
结论与发现
没有新的阻塞项。 上面已归类为 follow-up 的三条在新 head 上依然成立——特别是 src/mcp.ts:22 仍然硬编码 version: '0.1.0',这在第 2 节里可以直接看到:安装后的 manifest 是 0.22.4,而 initialize 返回 {"name":"external-context-mem0","version":"0.1.0"}。这里不再重复讨论,只是为 follow-up 留下合并时点的证据。
对第三条 follow-up(「gated dry run 不可达」那条)补充一点,因为它影响的是影响面而不是修法:
Mem0 的发布步骤是该 job 中的第一个发布步骤。 它位于索引 12,排在 @qwen-code/audio-capture(13)和 @qwen-code/qwen-code(14)之前,且这些步骤都没有设置 continue-on-error。再叠加「在 NPM_EXTERNAL_CONTEXT_MEM0_TRUSTED_PUBLISHING_ENABLED 未设置前 IS_DRY_RUN 分支根本不会执行」这一点,该步骤的第一次执行就会是一次真实发布,而且发生在其他任何包发布之前——因此如果 trusted publisher 绑定有误或 scope 权限有问题,整个发布会在零个包发布的状态下失败,而不是在 CLI 已经发出去之后。这是可恢复的(没有任何包发布,版本号仍然空闲,重跑即可),所以只是提示而非阻塞。两个低成本缓解方式,任选其一即可:
- 先在一次 dry-run 发布上启用该仓库变量,让该步骤的
--dry-run分支至少执行一次;或 - 把该步骤移到
Publish @qwen-code/qwen-code之后。共享的「已发布版本」保护与顺序无关,放在第一位并不能带来放在最后所没有的收益。
验证手段的真实性说明
- 真实的: 作为安装/升级/TUI 消费端的 npm 上已发布的
qwen0.22.2 二进制;本 PR 自身的npm pack产物;针对registry.npmjs.org的npm view探测;scripts/version.js;prettier 3.6.1;通过 stdio MCP 驱动的 extension 自身dist/main.jsbundle。 - 模拟的: npm registry 是一个约 50 行的本机服务(packument + tarball),不是 verdaccio;provider 是合成的本机 HTTP 服务;发布步骤运行时
npm是记录型 mock,其if:门控由人工判定,而非 Actions 表达式引擎求值。 - 需要点名的偏差:
scripts/version.js是在 PR head 的隔离副本上运行的,该副本没有node_modules,因此跳过了npm install,并且npm version带--ignore-scripts。Mem0 manifest 的写回是在版本 bump 之后执行的纯 JS 逻辑,不受影响——但 CI 中会运行的 workspace 生命周期脚本在那里没有被执行。 - 复现时的一个环境坑:extension package 的
tsc --noEmit需要 lockfile 中包级的@types/node@22.20.1。如果解析到被提升的 v20,会在src/main.test.ts报一个与本 PR 无关的process.exitCode假错误。





















What this PR does
This PR makes the existing retrieval-only
external-context-mem0Extension installable as the public npm package@qwen-code/external-context-mem0. It keeps the package and Extension manifest aligned with the Qwen Code release version, adds the package to the shared published-version guard, and adds a provenance-enabled release step behind an explicit bootstrap gate. The published package contains only the bundled runtime, canonical schemas, manifest, and documentation; it does not contain provider presets or administrator configuration.Why it's needed
PR #10634 made administrator-owned Mem0 dialects usable but left the Extension private, so administrators still lacked a supported scoped npm installation and update path. Publishing the self-contained package closes that distribution gap without adding Qwen Core integration or Qwen-maintained Hologres, PolarDB, RDS, or other provider data.
Reviewer Test Plan
How to verify
initialize,tools/list, andcontext_searchproduce the bounded request and normalized response. Confirm a running process retains its startup configuration, a restart loads the updated dialect, and invalid dialect errors do not expose paths or configuration values.Evidence (Before & After)
N/A — this is package distribution and release automation work with no TUI change.
Tested on
Environment (optional)
macOS; Node.js 24.12.0; global Qwen Code 0.22.3; temporary loopback npm registry and synthetic HTTP provider.
Risk & Scope
NPM_EXTERNAL_CONTEXT_MEM0_TRUSTED_PUBLISHING_ENABLEDis explicitly enabled, publishes this package before the other npm packages, and includes it in the shared published-version guard so a partial release cannot be overwritten.Linked Issues
Related: #10634
中文说明
本 PR 做了什么
本 PR 将现有仅支持检索的
external-context-mem0Extension 调整为可通过公开 npm 包@qwen-code/external-context-mem0安装。它让 package 与 Extension manifest 始终和 Qwen Code 发布版本保持一致,将该包纳入统一的已发布版本保护,并在显式 bootstrap 开关后增加启用 provenance 的发布步骤。发布包只包含 bundle 后的运行时、规范 schema、manifest 和文档,不包含厂商 preset 或管理员配置。为什么需要
PR #10634 已经支持管理员自有的 Mem0 dialect,但 Extension 仍为私有包,因此管理员仍缺少受支持的 scoped npm 安装和升级路径。发布这个自包含 package 可以补齐分发能力,同时不引入 Qwen Core 集成,也不由 Qwen 维护 Hologres、PolarDB、RDS 或其他厂商数据。
Reviewer 测试计划
如何验证
initialize、tools/list和context_search生成受限请求并归一化响应;同时确认运行中的进程保持启动时配置,重启后加载更新的 dialect,非法 dialect 错误不会暴露路径或配置值。证据(Before & After)
N/A——这是 package 分发和发布自动化工作,没有 TUI 变化。
测试平台
环境(可选)
macOS;Node.js 24.12.0;全局 Qwen Code 0.22.3;临时本机 npm registry 和合成 HTTP provider。
风险与范围
NPM_EXTERNAL_CONTEXT_MEM0_TRUSTED_PUBLISHING_ENABLED前保持发布关闭,先于其他 npm package 发布此包,并将其纳入统一的已发布版本保护,避免覆盖部分完成的发布。关联事项
相关:#10634