sage-mem 2.0.1 → 2.1.0

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/lib/client.js CHANGED
@@ -3879,6 +3879,220 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
3879
3879
  "notice.conflictDismissed": "已标记为已裁决 —— 没有改任何记忆文件",
3880
3880
  "notice.conflictUndone": "已撤销 {file} 的失效标记 —— 它重新参与注入",
3881
3881
  "notice.conflictRemoved": "已删除冲突记录(记忆文件没动)",
3882
+
3883
+ // ── 2.1.0: autodream tab (60-autodream-panel.js) ──
3884
+ "ad.tab.run": "运行",
3885
+ "ad.tab.settings": "设置",
3886
+ "ad.tab.records": "记录",
3887
+ "ad.title": "自动做梦",
3888
+ "ad.switch.onTitle": "点击停用",
3889
+ "ad.switch.offTitle": "点击启用",
3890
+ "ad.switch.aria": "启用或停用自动做梦",
3891
+ "ad.status.running": "运行中 · {phase}",
3892
+ "ad.status.preparing": "准备中",
3893
+ "ad.status.idle": "已启用 · 空闲",
3894
+ "ad.status.off": "未启用",
3895
+ "ad.note.what": "把记忆目录通读一遍:合并重复的、删掉被推翻的、修索引与断链、把相对日期换成绝对日期。",
3896
+ "ad.row.trigger": "触发",
3897
+ "ad.trigger.manual": "只手动",
3898
+ "ad.trigger.auto": "满足条件自动跑",
3899
+ "ad.row.apply": "改动方式",
3900
+ "ad.apply.report": "只出报告",
3901
+ "ad.apply.apply": "直接改写(先快照)",
3902
+ "ad.row.source": "输入源",
3903
+ "ad.source.memory": "仅记忆目录",
3904
+ "ad.source.sessions": "记忆 + 会话记录",
3905
+ "ad.note.sourceSessions": "会解压会话记录做定向搜索 —— 私密对话会进入模型上下文,token 成本也更高。",
3906
+ "ad.note.sourceMemory": "只看记忆文件本身,不碰会话记录。",
3907
+ "ad.note.applySnapshot": "改写前会自动把整个记忆目录快照一份(memory 上一层 .sage-mem/autodream/snapshots/),改坏可回退。",
3908
+ "ad.note.reportOnly": "只读模式:这一趟连写工具都不会挂给模型,所以它不可能改到任何文件。",
3909
+ "ad.row.model": "模型",
3910
+ "ad.model.followDefault": "跟随当前会话默认模型",
3911
+ "ad.model.defaultRoute": "({provider} / {model})",
3912
+ "ad.model.currentOutOfCatalog": "(配置里指定,不在目录中)",
3913
+ "ad.model.placeholder.provider": "provider(留空=跟随默认)",
3914
+ "ad.model.placeholder.model": "model(留空=跟随默认)",
3915
+ "ad.model.saved": "模型已保存",
3916
+ "ad.action.save": "保存",
3917
+ "ad.action.manualEntry": "手填",
3918
+ "ad.loadingModels": "模型目录载入中…",
3919
+ "ad.route.bad": "实际生效路线:拿不到 —— {error}",
3920
+ "ad.route.badFallback": "宿主没能解析出 provider / model(配置留空且取不到会话默认模型)",
3921
+ "ad.route.ok": "实际生效路线:{provider} / {model}",
3922
+ "ad.route.fromDefault": "(跟随默认)",
3923
+ "ad.route.fromConfig": "(配置指定)",
3924
+ "ad.action.runningNow": "正在整理…",
3925
+ "ad.action.runNow": "立即整理",
3926
+ "ad.run.steps": "已跑 {count} 轮 · {phase}",
3927
+ "ad.run.hoursSince": "距上次整理 {hours} 小时",
3928
+ "ad.run.never": "从未整理过",
3929
+ "ad.run.progress": "第 {step} 轮:{tools}",
3930
+ "ad.run.stepDone": "结束",
3931
+ "ad.toolSep": "、",
3932
+ "ad.errSep": ";",
3933
+ "ad.progressSep": " · ",
3934
+ "ad.last.prefix": "上次 {at} · {result}",
3935
+ "ad.last.ok": "成功",
3936
+ "ad.last.applied": ",改写了 {count} 个文件",
3937
+ "ad.last.reportOnly": "(只出报告)",
3938
+ "ad.last.problems": " · 结构问题 {before} → {after}",
3939
+ "ad.last.tokens": " · token {in} / {out}",
3940
+ "ad.last.failed": "失败:{error}",
3941
+ "ad.unknown": "未知",
3942
+ "ad.last.truncated": " · ⚠ 报告被截断(残篇)",
3943
+ "ad.last.retried": " · 零正文重试 {count} 次后补齐",
3944
+ "ad.last.continuations": " · 续写 {count} 轮",
3945
+ "ad.last.report": " · 报告 {name}",
3946
+ "ad.sidebar.title": "侧栏入口",
3947
+ "ad.sidebar.on": "已开启",
3948
+ "ad.sidebar.off": "已关闭",
3949
+ "ad.sidebar.note": "在 DSH 侧栏显示「记忆管理」图标,点它把中央主区切到本页。默认开启;关闭后「设置 → 记忆管理」这个入口仍然保留。改动立即生效,不需要重启。",
3950
+ "ad.sidebar.label": "在侧栏显示「记忆管理」入口",
3951
+ "ad.limits.title": "记忆注入参数",
3952
+ "ad.limits.pending": "有改动待生效",
3953
+ "ad.limits.restartWarn": "⚠ 这些值在 DSH 启动时就固化了 —— 要重启 DSH 才生效。下面每行都写清「当前 → 重启后」。",
3954
+ "ad.limits.restartNote": "这些值在 DSH 启动时固化:保存后要重启 DSH 才生效 —— 所以每行都会标出「当前生效」与「重启后」。",
3955
+ "ad.limit.maxResults": "一次注入条数",
3956
+ "ad.limit.maxChars": "单条字符上限",
3957
+ "ad.limit.maxBaseline": "baseline 名额",
3958
+ "ad.limit.maxSessionBytes": "单会话字节预算",
3959
+ "ad.limit.staleDays": "新鲜度门槛",
3960
+ "ad.unit.items": "条",
3961
+ "ad.unit.chars": "字符",
3962
+ "ad.unit.bytes": "字节",
3963
+ "ad.unit.days": "天",
3964
+ "ad.unit.copies": "份",
3965
+ "ad.limit.labelTitle": "{unit} · 范围 {lo}–{hi}",
3966
+ "ad.limit.inputTitle": "范围 {lo}–{hi} {unit}",
3967
+ "ad.limit.diff": "当前 {current} → 重启后 {pending}",
3968
+ "ad.limit.range": "范围 {lo}–{hi}",
3969
+ "ad.limit.current": " · 当前生效 {current}",
3970
+ "ad.action.saving": "保存中…",
3971
+ "ad.action.saveLimits": "保存注入参数",
3972
+ "ad.limits.saveHint": "保存后需重启 DSH 才生效",
3973
+ "ad.reserved.title": "保留名保护名单",
3974
+ "ad.reserved.subtitle": "名单里的文件不参与注入",
3975
+ "ad.reserved.note": "内置的两个固定不可删;下面可以再加自己的(只写文件名,不要带路径)。改完需重启 DSH 才生效。",
3976
+ "ad.reserved.fixedSuffix": " · 固定",
3977
+ "ad.reserved.fixedTitle": "内置固定,不可删除",
3978
+ "ad.reserved.removeTitle": "从名单里移除 {name}",
3979
+ "ad.reserved.empty": "(还没有自己加的保留名)",
3980
+ "ad.reserved.placeholder": "例如 project_notes.md(要 .md 文件名)",
3981
+ "ad.action.addToList": "加入名单",
3982
+ "ad.advanced": "高级设置",
3983
+ "ad.adv.autoTrigger": "自动触发",
3984
+ "ad.adv.since": "距上次 ≥",
3985
+ "ad.adv.hoursAndSessions": "小时,且期间 ≥",
3986
+ "ad.adv.sessionsUpdated": "个会话有更新",
3987
+ "ad.adv.maxSteps": "最多轮数",
3988
+ "ad.adv.stepNote": "一轮 = 一次模型调用 + 它要的工具调用",
3989
+ "ad.adv.maxOutput": "单次输出上限",
3990
+ "ad.adv.maxOutputNote": "token(思考与正文共用;报告残篇时先调它)",
3991
+ "ad.adv.keepSnapshots": "保留快照",
3992
+ "ad.adv.paths": "配置与状态:{path}",
3993
+ "ad.records.snapshots": "回滚点",
3994
+ "ad.records.countSnaps": "{count} 个",
3995
+ "ad.action.refresh": "刷新",
3996
+ "ad.row.rollbackScope": "回滚范围",
3997
+ "ad.scope.files": "只恢复本次动过的文件",
3998
+ "ad.scope.all": "整目录回到该时点",
3999
+ "ad.note.rollback": "回滚前会自动把整个记忆目录再存一份保护快照 —— 所以回滚本身也能再退回来。多出来的文件只移进 archive/,不删除。",
4000
+ "ad.snap.empty": "还没有回滚点 —— 用「直接改写」跑过一次就有了。",
4001
+ "ad.snap.legacyLabel": "旧版快照",
4002
+ "ad.snap.runLabel": "运行 {id}",
4003
+ "ad.snap.protectionLabel": "回滚保护",
4004
+ "ad.snap.meta": "{at} · {files} 个文件",
4005
+ "ad.snap.legacyBadge": "旧版",
4006
+ "ad.action.rollback": "回滚",
4007
+ "ad.snap.confirmTitle": "确认回滚到 {at}?",
4008
+ "ad.snap.confirmScope": "范围:{scope}",
4009
+ "ad.snap.scopeAll": "整目录回到该时点 —— 快照里没有、当前目录里有的文件会被移进 archive/(不删除)",
4010
+ "ad.snap.scopeFiles": "只恢复这次运行动过的文件 —— 该次新建的移进 archive/,该次归档的移回顶层",
4011
+ "ad.snap.confirmSuffix": "。回滚前会先存一份保护快照,所以这一步本身也能退。",
4012
+ "ad.action.rollingBack": "回滚中…",
4013
+ "ad.action.confirmRollback": "确认回滚",
4014
+ "ad.action.cancel": "取消",
4015
+ "ad.rb.rolledBack": "已回滚到 {at}:恢复 {restored} 个文件,移走 {parked} 个",
4016
+ "ad.rb.skipped": ",跳过 {count} 个(详见回滚报告)",
4017
+ "ad.rb.protection": " · 保护快照 {id}",
4018
+ "ad.rb.report": " · 报告 {name}",
4019
+ "ad.records.runs": "整理记录",
4020
+ "ad.records.countRuns": "{count} 条",
4021
+ "ad.records.runsNote": "每一趟都留一份整理声明:改了什么、为什么改、哪些想法被拒了。点一条看全文。",
4022
+ "ad.records.runsEmpty": "还没有整理记录 —— 跑一次就有了。",
4023
+ "ad.badge.apply": "直接改写",
4024
+ "ad.badge.report": "只出报告",
4025
+ "ad.run.changes": "改 {count} 条",
4026
+ "ad.run.reportName": "报告 {name}",
4027
+ "ad.run.rolledBack": "已回滚",
4028
+ "ad.run.rolledBackAt": "已回滚 · {at}",
4029
+ "ad.action.collapse": "收起",
4030
+ "ad.action.showDeclaration": "看声明",
4031
+ "ad.records.reports": "历史报告",
4032
+ "ad.records.countReports": "{count} 份",
4033
+ "ad.records.reportsEmpty": "还没有报告 —— 跑一次就有了。报告写在 memory/autodream/ 下,不会混进记忆列表。",
4034
+ "ad.action.view": "查看",
4035
+ "ad.loading": "载入中…",
4036
+ "ad.empty": "(空)",
4037
+ "ad.emptyDeclaration": "(这一趟没有留下声明正文)",
4038
+ "ad.err.readFailed": "读取失败:{error}",
4039
+ "ad.err.rollbackFailed": "回滚失败:{error}",
4040
+ "ad.unknownReason": "未知原因",
4041
+ "ad.err.startFailed": "启动失败",
4042
+ "ad.notice.started": "已开始整理 —— 进度每 2 秒刷新一次",
4043
+ "ad.err.remoteUnavailable": "remote.autodream 不可用 —— 插件的 宿主侧可能没加载成功,看 DSH 启动日志",
4044
+ "ad.err.noInterface": "宿主侧没有 {name} 接口(版本较旧?)",
4045
+ "ad.err.noSetSettings": "宿主侧还没有 setSettings 接口(插件版本较旧)",
4046
+ "ad.err.limitInteger": "{label}要整数",
4047
+ "ad.err.limitRange": "{label} 要在 {lo}–{hi} 之间",
4048
+ "ad.err.notSaved": "没保存 —— {detail}",
4049
+ "ad.err.saveRejected": "保存被拒绝",
4050
+ "ad.notice.limitsSaved": "已保存 —— 需重启 DSH 才生效",
4051
+ "ad.notice.sidebarOn": "侧栏入口已开启(立即生效)",
4052
+ "ad.notice.sidebarOff": "侧栏入口已关闭(设置页入口仍在)",
4053
+ "ad.err.reservedRejected": "名单被拒绝",
4054
+ "ad.notice.reservedSaved": "保留名名单已保存 —— 需重启 DSH 才生效",
4055
+ "ad.err.reservedNeedName": "先写一个文件名,例如 project_notes.md",
4056
+ "ad.err.reservedBadName": "保留名要是一个 .md 文件名(不能带路径):{name}",
4057
+ "ad.err.reservedDup": "名单里已经有 {name} 了",
4058
+ "ad.note.modelCatalogUnavailable": "模型目录不可用({error})—— 请在下面手填 provider 与 model。",
4059
+
4060
+ // ── 2.1.0: auto-archive settings (62-archive-settings.js) ──
4061
+ "archive.title": "自动归档做什么",
4062
+ "archive.intro1": "闲置太久没被注入过的记忆,会被移进 archive/ —— 不删除,随时能恢复。这一页决定「多久算闲置」,以及要不要真的动手。",
4063
+ "archive.intro2": "它不调模型:候选完全按访问台账的天数算,同一份输入每次算出来都一样。",
4064
+ "archive.intro3": "想现在看看会被收走哪些:文件列表 →「工具 ▾」→ 归档候选。",
4065
+ "archive.modeTitle": "自动归档",
4066
+ "archive.mode.off": "关闭",
4067
+ "archive.mode.report": "只出报告",
4068
+ "archive.mode.auto": "满足条件自动跑",
4069
+ "archive.cost.off": "关闭:一个文件都不动 —— 候选只列在清单里,等你手点。",
4070
+ "archive.cost.report": "只出报告:候选写进整理报告,一个文件都不动。默认档,最保守。",
4071
+ "archive.cost.auto": "满足条件自动跑:会真的把候选移进 archive/(只移不删,随时可恢复);若「改动方式」是只出报告,这一档会自动降级成只出报告。",
4072
+ "archive.degrade": "注意:「自动做梦 → 改动方式」现在是只出报告 —— 这一档会自动降级成只出报告,不会动文件。",
4073
+ "archive.th.title": "闲置多少天算可以归档",
4074
+ "archive.th.project": "项目",
4075
+ "archive.th.reference": "参考",
4076
+ "archive.th.user": "用户",
4077
+ "archive.th.hintProject": "project_* 连续这么多天没被注入 → 进候选",
4078
+ "archive.th.hintReference": "reference_* 同上(资料一般比项目留得久)",
4079
+ "archive.th.hintUser": "user_* 同上(人设这类动它要格外谨慎)",
4080
+ "archive.loadingConfig": "读配置中…",
4081
+ "archive.action.save": "保存归档条件",
4082
+ "archive.action.saving": "保存中…",
4083
+ "archive.note.immediate": "保存即生效,不用重启 DSH",
4084
+ "archive.note.thresholds": "天数按访问台账算(这条记忆上次被注入给模型是什么时候)。feedback_* 永不入选;锁定的记忆也不会被选中。",
4085
+ "archive.err.noConfig": "宿主侧没有 autodream 配置接口(插件版本较旧)",
4086
+ "archive.err.readConfig": "读不出自动做梦配置",
4087
+ "archive.err.readFailed": "读配置失败:{error}",
4088
+ "archive.err.saveFailed": "保存失败:{error}",
4089
+ "archive.err.notApplied": "保存没生效:{error}。setConfig 对非法值会忽略并保留原值 —— 请重试,或看 DSH 启动日志。",
4090
+ "archive.notice.modeSet": "自动归档已设为「{mode}」",
4091
+ "archive.err.modeStill": "配置里仍是「{mode}」",
4092
+ "archive.unknown": "读不出来",
4093
+ "archive.err.thresholdBad": "{label}的阈值要是 1~3650 之间的天数(现在是「{value}」)",
4094
+ "archive.notice.thresholdsSaved": "归档条件已保存",
4095
+ "archive.err.thresholdStill": "{label} 仍是 {value}",
3882
4096
  };
3883
4097
 
3884
4098
  const EN = {
@@ -4143,6 +4357,220 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4143
4357
  "notice.conflictDismissed": "Marked as decided — no memory file was changed",
4144
4358
  "notice.conflictUndone": "Undid the invalidation of {file} — it takes part in injection again",
4145
4359
  "notice.conflictRemoved": "Deleted the conflict record (no memory file was touched)",
4360
+
4361
+ // ── 2.1.0: autodream tab (60-autodream-panel.js) ──
4362
+ "ad.tab.run": "Run",
4363
+ "ad.tab.settings": "Settings",
4364
+ "ad.tab.records": "Records",
4365
+ "ad.title": "Autodream",
4366
+ "ad.switch.onTitle": "Click to disable",
4367
+ "ad.switch.offTitle": "Click to enable",
4368
+ "ad.switch.aria": "Enable or disable Autodream",
4369
+ "ad.status.running": "Running · {phase}",
4370
+ "ad.status.preparing": "preparing",
4371
+ "ad.status.idle": "Enabled · idle",
4372
+ "ad.status.off": "Disabled",
4373
+ "ad.note.what": "Read the whole memory directory: merge duplicates, drop what has been overruled, fix the index and broken links, and turn relative dates into absolute ones.",
4374
+ "ad.row.trigger": "Trigger",
4375
+ "ad.trigger.manual": "Manual only",
4376
+ "ad.trigger.auto": "Run when conditions are met",
4377
+ "ad.row.apply": "Changes",
4378
+ "ad.apply.report": "Report only",
4379
+ "ad.apply.apply": "Rewrite directly (snapshot first)",
4380
+ "ad.row.source": "Input",
4381
+ "ad.source.memory": "Memory directory only",
4382
+ "ad.source.sessions": "Memory + session logs",
4383
+ "ad.note.sourceSessions": "Session logs are unpacked for a targeted search — private conversations enter the model context, and token cost is higher.",
4384
+ "ad.note.sourceMemory": "Only the memory files themselves; session logs are never touched.",
4385
+ "ad.note.applySnapshot": "Before rewriting, the whole memory directory is snapshotted (under .sage-mem/autodream/snapshots/, one level above memory), so a bad run can be rolled back.",
4386
+ "ad.note.reportOnly": "Read-only mode: this run does not even give the model write tools, so it cannot change any file.",
4387
+ "ad.row.model": "Model",
4388
+ "ad.model.followDefault": "Follow the current session's default model",
4389
+ "ad.model.defaultRoute": " ({provider} / {model})",
4390
+ "ad.model.currentOutOfCatalog": " (set in config, not in the catalog)",
4391
+ "ad.model.placeholder.provider": "provider (blank = follow default)",
4392
+ "ad.model.placeholder.model": "model (blank = follow default)",
4393
+ "ad.model.saved": "Model saved",
4394
+ "ad.action.save": "Save",
4395
+ "ad.action.manualEntry": "Enter manually",
4396
+ "ad.loadingModels": "Loading the model catalog…",
4397
+ "ad.route.bad": "Effective route: unavailable — {error}",
4398
+ "ad.route.badFallback": "the host could not resolve provider / model (config is blank and no session default is available)",
4399
+ "ad.route.ok": "Effective route: {provider} / {model}",
4400
+ "ad.route.fromDefault": " (following default)",
4401
+ "ad.route.fromConfig": " (from config)",
4402
+ "ad.action.runningNow": "Organizing…",
4403
+ "ad.action.runNow": "Organize now",
4404
+ "ad.run.steps": "Ran {count} rounds · {phase}",
4405
+ "ad.run.hoursSince": "Last organized {hours} hours ago",
4406
+ "ad.run.never": "Never organized",
4407
+ "ad.run.progress": "Round {step}: {tools}",
4408
+ "ad.run.stepDone": "done",
4409
+ "ad.toolSep": ", ",
4410
+ "ad.errSep": "; ",
4411
+ "ad.progressSep": " · ",
4412
+ "ad.last.prefix": "Last {at} · {result}",
4413
+ "ad.last.ok": "succeeded",
4414
+ "ad.last.applied": ", rewrote {count} files",
4415
+ "ad.last.reportOnly": " (report only)",
4416
+ "ad.last.problems": " · structural issues {before} → {after}",
4417
+ "ad.last.tokens": " · tokens {in} / {out}",
4418
+ "ad.last.failed": " failed: {error}",
4419
+ "ad.unknown": "unknown",
4420
+ "ad.last.truncated": " · ⚠ report was truncated",
4421
+ "ad.last.retried": " · completed after {count} zero-body retries",
4422
+ "ad.last.continuations": " · {count} continuation rounds",
4423
+ "ad.last.report": " · report {name}",
4424
+ "ad.sidebar.title": "Sidebar entry",
4425
+ "ad.sidebar.on": "On",
4426
+ "ad.sidebar.off": "Off",
4427
+ "ad.sidebar.note": "Show the “Memory management” icon in the DSH sidebar; clicking it switches the main area to this page. On by default; turning it off keeps the Settings → Memory management entry. Changes take effect immediately, no restart needed.",
4428
+ "ad.sidebar.label": "Show the “Memory management” entry in the sidebar",
4429
+ "ad.limits.title": "Injection limits",
4430
+ "ad.limits.pending": "changes pending",
4431
+ "ad.limits.restartWarn": "⚠ These values are fixed at DSH startup — restart DSH for them to apply. Each row below shows “current → after restart”.",
4432
+ "ad.limits.restartNote": "These values are fixed at DSH startup: saving requires a DSH restart to take effect — so each row shows both “currently effective” and “after restart”.",
4433
+ "ad.limit.maxResults": "Memories per injection",
4434
+ "ad.limit.maxChars": "Characters per memory",
4435
+ "ad.limit.maxBaseline": "baseline slots",
4436
+ "ad.limit.maxSessionBytes": "Bytes per session",
4437
+ "ad.limit.staleDays": "Freshness threshold",
4438
+ "ad.unit.items": "items",
4439
+ "ad.unit.chars": "chars",
4440
+ "ad.unit.bytes": "bytes",
4441
+ "ad.unit.days": "days",
4442
+ "ad.unit.copies": "copies",
4443
+ "ad.limit.labelTitle": "{unit} · range {lo}–{hi}",
4444
+ "ad.limit.inputTitle": "range {lo}–{hi} {unit}",
4445
+ "ad.limit.diff": "current {current} → after restart {pending}",
4446
+ "ad.limit.range": "range {lo}–{hi}",
4447
+ "ad.limit.current": " · currently {current}",
4448
+ "ad.action.saving": "Saving…",
4449
+ "ad.action.saveLimits": "Save injection limits",
4450
+ "ad.limits.saveHint": "Requires a DSH restart to take effect",
4451
+ "ad.reserved.title": "Reserved-name protection",
4452
+ "ad.reserved.subtitle": "Files in this list never take part in injection",
4453
+ "ad.reserved.note": "The two built-in names are fixed and cannot be removed; you can add your own below (file name only, no path). Requires a DSH restart to take effect.",
4454
+ "ad.reserved.fixedSuffix": " · fixed",
4455
+ "ad.reserved.fixedTitle": "Built-in and fixed; cannot be removed",
4456
+ "ad.reserved.removeTitle": "Remove {name} from the list",
4457
+ "ad.reserved.empty": "(no reserved names of your own yet)",
4458
+ "ad.reserved.placeholder": "e.g. project_notes.md (must be a .md file name)",
4459
+ "ad.action.addToList": "Add to list",
4460
+ "ad.advanced": "Advanced",
4461
+ "ad.adv.autoTrigger": "Auto trigger",
4462
+ "ad.adv.since": "At least",
4463
+ "ad.adv.hoursAndSessions": "hours since last run, and at least",
4464
+ "ad.adv.sessionsUpdated": "sessions with updates meanwhile",
4465
+ "ad.adv.maxSteps": "Max rounds",
4466
+ "ad.adv.stepNote": "one round = one model call plus the tool calls it makes",
4467
+ "ad.adv.maxOutput": "Max output per call",
4468
+ "ad.adv.maxOutputNote": "tokens (thinking and body share this budget; raise it first when reports get truncated)",
4469
+ "ad.adv.keepSnapshots": "Keep snapshots",
4470
+ "ad.adv.paths": "Config and state: {path}",
4471
+ "ad.records.snapshots": "Rollback points",
4472
+ "ad.records.countSnaps": "{count}",
4473
+ "ad.action.refresh": "Refresh",
4474
+ "ad.row.rollbackScope": "Rollback scope",
4475
+ "ad.scope.files": "Restore only the files this run touched",
4476
+ "ad.scope.all": "Return the whole directory to that moment",
4477
+ "ad.note.rollback": "Before rolling back, the whole memory directory is snapshotted again as a protection copy — so the rollback itself can be undone. Extra files are only moved into archive/, never deleted.",
4478
+ "ad.snap.empty": "No rollback points yet — run once with “Rewrite directly” and one appears.",
4479
+ "ad.snap.legacyLabel": "Legacy snapshot",
4480
+ "ad.snap.runLabel": "Run {id}",
4481
+ "ad.snap.protectionLabel": "Rollback protection",
4482
+ "ad.snap.meta": "{at} · {files} files",
4483
+ "ad.snap.legacyBadge": "Legacy",
4484
+ "ad.action.rollback": "Roll back",
4485
+ "ad.snap.confirmTitle": "Roll back to {at}?",
4486
+ "ad.snap.confirmScope": "Scope: {scope}",
4487
+ "ad.snap.scopeAll": "Return the whole directory to that moment — files that exist now but not in the snapshot are moved into archive/ (not deleted)",
4488
+ "ad.snap.scopeFiles": "Restore only the files that run touched — files created by it move into archive/, files it archived move back to the top level",
4489
+ "ad.snap.confirmSuffix": ". A protection snapshot is taken first, so this step can itself be undone.",
4490
+ "ad.action.rollingBack": "Rolling back…",
4491
+ "ad.action.confirmRollback": "Confirm rollback",
4492
+ "ad.action.cancel": "Cancel",
4493
+ "ad.rb.rolledBack": "Rolled back to {at}: restored {restored} files, moved away {parked}",
4494
+ "ad.rb.skipped": ", skipped {count} (see the rollback report)",
4495
+ "ad.rb.protection": " · protection snapshot {id}",
4496
+ "ad.rb.report": " · report {name}",
4497
+ "ad.records.runs": "Organization records",
4498
+ "ad.records.countRuns": "{count}",
4499
+ "ad.records.runsNote": "Every run leaves a declaration: what changed, why, and which ideas were rejected. Click one to read the full text.",
4500
+ "ad.records.runsEmpty": "No organization records yet — run once and one appears.",
4501
+ "ad.badge.apply": "Rewritten",
4502
+ "ad.badge.report": "Report only",
4503
+ "ad.run.changes": "{count} changes",
4504
+ "ad.run.reportName": "report {name}",
4505
+ "ad.run.rolledBack": "Rolled back",
4506
+ "ad.run.rolledBackAt": "Rolled back · {at}",
4507
+ "ad.action.collapse": "Collapse",
4508
+ "ad.action.showDeclaration": "Show declaration",
4509
+ "ad.records.reports": "Past reports",
4510
+ "ad.records.countReports": "{count}",
4511
+ "ad.records.reportsEmpty": "No reports yet — run once and one appears. Reports live under memory/autodream/ and never mix into the memory list.",
4512
+ "ad.action.view": "View",
4513
+ "ad.loading": "Loading…",
4514
+ "ad.empty": "(empty)",
4515
+ "ad.emptyDeclaration": "(that run left no declaration body)",
4516
+ "ad.err.readFailed": "Read failed: {error}",
4517
+ "ad.err.rollbackFailed": "Rollback failed: {error}",
4518
+ "ad.unknownReason": "unknown reason",
4519
+ "ad.err.startFailed": "Failed to start",
4520
+ "ad.notice.started": "Organization started — progress refreshes every 2 seconds",
4521
+ "ad.err.remoteUnavailable": "remote.autodream is unavailable — the host side of the plugin may have failed to load; check the DSH startup log",
4522
+ "ad.err.noInterface": "The host has no {name} API (older version?)",
4523
+ "ad.err.noSetSettings": "The host has no setSettings API yet (older plugin version)",
4524
+ "ad.err.limitInteger": "{label} must be an integer",
4525
+ "ad.err.limitRange": "{label} must be between {lo}–{hi}",
4526
+ "ad.err.notSaved": "Not saved — {detail}",
4527
+ "ad.err.saveRejected": "Save rejected",
4528
+ "ad.notice.limitsSaved": "Saved — restart DSH for it to take effect",
4529
+ "ad.notice.sidebarOn": "Sidebar entry enabled (takes effect immediately)",
4530
+ "ad.notice.sidebarOff": "Sidebar entry disabled (the Settings entry remains)",
4531
+ "ad.err.reservedRejected": "Reserved-name list rejected",
4532
+ "ad.notice.reservedSaved": "Reserved-name list saved — restart DSH for it to take effect",
4533
+ "ad.err.reservedNeedName": "Enter a file name first, e.g. project_notes.md",
4534
+ "ad.err.reservedBadName": "A reserved name must be a .md file name (no path): {name}",
4535
+ "ad.err.reservedDup": "{name} is already in the list",
4536
+ "ad.note.modelCatalogUnavailable": "The model catalog is unavailable ({error}) — enter provider and model below by hand.",
4537
+
4538
+ // ── 2.1.0: auto-archive settings (62-archive-settings.js) ──
4539
+ "archive.title": "What auto-archive does",
4540
+ "archive.intro1": "Memories that have not been injected for too long are moved into archive/ — never deleted, always recoverable. This page decides how long counts as idle, and whether it may actually act.",
4541
+ "archive.intro2": "It does not call a model: candidates are computed purely from the access ledger's day counts, so the same input always yields the same result.",
4542
+ "archive.intro3": "To see what would be collected right now: Files → “Tools ▾” → Archive candidates.",
4543
+ "archive.modeTitle": "Auto-archive",
4544
+ "archive.mode.off": "Off",
4545
+ "archive.mode.report": "Report only",
4546
+ "archive.mode.auto": "Run when conditions are met",
4547
+ "archive.cost.off": "Off: no file is touched — candidates are only listed, waiting for you to act.",
4548
+ "archive.cost.report": "Report only: candidates go into the organization report, no file is touched. The default and most conservative option.",
4549
+ "archive.cost.auto": "Run when conditions are met: candidates are really moved into archive/ (moved, not deleted; recoverable at any time); if “Changes” is set to report only, this option degrades to report only.",
4550
+ "archive.degrade": "Note: “Autodream → Changes” is currently report only — this option degrades to report only and will not touch any file.",
4551
+ "archive.th.title": "How many idle days before a memory may be archived",
4552
+ "archive.th.project": "Project",
4553
+ "archive.th.reference": "Reference",
4554
+ "archive.th.user": "User",
4555
+ "archive.th.hintProject": "project_* not injected for this many days in a row → candidate",
4556
+ "archive.th.hintReference": "reference_* same (documents usually outlive projects)",
4557
+ "archive.th.hintUser": "user_* same (be extra careful with persona-type memories)",
4558
+ "archive.loadingConfig": "Reading config…",
4559
+ "archive.action.save": "Save archive conditions",
4560
+ "archive.action.saving": "Saving…",
4561
+ "archive.note.immediate": "Takes effect immediately, no DSH restart needed",
4562
+ "archive.note.thresholds": "Days are counted from the access ledger (when this memory was last injected to the model). feedback_* is never selected; locked memories are never selected either.",
4563
+ "archive.err.noConfig": "The host has no autodream config API yet (older plugin version)",
4564
+ "archive.err.readConfig": "Could not read the Autodream config",
4565
+ "archive.err.readFailed": "Failed to read config: {error}",
4566
+ "archive.err.saveFailed": "Save failed: {error}",
4567
+ "archive.err.notApplied": "Save did not take effect: {error}. setConfig ignores invalid values and keeps the old ones — try again, or check the DSH startup log.",
4568
+ "archive.notice.modeSet": "Auto-archive set to “{mode}”",
4569
+ "archive.err.modeStill": "Config still says “{mode}”",
4570
+ "archive.unknown": "unreadable",
4571
+ "archive.err.thresholdBad": "{label} threshold must be between 1 and 3650 days (currently “{value}”)",
4572
+ "archive.notice.thresholdsSaved": "Archive conditions saved",
4573
+ "archive.err.thresholdStill": "{label} is still {value}",
4146
4574
  };
4147
4575
 
4148
4576
  /**
@@ -4272,7 +4700,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4272
4700
  const sidebarEntryOn = !(memSet && memSet.ui && memSet.ui.sidebarEntry === false);
4273
4701
 
4274
4702
  react.useEffect(function () {
4275
- if (!remote) { setErr("remote.autodream 不可用 —— 插件的 宿主侧可能没加载成功,看 DSH 启动日志"); return undefined; }
4703
+ if (!remote) { setErr(t("ad.err.remoteUnavailable")); return undefined; }
4276
4704
  let alive = true;
4277
4705
  function pull() {
4278
4706
  callRemote("status", [], function (d) {
@@ -4312,7 +4740,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4312
4740
  */
4313
4741
  function callRemote(name, args, onOk, onErr) {
4314
4742
  const fn = remote ? remote[name] : null;
4315
- if (typeof fn !== "function") { if (onErr) onErr("宿主侧没有 " + name + " 接口(版本较旧?)"); return; }
4743
+ if (typeof fn !== "function") { if (onErr) onErr(t("ad.err.noInterface", { name: name })); return; }
4316
4744
  let p;
4317
4745
  try { p = fn.apply(remote, args || []); }
4318
4746
  catch (e) { if (onErr) onErr(detail(e)); return; }
@@ -4330,12 +4758,14 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4330
4758
  // ── 注入设置:5 项上限 + 保留名名单 ─────────────────────────────
4331
4759
  // 边界与 host 的 LIMIT_SPECS 一一对应(那边是权威,这里只是提前拦明显笔误)。
4332
4760
  const LIMIT_FIELDS = [
4333
- { key: "maxResults", label: "一次注入条数", lo: 1, hi: 30, unit: "条" },
4334
- { key: "maxChars", label: "单条字符上限", lo: 200, hi: 20000, unit: "字符" },
4335
- { key: "maxBaseline", label: "baseline 名额", lo: 1, hi: 50, unit: "条" },
4336
- { key: "maxSessionBytes", label: "单会话字节预算", lo: 4096, hi: 524288, unit: "字节" },
4337
- { key: "staleDays", label: "新鲜度门槛", lo: 0, hi: 365, unit: "天" },
4761
+ { key: "maxResults", labelKey: "ad.limit.maxResults", lo: 1, hi: 30, unitKey: "ad.unit.items" },
4762
+ { key: "maxChars", labelKey: "ad.limit.maxChars", lo: 200, hi: 20000, unitKey: "ad.unit.chars" },
4763
+ { key: "maxBaseline", labelKey: "ad.limit.maxBaseline", lo: 1, hi: 50, unitKey: "ad.unit.items" },
4764
+ { key: "maxSessionBytes", labelKey: "ad.limit.maxSessionBytes", lo: 4096, hi: 524288, unitKey: "ad.unit.bytes" },
4765
+ { key: "staleDays", labelKey: "ad.limit.staleDays", lo: 0, hi: 365, unitKey: "ad.unit.days" },
4338
4766
  ];
4767
+ const limitLabel = function (f) { return t(f.labelKey); };
4768
+ const limitUnit = function (f) { return t(f.unitKey); };
4339
4769
 
4340
4770
  /** 把一份 limits 对象摊成输入框用的字符串草稿(输入期间允许非法,保存时再校验)。 */
4341
4771
  function limitsDraftOf(src) {
@@ -4356,27 +4786,27 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4356
4786
 
4357
4787
  /** 保存 5 项上限。越界/非整数**本地先拦**(host 也是直接拒绝),别让它白跑一趟。 */
4358
4788
  function saveLimits() {
4359
- if (!memRemote || typeof memRemote.setSettings !== "function") { setErr("宿主侧还没有 setSettings 接口(插件版本较旧)"); return; }
4789
+ if (!memRemote || typeof memRemote.setSettings !== "function") { setErr(t("ad.err.noSetSettings")); return; }
4360
4790
  const draft = limitsDraft || {};
4361
4791
  const limits = {};
4362
4792
  const bad = [];
4363
4793
  for (const f of LIMIT_FIELDS) {
4364
4794
  const raw = String(draft[f.key] == null ? "" : draft[f.key]).trim();
4365
4795
  const n = Number(raw);
4366
- if (raw === "" || !Number.isFinite(n) || !Number.isInteger(n)) { bad.push(f.label + "要整数"); continue; }
4367
- if (n < f.lo || n > f.hi) { bad.push(f.label + " 要在 " + f.lo + "–" + f.hi + " 之间"); continue; }
4796
+ if (raw === "" || !Number.isFinite(n) || !Number.isInteger(n)) { bad.push(t("ad.err.limitInteger", { label: limitLabel(f) })); continue; }
4797
+ if (n < f.lo || n > f.hi) { bad.push(t("ad.err.limitRange", { label: limitLabel(f), lo: f.lo, hi: f.hi })); continue; }
4368
4798
  limits[f.key] = n;
4369
4799
  }
4370
- if (bad.length) { setErr("没保存 —— " + bad.join(";")); return; }
4800
+ if (bad.length) { setErr(t("ad.err.notSaved", { detail: bad.join(t("ad.errSep")) })); return; }
4371
4801
  setSaveBusy(true); setErr(null); setNotice("");
4372
4802
  Promise.resolve(memRemote.setSettings({ limits: limits })).then(function (out) {
4373
4803
  setSaveBusy(false);
4374
4804
  // setSettings 是 {ok:true, settings, restartRequired} 形状:不要过 unwrap
4375
4805
  // (unwrap 只认 {ok, value},这里会把 settings 吃掉)。
4376
- if (out && out.ok === false) { setErr(out.error || "保存被拒绝"); return; }
4806
+ if (out && out.ok === false) { setErr(out.error || t("ad.err.saveRejected")); return; }
4377
4807
  if (out && out.settings) { setMemSet(out.settings); setLimitsDraft(limitsDraftOf(out.settings.pendingLimits)); }
4378
4808
  else loadMemSettings();
4379
- setNotice("已保存 —— 需重启 DSH 才生效");
4809
+ setNotice(t("ad.notice.limitsSaved"));
4380
4810
  }, function (e) { setSaveBusy(false); setErr(detail(e)); });
4381
4811
  }
4382
4812
 
@@ -4389,7 +4819,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4389
4819
  */
4390
4820
  function saveSidebarEntry(next) {
4391
4821
  if (!memRemote || typeof memRemote.setSettings !== "function") {
4392
- setSidebarErr("宿主侧还没有 setSettings 接口(插件版本较旧)");
4822
+ setSidebarErr(t("ad.err.noSetSettings"));
4393
4823
  return;
4394
4824
  }
4395
4825
  if (sidebarBusy) return;
@@ -4397,11 +4827,11 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4397
4827
  Promise.resolve(memRemote.setSettings({ ui: { sidebarEntry: !!next } })).then(function (out) {
4398
4828
  setSidebarBusy(false);
4399
4829
  // 与 saveLimits 同款:setSettings 是 {ok, settings, restartRequired} 形状,不要过 unwrap。
4400
- if (out && out.ok === false) { setSidebarErr(out.error || "保存被拒绝"); return; }
4830
+ if (out && out.ok === false) { setSidebarErr(out.error || t("ad.err.saveRejected")); return; }
4401
4831
  if (out && out.settings) setMemSet(out.settings);
4402
4832
  else loadMemSettings();
4403
4833
  setSidebarEntry(!!next);
4404
- setNotice(!!next ? "侧栏入口已开启(立即生效)" : "侧栏入口已关闭(设置页入口仍在)");
4834
+ setNotice(!!next ? t("ad.notice.sidebarOn") : t("ad.notice.sidebarOff"));
4405
4835
  }, function (e) { setSidebarBusy(false); setSidebarErr(detail(e)); });
4406
4836
  }
4407
4837
 
@@ -4412,28 +4842,28 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4412
4842
  * 那是生效清单里固定的两个,删不掉。这里管的只是用户自己加的那些。
4413
4843
  */
4414
4844
  function saveReserved(list) {
4415
- if (!memRemote || typeof memRemote.setSettings !== "function") { setErr("宿主侧还没有 setSettings 接口(插件版本较旧)"); return; }
4845
+ if (!memRemote || typeof memRemote.setSettings !== "function") { setErr(t("ad.err.noSetSettings")); return; }
4416
4846
  setSaveBusy(true); setErr(null); setNotice("");
4417
4847
  Promise.resolve(memRemote.setSettings({ reserved: list })).then(function (out) {
4418
4848
  setSaveBusy(false);
4419
- if (out && out.ok === false) { setErr(out.error || "名单被拒绝"); return; }
4849
+ if (out && out.ok === false) { setErr(out.error || t("ad.err.reservedRejected")); return; }
4420
4850
  if (out && out.settings) setMemSet(out.settings);
4421
4851
  else loadMemSettings();
4422
- setNotice("保留名名单已保存 —— 需重启 DSH 才生效");
4852
+ setNotice(t("ad.notice.reservedSaved"));
4423
4853
  }, function (e) { setSaveBusy(false); setErr(detail(e)); });
4424
4854
  }
4425
4855
 
4426
4856
  function addReserved() {
4427
4857
  const name = String(reservedNew || "").trim();
4428
4858
  const extra = Array.isArray(memSet && memSet.reservedExtra) ? memSet.reservedExtra : [];
4429
- if (!name) { setErr("先写一个文件名,例如 project_notes.md"); return; }
4859
+ if (!name) { setErr(t("ad.err.reservedNeedName")); return; }
4430
4860
  // 与 host 的校验同一套:单个 .md 文件名、不许带路径
4431
4861
  if (!name.endsWith(".md") || name.includes("/") || name.includes("\\") || name.startsWith("..")) {
4432
- setErr("保留名要是一个 .md 文件名(不能带路径):" + name);
4862
+ setErr(t("ad.err.reservedBadName", { name: name }));
4433
4863
  return;
4434
4864
  }
4435
4865
  if (extra.some(function (x) { return String(x).toLowerCase() === name.toLowerCase(); })) {
4436
- setErr("名单里已经有 " + name + " 了");
4866
+ setErr(t("ad.err.reservedDup", { name: name }));
4437
4867
  return;
4438
4868
  }
4439
4869
  setReservedNew("");
@@ -4462,7 +4892,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4462
4892
  callRemote("listModels", [], function (d) {
4463
4893
  setModels(d && typeof d === "object" && !Array.isArray(d) ? d : { catalogAvailable: false, routes: [], note: "" });
4464
4894
  }, function (m) {
4465
- setModels({ catalogAvailable: false, routes: [], note: "模型目录不可用(" + m + ")—— 请在下面手填 provider 与 model。" });
4895
+ setModels({ catalogAvailable: false, routes: [], note: t("ad.note.modelCatalogUnavailable", { error: m }) });
4466
4896
  });
4467
4897
  }
4468
4898
 
@@ -4471,27 +4901,27 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4471
4901
  // 契约:runNow({ reason })。reason 只落进 manifest 给人看;门控的豁免
4472
4902
  // 由 宿主侧负责(手动点了按钮就不该被「还没到 N 小时」挡回来)。
4473
4903
  callRemote("runNow", [{ reason: "设置页手动触发" }], function (d) {
4474
- if (d && d.ok === false) setErr(d.error || "启动失败");
4475
- else { setNotice("已开始整理 —— 进度每 2 秒刷新一次"); refreshLists(); }
4904
+ if (d && d.ok === false) setErr(d.error || t("ad.err.startFailed"));
4905
+ else { setNotice(t("ad.notice.started")); refreshLists(); }
4476
4906
  setBusy(false);
4477
4907
  }, function (m) { setErr(m); setBusy(false); });
4478
4908
  }
4479
4909
 
4480
4910
  function openReport(name) {
4481
4911
  if (name === null) { setOpen(null); setBody(""); return; }
4482
- setOpen(name); setBody("载入中…");
4912
+ setOpen(name); setBody(t("ad.loading"));
4483
4913
  callRemote("readReport", [name], function (d) {
4484
- setBody((d && d.content) || "(空)");
4485
- }, function (m) { setBody("读取失败:" + m); });
4914
+ setBody((d && d.content) || t("ad.empty"));
4915
+ }, function (m) { setBody(t("ad.err.readFailed", { error: m })); });
4486
4916
  }
4487
4917
 
4488
4918
  /** 整理声明:点开才拉,拉回来的是 Markdown 原文,直接用 pre 呈现。 */
4489
4919
  function openDecl(runId) {
4490
4920
  if (declOpen === runId) { setDeclOpen(null); setDeclBody(""); return; }
4491
- setDeclOpen(runId); setDeclBody("载入中…");
4921
+ setDeclOpen(runId); setDeclBody(t("ad.loading"));
4492
4922
  callRemote("readDeclaration", [{ runId: runId }], function (d) {
4493
- setDeclBody((d && d.markdown) || "(这一趟没有留下声明正文)");
4494
- }, function (m) { setDeclBody("读取失败:" + m); });
4923
+ setDeclBody((d && d.markdown) || t("ad.emptyDeclaration"));
4924
+ }, function (m) { setDeclBody(t("ad.err.readFailed", { error: m })); });
4495
4925
  }
4496
4926
 
4497
4927
  /**
@@ -4504,26 +4934,31 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4504
4934
  callRemote("rollback", [{ snapshotId: sp.name, scope: scope }], function (d) {
4505
4935
  const x = d || {};
4506
4936
  if (x.ok === false) {
4507
- setRbMsg({ bad: true, text: "回滚失败:" + (x.error || "未知原因") });
4937
+ setRbMsg({ bad: true, text: t("ad.err.rollbackFailed", { error: x.error || t("ad.unknownReason") }) });
4508
4938
  } else {
4509
- setRbMsg({ bad: false, text: "已回滚到 " + String(sp.atHuman || "") + ":恢复 " +
4510
- (x.restored == null ? "?" : x.restored) + " 个文件,移走 " + (x.parked == null ? "?" : x.parked) + " 个" +
4939
+ setRbMsg({ bad: false, text: t("ad.rb.rolledBack", {
4940
+ at: String(sp.atHuman || ""),
4941
+ restored: (x.restored == null ? "?" : x.restored),
4942
+ parked: (x.parked == null ? "?" : x.parked),
4943
+ }) +
4511
4944
  // skipped = 想动但没动的(archive/ 已有同名、或回滚点里没那一版)。
4512
4945
  // 不显示它等于「部分成功」被说成「成功」——回滚这种事不能含糊。
4513
- (x.skipped ? ",跳过 " + x.skipped + " 个(详见回滚报告)" : "") +
4514
- (x.protection ? " · 保护快照 " + x.protection : "") + (x.report ? " · 报告 " + x.report : "") });
4946
+ (x.skipped ? t("ad.rb.skipped", { count: x.skipped }) : "") +
4947
+ (x.protection ? t("ad.rb.protection", { id: x.protection }) : "") +
4948
+ (x.report ? t("ad.rb.report", { name: x.report }) : "") });
4515
4949
  setConfirmSnap(null);
4516
4950
  }
4517
4951
  setRbBusy(null);
4518
4952
  refreshLists();
4519
- }, function (m) { setRbMsg({ bad: true, text: "回滚失败:" + m }); setRbBusy(null); });
4953
+ }, function (m) { setRbMsg({ bad: true, text: t("ad.err.rollbackFailed", { error: m }) }); setRbBusy(null); });
4520
4954
  }
4521
4955
 
4522
4956
  // 实际生效路线(契约:route 可能为 null;source==='none' 时 error 里是原因)。
4523
4957
  const routeBad = !route || route.source === "none" || !route.provider || !route.model;
4524
4958
  const routeInfo = routeBad
4525
- ? ("实际生效路线:拿不到 —— " + ((route && route.error) || "宿主没能解析出 provider / model(配置留空且取不到会话默认模型)"))
4526
- : ("实际生效路线:" + route.provider + " / " + route.model + (route.fromDefault ? "(跟随默认)" : "(配置指定)"));
4959
+ ? t("ad.route.bad", { error: (route && route.error) || t("ad.route.badFallback") })
4960
+ : t("ad.route.ok", { provider: route.provider, model: route.model }) +
4961
+ (route.fromDefault ? t("ad.route.fromDefault") : t("ad.route.fromConfig"));
4527
4962
 
4528
4963
  // 模型 <select> 的取值:''=跟随默认;否则是 "provider::model" 的 JSON 编码。
4529
4964
  // 配置里指定的路线万一下架了(不在目录里),也要让它显示出来 —— 否则
@@ -4547,9 +4982,9 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4547
4982
 
4548
4983
  return h("div", { className: "smem-autodream" }, [
4549
4984
  h("div", { className: "smem-subtabs", key: "segs" }, [
4550
- subtab("run", "运行"),
4551
- subtab("settings", "设置"),
4552
- subtab("records", "记录", String(snaps.length + runs.length + reports.length)),
4985
+ subtab("run", t("ad.tab.run")),
4986
+ subtab("settings", t("ad.tab.settings")),
4987
+ subtab("records", t("ad.tab.records"), String(snaps.length + runs.length + reports.length)),
4553
4988
  ]),
4554
4989
  segOf === "run" ? h("div", { key: "seg-run" }, [
4555
4990
  h("div", { className: "smem-autodream-sec", key: "top" }, [
@@ -4557,54 +4992,53 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4557
4992
  h("button", {
4558
4993
  type: "button", key: "sw", className: "smem-switch",
4559
4994
  "data-on": c.enabled ? "1" : "0",
4560
- title: c.enabled ? "点击停用" : "点击启用",
4561
- "aria-label": "启用或停用自动做梦",
4995
+ title: c.enabled ? t("ad.switch.onTitle") : t("ad.switch.offTitle"),
4996
+ "aria-label": t("ad.switch.aria"),
4562
4997
  onClick: function () { patch({ enabled: !c.enabled }); },
4563
4998
  }),
4564
- h("span", { key: "t", style: { fontWeight: "600" } }, "自动做梦"),
4999
+ h("span", { key: "t", style: { fontWeight: "600" } }, t("ad.title")),
4565
5000
  h("span", { className: "smem-status", key: "s" },
4566
- running ? ("运行中 · " + String((st && st.phase) || "准备中")) : (c.enabled ? "已启用 · 空闲" : "未启用")),
5001
+ running ? t("ad.status.running", { phase: String((st && st.phase) || t("ad.status.preparing")) }) : (c.enabled ? t("ad.status.idle") : t("ad.status.off"))),
4567
5002
  ]),
4568
- h("div", { className: "smem-autodream-note", key: "n" },
4569
- "把记忆目录通读一遍:合并重复的、删掉被推翻的、修索引与断链、把相对日期换成绝对日期。"),
5003
+ h("div", { className: "smem-autodream-note", key: "n" }, t("ad.note.what")),
4570
5004
  ]),
4571
5005
  h("div", { className: "smem-autodream-sec", key: "mode" }, [
4572
5006
  h("div", { className: "smem-autodream-row", key: "a" }, [
4573
- h("span", { className: "smem-autodream-key", key: "k" }, "触发"),
5007
+ h("span", { className: "smem-autodream-key", key: "k" }, t("ad.row.trigger")),
4574
5008
  h(Seg, {
4575
5009
  key: "v", value: c.trigger,
4576
- options: [{ value: "manual", label: "只手动" }, { value: "auto", label: "满足条件自动跑" }],
5010
+ options: [{ value: "manual", label: t("ad.trigger.manual") }, { value: "auto", label: t("ad.trigger.auto") }],
4577
5011
  onChange: function (v) { patch({ trigger: v }); },
4578
5012
  }),
4579
5013
  ]),
4580
5014
  h("div", { className: "smem-autodream-row", key: "b" }, [
4581
- h("span", { className: "smem-autodream-key", key: "k" }, "改动方式"),
5015
+ h("span", { className: "smem-autodream-key", key: "k" }, t("ad.row.apply")),
4582
5016
  h(Seg, {
4583
5017
  key: "v", value: c.apply ? "apply" : "report",
4584
- options: [{ value: "report", label: "只出报告" }, { value: "apply", label: "直接改写(先快照)" }],
5018
+ options: [{ value: "report", label: t("ad.apply.report") }, { value: "apply", label: t("ad.apply.apply") }],
4585
5019
  onChange: function (v) { patch({ apply: v === "apply" }); },
4586
5020
  }),
4587
5021
  ]),
4588
5022
  h("div", { className: "smem-autodream-row", key: "c" }, [
4589
- h("span", { className: "smem-autodream-key", key: "k" }, "输入源"),
5023
+ h("span", { className: "smem-autodream-key", key: "k" }, t("ad.row.source")),
4590
5024
  h(Seg, {
4591
5025
  key: "v", value: c.source,
4592
- options: [{ value: "memory", label: "仅记忆目录" }, { value: "memory+sessions", label: "记忆 + 会话记录" }],
5026
+ options: [{ value: "memory", label: t("ad.source.memory") }, { value: "memory+sessions", label: t("ad.source.sessions") }],
4593
5027
  onChange: function (v) { patch({ source: v }); },
4594
5028
  }),
4595
5029
  ]),
4596
5030
  h("div", { className: "smem-autodream-note", key: "w" },
4597
5031
  c.source === "memory+sessions"
4598
- ? "会解压会话记录做定向搜索 —— 私密对话会进入模型上下文,token 成本也更高。"
4599
- : "只看记忆文件本身,不碰会话记录。"),
5032
+ ? t("ad.note.sourceSessions")
5033
+ : t("ad.note.sourceMemory")),
4600
5034
  h("div", { className: "smem-autodream-note", key: "w2" },
4601
5035
  c.apply
4602
- ? "改写前会自动把整个记忆目录快照一份(memory 上一层 .sage-mem/autodream/snapshots/),改坏可回退。"
4603
- : "只读模式:这一趟连写工具都不会挂给模型,所以它不可能改到任何文件。"),
5036
+ ? t("ad.note.applySnapshot")
5037
+ : t("ad.note.reportOnly")),
4604
5038
  ]),
4605
5039
  h("div", { className: "smem-autodream-sec", key: "model" }, [
4606
5040
  h("div", { className: "smem-autodream-row", key: "model" }, [
4607
- h("span", { className: "smem-autodream-key", key: "k" }, "模型"),
5041
+ h("span", { className: "smem-autodream-key", key: "k" }, t("ad.row.model")),
4608
5042
  // 有可枚举的模型目录就给 <select>(首项 = 跟随会话默认,值 "":"");
4609
5043
  // 目录不可用就退化成两个文本框(契约第六节第 2 条)。
4610
5044
  (models && models.catalogAvailable)
@@ -4618,11 +5052,11 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4618
5052
  patch({ provider: r.provider, model: r.model });
4619
5053
  },
4620
5054
  }, [h("option", { key: "__def", value: "" },
4621
- "跟随当前会话默认模型" + ((models.defaultRoute && models.defaultRoute.provider && models.defaultRoute.model)
4622
- ? ("(" + String(models.defaultRoute.provider) + " / " + String(models.defaultRoute.model) + ")")
5055
+ t("ad.model.followDefault") + ((models.defaultRoute && models.defaultRoute.provider && models.defaultRoute.model)
5056
+ ? t("ad.model.defaultRoute", { provider: String(models.defaultRoute.provider), model: String(models.defaultRoute.model) })
4623
5057
  : ""),
4624
5058
  )].concat(
4625
- modelValueInCatalog ? [] : [h("option", { key: "__cur", value: modelValue }, String(c.provider) + " / " + String(c.model) + "(配置里指定,不在目录中)")],
5059
+ modelValueInCatalog ? [] : [h("option", { key: "__cur", value: modelValue }, String(c.provider) + " / " + String(c.model) + t("ad.model.currentOutOfCatalog"))],
4626
5060
  modelRoutes.map(function (r) {
4627
5061
  return h("option", { key: String(r.provider) + "/" + String(r.model), value: encodeRoute(r.provider, r.model) },
4628
5062
  r.label || (String(r.provider) + " / " + String(r.model)));
@@ -4630,13 +5064,13 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4630
5064
  ))
4631
5065
  : models
4632
5066
  ? [
4633
- h("input", { className: "smem-input", key: "p", style: { flex: "1", minWidth: "110px" }, value: prov, placeholder: "provider(留空=跟随默认)", onChange: function (e) { setProv(e.target.value); } }),
4634
- h("input", { className: "smem-input", key: "m", style: { flex: "1", minWidth: "110px" }, value: model, placeholder: "model(留空=跟随默认)", onChange: function (e) { setModel(e.target.value); } }),
4635
- h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "ok", onClick: function () { patch({ provider: prov, model: model }); setNotice("模型已保存"); } }, "保存"),
5067
+ h("input", { className: "smem-input", key: "p", style: { flex: "1", minWidth: "110px" }, value: prov, placeholder: t("ad.model.placeholder.provider"), onChange: function (e) { setProv(e.target.value); } }),
5068
+ h("input", { className: "smem-input", key: "m", style: { flex: "1", minWidth: "110px" }, value: model, placeholder: t("ad.model.placeholder.model"), onChange: function (e) { setModel(e.target.value); } }),
5069
+ h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "ok", onClick: function () { patch({ provider: prov, model: model }); setNotice(t("ad.model.saved")); } }, t("ad.action.save")),
4636
5070
  ]
4637
5071
  : [
4638
- h("span", { className: "smem-status", key: "load" }, "模型目录载入中…"),
4639
- h("button", { type: "button", className: "smem-btn", key: "man", onClick: function () { setModels({ catalogAvailable: false, routes: [], note: "" }); } }, "手填"),
5072
+ h("span", { className: "smem-status", key: "load" }, t("ad.loadingModels")),
5073
+ h("button", { type: "button", className: "smem-btn", key: "man", onClick: function () { setModels({ catalogAvailable: false, routes: [], note: "" }); } }, t("ad.action.manualEntry")),
4640
5074
  ],
4641
5075
  ]),
4642
5076
  h("div", {
@@ -4653,29 +5087,32 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4653
5087
  type: "button", className: "smem-btn smem-btn-primary", key: "go",
4654
5088
  disabled: busy || running || !remote,
4655
5089
  onClick: run,
4656
- }, running ? "正在整理…" : "立即整理"),
5090
+ }, running ? t("ad.action.runningNow") : t("ad.action.runNow")),
4657
5091
  h("span", { className: "smem-status", key: "s" },
4658
5092
  running
4659
- ? ("已跑 " + ((st && st.steps && st.steps.length) || 0) + " 轮 · " + String((st && st.phase) || ""))
4660
- : (st && st.hoursSince != null ? ("距上次整理 " + st.hoursSince.toFixed(1) + " 小时") : "从未整理过")),
5093
+ ? t("ad.run.steps", { count: ((st && st.steps && st.steps.length) || 0), phase: String((st && st.phase) || "") })
5094
+ : (st && st.hoursSince != null ? t("ad.run.hoursSince", { hours: st.hoursSince.toFixed(1) }) : t("ad.run.never"))),
4661
5095
  ]),
4662
5096
  running && st && st.steps && st.steps.length
4663
5097
  ? h("div", { className: "smem-autodream-note", key: "prog" }, st.steps.map(function (x) {
4664
- return "第 " + x.step + " 轮:" + (x.tools.length ? x.tools.join("、") : "结束");
4665
- }).join(" · "))
5098
+ return t("ad.run.progress", { step: x.step, tools: (x.tools.length ? x.tools.join(t("ad.toolSep")) : t("ad.run.stepDone")) });
5099
+ }).join(t("ad.progressSep")))
4666
5100
  : null,
4667
5101
  last
4668
5102
  ? h("div", { className: "smem-status", key: "last" },
4669
- "上次 " + String(last.atHuman || "") + " · " + (last.ok
4670
- ? ("成功" + (last.apply ? ",改写了 " + ((last.touched || []).length) + " 个文件" : "(只出报告)") +
4671
- " · 结构问题 " + last.problemsBefore + " → " + last.problemsAfter +
4672
- " · token " + last.tokensIn + " / " + last.tokensOut)
4673
- : ("失败:" + (last.error || "未知"))) +
5103
+ t("ad.last.prefix", {
5104
+ at: String(last.atHuman || ""),
5105
+ result: (last.ok
5106
+ ? (t("ad.last.ok") + (last.apply ? t("ad.last.applied", { count: ((last.touched || []).length) }) : t("ad.last.reportOnly")) +
5107
+ t("ad.last.problems", { before: last.problemsBefore, after: last.problemsAfter }) +
5108
+ t("ad.last.tokens", { in: last.tokensIn, out: last.tokensOut }))
5109
+ : t("ad.last.failed", { error: (last.error || t("ad.unknown")) })),
5110
+ }) +
4674
5111
  // 1.9.2:残篇必须在状态行上就能看见 —— 只写「成功」正是 10-25 那次误判的来源。
4675
- (last.ok && last.truncated ? " · ⚠ 报告被截断(残篇)" : "") +
4676
- (last.ok && !last.truncated && last.retries ? (" · 零正文重试 " + last.retries + " 次后补齐") : "") +
4677
- (last.continuations ? (" · 续写 " + last.continuations + " 轮") : "") +
4678
- (last.report ? " · 报告 " + last.report : ""))
5112
+ (last.ok && last.truncated ? t("ad.last.truncated") : "") +
5113
+ (last.ok && !last.truncated && last.retries ? t("ad.last.retried", { count: last.retries }) : "") +
5114
+ (last.continuations ? t("ad.last.continuations", { count: last.continuations }) : "") +
5115
+ (last.report ? t("ad.last.report", { name: last.report }) : ""))
4679
5116
  : null,
4680
5117
  ]),
4681
5118
  ]) : null,
@@ -4684,11 +5121,10 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4684
5121
  // 「设置 → 记忆管理」这个入口永远在(它注册在 80-apply,和这个开关无关)。
4685
5122
  h("div", { className: "smem-autodream-sec", key: "sidebar-entry" }, [
4686
5123
  h("div", { className: "smem-autodream-row", key: "h" }, [
4687
- h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, "侧栏入口"),
4688
- h("span", { className: "smem-status", key: "s" }, sidebarEntryOn ? "已开启" : "已关闭"),
5124
+ h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, t("ad.sidebar.title")),
5125
+ h("span", { className: "smem-status", key: "s" }, sidebarEntryOn ? t("ad.sidebar.on") : t("ad.sidebar.off")),
4689
5126
  ]),
4690
- h("div", { className: "smem-autodream-note", key: "n" },
4691
- "在 DSH 侧栏显示「记忆管理」图标,点它把中央主区切到本页。默认开启;关闭后「设置 → 记忆管理」这个入口仍然保留。改动立即生效,不需要重启。"),
5127
+ h("div", { className: "smem-autodream-note", key: "n" }, t("ad.sidebar.note")),
4692
5128
  h("label", { className: "smem-check", key: "row" }, [
4693
5129
  h("input", {
4694
5130
  type: "checkbox", key: "c",
@@ -4696,7 +5132,7 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4696
5132
  disabled: sidebarBusy,
4697
5133
  onChange: function (e) { saveSidebarEntry(!!(e.target && e.target.checked)); },
4698
5134
  }),
4699
- h("span", { key: "l" }, "在侧栏显示「记忆管理」入口"),
5135
+ h("span", { key: "l" }, t("ad.sidebar.label")),
4700
5136
  ]),
4701
5137
  sidebarErr ? h("div", { className: "smem-autodream-note smem-ad-warn", key: "err" }, sidebarErr) : null,
4702
5138
  ]),
@@ -4705,27 +5141,25 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4705
5141
  // 同一件事只在一处设定,这里不再重复摆一份。
4706
5142
  h("div", { className: "smem-autodream-sec", key: "limits" }, [
4707
5143
  h("div", { className: "smem-autodream-row", key: "h" }, [
4708
- h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, "记忆注入参数"),
5144
+ h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, t("ad.limits.title")),
4709
5145
  memSet && memSet.restartRequired
4710
- ? h("span", { className: "smem-ad-badge smem-ad-badge--warn", key: "w" }, "有改动待生效")
5146
+ ? h("span", { className: "smem-ad-badge smem-ad-badge--warn", key: "w" }, t("ad.limits.pending"))
4711
5147
  : null,
4712
5148
  ]),
4713
5149
  memSet && memSet.restartRequired
4714
- ? h("div", { className: "smem-autodream-note smem-ad-warn", key: "rw" },
4715
- "⚠ 这些值在 DSH 启动时就固化了 —— 要重启 DSH 才生效。下面每行都写清「当前 → 重启后」。")
4716
- : h("div", { className: "smem-autodream-note", key: "rn" },
4717
- "这些值在 DSH 启动时固化:保存后要重启 DSH 才生效 —— 所以每行都会标出「当前生效」与「重启后」。"),
5150
+ ? h("div", { className: "smem-autodream-note smem-ad-warn", key: "rw" }, t("ad.limits.restartWarn"))
5151
+ : h("div", { className: "smem-autodream-note", key: "rn" }, t("ad.limits.restartNote")),
4718
5152
  // 五个参数排两列(一行两个):同样的信息,行数少一半,扫读反而更清楚。
4719
5153
  h("div", { className: "smem-ad-grid2", key: "grid" }, LIMIT_FIELDS.map(function (f) {
4720
5154
  const cur = memSet && memSet.limits ? memSet.limits[f.key] : null;
4721
5155
  const pend = memSet && memSet.pendingLimits ? memSet.pendingLimits[f.key] : null;
4722
5156
  const diff = cur != null && pend != null && cur !== pend;
4723
5157
  return h("div", { className: "smem-ad-field", key: f.key }, [
4724
- h("span", { className: "smem-autodream-key", key: "k", title: f.unit + " · 范围 " + f.lo + "–" + f.hi }, f.label),
5158
+ h("span", { className: "smem-autodream-key", key: "k", title: t("ad.limit.labelTitle", { unit: limitUnit(f), lo: f.lo, hi: f.hi }) }, limitLabel(f)),
4725
5159
  h("input", {
4726
5160
  className: "smem-num", key: "v", type: "number",
4727
5161
  min: String(f.lo), max: String(f.hi),
4728
- title: "范围 " + f.lo + "–" + f.hi + " " + f.unit,
5162
+ title: t("ad.limit.inputTitle", { lo: f.lo, hi: f.hi, unit: limitUnit(f) }),
4729
5163
  value: limitsDraft && limitsDraft[f.key] != null ? limitsDraft[f.key] : "",
4730
5164
  onChange: function (e) {
4731
5165
  const nx = Object.assign({}, limitsDraft);
@@ -4734,73 +5168,72 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4734
5168
  },
4735
5169
  }),
4736
5170
  diff
4737
- ? h("span", { className: "smem-ad-badge smem-ad-badge--warn", key: "d" }, "当前 " + String(cur) + " → 重启后 " + String(pend))
4738
- : h("span", { className: "smem-status", key: "d" }, "范围 " + f.lo + "–" + f.hi + (cur == null ? "" : (" · 当前生效 " + String(cur)))),
5171
+ ? h("span", { className: "smem-ad-badge smem-ad-badge--warn", key: "d" }, t("ad.limit.diff", { current: String(cur), pending: String(pend) }))
5172
+ : h("span", { className: "smem-status", key: "d" }, t("ad.limit.range", { lo: f.lo, hi: f.hi }) + (cur == null ? "" : t("ad.limit.current", { current: String(cur) }))),
4739
5173
  ]);
4740
5174
  })),
4741
5175
  h("div", { className: "smem-autodream-row", key: "save" }, [
4742
- h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "s", disabled: saveBusy, onClick: saveLimits }, saveBusy ? "保存中…" : "保存注入参数"),
4743
- h("span", { className: "smem-status", key: "n" }, "保存后需重启 DSH 才生效"),
5176
+ h("button", { type: "button", className: "smem-btn smem-btn-primary", key: "s", disabled: saveBusy, onClick: saveLimits }, saveBusy ? t("ad.action.saving") : t("ad.action.saveLimits")),
5177
+ h("span", { className: "smem-status", key: "n" }, t("ad.limits.saveHint")),
4744
5178
  ]),
4745
5179
  ]),
4746
5180
  h("div", { className: "smem-autodream-sec", key: "reserved" }, [
4747
5181
  h("div", { className: "smem-autodream-row", key: "h" }, [
4748
- h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, "保留名保护名单"),
4749
- h("span", { className: "smem-status", key: "c" }, "名单里的文件不参与注入"),
5182
+ h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, t("ad.reserved.title")),
5183
+ h("span", { className: "smem-status", key: "c" }, t("ad.reserved.subtitle")),
4750
5184
  ]),
4751
- h("div", { className: "smem-autodream-note", key: "n" },
4752
- "内置的两个固定不可删;下面可以再加自己的(只写文件名,不要带路径)。改完需重启 DSH 才生效。"),
5185
+ h("div", { className: "smem-autodream-note", key: "n" }, t("ad.reserved.note")),
4753
5186
  h("div", { className: "smem-raw-names", key: "fixed" }, fixedReserved.map(function (n) {
4754
- return h("span", { className: "smem-chip smem-chip--fixed", key: n, title: "内置固定,不可删除" }, n + " · 固定");
5187
+ return h("span", { className: "smem-chip smem-chip--fixed", key: n, title: t("ad.reserved.fixedTitle") }, n + t("ad.reserved.fixedSuffix"));
4755
5188
  })),
4756
5189
  h("div", { className: "smem-raw-names", key: "extra" },
4757
5190
  extraReserved.length
4758
5191
  ? extraReserved.map(function (n) {
4759
5192
  return h("span", { className: "smem-chip", key: n, "data-on": "1" }, [
4760
5193
  n,
4761
- h("button", { type: "button", className: "smem-chip-x", key: "x", title: "从名单里移除 " + n, onClick: function () { removeReserved(n); } }, "✕"),
5194
+ h("button", { type: "button", className: "smem-chip-x", key: "x", title: t("ad.reserved.removeTitle", { name: n }), onClick: function () { removeReserved(n); } }, "✕"),
4762
5195
  ]);
4763
5196
  })
4764
- : h("span", { className: "smem-status", key: "none" }, "(还没有自己加的保留名)")),
5197
+ : h("span", { className: "smem-status", key: "none" }, t("ad.reserved.empty"))),
4765
5198
  h("div", { className: "smem-autodream-row", key: "add" }, [
4766
5199
  h("input", {
4767
5200
  className: "smem-input", key: "i",
4768
5201
  style: { flex: "1", minWidth: "150px" },
4769
5202
  value: reservedNew,
4770
- placeholder: "例如 project_notes.md(要 .md 文件名)",
5203
+ placeholder: t("ad.reserved.placeholder"),
4771
5204
  onChange: function (e) { setReservedNew(e.target.value); },
4772
5205
  }),
4773
- h("button", { type: "button", className: "smem-btn", key: "a", disabled: saveBusy, onClick: addReserved }, "加入名单"),
5206
+ h("button", { type: "button", className: "smem-btn", key: "a", disabled: saveBusy, onClick: addReserved }, t("ad.action.addToList")),
4774
5207
  ]),
4775
5208
  ]),
4776
5209
  h("details", { className: "smem-autodream-adv", key: "adv", open: true }, [
4777
- h("summary", { key: "s" }, "高级设置"),
5210
+ h("summary", { key: "s" }, t("ad.advanced")),
4778
5211
  h("div", { key: "d" }, [
4779
5212
  c.trigger === "auto" ? h("div", { className: "smem-autodream-row", key: "gate" }, [
4780
- h("span", { className: "smem-autodream-key", key: "k" }, "自动触发"),
4781
- h("span", { className: "smem-status", key: "a" }, "距上次 ≥"),
5213
+ h("span", { className: "smem-autodream-key", key: "k" }, t("ad.adv.autoTrigger")),
5214
+ h("span", { className: "smem-status", key: "a" }, t("ad.adv.since")),
4782
5215
  h("input", { className: "smem-num", key: "h", type: "number", min: "1", max: "720", value: c.minHours, onChange: function (e) { patch({ minHours: Number(e.target.value) }); } }),
4783
- h("span", { className: "smem-status", key: "b" }, "小时,且期间 ≥"),
5216
+ h("span", { className: "smem-status", key: "b" }, t("ad.adv.hoursAndSessions")),
4784
5217
  h("input", { className: "smem-num", key: "m", type: "number", min: "1", max: "200", value: c.minSessions, onChange: function (e) { patch({ minSessions: Number(e.target.value) }); } }),
4785
- h("span", { className: "smem-status", key: "c" }, "个会话有更新"),
5218
+ h("span", { className: "smem-status", key: "c" }, t("ad.adv.sessionsUpdated")),
4786
5219
  ]) : null,
4787
5220
  h("div", { className: "smem-autodream-row", key: "steps" }, [
4788
- h("span", { className: "smem-autodream-key", key: "k" }, "最多轮数"),
5221
+ h("span", { className: "smem-autodream-key", key: "k" }, t("ad.adv.maxSteps")),
4789
5222
  h("input", { className: "smem-num", key: "v", type: "number", min: "1", max: "80", value: c.maxSteps, onChange: function (e) { patch({ maxSteps: Number(e.target.value) }); } }),
4790
- h("span", { className: "smem-status", key: "s" }, "一轮 = 一次模型调用 + 它要的工具调用"),
5223
+ h("span", { className: "smem-status", key: "s" }, t("ad.adv.stepNote")),
4791
5224
  ]),
4792
5225
  // 1.9.2:单次输出上限(原写死 4096)。思考型模型下 4096 会让收尾轮只剩思考、零正文。
4793
5226
  h("div", { className: "smem-autodream-row", key: "maxout" }, [
4794
- h("span", { className: "smem-autodream-key", key: "k" }, "单次输出上限"),
5227
+ h("span", { className: "smem-autodream-key", key: "k" }, t("ad.adv.maxOutput")),
4795
5228
  h("input", { className: "smem-num", key: "v", type: "number", min: "1024", max: "131072", value: c.maxOutputTokens, onChange: function (e) { patch({ maxOutputTokens: Number(e.target.value) }); } }),
4796
- h("span", { className: "smem-status", key: "s" }, "token(思考与正文共用;报告残篇时先调它)"),
5229
+ h("span", { className: "smem-status", key: "s" }, t("ad.adv.maxOutputNote")),
4797
5230
  ]),
4798
5231
  h("div", { className: "smem-autodream-row", key: "keep" }, [
4799
- h("span", { className: "smem-autodream-key", key: "k" }, "保留快照"),
5232
+ h("span", { className: "smem-autodream-key", key: "k" }, t("ad.adv.keepSnapshots")),
4800
5233
  h("input", { className: "smem-num", key: "v", type: "number", min: "1", max: "50", value: c.maxSnapshotKeep, onChange: function (e) { patch({ maxSnapshotKeep: Number(e.target.value) }); } }),
4801
- h("span", { className: "smem-status", key: "s" }, "份"),
5234
+ h("span", { className: "smem-status", key: "s" }, t("ad.unit.copies")),
4802
5235
  ]),
4803
- h("div", { className: "smem-status", key: "paths" }, "配置与状态:" + ((st && st.paths && st.paths.configPath) || "—")),
5236
+ h("div", { className: "smem-status", key: "paths" }, t("ad.adv.paths", { path: ((st && st.paths && st.paths.configPath) || "—") })),
4804
5237
  ]),
4805
5238
  ]),
4806
5239
  ]) : null,
@@ -4809,53 +5242,51 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4809
5242
  segOf === "records" ? h("div", { key: "seg-rec" }, [
4810
5243
  h("div", { className: "smem-autodream-sec", key: "snap" }, [
4811
5244
  h("div", { className: "smem-autodream-row", key: "h" }, [
4812
- h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, "回滚点"),
4813
- h("span", { className: "smem-status", key: "c" }, String(snaps.length) + " 个"),
4814
- h("button", { type: "button", className: "smem-btn", key: "rf", onClick: refreshLists }, "刷新"),
5245
+ h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, t("ad.records.snapshots")),
5246
+ h("span", { className: "smem-status", key: "c" }, t("ad.records.countSnaps", { count: String(snaps.length) })),
5247
+ h("button", { type: "button", className: "smem-btn", key: "rf", onClick: refreshLists }, t("ad.action.refresh")),
4815
5248
  ]),
4816
5249
  h("div", { className: "smem-autodream-row", key: "scope" }, [
4817
- h("span", { className: "smem-autodream-key", key: "k" }, "回滚范围"),
5250
+ h("span", { className: "smem-autodream-key", key: "k" }, t("ad.row.rollbackScope")),
4818
5251
  h(Seg, {
4819
5252
  key: "v", value: rollbackScope,
4820
- options: [{ value: "files", label: "只恢复本次动过的文件" }, { value: "all", label: "整目录回到该时点" }],
5253
+ options: [{ value: "files", label: t("ad.scope.files") }, { value: "all", label: t("ad.scope.all") }],
4821
5254
  onChange: function (v) { patch({ rollbackScope: v }); },
4822
5255
  }),
4823
5256
  ]),
4824
- h("div", { className: "smem-autodream-note", key: "n" },
4825
- "回滚前会自动把整个记忆目录再存一份保护快照 —— 所以回滚本身也能再退回来。多出来的文件只移进 archive/,不删除。"),
5257
+ h("div", { className: "smem-autodream-note", key: "n" }, t("ad.note.rollback")),
4826
5258
  snaps.length === 0
4827
- ? h("div", { className: "smem-status", key: "e" }, "还没有回滚点 —— 用「直接改写」跑过一次就有了。")
5259
+ ? h("div", { className: "smem-status", key: "e" }, t("ad.snap.empty"))
4828
5260
  : h("div", { className: "smem-autodream-list", key: "l" }, snaps.slice(0, 15).map(function (sp) {
4829
- const label = sp.legacy ? "旧版快照" : (sp.runId ? ("运行 " + String(sp.runId)) : "回滚保护");
5261
+ const label = sp.legacy ? t("ad.snap.legacyLabel") : (sp.runId ? t("ad.snap.runLabel", { id: String(sp.runId) }) : t("ad.snap.protectionLabel"));
4830
5262
  return h("div", { className: "smem-ad-wrap", key: String(sp.name) }, [
4831
5263
  h("div", { className: "smem-ad-snap", key: "row" }, [
4832
5264
  h("div", { className: "smem-ad-main", key: "m" }, [
4833
- h("div", { key: "l1" }, String(sp.atHuman || "") + " · " + (sp.files == null ? "?" : sp.files) + " 个文件"),
5265
+ h("div", { key: "l1" }, t("ad.snap.meta", { at: String(sp.atHuman || ""), files: (sp.files == null ? "?" : sp.files) })),
4834
5266
  h("div", { className: "smem-ad-sub", key: "l2" }, label + " · " + String(sp.name || "")),
4835
5267
  ]),
4836
- sp.legacy ? h("span", { className: "smem-ad-badge smem-ad-badge--legacy", key: "lg" }, "旧版") : null,
5268
+ sp.legacy ? h("span", { className: "smem-ad-badge smem-ad-badge--legacy", key: "lg" }, t("ad.snap.legacyBadge")) : null,
4837
5269
  confirmSnap === sp.name
4838
5270
  ? null
4839
5271
  : h("button", {
4840
5272
  type: "button", className: "smem-btn", key: "go",
4841
5273
  disabled: rbBusy !== null,
4842
5274
  onClick: function () { setRbMsg(null); setConfirmSnap(sp.name); },
4843
- }, "回滚"),
5275
+ }, t("ad.action.rollback")),
4844
5276
  ]),
4845
5277
  confirmSnap === sp.name
4846
5278
  ? h("div", { className: "smem-ad-confirm", key: "cf" }, [
4847
- h("div", { key: "t", style: { fontWeight: "600" } }, "确认回滚到 " + String(sp.atHuman || "") + "?"),
4848
- h("div", { key: "d" }, "范围:" + (rollbackScope === "all"
4849
- ? "整目录回到该时点 —— 快照里没有、当前目录里有的文件会被移进 archive/(不删除)"
4850
- : "只恢复这次运行动过的文件 —— 该次新建的移进 archive/,该次归档的移回顶层") +
4851
- "。回滚前会先存一份保护快照,所以这一步本身也能退。"),
5279
+ h("div", { key: "t", style: { fontWeight: "600" } }, t("ad.snap.confirmTitle", { at: String(sp.atHuman || "") })),
5280
+ h("div", { key: "d" }, t("ad.snap.confirmScope", {
5281
+ scope: rollbackScope === "all" ? t("ad.snap.scopeAll") : t("ad.snap.scopeFiles"),
5282
+ }) + t("ad.snap.confirmSuffix")),
4852
5283
  h("div", { className: "smem-autodream-row", key: "acts" }, [
4853
5284
  h("button", {
4854
5285
  type: "button", className: "smem-btn smem-btn-danger", key: "yes",
4855
5286
  disabled: rbBusy === sp.name,
4856
5287
  onClick: function () { doRollback(sp); },
4857
- }, rbBusy === sp.name ? "回滚中…" : "确认回滚"),
4858
- h("button", { type: "button", className: "smem-btn", key: "no", onClick: function () { setConfirmSnap(null); } }, "取消"),
5288
+ }, rbBusy === sp.name ? t("ad.action.rollingBack") : t("ad.action.confirmRollback")),
5289
+ h("button", { type: "button", className: "smem-btn", key: "no", onClick: function () { setConfirmSnap(null); } }, t("ad.action.cancel")),
4859
5290
  ]),
4860
5291
  ])
4861
5292
  : null,
@@ -4866,14 +5297,13 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4866
5297
  // ── 整理声明(新)──────────────────────────────────────────
4867
5298
  h("div", { className: "smem-autodream-sec", key: "runs" }, [
4868
5299
  h("div", { className: "smem-autodream-row", key: "h" }, [
4869
- h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, "整理记录"),
4870
- h("span", { className: "smem-status", key: "c" }, String(runs.length) + " 条"),
4871
- h("button", { type: "button", className: "smem-btn", key: "rf", onClick: refreshLists }, "刷新"),
5300
+ h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, t("ad.records.runs")),
5301
+ h("span", { className: "smem-status", key: "c" }, t("ad.records.countRuns", { count: String(runs.length) })),
5302
+ h("button", { type: "button", className: "smem-btn", key: "rf", onClick: refreshLists }, t("ad.action.refresh")),
4872
5303
  ]),
4873
- h("div", { className: "smem-autodream-note", key: "n" },
4874
- "每一趟都留一份整理声明:改了什么、为什么改、哪些想法被拒了。点一条看全文。"),
5304
+ h("div", { className: "smem-autodream-note", key: "n" }, t("ad.records.runsNote")),
4875
5305
  runs.length === 0
4876
- ? h("div", { className: "smem-status", key: "e" }, "还没有整理记录 —— 跑一次就有了。")
5306
+ ? h("div", { className: "smem-status", key: "e" }, t("ad.records.runsEmpty"))
4877
5307
  : h("div", { className: "smem-autodream-list", key: "l" }, runs.slice(0, 15).map(function (rn) {
4878
5308
  return h("div", { className: "smem-ad-wrap", key: String(rn.runId) }, [
4879
5309
  h("button", {
@@ -4882,14 +5312,14 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4882
5312
  onClick: function () { openDecl(rn.runId); },
4883
5313
  }, [
4884
5314
  h("span", { key: "t" }, String(rn.atHuman || "")),
4885
- h("span", { className: "smem-ad-badge", key: "m" }, rn.mode === "apply" ? "直接改写" : "只出报告"),
4886
- h("span", { key: "c" }, "改 " + (rn.changeCount == null ? "?" : rn.changeCount) + " 条"),
4887
- rn.report ? h("span", { className: "smem-ad-sub", key: "r" }, "报告 " + String(rn.report)) : null,
5315
+ h("span", { className: "smem-ad-badge", key: "m" }, rn.mode === "apply" ? t("ad.badge.apply") : t("ad.badge.report")),
5316
+ h("span", { key: "c" }, t("ad.run.changes", { count: (rn.changeCount == null ? "?" : rn.changeCount) })),
5317
+ rn.report ? h("span", { className: "smem-ad-sub", key: "r" }, t("ad.run.reportName", { name: String(rn.report) })) : null,
4888
5318
  rn.rolledBackAt
4889
5319
  ? h("span", { className: "smem-ad-badge smem-ad-badge--warn", key: "rb" },
4890
- "已回滚" + (rn.rolledBackAtHuman ? (" · " + String(rn.rolledBackAtHuman)) : ""))
5320
+ rn.rolledBackAtHuman ? t("ad.run.rolledBackAt", { at: String(rn.rolledBackAtHuman) }) : t("ad.run.rolledBack"))
4891
5321
  : null,
4892
- h("span", { className: "smem-ad-sub", key: "x" }, declOpen === rn.runId ? "收起" : "看声明"),
5322
+ h("span", { className: "smem-ad-sub", key: "x" }, declOpen === rn.runId ? t("ad.action.collapse") : t("ad.action.showDeclaration")),
4893
5323
  ]),
4894
5324
  declOpen === rn.runId
4895
5325
  ? h("pre", { className: "smem-ad-md", key: "body" }, declBody)
@@ -4899,11 +5329,11 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4899
5329
  ]),
4900
5330
  h("div", { className: "smem-autodream-sec", key: "rep" }, [
4901
5331
  h("div", { className: "smem-autodream-row", key: "h" }, [
4902
- h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, "历史报告"),
4903
- h("span", { className: "smem-status", key: "c" }, String(reports.length) + " 份"),
5332
+ h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, t("ad.records.reports")),
5333
+ h("span", { className: "smem-status", key: "c" }, t("ad.records.countReports", { count: String(reports.length) })),
4904
5334
  ]),
4905
5335
  reports.length === 0
4906
- ? h("div", { className: "smem-status", key: "e" }, "还没有报告 —— 跑一次就有了。报告写在 memory/autodream/ 下,不会混进记忆列表。")
5336
+ ? h("div", { className: "smem-status", key: "e" }, t("ad.records.reportsEmpty"))
4907
5337
  : h("div", { className: "smem-autodream-list", key: "l" }, reports.slice(0, 12).map(function (r) {
4908
5338
  return h("button", {
4909
5339
  type: "button", key: r.name, className: "smem-report",
@@ -4911,8 +5341,8 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4911
5341
  onClick: function () { openReport(open === r.name ? null : r.name); },
4912
5342
  }, [
4913
5343
  h("span", { key: "t" }, String(r.atHuman || "") + " · " + String(r.name) + " · " + fmtSize(r.bytes)),
4914
- r.legacy ? h("span", { className: "smem-ad-badge smem-ad-badge--legacy", key: "lg", style: { marginLeft: "6px" } }, "旧版") : null,
4915
- h("span", { className: "smem-ad-sub", key: "x", style: { marginLeft: "6px" } }, open === r.name ? "收起" : "查看"),
5344
+ r.legacy ? h("span", { className: "smem-ad-badge smem-ad-badge--legacy", key: "lg", style: { marginLeft: "6px" } }, t("ad.snap.legacyBadge")) : null,
5345
+ h("span", { className: "smem-ad-sub", key: "x", style: { marginLeft: "6px" } }, open === r.name ? t("ad.action.collapse") : t("ad.action.view")),
4916
5346
  ]);
4917
5347
  })),
4918
5348
  open && body ? h("pre", { className: "smem-report-pre", key: "body" }, body) : null,
@@ -4953,12 +5383,25 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4953
5383
  const sBusy = react.useState(false); const busy = sBusy[0], setBusy = sBusy[1];
4954
5384
 
4955
5385
  const TH_KEYS = ["archiveAfterDaysProject", "archiveAfterDaysReference", "archiveAfterDaysUser"];
4956
- const TH_LABEL = { archiveAfterDaysProject: "项目", archiveAfterDaysReference: "参考", archiveAfterDaysUser: "用户" };
4957
- const TH_HINT = {
4958
- archiveAfterDaysProject: "project_* 连续这么多天没被注入 → 进候选",
4959
- archiveAfterDaysReference: "reference_* 同上(资料一般比项目留得久)",
4960
- archiveAfterDaysUser: "user_* 同上(人设这类动它要格外谨慎)",
5386
+ // 2.1.0:标签与提示走字典(键在这里,文案在 55-i18n-memory.js)。
5387
+ const TH_LABEL_KEY = {
5388
+ archiveAfterDaysProject: "archive.th.project",
5389
+ archiveAfterDaysReference: "archive.th.reference",
5390
+ archiveAfterDaysUser: "archive.th.user",
5391
+ };
5392
+ const TH_HINT_KEY = {
5393
+ archiveAfterDaysProject: "archive.th.hintProject",
5394
+ archiveAfterDaysReference: "archive.th.hintReference",
5395
+ archiveAfterDaysUser: "archive.th.hintUser",
4961
5396
  };
5397
+ const thLabel = function (k) { return t(TH_LABEL_KEY[k] || ""); };
5398
+ const thHint = function (k) { return t(TH_HINT_KEY[k] || ""); };
5399
+ // 2.1.0:三档策略的标题与代价走字典。键必须**写成字面量**(死键守卫会逐键回分片里找);
5400
+ // 用 "archive.mode." + v 拼接既拿不到字面量,也会让守卫把前缀当成一个缺失的键。
5401
+ const MODE_KEY = { off: "archive.mode.off", report: "archive.mode.report", auto: "archive.mode.auto" };
5402
+ const COST_KEY = { off: "archive.cost.off", report: "archive.cost.report", auto: "archive.cost.auto" };
5403
+ const modeLabel = function (v) { return t(MODE_KEY[v] || ""); };
5404
+ const costText = function (v) { return t(COST_KEY[v] || ""); };
4962
5405
 
4963
5406
  /** 阈值 → 输入框草稿。存成字符串:number input 清空时立刻变 0 会很难用。 */
4964
5407
  const draftOf = function (c) {
@@ -4970,18 +5413,18 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
4970
5413
 
4971
5414
  react.useEffect(function () {
4972
5415
  if (!remote || typeof remote.getConfig !== "function") {
4973
- setErr("宿主侧没有 autodream 配置接口(插件版本较旧)");
5416
+ setErr(t("archive.err.noConfig"));
4974
5417
  return;
4975
5418
  }
4976
5419
  remote.getConfig()
4977
5420
  .then(function (r) {
4978
5421
  const d = unwrap(r);
4979
5422
  const c = d && d.config ? d.config : null;
4980
- if (!c) { setErr("读不出自动做梦配置"); return; }
5423
+ if (!c) { setErr(t("archive.err.readConfig")); return; }
4981
5424
  setCfg(c);
4982
5425
  setDraft(draftOf(c));
4983
5426
  })
4984
- .catch(function (e) { setErr("读配置失败:" + ((e && e.message) || e)); });
5427
+ .catch(function (e) { setErr(t("archive.err.readFailed", { error: (e && e.message) || e })); });
4985
5428
  }, []);
4986
5429
 
4987
5430
  const c = cfg || {};
@@ -5003,16 +5446,16 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
5003
5446
  if (got) { setCfg(got); setDraft(draftOf(got)); }
5004
5447
  const bad = failOf(got);
5005
5448
  if (!bad) { setNotice(okMsg); return; }
5006
- setErr("保存没生效:" + bad + "。setConfig 对非法值会忽略并保留原值 —— 请重试,或看 DSH 启动日志。");
5449
+ setErr(t("archive.err.notApplied", { error: bad }));
5007
5450
  })
5008
- .catch(function (e) { setErr("保存失败:" + ((e && e.message) || e)); })
5451
+ .catch(function (e) { setErr(t("archive.err.saveFailed", { error: (e && e.message) || e })); })
5009
5452
  .then(function () { setBusy(false); });
5010
5453
  }
5011
5454
 
5012
5455
  function saveMode(v) {
5013
- save({ autoArchive: v }, "自动归档已设为「" + AUTO_ARCHIVE_LABEL[v] + "」", function (got) {
5456
+ save({ autoArchive: v }, t("archive.notice.modeSet", { mode: modeLabel(v) }), function (got) {
5014
5457
  const g = got ? autoArchiveOf(got.autoArchive) : null;
5015
- return g === v ? null : ("配置里仍是「" + (g ? AUTO_ARCHIVE_LABEL[g] : "读不出来") + "」");
5458
+ return g === v ? null : t("archive.err.modeStill", { mode: g ? modeLabel(g) : t("archive.unknown") });
5016
5459
  });
5017
5460
  }
5018
5461
 
@@ -5023,15 +5466,15 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
5023
5466
  for (const k of TH_KEYS) {
5024
5467
  const n = Number(d[k]);
5025
5468
  if (!Number.isFinite(n) || n < 1 || n > 3650) {
5026
- setErr(TH_LABEL[k] + "的阈值要是 1~3650 之间的天数(现在是「" + d[k] + "」)");
5469
+ setErr(t("archive.err.thresholdBad", { label: thLabel(k), value: d[k] }));
5027
5470
  return;
5028
5471
  }
5029
5472
  patch[k] = Math.round(n);
5030
5473
  }
5031
- save(patch, "归档条件已保存", function (got) {
5474
+ save(patch, t("archive.notice.thresholdsSaved"), function (got) {
5032
5475
  const bad = TH_KEYS.filter(function (k) { return !got || Number(got[k]) !== patch[k]; });
5033
5476
  if (!bad.length) return null;
5034
- return bad.map(function (k) { return TH_LABEL[k] + " 仍是 " + (got ? got[k] : "读不出来"); }).join("、");
5477
+ return bad.map(function (k) { return t("archive.err.thresholdStill", { label: thLabel(k), value: got ? got[k] : t("archive.unknown") }); }).join(t("ad.toolSep"));
5035
5478
  });
5036
5479
  }
5037
5480
 
@@ -5039,45 +5482,41 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
5039
5482
  // ── ① 它做什么(这一屏存在的理由写在最前面)──
5040
5483
  h("div", { className: "smem-autodream-sec", key: "what" }, [
5041
5484
  h("div", { className: "smem-autodream-row", key: "h" }, [
5042
- h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, "自动归档做什么"),
5485
+ h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, t("archive.title")),
5043
5486
  ]),
5044
- h("div", { className: "smem-autodream-note", key: "p1" },
5045
- "闲置太久没被注入过的记忆,会被移进 archive/ —— 不删除,随时能恢复。这一页决定「多久算闲置」,以及要不要真的动手。"),
5046
- h("div", { className: "smem-autodream-note", key: "p2" },
5047
- "它不调模型:候选完全按访问台账的天数算,同一份输入每次算出来都一样。"),
5048
- h("div", { className: "smem-autodream-note", key: "p3" },
5049
- "想现在看看会被收走哪些:文件列表 →「工具 ▾」→ 归档候选。"),
5487
+ h("div", { className: "smem-autodream-note", key: "p1" }, t("archive.intro1")),
5488
+ h("div", { className: "smem-autodream-note", key: "p2" }, t("archive.intro2")),
5489
+ h("div", { className: "smem-autodream-note", key: "p3" }, t("archive.intro3")),
5050
5490
  ]),
5051
5491
  // ── ② 三档策略 ──
5052
5492
  h("div", { className: "smem-autodream-sec", key: "mode" }, [
5053
5493
  h("div", { className: "smem-autodream-row", key: "r" }, [
5054
- h("span", { className: "smem-autodream-key", key: "k" }, "自动归档"),
5494
+ h("span", { className: "smem-autodream-key", key: "k" }, t("archive.modeTitle")),
5055
5495
  h(Seg, {
5056
5496
  key: "v", value: mode,
5057
5497
  options: [
5058
- { value: "off", label: AUTO_ARCHIVE_LABEL.off },
5059
- { value: "report", label: AUTO_ARCHIVE_LABEL.report },
5060
- { value: "auto", label: AUTO_ARCHIVE_LABEL.auto },
5498
+ { value: "off", label: modeLabel("off") },
5499
+ { value: "report", label: modeLabel("report") },
5500
+ { value: "auto", label: modeLabel("auto") },
5061
5501
  ],
5062
5502
  onChange: function (v) { if (v !== mode) saveMode(v); },
5063
5503
  }),
5064
- busy ? h("span", { className: "smem-status", key: "s" }, "保存中…") : null,
5504
+ busy ? h("span", { className: "smem-status", key: "s" }, t("archive.action.saving")) : null,
5065
5505
  ]),
5066
- h("div", { className: "smem-autodream-note", key: "cost" }, AUTO_ARCHIVE_COST[mode]),
5506
+ h("div", { className: "smem-autodream-note", key: "cost" }, costText(mode)),
5067
5507
  mode === "auto" && !c.apply
5068
- ? h("div", { className: "smem-autodream-note smem-ad-warn", key: "deg" },
5069
- "注意:「自动做梦 → 改动方式」现在是只出报告 —— 这一档会自动降级成只出报告,不会动文件。")
5508
+ ? h("div", { className: "smem-autodream-note smem-ad-warn", key: "deg" }, t("archive.degrade"))
5070
5509
  : null,
5071
5510
  ]),
5072
5511
  // ── ③ 三个阈值 ──
5073
5512
  h("div", { className: "smem-autodream-sec", key: "th" }, [
5074
5513
  h("div", { className: "smem-autodream-row", key: "h" }, [
5075
- h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, "闲置多少天算可以归档"),
5514
+ h("span", { key: "t", style: { fontWeight: "600", fontSize: "12.5px" } }, t("archive.th.title")),
5076
5515
  ]),
5077
5516
  draft
5078
5517
  ? h("div", { key: "rows" }, TH_KEYS.map(function (k) {
5079
5518
  return h("div", { className: "smem-autodream-row", key: k }, [
5080
- h("span", { className: "smem-autodream-key", key: "k" }, TH_LABEL[k]),
5519
+ h("span", { className: "smem-autodream-key", key: "k" }, thLabel(k)),
5081
5520
  h("input", {
5082
5521
  className: "smem-input smem-num", key: "i", type: "number", min: 1, max: 3650,
5083
5522
  value: draft[k],
@@ -5087,19 +5526,18 @@ h("span", { key: "sep", className: "smem-actions-sep", "aria-hidden": "true" }),
5087
5526
  setDraft(nx);
5088
5527
  },
5089
5528
  }),
5090
- h("span", { className: "smem-muted", key: "u" }, TH_HINT[k]),
5529
+ h("span", { className: "smem-muted", key: "u" }, thHint(k)),
5091
5530
  ]);
5092
5531
  }))
5093
- : h("div", { className: "smem-muted", key: "loading" }, "读配置中…"),
5532
+ : h("div", { className: "smem-muted", key: "loading" }, t("archive.loadingConfig")),
5094
5533
  h("div", { className: "smem-autodream-row", key: "btns" }, [
5095
5534
  h("button", {
5096
5535
  type: "button", className: "smem-btn smem-btn-primary", key: "save",
5097
5536
  disabled: busy || !draft, onClick: saveThresholds,
5098
- }, busy ? "保存中…" : "保存归档条件"),
5099
- h("span", { className: "smem-muted", key: "n" }, "保存即生效,不用重启 DSH"),
5537
+ }, busy ? t("archive.action.saving") : t("archive.action.save")),
5538
+ h("span", { className: "smem-muted", key: "n" }, t("archive.note.immediate")),
5100
5539
  ]),
5101
- h("div", { className: "smem-autodream-note", key: "note" },
5102
- "天数按访问台账算(这条记忆上次被注入给模型是什么时候)。feedback_* 永不入选;锁定的记忆也不会被选中。"),
5540
+ h("div", { className: "smem-autodream-note", key: "note" }, t("archive.note.thresholds")),
5103
5541
  ]),
5104
5542
  err ? h("div", { className: "smem-err", key: "err" }, "⚠ " + err) : null,
5105
5543
  notice ? h("div", { className: "smem-ok", key: "ok" }, "✓ " + notice) : null,