feat(backthink): 静默时段心跳降频(A)—— THINK_QUIET_INTERVAL_SEC 默认 3600

主人裁定「先做 A(静默降频)再转向 C」;背景实测:静默期**思考频率不变**(恒定 900s 心跳),只拦打扰 ⇒ 成本照付。
- 新键 THINK_QUIET_INTERVAL_SEC(默认 3600):窗口内用该间隔、窗外仍用 THINK_INTERVAL_SEC;**≤0 = 关闭降频**(不许出现「半夜完全不思考」暗坑);窗口关闭/钟点非法 ⇒ 不降频
- 心跳**每拍按时刻选间隔**(放到循环外就永远不生效——变异实测能打红)
- 🔴 不变量:**事件驱动不被降频影响**(TestBackthink_EventTrigger_NotSlowedByQuietInterval 钉住)
- 事实卡 9→12 条(+思考总开关/普通间隔/静默间隔,标「改需重启」);契约形状未变、未升版
- ⚠️ 如实登记:ENABLE_BACKGROUND_THINKING 是**死配置**(全仓无消费者),已在 env/面板标签/契约三处标注,**是否接线待主人拍**
- 变异 5 条全红;C 的缺口清单(唤醒面 7 处)已回给主会话排期
This commit is contained in:
AskaEth committed 2026-10-05 22:38:17 +08:00
1 parent d6ed9cfa2b
commit 236b202fda
13 files changed
+633 -54

No files matched your search

@@ -14,10 +14,13 @@
import type { AdminConfigFacts } from "../../lib/types";
import {
BACKTHINK_HEARTBEAT_KEYS,
FACT_MISSING_VALUE,
MEMORY_TIDY_KEYS,
QUIET_HOURS_KEYS,
factRows,
heartbeatPurpose,
heartbeatRows,
memoryTidyRows,
quietHoursPurpose,
quietHoursRows,
@@ -52,6 +55,9 @@ const DEFAULTS: AdminConfigFacts = {
{ key: "MEMORY_TIDY_RECALL_THRESHOLD", value: "0.8", default: "0.8", isDefault: true },
{ key: "MEMORY_TIDY_SCAN_LIMIT", value: "5000", default: "5000", isDefault: true },
{ key: "MEMORY_TIDY_CANDIDATE_LIMIT", value: "20", default: "20", isDefault: true },
{ key: "ENABLE_BACKGROUND_THINKING", value: "true", default: "true", isDefault: true },
{ key: "THINK_INTERVAL_SEC", value: "900", default: "900", isDefault: true },
{ key: "THINK_QUIET_INTERVAL_SEC", value: "3600", default: "3600", isDefault: true },
{ key: "QUIET_START_HOUR", value: "23", default: "23", isDefault: true },
{ key: "QUIET_END_HOUR", value: "7", default: "7", isDefault: true },
],
@@ -133,5 +139,42 @@ const unknown = factRows(DEFAULTS, ["NOT_A_REAL_KEY"]);
same(unknown[0].label, "NOT_A_REAL_KEY", "没有元数据的键用键名兜底(不编中文标签)");
same(unknown[0].value, FACT_MISSING_VALUE, "没有元数据的键如实「未报告」");
// ── ⑥ 后台思考心跳:3 行 + 静默降频的用途文案(design/12 §N A4) ─────────────
same(
heartbeatRows(DEFAULTS).map((r) => r.key),
[...BACKTHINK_HEARTBEAT_KEYS],
"心跳 3 行,键序与后端事实表一致",
);
same(
heartbeatRows(DEFAULTS).find((r) => r.key === "THINK_QUIET_INTERVAL_SEC")?.value,
"3600 秒",
"降频间隔带单位显示,数值原样取后端",
);
same(
heartbeatRows(DEFAULTS).find((r) => r.key === "ENABLE_BACKGROUND_THINKING")?.value,
"true",
"总开关原样显示后端布尔值",
);
// ⚠️ 诚实边界:这个开关**未接线**(全仓无消费者,DevDocs/audit/07 §1-17)。
// 面板必须说清「配了不生效」,否则它会把一个死开关显示成生效开关。
ok(
has(
heartbeatRows(DEFAULTS).find((r) => r.key === "ENABLE_BACKGROUND_THINKING")?.label ?? "",
"未接线",
),
"未接线的总开关必须在标签里标明,不能显示成生效开关",
);
same(heartbeatRows(null).length, 3, "整份响应都没拿到时,3 行仍然都在(全为未报告)");
ok(
heartbeatRows(null).every((r) => r.state === FACT_MISSING_VALUE),
"没有响应时全部如实标「未报告」,不编 0 或默认值",
);
const hbPurpose = heartbeatPurpose();
ok(has(hbPurpose, "只把「例行自主思考」的心跳放慢"), "必须点明降频只作用于周期性心跳");
ok(has(hbPurpose, "事件驱动"), "必须点明事件驱动通路不受影响(安全域照旧立刻触发)");
ok(has(hbPurpose, "≤0"), "必须说明 ≤0 = 关闭降频");
ok(has(hbPurpose, "不是「半夜完全不思考」"), "不得把 ≤0 说成「半夜完全不思考」");
// eslint-disable-next-line no-console
console.log(`configFacts: ${checks} checks passed`);