@modusensus/dsh-mneme 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -10
- package/bin/cli.mjs +61 -7
- package/docs/CONFIGURATION.md +26 -2
- package/docs/WORKFLOWS.md +70 -3
- package/lib/api.js +176 -3
- package/lib/capabilities.js +1 -1
- package/lib/client.js +170 -16
- package/lib/config.js +37 -2
- package/lib/dream/sleep.js +35 -12
- package/lib/dream.js +86 -14
- package/lib/embedding.js +52 -22
- package/lib/index.js +15 -0
- package/lib/pipeline-nudge-auto.js +126 -0
- package/lib/pipeline-nudge.js +337 -28
- package/lib/pipeline-runs.js +1 -1
- package/lib/pipeline.js +4 -4
- package/lib/scope-state.js +277 -0
- package/lib/scope.js +30 -1
- package/lib/service.js +80 -3
- package/lib/settings.js +12 -1
- package/lib/store.js +139 -0
- package/lib/tools.js +71 -0
- package/package.json +1 -1
- package/src/api.js +176 -3
- package/src/capabilities.js +1 -1
- package/src/config.js +37 -2
- package/src/dream/sleep.js +35 -12
- package/src/dream.js +86 -14
- package/src/embedding.js +52 -22
- package/src/index.js +15 -0
- package/src/pipeline-nudge-auto.js +126 -0
- package/src/pipeline-nudge.js +337 -28
- package/src/pipeline-runs.js +1 -1
- package/src/pipeline.js +4 -4
- package/src/scope-state.js +277 -0
- package/src/scope.js +30 -1
- package/src/service.js +80 -3
- package/src/settings.js +12 -1
- package/src/store.js +139 -0
- package/src/tools.js +71 -0
- package/test/api-nudge-route.test.js +259 -0
- package/test/api.test.js +118 -1
- package/test/cli-flag-value.test.js +86 -0
- package/test/client.test.js +103 -4
- package/test/distill-tx-embed-gap.test.js +110 -0
- package/test/dream-maxtokens-fallback.test.js +132 -0
- package/test/embed-text-reasons.test.js +112 -0
- package/test/pipeline-nudge-auto.test.js +224 -0
- package/test/pipeline-nudge.test.js +161 -6
- package/test/readme-claims.test.js +68 -0
- package/test/scope-optional-service.test.js +110 -0
- package/test/scope-state.test.js +481 -0
- package/test/session-usage.test.js +11 -2
- package/test/tools-dto-schema-matrix.test.js +12 -3
- package/test/tools.test.js +6 -6
package/lib/client.js
CHANGED
|
@@ -495,6 +495,11 @@ window.__ModuleLoader__.load({
|
|
|
495
495
|
"memory.features.tagDefault.hint": "这一项用的是出厂默认值,没有被你改过",
|
|
496
496
|
"memory.features.tagChanged": "已改",
|
|
497
497
|
"memory.features.tagChanged.hint": "你改过这一项,它不再用默认值",
|
|
498
|
+
// 「待重启」是第三态,不是「已改」的加强版:#481 之前面板只有「已改」,
|
|
499
|
+
// 用户改完总闸看到徽标亮起就以为生效了,实际运行时还在用启动时的旧值。
|
|
500
|
+
"memory.features.tagPending": "待重启",
|
|
501
|
+
"memory.features.tagPending.hint": "这一项已存进配置,但本次启动还没用上——重启 DSH(或在插件管理里关开一次本插件)后才生效",
|
|
502
|
+
"memory.features.pendingRestartNotice": "有改动还没生效:标着「待重启」的开关要等重启 DSH 后才起作用(在插件管理里把本插件关开一次也行)。",
|
|
498
503
|
"memory.features.autoSummarize": "自动把对话记下来(自动注入之外)",
|
|
499
504
|
"memory.features.autoSummarize.hint": "开:对话结束自动提炼成长期记忆条目(autoSummarize);关:只有你手动存或 Agent 主动存才有记忆",
|
|
500
505
|
"memory.features.hotMemoryEnabled": "顺带带上最近几轮原话",
|
|
@@ -526,12 +531,12 @@ window.__ModuleLoader__.load({
|
|
|
526
531
|
"memory.features.heatEnabled.hint": "开:像遗忘曲线一样给记忆降温,长期没用过的往后排、不占注入名额;关:一直按相关度排(热度衰减,默认关)",
|
|
527
532
|
"memory.features.documentMemoryEnabled": "长文档只存一张摘要卡",
|
|
528
533
|
"memory.features.documentMemoryEnabled.hint": "开:Agent 写的长文档在记忆里只放「摘要卡」,正文仍由 Agent 保管、按需读取,注入只带摘要;关:整篇都往上下文里带(document 型记忆,默认关)",
|
|
529
|
-
// --- v0.9.0 M.O.(Modus Operandi
|
|
534
|
+
// --- v0.9.0 M.O.(Modus Operandi,beta)---
|
|
530
535
|
// M.O. = Modus Operandi,拉丁语「一个人一贯的做事方式」;也是 org 词根
|
|
531
536
|
// modus(modusensus)。界面主显示 M.O.,设置项 hint 第一句给全名。
|
|
532
537
|
"memory.actions.beta": "beta",
|
|
533
538
|
"memory.actions.notice": "提炼结果是候选,可能有错、过时、会被推翻。",
|
|
534
|
-
"memory.actions.title": "M.O. ·
|
|
539
|
+
"memory.actions.title": "M.O. · 工作流",
|
|
535
540
|
"memory.actions.open": "打开 M.O.",
|
|
536
541
|
"memory.actions.viewFull": "完整视图",
|
|
537
542
|
"memory.actions.openPane": "在右栏打开完整视图",
|
|
@@ -583,13 +588,24 @@ window.__ModuleLoader__.load({
|
|
|
583
588
|
"memory.actions.broken": "有文件读不出来:",
|
|
584
589
|
"memory.actions.empty.title": "这个项目里还没有工作流",
|
|
585
590
|
"memory.actions.empty.body": "工作流的本体住在项目里的 .mneme/pipelines/ 下——跟着仓库走,能被提交、能被 review、能被 CI 读。做法被复用第二次才算节点类型,第一次只是实例;同一路径被走通两次之后,它才会从会话里被提炼出来、落到那个目录里。",
|
|
591
|
+
"memory.actions.distill.action": "现在沉淀一次",
|
|
592
|
+
"memory.actions.distill.busy": "正在读会话…",
|
|
593
|
+
"memory.actions.distill.hint": "看一遍这个项目自己的会话日志,把反复走通的动作挖成提议。只写提议,不落草稿、不改仓库。",
|
|
594
|
+
"memory.actions.distill.noProject": "读不到会话的工作目录,所以不知道该看哪个项目的日志",
|
|
595
|
+
"memory.actions.distill.saved": "已提出 {n} 条提议(本次挖到 {m} 条候选)",
|
|
596
|
+
"memory.actions.distill.none": "这一遍没挖到新的重复动作",
|
|
597
|
+
"memory.actions.distill.noSessions": "这个项目还没有自己的会话日志,先攒几轮再来",
|
|
598
|
+
"memory.actions.distill.unreadable": "会话日志读不出来(这是环境问题,不是「没有重复动作」)",
|
|
599
|
+
"memory.actions.distill.failed": "这一遍没跑完,详见插件日志",
|
|
586
600
|
"memory.actions.noRuns": "这一条还没有运行记录,所以每个节点都还是候选。",
|
|
587
601
|
"memory.actions.settle.title": "本次运行",
|
|
588
602
|
"memory.actions.settle.line": "「{title}」",
|
|
589
603
|
"memory.actions.settle.used": "本次复用 {n} 条做法(勾选为被用上)",
|
|
590
604
|
"memory.actions.settle.refutedWarn": "本次路径里有已被推翻的节点,结果仅供参考",
|
|
591
605
|
"memory.features.actionPipelineEnabled": "M.O.(beta)",
|
|
592
|
-
"memory.features.actionPipelineEnabled.hint": "
|
|
606
|
+
"memory.features.actionPipelineEnabled.hint": "只读视图:把真实会话里反复走通的动作连成一张图谱——会话头图标、右栏完整视图、每次运行的结算卡。看 ≠ 写:沉淀由「M.O. 自动沉淀」单独开,执行记账由 /mo-run 单独开。Modus Operandi(工作流)。重启 DSH 后生效",
|
|
607
|
+
"memory.features.actionNudgeEnabled": "自动沉淀工作流(beta)",
|
|
608
|
+
"memory.features.actionNudgeEnabled.hint": "开:每轮结束后看一眼这个项目自己的会话日志,把反复走通的动作挖成记忆库里的一条「提议」,等你确认才可能变成工作流。只产草稿、不自动启用、不往仓库里塞文件。挂热路径且要解压日志,轻量档会强制关掉。重启 DSH 后生效",
|
|
593
609
|
"memory.features.resilientModelDownload": "模型下载断了能续上",
|
|
594
610
|
"memory.features.resilientModelDownload.hint": "开:下载中断后从断点续传并自动重试;关:中断就整份重来(断点续传,默认开)",
|
|
595
611
|
"memory.features.searchSemanticDedup": "搜到意思相同的只留一条",
|
|
@@ -676,6 +692,12 @@ window.__ModuleLoader__.load({
|
|
|
676
692
|
"memory.features.sleepProvider": "睡眠 Provider",
|
|
677
693
|
"memory.features.sleepModel": "睡眠模型",
|
|
678
694
|
"memory.features.sleepModelHint": "留空 = 用巩固模型或当前模型;建议选非思考模型",
|
|
695
|
+
// Issue #478:max_tokens 预算键。hint 写清两件事:这是「单轮输出预算」,
|
|
696
|
+
// 以及服务商硬上限更小时后端会按报错自动降级重试(对齐 #478 的后端行为)。
|
|
697
|
+
"memory.features.dreamMaxTokens": "巩固单轮输出上限 (max_tokens)",
|
|
698
|
+
"memory.features.dreamMaxTokens.hint": "单轮巩固最多生成多少 token,256–131072。默认 131072 超过部分服务商硬上限时,后端会按报错里的上限自动降级重试",
|
|
699
|
+
"memory.features.sleepMaxTokens": "睡眠单轮输出上限 (max_tokens)",
|
|
700
|
+
"memory.features.sleepMaxTokens.hint": "睡眠阶段冲突/模式判断的单轮输出预算,256–131072;超上限同样自动降级",
|
|
679
701
|
"memory.features.routeFollowDefault": "跟随默认路由",
|
|
680
702
|
"memory.features.modelTest": "测试连通性",
|
|
681
703
|
"memory.features.modelTesting": "测试中…",
|
|
@@ -1156,6 +1178,9 @@ window.__ModuleLoader__.load({
|
|
|
1156
1178
|
"memory.features.tagDefault.hint": "This one is at its factory default — you have not changed it",
|
|
1157
1179
|
"memory.features.tagChanged": "changed",
|
|
1158
1180
|
"memory.features.tagChanged.hint": "You changed this one; it no longer uses the default",
|
|
1181
|
+
"memory.features.tagPending": "restart pending",
|
|
1182
|
+
"memory.features.tagPending.hint": "Saved, but not yet in effect for this session — restart DSH (or toggle this plugin off and on in the plugin manager) to apply it",
|
|
1183
|
+
"memory.features.pendingRestartNotice": "Some changes are not in effect yet: switches marked \"restart pending\" apply only after you restart DSH (toggling this plugin off and on in the plugin manager works too).",
|
|
1159
1184
|
"memory.features.autoSummarize": "Turn conversations into memory automatically",
|
|
1160
1185
|
"memory.features.autoSummarize.hint": "On: memory entries are distilled when a conversation ends (autoSummarize). Off: memory only exists if you save it, or the agent decides to",
|
|
1161
1186
|
"memory.features.hotMemoryEnabled": "Carry the last few turns verbatim",
|
|
@@ -1240,13 +1265,24 @@ window.__ModuleLoader__.load({
|
|
|
1240
1265
|
"memory.actions.broken": "Some files could not be read:",
|
|
1241
1266
|
"memory.actions.empty.title": "No workflows in this project yet",
|
|
1242
1267
|
"memory.actions.empty.body": "A workflow lives in the project, under .mneme/pipelines/ — it travels with the repository: committable, reviewable, readable by CI. A practice becomes a node type on its second reuse; the first run is just an instance. Once the same path has been walked twice it gets distilled out of the session and lands in that directory.",
|
|
1268
|
+
"memory.actions.distill.action": "Distill now",
|
|
1269
|
+
"memory.actions.distill.busy": "Reading sessions…",
|
|
1270
|
+
"memory.actions.distill.hint": "Scan this project's own session logs and mine repeated actions into proposals. Writes proposals only — no drafts, no repository changes.",
|
|
1271
|
+
"memory.actions.distill.noProject": "The session's working directory is unknown, so there is no project to scan",
|
|
1272
|
+
"memory.actions.distill.saved": "{n} proposal(s) written ({m} candidate(s) mined this pass)",
|
|
1273
|
+
"memory.actions.distill.none": "No new repeated action this pass",
|
|
1274
|
+
"memory.actions.distill.noSessions": "This project has no session logs of its own yet — come back after a few turns",
|
|
1275
|
+
"memory.actions.distill.unreadable": "The session logs could not be read (an environment problem, not an empty result)",
|
|
1276
|
+
"memory.actions.distill.failed": "This pass did not finish; see the plugin log",
|
|
1243
1277
|
"memory.actions.noRuns": "No runs recorded for this workflow yet, so every step is still a candidate.",
|
|
1244
1278
|
"memory.actions.settle.title": "This run",
|
|
1245
1279
|
"memory.actions.settle.line": "\"{title}\"",
|
|
1246
1280
|
"memory.actions.settle.used": "Reused {n} steps this run (checked = actually used)",
|
|
1247
1281
|
"memory.actions.settle.refutedWarn": "This path contains refuted steps; take the result with caution",
|
|
1248
1282
|
"memory.features.actionPipelineEnabled": "M.O. (beta)",
|
|
1249
|
-
"memory.features.actionPipelineEnabled.hint": "
|
|
1283
|
+
"memory.features.actionPipelineEnabled.hint": "Read-only view: the ways of working that actually got walked through in real sessions, drawn as a graph — session-header icon, full sidebar view, and a per-run settlement card. Viewing is not writing: distillation has its own switch below, and run accounting has its own (/mo-run). Modus Operandi. Takes effect after restarting DSH",
|
|
1284
|
+
"memory.features.actionNudgeEnabled": "Auto-distill workflows (beta)",
|
|
1285
|
+
"memory.features.actionNudgeEnabled.hint": "On: after each turn, look at this project's own session logs and mine the actions it keeps repeating into a proposal in the memory store — it still needs your sign-off before becoming a workflow. Drafts only; never auto-activates; never writes files into the repo. Hooks a hot path and decompresses logs, so light mode forces it off. Takes effect after restarting DSH",
|
|
1250
1286
|
"memory.features.resilientModelDownload": "Model downloads resume after interruption",
|
|
1251
1287
|
"memory.features.resilientModelDownload.hint": "On: downloads continue from where they stopped and retry automatically. Off: an interrupted download starts over (resumable downloads, on by default)",
|
|
1252
1288
|
"memory.features.searchSemanticDedup": "Keep one entry when search hits near-duplicates",
|
|
@@ -1329,6 +1365,12 @@ window.__ModuleLoader__.load({
|
|
|
1329
1365
|
"memory.features.sleepProvider": "Sleep provider",
|
|
1330
1366
|
"memory.features.sleepModel": "Sleep model",
|
|
1331
1367
|
"memory.features.sleepModelHint": "Leave empty to reuse the consolidation model; a non-reasoning model is recommended",
|
|
1368
|
+
// Issue #478: max_tokens budget keys. Hint states both the scope (single-run
|
|
1369
|
+
// output budget) and the backend auto-downgrade on provider cap rejection.
|
|
1370
|
+
"memory.features.dreamMaxTokens": "Consolidation output cap (max_tokens)",
|
|
1371
|
+
"memory.features.dreamMaxTokens.hint": "Max tokens generated per consolidation run, 256–131072. When the default 131072 exceeds a provider's hard limit, the backend retries with the cap from the error",
|
|
1372
|
+
"memory.features.sleepMaxTokens": "Sleep output cap (max_tokens)",
|
|
1373
|
+
"memory.features.sleepMaxTokens.hint": "Single-run output budget for sleep conflict/mode calls, 256–131072; over-cap values are downgraded automatically",
|
|
1332
1374
|
"memory.features.routeFollowDefault": "Follow default route",
|
|
1333
1375
|
"memory.features.modelTest": "Test connectivity",
|
|
1334
1376
|
"memory.features.modelTesting": "Testing…",
|
|
@@ -1830,6 +1872,13 @@ window.__ModuleLoader__.load({
|
|
|
1830
1872
|
".mneme-set-del{flex:none;opacity:0;transition:opacity .12s;border:none;background:none;color:var(--mneme-fg-dim);cursor:pointer;font-size:14px;line-height:20px;padding:0 4px;border-radius:6px}",
|
|
1831
1873
|
".mneme-set-row:hover .mneme-set-del,.mneme-set-row:focus-within .mneme-set-del{opacity:1}",
|
|
1832
1874
|
".mneme-set-del:hover{color:var(--mneme-danger);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
1875
|
+
// 人格卡删除 × 独立类:不复用 mneme-set-del——其 opacity:0 仅在
|
|
1876
|
+
// .mneme-set-row:hover 解除,卡标签不在该容器里 → × 永远透明(v1.2
|
|
1877
|
+
// 实测踩中)。#457 曾用内联 opacity:1 覆写,但悬停变红仍走
|
|
1878
|
+
// .mneme-set-del:hover,连带常点亮了规则区的真删除按钮,被 #459 回滚。
|
|
1879
|
+
// 独立类常显,只沿用配色,规则区行为分毫不动。
|
|
1880
|
+
".mneme-persona-del{flex:none;opacity:1;border:none;background:none;color:var(--mneme-fg-dim);cursor:pointer;font-size:16px;line-height:18px;padding:0 3px;border-radius:6px}",
|
|
1881
|
+
".mneme-persona-del:hover{color:var(--mneme-danger);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
1833
1882
|
".mneme-set-cmd{flex:1;min-width:0}",
|
|
1834
1883
|
".mneme-set-cmdname{font-size:13px;font-weight:600;color:var(--mneme-accent)}",
|
|
1835
1884
|
".mneme-set-cmddesc{font-size:12px;line-height:17px;color:var(--mneme-fg-dim);margin-top:1px;word-break:break-word}",
|
|
@@ -1967,6 +2016,13 @@ window.__ModuleLoader__.load({
|
|
|
1967
2016
|
".mneme-conflict-primary:disabled{opacity:.55;cursor:default}",
|
|
1968
2017
|
".mneme-conflict-primary:hover:not(:disabled){background:color-mix(in srgb,var(--mneme-accent) 85%,var(--dsw-alias-label-primary))}",
|
|
1969
2018
|
".mneme-badge--frozen{color:var(--mneme-warn);background:color-mix(in srgb,var(--mneme-warn) 12%,transparent)}",
|
|
2019
|
+
// 「待重启」徽标:沿用 frozen 的 warn 语义(都是「现状不是你刚设的样子」),
|
|
2020
|
+
// 但加边框把它和只读冻结态区分开——它需要用户动手,不是终态。
|
|
2021
|
+
".mneme-badge--pending{color:var(--mneme-warn);background:color-mix(in srgb,var(--mneme-warn) 12%,transparent);border:1px solid color-mix(in srgb,var(--mneme-warn) 30%,transparent)}",
|
|
2022
|
+
".mneme-pending{display:flex;align-items:flex-start;gap:8px;margin:8px 0 0;padding:8px 10px;border-radius:8px;font-size:12.5px;line-height:18px;color:var(--mneme-warn);background:color-mix(in srgb,var(--mneme-warn) 10%,transparent);border:1px solid color-mix(in srgb,var(--mneme-warn) 28%,transparent)}",
|
|
2023
|
+
// 圆点用当前色(currentColor)而不是再引一次 --mneme-warn:色值只有一个真源,
|
|
2024
|
+
// 改主题时提示行与圆点不会走散。
|
|
2025
|
+
".mneme-pending-dot{flex:none;width:6px;height:6px;margin-top:6px;border-radius:50%;background:currentColor}",
|
|
1970
2026
|
".mneme-conflict-empty{border:1px dashed var(--dsw-alias-border-l2);border-radius:10px;padding:12px 14px;display:flex;flex-direction:column;gap:4px}",
|
|
1971
2027
|
".mneme-conflict-empty-title{font-size:12.5px;font-weight:600;color:var(--dsw-alias-label-secondary)}",
|
|
1972
2028
|
".mneme-conflict-empty-body{font-size:12px;line-height:18px;color:var(--mneme-fg-dim)}",
|
|
@@ -2317,6 +2373,17 @@ window.__ModuleLoader__.load({
|
|
|
2317
2373
|
".mneme-pipe-wfMeta{display:block;margin-top:3px;font-size:12px;color:var(--mneme-fg-dim)}",
|
|
2318
2374
|
".mneme-pipe-wfId{display:block;margin-top:2px;font-size:11px;color:var(--mneme-fg-dim)}",
|
|
2319
2375
|
".mneme-pipe-broken{flex:none;font-size:12px;line-height:17px;color:var(--mneme-warn);padding:6px 14px 0}",
|
|
2376
|
+
// 手动沉淀:按钮与回执占一行。回执用 --mneme-fg-dim 而不是 --mneme-warn——
|
|
2377
|
+
// 「这一遍没挖到」是正常结果,不是警告;只有「读不出来」才是故障,那也由文案说清,
|
|
2378
|
+
// 不靠颜色区分(红黄两色在深浅主题里最容易一起掉对比度)。
|
|
2379
|
+
".mneme-pipe-distill{flex:none;display:flex;align-items:center;gap:8px;flex-wrap:wrap;font-size:12px}",
|
|
2380
|
+
".mneme-pipe-distillBtn{border:1px solid var(--dsw-alias-border-l2);background:0 0;border-radius:8px;cursor:pointer;color:var(--mneme-fg-dim);font-family:inherit;font-size:12px;line-height:16px;padding:2px 9px}",
|
|
2381
|
+
".mneme-pipe-distillBtn:hover:enabled{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
2382
|
+
".mneme-pipe-distillBtn:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:1px}",
|
|
2383
|
+
// 禁用态用虚线边而不是降透明度:整块降 opacity 会把文字一起压到 AA 以下,
|
|
2384
|
+
// 而「为什么禁用」本来就由 title 说明(读不到会话工作目录)。
|
|
2385
|
+
".mneme-pipe-distillBtn:disabled{cursor:not-allowed;border-style:dashed;color:var(--mneme-fg-dim)}",
|
|
2386
|
+
".mneme-pipe-distillNote{color:var(--mneme-fg-dim);line-height:17px;min-width:0}",
|
|
2320
2387
|
].join("\n");
|
|
2321
2388
|
if (typeof document !== "undefined" && document.querySelector(`style[data-plugin-css="${CSS_TAG}"]`) === null) {
|
|
2322
2389
|
const tag = document.createElement("style");
|
|
@@ -2889,7 +2956,10 @@ window.__ModuleLoader__.load({
|
|
|
2889
2956
|
// (关=一个座位都不注册,见 apply 内的 M.O. 块)。独立成组只解决「混在
|
|
2890
2957
|
// enhance 末位」;可见层由 FEATURE_FIRST_SCREEN 决定(见其注释),
|
|
2891
2958
|
// 本轮已把组名提进首屏——组成员资格不决定可见性。
|
|
2892
|
-
|
|
2959
|
+
// 同一组的三个键是**三件独立能力**,不是一条链的三段:看图(座位闸门)、
|
|
2960
|
+
// 自动沉淀(写提议到记忆库)、执行记账(/mo-run 开窗)。各自默认关,
|
|
2961
|
+
// 开了任何一个都不会顺带打开另一个——写能力必须由人一项一项点。
|
|
2962
|
+
{ key: "group.mo", items: ["actionPipelineEnabled", "actionNudgeEnabled"] },
|
|
2893
2963
|
{ key: "group.enhance", items: ["entityExtractionEnabled", "codingRetrospect", "rerankEnabled", "resilientModelDownload", "searchSemanticDedup", "bm25SearchEnabled", "heatEnabled", "documentMemoryEnabled",
|
|
2894
2964
|
// 人格定义页(beta):注入总闸。分组随 enhance(行为开关,非座位闸门);
|
|
2895
2965
|
// 人格内容在设置页「个性化」区的人格卡。
|
|
@@ -2910,7 +2980,7 @@ window.__ModuleLoader__.load({
|
|
|
2910
2980
|
// 后端白名单、面板够不着——而 CONFIGURATION.md 明写「面板可启停」。
|
|
2911
2981
|
// 其余整数键(429 调速器参数、distillMaxChars、codingBoostFactor)是有意
|
|
2912
2982
|
// 不上 UI 的调优噪音,见上方注释,别顺手搬进来。
|
|
2913
|
-
const FEATURE_NUMBERS = ["distillRecords.retentionDays"];
|
|
2983
|
+
const FEATURE_NUMBERS = ["distillRecords.retentionDays", "dreamMaxTokens", "sleepMaxTokens"];
|
|
2914
2984
|
const EMBED_PROVIDERS = ["openai", "local", "ollama"];
|
|
2915
2985
|
// 实体抽取思考强度(issue #109):与后端 FEATURE_FLAG_ENUMS 枚举对齐。
|
|
2916
2986
|
const ENTITY_REASONING = ["none", "low", "medium", "high"];
|
|
@@ -3063,11 +3133,25 @@ window.__ModuleLoader__.load({
|
|
|
3063
3133
|
// = 值仍来自 config schema 的默认值。面板自己判断,不加后端字段、
|
|
3064
3134
|
// 不动 config.js(#427 的验收红线之一)。
|
|
3065
3135
|
const ovr = (state && state.overrides) || {};
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3136
|
+
// 「待重启」是第三态,必须由后端算:#481 的坑正是「面板显示已改 ≠ 运行时
|
|
3137
|
+
// 已改」——开关在宿主启动时合并进 cfg 并冻结,此后改 kv 只是持久化。这个
|
|
3138
|
+
// 落差前端看不见(overrides 里只有「改了」这一个事实),所以后端另比一次
|
|
3139
|
+
// 冻结值并给出 pendingRestart 名单;本面板只负责把它显示出来。
|
|
3140
|
+
const pending = new Set((state && state.pendingRestart) || []);
|
|
3141
|
+
const flagTag = (k) => {
|
|
3142
|
+
if (pending.has(k)) {
|
|
3143
|
+
return {
|
|
3144
|
+
kind: "mneme-badge--pending",
|
|
3145
|
+
text: t("memory.features.tagPending"),
|
|
3146
|
+
title: t("memory.features.tagPending.hint")
|
|
3147
|
+
};
|
|
3148
|
+
}
|
|
3149
|
+
return {
|
|
3150
|
+
kind: Object.prototype.hasOwnProperty.call(ovr, k) ? "mneme-badge--changed" : "mneme-badge--default",
|
|
3151
|
+
text: t(Object.prototype.hasOwnProperty.call(ovr, k) ? "memory.features.tagChanged" : "memory.features.tagDefault"),
|
|
3152
|
+
title: t(Object.prototype.hasOwnProperty.call(ovr, k) ? "memory.features.tagChanged.hint" : "memory.features.tagDefault.hint")
|
|
3153
|
+
};
|
|
3154
|
+
};
|
|
3071
3155
|
const boolRow = (k, opts = {}) => h(FeatureRow, {
|
|
3072
3156
|
key: k,
|
|
3073
3157
|
name: t(`memory.features.${k}`),
|
|
@@ -3256,14 +3340,19 @@ window.__ModuleLoader__.load({
|
|
|
3256
3340
|
// Issue #239 第 4 项镜像到巩固:高峰时段串(空 = 关闭)。放在 autoDream
|
|
3257
3341
|
// 子块内——它是巩固的排程,开关关掉时不该还在界面上留着可编辑的输入框。
|
|
3258
3342
|
strRow("dreamPeakHours"),
|
|
3259
|
-
h("div", { className: "mneme-featsubhint" }, t("memory.features.dreamPeakHours.hint"))
|
|
3343
|
+
h("div", { className: "mneme-featsubhint" }, t("memory.features.dreamPeakHours.hint")),
|
|
3344
|
+
// Issue #478:单轮巩固的 max_tokens 预算。后端白名单(256–131072)早就有,
|
|
3345
|
+
// 修复前 UI 够不着;默认 131072 超过很多 OpenAI 兼容网关的硬上限又看不见调不了。
|
|
3346
|
+
numRow("dreamMaxTokens")
|
|
3260
3347
|
);
|
|
3261
3348
|
|
|
3262
3349
|
// 睡眠模型:sleepModeEnabled 开着才展开(sleepProvider/sleepModel 随本版
|
|
3263
3350
|
// 进白名单;下拉与测试复用同一 /llm-providers 数据源)。
|
|
3264
3351
|
const sleepSub = eff.sleepModeEnabled && Array.isArray(routes) && h("div", { className: "mneme-featsub" },
|
|
3265
3352
|
routeSelects("sleepProvider", "sleepModel", sleepTest, setSleepTest, "sleepReasoningEffort"),
|
|
3266
|
-
h("div", { className: "mneme-featsubhint" }, t("memory.features.sleepModelHint"))
|
|
3353
|
+
h("div", { className: "mneme-featsubhint" }, t("memory.features.sleepModelHint")),
|
|
3354
|
+
// Issue #478:睡眠侧同款预算键(#257 引入后端白名单,UI 一直缺席)。
|
|
3355
|
+
numRow("sleepMaxTokens")
|
|
3267
3356
|
);
|
|
3268
3357
|
|
|
3269
3358
|
// 实体抽取路由与思考强度(issue #109):entityExtractionEnabled 开着才
|
|
@@ -3325,6 +3414,14 @@ window.__ModuleLoader__.load({
|
|
|
3325
3414
|
savedTick && h("span", { className: "mneme-saved", style: { marginLeft: 8, fontWeight: 400 } }, t("memory.features.restartHint"))
|
|
3326
3415
|
),
|
|
3327
3416
|
h("div", { className: "mneme-set-desc" }, t("memory.features.desc")),
|
|
3417
|
+
// #481:改了但没生效的开关,在这张卡里明说。放在 desc 之后、错误行之前
|
|
3418
|
+
// ——它是常态提示而非错误,但比错误更该先被看见(用户刚改完就会看这里)。
|
|
3419
|
+
// 行级 badge(flagTag 的第三态)标出具体是哪几行,这条只负责说清「有事
|
|
3420
|
+
// 没生效」和「怎么办」。
|
|
3421
|
+
pending.size > 0 && h("div", { className: "mneme-pending" },
|
|
3422
|
+
h("span", { className: "mneme-pending-dot" }),
|
|
3423
|
+
h("span", null, t("memory.features.pendingRestartNotice"))
|
|
3424
|
+
),
|
|
3328
3425
|
// 见 saveError 的声明处:这条分支不能跟着加载失败那条走。
|
|
3329
3426
|
saveError && h("div", { className: "mneme-mainterr" }, saveError),
|
|
3330
3427
|
h(RuntimeProvisionBlock, { t }),
|
|
@@ -3857,9 +3954,11 @@ window.__ModuleLoader__.load({
|
|
|
3857
3954
|
className: "mneme-btn" + (personaEditing === id ? " mneme-btn--active" : ""),
|
|
3858
3955
|
onClick: () => setPersonaEditing(id)
|
|
3859
3956
|
}, id),
|
|
3957
|
+
// 卡删除 × 常显:用独立类 mneme-persona-del(见上方样式定义处的
|
|
3958
|
+
// 注释——为何不复用 mneme-set-del,以及 #457→#459 一修一回滚的教训)。
|
|
3860
3959
|
h("button", {
|
|
3861
3960
|
type: "button",
|
|
3862
|
-
className: "mneme-
|
|
3961
|
+
className: "mneme-persona-del",
|
|
3863
3962
|
title: t("memory.settings.personaDeleteCard"),
|
|
3864
3963
|
"aria-label": t("memory.settings.personaDeleteCard") + " " + id,
|
|
3865
3964
|
onClick: () => removePersonaCard(id)
|
|
@@ -7540,8 +7639,62 @@ window.__ModuleLoader__.load({
|
|
|
7540
7639
|
// 条目清单(名字 + 规模 + 作用域):图上方的**选择器**。
|
|
7541
7640
|
// 它解决的是「这是哪条工作流」——一进来就是一张图时这个问题没有落点,多条之间也没得选。
|
|
7542
7641
|
// 但它不占整屏:图和它同屏,见下面的 PipelineBrowser。
|
|
7642
|
+
// 「现在就跑一次」。自动沉淀只在 turn/end 之后跑、还有 10 分钟冷却,所以人想立刻
|
|
7643
|
+
// 看到结果时没有入口。这里的 POST 与自动跑走**同一条** nudgeForProject,区别只有
|
|
7644
|
+
// materialize:按钮永远不落草稿,只把提议写进记忆库——升格仍然要人批。
|
|
7645
|
+
// project 为空(会话里读不到 cwd)时按钮是禁用的:服务端只会回 400,
|
|
7646
|
+
// 而一个点了必然失败的按钮比没有按钮更糟。
|
|
7647
|
+
function PipelineDistillButton(props) {
|
|
7648
|
+
const { t, project } = props;
|
|
7649
|
+
const [busy, setBusy] = useState(false);
|
|
7650
|
+
const [note, setNote] = useState("");
|
|
7651
|
+
const disabled = busy || !project;
|
|
7652
|
+
const run = () => {
|
|
7653
|
+
if (disabled) return;
|
|
7654
|
+
setBusy(true);
|
|
7655
|
+
setNote("");
|
|
7656
|
+
apiFetch("/api/dsh-mneme/nudge?project=" + encodeURIComponent(project), {
|
|
7657
|
+
method: "POST",
|
|
7658
|
+
headers: { "Content-Type": "application/json" },
|
|
7659
|
+
body: JSON.stringify({})
|
|
7660
|
+
})
|
|
7661
|
+
.then((res) => res.json().then((j) => ({ ok: res.ok, status: res.status, body: j || {} })))
|
|
7662
|
+
.then(({ ok, status, body }) => {
|
|
7663
|
+
if (!ok) {
|
|
7664
|
+
// 503 = 会话日志读不到(环境问题),其余是这一跑本身失败。两句不能合并:
|
|
7665
|
+
// 「读不到」和「没有重复动作」在界面上长得一样时,人就只能瞎猜。
|
|
7666
|
+
setNote(t(status === 503
|
|
7667
|
+
? "memory.actions.distill.unreadable"
|
|
7668
|
+
: "memory.actions.distill.failed"));
|
|
7669
|
+
return;
|
|
7670
|
+
}
|
|
7671
|
+
if (body.skipped) {
|
|
7672
|
+
setNote(t("memory.actions.distill.noSessions"));
|
|
7673
|
+
return;
|
|
7674
|
+
}
|
|
7675
|
+
const saved = (body.counts && body.counts.savedProposals) || 0;
|
|
7676
|
+
const found = (body.counts && body.counts.candidates) || 0;
|
|
7677
|
+
setNote(found === 0
|
|
7678
|
+
? t("memory.actions.distill.none")
|
|
7679
|
+
: t("memory.actions.distill.saved").replace("{n}", String(saved)).replace("{m}", String(found)));
|
|
7680
|
+
})
|
|
7681
|
+
.catch(() => setNote(t("memory.actions.distill.failed")))
|
|
7682
|
+
.then(() => setBusy(false));
|
|
7683
|
+
};
|
|
7684
|
+
return h("span", { className: "mneme-pipe-distill" },
|
|
7685
|
+
h("button", {
|
|
7686
|
+
type: "button",
|
|
7687
|
+
className: "mneme-pipe-distillBtn",
|
|
7688
|
+
disabled,
|
|
7689
|
+
title: !project ? t("memory.actions.distill.noProject") : t("memory.actions.distill.hint"),
|
|
7690
|
+
onClick: run
|
|
7691
|
+
}, busy ? t("memory.actions.distill.busy") : t("memory.actions.distill.action")),
|
|
7692
|
+
note ? h("span", { className: "mneme-pipe-distillNote", role: "status" }, note) : null
|
|
7693
|
+
);
|
|
7694
|
+
}
|
|
7695
|
+
|
|
7543
7696
|
function PipelineWorkflowList(props) {
|
|
7544
|
-
const { t, data, activeId, onOpen } = props;
|
|
7697
|
+
const { t, data, activeId, onOpen, project } = props;
|
|
7545
7698
|
const list = (data && data.available) || [];
|
|
7546
7699
|
const scopeOf = (row) => (row.scope === "global"
|
|
7547
7700
|
? t("memory.actions.scope.global")
|
|
@@ -7551,6 +7704,7 @@ window.__ModuleLoader__.load({
|
|
|
7551
7704
|
h("span", null, t("memory.actions.list.heading")),
|
|
7552
7705
|
h("span", null, t("memory.actions.list.count").replace("{n}", String(list.length)))
|
|
7553
7706
|
),
|
|
7707
|
+
h(PipelineDistillButton, { t, project }),
|
|
7554
7708
|
list.length
|
|
7555
7709
|
? list.map((row) => h("button", {
|
|
7556
7710
|
key: row.id,
|
|
@@ -7601,7 +7755,7 @@ window.__ModuleLoader__.load({
|
|
|
7601
7755
|
className: "mneme-pipe",
|
|
7602
7756
|
style: { height: "100%", display: "flex", flexDirection: "column", minHeight: "0" }
|
|
7603
7757
|
},
|
|
7604
|
-
h(PipelineWorkflowList, { t, data: listDoc, activeId, onOpen: setPicked }),
|
|
7758
|
+
h(PipelineWorkflowList, { t, data: listDoc, activeId, onOpen: setPicked, project }),
|
|
7605
7759
|
h(PipelineView, { t, data: detailDoc })
|
|
7606
7760
|
);
|
|
7607
7761
|
}
|
package/lib/config.js
CHANGED
|
@@ -863,7 +863,24 @@ export const Config = z.object({
|
|
|
863
863
|
// 数据、观察 A2 加权质量后再切硬过滤。
|
|
864
864
|
strictScope: z.boolean().default(false),
|
|
865
865
|
|
|
866
|
-
//
|
|
866
|
+
// scopeState(issue #471):政策轴——作用域级可逆闸门。与上面两个键是**两条不同
|
|
867
|
+
// 的轴**:scopeEnabled/strictScope 管「这条记忆属于谁」(事实轴:持久、跟着记忆
|
|
868
|
+
// 行走、改的是归属数据),本键管「这个作用域现在通不通」(政策轴:暂时、由人单
|
|
869
|
+
// 方面按下、松开即恢复、不改任何记忆行)。
|
|
870
|
+
// 两级语义同 writeAdmission/preInjectGate——enabled 打开时被按下的作用域其候选
|
|
871
|
+
// 照常注入,但留一条审计行说清「本应被拦下几条」(观察档,先在真实流量看会少掉
|
|
872
|
+
// 多少再决定真按);enforce 打开才真的不注入(拦截档)。两个都默认关,打开前
|
|
873
|
+
// 行为与今天逐字段一致。
|
|
874
|
+
// 不进 LIGHT_MODE_OFF:它只是查一张小表的判定,不引入重型后台路径、不加常驻
|
|
875
|
+
// 文本(同 actionPipelineEnabled 的定性)——轻量档预设该关的是重型能力,不该替
|
|
876
|
+
// 用户关掉一个纯判定入口,用户显式勾选仍然赢。
|
|
877
|
+
// 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
|
|
878
|
+
scopeState: z.object({
|
|
879
|
+
enabled: z.boolean().default(false),
|
|
880
|
+
enforce: z.boolean().default(false)
|
|
881
|
+
}).default({}),
|
|
882
|
+
|
|
883
|
+
// --- v0.9.0 M.O.(Modus Operandi,beta)--------------------------
|
|
867
884
|
// 命名:M.O. = Modus Operandi,拉丁语「一个人一贯的做事方式」,缩写 M.O.;
|
|
868
885
|
// 也正是 org 词根 modus(modusensus)。界面主显示 M.O.,设置项 hint 第一句
|
|
869
886
|
// 给全名——缩写不靠注释解释,靠用户第一眼能看见全名。
|
|
@@ -885,6 +902,20 @@ export const Config = z.object({
|
|
|
885
902
|
// 所以轻量档要关掉:`LIGHT_MODE_OFF` 里有它。
|
|
886
903
|
actionRunEnabled: z.boolean().default(false),
|
|
887
904
|
|
|
905
|
+
// M.O. 自动沉淀(v0.9.0 第三批):会话跑完一轮后,把这个项目自己的会话日志挖一遍,
|
|
906
|
+
// 把「反复出现的同一段能力序列」落成提议(memoryDir 下),可选再落一份 draft 草稿。
|
|
907
|
+
//
|
|
908
|
+
// 与上面两个键三件事分开:actionPipelineEnabled 管「看图的座位」,actionRunEnabled
|
|
909
|
+
// 管「往账本里写」,这个管「从日志里长出候选」。三个都是独立能力,混成一个键会让
|
|
910
|
+
// 「只想看图」的人顺手获得写盘能力。
|
|
911
|
+
//
|
|
912
|
+
// 默认关(beta 必须 opt-in),并且进 LIGHT_MODE_OFF:它挂在 session/event 热路径上,
|
|
913
|
+
// 且解压会话日志是**同步**的(本机 118 份实测 16.2s),轻量档最不该背这条路径。
|
|
914
|
+
// 触发侧另有 10 分钟/项目的冷却与 budgetMs 上限(见 src/pipeline-nudge.js 的
|
|
915
|
+
// NUDGE_AUTO_MIN_INTERVAL_MS 与 src/index.js 的装配注释)——旗标只是总闸。
|
|
916
|
+
// (冷却值以常量为准:早前这里写「30 分钟」,与实际 10 分钟不符。)
|
|
917
|
+
actionNudgeEnabled: z.boolean().default(false),
|
|
918
|
+
|
|
888
919
|
// --- 人格定义页(beta)---------------------------------------------------
|
|
889
920
|
// 注入器渲染人格段的顶层总闸(双闸门的 config 半边;user_settings "persona"
|
|
890
921
|
// 键内的 persona.enabled 是 per-agent 半边,两闸同真才渲染)。API 的 effective
|
|
@@ -945,7 +976,11 @@ const LIGHT_MODE_OFF = [
|
|
|
945
976
|
// M.O. 执行记账:它挂在**热路径**上(每个会话事件都过一次监听)。轻量档的取向
|
|
946
977
|
// 是「小模型 / 小上下文 / 少常驻路径」,这与一个每事件都过一遍的监听器相冲突。
|
|
947
978
|
// 默认关,这里只是轻量预设再确认一次;用户显式勾选仍然赢(合并顺序同上面两条)。
|
|
948
|
-
"actionRunEnabled"
|
|
979
|
+
"actionRunEnabled",
|
|
980
|
+
// M.O. 自动沉淀:同样挂在 session/event 热路径上,而且解压会话日志是同步 I/O。
|
|
981
|
+
// 轻量档的取向(小模型 / 小上下文 / 少常驻路径)与它直接冲突,预设里关掉;
|
|
982
|
+
// 用户显式勾选仍然赢(合并顺序同上面几条)。
|
|
983
|
+
"actionNudgeEnabled"
|
|
949
984
|
];
|
|
950
985
|
|
|
951
986
|
/**
|
package/lib/dream/sleep.js
CHANGED
|
@@ -21,7 +21,7 @@ import { randomUUID, createHash } from "node:crypto";
|
|
|
21
21
|
import { validateDecisions, applyDecisions, ACTIONS } from "./decisions.js";
|
|
22
22
|
import { findPotentialConflicts, cosineSimilarity } from "./clustering.js";
|
|
23
23
|
import { scopeKeyOf } from "../scope.js";
|
|
24
|
-
import { buildReceipt, describeStreamFailure, resolveDreamEffort, withEffortFallback, maintainIndexAfterDream } from "../dream.js";
|
|
24
|
+
import { buildReceipt, describeStreamFailure, resolveDreamEffort, withEffortFallback, withMaxTokensFallback, maintainIndexAfterDream } from "../dream.js";
|
|
25
25
|
import { computeHeat } from "../heat.js";
|
|
26
26
|
|
|
27
27
|
// Issue #126 review(Copilot):默认档(sleepActionSet="conflict")允许的动作 = 全局
|
|
@@ -172,6 +172,22 @@ function makeSummary(m) {
|
|
|
172
172
|
return text.length <= SUMMARY_MAX ? text : `${text.slice(0, SUMMARY_MAX)}…`;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
// issue #471 政策轴:被按下的作用域也不参与睡眠整理。与 dream.js 的 withoutPaused
|
|
176
|
+
// 同源(那里记着「判定只许有一份实现」的理由),但这里是**单独一处**而不是复用:
|
|
177
|
+
// 两个模块各被独立装配(runSleep / runDream 各自接 service),任何一个被换进只
|
|
178
|
+
// 实现了一半 service 的夹具时都该各自降级,而不是因为对方模块没装而双双失效。
|
|
179
|
+
//
|
|
180
|
+
// 睡眠侧比日间加固侧更要紧:这几个相位不只读——phaseDemotion 会 setArchived 与
|
|
181
|
+
// demoteToSummary(真改归档面),phasePatterns 会 mint 出 type=pattern 的新行
|
|
182
|
+
// (凭空给被按下的作用域生出衍生记忆)。只挡日间那几处读入口的话,闸门开着而每
|
|
183
|
+
// 6 小时一次的睡眠照样动手,那等于「按下」只是让它在白天看不见。
|
|
184
|
+
//
|
|
185
|
+
// service 上取不到 filterPaused 时(老装配、测试夹具)原样返回,与改动前一致。
|
|
186
|
+
function withoutPaused(service, rows) {
|
|
187
|
+
if (typeof service?.filterPaused !== "function") return rows;
|
|
188
|
+
return service.filterPaused(rows);
|
|
189
|
+
}
|
|
190
|
+
|
|
175
191
|
// ---------------------------------------------------------------- phases
|
|
176
192
|
|
|
177
193
|
/**
|
|
@@ -189,7 +205,7 @@ async function phaseConflicts(ctx, service, config, logger, runId, semantic = nu
|
|
|
189
205
|
}
|
|
190
206
|
const strictness = config.sleepConflictStrictness ?? "normal";
|
|
191
207
|
const threshold = CONFLICT_THRESHOLDS[strictness] ?? CONFLICT_THRESHOLDS.normal;
|
|
192
|
-
const memories = service.all().filter((m) => !m.archived && !m.forgotten && m.type !== "summary" && m.type !== "document");
|
|
208
|
+
const memories = withoutPaused(service, service.all().filter((m) => !m.archived && !m.forgotten && m.type !== "summary" && m.type !== "document"));
|
|
193
209
|
if (memories.length < 2) return { status: "skipped", reason: "too few memories" };
|
|
194
210
|
if (signal?.aborted) return { status: "aborted", reason: "user activity" };
|
|
195
211
|
|
|
@@ -313,7 +329,7 @@ async function phaseConflicts(ctx, service, config, logger, runId, semantic = nu
|
|
|
313
329
|
// actionSet 已在候选构造前解析(跨类型候选需要它),这里只按档位选 prompt:
|
|
314
330
|
// 默认档保持只有 conflict/keep 的窄 prompt,"full" 才开放六分支。
|
|
315
331
|
const conflictPrompt = actionSet === "full" ? STR.prompts.conflictFull[language] : STR.prompts.conflict[language];
|
|
316
|
-
const runConflict = (withEffort) => {
|
|
332
|
+
const runConflict = (withEffort, tokens) => {
|
|
317
333
|
conflictStreamFailure = "";
|
|
318
334
|
return auditedSleepLlm(ctx, service, config, logger, {
|
|
319
335
|
triggerSource: "sleep",
|
|
@@ -331,7 +347,7 @@ async function phaseConflicts(ctx, service, config, logger, runId, semantic = nu
|
|
|
331
347
|
provider: route.provider,
|
|
332
348
|
model: route.model,
|
|
333
349
|
purpose: "sleep-conflict",
|
|
334
|
-
maxTokens:
|
|
350
|
+
maxTokens: tokens,
|
|
335
351
|
...(withEffort && sleepEffort ? { reasoningEffort: sleepEffort } : {}),
|
|
336
352
|
messages: [
|
|
337
353
|
{ role: "system", content: [{ type: "text", text: conflictPrompt }], source: { kind: "plugin:dsh-mneme", plugin: "dsh-mneme" } },
|
|
@@ -339,7 +355,9 @@ async function phaseConflicts(ctx, service, config, logger, runId, semantic = nu
|
|
|
339
355
|
]
|
|
340
356
|
}, reportUsage, (reason) => { conflictStreamFailure = describeStreamFailure(reason); }));
|
|
341
357
|
};
|
|
342
|
-
|
|
358
|
+
// Issue #478:睡眠侧同样接 max_tokens 降级——面板 hint 已承诺「超上限同样自动降级」,
|
|
359
|
+
// 只挂 dream 三个点会让 sleepMaxTokens 的 131072 上限继续原样发。
|
|
360
|
+
const text = await withMaxTokensFallback(ctx, config.sleepMaxTokens ?? 2048, (tokens) => withEffortFallback(ctx, sleepEffort, () => runConflict(true, tokens), () => runConflict(false, tokens), () => conflictStreamFailure), () => conflictStreamFailure);
|
|
343
361
|
if (text === undefined) {
|
|
344
362
|
if (conflictStreamFailure) ctx.logger?.warn?.(`dsh-mneme sleep: conflict stream aborted or errored (${conflictStreamFailure})`);
|
|
345
363
|
return { status: "failed", error: "llm failed" };
|
|
@@ -423,7 +441,10 @@ function phaseDemotion(service, config, logger, runId, signal = null) {
|
|
|
423
441
|
const compressCut = Date.now() - compressDays * 86400000;
|
|
424
442
|
const demoted = [];
|
|
425
443
|
const archived = [];
|
|
426
|
-
|
|
444
|
+
// 政策轴在这里尤其要紧:下面两行会真改归档面(setArchived / demoteToSummary)。
|
|
445
|
+
// 把过滤放在循环之外、而不是在循环体里 continue:一旦将来有人在循环体里加
|
|
446
|
+
// 副作用(现已有),漏掉那条 continue 就是静默改到被按下的行。
|
|
447
|
+
for (const m of withoutPaused(service, service.all())) {
|
|
427
448
|
if (signal?.aborted) break;
|
|
428
449
|
if (m.archived || m.forgotten) continue;
|
|
429
450
|
const ref = m.last_accessed_at ?? m.created_at;
|
|
@@ -472,9 +493,11 @@ async function phasePatterns(ctx, service, config, logger, runId, signal = null)
|
|
|
472
493
|
const limit = config.sleepPatternMinMemories ?? 100;
|
|
473
494
|
// #230:document 在查询层排除(excludeTypes)而不是截断后过滤——LIMIT 200
|
|
474
495
|
// 先生效的话,指针行一多就会把普通记忆挤出扫描窗,池子饿到门槛以下。
|
|
475
|
-
|
|
496
|
+
// 政策轴同理必须排在 `.slice(0, limit)` **之前**,否则被按下的行照样占掉
|
|
497
|
+
// sleepPatternMinMemories 的名额,而它们既不会被扫、也不该挤掉别人(#471)。
|
|
498
|
+
const memories = withoutPaused(service, service
|
|
476
499
|
.list({ limit: 200, includeForgotten: false, excludeTypes: ["document"] })
|
|
477
|
-
.filter((m) => !m.archived && m.type !== "summary" && m.type !== "pattern")
|
|
500
|
+
.filter((m) => !m.archived && m.type !== "summary" && m.type !== "pattern"))
|
|
478
501
|
.sort((a, b) => (a.updated_at < b.updated_at ? 1 : -1))
|
|
479
502
|
.slice(0, limit);
|
|
480
503
|
if (memories.length === 0) return { status: "skipped", reason: "no memories to scan" };
|
|
@@ -485,7 +508,7 @@ async function phasePatterns(ctx, service, config, logger, runId, signal = null)
|
|
|
485
508
|
const maxPatterns = config.sleepMaxPatternPerRun ?? 3;
|
|
486
509
|
const sleepEffort = await resolveDreamEffort(ctx, route, config.sleepReasoningEffort, logger);
|
|
487
510
|
let patternStreamFailure = "";
|
|
488
|
-
const runPattern = (withEffort) => {
|
|
511
|
+
const runPattern = (withEffort, tokens) => {
|
|
489
512
|
patternStreamFailure = "";
|
|
490
513
|
return auditedSleepLlm(ctx, service, config, logger, {
|
|
491
514
|
triggerSource: "sleep",
|
|
@@ -500,7 +523,7 @@ async function phasePatterns(ctx, service, config, logger, runId, signal = null)
|
|
|
500
523
|
provider: route.provider,
|
|
501
524
|
model: route.model,
|
|
502
525
|
purpose: "sleep-pattern",
|
|
503
|
-
maxTokens:
|
|
526
|
+
maxTokens: tokens,
|
|
504
527
|
...(withEffort && sleepEffort ? { reasoningEffort: sleepEffort } : {}),
|
|
505
528
|
messages: [
|
|
506
529
|
{ role: "system", content: [{ type: "text", text: STR.prompts.pattern[language].replace("N", String(maxPatterns)) }], source: { kind: "plugin:dsh-mneme", plugin: "dsh-mneme" } },
|
|
@@ -508,7 +531,7 @@ async function phasePatterns(ctx, service, config, logger, runId, signal = null)
|
|
|
508
531
|
]
|
|
509
532
|
}, reportUsage, (reason) => { patternStreamFailure = describeStreamFailure(reason); }));
|
|
510
533
|
};
|
|
511
|
-
const text = await withEffortFallback(ctx, sleepEffort, () => runPattern(true), () => runPattern(false), () => patternStreamFailure);
|
|
534
|
+
const text = await withMaxTokensFallback(ctx, config.sleepMaxTokens ?? 2048, (tokens) => withEffortFallback(ctx, sleepEffort, () => runPattern(true, tokens), () => runPattern(false, tokens), () => patternStreamFailure), () => patternStreamFailure);
|
|
512
535
|
if (text === undefined) {
|
|
513
536
|
if (patternStreamFailure) ctx.logger?.warn?.(`dsh-mneme sleep: pattern stream aborted or errored (${patternStreamFailure})`);
|
|
514
537
|
return { status: "failed", error: "llm failed" };
|
|
@@ -563,7 +586,7 @@ function phaseRelations(service, config, logger, runId, signal = null) {
|
|
|
563
586
|
if (entities.length < 2) return { status: "skipped", reason: "too few entities" };
|
|
564
587
|
const orphans = entities.filter((e) => (service.getRelations(e.id) ?? []).length === 0);
|
|
565
588
|
if (orphans.length === 0) return { status: "skipped", reason: "no orphan entities" };
|
|
566
|
-
const memories = service.all().filter((m) => !m.archived && !m.forgotten);
|
|
589
|
+
const memories = withoutPaused(service, service.all().filter((m) => !m.archived && !m.forgotten));
|
|
567
590
|
const seen = new Set();
|
|
568
591
|
const related = [];
|
|
569
592
|
const MAX_RELATIONS_PER_ORPHAN = 3;
|