@a9i5k4/dsh-auto-memory 0.1.28 → 0.1.30

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
@@ -30,15 +30,43 @@ window.__ModuleLoader__.load({
30
30
  var DEFAULT_W = 440
31
31
  var DEFAULT_H = 560
32
32
  var DEFAULT_GAP = 16
33
+ // 侧边栏「记忆」入口按钮位置(DSH Desktop 增强模式下面板贴左下角会盖住它)——@ProperSAMA PR#12
34
+ function entryButtonRect() {
35
+ try {
36
+ var btn = document.querySelector('[data-dam-sidebar-btn]')
37
+ if (btn) { var r = btn.getBoundingClientRect(); if (r && r.width > 0) return r }
38
+ } catch (e) {}
39
+ return null
40
+ }
33
41
  function defaultGeom() {
34
42
  var vh = window.innerHeight || 800
43
+ var top = Math.max(DEFAULT_GAP, vh - DEFAULT_H - DEFAULT_GAP)
44
+ // 默认锚定在「记忆」按钮正上方,保证开关入口始终可见可点
45
+ var r = entryButtonRect()
46
+ if (r) {
47
+ var anchored = r.top - DEFAULT_H - 12
48
+ if (anchored >= DEFAULT_GAP) top = Math.min(top, anchored)
49
+ }
35
50
  return {
36
51
  left: DEFAULT_GAP,
37
- top: Math.max(DEFAULT_GAP, vh - DEFAULT_H - DEFAULT_GAP),
52
+ top: top,
38
53
  width: DEFAULT_W,
39
54
  height: DEFAULT_H,
40
55
  }
41
56
  }
57
+ // 面板与「记忆」入口按钮重叠时自动上移让出入口(含旧版本持久化的贴底几何)
58
+ function avoidCoveringEntry() {
59
+ var r = entryButtonRect()
60
+ if (!r) return
61
+ var g = controller.geom()
62
+ var overlapX = g.left < r.right + 4 && g.left + g.width > r.left - 4
63
+ var overlapY = g.top < r.bottom + 4 && g.top + g.height > r.top - 4
64
+ if (!overlapX || !overlapY) return
65
+ var top = r.top - g.height - 12
66
+ if (top < DEFAULT_GAP) top = DEFAULT_GAP
67
+ geom = clampGeom({ left: g.left, top: top, width: g.width, height: g.height })
68
+ persistGeom()
69
+ }
42
70
  var geom = null
43
71
  function clampGeom(g) {
44
72
  var vw = window.innerWidth || 1280
@@ -60,6 +88,18 @@ window.__ModuleLoader__.load({
60
88
  return clampGeom(defaultGeom())
61
89
  }
62
90
  function persistGeom() { if (geom) { try { localStorage.setItem(GEOM_KEY, JSON.stringify(geom)) } catch (e) {} } }
91
+ // 解析 computed color(rgba() / color(srgb) / #RRGGBBAA)的通道与 alpha,用于可读性兜底——@ProperSAMA PR#12(适配:补 hex8)
92
+ function parseCssColor(str) {
93
+ if (!str) return null
94
+ function alphaOf(v) { return v === undefined ? 1 : (v.charAt(v.length - 1) === '%' ? parseFloat(v) / 100 : parseFloat(v)) }
95
+ var m = /^rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:[,\s/]+([\d.]+%?))?\s*\)$/i.exec(str)
96
+ if (m) return { r: Math.round(+m[1]), g: Math.round(+m[2]), b: Math.round(+m[3]), a: alphaOf(m[4]) }
97
+ var c = /^color\(\s*srgb\s+([\d.]+)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+%?))?\s*\)$/i.exec(str)
98
+ if (c) return { r: Math.round(+c[1] * 255), g: Math.round(+c[2] * 255), b: Math.round(+c[3] * 255), a: alphaOf(c[4]) }
99
+ var h8 = /^#([0-9a-f]{8})$/i.exec(str)
100
+ if (h8) return { r: parseInt(h8[1].slice(0, 2), 16), g: parseInt(h8[1].slice(2, 4), 16), b: parseInt(h8[1].slice(4, 6), 16), a: parseInt(h8[1].slice(6, 8), 16) / 255 }
101
+ return null
102
+ }
63
103
  function emit() { listeners.forEach(function (fn) { try { fn() } catch (e) {} }) }
64
104
  var controller = {
65
105
  isOpen: function () { return panelOpen },
@@ -80,6 +120,7 @@ window.__ModuleLoader__.load({
80
120
  toggle: function () { if (panelOpen) controller.close(); else controller.open() },
81
121
  open: function () {
82
122
  if (closeTimer) { clearTimeout(closeTimer); closeTimer = null }
123
+ avoidCoveringEntry()
83
124
  panelOpen = true; panelClosing = false; emit()
84
125
  },
85
126
  close: function () {
@@ -101,7 +142,41 @@ window.__ModuleLoader__.load({
101
142
  memoryPanel: '记忆面板',
102
143
  memory: '记忆',
103
144
  autoMemory: '自动记忆',
104
- overview: '概览', logs: '日志', notes: '笔记', reflections: '反思', connect: '接续', calendar: '日历', search: '检索', workspaces: '工作区',
145
+ overview: '概览', logs: '日志', refineTab: '唤起回顾', notes: '笔记', reflections: '反思', connect: '接续', calendar: '日历', search: '检索', workspaces: '工作区', hubTab: '记忆中枢', storageTab: '存储管理',
146
+ hubSkills: '技能 (Procedural)', hubSkillsEmpty: '暂无已固化的技能。反复成功的流程会自动固化为技能并在相似场景召回。', hubFacts: '事实 (Semantic)', hubFactsEmpty: '暂无固化的事实。', hubConflicts: '待决冲突', hubEpisodic: '经历 (Episodic)', hubEpisodicEmpty: '暂无已巩固的经历。',
147
+ storageScanHint: '语料健康 = 逐源比对索引(sidecar)与正文的 digest。手动改动记忆文件后索引会失配,该记忆会退出检索直到重建索引。',
148
+ storageDeleteHint: '删除记忆 = 正文原子删除 + 在途唤起包清理 + 派生事实撤销(三联动)。已产生的 seen 证据不改写。',
149
+ secSemantic: '自动记忆引擎', semMode: '检索模式', semAuto: '自动(推荐)', semLexOnly: '仅词法', semJs: '内置语义', semPy: '高级 Python',
150
+ semModeHint: '自动=内置语义就绪即用,否则词法保底;高级 Python 需另行安装。',
151
+ fAssocEngine: '启用自动记忆引擎', fAssocEngineHint: '总开关。开启后自动观测上下文、语义检索并适时唤起记忆注入(消费少量 token)。关闭则整个引擎不运行——不检索、不判定、不注入、不生成唤起记录。介意 token 消耗或担心动作跑偏的用户可关闭。默认关。',
152
+ secMemoryHubHint: '记忆中枢 = 三层记忆(经历/事实/技能)的编排器。开启后自动从对话沉淀经历、固化事实、把反复成功的流程固化为技能(skill),并在相似场景自动召回注入。',
153
+ fMemoryHub: '启用记忆中枢', fMemoryHubHint: '总开关。开启后三层记忆(episodic 经历 / semantic 事实 / procedural 技能)开始运行;关闭则只保留已有记忆,不再沉淀新内容。默认关。',
154
+ fEpisodicMin: '经历最少对话段数', fEpisodicMinHint: '一次经历(episode)至少积累多少段对话才巩固为记忆。太少=噪声多,太多=小对话被丢弃。默认 2。',
155
+ fEpisodicRet: '经历保留上限(条)', fEpisodicRetHint: '保留的最近经历条数,超出按时间淘汰最旧的。默认 256。',
156
+ fProcSessions: '技能晋升跨会话数', fProcSessionsHint: '一个流程至少出现在 N 个独立会话中才考虑晋升为技能。默认 3(M-04 元代码)。',
157
+ fProcSuccess: '技能晋升成功次数', fProcSuccessHint: '流程至少成功 N 次才可晋升。一次成功不足以证明可靠。默认 2。',
158
+ fProcCorr: '技能纠正容忍度', fProcCorrHint: '纠正/错误占该流程总证据的比例上限。超过则保持候选,不晋升。默认 0.3(30%)。',
159
+ fProcRisk: '高风险流程需批准', fProcRiskHint: '高风险流程(SSH/部署/删除等)晋升需用户明确批准,且永不因相似度自动执行。默认开。',
160
+ fProcLevel: '技能注入形式', fProcLevelHint: 'active 技能注入时给模型的提示形态:checklist=完整步骤+完成标准;excerpt=摘要;hint=仅提示可参考。高风险自动降级为 hint。',
161
+ memoryHubViewHint: '查看记忆中枢内容请打开「记忆」面板的「记忆中枢」页签。',
162
+ fJsCooldown: '唤起冷却(分钟)', fJsCooldownHint: '自动唤起注入后,N 分钟内不再判定,防止连续唤起浪费 token。默认 1;0=不冷却。',
163
+ fJsDelta: '唤起margin阈值(e5档)', fJsDeltaHint: '候选第1/2名分差须超过此值才注入(e5 余弦分布紧,默认 0.01;bge-m3 校准值为 0.03)。调小=更容易唤起,调大=更保守。0=不过滤。',
164
+ fEmitMode: '唤起注入模式', fEmitModeHint: 'shadow=只记录决策不注入(校准用);canary-explicit=仅明确回忆时注入(推荐);active=所有判定注入。JS/Python 双轨同源。',
165
+ fCandScheme: '唤起候选方案', fCandSchemeHint: 'balanced=3条×40字符(默认,信息量/token 平衡);dense=6条×20字符(更多候选更广联想);custom=自定义条数与长度。',
166
+ fCandN: '自定义候选条数', fCandNHint: 'custom 档的候选条数(1-8)。',
167
+ fJsExcerpt: '唤起注入内容长度(字符)', fJsExcerptHint: 'Reference Tail 的 Reference 行内容上限。默认 40=几个字/关键词级(省 token,需要细节时模型用 memory_read 取全文);调大可注入更多记忆正文。范围 20-480。',
168
+ fReasoning: '思维链监听', fReasoningHint: '把模型思维链纳入实时观测(重启后生效)。默认开——闭源模型的概括式思维链同样纳入。',
169
+ fChildObs: '分支会话观测', fChildObsHint: '跨天续接的会话会被标记为分支;开启后同样纳入记忆观测。默认开。',
170
+ fTauHi: '唤起门槛 tauHi', fDeltaExp: '明确召回余量 deltaExp', fDeltaPro: '主动预取余量 deltaPro',
171
+ fTuningHint: '阈值由校准策略 JSON 权威控制;此处为高级调参入口,修改保存后随下轮生效。',
172
+ refineTitle: '唤起记录与语料精修', refineSub: '对每次唤起判断给出你的裁定(A 该激活/P 只预取/S 应抑制/H 有害/E 改目标),审批队列将用于离线重放与策略演进。',
173
+ refineEmpty: '(暂无唤起记录——需要 shadow 观测产生数据)', refineLoadErr: '加载失败: ',
174
+ sent1: '已入审批队列', semReady: '内置语义已就绪', semMissing: '语义包未下载(约130MB)', semStatusErr: '状态未知',
175
+ semResolved: '当前生效检索', tierC1: 'C1 词法保底(BM25)', tierC2: 'C2 内置语义 e5-small q8', tierC3: 'C3 高级 Python bge-m3',
176
+ semDlStart: '开始下载', semDlRetry: '重试', semDlCancel: '取消',
177
+ mAuto: '自动(国内优先)', mCn: '国内源 · hf-mirror', mIntl: '国际源 · huggingface',
178
+ dlDownloading: '下载中', dlVerifying: '校验中(SHA256)', dlDone: '下载完成', dlCancelled: '已取消', dlError: '下载失败',
179
+ fWelcomeTour: '欢迎向导', fWelcomeTourHint: '首次启动后自动播放分步功能引导(含语义引擎检测/下载);关闭后仅可从此处手动打开。', tourReplay: '▶ 重看引导',
105
180
  wsGenerating: '正在生成各工作区总结(每个工作区一次 AI 调用,可能稍慢)…', wsNone: '未发现带记忆的工作区', wsNoSummary: '(无总结)', wsOverview: '工作区总结(跨工作区)',
106
181
  debugCenter: '调试中心', dbgLoading: '加载诊断信息…', dbgRefresh: '刷新', dbgRefreshing: '刷新中…',
107
182
  fMemoryRoot: '记忆根目录', fMemoryRootHint: '集中式存储:所有工作区记忆统一放在此目录下(每工作区一个子目录),旧版分散的记忆会自动迁移。',
@@ -171,11 +246,17 @@ window.__ModuleLoader__.load({
171
246
  fUserDir: '用户记忆目录', fUserDirHint: '跨项目规则存放处,支持 ~ 开头;需有文件写权限。',
172
247
  fProjectDir: '项目记忆目录', fProjectDirHint: '相对各工作区的目录名(默认 .dsh-memory)。',
173
248
  fInject: '注入记忆上下文', fInjectHint: '每次组装提示词时自动注入 <memory_system> 块。',
174
- fBudget: '注入预算(字符)', fBudgetHint: '记忆块总预算,超出部分截断。',
249
+ fBudget: '注入预算(字符)', fBudgetHint: '记忆块总预算,超出部分截断。默认 1600(≈400-600 token/轮);活跃会话每轮都注入这段背景,调低更省 token,调高保留更多记忆。',
250
+ fSnapGap: '快照最小间隔(轮)', fSnapGapHint: '动态记忆快照内容变化后,至少隔 N 轮才重新注入,避免每轮日志微变都追加快照导致历史膨胀。默认 5;0=每轮都尝试(仍受内容变化约束)。',
251
+ fReinjectOnCompact: '压缩后立即重注入快照', fReinjectOnCompactHint: '上下文被压缩/截断(通常伴随 contextVersion 重置)后,快照会被清掉;开启后强制立即重注入一次,确保记忆背景重建。默认开。',
252
+ fPromptCustom: '自定义记忆注入 prompt', fPromptCustomHint: '可覆盖各层提示文案(小众功能)。支持占位符 {date} {ws} {budget} {n}。改坏了可一键恢复默认。',
175
253
  fDays: '注入最近日志天数', fDaysHint: '会话开始时注入最近 N 天的工作日志尾部。默认 1。',
176
254
  fExtBudget: '外部记忆注入预算(字符)', fExtBudgetHint: '外部记忆来源在上下文中的注入预算。默认 1400(路径模式下影响有限)。',
177
255
  fConsolidateMin: '自动沉淀内容门槛(字符)', fConsolidateMinHint: '本轮 user+assistant 总字符低于此值视为寒暄跳过。默认 240。',
178
256
  fAway: '暂离阈值(分钟)', fAwayHint: '距上次活动超过该值视为暂离,回归时自动弹出记忆窗口。默认 60。',
257
+ fAutoPopup: '自动弹出记忆窗口', fAutoPopupHint: '暂离/回归时自动弹出记忆窗口(corner)并欢迎;关闭后只能手动打开。默认开。',
258
+ fUnattended: '无人值守模式', fUnattendedHint: '面向无人值守批量任务(托管/夜间)。开启后不注入欢迎回来指令、行为指令、暂离/回归提示、日历提醒——只注入纯事实记忆,避免无人值守时模型在寒暄上浪费 token。与模型侧的"托管模式"判断联动。默认关。',
259
+ fUnattendedAuto: '夜间/非工作时间自动托管', fUnattendedAutoHint: '开启后,本地时间处于非工作时间窗(默认 22:00-08:00,可在配置中调 unattendedAutoHours)或检测到自动托管任务时,自动进入无人值守模式,不弹欢迎窗、不注入寒暄。手动开关优先;默认关。',
179
260
  fAutoConsolidate: '自动沉淀(每轮对话结束AI评估)', fAutoConsolidateHint: '关闭后每轮对话结束不再自动调用 AI 评估与写入今日日志。',
180
261
  fConsolidate: '自动沉淀间隔(分钟)', fConsolidateHint: '两轮自动沉淀之间的最短间隔。默认 30;非工作时间(22:00-08:00)自动翻倍,避免短时间耗尽每日额度。',
181
262
  fConsolidateMax: '自动沉淀每日额度(次)', fConsolidateMaxHint: '每天最多触发自动沉淀的次数,到点后当天不再调用。默认 8。',
@@ -201,7 +282,40 @@ window.__ModuleLoader__.load({
201
282
  memoryPanel: 'Memory Panel',
202
283
  memory: 'Memory',
203
284
  autoMemory: 'Auto Memory',
204
- overview: 'Overview', logs: 'Logs', notes: 'Notes', reflections: 'Reflections', connect: 'Connect', calendar: 'Calendar', search: 'Search', workspaces: 'Workspaces',
285
+ overview: 'Overview', logs: 'Logs', refineTab: 'Recall review', notes: 'Notes', reflections: 'Reflections', connect: 'Connect', calendar: 'Calendar', search: 'Search', workspaces: 'Workspaces', hubTab: 'Memory Hub', storageTab: 'Storage',
286
+ hubSkills: 'Skills (Procedural)', hubSkillsEmpty: 'No solidified skills yet. Repeatedly-successful workflows become skills and are recalled in similar contexts.', hubFacts: 'Facts (Semantic)', hubFactsEmpty: 'No solidified facts yet.', hubConflicts: 'Pending conflicts', hubEpisodic: 'Episodes (Episodic)', hubEpisodicEmpty: 'No consolidated episodes yet.',
287
+ storageScanHint: 'Corpus health = compare each source\'s index (sidecar) against its body digest. After you hand-edit a memory file the index no longer matches, and that memory drops out of retrieval until the index is rebuilt.',
288
+ storageDeleteHint: 'Delete = atomic body removal + in-flight activation purge + derived-fact revocation (cascading). Evidence already recorded (seen) is never rewritten.',
289
+ secSemantic: 'Semantic engine', semMode: 'Retrieval mode', semAuto: 'Auto (recommended)', semLexOnly: 'Lexical only', semJs: 'Built-in semantic', semPy: 'Advanced Python',
290
+ fAssocEngine: 'Enable automatic memory engine', fAssocEngineHint: 'Master switch. On = auto-observe context, semantic retrieval, and timely memory-activation injection (costs a little token). Off = the whole engine stops — no retrieval, no decide, no injection, no activation records. For users concerned about token cost or off-course actions. Default off.',
291
+ secMemoryHubHint: 'Memory Hub = the orchestrator for three memory layers (episodic / semantic / procedural). When on, it distills episodes from dialogue, solidifies facts, and turns repeatedly-successful workflows into skills that are auto-recalled in similar contexts.',
292
+ fMemoryHub: 'Enable Memory Hub', fMemoryHubHint: 'Master switch. On = the three memory layers (episodic / semantic / procedural) start running; Off = keep existing memories but stop distilling new ones. Default off.',
293
+ fEpisodicMin: 'Min segments per episode', fEpisodicMinHint: 'How many dialogue segments an episode needs before it is consolidated. Too low = noise; too high = small talks discarded. Default 2.',
294
+ fEpisodicRet: 'Episode retention (count)', fEpisodicRetHint: 'Max recent episodes kept; oldest are evicted beyond this. Default 256.',
295
+ fProcSessions: 'Skill promotion sessions', fProcSessionsHint: 'A workflow must appear in N distinct sessions before it can be promoted to a skill. Default 3 (M-04 meta-code).',
296
+ fProcSuccess: 'Skill promotion successes', fProcSuccessHint: 'A workflow must succeed N times before promotion. One success is not enough proof. Default 2.',
297
+ fProcCorr: 'Skill correction tolerance', fProcCorrHint: 'Max ratio of corrections/errors to total evidence for a workflow. Above this it stays a candidate. Default 0.3 (30%).',
298
+ fProcRisk: 'High-risk needs approval', fProcRiskHint: 'High-risk workflows (SSH/deploy/delete) require explicit user approval to promote, and are never auto-executed on similarity alone. Default on.',
299
+ fProcLevel: 'Skill injection form', fProcLevelHint: 'What form an active skill takes when injected: checklist = full steps + success criteria; excerpt = summary; hint = just "refer to this skill". High-risk auto-downgrades to hint.',
300
+ memoryHubViewHint: 'View Memory Hub content in the "Memory Hub" tab of the Memory panel.',
301
+ fJsCooldown: 'Activation cooldown (min)', fJsCooldownHint: 'After an auto-activation injection, do not decide again for N minutes, preventing consecutive activations from wasting tokens. Default 1; 0 = no cooldown.',
302
+ fJsDelta: 'Activation margin threshold (e5)', fJsDeltaHint: 'Inject only when the gap between top-1/top-2 candidates exceeds this (e5 cosine is tight, default 0.01; the bge-m3 calibrated value is 0.03). Lower = easier recall, higher = conservative. 0 = no filter.',
303
+ fEmitMode: 'Activation emit mode', fEmitModeHint: 'shadow = record decisions only, no injection (calibration); canary-explicit = inject only on explicit recall (recommended); active = inject on every decide. Shared by JS/Python tracks.',
304
+ fCandScheme: 'Activation candidate scheme', fCandSchemeHint: 'balanced = 3×40 chars (default, info/token balance); dense = 6×20 chars (more candidates, wider recall); custom = your own count & length.',
305
+ fCandN: 'Custom candidate count', fCandNHint: 'Candidate count for custom scheme (1-8).',
306
+ fJsExcerpt: 'Activation excerpt length (chars)', fJsExcerptHint: 'Max length of the Reference line in the Reference Tail. Default 40 = a few words/keyword level (saves tokens; model uses memory_read for details). Raise to inject more memory body. Range 20-480.',
307
+ semModeHint: 'Auto = built-in semantics when ready, lexical fallback otherwise; Advanced Python requires separate installation.',
308
+ fReasoning: 'Chain-of-thought listening', fReasoningHint: 'Include model reasoning in live observation (applies after restart). On by default — summary-style CoT from closed models is captured too.',
309
+ fChildObs: 'Branched-session observation', fChildObsHint: 'Sessions resumed across days are flagged as branched; enable to include them too. On by default.',
310
+ fTauHi: 'Activation thresholds (calibrated)', fTuningHint: 'Thresholds are owned by the calibrated policy JSON; these inputs are a preview tuning entry and apply on next round.',
311
+ refineTitle: 'Recall review & corpus refinement', refineSub: 'Give your ruling on each activation decision (A activate / P prefetch / S suppress / H harmful / E edit target). Rulings feed an append-only review queue for offline replay and policy evolution.',
312
+ refineEmpty: '(no activation records yet — they appear as shadow observation produces data)', refineLoadErr: 'Failed to load: ',
313
+ sent1: 'queued', semReady: 'Built-in semantic engine ready', semMissing: 'Semantic pack not downloaded (~130MB)', semStatusErr: 'status unknown', semGuide: 'Setup guide', semGuideJs: 'The built-in semantic engine downloads a ~130MB local model (multilingual-e5-small, quantized). It runs entirely on your machine — memories never leave it. After download it verifies checksums, builds the index, then switches on automatically. You can keep using lexical search meanwhile.', semGuidePy: 'The advanced Python engine runs BGE-M3 int8 (~563MB) via a local sidecar for the highest recall. It requires a guided install (Python runtime + model). Not required for normal use.', semLater: 'Later', semInstall: 'Install', semPending: 'will be available in an upcoming release; this guide will walk through it once shipped.',
314
+ semResolved: 'Active retrieval', tierC1: 'C1 lexical floor (BM25)', tierC2: 'C2 built-in semantic e5-small q8', tierC3: 'C3 advanced Python bge-m3',
315
+ semDlStart: 'Download', semDlRetry: 'Retry', semDlCancel: 'Cancel',
316
+ mAuto: 'Auto (CN mirror first)', mCn: 'CN · hf-mirror', mIntl: 'Intl · huggingface',
317
+ dlDownloading: 'Downloading', dlVerifying: 'Verifying (SHA256)', dlDone: 'Download complete', dlCancelled: 'Cancelled', dlError: 'Download failed',
318
+ fWelcomeTour: 'Welcome tour', fWelcomeTourHint: 'Auto-plays the step-by-step feature tour (with engine detection/download) on first launch; turn off to keep it manual. Replay anytime.', tourReplay: '▶ Replay tour',
205
319
  wsGenerating: 'Generating per-workspace summaries (one AI call each, may take a while)…', wsNone: 'No workspace with memory found', wsNoSummary: '(no summary)', wsOverview: 'Workspace summaries (all workspaces)',
206
320
  debugCenter: 'Debug Center', dbgLoading: 'Loading diagnostics…', dbgRefresh: 'Refresh', dbgRefreshing: 'Refreshing…',
207
321
  fMemoryRoot: 'Memory root', fMemoryRootHint: 'Centralized storage: all workspace memories live here (one subdir per workspace); legacy memories are auto-migrated.',
@@ -216,7 +330,7 @@ window.__ModuleLoader__.load({
216
330
  kvAvail: 'available', kvUnavail: 'unavailable', kvDup: 'Duplicate note headings', kvDupCount: '',
217
331
  kvMem: 'Memory files', kvMemUser: 'user', kvMemNotes: 'notes', kvMemLog: 'log', kvMemMissing: 'missing',
218
332
  kvWs: 'Current workspace', kvWsUnknown: '(unknown)', kvApi: 'API probes', unknown: '(unknown)',
219
- guideTitle: 'Welcome to dsh-auto-memory', guideSub: 'What this plugin does:',
333
+ guideTitle: 'Welcome to dsh-auto-memory (pre)', guideSub: 'What this plugin does:',
220
334
  gFeat1: 'Three-layer memory with automatic injection & retrieval: user / project notes / daily logs',
221
335
  gFeat2: 'Auto-consolidation after every turn: bugs fixed and decisions made are written into today\'s log automatically',
222
336
  gFeat3: 'AI period greetings with three-level drawers: open the Memory panel for a period-by-period digest',
@@ -271,11 +385,17 @@ window.__ModuleLoader__.load({
271
385
  fUserDir: 'User memory dir', fUserDirHint: 'Cross-project rules; supports ~ prefix; needs write permission.',
272
386
  fProjectDir: 'Project memory dir', fProjectDirHint: 'Directory name relative to each workspace (default .dsh-memory).',
273
387
  fInject: 'Inject memory context', fInjectHint: 'Auto-inject <memory_system> block into every prompt.',
274
- fBudget: 'Injection budget (chars)', fBudgetHint: 'Total budget for the memory block; excess is truncated.',
388
+ fBudget: 'Injection budget (chars)', fBudgetHint: 'Total budget for the memory block; excess is truncated. Default 1600 (~400-600 tokens/turn); this background is injected every turn, so lower = fewer tokens, higher = more memory retained.',
389
+ fSnapGap: 'Snapshot min gap (turns)', fSnapGapHint: 'After the dynamic memory snapshot changes, re-inject at least N turns later, so small per-turn log changes do not append a new snapshot every turn (history bloat). Default 5; 0 = try every turn (still change-gated).',
390
+ fReinjectOnCompact: 'Re-inject snapshot immediately after compaction', fReinjectOnCompactHint: 'When the context is compacted/truncated (usually a contextVersion reset), the snapshot is cleared; on = force re-inject once so the memory background rebuilds. Default on.',
391
+ fPromptCustom: 'Customize memory-injection prompt', fPromptCustomHint: 'Override any prompt layer (power feature). Placeholders: {date} {ws} {budget} {n}. One-click reset restores defaults.',
275
392
  fDays: 'Recent days injected', fDaysHint: 'Inject tails of the last N days of work logs at session start. Default 1.',
276
393
  fExtBudget: 'External memory injection budget (chars)', fExtBudgetHint: 'Budget for external memory sources in context. Default 1400 (limited effect with path mode).',
277
394
  fConsolidateMin: 'Auto-consolidation content threshold (chars)', fConsolidateMinHint: 'Turns with fewer combined user+assistant chars are treated as chit-chat and skipped. Default 240.',
278
395
  fAway: 'Away threshold (minutes)', fAwayHint: 'Marked away when inactive longer than this; the memory panel auto-opens on return. Default 60.',
396
+ fAutoPopup: 'Auto-open memory panel', fAutoPopupHint: 'Auto-open the memory panel (corner) with a welcome when returning from away; disabled = open manually only. Default on.',
397
+ fUnattended: 'Unattended / headless mode', fUnattendedHint: 'For unattended batch tasks (hosted/nightly). On = no welcome-back directives, no behavioral instructions, no away/return prompts, no calendar reminders — only factual memory is injected, so the model wastes no tokens on niceties. Pairs with the model-side hosted-mode detection. Default off.',
398
+ fUnattendedAuto: 'Auto-unattended at night / off-hours', fUnattendedAutoHint: 'When on, auto-enters unattended mode during off-hours (default 22:00-08:00, tunable via unattendedAutoHours) or when a hosted task is detected — no welcome popup, no niceties. Manual toggle takes precedence; default off.',
279
399
  fAutoConsolidate: 'Auto-consolidation (AI review each turn end)', fAutoConsolidateHint: 'When off, no automatic AI review or daily-log writes at turn end.',
280
400
  fConsolidate: 'Auto-consolidate interval (minutes)', fConsolidateHint: 'Minimum gap between auto-consolidations. Default 30; doubled automatically outside work hours (22:00-08:00).',
281
401
  fConsolidateMax: 'Daily auto-consolidate quota', fConsolidateMaxHint: 'Max auto-consolidation runs per day; no more runs after the quota is reached. Default 8.',
@@ -320,6 +440,21 @@ window.__ModuleLoader__.load({
320
440
  }
321
441
  var fontScale = 'lg'
322
442
  var FONT_SCALES = { sm: '小', md: '标准', lg: '大', xl: '特大' }
443
+ // 与服务端 lib/index.js DEFAULT_PROMPT_LAYERS 保持一致(设置页显示各层默认文案)
444
+ var DEFAULT_PROMPT_LAYERS_CLIENT = {
445
+ snapshotHead: '<memory_system>\n[记忆定位 — 读法]\n以下记忆文本只是背景事实与规则参考…',
446
+ snapshotMeta: '自动记忆已启用。工作区: {ws} | 日期: {date}(日界 {dayBoundary} 分钟,凌晨归前一天){consolidate}',
447
+ snapshotLogsTitle: '最近 {n} 天工作日志(尾部)',
448
+ snapshotReflectionTitle: '最近反思 {date}(前一天工作精华)',
449
+ snapshotUserTitle: '用户级记忆 ~/.dsh/memory/MEMORY.md — 跨项目,必须遵守',
450
+ snapshotNotesTitle: '项目长期笔记',
451
+ snapshotExternalTitle: '[外部记忆 — 其他 AI 工具遗产,可继承(内容按需读取,不整段注入)]',
452
+ snapshotCalendarTitle: '[日历与日程(未完成)]',
453
+ snapshotWelcomeTitle: '[欢迎回来]',
454
+ snapshotWelcomeBody: '用户离开已超过 1 小时(暂离/下班后回来)。在本轮回复的开头,先用一句简短温暖的话欢迎用户回来…',
455
+ snapshotInscription: '[铭文 · 每轮提醒 {date}]',
456
+ snapshotTail: '</memory_system>',
457
+ }
323
458
  var FONT_SCALE_VALUES = { sm: '0.9', md: '1', lg: '1.15', xl: '1.3' }
324
459
  var accentTheme = 'deepseek'
325
460
  var graphDensity = 'relaxed'
@@ -337,6 +472,7 @@ window.__ModuleLoader__.load({
337
472
  // 暂离检测:优先用 host 定时检测的 away 状态(阈值可配 awayMinutes),回退本地 lastActive 旧逻辑
338
473
  var hostAway = false
339
474
  var hostAwayReady = false
475
+ var autoPopupEnabled = true
340
476
  function isAway() {
341
477
  if (hostAwayReady) return hostAway
342
478
  var lastSeen = 0
@@ -346,6 +482,34 @@ window.__ModuleLoader__.load({
346
482
 
347
483
  // ───────────────────────── 更新弹窗 / 首次指导 ─────────────────────────
348
484
  var CHANGELOG = {
485
+ '0.1.30': { zh: [
486
+ '★ 大更新:全新「欢迎向导」——首次启动/升级后分步介绍全部功能,每项当场开关(自动联想/周期快照/暂离问候/夜间托管/每日反思/定时总结/外部记忆/技能固化…),语义引擎检测/下载/自检与外部来源实时扫描全部内联在向导里完成。',
487
+ '★ 全新品牌视觉:Office/Fluent 式液态玻璃应用图标族——每步一枚彩色玻璃 Squircle 图标(注入青蓝/问候暖金/日历青绿/引擎紫蓝/雷达天青/完成珊瑚金),配专属循环动效(铃摆/翻页/双环/棱镜旋转/雷达扫描/火花上升);「核心能力」步保留标志性三层磨砂玻璃板 Logo。',
488
+ '★ 更新日志开场动画:打开更新说明时先播放三层玻璃 Logo 组装→展开→消散,再浮现更新内容;点击任意处可跳过;内容过长自动滚动。',
489
+ '★ 无人值守就绪:设置→自动化提供「无人值守模式」与「夜间/批量自动托管」(22:00-08:00);托管期间不注入欢迎语/寒暄/行为指令,日历静默,上下文稳定,面向长批处理任务。',
490
+ '修复:DSH Desktop 增强模式(透明/Mica 窗口材质)下记忆面板半透明、文字几乎不可读——打开时实测主题令牌 alpha,过低时自动提升到 0.96(保留色相),普通模式玻璃观感零变化。感谢 @ProperSAMA(PR #12)。',
491
+ '修复:记忆面板默认位置不再遮挡侧边栏「记忆」入口按钮——改为锚定按钮正上方,重叠时自动让出;支持点击面板外部或按 Esc 关闭。感谢 @ProperSAMA(PR #12)。',
492
+ '修复:更新弹窗在小卡形态下内容被裁剪、底部按钮不可达导致无法关闭——新增右上 ✕ 关闭(同步已读版本),内容区自动滚动。',
493
+ '改进:「唤起回顾」决策↔投递时间线——每条决策标注真实投递结果(✓投递×N/未投递/技能✓),判定队列汇总与政策提示;记忆固化(stale 门降版本容忍,语料升版不再整单压制召回)。',
494
+ ], en: [
495
+ '★ MAJOR: Brand-new Welcome Tour — after first launch or upgrade, every feature is introduced step by step with per-feature switches right in the tour (association / snapshot / greeting / night unattended / reflection / summaries / external memory / skill promotion…); engine detection, download, self-test and live external-source scanning are all inline.',
496
+ '★ New brand visuals: an Office/Fluent-style liquid-glass app icon family — each step gets its own colored glass squircle icon (cyan inject / amber greeting / green calendar / violet engine / sky radar / coral finish) with dedicated looping motion (bell sway, page flip, linked rings, prism spin, radar sweep, rising spark); the signature three-slab frosted-glass logo stays on the Core step.',
497
+ '★ Changelog intro animation: opening the update notes now plays the glass logo assembling → expanding → dissolving before the content fades in; click anywhere to skip; long content scrolls automatically.',
498
+ '★ Unattended-ready: Settings → Automation offers "Unattended mode" and "Auto-unattended at night" (22:00-08:00); while engaged, greetings/niceties/behavioural directives are stripped and the calendar stays silent — built for long batch jobs.',
499
+ 'Fix: In DSH Desktop enhanced mode (transparent/Mica materials) the memory panel was semi-transparent and barely readable — token alpha is measured on open and raised to 0.96 when too low (hue preserved); normal modes keep their glass look. Thanks @ProperSAMA (PR #12).',
500
+ 'Fix: The panel no longer covers the sidebar "Memory" entry button — default position anchors above it, overlapping geometry auto-yields, and outside-click/Esc close is supported. Thanks @ProperSAMA (PR #12).',
501
+ 'Fix: The update dialog could become impossible to close when its content overflowed the compact card — added an ✕ in the top-right (marks version seen) and made the content area scrollable.',
502
+ 'Improved: Recall-review decision↔delivery timeline (delivered×N / not delivered / skill badges per decision), review-queue digest with policy hints, and consolidation stale-gate version tolerance so corpus version bumps no longer suppress recall.',
503
+ ] },
504
+ '0.1.29': { zh: [
505
+ '修复:工作区总览一直显示「未发现带记忆的工作区」——DSH 新版会话持久化改为 session.jsonl.zstd 压缩帧,旧逻辑只认 .jsonl 扫描不到任何会话;现已用 zstd 解压读取首行提取 cwd,并让空的 summary 缓存短时失效,避免空结果被永久固化。',
506
+ '新增:设置页「自动化」新增「自动弹出记忆窗口」开关——关闭后暂离/回归不再自动弹出 corner 问候栏,只能手动打开。',
507
+ '提示:这是近期稳定版(0.1.x 维护线)的发布。实验版(下一大版本)预计未来几周内发布,在此之前稳定版仅做维护性更新。',
508
+ ], en: [
509
+ 'Fix: Workspace overview kept showing "No memory workspaces found" — DSH now stores sessions as session.jsonl.zstd compressed frames that were not recognized; the overview now decompresses them to read the first line and extract each workspace, and empty summary caches expire instead of being fixed forever.',
510
+ 'New: "Auto-open memory panel" toggle in Settings → Automation - turn it off to stop auto-opening the corner greeting on away/return; the panel stays manual-only.',
511
+ 'Note: this is a recent stable-line (0.1.x maintenance) release. The experimental next major version is expected within a few weeks; until then the stable line only receives maintenance updates.',
512
+ ] },
349
513
  '0.1.28': { zh: [
350
514
  '脏 token 检查器(prion-scan 四类启发式整合):mojibake GBK 残骸特征表补全至 34 项(与 prion-scan.mjs 逐字一致),写入闸门新增 raw JSON envelope(memoryBlock/"uid"/updatedAt/"role")与 base64 残骸行拒写——外部 AI 工具画像无法再整段混入。',
351
515
  '新增「扫描脏 token」:设置页调试中心一键扫描用户级/项目笔记/每日日志/反思,按行区间返回 mojibake / raw JSON / 超长行 / base64 / 重复块报告(只给位置,不含正文)。',
@@ -363,7 +527,7 @@ window.__ModuleLoader__.load({
363
527
  'External memory import records only path pointers; injection scrubs mojibake/code-block/stutter lines; the injected block adds "how to read memory" and the voice discipline (entries must be third-person objective statements).',
364
528
  ] },
365
529
  '0.1.20': { zh: [
366
- '修复:正式发布流程 cordis.patch.yml(loader 入口 id + 包名)转换事故——发布包与开发版 identity 完全隔离,不再互相撞车。',
530
+ '修复:正式发布流程 cordis.patch.yml(loader 入口 id + 包名)转换事故——发布包与预览版 identity 完全隔离,不再互相撞车。',
367
531
  ], en: [
368
532
  'Fix: cordis.patch.yml conversion mishap in the release pipeline (loader entry id + package name) — published package identity is now fully isolated from the dev build.',
369
533
  ] },
@@ -391,7 +555,7 @@ window.__ModuleLoader__.load({
391
555
  'Stability: fixed host freezes/crashes at turn end (lazy projection removed, timeout fallbacks, delayed start); auto-consolidation recovered with daily rate limiting.',
392
556
  ] },
393
557
  '0.1.23': { zh: [
394
- '修复:正式发布包首次欢迎文案仍显示“开发版”;发布转换与残留校验已加强,确保开发版和正式版身份完全隔离。',
558
+ '修复:正式发布包首次欢迎文案仍显示“预览版”;发布转换与残留校验已加强,确保预览版和正式版身份完全隔离。',
395
559
  ], en: [
396
560
  'Fix: the published first-run guide still showed a dev label; release conversion and residual checks now enforce complete dev/release identity isolation.',
397
561
  ] },
@@ -477,6 +641,8 @@ window.__ModuleLoader__.load({
477
641
  if (!d) return 0
478
642
  if (d.kind === 'update') return 100
479
643
  if (d.kind === 'first') return 90
644
+ if (d.kind === 'welcomeTour') return 85
645
+ if (d.kind === 'modelDownload') return 85
480
646
  if (d.kind === 'notice') return d.notice && d.notice.level === 'urgent' ? 80 : 70
481
647
  if (d.kind === 'summary') return 60
482
648
  if (d.kind === 'welcomeBack') return 50
@@ -501,6 +667,10 @@ window.__ModuleLoader__.load({
501
667
  }
502
668
  function closeDialog() { dialogState = dialogQueue.shift() || null; notifyDialog() }
503
669
  function onDialog(fn) { dialogListeners.add(fn); return function () { dialogListeners.delete(fn) } }
670
+ // 调试/重看入口:控制台 window['dsh-auto-memory.openWelcomeTour']() 随时重开首启向导
671
+ try {
672
+ window['dsh-auto-memory.openWelcomeTour'] = function () { openDialog({ kind: 'welcomeTour' }) }
673
+ } catch (eOpenTour) {}
504
674
  function cmpVersion(a, b) {
505
675
  var pa = String(a).split('.').map(Number), pb = String(b).split('.').map(Number)
506
676
  for (var i = 0; i < 3; i++) {
@@ -542,6 +712,7 @@ window.__ModuleLoader__.load({
542
712
  summarize: '/api/dsh-auto-memory/summarize',
543
713
  greet: '/api/dsh-auto-memory/greet',
544
714
  notices: '/api/dsh-auto-memory/notices',
715
+ models: '/api/dsh-auto-memory/models',
545
716
  }
546
717
  function query(params) {
547
718
  var search = new URLSearchParams()
@@ -584,6 +755,7 @@ window.__ModuleLoader__.load({
584
755
  '[data-dam-panel][data-scale="xl"] { --dam-scale: 1.3; }',
585
756
  '[data-dam-panel]::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 64%; pointer-events: none;',
586
757
  ' background: linear-gradient(180deg, rgba(255,255,255,.13), rgba(255,255,255,0) 70%); border-radius: 16px 16px 0 0; }',
758
+ '[data-dam-panel][data-solid="true"]::before { background: linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,0) 70%); }',
587
759
  '[data-dam-panel][data-dragging="true"] { user-select: none; }',
588
760
  '[data-dam-panel] header { display: flex; align-items: center; gap: 8px; padding: 10px 14px; cursor: grab;',
589
761
  ' border-bottom: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.25)) 55%, transparent); }',
@@ -642,7 +814,10 @@ window.__ModuleLoader__.load({
642
814
  '[data-dam-disclosure][data-phase="closing"] { opacity: 0; max-height: 0; transform: translateY(-5px) scale(.985); }',
643
815
  '[data-dam-card], [data-dam-banner] { animation: dam-rise .34s cubic-bezier(.22,.8,.2,1) both; }',
644
816
  '@keyframes dam-rise { from { opacity: 0; transform: translateY(7px) scale(.988); } to { opacity: 1; transform: translateY(0) scale(1); } }',
645
- '@media (prefers-reduced-motion: reduce) { [data-dam-tab-strip], [data-dam-tab], [data-dam-disclosure], [data-dam-card], [data-dam-banner] { transition: none !important; animation: none !important; } }',
817
+ '@media (prefers-reduced-motion: reduce) { [data-dam-tab-strip], [data-dam-tab], [data-dam-disclosure], [data-dam-card], [data-dam-banner], [data-dam-tour-orb-wrap] *, [data-dam-update-box] * { transition: none !important; animation: none !important; }',
818
+ ' [data-dam-tour-bokeh], [data-dam-tour-slab], [data-dam-tour-orb-core] { opacity: 1 !important; transform: none !important; filter: none !important; }',
819
+ ' [data-dam-update-stage] { display: none !important; }',
820
+ ' [data-dam-update-content] { opacity: 1 !important; transform: none !important; animation: none !important; } }',
646
821
  '[data-dam-calendar] { animation: dam-rise .34s cubic-bezier(.22,.8,.2,1) both; }',
647
822
  '[data-dam-calendar] [data-dam-calendar-day] { transition: transform .18s ease, border-color .2s ease, background .2s ease, box-shadow .2s ease; }',
648
823
  '[data-dam-calendar] [data-dam-calendar-day]:hover { transform: translateY(-1px); border-color: var(--dam-accent, #1d4ed8) !important; box-shadow: 0 4px 12px rgba(0,0,0,.08); }',
@@ -667,6 +842,201 @@ window.__ModuleLoader__.load({
667
842
  '@keyframes dam-spin { to { transform: rotate(360deg); } }',
668
843
  '@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {',
669
844
  ' [data-dam-panel] { background: var(--dsw-alias-bg-overlay, #ffffff); } }',
845
+ // ── 首启引导向导(Win11 OOBE × macOS 欢迎 × Liquid Glass) ──
846
+ '[data-dam-tour-backdrop] { position: fixed; inset: 0; z-index: 2147482900; display: flex; align-items: center; justify-content: center;',
847
+ ' background: radial-gradient(ellipse at 50% 42%, rgba(20,30,60,.30), rgba(6,10,22,.44)); backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px); animation: dam-tour-fade .28s ease both; }',
848
+ '@keyframes dam-tour-fade { from { opacity: 0 } to { opacity: 1 } }',
849
+ '[data-dam-tour] { position: relative; width: min(620px, calc(100vw - 48px)); border-radius: 22px; overflow: hidden; padding-bottom: 4px;',
850
+ ' background: color-mix(in srgb, var(--dsw-alias-bg-overlay, rgba(30,34,46,.9)) 60%, transparent);',
851
+ ' backdrop-filter: blur(30px) saturate(1.6); -webkit-backdrop-filter: blur(30px) saturate(1.6);',
852
+ ' border: 1px solid rgba(255,255,255,.5);',
853
+ ' box-shadow: 0 32px 90px rgba(8,14,38,.5), 0 6px 24px rgba(8,14,38,.28), inset 0 1px 0 rgba(255,255,255,.5), inset 0 -1px 0 rgba(255,255,255,.14);',
854
+ ' color: var(--dsw-alias-label-primary, #1f2328); font: 13.5px/1.6 system-ui, "Segoe UI", sans-serif; --dam-mx: 50%; --dam-my: 18%;',
855
+ ' animation: dam-tour-pop .38s cubic-bezier(.2,.9,.3,1.16) both; }',
856
+ '@keyframes dam-tour-pop { from { opacity: 0; transform: scale(.9) translateY(22px); } to { opacity: 1; transform: none; } }',
857
+ '[data-dam-tour]::before { content: ""; position: absolute; inset: 0 0 auto 0; height: 55%; pointer-events: none;',
858
+ ' background: linear-gradient(168deg, rgba(255,255,255,.20), rgba(255,255,255,0) 58%); }',
859
+ '[data-dam-tour-glare] { position: absolute; inset: 0; pointer-events: none; z-index: 1;',
860
+ ' background: radial-gradient(300px circle at var(--dam-mx) var(--dam-my), rgba(255,255,255,.16), transparent 62%); mix-blend-mode: screen; }',
861
+ '[data-dam-tour-close] { position: absolute; top: 12px; right: 12px; z-index: 3; width: 30px; height: 30px; border-radius: 50%;',
862
+ ' border: 1px solid rgba(255,255,255,.35); background: rgba(255,255,255,.10); cursor: pointer; opacity: .7; font-size: 13px; color: inherit; line-height: 1; }',
863
+ '[data-dam-tour-close]:hover { opacity: 1; background: rgba(255,255,255,.22); }',
864
+ // ── 首启向导 Logo:三层磨砂玻璃板堆叠 ──
865
+ '[data-dam-tour-orb-wrap] { position: relative; width: 150px; height: 150px; margin: 34px auto 4px; perspective: 680px; z-index: 2;',
866
+ ' transform-style: preserve-3d; --dam-slab-z: 22px; }',
867
+ '[data-dam-tour-orb-wrap]::before { content: ""; position: absolute; inset: 0; border-radius: 50%;',
868
+ ' background: radial-gradient(ellipse at 50% 45%, rgba(36,86,196,.14), transparent 62%); pointer-events: none; }',
869
+ '[data-dam-tour-bokeh] { position: absolute; border-radius: 50%; filter: blur(16px); opacity: 0; pointer-events: none;',
870
+ ' animation: dam-bokeh-in .55s ease both, dam-bokeh-drift 9s ease-in-out infinite; }',
871
+ '[data-dam-tour-bokeh="a"] { width: 72px; height: 72px; left: 10px; top: 18px;',
872
+ ' background: radial-gradient(circle at 35% 35%, rgba(77,107,254,.80), rgba(77,107,254,.18) 55%, transparent 72%); animation-delay: .38s, 0s; }',
873
+ '[data-dam-tour-bokeh="b"] { width: 86px; height: 86px; right: 4px; top: 26px;',
874
+ ' background: radial-gradient(circle at 40% 40%, rgba(155,126,255,.72), rgba(155,126,255,.16) 58%, transparent 76%); animation-delay: .50s, -2.4s; }',
875
+ '[data-dam-tour-bokeh="c"] { width: 64px; height: 64px; left: 32px; bottom: 8px;',
876
+ ' background: radial-gradient(circle at 45% 45%, rgba(77,107,254,.68), rgba(100,80,230,.14) 56%, transparent 74%); animation-delay: .62s, -5.1s; }',
877
+ '@keyframes dam-bokeh-in { from { opacity: 0; transform: scale(.7); } to { opacity: .92; transform: scale(1); } }',
878
+ '@keyframes dam-bokeh-drift { 0%, 100% { transform: translate(0, 0) scale(1); } 33% { transform: translate(6px, -5px) scale(1.04); } 66% { transform: translate(-4px, 5px) scale(.97); } }',
879
+ '[data-dam-tour-stage] { position: absolute; left: 50%; top: 54%; width: 88px; height: 88px; transform-style: preserve-3d;',
880
+ ' transform: translate(-50%, -50%) rotateX(55deg) rotateZ(45deg);',
881
+ ' filter: drop-shadow(0 22px 34px rgba(4,8,20,.42)); animation: dam-stage-float 4.6s ease-in-out infinite; }',
882
+ '@keyframes dam-stage-float { 0%, 100% { transform: translate(-50%, -50%) rotateX(55deg) rotateZ(45deg) translateZ(0); } 50% { transform: translate(-50%, calc(-50% - 5px)) rotateX(55deg) rotateZ(45deg) translateZ(4px); } }',
883
+ '[data-dam-tour-slab] { position: absolute; left: 0; top: 0; width: 88px; height: 88px; border-radius: 23px; transform-style: preserve-3d;',
884
+ ' background: linear-gradient(135deg, rgba(255,255,255,.17), rgba(255,255,255,.06));',
885
+ ' border: 1.5px solid rgba(255,255,255,.52);',
886
+ ' box-shadow: inset 0 1px 0 rgba(255,255,255,.62), inset 0 0 26px rgba(255,255,255,.13);',
887
+ ' backdrop-filter: blur(6px) saturate(1.35); -webkit-backdrop-filter: blur(6px) saturate(1.35);',
888
+ ' animation: dam-slab-drop .62s cubic-bezier(.2,.9,.3,1.15) both; }',
889
+ '[data-dam-tour-slab]::before { content: ""; position: absolute; inset: -1px; border-radius: 23px; transform: translateZ(-7px); transform-style: preserve-3d;',
890
+ ' background: linear-gradient(135deg, rgba(255,255,255,.09), rgba(255,255,255,.03));',
891
+ ' border: 1px solid rgba(255,255,255,.18); box-shadow: 0 0 0 1px rgba(255,255,255,.04); }',
892
+ '[data-dam-tour-slab]::after { content: ""; position: absolute; inset: 6px; border-radius: 18px; opacity: 0;',
893
+ ' background: conic-gradient(from var(--dam-orb-a, 0deg), transparent 0deg, rgba(255,255,255,.42) 24deg, transparent 72deg, transparent 252deg, rgba(255,255,255,.22) 288deg, transparent 336deg);',
894
+ ' filter: blur(2px); mix-blend-mode: screen; animation: dam-orb-shine 6.5s linear infinite; }',
895
+ '[data-dam-tour-slab="top"] { transform: translateZ(calc(var(--dam-slab-z) * 1)); animation-delay: 0s; z-index: 3; }',
896
+ '[data-dam-tour-slab="top"]::after { opacity: 1; }',
897
+ '[data-dam-tour-slab="mid"] { transform: translateZ(0); animation-delay: .16s; z-index: 2; }',
898
+ '[data-dam-tour-slab="bot"] { transform: translateZ(calc(var(--dam-slab-z) * -1)); animation-delay: .32s; z-index: 1; }',
899
+ '@keyframes dam-slab-drop { from { opacity: 0; transform: translateY(-28px) scale(.85) translateZ(var(--dam-slab-from-z, 0)); } to { opacity: 1; transform: translateY(0) scale(1) translateZ(var(--dam-slab-to-z, 0)); } }',
900
+ '[data-dam-tour-slab="top"] { --dam-slab-from-z: calc(var(--dam-slab-z) * 1.5); --dam-slab-to-z: calc(var(--dam-slab-z) * 1); }',
901
+ '[data-dam-tour-slab="mid"] { --dam-slab-from-z: 0; --dam-slab-to-z: 0; }',
902
+ '[data-dam-tour-slab="bot"] { --dam-slab-from-z: calc(var(--dam-slab-z) * -1.5); --dam-slab-to-z: calc(var(--dam-slab-z) * -1); }',
903
+ '@property --dam-orb-a { syntax: "<angle>"; initial-value: 0deg; inherits: false; }',
904
+ '@keyframes dam-orb-shine { to { --dam-orb-a: 360deg; } }',
905
+ '[data-dam-tour-orb-core] { position: absolute; right: -10px; top: -6px; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; font-size: 20px;',
906
+ ' border-radius: 12px; transform: translateZ(48px) rotateX(-55deg) rotateZ(-45deg); transform-style: preserve-3d;',
907
+ ' background: linear-gradient(135deg, rgba(255,255,255,.22), rgba(255,255,255,.08));',
908
+ ' border: 1px solid rgba(255,255,255,.50); box-shadow: inset 0 1px 0 rgba(255,255,255,.55), 0 8px 18px rgba(18,34,90,.28);',
909
+ ' backdrop-filter: blur(5px); -webkit-backdrop-filter: blur(5px);',
910
+ ' filter: drop-shadow(0 4px 8px rgba(18,38,120,.28)); animation: dam-core-pop .40s cubic-bezier(.2,.9,.3,1.25) both; }',
911
+ '@keyframes dam-core-pop { from { opacity: 0; transform: translateZ(62px) rotateX(-55deg) rotateZ(-45deg) scale(.6); } to { opacity: 1; transform: translateZ(48px) rotateX(-55deg) rotateZ(-45deg) scale(1); } }',
912
+ // ── 每步 Office/Fluent 式彩色玻璃图形:每步只渲染自身 DOM,并拥有专属循环动画 ──
913
+ // 非 store 步采用 Microsoft Office/Fluent 式彩色玻璃 Squircle 底牌;物件作为正面主符号,不再叠在菱形托盘上。
914
+ '[data-dam-tour-app-tile] { --tile-rgb: 83,122,255; --tile-rgb-2: 151,111,255; position:absolute; left:50%; top:50%; width:92px; height:92px; margin:-46px 0 0 -46px; z-index:3; border-radius:27px;',
915
+ ' background:linear-gradient(145deg,rgba(255,255,255,.35) 0%,rgba(var(--tile-rgb),.52) 42%,rgba(var(--tile-rgb-2),.34) 100%); border:1.5px solid rgba(255,255,255,.65); box-shadow:inset 0 2px 0 rgba(255,255,255,.68),inset 0 -14px 26px rgba(17,32,88,.18),0 20px 38px rgba(var(--tile-rgb),.25); backdrop-filter:blur(12px) saturate(1.5); -webkit-backdrop-filter:blur(12px) saturate(1.5); animation:dam-tile-enter .55s cubic-bezier(.2,.9,.3,1.18) both,dam-tile-breathe 5s .6s ease-in-out infinite; overflow:hidden; }',
916
+ '[data-dam-tour-app-tile]::before { content:""; position:absolute; left:9%; top:6%; width:68%; height:34%; border-radius:50%; background:linear-gradient(105deg,rgba(255,255,255,.48),rgba(255,255,255,0)); filter:blur(4px); }',
917
+ '[data-dam-tour-orb-wrap][data-art="inject"] [data-dam-tour-app-tile] { --tile-rgb:76,201,240; --tile-rgb-2:85,120,255; }',
918
+ '[data-dam-tour-orb-wrap][data-art="bell"] [data-dam-tour-app-tile] { --tile-rgb:255,177,69; --tile-rgb-2:255,103,111; }',
919
+ '[data-dam-tour-orb-wrap][data-art="calendar"] [data-dam-tour-app-tile] { --tile-rgb:80,201,143; --tile-rgb-2:50,148,255; }',
920
+ '[data-dam-tour-orb-wrap][data-art="link"] [data-dam-tour-app-tile] { --tile-rgb:74,208,203; --tile-rgb-2:118,107,255; }',
921
+ '[data-dam-tour-orb-wrap][data-art="engine"] [data-dam-tour-app-tile] { --tile-rgb:142,104,255; --tile-rgb-2:48,154,255; }',
922
+ '[data-dam-tour-orb-wrap][data-art="radar"] [data-dam-tour-app-tile] { --tile-rgb:52,202,231; --tile-rgb-2:88,117,255; }',
923
+ '[data-dam-tour-orb-wrap][data-art="rocket"] [data-dam-tour-app-tile] { --tile-rgb:255,115,105; --tile-rgb-2:255,192,71; }',
924
+ '@keyframes dam-tile-enter { from{opacity:0;transform:scale(.72) rotate(-6deg) translateY(12px)} to{opacity:1;transform:scale(1) rotate(0) translateY(0)} }',
925
+ '@keyframes dam-tile-breathe { 0%,100%{transform:translateY(0) rotate(0)} 50%{transform:translateY(-5px) rotate(.8deg)} }',
926
+ '[data-dam-tour-art] { --art-rgb: 83,122,255; --art-rgb-2: 151,111,255; position: absolute; left: 50%; top: 50%; width: 64px; height: 64px; margin: -32px 0 0 -32px; z-index: 5; transform: translateZ(0); transform-style: preserve-3d; animation: dam-art-enter .46s cubic-bezier(.2,.9,.3,1.22) both, dam-art-float 4.2s .5s ease-in-out infinite; filter: drop-shadow(0 8px 16px rgba(15,28,75,.34)); }',
927
+ '[data-dam-tour-art="inject"] { --art-rgb: 76,201,240; --art-rgb-2: 85,120,255; }',
928
+ '[data-dam-tour-art="bell"] { --art-rgb: 255,177,69; --art-rgb-2: 255,103,111; }',
929
+ '[data-dam-tour-art="calendar"] { --art-rgb: 80,201,143; --art-rgb-2: 50,148,255; }',
930
+ '[data-dam-tour-art="link"] { --art-rgb: 74,208,203; --art-rgb-2: 118,107,255; }',
931
+ '[data-dam-tour-art="engine"] { --art-rgb: 142,104,255; --art-rgb-2: 48,154,255; }',
932
+ '[data-dam-tour-art="radar"] { --art-rgb: 52,202,231; --art-rgb-2: 88,117,255; }',
933
+ '[data-dam-tour-art="rocket"] { --art-rgb: 255,115,105; --art-rgb-2: 255,192,71; }',
934
+ '[data-dam-tour-art] .ap { position: absolute; box-sizing: border-box; background: linear-gradient(145deg, rgba(255,255,255,.56) 0%, rgba(var(--art-rgb),.38) 38%, rgba(var(--art-rgb-2),.22) 100%); border: 1.4px solid rgba(255,255,255,.72); box-shadow: inset 0 2px 0 rgba(255,255,255,.72), inset 0 -7px 13px rgba(var(--art-rgb-2),.18), 0 7px 17px rgba(var(--art-rgb),.24); backdrop-filter: blur(5px) saturate(1.4); -webkit-backdrop-filter: blur(5px) saturate(1.4); }',
935
+ '[data-dam-tour-art] .ap::after { content:""; position:absolute; left:18%; top:12%; width:48%; height:24%; border-radius:50%; background:linear-gradient(100deg,rgba(255,255,255,.65),rgba(255,255,255,0)); filter:blur(1.5px); pointer-events:none; }',
936
+ '@keyframes dam-art-enter { from { opacity:0; transform:translateY(-12px) scale(.72) rotate(-8deg); } to { opacity:1; transform:translateY(0) scale(1) rotate(0); } }',
937
+ '@keyframes dam-art-float { 0%,100% { transform:translateY(0) rotate(0); } 50% { transform:translateY(-5px) rotate(2deg); } }',
938
+ // welcome:主泡+两颗品牌色种子,持续呼吸
939
+ '[data-dam-tour-art="bubble"] .bubble-orb { left:10px; top:9px; width:44px; height:44px; border-radius:46% 54% 52% 48% / 50% 44% 56% 50%; animation:dam-bubble-breathe 3.4s ease-in-out infinite; }',
940
+ '[data-dam-tour-art="bubble"] .bubble-seed { border-radius:50%; background:radial-gradient(circle at 35% 30%,#fff 0%,rgba(var(--art-rgb),.9) 34%,rgba(var(--art-rgb-2),.42) 100%); border-color:rgba(255,255,255,.8); }',
941
+ '[data-dam-tour-art="bubble"] .s1 { left:19px; top:24px; width:12px; height:12px; } [data-dam-tour-art="bubble"] .s2 { left:34px; top:18px; width:9px; height:9px; animation:dam-seed-orbit 3s ease-in-out infinite; }',
942
+ '@keyframes dam-bubble-breathe { 0%,100%{border-radius:46% 54% 52% 48% / 50% 44% 56% 50%;transform:scale(1)} 50%{border-radius:54% 46% 47% 53% / 44% 56% 45% 55%;transform:scale(1.05)} }',
943
+ '@keyframes dam-seed-orbit { 0%,100%{transform:translate(0,0)} 50%{transform:translate(4px,-5px)} }',
944
+ // store:同视角彩色微缩板,各自错相浮动
945
+ '[data-dam-tour-art="store"] .plate { left:12px; width:40px; height:14px; border-radius:6px; transform:skewX(-18deg); }',
946
+ '[data-dam-tour-art="store"] .p1 { top:7px; animation:dam-plate-hover 3.2s 0s ease-in-out infinite; } [data-dam-tour-art="store"] .p2 { top:25px; animation:dam-plate-hover 3.2s .38s ease-in-out infinite; } [data-dam-tour-art="store"] .p3 { top:43px; animation:dam-plate-hover 3.2s .76s ease-in-out infinite; }',
947
+ '@keyframes dam-plate-hover { 0%,100%{transform:skewX(-18deg) translateY(0)} 50%{transform:skewX(-18deg) translateY(-4px)} }',
948
+ // inject:青蓝光滴沿胶囊下落并触发脉冲
949
+ '[data-dam-tour-art="inject"] .inject-capsule { left:27px; top:5px; width:11px; height:38px; border-radius:8px 8px 12px 12px; }',
950
+ '[data-dam-tour-art="inject"] .inject-drop { left:25px; top:42px; width:15px; height:15px; border-radius:70% 30% 58% 42% / 66% 40% 60% 34%; transform:rotate(45deg); animation:dam-drop 1.7s ease-in-out infinite; }',
951
+ '[data-dam-tour-art="inject"] .inject-pulse { left:14px; top:52px; width:36px; height:8px; border-radius:50%; border:1.5px solid rgba(var(--art-rgb),.65); background:transparent; animation:dam-pulse 1.7s ease-out infinite; }',
952
+ '@keyframes dam-drop { 0%{transform:translateY(-9px) rotate(45deg);opacity:.35} 55%{transform:translateY(1px) rotate(45deg);opacity:1} 100%{transform:translateY(1px) rotate(45deg);opacity:.5} } @keyframes dam-pulse { 0%,45%{transform:scale(.35);opacity:0} 65%{opacity:.8} 100%{transform:scale(1.2);opacity:0} }',
953
+ // bell:暖金玻璃罩+摆动球舌
954
+ '[data-dam-tour-art="bell"] .bell-shell { left:14px; top:8px; width:36px; height:34px; border-radius:20px 20px 9px 9px; transform-origin:50% 8%; animation:dam-bell-sway 2.8s ease-in-out infinite; }',
955
+ '[data-dam-tour-art="bell"] .bell-base { left:9px; top:42px; width:46px; height:9px; border-radius:8px; } [data-dam-tour-art="bell"] .bell-clapper { left:28px; top:48px; width:9px; height:9px; border-radius:50%; animation:dam-clapper 2.8s ease-in-out infinite; }',
956
+ '@keyframes dam-bell-sway { 0%,100%{transform:rotate(-4deg)} 50%{transform:rotate(4deg)} } @keyframes dam-clapper { 0%,100%{transform:translateX(-3px)} 50%{transform:translateX(3px)} }',
957
+ // calendar:青绿玻璃页+周期翻页
958
+ '[data-dam-tour-art="calendar"] .calendar-card { left:9px; top:13px; width:46px; height:40px; border-radius:11px; } [data-dam-tour-art="calendar"] .calendar-bind { top:5px; width:7px; height:17px; border-radius:5px; } [data-dam-tour-art="calendar"] .b1 { left:20px; } [data-dam-tour-art="calendar"] .b2 { left:38px; }',
959
+ '[data-dam-tour-art="calendar"] .calendar-page { left:13px; top:26px; width:38px; height:21px; border-radius:6px; transform-origin:50% 0; animation:dam-page-flip 4s ease-in-out infinite; } @keyframes dam-page-flip { 0%,68%,100%{transform:rotateX(0)} 78%{transform:rotateX(72deg)} 88%{transform:rotateX(0)} }',
960
+ // link:青紫双环反向摆动
961
+ '[data-dam-tour-art="link"] .link-ring { top:20px; width:31px; height:25px; border-radius:50%; background:rgba(var(--art-rgb),.16); border-width:6px; } [data-dam-tour-art="link"] .l1 { left:2px; transform:rotate(-28deg); animation:dam-link-a 3.2s ease-in-out infinite; } [data-dam-tour-art="link"] .l2 { left:30px; transform:rotate(28deg); animation:dam-link-b 3.2s ease-in-out infinite; }',
962
+ '[data-dam-tour-art="link"] .link-glint { left:29px; top:27px; width:7px; height:7px; border-radius:50%; background:rgba(255,255,255,.9); animation:dam-glint 1.6s ease-in-out infinite; } @keyframes dam-link-a{50%{transform:rotate(-18deg) translateX(2px)}} @keyframes dam-link-b{50%{transform:rotate(18deg) translateX(-2px)}} @keyframes dam-glint{50%{transform:scale(1.5);opacity:.55}}',
963
+ // engine:紫蓝棱镜+呼吸核心+旋转轨道
964
+ '[data-dam-tour-art="engine"] .engine-prism { left:13px; top:13px; width:38px; height:38px; border-radius:12px; transform:rotate(45deg); animation:dam-prism 6s linear infinite; } [data-dam-tour-art="engine"] .engine-core { left:25px; top:25px; width:14px; height:14px; border-radius:50%; background:radial-gradient(circle at 35% 28%,#fff,rgba(var(--art-rgb),.78)); animation:dam-core-breathe 1.8s ease-in-out infinite; }',
965
+ '[data-dam-tour-art="engine"] .engine-orbit { left:6px; top:27px; width:52px; height:14px; border-radius:50%; background:transparent;border:1.5px solid rgba(var(--art-rgb),.62);animation:dam-orbit 4s linear infinite}@keyframes dam-prism{to{transform:rotate(405deg)}}@keyframes dam-core-breathe{50%{transform:scale(1.3);box-shadow:0 0 18px rgba(var(--art-rgb),.7)}}@keyframes dam-orbit{to{transform:rotate(360deg)}}',
966
+ // radar:同心环+真正旋转扫描扇面
967
+ '[data-dam-tour-art="radar"] .radar-outer { left:7px; top:7px; width:50px; height:50px; border-radius:50%; background:rgba(var(--art-rgb),.10); } [data-dam-tour-art="radar"] .radar-inner { left:19px; top:19px; width:26px; height:26px; border-radius:50%; background:rgba(var(--art-rgb-2),.14); }',
968
+ '[data-dam-tour-art="radar"] .radar-sweep { left:8px; top:8px; width:48px; height:48px; border-radius:50%; border:0;background:conic-gradient(from 0deg,rgba(var(--art-rgb),.75),transparent 72deg,transparent);animation:dam-radar-spin 2.2s linear infinite; } [data-dam-tour-art="radar"] .radar-ping { left:27px; top:27px; width:10px; height:10px; border-radius:50%; background:#fff; box-shadow:0 0 15px rgba(var(--art-rgb),.8); animation:dam-core-breathe 1.4s ease-in-out infinite;}@keyframes dam-radar-spin{to{transform:rotate(360deg)}}',
969
+ // rocket:珊瑚金阶梯+循环上升火花
970
+ '[data-dam-tour-art="rocket"] .rocket-tier { height:11px; border-radius:7px; } [data-dam-tour-art="rocket"] .t1 { left:20px; top:42px; width:24px; } [data-dam-tour-art="rocket"] .t2 { left:14px; top:27px; width:36px; } [data-dam-tour-art="rocket"] .t3 { left:8px; top:12px; width:48px; }',
971
+ '[data-dam-tour-art="rocket"] .rocket-spark { left:28px; top:47px; width:9px; height:9px; border-radius:50%; background:radial-gradient(circle,#fff,rgba(var(--art-rgb-2),.85));box-shadow:0 0 14px rgba(var(--art-rgb-2),.7);animation:dam-spark-rise 1.8s ease-in infinite;}@keyframes dam-spark-rise{0%{transform:translateY(8px) scale(.6);opacity:0}25%{opacity:1}100%{transform:translateY(-50px) scale(1.15);opacity:0}}',
972
+ '[data-dam-tour-body] { position: relative; padding: 0 52px; text-align: center; z-index: 2; }',
973
+ '[data-dam-tour-kicker] { font-size: 10px; letter-spacing: .22em; text-transform: uppercase; opacity: .48; font-weight: 700; }',
974
+ '[data-dam-tour-title] { font-size: 23px; font-weight: 750; margin: 6px 0 10px; letter-spacing: -.015em; line-height: 1.22; }',
975
+ '[data-dam-tour-text] { font-size: 13.5px; opacity: .82; line-height: 1.72; min-height: 64px; }',
976
+ '[data-dam-tour-swap] { animation: dam-tour-swap .34s cubic-bezier(.2,.8,.3,1) both; }',
977
+ '@keyframes dam-tour-swap { from { opacity: 0; transform: translateX(22px); } to { opacity: 1; transform: none; } }',
978
+ '[data-dam-tour-dl] { margin: 8px auto 0; max-width: 420px; text-align: left; font-size: 12px; }',
979
+ '[data-dam-tour-dl-row] { display: flex; justify-content: space-between; gap: 10px; opacity: .75; font-size: 10.5px; margin-bottom: 3px; }',
980
+ '[data-dam-tour-bar] { height: 7px; border-radius: 99px; background: rgba(128,128,128,.18); overflow: hidden; }',
981
+ '[data-dam-tour-bar-i] { height: 100%; border-radius: 99px; background: linear-gradient(90deg, var(--dam-accent, #2456c4), #7ea4ff); transition: width .4s ease; }',
982
+ '[data-dam-tour-dots] { display: flex; gap: 7px; justify-content: center; margin: 18px 0 4px; z-index: 2; position: relative; }',
983
+ '[data-dam-tour-dot] { width: 7px; height: 7px; border-radius: 99px; background: currentColor; opacity: .22; transition: all .35s cubic-bezier(.4,0,.2,1); border: none; cursor: pointer; padding: 0; }',
984
+ '[data-dam-tour-dot]:hover { opacity: .5; }',
985
+ '[data-dam-tour-dot][data-on="true"] { width: 22px; opacity: .85; }',
986
+ '[data-dam-tour-foot] { display: flex; align-items: center; gap: 11px; padding: 10px 28px 22px; z-index: 2; position: relative; }',
987
+ '[data-dam-tour-skip] { border: none; background: transparent; color: inherit; opacity: .45; cursor: pointer; font-size: 12px; margin-right: auto; padding: 7px 11px; border-radius: 8px; transition: opacity .2s ease, background .2s ease; }',
988
+ '[data-dam-tour-skip]:hover { opacity: .85; background: rgba(128,128,128,.12); }',
989
+ '[data-dam-tour-btn] { min-width: 100px; padding: 10px 24px; border-radius: 99px; font-size: 13px; font-weight: 650; cursor: pointer; transition: all .22s ease; border: 1px solid rgba(255,255,255,.4); color: inherit; }',
990
+ '[data-dam-tour-btn][data-primary="true"] { background: linear-gradient(180deg, var(--dam-accent, #3a6df0), color-mix(in srgb, var(--dam-accent, #3a6df0) 82%, #000)); color: #fff; border-color: transparent; box-shadow: 0 6px 18px color-mix(in srgb, var(--dam-accent, #3a6df0) 45%, transparent), inset 0 1px 0 rgba(255,255,255,.35); }',
991
+ '[data-dam-tour-btn][data-primary="true"]:hover { transform: translateY(-1px); box-shadow: 0 10px 24px color-mix(in srgb, var(--dam-accent, #3a6df0) 55%, transparent), inset 0 1px 0 rgba(255,255,255,.35); }',
992
+ '[data-dam-tour-btn][data-primary="false"] { background: rgba(255,255,255,.10); }',
993
+ '[data-dam-tour-btn][data-primary="false"]:hover { background: rgba(255,255,255,.22); }',
994
+ '[data-dam-tour-btn]:disabled { opacity: .35; cursor: default; transform: none; }',
995
+ '[data-dam-tour-badge] { display: inline-block; font-size: 11px; font-weight: 700; padding: 3px 12px; border-radius: 99px; margin-top: 2px; }',
996
+ '[data-dam-tour-rec] { display: inline-block; font-size: 9.5px; font-weight: 700; padding: 1px 7px; border-radius: 99px; margin-left: 7px; vertical-align: 1px; letter-spacing: .04em; background: color-mix(in srgb, var(--dam-accent, #3a6df0) 20%, transparent); color: var(--dam-accent, #3a6df0); }',
997
+ // 向导内功能开关(即时写配置)
998
+ '[data-dam-tour-toggles] { display: flex; flex-direction: column; gap: 9px; margin: 14px auto 0; max-width: 448px; text-align: left; }',
999
+ '[data-dam-tour-toggles][data-scroll="true"] { max-height: min(240px, 42vh); overflow-y: auto; padding-right: 4px; }',
1000
+ '[data-dam-tour-toggles][data-scroll="true"]::-webkit-scrollbar { width: 5px; }',
1001
+ '[data-dam-tour-toggles][data-scroll="true"]::-webkit-scrollbar-thumb { background: rgba(255,255,255,.18); border-radius: 99px; }',
1002
+ '[data-dam-tour-tg] { display: flex; align-items: center; gap: 13px; padding: 11px 16px; border-radius: 14px; background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.14); cursor: pointer; transition: background .2s ease, border-color .2s ease, transform .16s ease; text-align: left; color: inherit; font: inherit; }',
1003
+ '[data-dam-tour-tg]:hover { background: rgba(255,255,255,.13); }',
1004
+ '[data-dam-tour-tg]:active { transform: scale(.992); }',
1005
+ '[data-dam-tour-tg][data-on="true"] { border-color: color-mix(in srgb, var(--dam-accent, #3a6df0) 55%, transparent); background: color-mix(in srgb, var(--dam-accent, #3a6df0) 11%, rgba(255,255,255,.06)); }',
1006
+ '[data-dam-tour-tg-txt] { flex: 1; min-width: 0; }',
1007
+ '[data-dam-tour-tg-name] { font-size: 13px; font-weight: 700; line-height: 1.35; }',
1008
+ '[data-dam-tour-tg-sub] { font-size: 11px; opacity: .60; margin-top: 2px; line-height: 1.45; }',
1009
+ '[data-dam-tour-sw] { flex: none; width: 40px; height: 23px; border-radius: 99px; position: relative; background: rgba(128,128,128,.35); transition: background .25s ease; }',
1010
+ '[data-dam-tour-sw]::after { content: ""; position: absolute; top: 2.5px; left: 2.5px; width: 18px; height: 18px; border-radius: 50%; background: #fff; transition: left .25s cubic-bezier(.4,0,.2,1); box-shadow: 0 1px 4px rgba(0,0,0,.3); }',
1011
+ '[data-dam-tour-sw][data-on="true"] { background: var(--dam-accent, #3a6df0); }',
1012
+ '[data-dam-tour-sw][data-on="true"]::after { left: 19.5px; }',
1013
+ '[data-dam-tour-chips] { display: flex; flex-wrap: wrap; gap: 7px; justify-content: center; margin-top: 12px; }',
1014
+ '[data-dam-tour-where] { font-size: 11.5px; opacity: .72; line-height: 1.75; margin-top: 12px; text-align: left; max-width: 460px; margin-left: auto; margin-right: auto; }',
1015
+ '[data-dam-tour-where] b { opacity: .96; font-weight: 650; }',
1016
+ // ── CHANGELOG 开场序列:Logo 组装→展开→消散→内容浮现 ──
1017
+ '[data-dam-update-box] { position: relative; overflow: hidden; min-height: 150px; }',
1018
+ '[data-dam-update-stage] { position: absolute; inset: 0; z-index: 2; display: flex; align-items: center; justify-content: center; background: inherit;',
1019
+ ' animation: dam-update-intro 1.7s cubic-bezier(.2,.8,.2,1) both; will-change: opacity, filter, transform; }',
1020
+ '[data-dam-update-click] { position: absolute; inset: 0; z-index: 3; cursor: pointer; }',
1021
+ '[data-dam-update-box][data-skip="true"] [data-dam-update-stage], [data-dam-update-box][data-skip="true"] [data-dam-update-click] { display: none; }',
1022
+ '[data-dam-update-content] { animation: dam-update-content-in 1.7s ease both; }',
1023
+ // 内容可滚动:update-content 自管滚动(update-box 是 overflow:hidden 的舞台层,内容长会被裁)
1024
+ '[data-dam-update-content] { max-height: min(46vh, 430px); overflow-y: auto; overscroll-behavior: contain; padding-right: 4px; scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.25) transparent; }',
1025
+ '[data-dam-update-content]::-webkit-scrollbar { width: 6px; }',
1026
+ '[data-dam-update-content]::-webkit-scrollbar-thumb { background: rgba(255,255,255,.22); border-radius: 99px; }',
1027
+ '[data-dam-update-content]::-webkit-scrollbar-track { background: transparent; }',
1028
+ '[data-dam-update-box][data-skip="true"] [data-dam-update-content] { animation: none; opacity: 1; transform: none; }',
1029
+ '@keyframes dam-update-intro { 0%, 41% { opacity: 1; filter: blur(0); transform: scale(1); } 59% { opacity: 1; filter: blur(0); transform: scale(1); } 88% { opacity: 0; filter: blur(6px); transform: scale(1.06); height: 100%; } 100% { opacity: 0; height: 0; } }',
1030
+ '@keyframes dam-update-content-in { 0%, 82% { opacity: 0; transform: translateY(12px); } 100% { opacity: 1; transform: none; } }',
1031
+ '[data-dam-update-logo] { position: relative; width: 120px; height: 120px; perspective: 680px; transform: scale(.78); --dam-slab-z: 22px; }',
1032
+ '[data-dam-update-logo] [data-dam-tour-stage] { animation: dam-stage-float 4.6s ease-in-out infinite, dam-update-logo-expand 1.7s cubic-bezier(.2,.8,.2,1) both; }',
1033
+ '@keyframes dam-update-logo-expand { 0%, 41% { transform: translate(-50%, -50%) rotateX(55deg) rotateZ(45deg) translateZ(0) scale(1); } 59% { transform: translate(-50%, -50%) rotateX(48deg) rotateZ(38deg) translateZ(16px) scale(1.16); } 100% { transform: translate(-50%, -50%) rotateX(55deg) rotateZ(45deg) translateZ(0) scale(1); } }',
1034
+ '[data-dam-update-logo] [data-dam-tour-bokeh] { animation-delay: 0s; opacity: .92; }',
1035
+ '[data-dam-update-logo] [data-dam-tour-slab="top"] { animation-delay: 0s; }',
1036
+ '[data-dam-update-logo] [data-dam-tour-slab="mid"] { animation-delay: .13s; }',
1037
+ '[data-dam-update-logo] [data-dam-tour-slab="bot"] { animation-delay: .26s; }',
1038
+ '[data-dam-update-logo] [data-dam-tour-orb-core] { animation-delay: .42s; }',
1039
+ '[data-dam-update-hint] { position: absolute; bottom: 14px; font-size: 11px; opacity: .42; letter-spacing: .04em; pointer-events: none; }',
670
1040
  ].join('\n')
671
1041
  var STYLE_ID = 'dsh-auto-memory-css'
672
1042
  function ensureStyle() {
@@ -951,6 +1321,132 @@ window.__ModuleLoader__.load({
951
1321
  h('b', null, t('refreshTime')), h('span', null, state.refreshedAt ? new Date(state.refreshedAt).toLocaleString() : t('notYetShort'))) : null)
952
1322
  }
953
1323
 
1324
+ // M7.5/G-02 前置:唤起记录与语料精修(A/P/S/H/E)——数据源=shadow-recent 只读投影,
1325
+ // 用户判定写入 append-only review-queue.jsonl(不直接改任何策略/参数)。
1326
+ function RefineTab() {
1327
+ var dataPair = useState(null)
1328
+ var data = dataPair[0]
1329
+ var setData = dataPair[1]
1330
+ var errPair = useState('')
1331
+ var err = errPair[0]
1332
+ var setErr = errPair[1]
1333
+ var sentPair = useState({})
1334
+ var sent = sentPair[0]
1335
+ var setSent = sentPair[1]
1336
+ var fbPair = useState(null)
1337
+ var fb = fbPair[0]
1338
+ var setFb = fbPair[1]
1339
+ useEffect(function () {
1340
+ var alive = true
1341
+ fetch('/api/dsh-auto-memory/shadow-recent').then(function (r) { return r.json() }).then(function (j) {
1342
+ if (alive) setData((j && j.rows) || [])
1343
+ }).catch(function (e) { if (alive) setErr(String(e && e.message)) })
1344
+ fetch('/api/dsh-auto-memory/review-feedback').then(function (r) { return r.json() }).then(function (j) {
1345
+ if (alive) setFb(j || {})
1346
+ }).catch(function () {})
1347
+ return function () { alive = false }
1348
+ }, [])
1349
+ function send(obsId, choice) {
1350
+ fetch('/api/dsh-auto-memory/review-feedback', { method: 'POST',
1351
+ headers: { 'Content-Type': 'application/json' },
1352
+ body: JSON.stringify({ observationId: obsId, choice: choice }) })
1353
+ .then(function (r) { return r.json() })
1354
+ .then(function (j) { if (j && j.ok) { var n = Object.assign({}, sent); n[obsId] = choice; setSent(n) } })
1355
+ .catch(function () {})
1356
+ }
1357
+ // 美术规格对齐 artifacts/m7-live-pre/ui-assets/semantic-tier-ui.html 组件③
1358
+ var card = { border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(255,255,255,.16)) 55%, transparent)', borderRadius: '12px', padding: '10px 12px', marginBottom: '9px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.08)) 45%, transparent)', boxShadow: '0 4px 14px rgba(0,0,0,.12), inset 0 1px 0 rgba(255,255,255,.14)' }
1359
+ var badge = function (txt, bg, fg) { return h('span', { style: { fontSize: 'calc(10.5px * var(--dam-scale))', fontWeight: 700, padding: '2px 8px', borderRadius: '6px', background: bg, color: fg || 'var(--dsw-alias-text-primary, inherit)', letterSpacing: '.02em' } }, txt) }
1360
+ var decBg = { emit: ['rgba(47,164,106,.24)', '#7fdcb0'], prefetch: ['rgba(196,138,42,.2)', '#e8c584'], suppress: ['rgba(128,128,128,.16)', 'rgba(255,255,255,.65)'] }
1361
+ var laneBg = { explicit: ['rgba(111,155,255,.24)', '#b9ceff'], proactive: ['rgba(160,120,255,.22)', '#d4c2ff'] }
1362
+ var reasonChip = function (txt) { return h('span', { style: { fontFamily: 'ui-monospace, Consolas, monospace', fontSize: 'calc(10px * var(--dam-scale))', padding: '2px 7px', borderRadius: '5px', background: 'rgba(255,214,150,.1)', color: '#ffd9a1' } }, txt) }
1363
+ var apeRow = function (obsId) {
1364
+ var choices = [['A', locale === 'zh' ? '该激活' : 'activate'], ['P', locale === 'zh' ? '只预取' : 'prefetch'], ['S', locale === 'zh' ? '应抑制' : 'suppress'], ['H', locale === 'zh' ? '有害' : 'harmful'], ['E', locale === 'zh' ? '改目标' : 'edit']]
1365
+ return h('div', { style: { display: 'flex', gap: '6px', marginTop: '8px' } }, choices.map(function (c) {
1366
+ var picked = sent[obsId] === c[0]
1367
+ return h('button', { key: c[0], 'data-dam-btn': '', onClick: function () { send(obsId, c[0]) },
1368
+ style: Object.assign({ flex: '1', fontSize: 'calc(11px * var(--dam-scale))', padding: '6px 2px', borderRadius: '9px', cursor: 'pointer', border: '1px solid rgba(255,255,255,.12)', transition: 'all .2s' },
1369
+ picked ? { borderColor: 'var(--dam-accent, #2456c4)', background: 'color-mix(in srgb, var(--dam-accent, #2456c4) 26%, transparent)', boxShadow: '0 2px 10px rgba(36,86,196,.35)' } : {}),
1370
+ onmouseover: function (e) { e.currentTarget.style.background = picked ? e.currentTarget.style.background : 'rgba(255,255,255,.1)' },
1371
+ onmouseout: function (e) { if (!picked) e.currentTarget.style.background = '' } },
1372
+ c[0], h('small', { style: { display: 'block', opacity: .6 } }, c[1]))
1373
+ }))
1374
+ }
1375
+ if (err) return h('div', null, t('refineLoadErr'), err)
1376
+ if (!data) return h('div', null, t('loading'))
1377
+ if (!data.length) return h('div', { style: { opacity: .65 } }, t('refineEmpty'))
1378
+ // 按天分组(行带 ts;无 ts 的旧行归入「更早」),组内倒序
1379
+ var sorted = data.slice().sort(function (a, b) { return (b.ts || 0) - (a.ts || 0) })
1380
+ var dayKey = function (ts) {
1381
+ if (!ts) return locale === 'zh' ? '更早' : 'Earlier'
1382
+ var d = new Date(ts * 1000)
1383
+ var today = new Date(); var yest = new Date(today.getTime() - 86400000)
1384
+ var same = function (a, b) { return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate() }
1385
+ if (same(d, today)) return locale === 'zh' ? '今天' : 'Today'
1386
+ if (same(d, yest)) return locale === 'zh' ? '昨天' : 'Yesterday'
1387
+ return (d.getMonth() + 1) + '-' + d.getDate()
1388
+ }
1389
+ var groups = []
1390
+ var index = {}
1391
+ sorted.forEach(function (row) {
1392
+ var k = dayKey(row.ts)
1393
+ if (!index[k]) { index[k] = []; groups.push({ day: k, rows: index[k] }) }
1394
+ index[k].push(row)
1395
+ })
1396
+ var card = { border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(255,255,255,.16)) 55%, transparent)', borderRadius: '12px', padding: '10px 12px', marginBottom: '9px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.08)) 45%, transparent)', boxShadow: '0 4px 14px rgba(0,0,0,.12), inset 0 1px 0 rgba(255,255,255,.14)' }
1397
+ var badge = function (txt, bg, fg) { return h('span', { style: { fontSize: 'calc(10.5px * var(--dam-scale))', fontWeight: 700, padding: '2px 8px', borderRadius: '6px', background: bg, color: fg || 'var(--dsw-alias-text-primary, inherit)', letterSpacing: '.02em' } }, txt) }
1398
+ var decBg = { emit: ['rgba(47,164,106,.24)', '#7fdcb0'], prefetch: ['rgba(196,138,42,.2)', '#e8c584'], suppress: ['rgba(128,128,128,.16)', 'rgba(255,255,255,.65)'] }
1399
+ var laneBg = { explicit: ['rgba(111,155,255,.24)', '#b9ceff'], proactive: ['rgba(160,120,255,.22)', '#d4c2ff'] }
1400
+ var reasonChip = function (txt) { return h('span', { style: { fontFamily: 'ui-monospace, Consolas, monospace', fontSize: 'calc(10px * var(--dam-scale))', padding: '2px 7px', borderRadius: '5px', background: 'rgba(255,214,150,.1)', color: '#ffd9a1' } }, txt) }
1401
+ var apeRow = function (obsId) {
1402
+ var choices = [['A', locale === 'zh' ? '该激活' : 'activate'], ['P', locale === 'zh' ? '只预取' : 'prefetch'], ['S', locale === 'zh' ? '应抑制' : 'suppress'], ['H', locale === 'zh' ? '有害' : 'harmful'], ['E', locale === 'zh' ? '改目标' : 'edit']]
1403
+ return h('div', { style: { display: 'flex', gap: '6px', marginTop: '8px' } }, choices.map(function (c) {
1404
+ var picked = sent[obsId] === c[0]
1405
+ return h('button', { key: c[0], 'data-dam-btn': '', onClick: function () { send(obsId, c[0]) },
1406
+ style: Object.assign({ flex: '1', fontSize: 'calc(11px * var(--dam-scale))', padding: '6px 2px', borderRadius: '9px', cursor: 'pointer', border: '1px solid rgba(255,255,255,.12)', transition: 'all .2s' },
1407
+ picked ? { borderColor: 'var(--dam-accent, #2456c4)', background: 'color-mix(in srgb, var(--dam-accent, #2456c4) 26%, transparent)', boxShadow: '0 2px 10px rgba(36,86,196,.35)' } : {}),
1408
+ onmouseover: function (e) { e.currentTarget.style.background = picked ? e.currentTarget.style.background : 'rgba(255,255,255,.1)' },
1409
+ onmouseout: function (e) { if (!picked) e.currentTarget.style.background = '' } },
1410
+ c[0], h('small', { style: { display: 'block', opacity: .6 } }, c[1]))
1411
+ }))
1412
+ }
1413
+ return h('div', null,
1414
+ h('div', { style: { fontSize: 'calc(11.5px * var(--dam-scale))', opacity: .75, marginBottom: '10px', lineHeight: 1.55 } }, t('refineSub')),
1415
+ // G-02 v2:判定队列汇总 + 政策提示(纯描述,不改参数)
1416
+ fb && (fb.queue && fb.queue.length || (fb.hints && fb.hints.length)) ? h('div', { style: Object.assign({}, card, { borderLeft: '3px solid var(--dam-accent, #2456c4)' }) },
1417
+ h('div', { style: { display: 'flex', gap: '6px', flexWrap: 'wrap', marginBottom: (fb.hints && fb.hints.length) ? '7px' : '0' } },
1418
+ ['A', 'P', 'S', 'H', 'E'].map(function (ch) {
1419
+ var n = (fb.byChoice || {})[ch] || 0
1420
+ if (!n) return null
1421
+ return badge(ch + '×' + n, ch === 'H' ? 'rgba(220,80,80,.2)' : 'rgba(90,140,255,.16)', ch === 'H' ? '#ff9c9c' : null)
1422
+ }),
1423
+ h('span', { style: { marginLeft: 'auto', opacity: .55, fontSize: 'calc(10px * var(--dam-scale))' } }, locale === 'zh' ? '判定队列(近 100 条)' : 'review queue (last 100)')),
1424
+ (fb.hints || []).map(function (hint, hi) {
1425
+ return h('div', { key: hi, style: { fontSize: 'calc(10.5px * var(--dam-scale))', opacity: .8, lineHeight: 1.5, margin: '3px 0' } }, '· ' + hint)
1426
+ })
1427
+ ) : null,
1428
+ groups.map(function (grp) {
1429
+ return h('div', { key: grp.day, style: { marginBottom: '16px' } },
1430
+ h('div', { style: { fontSize: 'calc(11.5px * var(--dam-scale))', fontWeight: 700, opacity: .7, margin: '2px 2px 8px' } }, grp.day),
1431
+ grp.rows.map(function (row, i) {
1432
+ var rc = row.reasonCodes || []
1433
+ var del = row.delivery
1434
+ return h('div', { key: row.observationId || i, style: card },
1435
+ h('div', { style: { display: 'flex', alignItems: 'center', gap: '7px', marginBottom: '5px' } },
1436
+ (function () { var lk = row.lane === 'explicit' ? 'explicit' : 'proactive'; var c = laneBg[lk] || ['rgba(128,128,128,.16)', null]; return badge(locale === 'zh' ? (lk === 'explicit' ? '明确召回' : '主动观测') : lk, c[0], c[1]) })(),
1437
+ (function () { var dk = String(row.decision); var c = decBg[dk] || ['rgba(128,128,128,.14)', null]; return badge(dk.toUpperCase(), c[0], c[1]) })(),
1438
+ // G-02 v2:投递结果徽标(emit/prefetch 行显示;关联是时间窗+记忆交集的启发式)
1439
+ del ? badge(locale === 'zh' ? '✓投递×' + del.count : '✓delivered×' + del.count, 'rgba(47,164,106,.2)', '#7fdcb0') : null,
1440
+ del && del.skill ? badge(locale === 'zh' ? '技能✓' : 'skill✓', 'rgba(160,120,255,.24)', '#d4c2ff') : null,
1441
+ !del && String(row.decision) === 'emit' ? badge(locale === 'zh' ? '未投递' : 'not delivered', 'rgba(196,80,80,.14)', '#e8a1a1') : null,
1442
+ row.ts ? h('span', { style: { marginLeft: 'auto', opacity: .5, fontSize: 'calc(10.5px * var(--dam-scale))' } }, new Date(row.ts * 1000).toTimeString().slice(0, 5)) : null),
1443
+ h('div', { style: { display: 'flex', flexWrap: 'wrap', gap: '5px', margin: '7px 0' } },
1444
+ rc.length ? rc.map(function (code, ci) { return reasonChip(code) }) : [h('span', { key: 'none', style: { opacity: .4, fontSize: 'calc(10px * var(--dam-scale))' } }, '-')]),
1445
+ apeRow(row.observationId))
1446
+ }))
1447
+ }))
1448
+ }
1449
+
954
1450
  function LogsTab() {
955
1451
  var dataPair = useState(null)
956
1452
  var data = dataPair[0]
@@ -1003,6 +1499,184 @@ window.__ModuleLoader__.load({
1003
1499
  }
1004
1500
  function pathName(p) { var parts = String(p).split(/[\\/]/); return parts[parts.length - 1] }
1005
1501
 
1502
+ // M8 记忆中枢页签:展示三层记忆(经历/事实/技能)概览。数据源=/memory-hub 只读投影。
1503
+ function MemoryHubTab(props) {
1504
+ var nonce = props && props.nonce ? props.nonce : 0
1505
+ var dataPair = useState(null)
1506
+ var data = dataPair[0]
1507
+ var setData = dataPair[1]
1508
+ var errPair = useState('')
1509
+ var err = errPair[0]
1510
+ var setErr = errPair[1]
1511
+ var refreshPair = useState(0)
1512
+ var refreshTick = refreshPair[0]
1513
+ var setRefresh = refreshPair[1]
1514
+ var actMsgPair = useState('')
1515
+ var actMsg = actMsgPair[0]
1516
+ var setActMsg = actMsgPair[1]
1517
+ useEffect(function () {
1518
+ var alive = true
1519
+ fetch('/api/dsh-auto-memory/memory-hub').then(function (r) { return r.json() }).then(function (j) {
1520
+ if (alive) setData(j || null)
1521
+ }).catch(function (e) { if (alive) setErr(String(e && e.message)) })
1522
+ return function () { alive = false }
1523
+ }, [nonce, refreshTick])
1524
+ // M9 审批动作(G-02 同款 append-only 精神):晋升/激活/弃用走 /memory-hub POST,
1525
+ // 后端走 store 门槛判定,不绕过任何 gate;动作后刷新 overview。
1526
+ function hubAct(action, procedureId, v) {
1527
+ fetch('/api/dsh-auto-memory/memory-hub', { method: 'POST',
1528
+ headers: { 'Content-Type': 'application/json' },
1529
+ body: JSON.stringify({ action: action, procedureId: procedureId, v: v }) })
1530
+ .then(function (r) { return r.json() })
1531
+ .then(function (j) {
1532
+ setActMsg(action + ': ' + (j && (j.decision || j.reason || (j.ok === false ? (j.reason || 'rejected') : 'ok')) || 'done'))
1533
+ setRefresh(function (x) { return x + 1 })
1534
+ })
1535
+ .catch(function (e) { setActMsg(action + ' failed: ' + String(e && e.message)) })
1536
+ }
1537
+ if (err) return h('div', { 'data-dam-hint': '' }, t('searchFailed') + err)
1538
+ if (!data) return h(Loading, { label: t('loading') })
1539
+ var rows = []
1540
+ var procs = data.procedures
1541
+ var facts = data.facts
1542
+ var epis = data.episodic
1543
+ // 技能层(最精彩: 反复成功的流程固化为 skill)
1544
+ rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '8px' } }, t('hubSkills')))
1545
+ var activeList = (procs && procs.active) || []
1546
+ if (!activeList.length) {
1547
+ rows.push(h(Card, { title: t('hubSkills') + ' (' + (locale === 'zh' ? '暂无' : 'none') + ')' }, h('div', { 'data-dam-content': '' }, t('hubSkillsEmpty'))))
1548
+ } else {
1549
+ rows.push(h(Card, { title: t('hubSkills') + ' (' + activeList.length + ')' },
1550
+ activeList.map(function (p) {
1551
+ var risk = p.riskLevel === 'high' ? (locale === 'zh' ? '· 高风险需确认' : '· high-risk') : ''
1552
+ return h('div', { 'data-dam-content': '', key: p.procedureId, style: { padding: '4px 0', borderBottom: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(255,255,255,.16)) 40%, transparent)' } },
1553
+ h('div', null, h('b', null, p.title), h('span', { 'data-dam-hint': '', style: { marginLeft: '6px' } }, (locale === 'zh' ? '成功 ' : 'success ') + (p.evidence ? p.evidence.success : 0) + ' · ' + (locale === 'zh' ? '会话 ' : 'sessions ') + (p.evidence ? p.evidence.sessions : 0) + ' ' + risk),
1554
+ h('button', { 'data-dam-btn': '', style: { marginLeft: '8px', fontSize: 'calc(10px * var(--dam-scale))', padding: '2px 8px' }, onClick: function () { hubAct('deprecate', p.procedureId) } }, locale === 'zh' ? '弃用' : 'deprecate'),
1555
+ h('button', { 'data-dam-btn': '', style: { marginLeft: '4px', fontSize: 'calc(10px * var(--dam-scale))', padding: '2px 8px' }, onClick: function () { hubAct('pin', p.procedureId, !(procs && procs.pipeline && procs.pipeline.concat(activeList).find(function (x) { return x.procedureId === p.procedureId && x.pinned }))) } }, locale === 'zh' ? '置顶' : 'pin')))
1556
+ })))
1557
+ }
1558
+ // M9 审批队列(observed/candidate/validated):用户确认晋升/激活/弃用
1559
+ var pipeline = (procs && procs.pipeline) || []
1560
+ if (pipeline.length) {
1561
+ rows.push(h(Card, { title: (locale === 'zh' ? '技能审批队列' : 'Skill approval queue') + ' (' + pipeline.length + ')' },
1562
+ pipeline.map(function (p) {
1563
+ var ev = p.evidence || {}
1564
+ return h('div', { 'data-dam-content': '', key: p.procedureId, style: { padding: '5px 0', borderBottom: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(255,255,255,.16)) 40%, transparent)' } },
1565
+ h('div', null, h('b', null, p.title), h('span', { 'data-dam-hint': '', style: { marginLeft: '6px' } }, '[' + p.stage + ']' + (p.pinned ? ' 📌' : '') + (p.riskLevel === 'high' ? ' ⚠' : '')),
1566
+ h('span', { 'data-dam-hint': '', style: { marginLeft: '6px' } }, (locale === 'zh' ? '成功 ' : 'succ ') + (ev.success || 0) + ' · ' + (locale === 'zh' ? '会话 ' : 'sess ') + (ev.sessions || 0))),
1567
+ h('div', { style: { display: 'flex', gap: '6px', marginTop: '5px' } },
1568
+ h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px' }, onClick: function () { hubAct('promote', p.procedureId) } }, locale === 'zh' ? '晋升' : 'promote'),
1569
+ h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px' }, onClick: function () { hubAct('activate', p.procedureId) } }, locale === 'zh' ? '直接激活' : 'activate'),
1570
+ h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px', opacity: .75 }, onClick: function () { hubAct('deprecate', p.procedureId) } }, locale === 'zh' ? '弃用' : 'deprecate'),
1571
+ h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px', opacity: .75 }, onClick: function () { hubAct('pin', p.procedureId, !p.pinned) } }, p.pinned ? (locale === 'zh' ? '取消置顶' : 'unpin') : (locale === 'zh' ? '置顶' : 'pin'))))
1572
+ })))
1573
+ }
1574
+ if (actMsg) rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '6px', color: 'var(--dsw-alias-warn, #e8c584)' } }, actMsg))
1575
+ // 事实层
1576
+ rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '12px' } }, t('hubFacts')))
1577
+ var factList = (facts && facts.recent) || []
1578
+ rows.push(h(Card, { title: t('hubFacts') + ' (' + (facts ? facts.size : 0) + ')' },
1579
+ factList.length ? factList.slice(0, 6).map(function (f) {
1580
+ return h('div', { 'data-dam-content': '', key: f.factId, style: { padding: '3px 0' } }, f.subject + ' · ' + f.predicate + (f.object ? ' · ' + f.object : ''))
1581
+ }) : h('div', { 'data-dam-content': '' }, t('hubFactsEmpty'))))
1582
+ if (facts && facts.pendingConflicts && facts.pendingConflicts.length) {
1583
+ rows.push(h('div', { 'data-dam-hint': '', style: { color: 'var(--dsw-alias-warn, #e8c584)' } }, t('hubConflicts') + ': ' + facts.pendingConflicts.length))
1584
+ }
1585
+ // 经历层
1586
+ rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '12px' } }, t('hubEpisodic')))
1587
+ var epiList = (epis && epis.recent) || []
1588
+ rows.push(h(Card, { title: t('hubEpisodic') + ' (' + (epis ? epis.size : 0) + ')' },
1589
+ epiList.length ? epiList.slice(0, 6).map(function (e) {
1590
+ return h('div', { 'data-dam-content': '', key: e.episodeId, style: { padding: '3px 0' } },
1591
+ (e.intent || '').slice(0, 40) + ' · ' + (e.outcome || 'unknown') + (e.success ? ' ✓' : ''))
1592
+ }) : h('div', { 'data-dam-content': '' }, t('hubEpisodicEmpty'))))
1593
+ // 统计
1594
+ rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '12px', opacity: .7 } },
1595
+ (locale === 'zh' ? '中枢统计: ' : 'Hub stats: ') + (data.stats ? JSON.stringify(data.stats) : '')))
1596
+ return h('div', null, rows)
1597
+ }
1598
+
1599
+ // M10 存储管理页签(2026-08-30 P3):数据源=/storage-manage(loopback 只读投影 + 三类动作)。
1600
+ // ①健康扫描:逐源 sidecar↔正文 digest 比对;②stale 一键自愈(只重建 sidecar,正文不动);
1601
+ // ③按 memoryId 删除(正文原子删 + 在途激活包清理 + 派生事实撤销三联动,后端做路径白名单)。
1602
+ function StorageTab(props) {
1603
+ var nonce = props && props.nonce ? props.nonce : 0
1604
+ var dataPair = useState(null)
1605
+ var data = dataPair[0]
1606
+ var setData = dataPair[1]
1607
+ var errPair = useState('')
1608
+ var err = errPair[0]
1609
+ var setErr = errPair[1]
1610
+ var msgPair = useState('')
1611
+ var msg = msgPair[0]
1612
+ var setMsg = msgPair[1]
1613
+ var delPair = useState('')
1614
+ var delId = delPair[0]
1615
+ var setDelId = delPair[1]
1616
+ var filePair = useState('')
1617
+ var delFile = filePair[0]
1618
+ var setDelFile = filePair[1]
1619
+ useEffect(function () {
1620
+ var alive = true
1621
+ fetch('/api/dsh-auto-memory/storage-manage').then(function (r) { return r.json() }).then(function (j) {
1622
+ if (alive) setData(j || null)
1623
+ }).catch(function (e) { if (alive) setErr(String(e && e.message)) })
1624
+ return function () { alive = false }
1625
+ }, [nonce])
1626
+ function act(action, payload, onDone) {
1627
+ setMsg('')
1628
+ var body = Object.assign({ action: action }, payload || {})
1629
+ fetch('/api/dsh-auto-memory/storage-manage', { method: 'POST',
1630
+ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) })
1631
+ .then(function (r) { return r.json() })
1632
+ .then(function (j) {
1633
+ setMsg(action + ': ' + (j && (j.reason || (j.ok === false ? 'rejected' : 'ok')) || 'done'))
1634
+ if (onDone) onDone(j)
1635
+ var again = fetch('/api/dsh-auto-memory/storage-manage').then(function (r2) { return r2.json() })
1636
+ again.then(function (j2) { setData(j2 || null) })
1637
+ })
1638
+ .catch(function (e) { setMsg(action + ' failed: ' + String(e && e.message)) })
1639
+ }
1640
+ if (err) return h('div', { 'data-dam-hint': '' }, t('searchFailed') + err)
1641
+ if (!data) return h(Loading, { label: t('loading') })
1642
+ if (data.error) return h('div', { 'data-dam-hint': '' }, String(data.error))
1643
+ var counts = data.counts || { total: 0, ok: 0, stale: 0, unrepairable: 0 }
1644
+ var rows = []
1645
+ rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '8px' } }, t('storageScanHint')))
1646
+ rows.push(h(Card, { title: (locale === 'zh' ? '语料健康' : 'Corpus health') + ' (' + counts.ok + '/' + counts.total + ' ok)' },
1647
+ h('div', null,
1648
+ (data.sources || []).map(function (s) {
1649
+ var mark = s.status === 'ok' ? '✓' : (s.status === 'stale' ? '⚠' : '✕')
1650
+ return h('div', { 'data-dam-content': '', key: s.sourceRef, style: { padding: '3px 0' } },
1651
+ mark + ' ' + s.sourceRef + ' [' + s.status + ']' + (s.reasons && s.reasons.length ? ' · ' + s.reasons.join(', ') : ''))
1652
+ }),
1653
+ h('div', { style: { display: 'flex', gap: '6px', marginTop: '8px' } },
1654
+ h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px' }, onClick: function () { act('scan') } }, locale === 'zh' ? '重新扫描' : 'rescan'),
1655
+ h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px', opacity: counts.stale ? 1 : .5 }, onClick: function () { if (counts.stale) act('repair', { items: (data.stale || []).map(function (s) { return { file: s.file, sourceRef: s.sourceRef } }) }) } },
1656
+ (locale === 'zh' ? '修复 stale' : 'repair stale') + (counts.stale ? ' (' + counts.stale + ')' : ''))))))
1657
+ // 删除(三联动):文件路径 + memoryId。后端校验路径必须属于当前语料三源之一。
1658
+ rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '12px' } }, t('storageDeleteHint')))
1659
+ rows.push(h(Card, { title: locale === 'zh' ? '删除记忆(三联动)' : 'Delete memory (cascading)' },
1660
+ h('div', null,
1661
+ h('select', { 'data-dam-select': '', value: delFile, onChange: function (e) { setDelFile(e.target.value) }, style: { width: '100%' } },
1662
+ h('option', { value: '' }, locale === 'zh' ? '选择语料文件…' : 'select corpus file…'),
1663
+ (data.sources || []).map(function (s) { return h('option', { key: s.sourceRef, value: s.file }, s.sourceRef + ' — ' + s.file) })),
1664
+ h('input', { 'data-dam-input': '', placeholder: 'mem_…', value: delId, onChange: function (e) { setDelId(e.target.value) }, style: { width: '100%', marginTop: '6px' } }),
1665
+ h('div', { style: { display: 'flex', gap: '6px', marginTop: '6px' } },
1666
+ h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px', opacity: (delFile && delId) ? 1 : .5 },
1667
+ onClick: function () {
1668
+ if (!(delFile && delId)) return
1669
+ act('delete', { filePath: delFile, memoryId: delId }, function () { setDelId('') })
1670
+ } }, locale === 'zh' ? '删除' : 'delete')))))
1671
+ if (msg) rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '6px', color: 'var(--dsw-alias-warn, #e8c584)' } }, msg))
1672
+ var audit = (data.audit || []).slice(-4)
1673
+ if (audit.length) {
1674
+ rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '12px', opacity: .7 } },
1675
+ (locale === 'zh' ? '最近动作: ' : 'recent: ') + audit.map(function (a) { return a.action }).join(', ')))
1676
+ }
1677
+ return h('div', null, rows)
1678
+ }
1679
+
1006
1680
  function NotesTab() {
1007
1681
  var dataPair = useState(null)
1008
1682
  var data = dataPair[0]
@@ -1647,6 +2321,7 @@ window.__ModuleLoader__.load({
1647
2321
 
1648
2322
  function MemoryPanel() {
1649
2323
  var tick = useTick()
2324
+ var panelRef = useRef(null)
1650
2325
  var tabPair = useState('overview')
1651
2326
  var tab = tabPair[0]
1652
2327
  var setTab = tabPair[1]
@@ -1656,17 +2331,41 @@ window.__ModuleLoader__.load({
1656
2331
  var setNonce = noncePair[1]
1657
2332
  var g = controller.geom()
1658
2333
  useEffect(function () { return controller.subscribe(tick[1]) }, [])
2334
+ // 可读性兜底(@ProperSAMA PR#12,适配版):DSH Desktop 增强模式 + 透明/Mica 窗口材质下,
2335
+ // 主题令牌 --dsw-alias-bg-overlay 本身就是半透明的,面板文字几乎不可读。判别信号用
2336
+ // 「令牌自身的 alpha」(增强模式显著低于普通模式的 0.86-0.9):< 0.65 时把面板背景提升到
2337
+ // 0.96(保留色相)并弱化顶部高光。普通模式令牌不透明 → 不触发,液态玻璃观感零变化。
2338
+ useEffect(function () {
2339
+ if (!panelOpen) return
2340
+ var el = panelRef.current
2341
+ if (!el) return
2342
+ try {
2343
+ var cs = getComputedStyle(el)
2344
+ var token = cs.getPropertyValue('--dsw-alias-bg-overlay')
2345
+ var col = parseCssColor((token || '').trim() || cs.backgroundColor)
2346
+ if (col && col.a < 0.65) {
2347
+ el.style.background = 'rgba(' + col.r + ', ' + col.g + ', ' + col.b + ', 0.96)'
2348
+ el.setAttribute('data-solid', 'true')
2349
+ } else {
2350
+ el.style.background = ''
2351
+ el.removeAttribute('data-solid')
2352
+ }
2353
+ } catch (e) {}
2354
+ }, [panelOpen])
1659
2355
  if (!panelOpen && !panelClosing) return null
1660
2356
  var body
1661
2357
  if (tab === 'overview') body = h(OverviewTab, { nonce: nonce })
1662
2358
  else if (tab === 'logs') body = h(LogsTab)
2359
+ else if (tab === 'refine') body = h(RefineTab)
2360
+ else if (tab === 'hub') body = h(MemoryHubTab, { nonce: nonce })
2361
+ else if (tab === 'storage') body = h(StorageTab, { nonce: nonce })
1663
2362
  else if (tab === 'notes') body = h(NotesTab)
1664
2363
  else if (tab === 'reflections') body = h(ReflectionsTab)
1665
2364
  else if (tab === 'connect') body = h(ConnectTab)
1666
2365
  else if (tab === 'calendar') body = h(CalendarTab)
1667
2366
  else if (tab === 'workspaces') body = h(WorkspaceTab)
1668
2367
  else body = h(SearchTab)
1669
- var tabs = [['overview', t('overview')], ['logs', t('logs')], ['notes', t('notes')], ['reflections', t('reflections')], ['connect', t('connect')], ['calendar', t('calendar')], ['search', t('search')], ['workspaces', t('workspaces')]]
2368
+ var tabs = [['overview', t('overview')], ['logs', t('logs')], ['refine', t('refineTab')], ['hub', t('hubTab')], ['storage', t('storageTab')], ['notes', t('notes')], ['reflections', t('reflections')], ['connect', t('connect')], ['calendar', t('calendar')], ['search', t('search')], ['workspaces', t('workspaces')]]
1670
2369
  var style = {
1671
2370
  left: g.left + 'px',
1672
2371
  top: g.top + 'px',
@@ -1684,6 +2383,7 @@ window.__ModuleLoader__.load({
1684
2383
  return h('div', {
1685
2384
  'data-dam-panel': '',
1686
2385
  'data-scale': fontScale in FONT_SCALES ? fontScale : 'md',
2386
+ ref: panelRef,
1687
2387
  style: style,
1688
2388
  'data-closing': panelClosing ? 'true' : undefined,
1689
2389
  'data-dragging': dragActive ? 'true' : undefined,
@@ -1793,7 +2493,51 @@ window.__ModuleLoader__.load({
1793
2493
  // ───────────────────────── 更新弹窗 / 首次指导(毛玻璃) ─────────────────────────
1794
2494
  function DialogHost() {
1795
2495
  var tickPair = useTick()
2496
+ var dlgTick = tickPair[1] // DialogHost 自身的重渲染通道(emit 只刷主面板,刷不到弹窗——开关点击即时反映全靠它)
1796
2497
  useEffect(function () { return onDialog(tickPair[1]) }, [])
2498
+ // 欢迎向导状态(hooks 必须无条件调用——置于 dialogState 早退之前,满足 hooks 顺序)
2499
+ var tourStepPair = useState(0)
2500
+ var tourStep = tourStepPair[0]
2501
+ var setTourStep = tourStepPair[1]
2502
+ var updateSkipPair = useState(false)
2503
+ var updateSkip = updateSkipPair[0]
2504
+ var setUpdateSkip = updateSkipPair[1]
2505
+ useEffect(function () { setUpdateSkip(false) }, [dialogState ? dialogState.kind : null])
2506
+ var wizSt = window['dsh-auto-memory.wizStatus']
2507
+ if (!wizSt) {
2508
+ wizSt = { loaded: false }
2509
+ window['dsh-auto-memory.wizStatus'] = wizSt
2510
+ fetch('/api/dsh-auto-memory/semantic-status').then(function (r) { return r.json() }).then(function (j) {
2511
+ Object.assign(wizSt, j, { loaded: true })
2512
+ try { dlgTick() } catch (e9) {}
2513
+ }).catch(function () { Object.assign(wizSt, { loaded: true, ready: false }) })
2514
+ }
2515
+ var tourDlPhase = wizSt && wizSt.download && wizSt.download.phase
2516
+ var tourShowing = !!dialogState && (dialogState.kind === 'welcomeTour' || dialogState.kind === 'modelDownload')
2517
+ useEffect(function () {
2518
+ // 每次向导变为可见时回到第一步,并拉一份当前配置快照(开关步读写用)
2519
+ if (tourShowing) {
2520
+ setTourStep(0)
2521
+ fetch('/api/dsh-auto-memory/config').then(function (r) { return r.json() }).then(function (j) {
2522
+ wizSt.tourCfg = (j && (j.config || j)) || {}
2523
+ try { dlgTick() } catch (e11) {}
2524
+ }).catch(function () { wizSt.tourCfg = wizSt.tourCfg || {} })
2525
+ fetch('/api/dsh-auto-memory/external').then(function (r) { return r.json() }).then(function (j) {
2526
+ wizSt.extScan = j
2527
+ try { dlgTick() } catch (e12) {}
2528
+ }).catch(function () {})
2529
+ }
2530
+ }, [tourShowing])
2531
+ useEffect(function () {
2532
+ if (!tourShowing) return undefined
2533
+ if (tourDlPhase !== 'downloading' && tourDlPhase !== 'verifying') return undefined
2534
+ var iv = setInterval(function () {
2535
+ fetch('/api/dsh-auto-memory/semantic-status').then(function (r) { return r.json() }).then(function (j) {
2536
+ Object.assign(wizSt, j); try { dlgTick() } catch (e10) {}
2537
+ }).catch(function () {})
2538
+ }, 1500)
2539
+ return function () { clearInterval(iv) }
2540
+ }, [tourShowing, tourDlPhase])
1797
2541
  if (!dialogState) return null
1798
2542
  // 左下角小卡片(记忆按钮上方),高透明毛玻璃,不遮全屏
1799
2543
  var overlay = { position: 'fixed', left: '10px', bottom: '64px', zIndex: 2147483000, width: 'min(360px, calc(100vw - 20px))', maxHeight: 'min(46vh, 430px)', display: 'flex', flexDirection: 'column' }
@@ -1809,21 +2553,316 @@ window.__ModuleLoader__.load({
1809
2553
  var item = { fontSize: 'calc(11px * var(--dam-scale))', lineHeight: 1.55, padding: '1px 0 1px 16px', position: 'relative' }
1810
2554
  var close = { alignSelf: 'flex-end', padding: '4px 16px', borderRadius: '8px', border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.35)) 45%, transparent)', background: 'color-mix(in srgb, var(--dsw-alias-brand-primary, #4f7cff) 12%, transparent)', color: 'var(--dsw-alias-text-primary, inherit)', cursor: 'pointer', fontSize: 'calc(11.5px * var(--dam-scale))' }
1811
2555
  var dot = { position: 'absolute', left: '0', top: '10px', width: '7px', height: '7px', borderRadius: '50%', background: 'var(--dsw-alias-brand-primary, #4f7cff)' }
2556
+ // 通用开场舞台必须在 first/notice/update 三个分支之前完成赋值;var 只提升声明、不提升赋值。
2557
+ var skipUpdateIntro = function () { setUpdateSkip(true) }
2558
+ var introEligible = dialogState.kind === 'update' ||
2559
+ dialogState.kind === 'first' ||
2560
+ (dialogState.kind === 'notice' && !((dialogState.notice || {}).level === 'urgent'))
2561
+ var DamIntroBox = function (children) {
2562
+ if (!introEligible) return children
2563
+ return h('div', { 'data-dam-update-box': '', 'data-skip': String(updateSkip) },
2564
+ h('div', { 'data-dam-update-stage': '' },
2565
+ h('div', { 'data-dam-update-logo': '' },
2566
+ h('div', { 'data-dam-tour-bokeh': 'a' }),
2567
+ h('div', { 'data-dam-tour-bokeh': 'b' }),
2568
+ h('div', { 'data-dam-tour-bokeh': 'c' }),
2569
+ h('div', { 'data-dam-tour-stage': '' },
2570
+ h('div', { 'data-dam-tour-slab': 'bot' }),
2571
+ h('div', { 'data-dam-tour-slab': 'mid' }),
2572
+ h('div', { 'data-dam-tour-slab': 'top' }))),
2573
+ h('div', { 'data-dam-update-hint': '' }, locale === 'zh' ? '点击任意处跳过' : 'Click anywhere to skip')),
2574
+ !updateSkip ? h('div', { 'data-dam-update-click': '', onClick: skipUpdateIntro }) : null,
2575
+ h('div', { 'data-dam-update-content': '' }, children))
2576
+ }
1812
2577
  if (dialogState.kind === 'first') {
1813
2578
  var feats = [t('gFeat1'), t('gFeat2'), t('gFeat3'), t('gFeat4'), t('gFeat5'), t('gFeat6')]
2579
+ // M7.5:语义引擎资产检测(模块级缓存,避免重复请求;SLIDES 扩展点见下方渲染块)
2580
+ var semStatus = window['dsh-auto-memory.semStatus']
2581
+ if (!semStatus) {
2582
+ semStatus = { status: 'checking' }
2583
+ window['dsh-auto-memory.semStatus'] = semStatus
2584
+ fetch('/api/dsh-auto-memory/semantic-status').then(function (r) { return r.json() }).then(function (j) {
2585
+ semStatus.status = j.ready ? 'ready' : 'missing'
2586
+ try { dlgTick() } catch (e9) {}
2587
+ }).catch(function () { semStatus.status = 'unknown' })
2588
+ }
2589
+ var firstButton = h('button', { 'data-dam-btn': '', style: close, onClick: function () {
2590
+ try {
2591
+ localStorage.setItem('dsh-auto-memory.firstRunDone', '1')
2592
+ if (dialogState && dialogState.currentVersion) localStorage.setItem('dsh-auto-memory.seenVersion', dialogState.currentVersion)
2593
+ // 规范 F2(RELEASE-SEMANTIC-OPTION.md):首启关闭后进入「欢迎向导」
2594
+ // (分步介绍+内联检测/下载;受设置 welcomeTourEnabled 门控,默认开)。
2595
+ fetch('/api/dsh-auto-memory/config').then(function (r) { return r.json() }).then(function (cj) {
2596
+ var cc = (cj && cj.config) || cj || {}
2597
+ if (cc.welcomeTourEnabled !== false && !localStorage.getItem('dsh-auto-memory.semWizardDone')) {
2598
+ openDialog({ kind: 'welcomeTour' })
2599
+ } else {
2600
+ try { localStorage.setItem('dsh-auto-memory.semWizardDone', '1') } catch (e5) {}
2601
+ }
2602
+ }).catch(function () {
2603
+ if (!localStorage.getItem('dsh-auto-memory.semWizardDone')) openDialog({ kind: 'welcomeTour' })
2604
+ })
2605
+ } catch (e3) {}
2606
+ closeDialog()
2607
+ } }, t('gotIt'))
2608
+ var firstChildren = [
2609
+ h('div', { style: head }, t('guideTitle')),
2610
+ h('div', { style: sub }, t('guideSub')),
2611
+ feats.map(function (f) { return h('div', { style: item }, h('span', { style: dot }), f) }),
2612
+ // M7.5 首启扩展点(SLIDES):新功能引导在此按序追加;当前块=内置语义引擎自动检测。
2613
+ (function () {
2614
+ var boxS = { margin: '8px 0 4px', padding: '8px 10px', borderRadius: '10px', border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.35)) 50%, transparent)', fontSize: 'calc(11px * var(--dam-scale))', lineHeight: 1.55 }
2615
+ var st = semStatus
2616
+ if (!st || st.status === 'checking') return h('div', { style: boxS }, locale === 'zh' ? '正在检测内置语义引擎…' : 'Detecting built-in semantic engine…')
2617
+ if (st.status === 'ready') return h('div', { style: boxS }, '✓ ' + (locale === 'zh' ? '内置语义引擎已就绪(本地运行,记忆不出电脑)。可在「记忆」面板设置中切换检索模式。' : 'Built-in semantic engine ready (local-only). Switchable in Memory panel settings.'))
2618
+ return h('div', { style: boxS }, locale === 'zh' ? '可选:内置语义引擎未下载(约130MB)。在「记忆」面板设置中可随时启用;未启用时词法检索照常可用。' : 'Optional: built-in semantic engine not downloaded (~130MB). Enable anytime in Memory panel settings; lexical search keeps working.')
2619
+ })(),
2620
+ h('div', { style: { fontSize: 'calc(12px * var(--dam-scale))', opacity: .8, marginTop: '4px' } }, t('guideTip')),
2621
+ firstButton,
2622
+ ]
1814
2623
  return h('div', { style: overlay },
1815
2624
  h('div', { style: box },
1816
- h('div', { style: head }, t('guideTitle')),
1817
- h('div', { style: sub }, t('guideSub')),
1818
- feats.map(function (f) { return h('div', { style: item }, h('span', { style: dot }), f) }),
1819
- h('div', { style: { fontSize: 'calc(12px * var(--dam-scale))', opacity: .8, marginTop: '4px' } }, t('guideTip')),
1820
- h('button', { 'data-dam-btn': '', style: close, onClick: function () {
1821
- try {
1822
- localStorage.setItem('dsh-auto-memory.firstRunDone', '1')
1823
- if (dialogState && dialogState.currentVersion) localStorage.setItem('dsh-auto-memory.seenVersion', dialogState.currentVersion)
1824
- } catch (e3) {}
1825
- closeDialog()
1826
- } }, t('gotIt'))))
2625
+ DamIntroBox(firstChildren)))
2626
+ }
2627
+ if (dialogState.kind === 'welcomeTour' || dialogState.kind === 'modelDownload') {
2628
+ // 首启引导向导 v2(2026-08-31):分步功能全覆盖 + 步内功能开关(点击即时写配置)
2629
+ // + 完成步提醒「随时可在设置的哪个分区重新打开」。跳过/✕ 也先落到完成步(提醒)
2630
+ var TOUR_STEPS = [
2631
+ { art: 'bubble', core: '', kicker: 'WELCOME',
2632
+ title: locale === 'zh' ? '欢迎使用 dsh-auto-memory' : 'Welcome to dsh-auto-memory',
2633
+ text: locale === 'zh' ? '这是 DeepSeek Harness 的个人联想记忆插件。接下来把所有功能向你解释清楚——每个功能都有开关,当场决定开不开;最后会告诉你在哪里随时改。'
2634
+ : 'A personal associative-memory plugin for DeepSeek Harness. This tour explains every feature — each has a switch you flip right here; the last page tells you where to change them later.' },
2635
+ { art: 'store', core: '', kicker: locale === 'zh' ? '核心能力' : 'CORE',
2636
+ title: locale === 'zh' ? '记忆是怎么被想起的' : 'How memories are recalled',
2637
+ text: locale === 'zh' ? '先说清楚两条互不依赖的路:①自动联想(下面的开关)——插件持续观察对话与工具事件,锚定记忆;需要回忆时经固定边界注入下一轮,不破坏前缀缓存。②就算关掉它,AI 仍会在每轮结尾默认写项目 memory(memory_log),也能用 memory_recall 主动读取;日历、问候等面板功能也独立运行。'
2638
+ : 'Two independent paths: (1) automatic association (switch below) — the plugin anchors memories from conversations and injects relevant ones into the next turn via a fixed boundary. (2) Even with it off, the AI still writes project memory each turn (memory_log) and can read via memory_recall; calendar, greeting and other panel features run independently.' },
2639
+ { art: 'inject', core: '', kicker: locale === 'zh' ? '记忆快照' : 'SNAPSHOT',
2640
+ title: locale === 'zh' ? '周期性记忆注入' : 'Periodic memory snapshot',
2641
+ text: locale === 'zh' ? '另一个独立机制:首次启用时会向上下文注入一段记忆提示(项目长期笔记的摘要),之后每隔一定轮次、或上下文被压缩后自动重新注入,保证模型始终带着记忆背景工作。'
2642
+ : 'A separate mechanism: on first enable a memory prompt (long-term notes digest) is injected into context; afterwards it re-injects every N rounds or whenever the context is compacted, so the model always works with memory background.',
2643
+ toggles: [
2644
+ { key: 'associativeMemoryEnabled', name: locale === 'zh' ? '自动联想注入' : 'Automatic association', sub: locale === 'zh' ? '上面①的开关——按相关性自动注入记忆(推荐开)' : 'Path (1) — inject memories by relevance (recommended)', rec: true, where: locale === 'zh' ? '自动记忆引擎' : 'Semantic engine' },
2645
+ { key: 'injectEnabled', name: locale === 'zh' ? '周期记忆快照' : 'Periodic snapshot', sub: locale === 'zh' ? '上面②的开关——定期/压缩后重注入记忆提示(推荐开)' : 'Path (2) — re-inject memory digest periodically / on compaction (recommended)', rec: true, where: locale === 'zh' ? '记忆窗口' : 'Memory window' },
2646
+ ] },
2647
+ { art: 'bell', core: '', kicker: locale === 'zh' ? '日常体验' : 'EXPERIENCE',
2648
+ title: locale === 'zh' ? '暂离问候与无人值守' : 'Greeting & unattended',
2649
+ text: locale === 'zh' ? '离开超过一小时回来,自动打开记忆面板并送上问候。跑批处理/无人值守任务?开启托管后:不弹问候、不注入寒暄与行为指令、日历提醒静默——模型专注干活,上下文稳定。夜间(22:00-08:00)可自动进入托管。'
2650
+ : 'After >1h away the memory panel auto-opens with a greeting. Running batch/unattended jobs? Turn on unattended mode: no greetings, no niceties or behavioural directives, calendar silent — the model stays focused and context stays stable. Auto-engage overnight (22:00-08:00) if you like.',
2651
+ toggles: [
2652
+ { key: 'autoPopupEnabled', name: locale === 'zh' ? '暂离问候' : 'Welcome-back greeting', sub: locale === 'zh' ? '暂离超 1 小时回归时自动弹出面板并问候(推荐开)' : 'Auto-open the panel with a greeting after >1h away (recommended)', rec: true, where: locale === 'zh' ? '自动化' : 'Automation' },
2653
+ { key: 'unattendedAuto', name: locale === 'zh' ? '夜间/批量自动托管' : 'Auto-unattended', sub: locale === 'zh' ? '22:00-08:00 或检测到托管任务时自动进入:零寒暄、上下文冻结、仅保留记忆存取' : 'Auto-engage 22:00-08:00 or when a hosted task is detected: zero niceties, frozen context, memory only', def: false, where: locale === 'zh' ? '自动化' : 'Automation' },
2654
+ ] },
2655
+ { art: 'calendar', core: '', kicker: locale === 'zh' ? '每日助理' : 'DAILY ASSISTANT',
2656
+ title: locale === 'zh' ? '反思与总结' : 'Reflections & summaries',
2657
+ text: locale === 'zh' ? '让记忆按天组织、按时汇报:'
2658
+ : 'Keep memories organized by day:',
2659
+ toggles: [
2660
+ { key: 'reflectEnabled', name: locale === 'zh' ? '每日反思' : 'Daily reflection', sub: locale === 'zh' ? '每天第一次会话时,主动呈现前一天的工作反思' : 'Present the reflection of the previous day at the first session of each day', where: locale === 'zh' ? '自动化' : 'Automation' },
2661
+ { key: 'autoSummaryTimes', name: locale === 'zh' ? '定时总结' : 'Scheduled summaries', sub: locale === 'zh' ? '到点(12:00 / 18:00 / 22:00)自动总结本时段工作并弹窗' : 'Summarize the current period at 12:00 / 18:00 / 22:00', boolOn: ['12:00', '18:00', '22:00'], boolOff: [], where: locale === 'zh' ? '自动化' : 'Automation' },
2662
+ ] },
2663
+ { art: 'link', core: '', kicker: locale === 'zh' ? '外部记忆' : 'EXTERNAL',
2664
+ title: locale === 'zh' ? '接入别的 AI(扫描结果)' : 'Other AIs (scanned)',
2665
+ text: locale === 'zh' ? '已扫描本机可读的外部来源——勾选你想让插件读取的(只存路径指针,不复制内容):'
2666
+ : 'Scanned sources found on this machine — tick the ones the plugin may read (path pointers only, no copying):',
2667
+ externalScan: true },
2668
+ { art: 'engine', core: '', kicker: locale === 'zh' ? '检索引擎' : 'RETRIEVAL', dl: true,
2669
+ title: locale === 'zh' ? '三级语义引擎' : 'Three-tier semantic engine',
2670
+ text: locale === 'zh' ? '词法检索(0GB)永远可用作保底;内置语义引擎(约 130MB 量化模型)显著提升召回;进阶 Python 引擎(BGE-M3,约 563MB)面向深度用户。以下为自动检测结果,可在此直接安装。另有一个与隐私相关的检索信号开关:'
2671
+ : 'Lexical search (0GB) is the always-on floor; the built-in engine (~130MB quantized model) boosts recall; the advanced Python engine (BGE-M3, ~563MB) is optional. Live detection below — install right here. One privacy-related retrieval switch:',
2672
+ toggles: [
2673
+ { key: 'reasoningObserverEnabled', name: locale === 'zh' ? '思维链监听' : 'Reasoning observer', sub: locale === 'zh' ? '监听模型思维链分段作为检索信号(默认关;内容比可见输出更敏感,按需开)' : 'Watch model CoT segments as a retrieval signal (default off; more sensitive than visible output)', def: false, where: locale === 'zh' ? '自动记忆引擎' : 'Semantic engine' },
2674
+ ] },
2675
+ { art: 'radar', core: '', kicker: locale === 'zh' ? '唤起与固化' : 'ACTIVATION',
2676
+ title: locale === 'zh' ? '该出手时才出手' : 'Interrupt only when it matters',
2677
+ text: locale === 'zh' ? '记忆唤起=在对话链(CoT/上下文)中直接检测回忆需求,命中即插入下一个环节;记忆固化=每轮结束把结论沉淀进记忆店,供下次唤起。每次决策都能在「唤起回顾」页复核打分。'
2678
+ : 'Recall = detect memory needs directly in the conversation chain (CoT/context) and insert at the next boundary. Consolidation = settle conclusions into the stores each turn for later recall. Grade every decision in the Recall review tab.',
2679
+ toggles: [
2680
+ { key: 'activationEmitMode', name: locale === 'zh' ? '记忆唤起(链中检测→插入)' : 'Memory recall (detect → inject)', sub: locale === 'zh' ? '开=canary 档(显式回忆注入,推荐)/ 关=shadow 档(只记录不注入)' : 'On = canary (inject on explicit recall, recommended) / Off = shadow (record only)', mode: true, rec: true, where: locale === 'zh' ? '自动记忆引擎' : 'Semantic engine' },
2681
+ { key: 'autoConsolidate', name: locale === 'zh' ? '记忆固化(自动沉淀)' : 'Memory consolidation', sub: locale === 'zh' ? '每轮对话结束自动把结论沉淀进每日日志与记忆店(推荐开)' : 'Consolidate conclusions into the log & stores each turn (recommended)', rec: true, where: locale === 'zh' ? '自动化' : 'Automation' },
2682
+ { key: 'procedurePromotionEnabled', name: locale === 'zh' ? '技能固化与晋升' : 'Skill crystallization', sub: locale === 'zh' ? '重复流程固化为 checklist 自动附上;跨会话验证后晋升(「记忆中枢」页审批)' : 'Turn repeated flows into auto-attached checklists; promote after validation (Memory Hub tab)', where: locale === 'zh' ? '记忆中枢' : 'Memory Hub' },
2683
+ ] },
2684
+ { art: 'rocket', core: '', kicker: locale === 'zh' ? '完成' : 'READY', final: true,
2685
+ title: locale === 'zh' ? '一切就绪' : 'All set',
2686
+ text: locale === 'zh' ? '你刚才的选择都已即时保存。改主意了?随时在这几个地方重新打开:'
2687
+ : 'Every choice above was saved instantly. Changed your mind? Revisit them here anytime:' },
2688
+ ]
2689
+ // 语义引擎状态(wizSt 已在 DialogHost 顶部初始化;下载轮询 useEffect 同样在顶部)
2690
+ var dl = wizSt.download || { phase: 'idle' }
2691
+ var dlActive = dl.phase === 'downloading' || dl.phase === 'verifying'
2692
+ var wizReady = wizSt.ready
2693
+ var totalBytes = wizSt.manifestBytes || Math.round(130 * 1024 * 1024)
2694
+ var prog = dlActive || dl.phase === 'done'
2695
+ ? Math.min(100, Math.round(((dl.bytesDone || 0) / Math.max(1, dl.bytesTotal || totalBytes)) * 100))
2696
+ : (wizReady ? 100 : 0)
2697
+ var fmtMB = function (b) { return b ? (b / (1024 * 1024)).toFixed(1) + ' MB' : '—' }
2698
+ var dlPhaseTxt = !wizSt.loaded ? (locale === 'zh' ? '检测中…' : 'Detecting…')
2699
+ : wizReady ? (locale === 'zh' ? '✓ 就绪(SHA256 校验 + 推理自检已通过)' : '✓ Ready (SHA256 verify + inference self-test passed)')
2700
+ : dl.phase === 'verifying' ? t('dlVerifying')
2701
+ : dl.phase === 'downloading' ? (t('dlDownloading') + ' · ' + fmtMB(dl.bytesDone || 0) + ' / ' + fmtMB(dl.bytesTotal || totalBytes))
2702
+ : dl.phase === 'error' ? (t('dlError') + ': ' + String(dl.error || '').slice(0, 90))
2703
+ : dl.phase === 'done' ? t('dlDone')
2704
+ : (locale === 'zh' ? '未下载 · 词法检索照常可用' : 'Not downloaded · lexical search keeps working')
2705
+ var startDl = function () {
2706
+ apiPost('/api/dsh-auto-memory/semantic-download', { action: 'start', mirror: 'auto' }).catch(function () {})
2707
+ }
2708
+ // 功能开关:读当前值(带每项默认)/点击即时写配置(乐观更新+POST,失败不回滚——下次重看向导会以真实配置为准)
2709
+ var EXT_SOURCE_KEYS = ['workbuddy-user', 'workbuddy-profile', 'codebuddy-memory', 'claude-global', 'project-conventions', 'workbuddy-sessions', 'claude-sessions', 'codex-sessions']
2710
+ var tourToggleOn = function (tg) {
2711
+ var c = wizSt.tourCfg || {}
2712
+ if (tg.key === 'autoSummaryTimes') return (c[tg.key] || []).length > 0
2713
+ if (tg.groupAll) { var v = c[tg.key] || {}; return !!v['workbuddy-user'] }
2714
+ if (tg.mode) { var m = c[tg.key]; if (m === undefined) return tg.def === true; return m === 'canary-explicit' || m === 'active' }
2715
+ return c[tg.key] === undefined ? tg.def !== false : !!c[tg.key]
2716
+ }
2717
+ var tourToggleClick = function (tg) {
2718
+ var c = wizSt.tourCfg = wizSt.tourCfg || {}
2719
+ var on = tourToggleOn(tg)
2720
+ var patch = {}
2721
+ if (tg.key === 'autoSummaryTimes') patch[tg.key] = on ? (tg.boolOff || []) : (tg.boolOn || [])
2722
+ else if (tg.groupAll) { var o = {}; for (var i = 0; i < EXT_SOURCE_KEYS.length; i++) o[EXT_SOURCE_KEYS[i]] = !on; patch[tg.key] = o }
2723
+ else if (tg.mode) patch[tg.key] = on ? 'shadow' : 'canary-explicit'
2724
+ else patch[tg.key] = !on
2725
+ Object.assign(c, patch)
2726
+ dlgTick()
2727
+ apiPost('/api/dsh-auto-memory/config', patch).catch(function () {})
2728
+ }
2729
+ // 外部来源单源勾选(扫描行;勾选即写 externalSources 对象)
2730
+ var tourExtToggle = function (id) {
2731
+ var c = wizSt.tourCfg = wizSt.tourCfg || {}
2732
+ var cur = Object.assign({}, c.externalSources || {})
2733
+ cur[id] = (cur[id] !== false) ? false : true
2734
+ c.externalSources = cur
2735
+ dlgTick()
2736
+ apiPost('/api/dsh-auto-memory/config', { externalSources: cur }).catch(function () {})
2737
+ }
2738
+ var tourExtOn = function (id) {
2739
+ var c = wizSt.tourCfg || {}
2740
+ return (c.externalSources || {})[id] !== false
2741
+ }
2742
+ var allToggles = []
2743
+ TOUR_STEPS.forEach(function (s) { if (s.toggles) allToggles = allToggles.concat(s.toggles) })
2744
+ var whereGroups = {}
2745
+ allToggles.forEach(function (tg) { var k = tg.where || '设置'; (whereGroups[k] = whereGroups[k] || []).push(tg.name) })
2746
+ var finishTour = function () {
2747
+ try { localStorage.setItem('dsh-auto-memory.semWizardDone', '1') } catch (e4) {}
2748
+ closeDialog()
2749
+ // v0.1.30 大更新链:向导结束 → 接 CHANGELOG。老用户(seen 落后)带出全部未读版本;
2750
+ // 已追平(含新装)给大更新专卡。apiGet 异步拉当前版本,失败静默(向导本身已完成使命)。
2751
+ try {
2752
+ apiGet(API.updateCheck).then(function (d) {
2753
+ var cur = (d && d.current) || '0.1.30'
2754
+ var seen = localStorage.getItem('dsh-auto-memory.seenVersion')
2755
+ if (seen && seen !== cur) {
2756
+ var versions = changelogBetween(seen, cur)
2757
+ if (versions.length) { openDialog({ kind: 'update', versions: versions, currentVersion: cur }); return }
2758
+ }
2759
+ openDialog({ kind: 'update', versions: [{ version: cur, items: CHANGELOG[cur] || CHANGELOG['0.1.30'] }], currentVersion: cur })
2760
+ }).catch(function () {
2761
+ openDialog({ kind: 'update', versions: [{ version: '0.1.30', items: CHANGELOG['0.1.30'] }], currentVersion: '0.1.30' })
2762
+ })
2763
+ } catch (eChain) {}
2764
+ }
2765
+ var closeOrRemind = function () {
2766
+ // 关闭时机提醒:未到完成步就关 → 先落到完成步(重开位置指引),再点才开始使用
2767
+ if (tourStep < TOUR_STEPS.length - 1) setTourStep(TOUR_STEPS.length - 1)
2768
+ else finishTour()
2769
+ }
2770
+ var step = TOUR_STEPS[Math.min(tourStep, TOUR_STEPS.length - 1)]
2771
+ var isLast = tourStep >= TOUR_STEPS.length - 1
2772
+ // 每步只生成当前图形所需 DOM;避免旧实现一次创建 22 个 span、非当前零件塌成 0/2px。
2773
+ var renderTourArt = function (type) {
2774
+ var piece = function (cls) { return h('span', { className: 'ap ' + cls }) }
2775
+ var children
2776
+ if (type === 'store') children = [piece('plate p1'), piece('plate p2'), piece('plate p3')]
2777
+ else if (type === 'inject') children = [piece('inject-capsule'), piece('inject-drop'), piece('inject-pulse')]
2778
+ else if (type === 'bell') children = [piece('bell-shell'), piece('bell-base'), piece('bell-clapper')]
2779
+ else if (type === 'calendar') children = [piece('calendar-card'), piece('calendar-bind b1'), piece('calendar-bind b2'), piece('calendar-page')]
2780
+ else if (type === 'link') children = [piece('link-ring l1'), piece('link-ring l2'), piece('link-glint')]
2781
+ else if (type === 'engine') children = [piece('engine-prism'), piece('engine-core'), piece('engine-orbit')]
2782
+ else if (type === 'radar') children = [piece('radar-outer'), piece('radar-inner'), piece('radar-sweep'), piece('radar-ping')]
2783
+ else if (type === 'rocket') children = [piece('rocket-tier t1'), piece('rocket-tier t2'), piece('rocket-tier t3'), piece('rocket-spark')]
2784
+ else children = [piece('bubble-orb'), piece('bubble-seed s1'), piece('bubble-seed s2')]
2785
+ return h('div', { 'data-dam-tour-art': type || 'bubble', key: 'art' + tourStep }, children)
2786
+ }
2787
+ return h('div', { 'data-dam-tour-backdrop': '',
2788
+ onMouseMove: function (e) {
2789
+ // Liquid Glass 动态响应:高光/图标 3D 倾斜跟随鼠标(卡内坐标百分比)
2790
+ var el = e.currentTarget.querySelector('[data-dam-tour]')
2791
+ if (!el) return
2792
+ var r = el.getBoundingClientRect()
2793
+ el.style.setProperty('--dam-mx', (((e.clientX - r.left) / r.width) * 100).toFixed(1) + '%')
2794
+ el.style.setProperty('--dam-my', (((e.clientY - r.top) / r.height) * 100).toFixed(1) + '%')
2795
+ } },
2796
+ h('div', { 'data-dam-tour': '' },
2797
+ h('div', { 'data-dam-tour-glare': '' }),
2798
+ h('button', { 'data-dam-tour-close': '', title: t('close'), onClick: closeOrRemind }, '✕'),
2799
+ h('div', { 'data-dam-tour-orb-wrap': '', key: 'orb' + tourStep, 'data-step': String(tourStep), 'data-art': step.art || 'bubble' },
2800
+ h('div', { 'data-dam-tour-bokeh': 'a' }),
2801
+ h('div', { 'data-dam-tour-bokeh': 'b' }),
2802
+ h('div', { 'data-dam-tour-bokeh': 'c' }),
2803
+ step.art === 'store' ? h('div', { 'data-dam-tour-stage': '', key: 'stage' + tourStep },
2804
+ h('div', { 'data-dam-tour-slab': 'bot' }),
2805
+ h('div', { 'data-dam-tour-slab': 'mid' }),
2806
+ h('div', { 'data-dam-tour-slab': 'top' })) : h('div', { 'data-dam-tour-app-tile': '', key: 'tile' + tourStep }),
2807
+ step.art === 'store' ? null : renderTourArt(step.art || 'bubble')),
2808
+ h('div', { 'data-dam-tour-body': '', key: 'body' + tourStep, 'data-dam-tour-swap': '' },
2809
+ h('div', { 'data-dam-tour-kicker': '' }, step.kicker),
2810
+ h('div', { 'data-dam-tour-title': '' }, step.title),
2811
+ h('div', { 'data-dam-tour-text': '' }, step.text),
2812
+ step.toggles ? h('div', { 'data-dam-tour-toggles': '' },
2813
+ step.toggles.map(function (tg, ti) {
2814
+ var on = tourToggleOn(tg)
2815
+ return h('button', { key: ti, 'data-dam-tour-tg': '', 'data-on': String(on), onClick: function () { tourToggleClick(tg) } },
2816
+ h('div', { 'data-dam-tour-tg-txt': '' },
2817
+ h('div', { 'data-dam-tour-tg-name': '' }, tg.name, tg.rec ? h('span', { 'data-dam-tour-rec': '' }, locale === 'zh' ? '推荐' : 'REC') : null),
2818
+ h('div', { 'data-dam-tour-tg-sub': '' }, tg.sub)),
2819
+ h('div', { 'data-dam-tour-sw': '', 'data-on': String(on) }))
2820
+ })) : null,
2821
+ step.externalScan ? h('div', { 'data-dam-tour-toggles': '', 'data-scroll': 'true' },
2822
+ !(wizSt.extScan && wizSt.extScan.sources) ? h('div', { style: { opacity: .55, fontSize: '12px', padding: '8px 4px' } }, locale === 'zh' ? '正在扫描本机来源…' : 'Scanning local sources…')
2823
+ : (wizSt.extScan.sources || []).map(function (src) {
2824
+ var on = tourExtOn(src.id)
2825
+ return h('button', { key: src.id, 'data-dam-tour-tg': '', 'data-on': String(on), onClick: function () { tourExtToggle(src.id) } },
2826
+ h('div', { 'data-dam-tour-tg-txt': '' },
2827
+ h('div', { 'data-dam-tour-tg-name': '' }, src.name, h('span', { style: { opacity: .5, fontWeight: 400, fontSize: '10.5px', marginLeft: '6px' } }, src.tool + ' · ' + src.kind)),
2828
+ h('div', { 'data-dam-tour-tg-sub': '' }, (locale === 'zh' ? '已检测到 · ' : 'found · ') + (src.size > 1048576 ? (src.size / 1048576).toFixed(1) + ' MB' : Math.max(1, Math.round(src.size / 1024)) + ' KB'))),
2829
+ h('div', { 'data-dam-tour-sw': '', 'data-on': String(on) }))
2830
+ })) : null,
2831
+ step.dl ? h('div', { 'data-dam-tour-dl': '' },
2832
+ h('div', { 'data-dam-tour-dl-row': '' },
2833
+ h('span', null, dlPhaseTxt),
2834
+ h('span', null, wizReady ? 'C2 · 129MB' : (prog + '%'))),
2835
+ wizReady ? null : h('div', { 'data-dam-tour-bar': '' },
2836
+ h('div', { 'data-dam-tour-bar-i': '', style: { width: prog + '%', background: dl.phase === 'error' ? 'linear-gradient(90deg,#c44a4a,#e08a8a)' : undefined } })),
2837
+ !wizReady && wizSt.loaded && !dlActive ? h('div', { 'data-dam-tour-dl-row': '', style: { marginTop: '4px' } },
2838
+ h('span', null, (locale === 'zh' ? '下载源: ' : 'Source: ') + (dl.mirrorUsed === 'cn' ? t('mCn') : dl.mirrorUsed === 'intl' ? t('mIntl') : t('mAuto')) + (locale === 'zh' ? ' · 失败自动切备用源' : ' · auto-failover'))) : null) : null,
2839
+ step.final ? h('div', null,
2840
+ h('div', { 'data-dam-tour-chips': '' },
2841
+ allToggles.filter(function (tg) { return tourToggleOn(tg) }).map(function (tg) {
2842
+ return h('span', { key: tg.key, 'data-dam-tour-badge': '', style: { background: 'rgba(47,164,106,.20)', color: '#7fdcb0' } }, '✓ ' + tg.name)
2843
+ }),
2844
+ allToggles.filter(function (tg) { return !tourToggleOn(tg) }).map(function (tg) {
2845
+ return h('span', { key: 'off' + tg.key, 'data-dam-tour-badge': '', style: { background: 'rgba(128,128,128,.16)', opacity: .7 } }, tg.name + (locale === 'zh' ? ' 关' : ' off'))
2846
+ })),
2847
+ h('div', { 'data-dam-tour-where': '' },
2848
+ Object.keys(whereGroups).map(function (k) {
2849
+ return h('div', { key: k }, '· ', h('b', null, k), ' —— ' + whereGroups[k].join(locale === 'zh' ? ' / ' : ' / '))
2850
+ }),
2851
+ h('div', null, '· ', h('b', null, locale === 'zh' ? '面板页签' : 'Panel tabs'), locale === 'zh' ? ' —— 唤起回顾(决策打分)/ 存储管理(扫描修复)' : ' — Recall review / Storage tools'))) : null),
2852
+ h('div', { 'data-dam-tour-dots': '' },
2853
+ TOUR_STEPS.map(function (s, si) {
2854
+ return h('button', { key: si, 'data-dam-tour-dot': '', 'data-on': String(si === tourStep),
2855
+ onClick: function () { setTourStep(si) }, title: s.kicker })
2856
+ })),
2857
+ h('div', { 'data-dam-tour-foot': '' },
2858
+ h('button', { 'data-dam-tour-skip': '', onClick: closeOrRemind }, locale === 'zh' ? '跳过向导' : 'Skip tour'),
2859
+ h('button', { 'data-dam-tour-btn': '', 'data-primary': 'false', disabled: tourStep === 0,
2860
+ onClick: function () { setTourStep(Math.max(0, tourStep - 1)) } },
2861
+ locale === 'zh' ? '‹ 上一步' : '‹ Back'),
2862
+ h('button', { 'data-dam-tour-btn': '', 'data-primary': 'true',
2863
+ onClick: function () { isLast ? finishTour() : setTourStep(tourStep + 1) } },
2864
+ isLast ? (locale === 'zh' ? '开始使用' : 'Get started')
2865
+ : (locale === 'zh' ? '下一步 ›' : 'Next ›')))))
1827
2866
  }
1828
2867
  if (dialogState.kind === 'notice') {
1829
2868
  var n = dialogState.notice || {}
@@ -1832,21 +2871,25 @@ window.__ModuleLoader__.load({
1832
2871
  var nMsg = zh ? (n.message || '') : (n.messageEn || n.message || '')
1833
2872
  var isUrgent = n.level === 'urgent'
1834
2873
  var accent = isUrgent ? 'var(--dsw-alias-danger, #e5534b)' : 'var(--dsw-alias-brand-primary, #4f7cff)'
2874
+ var noticeButton = h('button', { 'data-dam-btn': '', style: close, onClick: function () {
2875
+ try {
2876
+ var arr = []
2877
+ try { arr = JSON.parse(localStorage.getItem('dsh-auto-memory.seenNotices') || '[]') } catch (e3) {}
2878
+ if (n.id && arr.indexOf(n.id) < 0) arr.push(n.id)
2879
+ localStorage.setItem('dsh-auto-memory.seenNotices', JSON.stringify(arr))
2880
+ } catch (e3) {}
2881
+ closeDialog()
2882
+ } }, t('gotIt'))
2883
+ var noticeChildren = [
2884
+ h('div', { style: Object.assign({}, head, isUrgent ? { color: accent } : {}) }, nTitle),
2885
+ h('div', { style: { fontSize: 'calc(12px * var(--dam-scale))', lineHeight: 1.6, opacity: .92, whiteSpace: 'pre-wrap', marginTop: '4px' } }, nMsg),
2886
+ h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end', marginTop: '6px' } },
2887
+ n.link ? h('a', { href: n.link, target: '_blank', rel: 'noreferrer', style: Object.assign({}, close, { textDecoration: 'none' }) }, t('noticeOpen')) : null,
2888
+ noticeButton),
2889
+ ]
1835
2890
  return h('div', { style: overlay },
1836
2891
  h('div', { style: box },
1837
- h('div', { style: Object.assign({}, head, isUrgent ? { color: accent } : {}) }, nTitle),
1838
- h('div', { style: { fontSize: 'calc(12px * var(--dam-scale))', lineHeight: 1.6, opacity: .92, whiteSpace: 'pre-wrap', marginTop: '4px' } }, nMsg),
1839
- h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end', marginTop: '6px' } },
1840
- n.link ? h('a', { href: n.link, target: '_blank', rel: 'noreferrer', style: Object.assign({}, close, { textDecoration: 'none' }) }, t('noticeOpen')) : null,
1841
- h('button', { 'data-dam-btn': '', style: close, onClick: function () {
1842
- try {
1843
- var arr = []
1844
- try { arr = JSON.parse(localStorage.getItem('dsh-auto-memory.seenNotices') || '[]') } catch (e3) {}
1845
- if (n.id && arr.indexOf(n.id) < 0) arr.push(n.id)
1846
- localStorage.setItem('dsh-auto-memory.seenNotices', JSON.stringify(arr))
1847
- } catch (e3) {}
1848
- closeDialog()
1849
- } }, t('gotIt')))))
2892
+ DamIntroBox(noticeChildren)))
1850
2893
  }
1851
2894
  if (dialogState.kind === 'welcomeBack') {
1852
2895
  return h('div', { style: overlay },
@@ -1872,18 +2915,24 @@ window.__ModuleLoader__.load({
1872
2915
  var lastV = versions.length ? versions[versions.length - 1].version : ''
1873
2916
  return h('div', { style: overlay },
1874
2917
  h('div', { style: box },
1875
- h('div', { style: head }, t('updateTitle') + ' v' + lastV),
1876
- h('div', { style: sub }, t('updateSub')),
1877
- versions.map(function (v) {
1878
- var items = (v.items && (v.items[locale] || v.items.zh)) || []
1879
- return h('div', null,
1880
- h('div', { style: { fontSize: 'calc(13px * var(--dam-scale))', fontWeight: 700, margin: '6px 0 2px', opacity: .9 } }, 'v' + v.version),
1881
- items.map(function (it) { return h('div', { style: item }, h('span', { style: dot }), it) }))
1882
- }),
1883
- h('button', { 'data-dam-btn': '', style: close, onClick: function () {
1884
- try { if (lastV) localStorage.setItem('dsh-auto-memory.seenVersion', lastV) } catch (e3) {}
2918
+ // 右上 关闭(与小卡 46vh 裁剪下的底部按钮互为兜底——否则内容溢出时按钮不可达,弹窗关不掉)
2919
+ h('button', { 'data-dam-btn': '', title: t('close'), onClick: function () {
2920
+ try { if (lastV) localStorage.setItem('dsh-auto-memory.seenVersion', lastV) } catch (eX) {}
1885
2921
  closeDialog()
1886
- } }, t('gotIt'))))
2922
+ }, style: { position: 'absolute', top: '8px', right: '10px', zIndex: 5, fontSize: 'calc(13px * var(--dam-scale))', opacity: .6 } }, '✕'),
2923
+ DamIntroBox([
2924
+ h('div', { style: head }, t('updateTitle') + ' v' + lastV),
2925
+ h('div', { style: sub }, t('updateSub')),
2926
+ versions.map(function (v) {
2927
+ var items = (v.items && (v.items[locale] || v.items.zh)) || []
2928
+ return h('div', null,
2929
+ h('div', { style: { fontSize: 'calc(13px * var(--dam-scale))', fontWeight: 700, margin: '6px 0 2px', opacity: .9 } }, 'v' + v.version),
2930
+ items.map(function (it) { return h('div', { style: item }, h('span', { style: dot }), it) }))
2931
+ }),
2932
+ h('button', { 'data-dam-btn': '', style: close, onClick: function () {
2933
+ try { if (lastV) localStorage.setItem('dsh-auto-memory.seenVersion', lastV) } catch (e3) {}
2934
+ closeDialog()
2935
+ } }, t('gotIt'))])))
1887
2936
  }
1888
2937
 
1889
2938
  // ───────────────────────── 设置页 ─────────────────────────
@@ -1924,6 +2973,32 @@ window.__ModuleLoader__.load({
1924
2973
  var browseDirsPair = useState(null)
1925
2974
  var browseDirs = browseDirsPair[0]
1926
2975
  var setBrowseDirs = browseDirsPair[1]
2976
+ // 「总结/问候默认模型」模型抽屉状态
2977
+ var mdlOpenPair = useState(false)
2978
+ var mdlOpen = mdlOpenPair[0]
2979
+ var setMdlOpen = mdlOpenPair[1]
2980
+ var mdlLoadPair = useState(false)
2981
+ var mdlLoading = mdlLoadPair[0]
2982
+ var setMdlLoading = mdlLoadPair[1]
2983
+ var mdlDataPair = useState(null)
2984
+ var mdlData = mdlDataPair[0]
2985
+ var setMdlData = mdlDataPair[1]
2986
+ var mdlErrPair = useState('')
2987
+ var mdlErr = mdlErrPair[0]
2988
+ var setMdlErr = mdlErrPair[1]
2989
+ function openModels() {
2990
+ setMdlOpen(true)
2991
+ setMdlErr('')
2992
+ if (mdlData) return // 已加载过目录,直接展示(保存后重开设置页会重新挂载)
2993
+ setMdlLoading(true)
2994
+ apiGet(API.models).then(function (d) {
2995
+ setMdlData(d || { providers: [] })
2996
+ setMdlLoading(false)
2997
+ }).catch(function (e) {
2998
+ setMdlErr(String(e && e.message ? e.message : e))
2999
+ setMdlLoading(false)
3000
+ })
3001
+ }
1927
3002
  function browseTo(p) {
1928
3003
  setBrowsePath(p)
1929
3004
  apiPost(API.browseDir, { path: p }).then(function (d) {
@@ -1949,6 +3024,39 @@ window.__ModuleLoader__.load({
1949
3024
  browseTo(cfg.memoryRoot || '')
1950
3025
  })
1951
3026
  }
3027
+ // 「总结/问候默认模型」抽屉(复审轮2新增功能的选型 UI):自动检测 llm 目录,分组展示,点选即设
3028
+ function buildModelDrawer() {
3029
+ var panelStyle = { border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.25)) 60%, transparent)', borderRadius: '8px', padding: '8px', marginBottom: '8px', maxHeight: '260px', overflow: 'auto', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.06)) 40%, transparent)' }
3030
+ var kids = []
3031
+ kids.push(h('div', { 'data-dam-row': '', style: { marginBottom: '4px' } },
3032
+ h('b', { style: { flex: 1, fontSize: 'calc(12px * var(--dam-scale))' } }, locale === 'zh' ? '选择模型(自动检测)' : 'Pick a model (auto-detected)'),
3033
+ h('button', { 'data-dam-btn': '', onClick: function () { setMdlOpen(false) } }, t('close'))))
3034
+ if (mdlLoading) kids.push(h('div', { 'data-dam-hint': '' }, locale === 'zh' ? '正在检测可用模型…' : 'Detecting models…'))
3035
+ if (mdlErr) kids.push(h('div', { 'data-dam-error': '' }, mdlErr))
3036
+ if (!mdlLoading && !mdlErr && mdlData) {
3037
+ kids.push(h('button', { key: '__default__', 'data-dam-btn': '', style: { display: 'block', width: '100%', textAlign: 'left', padding: '4px 6px', opacity: cfg.subagentModel ? 1 : 0.75 }, onClick: function () { set('subagentModel', ''); setMdlOpen(false) } },
3038
+ locale === 'zh' ? '跟随路由默认(留空)' : 'Follow routing default (empty)'))
3039
+ ;(mdlData.providers || []).forEach(function (p) {
3040
+ var modelBtns = (p.models || []).length
3041
+ ? p.models.map(function (m) {
3042
+ return h('button', { key: p.id + '/' + m.id, 'data-dam-btn': '', style: { display: 'block', width: '100%', textAlign: 'left', padding: '3px 6px', fontWeight: cfg.subagentModel === m.id ? 700 : 400 }, onClick: function () { set('subagentModel', m.id); setMdlOpen(false) } },
3043
+ m.id + (m.name && m.name !== m.id ? ' · ' + m.name : '') + (cfg.subagentModel === m.id ? ' ✓' : ''))
3044
+ })
3045
+ : [h('div', { key: 'none', 'data-dam-hint': '' }, locale === 'zh' ? '(该 provider 未列出模型)' : '(no models advertised)')]
3046
+ kids.push(h('div', { key: 'g-' + p.id, style: { marginTop: '6px' } },
3047
+ h('div', { style: { fontSize: 'calc(11px * var(--dam-scale))', fontWeight: 700, opacity: 0.75, margin: '2px 0' } }, p.name || p.id),
3048
+ modelBtns))
3049
+ })
3050
+ ;(mdlData.failures || []).forEach(function (f) {
3051
+ kids.push(h('div', { key: 'f-' + f.id, 'data-dam-hint': '', style: { opacity: 0.65 } }, '⚠ ' + (f.name || f.id) + ': ' + f.message))
3052
+ })
3053
+ if (!(mdlData.providers || []).length && !(mdlData.failures || []).length) {
3054
+ kids.push(h('div', { 'data-dam-hint': '' }, locale === 'zh' ? '未检测到 provider;可直接在下方手动输入。' : 'No providers detected; use manual input below.'))
3055
+ }
3056
+ kids.push(h('input', { key: '__manual__', 'data-dam-input': '', style: { marginTop: '6px', width: '100%' }, value: cfg.subagentModel || '', placeholder: locale === 'zh' ? '手动输入(可选)' : 'manual entry (optional)', onChange: function (e) { set('subagentModel', String(e.target.value || '').trim()) } }))
3057
+ }
3058
+ return h('div', { style: panelStyle }, kids)
3059
+ }
1952
3060
  var verPair = useState(null)
1953
3061
  var verInfo = verPair[0]
1954
3062
  var setVerInfo = verPair[1]
@@ -1969,6 +3077,37 @@ window.__ModuleLoader__.load({
1969
3077
  apiGet(API.updateCheck).then(function (d) { if (alive) setVerInfo(d) }).catch(function () {})
1970
3078
  return function () { alive = false }
1971
3079
  }, [])
3080
+ // M7.5 语义引擎资产状态与安装引导(Hooks 必须位于任何条件 return 之前——React 规则,
3081
+ // 否则 cfg 未加载时提前 return 会跳过这些 useState,二次渲染 hooks 数量不一致 → error #310)
3082
+ var semPair = useState({ loaded: false, ready: false, assetPresent: false, peerPresent: false, pythonInt8Present: false })
3083
+ var sem = semPair[0]
3084
+ var setSem = semPair[1]
3085
+ var guidePair = useState('')
3086
+ var promptEditPair = useState(false)
3087
+ var promptEditOpen = promptEditPair[0]
3088
+ var setPromptEditOpen = promptEditPair[1]
3089
+ var guide = guidePair[0]
3090
+ var setGuide = guidePair[1]
3091
+ var mirrorPair = useState('auto')
3092
+ var mirror = mirrorPair[0]
3093
+ var setMirror = mirrorPair[1]
3094
+ useEffect(function () {
3095
+ fetch('/api/dsh-auto-memory/semantic-status').then(function (r) { return r.json() }).then(function (j) {
3096
+ setSem(Object.assign({ loaded: true }, j))
3097
+ }).catch(function () { setSem({ loaded: true, ready: false }) })
3098
+ return function () {}
3099
+ }, [])
3100
+ // 下载进行中每 1.5s 轮询真实进度(服务端流式记账 bytesDone/bytesTotal/mirrorUsed)
3101
+ useEffect(function () {
3102
+ var ph = sem && sem.download && sem.download.phase
3103
+ if (ph !== 'downloading' && ph !== 'verifying') return function () {}
3104
+ var iv = setInterval(function () {
3105
+ fetch('/api/dsh-auto-memory/semantic-status').then(function (r) { return r.json() }).then(function (j) {
3106
+ setSem(Object.assign({ loaded: true }, j))
3107
+ }).catch(function () {})
3108
+ }, 1500)
3109
+ return function () { clearInterval(iv) }
3110
+ }, [sem && sem.download && sem.download.phase])
1972
3111
  if (!cfg) return err ? h('div', { 'data-dam-error': '' }, err) : h(Loading)
1973
3112
  function set(key, value) { var next = Object.assign({}, cfg); next[key] = value; setCfg(next); setDirty(true) }
1974
3113
  function checkUpdate() {
@@ -1989,7 +3128,7 @@ window.__ModuleLoader__.load({
1989
3128
  function save() {
1990
3129
  if (busy) return
1991
3130
  setBusy(true); setMsg(''); setErr('')
1992
- apiPost(API.config, cfg).then(function (d) { setCfg(d.config); setDirty(false); setMsg(t('saved') + (d.migrated ? ' ' + d.migrated : '')); setBusy(false); if (d && d.config && d.config.locale) applyLocalePref(d.config.locale) })
3131
+ apiPost(API.config, cfg).then(function (d) { setCfg(d.config); setDirty(false); setMsg(t('saved') + (d.migrated ? ' ' + d.migrated : '')); setBusy(false); if (d && d.config && d.config.locale) applyLocalePref(d.config.locale); try { fetch('/api/dsh-auto-memory/semantic-status').then(function (r2) { return r2.json() }).then(function (j2) { setSem(Object.assign({ loaded: true }, j2)) }).catch(function () {}) } catch (_) {} })
1993
3132
  .catch(function (e) { setErr(e.message); setBusy(false) })
1994
3133
  }
1995
3134
  function field(label, control, hint) {
@@ -2007,7 +3146,30 @@ window.__ModuleLoader__.load({
2007
3146
  try { localStorage.setItem('dsh-auto-memory.graphDensity.v1', graphDensity) } catch (e) {}
2008
3147
  emit()
2009
3148
  }
3149
+ function onEngineModeChange(e) {
3150
+ var v = e.target.value
3151
+ // 2026-08-27 修复:切换永远执行,资产检测不 gate/不弹卡(资产缺失自动降级词法)。
3152
+ // 之前 sem 异步未加载时拦截导致「怎么切都没变化」。引导卡仅由用户主动点出。
3153
+ setGuide('')
3154
+ // 2026-08-27 模式联动(修基础 bug):一次 set 提交全部改动——连续多次 set 基于同一闭包
3155
+ // 会互相覆盖(React 异步,后者 Object.assign 旧 cfg 丢前者),导致 semanticEngineMode 不保存。
3156
+ // js=JS 判定闭环;python=Python sidecar;auto/lexical=默认(JS 判定+词法保底)。
3157
+ var next = Object.assign({}, cfg)
3158
+ next.semanticEngineMode = v
3159
+ if (v === 'js') { next.activationSource = 'js'; next.contextSinkMode = 'null' }
3160
+ else if (v === 'python') { next.activationSource = 'python'; next.contextSinkMode = 'python' }
3161
+ else { next.activationSource = 'js'; next.contextSinkMode = 'null' }
3162
+ try { console.log('[dam] engine mode change →', v, JSON.stringify({ semanticEngineMode: next.semanticEngineMode, activationSource: next.activationSource, contextSinkMode: next.contextSinkMode })) } catch (_) {}
3163
+ setCfg(next); setDirty(true)
3164
+ // 2026-08-27 修复显示不跟随:切换后重新 fetch semantic-status,刷新「当前生效检索」
3165
+ // (sem.resolvedTier 原只在挂载/下载时更新,切换后不刷新导致一直显示旧档位)。
3166
+ try {
3167
+ fetch('/api/dsh-auto-memory/semantic-status').then(function (r2) { return r2.json() }).then(function (j2) { setSem(Object.assign({ loaded: true }, j2)) }).catch(function () {})
3168
+ } catch (_) {}
3169
+ }
2010
3170
  var sectionLabels = {
3171
+ semantic: locale === 'zh' ? '自动记忆引擎' : 'Semantic engine',
3172
+ memoryHub: locale === 'zh' ? '记忆中枢' : 'Memory Hub',
2011
3173
  appearance: locale === 'zh' ? '外观' : 'Appearance', storage: locale === 'zh' ? '存储' : 'Storage',
2012
3174
  injection: locale === 'zh' ? '记忆窗口' : 'Memory window', automation: locale === 'zh' ? '自动化' : 'Automation',
2013
3175
  maintenance: locale === 'zh' ? '维护' : 'Maintenance'
@@ -2022,8 +3184,133 @@ window.__ModuleLoader__.load({
2022
3184
  return h('button', { key: key, 'data-dam-btn': '', 'data-active': settingsSection === key ? 'true' : undefined, onClick: function () { jumpToSection(key) } }, sectionLabels[key])
2023
3185
  })),
2024
3186
  h('div', { 'data-dam-settings-content': '' },
3187
+ section('semantic', sectionLabels.secSemantic, [
3188
+ field(t('fAssocEngine'), h('input', { type: 'checkbox', checked: !!cfg.associativeMemoryEnabled, onChange: function (e) { set('associativeMemoryEnabled', e.target.checked) } }), t('fAssocEngineHint')),
3189
+ field(t('fJsCooldown'), h('input', { 'data-dam-input': '', type: 'number', min: 0, max: 60, value: cfg.jsDecideCooldownRounds === undefined ? 1 : cfg.jsDecideCooldownRounds, onChange: function (e) { set('jsDecideCooldownRounds', Number(e.target.value) || 1) } }), t('fJsCooldownHint')),
3190
+ field(t('fJsDelta'), h('input', { 'data-dam-input': '', type: 'number', min: 0, max: 1, step: 0.005, value: cfg.jsDecideDeltaExp === undefined ? 0.01 : cfg.jsDecideDeltaExp, onChange: function (e) { var v = Number(e.target.value); set('jsDecideDeltaExp', Number.isFinite(v) && v >= 0 ? v : 0.01) } }), t('fJsDeltaHint')),
3191
+ field(t('fJsExcerpt'), h('input', { 'data-dam-input': '', type: 'number', min: 20, max: 480, value: cfg.jsDecideExcerptChars === undefined ? 40 : cfg.jsDecideExcerptChars, onChange: function (e) { set('jsDecideExcerptChars', Math.max(20, Math.min(480, Number(e.target.value) || 40))) } }), t('fJsExcerptHint')),
3192
+ field(t('fEmitMode'), h('select', { 'data-dam-select': '', value: (sem && sem.activationEmitMode) || 'shadow', onChange: function (e) { var m = e.target.value; apiPost('/api/dsh-auto-memory/semantic-emit', { mode: m }).then(function () { try { fetch('/api/dsh-auto-memory/semantic-status').then(function (r2) { return r2.json() }).then(function (j2) { setSem(Object.assign({ loaded: true }, j2)) }).catch(function () {}) } catch (_) {} }).catch(function () {}) } },
3193
+ h('option', { value: 'shadow' }, locale === 'zh' ? 'shadow 只记录' : 'shadow (record only)'),
3194
+ h('option', { value: 'canary-explicit' }, locale === 'zh' ? 'canary 显式回忆注入' : 'canary (explicit recall)'),
3195
+ h('option', { value: 'active' }, locale === 'zh' ? 'active 全部注入' : 'active (all)')), t('fEmitModeHint')),
3196
+ field(t('fCandScheme'), h('select', { 'data-dam-select': '', value: cfg.jsDecideCandidateScheme || 'balanced', onChange: function (e) { set('jsDecideCandidateScheme', e.target.value) } },
3197
+ h('option', { value: 'balanced' }, locale === 'zh' ? 'balanced 3×40' : 'balanced 3×40'),
3198
+ h('option', { value: 'dense' }, locale === 'zh' ? 'dense 6×20' : 'dense 6×20'),
3199
+ h('option', { value: 'custom' }, locale === 'zh' ? 'custom 自定义' : 'custom')), t('fCandSchemeHint')),
3200
+ (cfg.jsDecideCandidateScheme === 'custom') ? field(t('fCandN'), h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 8, value: cfg.jsDecideCandidatesN === undefined ? 4 : cfg.jsDecideCandidatesN, onChange: function (e) { set('jsDecideCandidatesN', Math.max(1, Math.min(8, Number(e.target.value) || 4))) } }), t('fCandNHint')) : null,
3201
+ field(t('semMode'), h('select', { 'data-dam-select': '', value: cfg.semanticEngineMode || 'auto', onChange: onEngineModeChange },
3202
+ h('option', { value: 'auto' }, t('semAuto')),
3203
+ h('option', { value: 'lexical' }, t('semLexOnly')),
3204
+ h('option', { value: 'js' }, t('semJs')),
3205
+ h('option', { value: 'python' }, t('semPy'))), t('semModeHint')),
3206
+ sem.loaded ? h('div', { 'data-dam-hint': '', style: { marginTop: '-4px', marginBottom: '8px' } },
3207
+ t('semResolved') + ': ' + (sem.resolvedTier === 'c2' ? t('tierC2') + ' ✓' : sem.resolvedTier === 'c3' ? t('tierC3') + ' ✓' : t('tierC1')))
3208
+ : null,
3209
+ guide === 'js' || guide === 'python' ? (function () {
3210
+ // 安装引导卡(对齐 ui-assets 原型:进度条/下载源/体积/状态)——资产检测由 sem 状态机驱动
3211
+ var isJs = guide === 'js'
3212
+ var title = isJs ? (locale === 'zh' ? '内置语义引擎 · 安装引导' : 'Built-in semantic engine · setup') : (locale === 'zh' ? '高级 Python 引擎 · 安装引导' : 'Advanced Python engine · setup')
3213
+ var desc = isJs
3214
+ ? (locale === 'zh' ? '下载约130MB本地量化模型(multilingual-e5-small),校验后离线运行——记忆不出电脑。下载期间词法检索照常可用,完成后自动启用。' : 'Downloads a ~130MB local quantized model (multilingual-e5-small), verifies and runs fully offline — memories never leave this machine. Lexical search keeps working during setup; the engine switches on automatically when ready.')
3215
+ : (locale === 'zh' ? '高级引擎通过本地 Python sidecar 运行 BGE-M3 int8(约563MB),召回质量最高。需要引导式安装(Python 环境 + 模型),适合深度用户;不安装不影响内置引擎。' : 'The advanced engine runs BGE-M3 int8 (~563MB) via a local Python sidecar for maximum recall. Guided install required (Python runtime + model); optional for power users.')
3216
+ var ready = isJs ? sem.ready : sem.pythonInt8Present
3217
+ var bytes = isJs ? (sem.assetBytes || 0) : (sem.pythonInt8Bytes || 0)
3218
+ var dl = (isJs && sem.download) ? sem.download : { phase: 'idle' }
3219
+ var dlActive = dl.phase === 'downloading' || dl.phase === 'verifying'
3220
+ // 规范 G 七态之「建库中」:SHA256 过了但引擎还在后台编码全量语料(jsSemantic.embedding)
3221
+ var building = isJs && sem.jsSemantic && sem.jsSemantic.embedding === true
3222
+ var totalJs = sem.manifestBytes || Math.round(130 * 1024 * 1024)
3223
+ var stateTxt, stateBg
3224
+ if (!sem.loaded) { stateTxt = locale === 'zh' ? '检测中…' : 'Detecting…'; stateBg = 'rgba(128,128,128,.16)' }
3225
+ else if (ready && building) { stateTxt = locale === 'zh' ? '已就绪 · 建库中…' : 'Ready · building index…'; stateBg = 'rgba(36,86,196,.2)' }
3226
+ else if (ready) { stateTxt = locale === 'zh' ? '已就绪 ✓' : 'Ready ✓'; stateBg = 'rgba(47,164,106,.24)' }
3227
+ else if (dl.phase === 'error') { stateTxt = t('dlError'); stateBg = 'rgba(196,74,74,.22)' }
3228
+ else if (dl.phase === 'cancelled') { stateTxt = t('dlCancelled'); stateBg = 'rgba(196,138,42,.2)' }
3229
+ else if (isJs && dlActive) { stateTxt = dl.phase === 'verifying' ? t('dlVerifying') : t('dlDownloading'); stateBg = 'rgba(36,86,196,.2)' }
3230
+ else if (isJs && sem.assetPresent && !sem.peerPresent) { stateTxt = locale === 'zh' ? '模型已存在,缺运行库' : 'Model present, runtime missing'; stateBg = 'rgba(196,138,42,.2)' }
3231
+ else { stateTxt = locale === 'zh' ? '未下载' : 'Not downloaded'; stateBg = 'rgba(196,138,42,.2)' }
3232
+ var progress = dlActive || (isJs && dl.phase === 'done')
3233
+ ? Math.min(100, Math.round(((dl.bytesDone || 0) / Math.max(1, dl.bytesTotal || totalJs)) * 100))
3234
+ : (bytes && !ready ? Math.min(100, Math.round(bytes / ((isJs ? 130 : 563) * 1024 * 1024) * 100)) : (ready ? 100 : 0))
3235
+ var fmtMB = function (b) { return b ? (b / (1024 * 1024)).toFixed(1) + ' MB' : '—' }
3236
+ var mirrorName = function (m) { return m === 'cn' ? t('mCn') : m === 'intl' ? t('mIntl') : t('mAuto') }
3237
+ var phaseLine = dlActive
3238
+ ? ((dl.phase === 'verifying' ? t('dlVerifying') : t('dlDownloading')) + ' · ' + fmtMB(dl.bytesDone || 0) + ' / ' + fmtMB(dl.bytesTotal || totalJs) + ' · ' + mirrorName(dl.mirrorUsed))
3239
+ : (dl.phase === 'error' ? (t('dlError') + ': ' + String(dl.error || '').slice(0, 120))
3240
+ : (dl.phase === 'cancelled' ? t('dlCancelled')
3241
+ : (isJs && dl.phase === 'done' && !ready ? t('dlDone') + (locale === 'zh' ? '(缺运行库时需安装 @huggingface/transformers)' : ' (install @huggingface/transformers if runtime missing)')
3242
+ : (locale === 'zh' ? '下载进度' : 'Download progress'))))
3243
+ var refreshSem = function () {
3244
+ fetch('/api/dsh-auto-memory/semantic-status').then(function (r2) { return r2.json() }).then(function (j2) { setSem(Object.assign({ loaded: true }, j2)) }).catch(function () {})
3245
+ }
3246
+ var startDl = function () {
3247
+ apiPost('/api/dsh-auto-memory/semantic-download', { action: 'start', mirror: mirror }).then(refreshSem).catch(function () {})
3248
+ }
3249
+ var cancelDl = function () {
3250
+ apiPost('/api/dsh-auto-memory/semantic-download', { action: 'cancel', mirror: mirror }).then(refreshSem).catch(function () {})
3251
+ }
3252
+ return h('div', { style: { border: '1px solid color-mix(in srgb, var(--dam-accent, #2456c4) 40%, transparent)', borderRadius: '10px', padding: '10px 12px', marginBottom: '8px', fontSize: 'calc(11.5px * var(--dam-scale))', lineHeight: 1.6 } },
3253
+ h('div', { 'data-dam-row': '', style: { alignItems: 'center' } },
3254
+ h('b', null, title),
3255
+ h('span', { style: { marginLeft: 'auto', fontSize: 'calc(10.5px * var(--dam-scale))', padding: '2px 8px', borderRadius: '6px', background: stateBg, fontWeight: 700 } }, stateTxt)),
3256
+ h('div', { style: { opacity: .85, marginTop: '4px' } }, desc),
3257
+ ready ? null : h('div', { style: { marginTop: '8px' } },
3258
+ h('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: 'calc(10px * var(--dam-scale))', opacity: .7, marginBottom: '3px', gap: '8px' } },
3259
+ h('span', null, phaseLine),
3260
+ h('span', null, progress + '%')),
3261
+ h('div', { style: { height: '7px', borderRadius: '99px', background: 'rgba(128,128,128,.14)', overflow: 'hidden' } },
3262
+ h('div', { style: { height: '100%', width: progress + '%', borderRadius: '99px', background: dl.phase === 'error' ? 'linear-gradient(90deg,#c44a4a,#e08a8a)' : 'linear-gradient(90deg, var(--dam-accent, #2456c4), #6f9bff)', transition: 'width .4s ease' } })),
3263
+ h('div', { style: { display: 'flex', justifyContent: 'space-between', fontSize: 'calc(10px * var(--dam-scale))', opacity: .6, marginTop: '3px' } },
3264
+ h('span', null, locale === 'zh' ? '体积' : 'Size', ': ', isJs ? fmtMB(totalJs) + '(5 个文件,SHA256 校验后离线运行)' : '~563MB'),
3265
+ h('span', null, locale === 'zh' ? '下载源' : 'Source', ': ', isJs ? mirrorName(mirror) + (locale === 'zh' ? ' · 失败自动切备用源' : ' · auto-failover') : (locale === 'zh' ? 'GitHub Releases 多通道' : 'GitHub Releases multi-mirror')))),
3266
+ h('div', { style: { display: 'flex', justifyContent: 'flex-end', gap: '6px', alignItems: 'center', marginTop: '8px' } },
3267
+ !ready && isJs && !dlActive ? h('select', { 'data-dam-select': '', value: mirror, onChange: function (e) { setMirror(e.target.value) }, style: { marginRight: 'auto' } },
3268
+ h('option', { value: 'auto' }, t('mAuto')),
3269
+ h('option', { value: 'cn' }, t('mCn')),
3270
+ h('option', { value: 'intl' }, t('mIntl'))) : null,
3271
+ ready ? h('button', { 'data-dam-btn': '', onClick: function () { setGuide(''); var n2 = Object.assign({}, cfg); n2.semanticEngineMode = guide; if (guide === 'js') { n2.activationSource = 'js'; n2.contextSinkMode = 'null' } else if (guide === 'python') { n2.activationSource = 'python'; n2.contextSinkMode = 'python' } setCfg(n2); setDirty(true) } }, locale === 'zh' ? '启用并继续' : 'Enable & continue') : null,
3272
+ !ready && isJs && !dlActive ? h('button', { 'data-dam-btn': '', onClick: startDl }, (dl.phase === 'error' || dl.phase === 'cancelled') ? t('semDlRetry') : t('semDlStart')) : null,
3273
+ !ready && isJs && dlActive ? h('button', { 'data-dam-btn': '', onClick: cancelDl }, t('semDlCancel')) : null,
3274
+ h('button', { 'data-dam-btn': '', onClick: function () { setGuide('') } }, t('gotIt'))))
3275
+ })()
3276
+ : null,
3277
+ field(t('fReasoning'), h('input', { type: 'checkbox', checked: !!cfg.reasoningObserverEnabled, onChange: function (e) { set('reasoningObserverEnabled', e.target.checked) } }), t('fReasoningHint')),
3278
+ field(t('fChildObs'), h('input', { type: 'checkbox', checked: !!cfg.contextBridgeObserveChildSessions, onChange: function (e) { set('contextBridgeObserveChildSessions', e.target.checked) } }), t('fChildObsHint')),
3279
+ field(locale === 'zh' ? '唤起阈值(校准策略)' : 'Activation thresholds (calibrated)', h('div', null,
3280
+ h('span', null, 'tauHi 0.45 · tauLo 0.35 · deltaExp 0.03 · deltaPro 0.05' + (sem.loaded ? ((locale === 'zh' ? ' · 发射模式:' : ' · emit: ') + (sem.activationEmitMode || 'shadow')) : ''))),
3281
+ t('fTuningHint')),
3282
+ ]),
3283
+ section('memoryHub', sectionLabels.memoryHub, [
3284
+ h('div', { 'data-dam-hint': '' }, t('secMemoryHubHint')),
3285
+ field(t('fMemoryHub'), h('input', { type: 'checkbox', checked: !!cfg.memoryHubEnabled, onChange: function (e) { set('memoryHubEnabled', e.target.checked) } }), t('fMemoryHubHint')),
3286
+ field(t('fEpisodicMin'), h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 16, value: cfg.episodicMinSegments === undefined ? 2 : cfg.episodicMinSegments, onChange: function (e) { set('episodicMinSegments', Math.max(1, Math.min(16, Number(e.target.value) || 2))) } }), t('fEpisodicMinHint')),
3287
+ field(t('fEpisodicRet'), h('input', { 'data-dam-input': '', type: 'number', min: 16, max: 4096, value: cfg.episodicRetention === undefined ? 256 : cfg.episodicRetention, onChange: function (e) { set('episodicRetention', Math.max(16, Math.min(4096, Number(e.target.value) || 256))) } }), t('fEpisodicRetHint')),
3288
+ field(t('fProcSessions'), h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 20, value: cfg.procedureMinSessions === undefined ? 3 : cfg.procedureMinSessions, onChange: function (e) { set('procedureMinSessions', Math.max(1, Math.min(20, Number(e.target.value) || 3))) } }), t('fProcSessionsHint')),
3289
+ field(t('fProcSuccess'), h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 20, value: cfg.procedureMinSuccess === undefined ? 2 : cfg.procedureMinSuccess, onChange: function (e) { set('procedureMinSuccess', Math.max(1, Math.min(20, Number(e.target.value) || 2))) } }), t('fProcSuccessHint')),
3290
+ field(t('fProcCorr'), h('input', { 'data-dam-input': '', type: 'number', min: 0, max: 1, step: 0.05, value: cfg.procedureCorrectionCap === undefined ? 0.3 : cfg.procedureCorrectionCap, onChange: function (e) { set('procedureCorrectionCap', Math.max(0, Math.min(1, Number(e.target.value) || 0.3))) } }), t('fProcCorrHint')),
3291
+ field(t('fProcRisk'), h('input', { type: 'checkbox', checked: cfg.procedureHighRiskApproval !== false, onChange: function (e) { set('procedureHighRiskApproval', e.target.checked) } }), t('fProcRiskHint')),
3292
+ field(t('fProcLevel'), h('select', { 'data-dam-select': '', value: cfg.procedureActiveLevel || 'checklist', onChange: function (e) { set('procedureActiveLevel', e.target.value) } },
3293
+ h('option', { value: 'checklist' }, locale === 'zh' ? 'checklist 完整步骤' : 'checklist (full steps)'),
3294
+ h('option', { value: 'excerpt' }, locale === 'zh' ? 'excerpt 摘要' : 'excerpt (summary)'),
3295
+ h('option', { value: 'hint' }, locale === 'zh' ? 'hint 仅提示' : 'hint (hint only)')), t('fProcLevelHint')),
3296
+ h('div', { 'data-dam-hint': '', style: { marginTop: '4px' } }, t('memoryHubViewHint')),
3297
+ ]),
2025
3298
  section('appearance', sectionLabels.appearance, [
2026
3299
  h('div', { 'data-dam-hint': '' }, t('settingsHeader')),
3300
+ // 欢迎向导:开关(首启自动播放)+ 立即重看按钮(闭包内直调 openDialog——同一作用域,点击立即弹;
3301
+ // 不走 window 全局入口,避免多实例时序导致"点了没反应要刷新")+ 查看更新日志(走 update 弹窗,
3302
+ // 带 Logo 开场动画;versions 取 CHANGELOG 最新一条)
3303
+ field(t('fWelcomeTour'), h('div', { 'data-dam-row': '' },
3304
+ h('input', { type: 'checkbox', checked: cfg.welcomeTourEnabled !== false, onChange: function (e) { set('welcomeTourEnabled', e.target.checked) } }),
3305
+ h('button', { 'data-dam-btn': '', onClick: function () { try { openDialog({ kind: 'welcomeTour' }) } catch (eTour) {} } }, t('tourReplay')),
3306
+ h('button', { 'data-dam-btn': '', onClick: function () {
3307
+ try {
3308
+ var keys = Object.keys(CHANGELOG)
3309
+ if (!keys.length) return
3310
+ var latest = keys.sort(cmpVersion)[keys.length - 1]
3311
+ openDialog({ kind: 'update', versions: [{ version: latest, items: CHANGELOG[latest] }], currentVersion: latest })
3312
+ } catch (eLog) {}
3313
+ } }, locale === 'zh' ? '查看更新日志' : 'View changelog')), t('fWelcomeTourHint')),
2027
3314
  field(t('fLocale'), h('select', { 'data-dam-select': '', value: cfg.locale || 'system', onChange: function (e) { set('locale', e.target.value) } },
2028
3315
  LOCALE_IDS_LIST.map(function (id) { return h('option', { key: id, value: id }, id === 'system' ? t('followSystem') : t(id)) })), t('fLocaleHint')),
2029
3316
  field(t('fFontSize'), h('select', { 'data-dam-select': '', value: fontScale, onChange: function (e) { fontScale = e.target.value; try { localStorage.setItem('dsh-auto-memory.fontScale.v2', fontScale) } catch (ee) {}; try { var pp = document.querySelector('[data-dam-panel]'); if (pp) pp.style.setProperty('--dam-scale', FONT_SCALE_VALUES[fontScale] || '1') } catch (ee2) {}; emit() } },
@@ -2053,17 +3340,39 @@ window.__ModuleLoader__.load({
2053
3340
  section('injection', sectionLabels.injection, [field(t('fInject'), h('input', { type: 'checkbox', checked: !!cfg.injectEnabled, onChange: function (e) { set('injectEnabled', e.target.checked) } }), t('fInjectHint')),
2054
3341
  field(t('fBudget'), h('input', { 'data-dam-input': '', type: 'number', min: 400, value: cfg.injectBudgetChars, onChange: function (e) { set('injectBudgetChars', Number(e.target.value) || 2400) } }), t('fBudgetHint')),
2055
3342
  field(t('fDays'), h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 14, value: cfg.recentDaysInjected, onChange: function (e) { set('recentDaysInjected', Number(e.target.value) || 1) } }), t('fDaysHint')),
2056
- field(t('fExtBudget'), h('input', { 'data-dam-input': '', type: 'number', min: 200, value: cfg.externalInjectionChars === undefined ? 1400 : cfg.externalInjectionChars, onChange: function (e) { set('externalInjectionChars', Number(e.target.value) || 1400) } }), t('fExtBudgetHint'))]),
3343
+ field(t('fExtBudget'), h('input', { 'data-dam-input': '', type: 'number', min: 200, value: cfg.externalInjectionChars === undefined ? 1400 : cfg.externalInjectionChars, onChange: function (e) { set('externalInjectionChars', Number(e.target.value) || 1400) } }), t('fExtBudgetHint')),
3344
+ field(t('fSnapGap'), h('input', { 'data-dam-input': '', type: 'number', min: 0, max: 50, value: cfg.snapshotMinGapRounds === undefined ? 5 : cfg.snapshotMinGapRounds, onChange: function (e) { set('snapshotMinGapRounds', Number(e.target.value) || 5) } }), t('fSnapGapHint')),
3345
+ field(t('fReinjectOnCompact'), h('input', { type: 'checkbox', checked: cfg.snapshotReinjectOnCompact !== false, onChange: function (e) { set('snapshotReinjectOnCompact', e.target.checked) } }), t('fReinjectOnCompactHint')),
3346
+ field(t('fPromptCustom'), h('button', { 'data-dam-btn': '', onClick: function () { setPromptEditOpen(!promptEditOpen) } }, (promptEditOpen ? (locale === 'zh' ? '收起' : 'Collapse') : (locale === 'zh' ? '编辑 prompt 层' : 'Edit prompt layers'))), t('fPromptCustomHint')),
3347
+ promptEditOpen ? [
3348
+ h('div', { key: '__layers', style: { padding: '6px 0 2px', width: '100%' } },
3349
+ (Object.keys(DEFAULT_PROMPT_LAYERS_CLIENT)).map(function (k) {
3350
+ return h('div', { key: k, style: { marginBottom: '6px' } },
3351
+ h('div', { 'data-dam-hint': '', style: { fontWeight: 700, marginBottom: '2px' } }, k),
3352
+ h('textarea', { 'data-dam-input': '', rows: 2, style: { width: '100%', fontFamily: 'monospace', fontSize: 'calc(11px * var(--dam-scale))' }, value: (cfg.promptLayerOverrides || {})[k] || '', placeholder: DEFAULT_PROMPT_LAYERS_CLIENT[k] || '(默认文案)', onChange: function (e) { var ov = Object.assign({}, cfg.promptLayerOverrides || {}); if (e.target.value.trim() === '') delete ov[k]; else ov[k] = e.target.value; set('promptLayerOverrides', ov) } }))
3353
+ })),
3354
+ h('div', { key: '__reset', 'data-dam-row': '', style: { marginTop: '6px' } },
3355
+ h('button', { 'data-dam-btn': '', onClick: function () { set('promptLayerOverrides', {}) } }, locale === 'zh' ? '一键恢复默认' : 'Reset to defaults'))
3356
+ ] : null]),
2057
3357
  section('automation', sectionLabels.automation, [field(t('fConsolidateMin'), h('input', { 'data-dam-input': '', type: 'number', min: 80, value: cfg.autoConsolidateMinChars === undefined ? 240 : cfg.autoConsolidateMinChars, onChange: function (e) { set('autoConsolidateMinChars', Number(e.target.value) || 240) } }), t('fConsolidateMinHint')),
2058
3358
  field(t('fAutoConsolidate'), h('input', { type: 'checkbox', checked: cfg.autoConsolidate !== false, onChange: function (e) { set('autoConsolidate', e.target.checked) } }), t('fAutoConsolidateHint')),
2059
3359
  field(t('fConsolidate'), h('input', { 'data-dam-input': '', type: 'number', min: 5, value: cfg.autoConsolidateCooldownMinutes === undefined ? 30 : cfg.autoConsolidateCooldownMinutes, onChange: function (e) { set('autoConsolidateCooldownMinutes', Number(e.target.value) || 30) } }), t('fConsolidateHint')),
2060
3360
  field(t('fConsolidateMax'), h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 50, value: cfg.autoConsolidateDailyMax === undefined ? 8 : cfg.autoConsolidateDailyMax, onChange: function (e) { set('autoConsolidateDailyMax', Number(e.target.value) || 8) } }), t('fConsolidateMaxHint')),
3361
+ field(t('fAutoPopup'), h('input', { type: 'checkbox', checked: cfg.autoPopupEnabled !== false, onChange: function (e) { set('autoPopupEnabled', e.target.checked) } }), t('fAutoPopupHint')),
3362
+ field(t('fUnattended'), h('input', { type: 'checkbox', checked: !!cfg.unattendedMode, onChange: function (e) { set('unattendedMode', e.target.checked) } }), t('fUnattendedHint')),
3363
+ field(t('fUnattendedAuto'), h('input', { type: 'checkbox', checked: !!cfg.unattendedAuto, onChange: function (e) { set('unattendedAuto', e.target.checked) } }), t('fUnattendedAutoHint')),
2061
3364
  field(t('fAway'), h('input', { 'data-dam-input': '', type: 'number', min: 1, value: cfg.awayMinutes || 60, onChange: function (e) { set('awayMinutes', Number(e.target.value) || 60) } }), t('fAwayHint')),
2062
3365
  field(t('fAutoSum'), h('input', { 'data-dam-input': '', value: (cfg.autoSummaryTimes || []).join(','), onChange: function (e) { set('autoSummaryTimes', String(e.target.value || '').split(',').map(function (s) { return s.trim() }).filter(Boolean)) } }), t('fAutoSumHint')),
2063
3366
  field(t('fDayBoundary'), h('input', { 'data-dam-input': '', type: 'number', min: 0, max: 1439, value: (cfg.dayBoundaryMinutes === undefined ? 450 : cfg.dayBoundaryMinutes), onChange: function (e) { set('dayBoundaryMinutes', parseInt(e.target.value || '0', 10)) } }), t('fDayBoundaryHint')),
2064
3367
  field(t('fReflect'), h('input', { type: 'checkbox', checked: !!cfg.reflectEnabled, onChange: function (e) { set('reflectEnabled', e.target.checked) } }), t('fReflectHint')),
2065
3368
  field(t('fStyle'), h('select', { 'data-dam-select': '', value: cfg.reflectStyle, onChange: function (e) { set('reflectStyle', e.target.value) } },
2066
- STYLE_IDS.map(function (id) { return h('option', { key: id, value: id }, t('style' + id.charAt(0).toUpperCase() + id.slice(1))) })), t('fStyleHint'))]),
3369
+ STYLE_IDS.map(function (id) { return h('option', { key: id, value: id }, t('style' + id.charAt(0).toUpperCase() + id.slice(1))) })), t('fStyleHint')),
3370
+ field(locale === 'zh' ? '总结/问候默认模型' : 'Summary & greeting model', h('div', { 'data-dam-row': '', style: { flex: 1 } },
3371
+ h('span', { style: { flex: 1, fontSize: 'calc(12px * var(--dam-scale))', wordBreak: 'break-all', opacity: cfg.subagentModel ? 1 : 0.6 } }, cfg.subagentModel || (locale === 'zh' ? '跟随路由默认' : 'routing default')),
3372
+ h('button', { 'data-dam-btn': '', onClick: openModels }, locale === 'zh' ? '选择模型' : 'Pick model')),
3373
+ locale === 'zh' ? '用于时段总结、问候语、自动沉淀等 subagent 功能;从检测到的模型中选择,或留空跟随路由默认。保存后生效。' : 'For scheduled summaries, greetings and auto-consolidation subagents; pick a detected model or leave empty for the routing default. Applies after saving.'),
3374
+ mdlOpen ? buildModelDrawer() : null,
3375
+ ]),
2067
3376
  section('maintenance', sectionLabels.maintenance, [field(t('fVersion'), h('div', { 'data-dam-row': '' },
2068
3377
  h('span', { style: { flex: 1 } }, verInfo ? (verInfo.current || '?') + (verInfo.latest ? ' → ' + verInfo.latest + (verInfo.upToDate ? ' ' + t('upToDate') : ' ' + t('hasUpdate')) : '') : (checkingUpdate ? t('checking') : '—')),
2069
3378
  h('button', { 'data-dam-btn': '', onClick: checkUpdate, disabled: checkingUpdate }, checkingUpdate ? t('checking') : t('checkUpdate')),
@@ -2092,6 +3401,29 @@ window.__ModuleLoader__.load({
2092
3401
  }
2093
3402
  }, 'dsh-auto-memory: styles')
2094
3403
 
3404
+ // 面板外交互关闭(@ProperSAMA PR#12):点击面板外任意处 / 按 Esc 关闭。
3405
+ // 增强模式下面板可能盖住侧边栏入口按钮,这里提供不依赖按钮的兜底关闭手段;
3406
+ // 点击入口按钮本身排除在外(保留按钮 toggle 语义)。
3407
+ try {
3408
+ var onDocPointerDown = function (e) {
3409
+ if (!panelOpen || panelClosing) return
3410
+ var el = e.target
3411
+ if (el && el.closest && (el.closest('[data-dam-panel]') || el.closest('[data-dam-sidebar-btn]'))) return
3412
+ controller.close()
3413
+ }
3414
+ var onDocKeyDown = function (e) {
3415
+ if (e.key === 'Escape' && panelOpen && !panelClosing) controller.close()
3416
+ }
3417
+ document.addEventListener('pointerdown', onDocPointerDown, true)
3418
+ document.addEventListener('keydown', onDocKeyDown, true)
3419
+ ctx.effect(function () {
3420
+ return function () {
3421
+ document.removeEventListener('pointerdown', onDocPointerDown, true)
3422
+ document.removeEventListener('keydown', onDocKeyDown, true)
3423
+ }
3424
+ }, 'dsh-auto-memory: panel-outside-close')
3425
+ } catch (e) {}
3426
+
2095
3427
  var slots = ctx.slots
2096
3428
  if (!slots) { console.warn('[dsh-auto-memory] slots service unavailable'); return }
2097
3429
  sessions = ctx.sessions
@@ -2109,6 +3441,7 @@ window.__ModuleLoader__.load({
2109
3441
  applyLocalePref('system')
2110
3442
  apiGet(API.config).then(function (d) {
2111
3443
  if (d && d.config && d.config.locale) applyLocalePref(d.config.locale)
3444
+ if (d && d.config && typeof d.config.autoPopupEnabled === 'boolean') autoPopupEnabled = d.config.autoPopupEnabled
2112
3445
  }).catch(function () {})
2113
3446
  // DSH 系统语言变化:跟随更新 + 重渲染 + 重注册入口 label
2114
3447
  ctx.on('locale/change', function (snap) {
@@ -2127,6 +3460,7 @@ window.__ModuleLoader__.load({
2127
3460
  var prevAwayState = null
2128
3461
  function autoOpenOnReturn() {
2129
3462
  try {
3463
+ if (autoPopupEnabled === false) { prevAwayState = hostAwayReady ? hostAway : prevAwayState; return }
2130
3464
  if (hostAwayReady && prevAwayState === true && hostAway === false) {
2131
3465
  // 暂离回归:打开窗口 + 欢迎弹窗(若有待展示总结一并显示)
2132
3466
  if (!controller.isOpen()) controller.open()
@@ -2147,10 +3481,20 @@ window.__ModuleLoader__.load({
2147
3481
  })
2148
3482
  } catch (e) {}
2149
3483
  // 更新弹窗 / 首次指导:对比本地记录的已见版本,有更新或首次安装时弹窗(host 12h 缓存,不重复查网)
3484
+ // v0.1.30 大更新触达:一次性标记 majorTour——所有用户(新老)升级后首次打开都先播放完整欢迎向导,
3485
+ // 向导结束后接 CHANGELOG(含大更新内容+社区致谢);此后永不再弹(标记落 localStorage)。
3486
+ var MAJOR_TOUR_KEY = 'dsh-auto-memory.majorTourV130'
2150
3487
  apiGet(API.updateCheck).then(function (d) {
2151
3488
  if (d && d.current) {
2152
3489
  try {
2153
3490
  var seen = localStorage.getItem('dsh-auto-memory.seenVersion')
3491
+ var majorPending = !localStorage.getItem(MAJOR_TOUR_KEY)
3492
+ if (majorPending && (d.current === '0.1.30' || seen)) {
3493
+ // 大更新触达(老用户:seen<current 或已有使用痕迹;新装用户走 first 流程不重复打扰)
3494
+ openDialog({ kind: 'welcomeTour' })
3495
+ try { localStorage.setItem(MAJOR_TOUR_KEY, '1') } catch (eM) {}
3496
+ return
3497
+ }
2154
3498
  if (!seen && !localStorage.getItem('dsh-auto-memory.firstRunDone')) {
2155
3499
  openDialog({ kind: 'first', currentVersion: d.current })
2156
3500
  } else if (seen && seen !== d.current) {
@@ -2193,6 +3537,7 @@ window.__ModuleLoader__.load({
2193
3537
  hostAway = d.away
2194
3538
  hostAwayReady = true
2195
3539
  }
3540
+ if (d && typeof d.autoPopupEnabled === 'boolean') autoPopupEnabled = d.autoPopupEnabled
2196
3541
  if (d && d.pendingSummary && d.pendingSummary.summary) {
2197
3542
  var seenKey = 'dsh-auto-memory.seenSummary.' + (d.pendingSummary.date || '') + '.' + (d.pendingSummary.time || '')
2198
3543
  try {
@@ -2213,7 +3558,7 @@ window.__ModuleLoader__.load({
2213
3558
  function registerSurfaces() {
2214
3559
  try {
2215
3560
  surfaceDisposers.push(slots.inject('sidebar.footer.action', function () {
2216
- return slots.register({ name: 'sidebar.footer.action', id: 'auto-memory', order: 5, label: t('memory') + '' }, function () { return h(SidebarButton) })
3561
+ return slots.register({ name: 'sidebar.footer.action', id: 'auto-memory', order: 5, label: t('memory') + ' (pre)' }, function () { return h(SidebarButton) })
2217
3562
  }))
2218
3563
  surfaceDisposers.push(slots.inject('shell.overlay', function () {
2219
3564
  return slots.register({ name: 'shell.overlay', id: 'auto-memory', order: 5 }, function () { return h(MemoryPanel) })
@@ -2222,7 +3567,7 @@ window.__ModuleLoader__.load({
2222
3567
  return slots.register({ name: 'shell.overlay', id: 'auto-memory-dialogs', order: 6 }, function () { return h(DialogHost) })
2223
3568
  }))
2224
3569
  surfaceDisposers.push(slots.inject('settings.section', function () {
2225
- return slots.register({ name: 'settings.section', id: 'auto-memory', order: 25, label: t('autoMemory') + '' }, function (props) { return h(SettingsPage, { close: props && props.close }) })
3570
+ return slots.register({ name: 'settings.section', id: 'auto-memory', order: 25, label: t('autoMemory') + ' (pre)' }, function (props) { return h(SettingsPage, { close: props && props.close }) })
2226
3571
  }))
2227
3572
  } catch (e) {
2228
3573
  console.warn('[dsh-auto-memory] slot registration failed', e)