Skip to content

feat(external-context): Load administrator-owned Mem0 dialects - #10634

Merged
doudouOUC merged 1 commit into
QwenLM:mainfrom
doudouOUC:feat/external-context-mem0-custom-dialects
Aug 31, 2026
Merged

feat(external-context): Load administrator-owned Mem0 dialects#10634
doudouOUC merged 1 commit into
QwenLM:mainfrom
doudouOUC:feat/external-context-mem0-custom-dialects

Conversation

@doudouOUC

Copy link
Copy Markdown
Collaborator

What this PR does

This PR changes the retrieval-only external-context-mem0 Extension to load an administrator-owned DialectV1 JSON file referenced by an absolute dialectPath in InstanceConfigV2. It removes the unused built-in preset registry while preserving the existing closed request grammar, bounded response normalization, External Context MCP Profile, and context_search tool surface.

The instance and dialect files are each limited to 64 KiB and read once at Extension startup. Endpoint, path, scope, and dialect semantics are validated before the credential named by credentialEnv is read. Errors remain fixed and redacted, and the package continues to contain no provider or customer dialect data.

Why it's needed

PR #10149 intentionally shipped an empty preset registry, so administrators could not connect a compatible service without a new Qwen release. Administrator-owned dialect files make the existing bounded runtime usable while keeping Qwen out of the provider-preset business and keeping credentials out of Qwen settings and JSON configuration files.

Reviewer Test Plan

How to verify

  1. Create an InstanceConfigV2 file outside the workspace that references an absolute DialectV1 file, set QWEN_EXTERNAL_CONTEXT_MEM0_CONFIG plus the named credential environment variable, and start the bundled Extension. It should initialize successfully, expose only context_search, and send the exact bounded request described by the dialect with the administrator-fixed scope.
  2. Modify the dialect while the Extension is running. The running process should retain its startup configuration; after restart, the new dialect should take effect.
  3. Try a v1 preset configuration, a relative or URL dialect path, missing or malformed files, a file over 64 KiB, an unsupported dialect field, inconsistent scope placement, or a missing credential. Startup should fail closed with a fixed redacted category that does not reveal paths, endpoints, credentials, queries, or upstream responses.
  4. Inspect the package dry-run output. It should contain only the bundle, schemas, manifest, README, and package metadata, with no provider-specific dialect or fixture.

Locally verified with 49 package tests, package and root typecheck/lint/build, a real bundled stdio MCP flow against a loopback synthetic HTTP service, restart-only reload assertions, redacted startup-failure assertions, formatting checks, and npm pack --dry-run.

Evidence (Before & After)

N/A — this changes non-UI Extension configuration and runtime behavior.

Tested on

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

Environment (optional)

macOS with Node.js 24.12.0 and npm 10.9.8, using the bundled stdio MCP server and a local loopback synthetic HTTP service.

Risk & Scope

  • Main risk or tradeoff: Administrators now own dialect correctness and file deployment, while Qwen accepts only the existing closed DialectV1 grammar and treats the files and process environment as an administrative trust boundary.
  • Not validated / out of scope: Provider-specific presets and fixtures, write operations, Auto Recall, retries, redirects, protocol probing, arbitrary templates, JSONPath, dynamic provider loading, Qwen Core changes, and ordinary Extension settings remain out of scope. Windows and Linux were not tested locally and are left to CI.
  • Breaking changes / migration notes: schemaVersion: 1 configurations using preset now fail closed. Administrators must migrate to schemaVersion: 2 and provide an absolute dialectPath; credentials remain in the environment variable named by credentialEnv.

Linked Issues

Related: #10113, #10149

中文说明

本 PR 做了什么

本 PR 将只读检索的 external-context-mem0 Extension 调整为加载管理员自有的 DialectV1 JSON 文件,该文件由 InstanceConfigV2 中的绝对路径 dialectPath 引用。同时删除未实际使用的内置 preset registry,并保持现有封闭请求语法、有界响应归一化、External Context MCP Profile 和 context_search 工具接口不变。

实例文件和 dialect 文件分别限制为 64 KiB,并且只在 Extension 启动时读取一次。系统先校验 endpoint、路径、scope 和 dialect 语义,之后才读取 credentialEnv 指定的凭证。错误仍使用固定脱敏分类,发布包继续不包含任何厂商或客户 dialect 数据。

为什么需要

PR #10149 有意发布了空 preset registry,因此管理员无法在不等待新的 Qwen 版本时接入兼容服务。管理员自有 dialect 文件让现有有界运行时真正可用,同时避免 Qwen 维护厂商 preset,并确保凭证不进入 Qwen settings 或 JSON 配置文件。

Reviewer 测试计划

如何验证

  1. 在工作区外创建一个 InstanceConfigV2 文件并通过绝对路径引用 DialectV1 文件,设置 QWEN_EXTERNAL_CONTEXT_MEM0_CONFIG 和指定的凭证环境变量,然后启动打包后的 Extension。它应成功初始化、只暴露 context_search,并按照 dialect 发送精确的有界请求,scope 固定由管理员配置。
  2. 在 Extension 运行期间修改 dialect。当前进程应继续使用启动时配置;重启后,新 dialect 应生效。
  3. 分别尝试 v1 preset 配置、相对路径或 URL dialect 路径、缺失或损坏文件、超过 64 KiB 的文件、不支持的 dialect 字段、不一致的 scope 位置或缺失凭证。启动应 fail closed,并只返回固定脱敏分类,不泄露路径、endpoint、凭证、query 或上游响应。
  4. 检查 package dry-run 输出。内容应只有 bundle、schemas、manifest、README 和 package 元数据,不包含任何厂商专用 dialect 或 fixture。

本地已验证 49 个包级测试、包级和根仓库 typecheck/lint/build、基于本地 loopback 合成 HTTP 服务的真实 bundle stdio MCP 流程、仅重启加载新配置的断言、启动失败脱敏断言、格式检查以及 npm pack --dry-run

证据(变更前后)

N/A — 本变更不涉及 UI,只调整 Extension 配置和运行时行为。

测试平台

OS 状态
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

环境(可选)

macOS,Node.js 24.12.0,npm 10.9.8;使用打包后的 stdio MCP server 和本地 loopback 合成 HTTP 服务。

风险与范围

  • 主要风险或取舍:管理员负责 dialect 正确性和文件部署;Qwen 只接受现有封闭 DialectV1 语法,并将这些文件和进程环境视为管理员信任边界。
  • 未验证 / 范围外:厂商专用 preset 和 fixture、写操作、Auto Recall、重试、重定向、协议探测、任意模板、JSONPath、动态 provider 加载、Qwen Core 改动和普通 Extension settings 均不在范围内。Windows 和 Linux 未在本地测试,交由 CI 验证。
  • 破坏性变更 / 迁移说明:使用 presetschemaVersion: 1 配置现在会 fail closed。管理员必须迁移到 schemaVersion: 2 并提供绝对 dialectPath;凭证继续仅存在于 credentialEnv 指定的环境变量中。

关联事项

相关:#10113#10149

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@doudouOUC

Copy link
Copy Markdown
Collaborator Author

E2E test report

Tested commit: 39aaf6ff311375d9bf0fb6e553e24a32e9fc9019

Environment: macOS, Node.js 24.12.0, npm 10.9.8.

Reproducible synthetic bundle test

  • Built the published stdio bundle, then created temporary administrator-owned instance and dialect JSON files outside the workspace plus a loopback synthetic HTTP service.
  • Completed MCP initialize → tools/list → context_search. The server exposed only context_search and sent the exact synthetic POST request, including the named synthetic credential, fixed filters.user_id, query, and limit: 5.
  • Rewrote the dialect path from /search-one to /search-two while the server was running. The current process continued using /search-one; after restart, the next request used /search-two.
  • Started the bundle with an invalid dialect containing secret-looking sentinel values. It exited with exactly Mem0 extension dialect configuration is invalid. and did not expose the values or file path.
  • npm pack --dry-run --json listed six package entries: README, bundle, package metadata, manifest, and the two schemas. No provider-specific dialect or fixture was packaged.
  • Temporary files and the local provider were removed after the run.

Read-only live compatibility smoke test

  • Separately validated an administrator-provided Hologres long-memory service with its observed compatible contract: POST /v2/memories/search/, token authentication, JSON query, fixed filters.user_id, and limit.
  • The direct read-only request returned HTTP 200 with an empty result set for an isolated test user. The bundled Extension then completed initialize → tools/list → context_search against the same service with isError: false, zero normalized results, the untrusted-context notice present, and no stderr output.
  • A temporary single-IP allowlist used for the smoke test was removed afterward, and the original private-network allowlist remained in place. No endpoint, IP address, credential, instance identifier, or provider dialect was added to this PR or retained in test artifacts.
中文说明

E2E 测试报告

测试提交:39aaf6ff311375d9bf0fb6e553e24a32e9fc9019

环境:macOS、Node.js 24.12.0、npm 10.9.8。

可重复的合成 bundle 测试

  • 构建发布用 stdio bundle,在工作区外创建临时管理员实例和 dialect JSON 文件,并启动本地 loopback 合成 HTTP 服务。
  • 完成 MCP initialize → tools/list → context_search。服务只暴露 context_search,并发送精确的合成 POST 请求,其中包含命名的合成凭证、固定 filters.user_id、query 和 limit: 5
  • 服务运行期间将 dialect 路径从 /search-one 改为 /search-two。当前进程继续使用 /search-one;重启后下一次请求使用 /search-two
  • 使用包含敏感信息样式哨兵值的非法 dialect 启动 bundle。进程只输出 Mem0 extension dialect configuration is invalid. 并退出,没有泄露这些值或文件路径。
  • npm pack --dry-run --json 仅列出六项:README、bundle、package 元数据、manifest 和两份 schema,没有打包厂商专用 dialect 或 fixture。
  • 运行结束后已删除临时文件并关闭本地 provider。

只读真实兼容性烟测

  • 另行使用实测兼容契约验证了管理员提供的 Hologres 长期记忆服务:POST /v2/memories/search/、Token 认证、JSON query、固定 filters.user_idlimit
  • 针对隔离测试用户的直接只读请求返回 HTTP 200 和空结果集;随后打包后的 Extension 对同一服务完成 initialize → tools/list → context_searchisError: false、规范化结果为 0 条、包含不可信上下文提示且 stderr 为空。
  • 烟测使用的临时单 IP 白名单已删除,原私网白名单保持不变。本 PR 和测试产物中没有加入或保留 endpoint、IP 地址、凭证、实例 ID 或厂商 dialect。

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finished — CI landed green on 39aaf6f and the deferred approval was posted. finalize run

Qwen Triage 已完成 —— 39aaf6f 的 CI 全绿,延迟审批已提交。查看 finalize 运行

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: this is a planned follow-up, not a speculative fix. PR #10149 intentionally shipped the extension with an empty preset registry, so it fails closed for every configuration and cannot connect any service until this step lands. The linked design (#10113) already anticipated the administrator-owned dialect path. The author's E2E report in this thread describes a real Hologres-compatible service they want to connect — a concrete need, not a theoretical one.

Direction: aligned. The merged design explicitly reserved the administrator-owned custom-preset path ("must use an absolute path, pass the same closed schema and semantic validation, contain no credentials"), and this PR implements exactly that with the DialectV1 grammar unchanged. One honest observation: the design rewrite retires the originally planned Qwen-maintained preset rollout (old PR2/PR3) entirely — Qwen will not ship verified provider presets. That direction is recorded in the doc itself and shrinks Qwen's surface (no provider data in the repo), but it is a product decision worth a maintainer's eyeball, not something this gate objects to. The extension route itself was already direction-escalated and approved when #10149 merged.

Size: not a core-module change — everything lives in integrations/external-context-mem0/ plus its design doc and README; no packages/core or cross-package surface is touched. Roughly 100 lines of production code, ~440 lines of tests, ~510 lines of documentation, plus minor schema/fixture updates.

Approach: the scope is right and the diff is minimal for the stated goal — the preset registry is swapped for an absolute dialectPath loaded through the same 64 KiB bounded reader, credentials are still read last, and error categories stay fixed and redacted (now distinguishing instance vs. dialect files). No drive-by changes. Tests were rewritten rather than deleted: the new cases pin relative/URL/${VAR} dialect paths, directory-as-file, per-file size bounds, v1/v3 rejection, extra-field rejection, and credential ordering.

Risk: no elevated risk signals — none of the changed files match the revert-correlated high-risk paths.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:这是计划内的后续步骤,不是投机性修复。#10149 有意发布了空 preset registry,扩展对所有配置都 fail closed,在本步骤落地前无法接入任何服务。关联设计(#10113)已预留了管理员自有 dialect 路径。作者在本帖的 E2E 报告描述了一个真实的 Hologres 兼容服务接入需求——是具体需求,不是理论问题。

方向:对齐。已合并的设计明确预留了管理员自有 custom-preset 路径("必须使用绝对路径、通过同样的封闭 schema 与语义校验、不包含凭证"),本 PR 正是按此实现,且 DialectV1 语法保持不变。一点诚实的观察:设计文档重写完全放弃了原计划由 Qwen 维护的 preset 发布(原 PR2/PR3)——Qwen 将不再发布经过验证的厂商 preset。该方向已记录在文档中,且缩小了 Qwen 的暴露面(仓库不含厂商数据),但这是一个值得维护者过目的产品决策,本关卡对此不持异议。Extension 路线本身在 #10149 合并时已经过方向升级并获得批准。

规模:非核心模块变更——全部改动位于 integrations/external-context-mem0/ 及其设计文档和 README;未触及 packages/core 或跨包接口。约 100 行生产代码、约 440 行测试、约 510 行文档,外加少量 schema/fixture 更新。

方案:范围合理,对所述目标而言 diff 已是最小——用绝对路径 dialectPath 替换 preset registry,复用同一 64 KiB 有界读取,凭证仍在最后读取,错误分类保持固定且脱敏(现在区分 instance 与 dialect 文件)。没有顺手改动。测试是重写而非删除:新用例钉住了相对/URL/${VAR} dialect 路径、目录当文件、逐文件大小上限、v1/v3 拒绝、额外字段拒绝和凭证读取顺序。

风险:无升级风险信号——改动文件均未命中与 revert 相关的高风险路径。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at 39aaf6ff311375d9bf0fb6e553e24a32e9fc9019 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Code review

Read the full diff against the merged skeleton. No blocking issues found — the change is exactly the swap it claims to be, and the load-bearing invariants survived it:

  • The preset registry lookup is replaced by an absolute dialectPath read through the same 64 KiB bounded reader (now parameterized by file kind), and the validation order is preserved: endpoint → static paths → dialect semantics → scope consistency → credential read last. The process still exposes no tool on any failure.
  • The DialectV1 grammar is untouched; the instance schema bumps to schemaVersion: 2 with additionalProperties: false, so v1 preset configs fail closed at schema validation. That breaking change is harmless in practice — the shipped registry was empty, so no v1 config could ever have worked.
  • Error categories stay fixed and redacted (now distinguishing instance vs. dialect files), and the new tests pin the security-relevant edges: relative / URL / ${VAR} dialect paths, a directory passed as a file, the exact 64 KiB boundary per file, v1 and v3 rejection, extra-field rejection, and credential ordering (missing credential rejects only after the dialect validates).
  • dialect.id is deliberately downgraded from registry key to administrator audit label — pinned by an explicit test. The manifest test asserts the package publishes only bundle, schemas, manifest, and README, which matches the actual files array in package.json.
  • No stale references: every InstanceConfigV1 / builtInPresets / "preset" occurrence in the repo lives in this package and is updated or deleted here. Nothing new is abstracted — the existing reader and validators are reused.

Two non-blocking notes:

Files changed (16 of 16 shown)
File What changed
docs/design/external-context-mem0-extension.md Design rewritten from built-in preset rollout to administrator-owned dialect files; records the pivot and the PR2 rollout step
integrations/external-context-mem0/README.md Administrator-facing configuration guide with instance and dialect examples
integrations/external-context-mem0/schemas/instance-config.schema.json v1 preset shape replaced by v2 dialectPath shape
integrations/external-context-mem0/src/config.ts Loads the dialect file by absolute path through the bounded reader; per-kind error categories
integrations/external-context-mem0/src/main.ts Drops the presets import; calls the loader with no registry
integrations/external-context-mem0/src/mcp.ts Type rename InstanceConfigV1 to InstanceConfigV2
integrations/external-context-mem0/src/presets.ts Deleted empty registry
integrations/external-context-mem0/src/schemas.ts Per-kind validation messages
integrations/external-context-mem0/src/types.ts InstanceConfigV2 with dialectPath
integrations/external-context-mem0/src/schemas.test.ts Bulk of the new fail-closed coverage listed above
integrations/external-context-mem0/src/main.test.ts Asserts the loader is called without a registry
integrations/external-context-mem0/src/manifest.test.ts Asserts the published package contents
integrations/external-context-mem0/src/mcp.test.ts Test config migrated to v2
integrations/external-context-mem0/src/request-engine.test.ts Type rename only
integrations/external-context-mem0/test/fixtures/synthetic-filtered-post-v1.json Fixture migrated to v2
integrations/external-context-mem0/test/fixtures/synthetic-query-get-v1.json Fixture migrated to v2

Testing

CI evidence from the PR's own runs on the reviewed commit, fetched via API — nothing was re-run locally:

Final CI results for 39aaf6f (auto-updated by the triage finalize job after CI completed):

Check Conclusion
Classify PR ✅ success
Dependency CVE audit ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
Integration Tests (no-AK, No Sandbox) ✅ success
Secret scan (TruffleHog) ✅ success
Test (ubuntu-latest, Node 22.x) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

  • The Linux unit suite (Test (ubuntu-latest, Node 22.x)) was still running at review time; per gate policy this table is not polled — the finalize job updates it once CI settles.
  • The macOS/Windows test lanes are skipped by standing repo CI policy (PR lanes disabled since 2026-07-02, nightly-only), not by anything in this PR. Consequence: the only platform CI exercises here is Linux. The author reports local macOS verification (49 package tests, a bundled stdio MCP flow against a loopback service, restart-only reload, redacted failures, npm pack --dry-run) — that is the author's claim in the thread, not independently re-run evidence.
  • The skipped Integration Tests (CLI, No Sandbox) and green Integration Tests (no-AK, No Sandbox) are distinct standing jobs; no red checks attributable to this PR.

Sandboxed verification would settle the remaining gap: @qwen-code /verify — the central behavioural claims (stdio startup with an administrator dialect file, restart-only reload, redacted fail-closed categories end to end) currently rest on the author's report and a unit suite that does not drive the real bundled server over stdio. The author has write access, so a maintainer (or the author) can trigger it directly against the reviewed head.

中文说明

代码审查

对照已合并的 skeleton 完整阅读了 diff,未发现阻塞问题——改动与声明完全一致,关键不变量都保住了:

  • preset registry 查找被绝对路径 dialectPath 取代,复用同一个 64 KiB 有界读取器(现在按文件类型参数化),校验顺序保持不变:endpoint → 静态路径 → dialect 语义 → scope 一致性 → 最后才读取凭证。任何一步失败进程都不会暴露工具。
  • DialectV1 语法未动;instance schema 升级为 schemaVersion: 2additionalProperties: false,v1 preset 配置在 schema 校验层即 fail closed。该破坏性变更实际无害——已发布的 registry 为空,v1 配置本就无法工作。
  • 错误分类保持固定且脱敏(现区分 instance 与 dialect 文件),新测试钉住了安全边界:相对/URL/${VAR} dialect 路径、目录当文件、逐文件 64 KiB 精确边界、v1 与 v3 拒绝、额外字段拒绝、凭证顺序(仅在 dialect 校验通过后才因缺失凭证而拒绝)。
  • dialect.id 有意从 registry 键降级为管理员审计标签,有专门测试钉住。manifest 测试断言发布包只含 bundle、schemas、manifest 和 README,与实际 package.json 的 files 一致。
  • 无遗留引用:仓库中所有 InstanceConfigV1 / builtInPresets / "preset" 均在本包内且已被更新或删除。未新增抽象——复用既有读取器与校验器。

两点非阻塞备注:

测试

来自该提交自身 CI 的证据,通过 API 获取——未在本地重新运行:

  • Linux 单元测试(Test (ubuntu-latest, Node 22.x))审查时仍在运行;按关卡策略不轮询,finalize 任务会在 CI 结束后更新上方表格。
  • macOS/Windows 测试通道因仓库既有 CI 策略被跳过(PR 通道自 2026-07-02 起关闭,仅 nightly 运行),与本 PR 无关。因此 CI 实际覆盖的平台只有 Linux。作者报告了本地 macOS 验证(49 个包级测试、对 loopback 服务的 bundle stdio MCP 流程、仅重启加载、脱敏失败、npm pack --dry-run)——这是作者在帖中的声明,不是独立复跑的证据。
  • 跳过的 Integration Tests (CLI, No Sandbox) 与绿色的 Integration Tests (no-AK, No Sandbox) 是两个不同的既有任务;没有可归因于本 PR 的红色检查。

沙盒验证可以补上剩余缺口:@qwen-code /verify——核心行为性声明(使用管理员 dialect 文件的 stdio 启动、仅重启加载、端到端脱敏 fail-closed 分类)目前依赖作者报告,且单测并未通过 stdio 驱动真实 bundle 服务。作者有写权限,维护者(或作者本人)可直接对审查的提交触发。

Qwen Code · qwen3.8-max

Reviewed at 39aaf6ff311375d9bf0fb6e553e24a32e9fc9019 · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean, minimal, well-tested step that completes the extension's stated rollout; approval waits only on CI landing green.

This PR does one thing and does it properly: it replaces the intentionally-empty preset registry with an administrator-owned dialect file, which is exactly the step the merged design reserved for making the extension usable without putting provider data in the Qwen repo. The approach matches what I would have proposed independently, and I did not find a simpler path it missed — the bounded reader, validation order, closed grammar, and redacted fail-closed behavior are all preserved rather than rebuilt, and the rewritten tests actually add coverage (URL/env-var dialect paths, per-file size bounds, credential ordering) instead of just being churned to compile.

The two reservations, both non-blocking: the design rewrite retires the planned Qwen-maintained preset rollout entirely, which is a real product decision — it is recorded in the doc and shrinks Qwen's surface, and the author is a repo maintainer driving their own merged design series, so I flagged it for visibility in Stage 1 rather than holding the PR on it; and the end-to-end runtime claims (stdio startup, restart-only reload, live Hologres smoke test) rest on the author's report since CI here can only exercise Linux unit lanes. The /verify line in my review comment names how to close that gap if a maintainer wants it before merge.

Every change in the diff is necessary for the stated goal; nothing unrelated rode along. If I'm maintaining this in six months, the config loader is still one small file with fixed error categories — I'd thank the author.

The Linux unit suite was still running at review time, so approval is deferred until CI lands green on 39aaf6ff311375d9bf0fb6e553e24a32e9fc9019 — the finalize job will post the commit-pinned approval once every check completes green, or flag this thread if anything lands red.

中文说明

置信度:4/5 —— 干净、最小化、测试充分的步骤,完成了该扩展既定的发布路线;仅待 CI 全绿后批准。

这个 PR 只做一件事,而且做得恰当:用管理员自有的 dialect 文件替换有意留空的 preset registry——这正是已合并设计预留的、让扩展在不向 Qwen 仓库引入厂商数据的前提下可用的那一步。方案与我独立设想的做法一致,也没有找到更简单的替代路径——有界读取器、校验顺序、封闭语法、脱敏 fail-closed 行为全部被保留而非重建,重写后的测试还新增了覆盖面(URL/环境变量 dialect 路径、逐文件大小边界、凭证顺序),而不只是为了编译通过的机械改动。

两点保留意见,均非阻塞:设计文档重写整体放弃了原计划由 Qwen 维护的 preset 发布路线,这是一个真实的产品决策——它已记录在文档中、缩小了 Qwen 的暴露面,且作者是推动自己已合并设计系列的仓库维护者,因此我在 Stage 1 中提示了可见性,而不以此卡住 PR;端到端运行时声明(stdio 启动、仅重启加载、Hologres 实测烟测)依赖作者报告,因为这里的 CI 只能覆盖 Linux 单元通道。审查评论中的 /verify 一行写明了维护者如想在合并前补齐该缺口该怎么做。

diff 中每一项改动都是目标所必需,没有无关内容夹带。若六个月后由我维护,配置加载器仍是一个带固定错误分类的小文件——我会感谢作者。

审查时 Linux 单元测试仍在运行,因此批准推迟到该提交的 CI 全绿之后——finalize 任务会在所有检查绿色完成后发布绑定该提交的批准;若有红色结果,会在本线程标记。

Qwen Code · qwen3.8-max

Reviewed at 39aaf6ff311375d9bf0fb6e553e24a32e9fc9019 · re-run with @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.

LGTM, looks ready to ship — CI landed green after the review. ✅

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

Reviewing the full diff at 39aaf6f against the merged skeleton: no blocking issues found.

  • The empty preset registry is cleanly replaced by the administrator-owned dialectPath flow: instance file → absolute-path check → bounded 64 KiB read of the dialect file → schema + semantic validation → credential read last. Fail-closed ordering is preserved.
  • Error categories stay fixed and leak-free (instance vs dialect, unavailable vs invalid); readConfigFile bounds apply to both files.
  • dialect.id correctly becomes an audit label with no lookup semantics, and the new tests pin the v1/v3 rejection, oversize bounds for both files, relative/${...}/URL dialect paths, and scope consistency.
  • No consumers of the removed presets.js remain outside the files this PR updates.

CI is green on this head (72 completed check-runs, all success/skipped; only the automatic review lane still running), matching the triage approval.

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

Reviewed. Suggestions are inline.

中文说明

已审查。 建议见行内评论。

— qwen3.8-max via Qwen Code /review (v0.22.3)

Comment on lines 190 to 192
throw new ConfigurationError(
'Mem0 extension configuration is unavailable.',
'Mem0 extension instance configuration is unavailable.',
);

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.

[Suggestion] R1-1: Credential failures are mislabeled as instance-configuration failures. The shared helper readRequiredEnvironment serves two call sites — the instance-config path read (config.ts:28, CONFIG_ENV) and the credential read (config.ts:51, instance.credentialEnv) — but this diff specialized its message to the "instance configuration" category, so a missing/blank/unresolved credential env var is now misreported as an unavailable instance configuration. Before this diff the message was generic ("configuration is unavailable") and covered both call sites, and the design doc's rewritten fixed-category taxonomy names instance, dialect, endpoint, path, and scope categories but no credential category.

An administrator deploys valid instance and dialect files, but the credential variable named by credentialEnv (e.g. MEMORY_API_KEY) is unset, blank, or left as the unresolved ${MEMORY_API_KEY} placeholder. Startup fails closed correctly, but stderr prints Mem0 extension instance configuration is unavailable. — the operator re-checks the instance file's existence and permissions, which are fine, instead of the missing credential. Because the design deliberately makes these fixed redacted categories the only diagnostic signal, the misdirection costs a full debugging round at deployment time.

Witness:

Probe (scratch tree; valid instance + dialect files; only the credential var unset):
unmodified PR: {"name":"ConfigurationError","message":"Mem0 extension instance configuration is unavailable."}
with fix:      {"name":"ConfigurationError","message":"Mem0 extension credential is unavailable."}   <- flips

Suggested fix — give readRequiredEnvironment a message/kind parameter mirroring readConfigFile(path, kind):

function readRequiredEnvironment(
  env: NodeJS.ProcessEnv,
  name: string,
  kind: 'instance configuration' | 'credential',
): string {
  const value = env[name];
  const trimmed = value?.trim();
  if (!value || !trimmed || trimmed === '${' + name + '}') {
    throw new ConfigurationError(`Mem0 extension ${kind} is unavailable.`);
  }
  return value;
}

Keep 'Mem0 extension instance configuration is unavailable.' (kind 'instance configuration') for the CONFIG_ENV call site and pass 'credential' at the credential call site; also add the credential category to the design doc's failure-category list. The new message must stay a fixed redacted category and must not embed the credentialEnv name or any path — docs/design/external-context-mem0-extension.md ("Startup and failure behavior") requires that messages never include the real path, endpoint, query, credential, or upstream response, and main.ts writes ConfigurationError.message verbatim to stderr. Please tighten the existing test 'rejects blank, unresolved, and missing credentials after validation' in schemas.test.ts to assert the credential-specific message — its loose .rejects.toThrow('configuration is unavailable') passes under both wordings today — and confirm the mutation: revert the credential call site to the instance-configuration message and the tightened test must go red.

中文说明

[Suggestion] R1-1:凭证缺失被误标为实例配置不可用。共享辅助函数 readRequiredEnvironment 有两个调用点——实例配置路径读取(config.ts:28,CONFIG_ENV)和凭证读取(config.ts:51,instance.credentialEnv)——但本 diff 把它的错误消息特化成了 "instance configuration" 类别,导致凭证变量缺失/为空/未解析时被误报为实例配置不可用。改动前消息是通用的("configuration is unavailable"),两个调用点都适用;设计文档重写后的固定错误类别列表列出了 instance、dialect、endpoint、path、scope 类别,却没有凭证类别。

当管理员部署了合法的实例文件和 dialect 文件,但 credentialEnv 指定的凭证变量(如 MEMORY_API_KEY)未设置、为空或仍是未解析的 ${MEMORY_API_KEY} 占位符时:启动会正确 fail closed,但 stderr 打印 Mem0 extension instance configuration is unavailable.——运维人员会去复查实例文件的存在性和权限(都没有问题),而不是缺失的凭证。由于设计上刻意让这些固定脱敏类别成为唯一的诊断信号,这种误导会在部署时浪费一整轮排查。

证据(探针):在临时树中用合法实例 + dialect 文件、仅取消设置凭证变量运行——未修复的 PR 输出 {"name":"ConfigurationError","message":"Mem0 extension instance configuration is unavailable."};应用修复后输出 {"name":"ConfigurationError","message":"Mem0 extension credential is unavailable."}——探针结果翻转。

建议修复:给 readRequiredEnvironment 增加 message/kind 参数(参照 readConfigFile(path, kind),见上方代码块),CONFIG_ENV 调用点保留 'Mem0 extension instance configuration is unavailable.'(kind 为 'instance configuration'),凭证调用点传入 'credential';同时在设计文档的错误类别列表中补充凭证类别。新消息必须是固定的脱敏类别,不得内嵌 credentialEnv 名称或任何路径——docs/design/external-context-mem0-extension.md("Startup and failure behavior")要求消息永远不包含真实路径、endpoint、query、凭证或上游响应,且 main.ts 会把 ConfigurationError.message 原样写到 stderr。请收紧 schemas.test.ts 中现有的 'rejects blank, unresolved, and missing credentials after validation' 测试:目前的宽松断言 .rejects.toThrow('configuration is unavailable') 两种措辞都能通过;改为断言凭证专用消息,并验证突变:当凭证调用点恢复为 instance-configuration 消息时该测试必须变红。

— qwen3.8-max via Qwen Code /review (v0.22.3)

Comment on lines 16 to 19
export function createMem0McpServer(runtime: {
instance: InstanceConfigV1;
instance: InstanceConfigV2;
search: SearchProvider;
}): McpServer {

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.

[Suggestion] R1-2: Runtime context_search failures have no observable category. The handler's catch (mcp.ts:58-60) collapses every provider failure into External context search failed. and writes nothing to stderr, while startup errors in main.ts do get categorized stderr lines. This item was explicitly deferred "to PR2" on the linked skeleton PR (10149) — author reply, 2026-08-27: "Deferring F1 to PR2. … should be designed with the live-provider observability work rather than widening this skeleton." This PR is the rollout's PR2 step per the Rollout section rewritten in this same diff, but ships without implementing or re-deferring the item — and the rewritten rollout deletes the live-provider preset steps the deferral pointed at.

This PR removes the empty-registry gate, so administrators now bind real services and runtime search failures become reachable for the first time. When context_search fails — expired credential (401), DNS failure, timeout, oversized or malformed response — the administrator sees only the generic result with an empty stderr and has no channel to identify the cause, diagnosing by blind trial and error.

Witness:

Probe (real MCP server over InMemoryTransport, rejecting SearchProvider):
unmodified PR: result [{"type":"text","text":"External context search failed."}], isError: true, stderr calls: []
with fix:      stderr calls ["Mem0 provider request failed.\n"], tool result byte-identical   <- flips

Suggested fix — write a fixed, redacted failure category to stderr in the catch before returning the redacted result:

      } catch {
        process.stderr.write('Provider request failed.\n');
        return errorResult('External context search failed.');
      }

Alternatively, explicitly re-defer the item in this PR's description with a named follow-up. The stderr category must stay a fixed label that never includes the real path, endpoint, query, credential, or upstream response (docs/design/external-context-mem0-extension.md, "Startup and failure behavior"). Please add a test in mcp.test.ts that spies on process.stderr.write, rejects the SearchProvider, calls context_search, and asserts the category line was written while the tool result stays exactly External context search failed. — removing the stderr write must turn it red.

中文说明

[Suggestion] R1-2:运行时 context_search 失败没有可观察的错误类别。处理函数的 catch(mcp.ts:58-60)把所有 provider 失败都折叠为 External context search failed.,且不向 stderr 写任何内容,而 main.ts 中的启动错误却有分类的 stderr 输出。该事项在关联的骨架 PR(10149)上被明确推迟到 "PR2"——作者 2026-08-27 回复:"Deferring F1 to PR2. … should be designed with the live-provider observability work rather than widening this skeleton."。按本 diff 重写的 Rollout 章节,本 PR 正是分阶段交付的 PR2 步骤,但既未实现也未重新推迟该事项——而且重写后的 rollout 删除了该推迟所指向的 live-provider preset 步骤。

本 PR 移除了空 registry 门槛,管理员现在可以绑定真实服务,运行时检索失败首次变得可达。当 context_search 失败时——凭证过期(401)、DNS 失败、超时、响应过大或格式非法——管理员只能看到通用结果和空的 stderr,没有任何渠道区分原因,只能盲目试错排查。

证据(探针):通过 InMemoryTransport 驱动真实 MCP server、令 SearchProvider 拒绝——未修复的 PR:结果 [{"type":"text","text":"External context search failed."}],isError: true,stderr 调用:[];应用修复后:stderr 调用 ["Mem0 provider request failed.\n"],工具结果逐字节一致——探针结果翻转。

建议修复:在 catch 中返回脱敏结果之前,把一个固定的脱敏失败类别写入 stderr(见上方代码块)。或者,在本 PR 描述中明确重新推迟该事项并指定后续跟进。stderr 类别必须是固定标签,永远不包含真实路径、endpoint、query、凭证或上游响应(docs/design/external-context-mem0-extension.md,"Startup and failure behavior")。请在 mcp.test.ts 中补充测试:spy process.stderr.write、令 SearchProvider 拒绝、调用 context_search,断言写入了类别行且工具结果保持为 External context search failed.——移除 stderr 写入后该测试必须变红。

— qwen3.8-max via Qwen Code /review (v0.22.3)

@doudouOUC
doudouOUC added this pull request to the merge queue Aug 31, 2026
Merged via the queue into QwenLM:main with commit 41cdc4f Aug 31, 2026
133 of 134 checks passed
@doudouOUC
doudouOUC deleted the feat/external-context-mem0-custom-dialects branch August 31, 2026 13:14
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