## 现象 有三个「默认开」的开关在代码里用 `=== true` 读取,而它们的默认值只写在 zod schema(`src/config.js`)里。走宿主的路径没问题——`src/index.js:202` 先经 `Config(config)` 解析,默认值已落进对象——但**绕过 `Config` 的裸配置调用方**(测试、第三方直接 `import { createSummarizer }` / 程序化建 service)拿到的是 `undefined`,于是「默认开」落不了地,等于默认关。 对比:`autoInject` / `autoDream` / `hybridInject` / `heatEnabled` / `summarizeBatchCrossTypeDedupe` 这批默认开的键,全仓统一用 `x !== false` 读取,裸配置下同样生效。 ## 涉及位置 - `dreamMergeGuard`:`src/dream.js:1440`、`src/dream/sleep.js:852` - `resilientModelDownload`:4 处读取 - `memoryQualityFilter.enabled`:`src/service.js:1553` `src/serve.js:63` 也是裸配置,但 daemon 不跑 summarizer / dream(无 LLM),暂不触发。 ## 建议 统一改成 `!== false`(与既有惯例一致),并补一条「裸配置下默认值仍生效」的测试;`src/config.js` 里的默认值保持不动。 ## 复现 ```js // 不走 Config,直接给裸对象 const svc = await createService({ /* 不写 dreamMergeGuard */ }); // 期望:合并护栏按默认开生效;实际:被当成 false ``` 由 PR #690 的评审顺带发现,与该 PR 无因果关系,单独处理。
Activity
modusensus commentedon Nov 13, 2026
两处读法已修(PR #695):
dreamMergeGuard(src/dream.js、src/dream/sleep.js各一处)与resilientModelDownload(装配层resilientDownloadEnabled(cfg),构造函数保留 #194 的显式 opt-in 契约)。裸配置调用方现在拿到的是「默认开」。第三处
memoryQualityFilter.enabled我先不动,想请你确认口径,因为它和前两处不是同一类:.default({})的对象里——memoryQualityFilter: z.object({ enabled: z.boolean().default(true), ... }).default({})。只要经Config(config),对象一定存在,过滤器一直是开的,所以宿主侧从来没有「默认开 = 默认关」的问题。=== true与!== false的差别,只出现在整个对象都不传的裸配置调用方,也就是程序化createService({ config: {} })这条路。"入门"、"喜欢猫"、"中文交流"),minContentLength默认 10,过滤器一开这些记忆在写入时就被归档,后续检索全空。逐个 fixture 显式关不是不行,但那是把 147 个测试文件的隐含前提写成明文,适合单独一个 sweep PR。我倾向保持现状 + 把这条写进文档(「程序化
createService要自己给memoryQualityFilter,宿主装配会自动带默认值」),而不是在真空气里翻默认值。如果决定要翻,我另开一个 PR 把 fixture 一起扫干净,不和这两处混。另外
resilientModelDownload那半边在构造层仍是=== true(故意的,见test/local-embedder.test.js:79),只有装配层按默认开——如果你希望连构造函数也默认开,那要先改 #194 定下的契约,请一起说。Merge pull request #695 from slow-stack/fix/692-default-true-read