Repository navigation
feat(memory): bundle Mem0 with the main CLI - #12891
Conversation
Co-authored-by: Qwen-Coder <[email protected]>
Verification report — controlled providersThe local implementation passed the checks below. The checked working-tree source is now committed as 46d1101f5f70. These results are from macOS, not a full CI matrix or live PolarDB acceptance.
Reproduction commandsnpm run build -- --cli-only
DEV=true npm run bundle
npm --prefix integrations/external-context run typecheck
npm --prefix packages/cli run typecheck
npm run typecheck:integration
# Run unit tests from their package directories:
cd integrations/external-context
npx vitest run src/config.test.ts src/providers.test.ts src/mcp.test.ts src/write-confirmation.test.ts
cd ../../packages/cli
npx vitest run src/config/mem0-settings.test.ts src/config/config.test.ts src/config/settings.test.ts src/config/hook-settings.test.ts src/ui/commands/hooksCommand.test.ts
cd ../..
# These installer fixtures temporarily replace dist: do not run them alongside bundling.
npx vitest run scripts/tests/package-assets.test.js
npx vitest run scripts/tests/install-script.test.js --testTimeout=15000
DEV=true npm run bundle
QWEN_SANDBOX=false npx vitest run --root ./integration-tests interactive/external-context-mem0-write.test.ts --retry=0Observed user-visible behaviorThe five existing Platform V3 scenarios still passed. The three OSS scenarios use user An independent stdio run selected default V2, sent Copying only the runtime and confirmation executable outside the repository (plus a module-type package marker) still passed initialize/list/search/Hook invocation without child Limits and initial failuresThe first Hook-command run exposed a missing No real model was used: the real CLI ran against a controlled OpenAI server. No current endpoint/credential pair was available for the real memory instance, so live authentication, slash behavior, limit handling, persistence, restart retrieval and targeted cleanup remain unverified. No real memory was read, changed or deleted. Windows/Linux native execution and the remote-aligned review gate were not run. CI state is separate from these local results. 中文验收说明本地 macOS 构建、打包、类型检查、改动文件 lint/格式检查通过。直连集成 128 个测试、CLI 相关文件 732 个测试、产物测试 46 个通过;安装/standalone 串行复验 131 个通过、11 个平台限定用例跳过;八个交互场景无重试通过。这里不是完整 CI 矩阵,也不是实际 PolarDB 验收。 真实 CLI 使用受控 OpenAI 模型与 HTTP 服务,保留五个 V3 用例,新增三个 OSS 自动配置用例。OSS 不手工配置 MCP 或 Hook:default 先确认普通 MCP 权限再确认精确内容,取消零写入,YOLO 仍确认内容。请求实测是 独立 stdio 验收实测默认 V2 的 首轮 Hook 测试 mock 缺 没有使用真实模型或联系真实记忆实例。缺少当前 endpoint/凭证,因此真实认证、斜杠、limit、持久化、重启搜索和定向清理尚未验证;未读取、写入或删除真实记忆。Windows/Linux 原生运行及 remote-aligned review gate 未运行,CI 结果与本地结果分开。 |
Co-authored-by: Qwen-Coder <[email protected]>
Verification update — settings-defined credentialsCommitted in 68fbb0a3d7bd. The checks below used the same source in the local working tree before commit; no product source changed afterward. The Mem0 settings path now follows model-provider configuration: Focused checks
The OSS run removes both the custom variable and default The first name filter selected zero tests because Vitest truncated generated parameterized names; that skipped run was not counted as verification. The corrected prefix filter executed the intended three cases successfully. Commands# CLI package
npx vitest run src/config/mem0-settings.test.ts
npm run build
# Worktree root
npm run typecheck:integration
DEV=true npm run bundle
# integration-tests directory, with isolated QWEN_HOME and controlled credentials
QWEN_SANDBOX=false QWEN_E2E_RENDERER=ink npx vitest run interactive/external-context-mem0-write.test.ts -t 'uses two confirmations for the OSS|does not write to the OSS|still asks for content confirmation f' --retry=0Actual tmux UI checkAn additional bounded real CLI session in tmux passed with the Ink renderer and a custom credential defined only in isolated user settings. The screen showed successful Actual terminal excerpts: The first tmux fixture incorrectly routed model responses by request index across two prompts; search rendered successfully but the fixture returned an unexpected continuation instead of proposing a write. That failed attempt was retained, not counted as a pass. Updating only the ignored controlled-model script to route from the actual search tool result allowed one bounded rerun to pass; no source or bundle change was required. These are local macOS results with the real bundled CLI and controlled model/memory HTTP services. The prior CI results belong to the original head, not to the new commit's CI. Live PolarDB authentication, 中文验收说明已提交为 68fbb0a3d7bd。以下验证在提交前针对同一工作树源码完成,之后没有修改产品源码。 Mem0 配置现在与 modelProviders 一致: 本地 CLI 构建(含 TypeScript)、最终 bundle、集成测试类型检查、改动文件 lint/格式及 diff 空白检查通过;Mem0 settings 19 个单测通过。真实 CLI 的三个 OSS 交互场景通过、五个未改动 V3 场景跳过,无失败、无重试。启动环境清除了默认和自定义 Mem0 变量,测试凭证仅存在隔离用户 settings.env,没有手工 MCP 或用户 Hook。批准后恰好写入一次,认证正确、保留原文且 infer:false;取消零请求;YOLO 仍确认具体内容。成功结果以 stored 和 ID 返回模型,最终完成标记实际出现在渲染 transcript 中,已保留真实 PTY 流和机器结果。 首次完整名称筛选因参数化测试名称截短而执行零个测试,这次 skipped 没有被算作成功;修正前缀后实际执行三个目标场景并通过。 另外补了一个实际 tmux/Ink CLI 会话:自定义凭证只来自隔离用户 settings。屏幕实际显示 context_search 成功和不可信结果 envelope,再出现普通 MCP 许可及精确内容确认,Esc 取消后回到输入框。受控服务只收到一次正确认证的 /search(top_k:5、固定用户 scope),/memories 写入为零,模型请求两次;保留了搜索、普通许可、内容确认及取消后的真实 tmux captures,上方是实际终端摘录。 首轮 tmux 脚本跨两个提示按请求序号路由模型响应,搜索可见成功,但 fixture 返回了错误的 continuation,没有发起预期写入。失败证据保留且没有算作成功;只调整 ignored 受控模型脚本,改为依据实际搜索工具结果路由,一次有界复验通过,源码和 bundle 未变化。 本次是本地 macOS 的真实打包 CLI 与受控模型/HTTP 服务,不是真实 PolarDB 验收。原有 CI 结果属于原始 head,不代表新提交 CI 已通过。真实认证、limit/斜杠行为、持久化和重启检索、Windows/Linux 原生 CLI 仍未验证。Ready for review 不代表批准、发布或真实服务验收通过。 |
Include upstream #12900 so Managed Agent migrations use distinct V16 and V17 versions. Preserve the bundled Mem0 implementation without feature changes. Co-authored-by: Qwen-Coder <[email protected]>
|
Merged main through #12900 in dd12698 to address the SDK Java failures. Both failed jobs in the old run reported Verification: the merged tree has 16 SQL migrations with 16 distinct versions; lifecycle now uses V17. One focused CLI configuration test pass exited 0: 20 passed, 482 filtered/skipped, no retries. Both affected Java jobs passed on #12900's exact head The branch was pushed normally without rewriting history or changing the Mem0 implementation. New SDK Java CI is pending; this is not a claim that the new PR head is fully green. 中文:已合入主分支 #12900 的修复并正常推送。原先两个 Java job 失败是上游两项变更都使用了 Flyway V16,并非 Mem0 改动引起;合并后生命周期迁移使用 V17,16 个 SQL 迁移版本无重复。一次聚焦配置验证通过 20 个用例,其余 482 个被过滤跳过,无重试。上游修复的对应 Java job 已通过,但本机没有 Java/Maven,没有执行本地 Java/MySQL 验证。当前 PR 新一轮 CI 仍待完成,未宣称全绿,也未重跑 UI 或真实 PolarDB 验收。 |
# Conflicts: # packages/cli/src/config/config.test.ts
|
Round 4 closeout: pushed c84059b and c9a801e to the existing PR branch. Operator Mem0 config is now selected as a whole, explicit null disables it, and a workspace MCP entry cannot hide an operator collision. The historical PolarDB preset keeps raw search content; only mem0-v2 unwraps verified direct-import text. Reserved config environment aliases and the Windows package-asset assertion are fixed. Six-preset documentation now includes linked complete English and Chinese designs. Verification: build, typecheck, focused ESLint/Prettier, 745 CLI config tests, 51 adapter tests, and 51 package-asset tests passed. Replacing the Windows-safe needle with win32.join made both new Windows regressions fail; restoring it passed all 51 tests. Native Windows and live Mem0 were not rerun in this round. The prior private-tunnel PolarDB acceptance report remains the live evidence: #12891 (comment) . CI for c9a801e is pending; no success is claimed for unfinished jobs. Scope: original goal remains opt-in Mem0 in the main CLI without a separate npm release. This fourth review round only addressed verified correctness/compatibility defects and their minimal regressions; no dependency, protocol family or generic MCP framework was added. Broader test matrices, Hologres/universal versions, automatic recall and generic MCP precedence redesign remain deferred. Other reviewer suggestions are not bulk-resolved. 中文:本轮已推送两条修复提交;解决配置跨层拼接与 null 禁用、工作区遮蔽操作者 MCP 冲突、旧预设返回值、Windows 环境变量别名和打包断言,并补齐双语预设设计。定向验证 847 项通过;Windows 原生和真实服务本轮未重跑,最新 CI 尚未完成。普通用户只需等待包含此 PR 的主 CLI 发版,不需要单独发布 Mem0 npm 包。 Disposition update: all 26 previously unresolved Suggestion threads received a current-head decision and are now resolved. Three were already covered by documentation, one Hologres request is outside this bounded protocol, and 22 test-only proposals are explicitly recorded for a later focused PR in the linked comment. This did not change code or increment the substantive round count. The old bot CHANGES_REQUESTED review still awaits re-review/dismissal; CI remains pending, not claimed green. Round 5 critical closeout: pushed 8bfd4aa. A temporary MCP allow-list filter can no longer delete Mem0's interactive write-confirmation hook during |
Ignore repository-scoped MCP conflicts, require the genuine bundled runtime for confirmation hooks, and use native artifact paths in packaging assertions. Improve field diagnostics and document existing precedence, scope, and verification limits. Co-authored-by: Qwen-Coder <[email protected]>
R1-1 (scripts/tests/package-assets.test.js): the two new multi-segment `missingArtifact` entries are compared against a message built with `path.join`, so `mem0/main.js` never matches the `\`-joined Windows path. Normalise the separator in the assertion; single-segment entries and POSIX behaviour are unchanged. R1-2 (packages/cli/src/config/config.ts): the mem0/external-context conflict guard read the merged all-scopes map, so a `external-context` entry that a trusted repository contributes through its own `.qwen/settings.json` (`scope: 'workspace'`) aborted `loadCliConfig` for every operator who configured `memory.mem0` — before `assembleMcpServers` applied the approval gate that would have held that server pending anyway. Limit the settings leg to operator scopes via `isGatedMcpScope`; a gated-scope entry is now overridden by the built-in binding (topTierMcpServers spreads last), exactly as a `.mcp.json` entry of the same name always has been, matching the design doc this PR adds. R1-28 (packages/cli/src/config/mem0-settings.ts): `bundledMem0Hooks` identified the bundled server by duck-typing an env-var name plus `includeTools`, so a repository-authored `external-context` entry reaching the `/hooks` reload path through `getMcpServers()` was filed under `systemHooks` — the one hook source folder trust does not gate — rendered as `[System]` configuration and pointed at a different script than the one the MCP approval dialog showed. Require identity instead: every file-sourced entry carries a provenance `scope`, the bundled one never does. Tests: workspace-scoped override + operator-scoped conflict still throws (config.test.ts), three scoped-entry rejection rows + an unscoped positive control (mem0-settings.test.ts), and a control/negative pair on the real reload path (hooksCommand.test.ts). Each fix was mutation-proved red. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmultfgjtdh
…opies R1-23: nothing in the repo observed `server.command` or `server.timeout`, and the `memory.mem0.timeoutMs` bounds are declared independently in mem0-settings.ts (zod, the runtime contract) and settingsSchema.ts (the settings-dialog copy). Replacing `command: process.execPath` with `'node'`, or dropping the `+ 5000` slack, left every suite green — a packaged or GUI-launched install routinely has no `node` on PATH, and the same value gates `bundledMem0Hooks` and is interpolated as the hook interpreter. Adds, in mem0-settings.test.ts: `command === process.execPath`, `config.timeoutMs === 5000` / `server.timeout === 10_000` for the default, an accept row at the documented ceiling asserting `35_000`, and reject rows for `0` / `30001` / `1.5`. Adds, in settingsSchema.test.ts, the bound test mirroring the existing `tools.webSearch.timeoutMs` precedent so the JSON-schema copy is pinned to the same documented numbers (docs/users/features/mem0.md: "defaults to 5000, between 1 and 30000"). Mutation-proved individually: `process.execPath` -> `'node'` red, `+ 5000` removed red, `maximum: 30000` -> `60000` red; intact tree green (27 + 61). Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-closeout/jmultfgjtdh
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
doudouOUC
left a comment
There was a problem hiding this comment.
Thanks — this is a carefully layered change and the containment story (operator-scope-only settings, workspace strip + memory: null restore, trust: false, read-only default, exact-content confirmation even in YOLO, no credential value serialized into the child config) holds up when traced through the code at the current head. The packaging gates on both the npm and standalone paths are also in place.
Nothing here rises above a suggestion, but a few items are worth a follow-up before or shortly after merge:
packages/cli/src/config/mem0-settings.ts:73— theenvKey === 'QWEN_BUNDLED_MEM0_CONFIG'guard is case-sensitive, whileisInternalSecretEnvVarright beside it is deliberately case-insensitive becauseprocess.envis case-insensitive on Windows. A lowercaseenvKey: 'qwen_bundled_mem0_config'would pass validation there and then collide with the injected config channel variable in the child environment. A case-insensitive comparison closes it.integrations/external-context/src/http-client.ts:27-33— the new control-character/DEL/backslash rejection applies to every provider, not only the bundled path, andhttp-client.test.tsis untouched. A backslash in an existing admin configuration used to be folded bynew URL()and now throws; the new branch deserves direct coverage.packages/cli/src/config/mem0-settings.ts:151-170— themem0RuntimePath()walk-up fallback and the "runtime is missing" throw are never exercised because the test mocksexistsSyncso the first check always hits. That throw is the message a source checkout sees whennpm run bundlewas skipped, so one test for it would be valuable.packages/cli/src/config/mem0-settings.ts:186-201— the Windows hook command shape (&+ PowerShell single-quote doubling +shell: 'powershell') reads correctly, but the macOS/Windows unit jobs are skipped in CI, so this branch has never executed. A small platform-mocked unit test would make it executed somewhere.packages/cli/src/config/config.ts:2410— the name-conflict error (and thememory.mem0 …validation errors) are plainErrors without thesettings.json:prefix that neighboring settings validation uses; the prefix is what tells the operator which file to fix.
Two non-code items for the record: with this merged there will be three Mem0 delivery paths in the tree, and the extension path's README currently documents an install command that 404s — that cleanup deserves a tracking issue. And since this moves Mem0 from admin-bound to operator-configured while #12596 still carries need-discussion, it would be good to record the direction decision explicitly in the issue rather than letting it be implied by the merge.
感谢——这个改动的分层做得很细,收敛设计(仅 operator 作用域设置、工作区剥离 + memory: null 恢复、trust: false、默认只读、YOLO 下仍要求精确内容确认、子进程配置不序列化凭证值)在当前 head 的代码里逐条核实都成立。npm 与 standalone 两侧的打包门槛也已就位。
没有超过建议级的问题,但有几处值得在合并前或合并后尽快跟进:
packages/cli/src/config/mem0-settings.ts:73——envKey === 'QWEN_BUNDLED_MEM0_CONFIG'是大小写敏感比较,而紧邻的isInternalSecretEnvVar明确按 Windows 上process.env大小写不敏感做了处理。小写的envKey: 'qwen_bundled_mem0_config'会绕过校验,随后在子进程环境块里与注入的配置通道变量冲突。改成大小写不敏感比较即可堵上。integrations/external-context/src/http-client.ts:27-33——新增的控制字符/DEL/反斜杠拒绝逻辑对所有 provider 生效,不只是内置路径,而http-client.test.ts没有改动。既有 admin 配置里的反斜杠过去会被new URL()折叠、现在会抛错,这个新分支值得直接补测试。packages/cli/src/config/mem0-settings.ts:151-170——mem0RuntimePath()的回溯查找和“运行时缺失”抛错从未被测试执行:测试 mock 了existsSync使第一次检查必中。而那个抛错正是源码 checkout 忘记npm run bundle时用户看到的信息,值得补一个用例。packages/cli/src/config/mem0-settings.ts:186-201——Windows hook 命令形态(&+ PowerShell 单引号双写转义 +shell: 'powershell')审读是正确的,但 CI 跳过 macOS/Windows 单测任务,该分支从未被执行过。一个 mock 平台的小单测就能让它至少在某处跑到。packages/cli/src/config/config.ts:2410——名称冲突报错(以及各memory.mem0 …校验错误)是不带settings.json:前缀的普通Error,而相邻的设置校验都有这个前缀;前缀正是告诉操作者该改哪个文件的信息。
另有两件非代码事项请记录在案:本 PR 合并后树里将同时存在三条 Mem0 交付路径,而扩展路径的 README 目前写着一个 404 的安装命令——这项清理值得单开跟踪 issue。另外,本 PR 把 Mem0 从 admin 绑定改为 operator 自行配置,而 #12596 仍挂 need-discussion,建议把方向决策显式记录在 issue 里,而不是让一次合并隐含地完成它。
Co-authored-by: Qwen-Coder <[email protected]>
|
Verified the bundled Mem0 path with the configured real
Focused verification: provider tests 51 passed, affected CLI Mem0 checks 38 passed, Hook command tests 19 passed, external-context typecheck passed, and the bundle rebuilt successfully. The original live harness exits are retained: one caught a missing model argument before approval; one assumed the raw provider response was plain text; the post-fix capture harness had a regex error, with a separate read-only evidence audit passing. This is observed real-provider acceptance, not a claim that every E2E automation run was green. Network boundary: the live instance was reached through the previously configured restricted SSH tunnel to its private endpoint. Public-direct access still timed out, so this does not claim public connectivity is fixed. Only one synthetic record was created in a dedicated test scope; it remains available for debugging. Existing memories were not changed. The ineffective temporary whitelist group was removed, existing whitelist groups were retained, and global user settings were not modified. No standalone Mem0 package publication was required; installed-package availability still depends on a main CLI release containing this PR. 中文验收摘要:已经用真实模型、真实 PolarDB 实例和实际 tmux CLI 验证 settings-only 连接、完整内容确认、一次批准写入、返回 ID、重启检索同一记录。发现并修复了 PolarDB direct import 返回消息数组 JSON 的兼容差异,修复后界面和模型收到的工具结果都是完整原文;保留给用户的直连模型调试窗口也已成功检索。51 个 provider 测试、38 个相关 CLI 检查、19 个 Hook 测试和相关 typecheck/bundle 通过。原始自动化失败证据未覆盖,不冒充全绿。当前可用链路是专用 SSH 私网隧道,公网直连问题仍未解决;没有发布独立 npm 包,也没有改全局 settings 或其他用户记忆。 |
Resolve two conflicts, both "each side appended its own entries to the
same list":
- scripts/prepare-package.js: verifyBundleArtifacts' requiredPaths now
carries this PR's dist/mem0/{main,write-confirmation}.js AND main's
dist/sandbox{BwrapRelay,LandlockRelay,FileWorker}.js. Both sets are
genuinely emitted (esbuild.config.js keeps the mem0 build plus the
three sandbox relay entry points), and the sibling
scripts/create-standalone-package.js auto-merged to the same union.
- scripts/tests/package-assets.test.js: the it.each artifact list is the
union of both sides (export-transcript-document.css deduplicated), and
the PR's separator-normalising assertion is kept because it is the more
general form -- it covers main's single-segment sandbox entries as well
as this PR's multi-segment mem0/* entries.
Verified: scripts/tests/package-assets.test.js passes 48/48 (1 skipped)
with all seven parametrised artifact variants green. The wider
test:scripts run shows 20 failures, but a clean origin/main baseline
with the same node_modules produces a byte-identical 20-failure set, so
none are attributable to this merge.
Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-conflict/jmum95axqe9
|
CI attribution for the red 37059 tests pass. The only failures are 7 cases in Evidence that these come from main:
This is a main-side breakage from #12545 that will red-flag every PR syncing main after 05:55Z, so it needs a fix on main rather than a workaround inside this PR. Not patching around it here. Note this PR is also over the patrol's +1500-line scope fuse at +1657, so this round made no code changes to it; the 4 findings verified real-and-unfixed in the previous round remain open with their evidence replies. Update — this is already tracked upstream: issue #12991 ( |
Picks up #12996 (e44e45e), which registers the Skill tool in the resume-listing fixture. This branch's merge-base is exactly d5a157c (#12545, "withhold the SkillManager from subagents whose tool policy has no Skill tool"), so it carries the behavior change without the fixture fix: 7 BackgroundAgentResumeService "launch-time skill listing" cases fail on `expect(initialMessages.includes('auto-skill-demo')).toBe(true)`. This branch does not touch packages/core/src/agents/background-agent-resume.*, so the merge takes main's version wholesale. Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
|
Review disposition at c9a801e: the five correctness/compatibility findings from the last round are fixed and their threads are resolved. I checked the remaining suggestions against the current head; none reports a new production failure. Already addressed in this PR: R1-35 (user/system/system-defaults are named in both designs), R1-11 (the user guide now distinguishes operator conflicts from workspace/project overrides and warns about extension shadowing), and R1-31 (both designs and the user guide explain worktree scope changes). These threads can be closed without more code. Outside this bounded contract: R1-8 would require a separately verified Hologres wire preset. The default mem0-v2 ID is explicitly PolarDB-style, not a promise of universal V2 compatibility. I am leaving Hologres protocol work outside this PR. Non-blocking regression coverage deferred to a focused follow-up PR after merge, not silently treated as implemented:
This is a late review round, so I am not expanding #12891 with another test matrix. The implementation guards remain present; build, typecheck and 847 focused tests passed on the pushed head. Native Windows remains unrun here, though both Windows-shaped artifact regressions were mutation-proven locally. The current CI snapshot has no failure, with core jobs still running; I will treat any PR-caused failure as actionable rather than calling pending checks green. |
|
@qwen-code /triage |
qqqys
left a comment
There was a problem hiding this comment.
Reviewed at head 8bfd4aa0b7777fd96108c2f669d1a19bdc2caff8.
Verdict: COMMENT — both historical Criticals are fixed, but my own diff-wide scan did not finish inside the review budget
Both blocking findings from the round-3 request are fixed at this head
I re-read the code rather than relying on the threads being marked resolved, since one of them had explicitly been left open and escalated two hours before that review.
R1-1 — the inverted Windows separator normalisation. Fixed, and now pinned on a platform-independent runner. The needle no longer goes through path.join: the new helper at scripts/tests/package-assets.test.js:34-40 normalises only the message and matches the raw artifact string —
function reportsMissingArtifact(message, missingArtifact, pathFlavor = path) {
const normalized = String(message).split(pathFlavor.sep).join('/');
return (
normalized.includes('Required package artifact not found') &&
normalized.includes(missingArtifact)
);
}The it.each rows at :718-719 are spelled mem0/main.js and mem0/write-confirmation.js, so the comparison is separator-free on every platform: on win32 the producer's backslash message normalises to / and matches, on posix nothing changes. The real assertion at :747 uses the platform default, and :752-758 adds an explicit win32 arm that builds the message with path.win32.join('D:\\publish\\dist', …) and asserts reportsMissingArtifact(message, missingArtifact, path.win32). That matters here specifically because Test (windows-latest, Node 22.x) is gated on merge_group || schedule || workflow_dispatch and reports skipping on this PR, so the lane that would have gone red never runs on the PR page — the injected pathFlavor makes the defect class observable on the Linux lane that does run. The producer in scripts/prepare-package.js is untouched, as the finding required.
R3-1 — the operatorMemory re-injection block's incoherent scope-selection and null semantics. Fixed; all three measured symptoms are gone at packages/cli/src/config/settings.ts:660-700.
- Field-wise merge across operator scopes is replaced by whole-value selection.
operatorMem0reduces[systemDefaults, user, system]and takes the entiremem0object of the highest-precedence scope that defines one, instead of deep-merging fields across scopes. So an enterprise system-scope binding can no longer be combined with a user-scopeenableWrites: true— the inversionWORKSPACE_TIGHTEN_ONLY_SETTINGSexists to prevent, one scope level up, is closed.mem0is then assigned explicitly (mem0: operatorMem0) rather than arriving through the merge. - The
undefined-only gate is gone. The reduce returnsnullboth forscope.memory === nulland for an explicitscope.memory.mem0 === null, andif (operatorMem0 === null) { delete merged.memory.mem0; }removes the key, so a{"memory":{"mem0":null}}— this codebase's own unset idiom — never reachescreateBundledMem0Server's.strict()schema and cannot escape as a bareErrorthat fails every launch. - The add-only
?? {}is gone for the same reason: an operator scope can now remove the binding, not just add or widen it.
The workspace-clobber concern that had been deferred alongside this block also does not hold at this head: merged.memory is rebuilt by spreading the operator-derived memory and then the existing merged.memory over it, so workspace-contributed memory keys survive and enableManagedAutoMemory / enableManagedAutoMemory-style defaults are not silently reset from {}. operatorExternalContext is likewise resolved from operator scopes only (system → user → systemDefaults), consistent with the R1-2 provenance fix rather than reading the merged map.
The gate I could not complete
Step 3 of this channel's process is an independent Critical-only scan of the complete diff, and this PR is 35 files at roughly +1657/−92. Inside the per-PR budget I read the two Critical sites above, the settings merge block, the Windows assertion and its new pin, and the full review history — I did not independently read the rest, so I cannot certify it. Specifically unexamined by me:
packages/cli/src/config/mem0-settings.ts(+238, new) — including the reject-guard at:207that a deferred item says can throw instead of returningundefined, half-applying a/hooksreload.packages/cli/src/config/config.ts(+47/−6) — the six-term activation gate and the new configuration aborts, which a deferred item says are bareErrors surfacing as a crash rather thanFatalConfigError(exit 52).integrations/external-context/src/bundled-mem0.ts(+33, new) — the shipped child-process entry point, which the round-1 review noted has no test at all.packages/cli/src/config/hook-settings.ts(+26/−6) andhooksCommand.ts— the deferred item about a filtered/hooksreload dropping the confirmation hook with nothing re-registering it when the server returns. The head commit is titled "Retain Mem0 safeguards across reloads", which suggests this was addressed, but I did not verify it.esbuild.config.js,scripts/create-standalone-package.js,scripts/prepare-package.js— the packaging and bundling legs.integrations/external-context/src/providers.tsandmem0-presets.ts— the direct-import unwrap guard and preset contract changes.
Eleven items were deferred as non-blocking in round 3, and round 4 at this head posted a single Suggestion. Under this channel's Critical-only policy none of the Suggestions gate the verdict, and I am not reporting any of them as findings. The reason this is a COMMENT rather than an APPROVE is only that my own scan is incomplete, not that I found a defect.
What would settle it: the unexamined list above is the whole gap. If a reviewer with budget reads those six areas and finds no provable Critical, this is approvable — the two things that were actually blocking are demonstrably fixed.
CI
At 8bfd4aa0: 15 checks succeeded, 85 skipped, none failed and none pending. Note that the skips include the Windows, macOS and CLI-integration lanes, so the green set is not evidence about them; the round-3 review disclosed the same gap. Nothing attributable to this PR. Per the review policy, pending checks were not waited on.
chiga0
left a comment
There was a problem hiding this comment.
Tier: Deep (trust boundary: credential handling, external service protocol, new MCP server lifecycle, new settings surface).
Scope: Reviewed all source (non-test, non-doc) changes: mem0-settings.ts, config.ts, hook-settings.ts, settings.ts, settingsSchema.ts, bundled-mem0.ts, providers.ts, mem0-presets.ts, mcp.ts, http-client.ts, config.ts, esbuild.config.js. Not reviewed: test bodies (mem0-settings.test.ts, config.test.ts, providers.test.ts, integration-tests), documentation files, VSCode schema, packaging scripts.
No blocking findings.
The credential design is correct: the JSON config written to QWEN_BUNDLED_MEM0_CONFIG carries only the env-var name (credentialEnv), not its value; the child process reads the credential from the inherited environment. The URL validation in createBundledMem0Server (control-character check, loopback exception, HTTPS enforcement) and the runtime validateProviderBaseUrl together provide defense-in-depth. The operatorMem0 re-injection in settings.ts correctly enforces operator scope precedence over workspace settings while preserving workspace control over non-mem0 fields. The bundledMem0Hooks identity check is layered — no-scope guard, env-var presence, command identity, and runtime path — and ties the hook to the exact bundled server built for this process.
Minor findings (non-blocking):
-
mem0-settings.ts:bundledMem0Hooks—server.scope !== undefinedis evaluated twice in the guard (both the first and a later disjunct). The effective check still fires on the first occurrence; the second is dead code. No correctness impact, but the duplicate weakens the human-readability of a security-sensitive check. -
mem0-settings.ts:createBundledMem0Server— the loopback carve-out (['localhost','127.0.0.1','[::1]'].includes(url.hostname)) allowshttp://localhostwithoutallowInsecureHttp: truepast settings validation, buthttp-client.ts:validateProviderBaseUrlhas no such carve-out and rejects it at runtime. A user who omitsallowInsecureHttpfor a local endpoint passes validation silently and gets a confusing startup failure rather than a settings-time error.
Cross-check — prior-reviewer Critical findings not independently confirmed:
-
R1-28 (
mem0-settings.ts:bundledMem0Hooks, qwen-code-ci-bot): marked "certifies-falsely" — concern about the identity check admitting a repository-authored lookalike server. My analysis: anyscope-stamped entry (workspace/project settings) is blocked by the first disjunct; an unstamped user-settings lookalike would need to know the exactmem0RuntimePath()output. This does not rise to a clear privilege-escalation vector in my reading; the duplicate scope check (my minor above) is the concrete weakness I found. -
R3-1 (
settings.ts:660, qwen-code-ci-bot): marked "certifies-falsely" about theoperatorMemoryre-injection. My analysis found no obviousincorrect ordering: workspace/projectmemoryfields spread afteroperatorMemorypreserve non-mem0 workspace preferences whilemem0: operatorMem0is enforced last. The full text of R3-1 was not visible to me in the cross-check output; I cannot confirm or refute the specific claim. -
R1-1 (
scripts/tests/package-assets.test.js:724, qwen-code-ci-bot): path-separator concern in the new packaging test. Not reviewed by this pass; flagged as an unreviewed test dimension.
Approval blockers: none from my analysis. R3-1 (settings.ts re-injection semantics) is an open cross-check item I could not fully evaluate; flag for the maintainer to re-examine against R3-1's full text before merge.
Reviewed with AI assistance.
What this PR does
This PR adds an opt-in Mem0 connection to the main Qwen Code CLI. Configure
memory.mem0with an endpoint andenvKey, and define the credential value in the top-level settingsenvfield, just like model providers. Qwen registers the MCP server automatically, supplies a stable user/repository scope, and exposes search without requiring a separately published extension package, a shell export, or a hand-authored MCP configuration. Shell exports and.envfiles remain supported credential sources.envKeydefaults toMEM0_API_KEY. The legacycredentialEnvalias remains supported; different names supplied together produce an explicit configuration error. The existing environment loader and source precedence are reused, and generated binding configuration contains only the variable reference. Credentials stored in settings JSON are plaintext and should stay in user settings, outside version control and shared reports.Search is read-only by default. Explicitly enabled writes in interactive CLI sessions automatically receive the existing exact-content confirmation Hook, including in YOLO mode. Noninteractive/ACP sessions and sessions with Hooks disabled retain search only. Workspace settings cannot enable or redirect the operator's connection.
The configuration selects a bounded protocol contract:
mem0-v2by default,mem0-v3, or the pinnedmem0-oss-2026-08contract. Existing preset IDs remain accepted. The new V2 contract sendslimit; the historical PolarDB preset keepstop_kuntil live verification establishes whether a migration is safe. This does not claim compatibility with every service called Mem0 V2.Why it's needed
The existing direct integration is private, while the separate extension delivery path requires additional setup. Neither provides the intended normal-install experience. This change reuses the existing adapters and confirmation behavior, ships the runtime with the main CLI, and removes a standalone npm publication from the critical path.
It also fills the missing OSS interactive write coverage through the new automatic configuration path.
Reviewer Test Plan
How to verify
memory.mem0.baseUrlandenvKey(defaultMEM0_API_KEY), and define that variable's value in the top-levelenvobject. Start the bundled CLI in a trusted local repository without exporting the Mem0 key. Theexternal-contextMCP server should appear without separate registration, and onlycontext_searchshould be available by default. LegacycredentialEnvstill works; conflicting aliases must produce an error.infer: falsewrite and render the provider result; cancellation should send no write. YOLO should still show exact-content confirmation.node_modulesorNODE_PATH.Evidence (Before & After)
Before: a normal installation required a separately configured integration, explicit MCP registration and a manually installed write-confirmation Hook.
After: the real interactive CLI originally passed eight controlled-model/provider scenarios without retries. The latest credential follow-up passed 19 focused unit tests and reverified the three OSS cases using a custom
envKeywhose value exists only in usersettings.env, with no inherited/default credential, manual MCP, or user Hook. Approval, cancellation with zero HTTP writes, and YOLO content confirmation passed without retries. The five unchanged V3 cases were not repeated for the credential-only follow-up. Independent SDK/process checks verified default read-only discovery, V2 requests and stable restart scope; a repository-independent copy of the two runtime files also passed. Separate PR comments record commands and observed results.An additional actual tmux/Ink session showed the retrieved memory on screen, followed by ordinary MCP permission and the exact-content confirmation. Esc returned to the input prompt; the controlled service received one authenticated search and zero writes. The verification comment includes terminal excerpts and the initial controlled-model fixture failure that was corrected without changing source.
Subsequent actual kimi-k3 + PolarDB-hosted Mem0 tmux acceptance observed one approved
infer: falsewrite (HTTP 200) and same-ID retrieval after restart (HTTP 200,limit: 5). The instance returns direct-import content as a serialized single-user-message array; the adapter now unwraps only the verified PolarDB-styleinfer: falseshape. A read-only real-service follow-up confirmed the exact two-line plain text in both the visible CLI and the model's tool response, with zero additional writes. The initial automation failures and successful read-only evidence audit are preserved in the live acceptance report, not relabeled green. Connectivity was through the existing restricted SSH/private-instance tunnel, not public-direct access. One acceptance record remains in its isolated scope; deletion was not tested. A final direct-settings session used the real model endpoint without an evidence relay, manual MCP, or user Hook.Tested on
Environment (optional)
Local Node.js runtime and built main CLI bundle; sandbox disabled for PTY testing. Earlier controlled-provider checks were followed by actual kimi-k3 and a real PolarDB-hosted Mem0 instance through the existing restricted SSH tunnel. Exactly one isolated acceptance record was written and retained; no pre-existing record was modified or deleted.
Risk & Scope
external-contextMCP server conflicts with the built-in setting; do not enable both. The built-in binding takes precedence over the same project.mcp.jsonname. Moving the repository or using another checkout changes the default scope; use explicit scope to retain existing memory. Asyncacceptedis not completed persistence, and ambiguous writes must not automatically retry.Design: English · 简体中文. Both versions cover the same behavior, limits and acceptance gates.
Linked Issues
Addresses #12596 (main-CLI delivery; live compatibility and full advanced-path consolidation remain open).
Fixes #9964.
中文说明
本 PR 的改动
此 PR 为 Qwen Code 主 CLI 增加主动启用的 Mem0 连接。通过
memory.mem0配置地址和envKey,并像 modelProviders 一样在顶层 settingsenv定义凭证值后,Qwen 自动注册 MCP 服务并提供稳定的用户/仓库 scope,无需单独发布扩展包、shell export 或手写 MCP 配置即可使用搜索。shell export 和.env仍是受支持的凭证来源。envKey默认值为MEM0_API_KEY。保留历史credentialEnv别名,两者同时指定不同变量名会明确报错。复用现有环境加载器和来源优先级,生成的绑定配置只含变量引用。JSON 中的凭证是明文,应保存在用户设置中,不提交到版本库或公开到报告。默认只读。在交互 CLI 中显式启用写入后,自动接入现有精确内容确认 Hook,YOLO 模式也会确认。非交互/ACP 和禁用 Hooks 的会话仅保留搜索。工作区设置不能启用或重定向操作者的连接。
配置选择有边界的完整协议合同:默认
mem0-v2,也可选择mem0-v3或固定的mem0-oss-2026-08合同。旧 preset ID 保留。新 V2 合同发送limit;历史 PolarDB preset 继续发送top_k,等待实测决定安全迁移方式。不承诺兼容所有标称 Mem0 V2 的服务。为什么需要
现有直连集成是 private,单独扩展的交付路径又需要额外设置,都不符合普通安装即可使用的目标。本改动复用已有适配器和确认行为,把运行文件随主 CLI 交付,不再把单独 npm 发布作为前置条件。
同时,通过新的自动配置路径补齐 OSS 交互写入覆盖。
Reviewer 验证计划
验证方式
memory.mem0.baseUrl和envKey(默认MEM0_API_KEY),在顶层env对象定义该变量的值;不 export Mem0 密钥,在可信本地仓库启动打包 CLI。应自动出现external-contextMCP 服务,默认仅提供context_search。旧credentialEnv仍可使用,冲突别名必须报错。infer: false写入并呈现结果;取消不能发送写入;YOLO 仍要确认原文。node_modules/NODE_PATH时,仍应能初始化并搜索。前后证据
此前:普通安装需要单独配置集成、手工注册 MCP 和安装写入确认 Hook。
之后:原始实现的真实交互 CLI 八个受控模型/服务场景无重试全部通过。最新凭证补齐通过 19 个定向单测,三个 OSS 场景复验使用自定义
envKey,其值只存在用户settings.env,没有继承/默认凭证、手工 MCP 或用户 Hook;批准、取消零 HTTP 写入和 YOLO 内容确认均无重试通过。此次凭证改动未重复运行五个未改动 V3 场景。独立 SDK/进程检查验证默认只读、V2 请求和重启 scope;仓库外双文件拷贝也通过。另附 PR 评论记录命令及实际结果。另外一个实际 tmux/Ink 会话在屏幕显示检索到的记忆,再出现普通 MCP 许可和精确内容确认。Esc 取消后回到输入框;受控服务只收到一次正确认证的搜索,零次写入。验收评论包含实际终端摘录及首轮受控模型 fixture 失败说明,该修正没有改动源码。
后续已使用真实 kimi-k3 与 PolarDB-hosted Mem0 完成实际 tmux 验收:批准后仅发送一次
infer: false写入,HTTP 200;重启后通过limit: 5搜索取回同一真实 ID,HTTP 200。实例把直接写入内容返回为单 user-message 数组的 JSON 字符串,适配器现只解包已验证的 PolarDB 风格infer: false形状;随后真实服务只读复验确认,CLI 可见结果与模型 tool response 都保留完整两行纯文本,额外写入为零。真实验收报告 保留首轮自动脚本失败与后续只读证据审核通过的区别,没有把失败重标为成功。网络经现有受限 SSH 私网隧道,不是公网直连;唯一验收记录仍保留,未测试删除。最终直接 settings 会话使用真实模型地址,无证据 relay、手工 MCP 或用户 Hook。本地测试平台
环境
本地 Node.js、构建后的主 CLI,PTY 测试关闭 sandbox。此前受控服务检查之后,使用真实 kimi-k3 与现有受限 SSH 隧道连接真实 PolarDB-hosted Mem0;仅新增并保留一条独立 scope 的验收记录,没有修改或删除既有记忆。
风险与范围
external-contextMCP 服务会冲突,不应同时开启;内置绑定优先于项目.mcp.json同名项。移动仓库或使用其他 checkout 会改变默认 scope,复用旧记忆需显式 scope。异步accepted不代表持久化完成,结果不确定的写入不可自动重试。设计:English · 简体中文。两个版本的行为、限制和验收门槛完整同步。
关联 Issue
关联 #12596(主 CLI 交付;真实兼容性和高级路径完整收敛仍待处理)。
修复 #9964。