@a9i5k4/dsh-auto-memory 3.1.0 → 3.1.1
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/docs/HANDBOOK.md +13 -9
- package/lib/client.js +695 -121
- package/lib/config-io.js +59 -6
- package/lib/context-host.js +10 -1
- package/lib/evidence-store.js +27 -1
- package/lib/index.js +680 -42
- package/lib/jsonl-tail-cursor.js +75 -0
- package/lib/m7-index-sync-host.js +4 -6
- package/lib/memory-hub.js +25 -2
- package/lib/migrate-pack.js +351 -0
- package/lib/python-setup.js +109 -27
- package/lib/recall-stats.js +242 -0
- package/lib/rules-layer.js +106 -15
- package/lib/semantic-js.js +25 -7
- package/lib/shadow-host.js +41 -2
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 3. settings.section — 自动记忆设置页(存储位置、注入、反思风格)
|
|
8
8
|
* Data flows over /api/dsh-auto-memory/* (loopback-only host routes).
|
|
9
9
|
*/
|
|
10
|
-
console.log('[dsh-auto-memory] client v3.1.
|
|
10
|
+
console.log('[dsh-auto-memory] client v3.1.1 fingerprint: migrate-pack-quiet-logs')
|
|
11
11
|
window.__ModuleLoader__.load({
|
|
12
12
|
id: '@a9i5k4/dsh-auto-memory',
|
|
13
13
|
factory: (require) => {
|
|
@@ -205,10 +205,41 @@ window.__ModuleLoader__.load({
|
|
|
205
205
|
memoryPanel: '记忆面板',
|
|
206
206
|
memory: '记忆',
|
|
207
207
|
autoMemory: '自动记忆',
|
|
208
|
-
overview: '概览',
|
|
208
|
+
overview: '概览',
|
|
209
|
+
statsTab: '统计',
|
|
210
|
+
statsTitle: '召回统计(谁在被想起来)',
|
|
211
|
+
statsSubtitle: '只统计、不改排序——数据攒够后再决定要不要加权。',
|
|
212
|
+
statsQueries: '检索次数',
|
|
213
|
+
statsZeroHit: '零命中',
|
|
214
|
+
statsDistinct: '被召回条目',
|
|
215
|
+
statsTotalHits: '命中总次数',
|
|
216
|
+
statsWarm: '只被召回一次',
|
|
217
|
+
statsByLayer: '按记忆层分布',
|
|
218
|
+
statsByDay: '按天走势',
|
|
219
|
+
statsSources: '按通路分布',
|
|
220
|
+
statsTop: '召回最多的条目',
|
|
221
|
+
statsEmpty: '(还没有统计数据——用 memory_recall 检索几次后再回来看)',
|
|
222
|
+
statsReset: '清零统计',
|
|
223
|
+
statsResetDone: '已清零',
|
|
224
|
+
statsChannels: '三条通路(分开统计,含义不同)',
|
|
225
|
+
statsChOverview: '各通路占多少',
|
|
226
|
+
statsChModel: '① 模型主动检索',
|
|
227
|
+
statsChModelHint: '模型明确想找 → 高信号:哪些内容真的被需要',
|
|
228
|
+
statsChInject: '② 每轮自动注入',
|
|
229
|
+
statsChInjectHint: '系统一直在塞 → 成本视角:注入预算花在哪一段',
|
|
230
|
+
statsChShadow: '③ 主动唤起',
|
|
231
|
+
statsChShadowHint: '系统判断该唤起 → 质量视角:唤起了但没命中的比例',
|
|
232
|
+
statsEvents: '发生次数',
|
|
233
|
+
statsNoInject: '还没有注入记录(宿主重启后开始累积)',
|
|
234
|
+
statsSegChars: '按字符数(谁最占预算)',
|
|
235
|
+
statsSegCount: '按出现次数',
|
|
236
|
+
statsSince: '开始记录于',
|
|
237
|
+
statsNever: '从未', logs: '日志', refineTab: '唤起回顾', notes: '笔记', reflections: '反思', connect: '接续', calendar: '日历', search: '检索', workspaces: '工作区', hubTab: '记忆中枢', storageTab: '存储管理', planTab: '白板', planTitle: '白板 · PLAN.md(项目全貌)', planEmpty: '(白板尚未建立——宿主会在该工作区第一次会话结束时自动落一版骨架,模型理解项目全貌后把它重写成真内容;也可在对话中让它用 memory_note(kind=plan) 写)', planVersions: '白板历史版本', planLedgers: '交接账本时间线', planDisabled: '交接白板未启用(设置 → 自动化)。', planWsUnbound: '当前会话未绑定工作区——白板与交接账本暂不可用(会话的工作区身份解析不出来;把会话开进项目文件夹或绑定工作区后自动恢复)。', planFileTitle: '查看',
|
|
209
238
|
hubSkills: '技能 (Procedural)', hubSkillsEmpty: '暂无已固化的技能。反复成功的流程会自动固化为技能并在相似场景召回。', hubFacts: '事实 (Semantic)', hubFactsEmpty: '暂无固化的事实。', hubConflicts: '待决冲突', hubEpisodic: '经历 (Episodic)', hubEpisodicEmpty: '暂无已巩固的经历。',
|
|
210
239
|
storageScanHint: '语料健康 = 逐源比对索引(sidecar)与正文的 digest。手动改动记忆文件后索引会失配,该记忆会退出检索直到重建索引。',
|
|
211
240
|
storageDeleteHint: '删除记忆 = 正文原子删除 + 在途唤起包清理 + 派生事实撤销(三联动)。已产生的 seen 证据不改写。',
|
|
241
|
+
migTitle: '迁移搬包', migHint: '把某个工作区的记忆打包带走,到另一台机器导入即可(路径变了会自动重写内部引用)。', migExport: '导出本工作区', migImport: '导入包', migPickPack: '选择 .dam-pack 文件', migPreview: '预览差异(不改任何文件)', migApply: '确认导入',
|
|
242
|
+
migPathChanged: '路径变化:将重写内部引用', migSamePath: '路径相同:不做任何重写', migConflictKeep: '冲突时保留本机', migConflictOverwrite: '冲突时用包内版本覆盖', migConflictRename: '冲突时改名为 .from-pack', migBackup: '已备份到', migWritten: '已写入文件数',
|
|
212
243
|
secSemantic: '自动记忆引擎', semMode: '检索模式', semAuto: '自动(推荐)', semLexOnly: '仅词法', semJs: '内置语义', semPy: '高级 Python',
|
|
213
244
|
pyWizTitle: 'Python 引擎一键安装', pyWizStep1: '① 检测 Python 环境', pyWizStep2: '② 创建独立虚拟环境(~/.dsh/python-engine/.venv,不污染系统)', pyWizStep3: '③ 安装引擎依赖(transformers + onnxruntime + torch)', pyWizStep4: '④ 下载 BGE-M3 int8 模型(~539MB,断点续传,失败自动换源)',
|
|
214
245
|
pyWizDetect: '开始检测', pyWizRedetect: '重新检测', pyWizCreate: '创建虚拟环境', pyWizInstall: '安装依赖', pyWizDownload: '开始下载', pyWizCancel: '取消下载', pyWizDone: '全部就绪 ✓ — 回上方启用即可', pyWizOk: 'ok', pyWizMissing: '未找到', pyWizTooOld: '版本不兼容(需 3.9-3.12)', pyWizVenvRec: '推荐', pyWizEta: '剩余', pyWizSec: '秒', pyWizModelHint: '模型与虚拟环境安装在 ~/.dsh/python-engine/(用户目录),升级/重装插件不受影响。',
|
|
@@ -258,7 +289,10 @@ window.__ModuleLoader__.load({
|
|
|
258
289
|
kvMem: '记忆文件', kvMemUser: '用户', kvMemNotes: '笔记', kvMemLog: '日志', kvMemMissing: '缺失',
|
|
259
290
|
kvWs: '当前工作区', kvWsUnknown: '(未取到)', kvApi: 'API 连通', unknown: '(未知)',
|
|
260
291
|
// ★P0-2/P0-3(2026-09-22):把后端早已算好、前端零消费者的两组可观测性接到界面上。
|
|
261
|
-
dbgHubIo: '三层记忆落盘', dbgPrune: '事实淘汰(保留上限)',
|
|
292
|
+
dbgHubIo: '三层记忆落盘', dbgPrune: '事实淘汰(保留上限)',
|
|
293
|
+
dbgLogs: '诊断日志',
|
|
294
|
+
dbgLogsNone: '尚未生成(本次运行还没有诊断输出)',
|
|
295
|
+
dbgLogsHint: '崩溃/异常时请把下面路径的文件附给维护者(只读展示,不含日志正文)',
|
|
262
296
|
kvPyTitle: 'Python 语义侧车', kvPyOff: '未启用(JS 端语义不受影响)', kvPyAlive: '运行中',
|
|
263
297
|
kvPyWatchdog: '看门狗击杀 / 重生', kvPyStderr: '最近 stderr 尾部', kvPyStderrTrunc: '(已截断)',
|
|
264
298
|
updateTitle: 'dsh-auto-memory 已更新', updateSub: '本次更新内容:', gotIt: '知道了', noticeOpen: '了解更多',
|
|
@@ -355,6 +389,7 @@ window.__ModuleLoader__.load({
|
|
|
355
389
|
fSnapGap: '快照最小间隔(轮)', fSnapGapHint: '动态记忆快照内容变化后,至少隔 N 轮才重新注入,避免每轮日志微变都追加快照导致历史膨胀。默认 5;0=每轮都尝试(仍受内容变化约束)。',
|
|
356
390
|
fReinjectOnCompact: '压缩后立即重注入快照', fReinjectOnCompactHint: '上下文被压缩/截断(通常伴随 contextVersion 重置)后,快照会被清掉;开启后强制立即重注入一次,确保记忆背景重建。默认开。',
|
|
357
391
|
fPromptCustom: '自定义记忆注入 prompt', fPromptCustomHint: '可覆盖各层提示文案(小众功能)。支持占位符 {date} {ws} {budget} {n}。改坏了可一键恢复默认。',
|
|
392
|
+
fPromptSections: '高级:逐段注入控制…', fPromptSectionsHint: '控制每一轮注入里包含哪些段落。默认全部开启;不推荐修改 —— 关掉某段会让模型少掉对应的上下文或纪律。',
|
|
358
393
|
fDays: '注入最近日志天数', fDaysHint: '会话开始时注入最近 N 天的工作日志尾部。默认 1。',
|
|
359
394
|
fExtBudget: '外部记忆注入预算(字符)', fExtBudgetHint: '外部记忆来源在上下文中的注入预算。默认 1400(路径模式下影响有限)。',
|
|
360
395
|
fConsolidateMin: '自动沉淀内容门槛(字符)', fConsolidateMinHint: '本轮 user+assistant 总字符低于此值视为寒暄跳过。默认 240。',
|
|
@@ -412,10 +447,41 @@ window.__ModuleLoader__.load({
|
|
|
412
447
|
memoryPanel: 'Memory Panel',
|
|
413
448
|
memory: 'Memory',
|
|
414
449
|
autoMemory: 'Auto Memory',
|
|
415
|
-
overview: 'Overview',
|
|
450
|
+
overview: 'Overview',
|
|
451
|
+
statsTab: 'Stats',
|
|
452
|
+
statsTitle: 'Recall statistics (what gets remembered)',
|
|
453
|
+
statsSubtitle: 'Recording only — ranking untouched. Weighting comes after the data proves the metric.',
|
|
454
|
+
statsQueries: 'Queries',
|
|
455
|
+
statsZeroHit: 'Zero-hit',
|
|
456
|
+
statsDistinct: 'Distinct recalled',
|
|
457
|
+
statsTotalHits: 'Total hits',
|
|
458
|
+
statsWarm: 'Recalled once',
|
|
459
|
+
statsByLayer: 'By layer',
|
|
460
|
+
statsByDay: 'Per day',
|
|
461
|
+
statsSources: 'By channel',
|
|
462
|
+
statsTop: 'Most recalled',
|
|
463
|
+
statsEmpty: '(no data yet — run memory_recall a few times and come back)',
|
|
464
|
+
statsReset: 'Reset stats',
|
|
465
|
+
statsResetDone: 'Cleared',
|
|
466
|
+
statsChannels: 'Three channels (counted separately — they mean different things)',
|
|
467
|
+
statsChOverview: 'Share per channel',
|
|
468
|
+
statsChModel: '1) Model-initiated recall',
|
|
469
|
+
statsChModelHint: 'The model explicitly looked for it -> high signal: what is actually needed',
|
|
470
|
+
statsChInject: '2) Auto-injected every turn',
|
|
471
|
+
statsChInjectHint: 'The system keeps feeding it -> cost view: which section eats the budget',
|
|
472
|
+
statsChShadow: '3) Proactive shadow recall',
|
|
473
|
+
statsChShadowHint: 'System judged it worth surfacing -> quality view: how often it came up empty',
|
|
474
|
+
statsEvents: 'Events',
|
|
475
|
+
statsNoInject: 'No injection recorded yet (accumulates after host restart)',
|
|
476
|
+
statsSegChars: 'By characters (who eats the budget)',
|
|
477
|
+
statsSegCount: 'By occurrences',
|
|
478
|
+
statsSince: 'Recording since',
|
|
479
|
+
statsNever: 'never', logs: 'Logs', refineTab: 'Recall review', notes: 'Notes', reflections: 'Reflections', connect: 'Connect', calendar: 'Calendar', search: 'Search', workspaces: 'Workspaces', hubTab: 'Memory Hub', storageTab: 'Storage', planTab: 'Whiteboard', planTitle: 'Whiteboard · PLAN.md (project plan)', planEmpty: '(not created yet — the host seeds a skeleton board when a turn first ends in this workspace; the model rewrites it with real content once it grasps the picture, or ask it to write memory_note kind=plan)', planVersions: 'Plan versions', planLedgers: 'Handoff ledger timeline', planDisabled: 'Handoff whiteboard is disabled (Settings → Automation).', planWsUnbound: 'This session has no bound workspace — whiteboard and handoff ledgers are unavailable (the session workspace could not be resolved; open the session inside a project folder or bind a workspace to restore).', planFileTitle: 'View',
|
|
416
480
|
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.',
|
|
417
481
|
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.',
|
|
418
482
|
storageDeleteHint: 'Delete = atomic body removal + in-flight activation purge + derived-fact revocation (cascading). Evidence already recorded (seen) is never rewritten.',
|
|
483
|
+
migTitle: 'Migrate pack', migHint: 'Pack one workspace memory and carry it to another machine; when the path changes, internal references are rewritten automatically.', migExport: 'Export this workspace', migImport: 'Import pack', migPickPack: 'Pick a .dam-pack file', migPreview: 'Preview diff (writes nothing)', migApply: 'Confirm import',
|
|
484
|
+
migPathChanged: 'Path changed: internal references will be rewritten', migSamePath: 'Same path: no rewrite at all', migConflictKeep: 'On conflict: keep this machine', migConflictOverwrite: 'On conflict: overwrite with pack', migConflictRename: 'On conflict: rename to .from-pack', migBackup: 'Backed up to', migWritten: 'Files written',
|
|
419
485
|
secSemantic: 'Semantic engine', semMode: 'Retrieval mode', semAuto: 'Auto (recommended)', semLexOnly: 'Lexical only', semJs: 'Built-in semantic', semPy: 'Advanced Python',
|
|
420
486
|
pyWizTitle: 'One-click Python engine setup', pyWizStep1: '1. Detect Python environment', pyWizStep2: '2. Create isolated venv (~/.dsh/python-engine/.venv, system untouched)', pyWizStep3: '3. Install engine deps (transformers + onnxruntime + torch)', pyWizStep4: '4. Download BGE-M3 int8 model (~539MB, resumable, auto mirror failover)',
|
|
421
487
|
pyWizDetect: 'Detect', pyWizRedetect: 'Re-detect', pyWizCreate: 'Create venv', pyWizInstall: 'Install deps', pyWizDownload: 'Start download', pyWizCancel: 'Cancel download', pyWizDone: 'All ready - enable it above', pyWizOk: 'ok', pyWizMissing: 'not found', pyWizTooOld: 'incompatible (need 3.9-3.12)', pyWizVenvRec: 'recommended', pyWizEta: 'eta', pyWizSec: 's', pyWizModelHint: 'Model and venv live in ~/.dsh/python-engine/ (user dir) - plugin upgrades never touch them.',
|
|
@@ -463,7 +529,10 @@ window.__ModuleLoader__.load({
|
|
|
463
529
|
kvAvail: 'available', kvUnavail: 'unavailable', kvDup: 'Duplicate note headings', kvDupCount: '',
|
|
464
530
|
kvMem: 'Memory files', kvMemUser: 'user', kvMemNotes: 'notes', kvMemLog: 'log', kvMemMissing: 'missing',
|
|
465
531
|
kvWs: 'Current workspace', kvWsUnknown: '(unknown)', kvApi: 'API probes', unknown: '(unknown)',
|
|
466
|
-
dbgHubIo: 'Layer-3 memory persistence', dbgPrune: 'Fact eviction (retention cap)',
|
|
532
|
+
dbgHubIo: 'Layer-3 memory persistence', dbgPrune: 'Fact eviction (retention cap)',
|
|
533
|
+
dbgLogs: 'Diagnostic log',
|
|
534
|
+
dbgLogsNone: 'not created yet (no diagnostic output this run)',
|
|
535
|
+
dbgLogsHint: 'On a crash/exception, attach the file at the path below (read-only; log body not shown)',
|
|
467
536
|
kvPyTitle: 'Python sidecar', kvPyOff: 'disabled (JS-side semantics unaffected)', kvPyAlive: 'running',
|
|
468
537
|
kvPyWatchdog: 'Watchdog kills / respawns', kvPyStderr: 'Last stderr tail', kvPyStderrTrunc: ' (truncated)',
|
|
469
538
|
updateTitle: 'dsh-auto-memory Updated', updateSub: 'What\'s new in this update:', gotIt: 'Got it', noticeOpen: 'Learn more',
|
|
@@ -557,6 +626,7 @@ window.__ModuleLoader__.load({
|
|
|
557
626
|
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).',
|
|
558
627
|
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.',
|
|
559
628
|
fPromptCustom: 'Customize memory-injection prompt', fPromptCustomHint: 'Override any prompt layer (power feature). Placeholders: {date} {ws} {budget} {n}. One-click reset restores defaults.',
|
|
629
|
+
fPromptSections: 'Advanced: per-section injection…', fPromptSectionsHint: 'Controls which sections each round injects. All on by default; not recommended to change — turning one off drops the matching context or discipline.',
|
|
560
630
|
fDays: 'Recent days injected', fDaysHint: 'Inject tails of the last N days of work logs at session start. Default 1.',
|
|
561
631
|
fExtBudget: 'External memory injection budget (chars)', fExtBudgetHint: 'Budget for external memory sources in context. Default 1400 (limited effect with path mode).',
|
|
562
632
|
fConsolidateMin: 'Auto-consolidation content threshold (chars)', fConsolidateMinHint: 'Turns with fewer combined user+assistant chars are treated as chit-chat and skipped. Default 240.',
|
|
@@ -703,7 +773,7 @@ window.__ModuleLoader__.load({
|
|
|
703
773
|
+ '【收尾自检】本轮有实质产出才做;纯只读/闲聊轮跳过。三个方向 + 分类别丢:\n'
|
|
704
774
|
+ '① 写记忆文件 memory_log —— kind 按性质选,**不要一律 fact**:rule=用户约束/约定(会被规则层当硬约束注入)、preference=偏好、fact=事实记录、todo=待办;\n'
|
|
705
775
|
+ '② 更新白板与账本 —— ★**硬映射(满足即必须做,别自行判断"算不算"变化)**:\n · 本轮**改过 lib/ 下任何文件**(含测试/工具) ⇒ 必写 **memory_note(kind=handoff)** 四段式账本;\n · 白板"当前进度"与本轮结束时的**事实不符**(回归数字/已完成项/下一步) ⇒ 必做 **memory_note(kind=plan)** 重写白板;\n · 仅补充一条可复用结论 ⇒ 才用 **memory_note(kind=note, action=append)**。\n ⚠️ **kind=note 不等于白板/账本**——只写 note 却宣称"已更新白板与账本"是**失职**;write 直接写 docs/ 的 md 也**不算**插件记忆。\n'
|
|
706
|
-
+ '③ 长期记忆判断(三个去处)—— 跨会话有用的 → memory_note / 跨项目规则 → memory_user / **跑通且可复用的多步流程 → memory_procedure**(技能库唯一模型入口,不写就没有;建议填 successCriteria);\n'
|
|
776
|
+
+ '③ 长期记忆判断(三个去处)—— 跨会话有用的 → memory_note / 跨项目规则 → memory_user / **规则条目增删改(用户级硬约束,含过时条目真删)→ memory_rules** / **跑通且可复用的多步流程 → memory_procedure**(技能库唯一模型入口,不写就没有;建议填 successCriteria);\n'
|
|
707
777
|
+ '④ 结论失效/被取代 —— 传 `supersedes=` 旧条目 mem_id 标 **superseded(被更新结论取代)**;**当时就做错了要撤回**则传 `retract=` 标 **retracted(撤回)**并尽量附 `retractReason=` 说明错在哪(两者不同:前者有后继,后者本身就是教训);标错了用 `restore=` 撤回;\n'
|
|
708
778
|
+ '⑤ 看板落列 —— 白板/账本内容要进面板看板泳道,须在标题或正文写 tag:`type:goal` / `type:state` / `type:dead-end` / `type:progress`(5 条泳道含「版本归档」,靠归档动作而非 tag);不写 tag 系统只能按标题文字猜,常落空;\n'
|
|
709
779
|
+ '⑥ 语体 —— 客观陈述、第三人称,只留可复用的事实/决策/规则/路径(不写"我考虑/我排查/我想")。',
|
|
@@ -736,6 +806,27 @@ window.__ModuleLoader__.load({
|
|
|
736
806
|
|
|
737
807
|
// ───────────────────────── 更新弹窗 / 首次指导 ─────────────────────────
|
|
738
808
|
var CHANGELOG = {
|
|
809
|
+
'3.1.1': { zh: [
|
|
810
|
+
'新增:记忆搬包 —— 把某个工作区的记忆整体搬到另一台机器。「存储管理」页签新增入口:导出本工作区得到 .dam-pack,到新机器导入。',
|
|
811
|
+
'导入是三步向导:选包 → 预览差异(只读,不动任何文件) → 确认导入。预览会列出路径是否变化、新增/覆盖几个文件、内部引用要重写多少处。',
|
|
812
|
+
'换机器会自动重写内部路径:包内指向旧路径/旧 slug 的引用(含正文里的路径字样)按 4 种写法逐一替换成新机器真实路径;路径相同则一个字都不改。',
|
|
813
|
+
'冲突默认保留本机(不覆盖);也可选覆盖或改名为 .from-pack 另存。四道底线:导入前整体备份(失败即中止)、sha256 校验不过即拒、拒绝路径穿越、workspaces-summary.json 走合并(不冲掉你其它工作区)。',
|
|
814
|
+
'修复:PowerShell/控制台不再被自动记忆的日志刷屏 —— 根因是诊断输出在落盘后又打了一遍控制台,而它是唯一每轮都会调用的日志出口。现在只落盘、不上控制台。',
|
|
815
|
+
'修复:诊断日志不再无限增长 —— 此前无任何轮转,实测单文件已达 11 MB / 11.2 万行;现在超过 2 MB 自动轮转,只保留当前 + 1 份历史。',
|
|
816
|
+
'新增:诊断日志现在可以在「设置 → 诊断」页签里查 —— 显示完整路径(可复制)、大小、行数,并提示崩溃时把该文件附给维护者。',
|
|
817
|
+
'说明:两台机器同步本轮只做一次性搬包。后续不发明私有协议 —— 把导出目录指到你的云盘(OneDrive/Dropbox/坚果云等),靠包内校验和 + 显式导入来对齐。',
|
|
818
|
+
'修复(上游回流 PR #124):记忆中枢的「批内合并落盘」此前只挂在宿主喂数循环上 ⇒ 其它的批量入口(HTTP action=feed,面板/外部重放器一次喂一整批)仍是每行一次整份写盘(写放大)。现已把批语义下沉到「拥有这批行」的那一层,任何批量入口都自动只落一次。',
|
|
819
|
+
], en: [
|
|
820
|
+
'New: memory pack — move one workspace\'s memory to another machine. A migrate card in the Storage tab exports this workspace to a .dam-pack; import it on the new machine.',
|
|
821
|
+
'Import is a 3-step wizard: pick pack -> preview diff (read-only, writes nothing) -> confirm. The preview lists whether paths changed, how many files are added or overwritten, and how many internal references will be rewritten.',
|
|
822
|
+
'Paths are rewritten automatically on a new machine: references to the old path and slug (including path text inside bodies) are replaced with the real new path in 4 spellings; if the path is identical, nothing is changed at all.',
|
|
823
|
+
'Conflicts default to keeping yours (no overwrite); you may instead overwrite or save alongside as .from-pack. Four safeguards: full backup before import (abort on failure), sha256 check rejects tampered packs, path traversal rejected, and workspaces-summary.json is MERGED so your other workspaces are preserved.',
|
|
824
|
+
'Fixed: PowerShell and the console are no longer flooded by auto-memory logs — the diagnostic writer logged to the console again after writing the file, and it is the only log outlet invoked every turn. It now writes to file only.',
|
|
825
|
+
'Fixed: the diagnostic log no longer grows without bound — there was no rotation at all and the file had reached 11 MB and 112k lines. It now rotates at 2 MB, keeping the current file plus one history file.',
|
|
826
|
+
'New: the diagnostic log is now viewable under Settings -> Diagnostics, showing the full path (copyable), size and line count, plus a note to attach that file when reporting a crash.',
|
|
827
|
+
'Note: two-machine sync in this release is one-shot packing only. No private sync protocol is invented — point the export folder at your cloud drive and reconcile via the pack checksum plus explicit import.',
|
|
828
|
+
'Fixed (upstream backport PR #124): batch-merged persistence in the memory hub used to hang off the host feed loop only, so other batch entries (HTTP action=feed, panel or external replay feeding a whole batch) still wrote the entire snapshot once per row. Batch semantics now live in the layer that owns the rows, so every batch entry persists once.',
|
|
829
|
+
] },
|
|
739
830
|
'3.1.0': { zh: [
|
|
740
831
|
'新增:记忆中枢「**人工越权晋升**」——给「证据还没攒够」被挡住的自沉淀条目一个真正可用的人工通道。两类门现在分得很清:**统计门**(跨会话数/成功次数没攒够)=内容没问题、只是证据不足 ⇒ 给按钮,你可以人工越过;**结构门**(已弃用/只是观察线索/有纠正记录/没写「怎么算成功」/高风险未批准)=内容本身还不构成技能 ⇒ 不给按钮,并直接写明为什么。所以不会再出现「点了没反应」的假按钮。越权过的条目会留下「人工授权」痕迹,可审计。',
|
|
741
832
|
'新增:设置页按用途重排为 9 个分区(语义记忆总开关 / 记忆窗口(注入什么) / 记忆容量与归档 / 自动沉淀成技能 / 长会话接续 / 自动化与免打扰 / 存储与外部记忆 / 外观与交互 / 关于与诊断)。**控件一字未改**,只是搬进对应分区并改了分组标题——此前「记忆中枢的判定参数」和「注入配置」混在一个分区里,找东西全靠翻。',
|
|
@@ -1230,6 +1321,7 @@ window.__ModuleLoader__.load({
|
|
|
1230
1321
|
update: ROUTE_PREFIX + '/update',
|
|
1231
1322
|
config: ROUTE_PREFIX + '/config',
|
|
1232
1323
|
reflect: ROUTE_PREFIX + '/reflect',
|
|
1324
|
+
recallStats: ROUTE_PREFIX + '/recall-stats',
|
|
1233
1325
|
reflectAuto: ROUTE_PREFIX + '/reflect-auto',
|
|
1234
1326
|
note: ROUTE_PREFIX + '/note',
|
|
1235
1327
|
// ★R7:用户级硬性约束的条目级读写
|
|
@@ -1264,6 +1356,9 @@ window.__ModuleLoader__.load({
|
|
|
1264
1356
|
reviewFeedback: ROUTE_PREFIX + '/review-feedback',
|
|
1265
1357
|
memoryHub: ROUTE_PREFIX + '/memory-hub',
|
|
1266
1358
|
storageManage: ROUTE_PREFIX + '/storage-manage',
|
|
1359
|
+
migrateExport: ROUTE_PREFIX + '/migrate-export',
|
|
1360
|
+
migrateInspect: ROUTE_PREFIX + '/migrate-inspect',
|
|
1361
|
+
migrateImport: ROUTE_PREFIX + '/migrate-import',
|
|
1267
1362
|
}
|
|
1268
1363
|
function query(params) {
|
|
1269
1364
|
var search = new URLSearchParams()
|
|
@@ -1441,7 +1536,13 @@ window.__ModuleLoader__.load({
|
|
|
1441
1536
|
// ③ 错开值改走 --dam-dur-stagger(数值不变,只刻度化)。
|
|
1442
1537
|
'[data-dam-page] [data-dam-flow] > [data-dam-card] { grid-column: auto; margin-bottom: 0;',
|
|
1443
1538
|
' animation: dam-card-in var(--dam-dur-fast) var(--dam-ease-out) both; }',
|
|
1444
|
-
'@keyframes dam-
|
|
1539
|
+
'@keyframes dam-stat-num { from { opacity: 0; transform: translateY(4px); filter: blur(2px); } to { opacity: 1; transform: none; filter: none; } }',
|
|
1540
|
+
'@keyframes dam-stat-reveal { from { opacity: 0; filter: blur(2px); } to { opacity: 1; filter: none; } }',
|
|
1541
|
+
'@keyframes dam-stat-grow { from { transform: scaleX(.02); transform-origin: left center; } to { transform: none; } }',
|
|
1542
|
+
'@keyframes dam-stat-pop { from { opacity: 0; transform: scale(.92); } to { opacity: 1; transform: none; } }',
|
|
1543
|
+
'@keyframes dam-stat-pulse { 0%, 100% { opacity: .45; } 50% { opacity: .85; } }',
|
|
1544
|
+
'@keyframes dam-stat-arc { from { opacity: 0; } to { opacity: 1; } }',
|
|
1545
|
+
'@keyframes dam-card-in { from { opacity: 0; transform: translateY(8px) scale(.988); } to { opacity: 1; transform: none; } }',
|
|
1445
1546
|
'[data-dam-page] [data-dam-flow] > [data-dam-card]:nth-child(2) { animation-delay: calc(var(--dam-dur-stagger) * 1); }',
|
|
1446
1547
|
'[data-dam-page] [data-dam-flow] > [data-dam-card]:nth-child(3) { animation-delay: calc(var(--dam-dur-stagger) * 2); }',
|
|
1447
1548
|
'[data-dam-page] [data-dam-flow] > [data-dam-card]:nth-child(4) { animation-delay: calc(var(--dam-dur-stagger) * 3); }',
|
|
@@ -3655,6 +3756,100 @@ window.__ModuleLoader__.load({
|
|
|
3655
3756
|
var filePair = useState('')
|
|
3656
3757
|
var delFile = filePair[0]
|
|
3657
3758
|
var setDelFile = filePair[1]
|
|
3759
|
+
// 迁移搬包(P4 前端接线):导出/三步向导的全部状态。只用 useState,不引新依赖。
|
|
3760
|
+
var migOutPair = useState('')
|
|
3761
|
+
var migOut = migOutPair[0]
|
|
3762
|
+
var setMigOut = migOutPair[1]
|
|
3763
|
+
var migPackPair = useState('')
|
|
3764
|
+
var migPack = migPackPair[0]
|
|
3765
|
+
var setMigPack = migPackPair[1]
|
|
3766
|
+
var migOutPickingPair = useState(false)
|
|
3767
|
+
var migOutPicking = migOutPickingPair[0]
|
|
3768
|
+
var setMigOutPicking = migOutPickingPair[1]
|
|
3769
|
+
var migPackPickingPair = useState(false)
|
|
3770
|
+
var migPackPicking = migPackPickingPair[0]
|
|
3771
|
+
var setMigPackPicking = migPackPickingPair[1]
|
|
3772
|
+
var migBusyPair = useState('')
|
|
3773
|
+
var migBusy = migBusyPair[0]
|
|
3774
|
+
var setMigBusy = migBusyPair[1]
|
|
3775
|
+
var migErrStatePair = useState('')
|
|
3776
|
+
var migErr = migErrStatePair[0]
|
|
3777
|
+
var setMigErr = migErrStatePair[1]
|
|
3778
|
+
var migNotePair = useState('')
|
|
3779
|
+
var migNote = migNotePair[0]
|
|
3780
|
+
var setMigNote = migNotePair[1]
|
|
3781
|
+
var migPlanPair = useState(null)
|
|
3782
|
+
var migPlan = migPlanPair[0]
|
|
3783
|
+
var setMigPlan = migPlanPair[1]
|
|
3784
|
+
var migResultPair = useState(null)
|
|
3785
|
+
var migResult = migResultPair[0]
|
|
3786
|
+
var setMigResult = migResultPair[1]
|
|
3787
|
+
var migConflictPair = useState('keep')
|
|
3788
|
+
var migConflict = migConflictPair[0]
|
|
3789
|
+
var setMigConflict = migConflictPair[1]
|
|
3790
|
+
// ok===false 时把 error / errors / warnings 一并显示,绝不静默。
|
|
3791
|
+
function migFail(tag, j, e) {
|
|
3792
|
+
var parts = []
|
|
3793
|
+
if (e) parts.push(String(e && e.message ? e.message : e))
|
|
3794
|
+
if (j && j.error) parts.push(String(j.error))
|
|
3795
|
+
if (j && j.errors) parts.push(Array.isArray(j.errors) ? j.errors.join(' | ') : String(j.errors))
|
|
3796
|
+
if (j && j.warnings && j.warnings.length) parts.push((locale === 'zh' ? '警告: ' : 'warnings: ') + j.warnings.join(' | '))
|
|
3797
|
+
if (j && j.skipped && j.skipped.length) parts.push((locale === 'zh' ? '跳过: ' : 'skipped: ') + j.skipped.join(' | '))
|
|
3798
|
+
setMigErr(tag + ' ✗ ' + (parts.length ? parts.join(' · ') : (locale === 'zh' ? '未知错误' : 'unknown error')))
|
|
3799
|
+
}
|
|
3800
|
+
// 选择目录:沿用本文件的 pickDir 原生选择器形态;拿不到目录就提示手填输入框(不阻塞)。
|
|
3801
|
+
function migPickInto(setVal, setPicking) {
|
|
3802
|
+
setPicking(true); setMigErr('')
|
|
3803
|
+
apiPost(API.pickDir, {}).then(function (d) {
|
|
3804
|
+
setPicking(false)
|
|
3805
|
+
if (d && d.dir) { setVal(d.dir); setMigNote((locale === 'zh' ? '已选择目录: ' : 'folder picked: ') + d.dir) }
|
|
3806
|
+
else { setMigNote(locale === 'zh' ? '系统文件夹选择器不可用,请手动填写路径。' : 'Native folder picker unavailable — type the path manually.') }
|
|
3807
|
+
}).catch(function () {
|
|
3808
|
+
setPicking(false)
|
|
3809
|
+
setMigNote(locale === 'zh' ? '系统文件夹选择器不可用,请手动填写路径。' : 'Native folder picker unavailable — type the path manually.')
|
|
3810
|
+
})
|
|
3811
|
+
}
|
|
3812
|
+
function migExport(opts) {
|
|
3813
|
+
if (migBusy) return
|
|
3814
|
+
opts = opts || {}
|
|
3815
|
+
setMigBusy('export'); setMigErr(''); setMigNote('')
|
|
3816
|
+
apiPost(API.migrateExport, { outPath: migOut || undefined }).then(function (j) {
|
|
3817
|
+
setMigBusy('')
|
|
3818
|
+
if (!j || j.ok === false) { migFail(t('migExport'), j, null); return }
|
|
3819
|
+
var s = j.stats || {}
|
|
3820
|
+
setMigNote(t('migExport') + ' ✓ ' + String(j.path || '') +
|
|
3821
|
+
' · ' + (locale === 'zh' ? '文件 ' : 'files ') + String(s.fileCount || 0) +
|
|
3822
|
+
' · ' + (locale === 'zh' ? '原始 ' : 'raw ') + String(s.bytes || 0) + ' B' +
|
|
3823
|
+
' · ' + (locale === 'zh' ? '压缩后 ' : 'packed ') + String(s.packedBytes || 0) + ' B' +
|
|
3824
|
+
(j.checksum ? ' · sha256 ' + String(j.checksum).slice(0, 12) : '') +
|
|
3825
|
+
(j.skipped && j.skipped.length ? ' · ' + (locale === 'zh' ? '跳过 ' : 'skipped ') + j.skipped.length : '') +
|
|
3826
|
+
(j.warnings && j.warnings.length ? ' · ' + (locale === 'zh' ? '警告 ' : 'warnings ') + j.warnings.length : ''))
|
|
3827
|
+
}).catch(function (e) { setMigBusy(''); migFail(t('migExport'), null, e) })
|
|
3828
|
+
}
|
|
3829
|
+
function migPreview() {
|
|
3830
|
+
if (migBusy) return
|
|
3831
|
+
if (!migPack) { setMigErr(t('migPickPack')); return }
|
|
3832
|
+
setMigBusy('preview'); setMigErr(''); setMigNote(''); setMigPlan(null); setMigResult(null)
|
|
3833
|
+
apiPost(API.migrateInspect, { packPath: migPack }).then(function (j) {
|
|
3834
|
+
setMigBusy('')
|
|
3835
|
+
if (!j || j.ok === false) { migFail(t('migPreview'), j, null); return }
|
|
3836
|
+
var p = j.plan || {}
|
|
3837
|
+
setMigPlan(p)
|
|
3838
|
+
setMigNote(t('migPreview') + ' ✓ ' + (p.pathChanged ? t('migPathChanged') : t('migSamePath')) +
|
|
3839
|
+
(j.sourceMissing ? (locale === 'zh' ? ' · ⚠ 源工作区已不存在' : ' · warning: source workspace missing') : ''))
|
|
3840
|
+
}).catch(function (e) { setMigBusy(''); migFail(t('migPreview'), null, e) })
|
|
3841
|
+
}
|
|
3842
|
+
function migApply() {
|
|
3843
|
+
if (migBusy) return
|
|
3844
|
+
if (!migPack) { setMigErr(t('migPickPack')); return }
|
|
3845
|
+
setMigBusy('apply'); setMigErr(''); setMigNote('')
|
|
3846
|
+
apiPost(API.migrateImport, { packPath: migPack, onConflict: migConflict }).then(function (j) {
|
|
3847
|
+
setMigBusy('')
|
|
3848
|
+
if (!j || j.ok === false) { migFail(t('migApply'), j, null); return }
|
|
3849
|
+
setMigResult(j)
|
|
3850
|
+
setMigNote(t('migApply') + ' ✓ ' + String(j.targetWs || ''))
|
|
3851
|
+
}).catch(function (e) { setMigBusy(''); migFail(t('migApply'), null, e) })
|
|
3852
|
+
}
|
|
3658
3853
|
useEffect(function () {
|
|
3659
3854
|
var alive = true
|
|
3660
3855
|
fetch(API.storageManage).then(function (r) { return r.json() }).then(function (j) {
|
|
@@ -3721,6 +3916,58 @@ window.__ModuleLoader__.load({
|
|
|
3721
3916
|
rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '12px', opacity: .7 } },
|
|
3722
3917
|
(locale === 'zh' ? '最近动作: ' : 'recent: ') + audit.map(function (a) { return a.action }).join(', ')))
|
|
3723
3918
|
}
|
|
3919
|
+
// 迁移搬包(P4):StorageTab 的「迁移搬包」卡片 —— 导出 + 导入三步向导(选包 → 预览差异 → 确认导入)。
|
|
3920
|
+
// 版式:只用 flexWrap / 百分比 / calc 字号,长路径 wordBreak:break-all ⇒ 窄浮层与宽页签都不横向溢出。
|
|
3921
|
+
var migOutPlaceholder = locale === 'zh' ? '输出目录(留空 = 默认导出目录)' : 'output dir (blank = default export dir)'
|
|
3922
|
+
var migRows = []
|
|
3923
|
+
if (migErr) migRows.push(h('div', { 'data-dam-error': '', key: 'merr', style: { wordBreak: 'break-all' } }, migErr))
|
|
3924
|
+
if (migNote) migRows.push(h('div', { 'data-dam-hint': '', key: 'mnote', style: { wordBreak: 'break-all' } }, migNote))
|
|
3925
|
+
migRows.push(h(Card, { title: t('migTitle') },
|
|
3926
|
+
h('div', { 'data-dam-hint': '', style: { marginBottom: '6px', wordBreak: 'break-all' } }, t('migHint')),
|
|
3927
|
+
h('div', { 'data-dam-row': '' },
|
|
3928
|
+
h('input', { 'data-dam-input': '', value: migOut, placeholder: migOutPlaceholder,
|
|
3929
|
+
onChange: function (e) { setMigOut(e.target.value) }, style: { flex: '1 1 100%' } }),
|
|
3930
|
+
h('button', { 'data-dam-btn': '', disabled: !!migBusy, onClick: function () { migPickInto(setMigOut, setMigOutPicking) } },
|
|
3931
|
+
migOutPicking ? t('loading') : (locale === 'zh' ? '选择目录' : 'pick folder')),
|
|
3932
|
+
h('button', { 'data-dam-btn': '', disabled: !!migBusy, style: { opacity: migBusy ? .5 : 1 },
|
|
3933
|
+
onClick: function () { migExport() } }, migBusy === 'export' ? t('saving') : t('migExport'))),
|
|
3934
|
+
h('div', { 'data-dam-row': '', style: { marginTop: '6px' } },
|
|
3935
|
+
h('input', { 'data-dam-input': '', value: migPack, placeholder: t('migPickPack'),
|
|
3936
|
+
onChange: function (e) { setMigPack(e.target.value) }, style: { flex: '1 1 100%' } }),
|
|
3937
|
+
h('button', { 'data-dam-btn': '', disabled: !!migBusy, onClick: function () { migPickInto(setMigPack, setMigPackPicking) } },
|
|
3938
|
+
migPackPicking ? t('loading') : t('migPickPack')),
|
|
3939
|
+
h('button', { 'data-dam-btn': '', disabled: !!migBusy, style: { opacity: migBusy ? .5 : 1 },
|
|
3940
|
+
onClick: migPreview }, migBusy === 'preview' ? t('loading') : t('migPreview'))),
|
|
3941
|
+
migPlan ? h('div', { style: { wordBreak: 'break-all', fontSize: 'calc(11.5px * var(--dam-scale))' } },
|
|
3942
|
+
h('div', null, migPlan.pathChanged ? t('migPathChanged') : t('migSamePath')),
|
|
3943
|
+
h('div', { 'data-dam-hint': '' }, (locale === 'zh' ? '路径: ' : 'path: ') + String(migPlan.fromPath || '') + ' → ' + String(migPlan.toPath || '')),
|
|
3944
|
+
h('div', { 'data-dam-hint': '' }, (locale === 'zh' ? '工作区标识: ' : 'slug: ') + String(migPlan.fromSlug || '') + ' → ' + String(migPlan.toSlug || '')),
|
|
3945
|
+
h('div', null, (locale === 'zh' ? '新增 ' : 'additions ') + String((migPlan.additions || []).length) +
|
|
3946
|
+
' · ' + (locale === 'zh' ? '覆盖 ' : 'overwrites ') + String((migPlan.overwrites || []).length) +
|
|
3947
|
+
' · ' + (locale === 'zh' ? '将写入 ' : 'will write ') + String((migPlan.stats && migPlan.stats.willWrite) || 0) +
|
|
3948
|
+
' · ' + (locale === 'zh' ? '重写命中 ' : 'rewrite hits ') + String((migPlan.rewrite && migPlan.rewrite.totalHits) || 0) +
|
|
3949
|
+
(locale === 'zh' ? ' 处 / 涉及文件 ' : ' across files ') + String((migPlan.rewrite && migPlan.rewrite.fileCount) || 0)),
|
|
3950
|
+
(migPlan.additions || []).length ? h('div', { 'data-dam-hint': '' },
|
|
3951
|
+
(locale === 'zh' ? '新增(前 10): ' : 'additions (top 10): ') + (migPlan.additions || []).slice(0, 10).map(function (a) { return String(a && a.path) + '(' + String((a && a.bytes) || 0) + 'B)' }).join(' , ')) : null,
|
|
3952
|
+
(migPlan.overwrites || []).length ? h('div', { 'data-dam-hint': '' },
|
|
3953
|
+
(locale === 'zh' ? '覆盖: ' : 'overwrites: ') + (migPlan.overwrites || []).map(function (o) { return String(o && o.path) + ' ' + String((o && o.oldBytes) || 0) + 'B→' + String((o && o.newBytes) || 0) + 'B' }).join(' , ')) : null,
|
|
3954
|
+
(migPlan.rewrite && (migPlan.rewrite.files || []).length) ? h('div', { 'data-dam-hint': '' },
|
|
3955
|
+
(locale === 'zh' ? '重写文件(前 10): ' : 'rewrite files (top 10): ') + (migPlan.rewrite.files || []).slice(0, 10).map(function (f) { return String(f && f.path) + '(' + String((f && f.hits) || 0) + ')' }).join(' , ')) : null,
|
|
3956
|
+
(migPlan.warnings && migPlan.warnings.length) ? h('div', { 'data-dam-hint': '' }, (locale === 'zh' ? '警告: ' : 'warnings: ') + migPlan.warnings.join(' | ')) : null,
|
|
3957
|
+
h('div', { 'data-dam-row': '', style: { marginTop: '6px' } },
|
|
3958
|
+
h('button', { 'data-dam-btn': '', style: { opacity: migConflict === 'keep' ? 1 : .5 }, onClick: function () { setMigConflict('keep') } }, (migConflict === 'keep' ? '● ' : '○ ') + t('migConflictKeep')),
|
|
3959
|
+
h('button', { 'data-dam-btn': '', style: { opacity: migConflict === 'overwrite' ? 1 : .5 }, onClick: function () { setMigConflict('overwrite') } }, (migConflict === 'overwrite' ? '● ' : '○ ') + t('migConflictOverwrite')),
|
|
3960
|
+
h('button', { 'data-dam-btn': '', style: { opacity: migConflict === 'rename' ? 1 : .5 }, onClick: function () { setMigConflict('rename') } }, (migConflict === 'rename' ? '● ' : '○ ') + t('migConflictRename'))),
|
|
3961
|
+
h('button', { 'data-dam-btn': '', disabled: !!migBusy, style: { marginTop: '6px', opacity: migBusy ? .5 : 1 }, onClick: migApply },
|
|
3962
|
+
migBusy === 'apply' ? t('saving') : t('migApply'))) : null,
|
|
3963
|
+
migResult ? h('div', { style: { wordBreak: 'break-all', fontSize: 'calc(11.5px * var(--dam-scale))' } },
|
|
3964
|
+
h('div', null, t('migWritten') + ': ' + String(migResult.written || 0) +
|
|
3965
|
+
((migResult.writtenFiles || []).length ? ' (' + migResult.writtenFiles.slice(0, 10).join(' , ') + ')' : '')),
|
|
3966
|
+
h('div', { 'data-dam-hint': '' }, t('migBackup') + ': ' + String(migResult.backup || '')),
|
|
3967
|
+
h('div', { 'data-dam-hint': '' }, (locale === 'zh' ? '冲突策略: ' : 'conflict: ') + String(migResult.onConflict || migConflict) +
|
|
3968
|
+
' · ' + (locale === 'zh' ? '目标工作区: ' : 'target: ') + String(migResult.targetWs || '')),
|
|
3969
|
+
h('div', null, String(migResult.summaryAction || '') + (migResult.summaryError ? ' ✗ ' + String(migResult.summaryError) : ''))) : null))
|
|
3970
|
+
rows.push(h('div', { 'data-dam-flow': '' }, migRows))
|
|
3724
3971
|
// 大屏分列:卡片流页签(见 CSS [data-dam-flow]);非卡片子元素仍整行。
|
|
3725
3972
|
return h('div', { 'data-dam-flow': '' }, rows)
|
|
3726
3973
|
}
|
|
@@ -4344,7 +4591,235 @@ window.__ModuleLoader__.load({
|
|
|
4344
4591
|
}))))
|
|
4345
4592
|
)
|
|
4346
4593
|
}
|
|
4347
|
-
|
|
4594
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
4595
|
+
// 统计页签(2026-09-22)—— 召回统计的可视化。
|
|
4596
|
+
//
|
|
4597
|
+
// 为什么单独一页:用户要「看哪个召回得更多、哪个更重要」,这是一眼看的活,
|
|
4598
|
+
// 不适合塞进已有 12 个页签的任意一个里(会把那一页的语义冲淡)。
|
|
4599
|
+
//
|
|
4600
|
+
// 立场:纯只读展示 + 一个清零按钮。**不改召回排序**(加权是后续独立批次,
|
|
4601
|
+
// 等这份数据证明口径可信之后再做)——故本页不做任何排序相关的写操作。
|
|
4602
|
+
//
|
|
4603
|
+
// 视觉:复用面板既有 --dam-* 令牌与 [data-dam-card] 入场,不引第三方图表库
|
|
4604
|
+
// (零构建环境下引库=多一个包+多一套主题;四种图手绘 SVG 足够表达)。
|
|
4605
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
4606
|
+
|
|
4607
|
+
/** 环形图:按层占比。用 stroke-dasharray 画弧,避免 path 三角计算。 */
|
|
4608
|
+
function DamDonut(props) {
|
|
4609
|
+
var items = props.items || []
|
|
4610
|
+
var total = items.reduce(function (n, it) { return n + (it.count || 0) }, 0)
|
|
4611
|
+
var R = 34, C = 2 * Math.PI * R
|
|
4612
|
+
var PALETTE = ['#5b8def', '#3fa96a', '#d4a94f', '#c061c0', '#4aa8b8', '#8a8f98']
|
|
4613
|
+
var acc = 0
|
|
4614
|
+
var arcs = items.slice(0, 6).map(function (it, i) {
|
|
4615
|
+
var frac = total ? (it.count || 0) / total : 0
|
|
4616
|
+
var seg = h('circle', {
|
|
4617
|
+
key: 'a' + i, cx: 44, cy: 44, r: R, fill: 'none',
|
|
4618
|
+
stroke: PALETTE[i % PALETTE.length], strokeWidth: 12,
|
|
4619
|
+
strokeDasharray: (frac * C) + ' ' + C,
|
|
4620
|
+
strokeDashoffset: -acc * C,
|
|
4621
|
+
transform: 'rotate(-90 44 44)',
|
|
4622
|
+
style: { animation: 'dam-stat-arc var(--dam-dur-slow) var(--dam-ease-out) both' },
|
|
4623
|
+
})
|
|
4624
|
+
acc += frac
|
|
4625
|
+
return seg
|
|
4626
|
+
})
|
|
4627
|
+
return h('div', { 'data-dam-stat-donut': '', style: { display: 'flex', gap: '14px', alignItems: 'center', flexWrap: 'wrap' } },
|
|
4628
|
+
h('svg', { width: 88, height: 88, viewBox: '0 0 88 88', role: 'img', 'aria-label': props.label }, arcs),
|
|
4629
|
+
h('div', { style: { display: 'flex', flexDirection: 'column', gap: '4px', minWidth: 0 } },
|
|
4630
|
+
items.slice(0, 6).map(function (it, i) {
|
|
4631
|
+
var pct = total ? Math.round(((it.count || 0) / total) * 100) : 0
|
|
4632
|
+
return h('div', { key: 'l' + i, style: { display: 'flex', alignItems: 'center', gap: '6px', fontSize: '12px' } },
|
|
4633
|
+
h('span', { style: { width: 8, height: 8, borderRadius: 8, background: PALETTE[i % PALETTE.length], flex: '0 0 auto' } }),
|
|
4634
|
+
h('span', null, it.key + ' · ' + it.count + ' (' + pct + '%)'))
|
|
4635
|
+
})))
|
|
4636
|
+
}
|
|
4637
|
+
|
|
4638
|
+
/** 横向柱状:召回最多的条目。条宽用百分比,随容器自适应。 */
|
|
4639
|
+
function DamBars(props) {
|
|
4640
|
+
var items = props.items || []
|
|
4641
|
+
var max = items.reduce(function (n, it) { return Math.max(n, it.count || 0) }, 0) || 1
|
|
4642
|
+
return h('div', { 'data-dam-stat-bars': '', style: { display: 'flex', flexDirection: 'column', gap: '6px' } },
|
|
4643
|
+
items.map(function (it, i) {
|
|
4644
|
+
var pct = Math.max(2, Math.round(((it.count || 0) / max) * 100))
|
|
4645
|
+
return h('div', { key: 'b' + i, style: { display: 'flex', alignItems: 'center', gap: '8px', fontSize: '12px' } },
|
|
4646
|
+
h('code', { style: { flex: '0 0 auto', opacity: .75, fontSize: '11px' } }, String(it.id || '').slice(0, 12)),
|
|
4647
|
+
h('div', { style: { flex: '1 1 auto', height: 8, borderRadius: 6, background: 'color-mix(in srgb, currentColor 10%, transparent)', overflow: 'hidden' } },
|
|
4648
|
+
h('div', {
|
|
4649
|
+
style: {
|
|
4650
|
+
height: '100%', width: pct + '%', borderRadius: 6,
|
|
4651
|
+
background: 'linear-gradient(90deg, #5b8def, #4aa8b8)',
|
|
4652
|
+
animation: 'dam-stat-grow var(--dam-dur-slow) var(--dam-ease-out) both',
|
|
4653
|
+
},
|
|
4654
|
+
})),
|
|
4655
|
+
h('span', { style: { flex: '0 0 auto', fontVariantNumeric: 'tabular-nums' } }, '×' + it.count),
|
|
4656
|
+
it.layer ? h('span', { style: { flex: '0 0 auto', opacity: .6, fontSize: '11px' } }, it.layer) : null)
|
|
4657
|
+
}))
|
|
4658
|
+
}
|
|
4659
|
+
|
|
4660
|
+
/** 折线 + 面积:按天走势。 */
|
|
4661
|
+
function DamSpark(props) {
|
|
4662
|
+
var days = props.items || []
|
|
4663
|
+
var W = 320, H = 68, P = 4
|
|
4664
|
+
if (!days.length) return h('div', { style: { opacity: .6, fontSize: '12px' } }, props.empty)
|
|
4665
|
+
var max = days.reduce(function (n, d) { return Math.max(n, d.count || 0) }, 0) || 1
|
|
4666
|
+
var step = days.length > 1 ? (W - P * 2) / (days.length - 1) : 0
|
|
4667
|
+
var pts = days.map(function (d, i) {
|
|
4668
|
+
var x = P + i * step
|
|
4669
|
+
var y = H - P - ((d.count || 0) / max) * (H - P * 2)
|
|
4670
|
+
return [x, y]
|
|
4671
|
+
})
|
|
4672
|
+
var line = pts.map(function (p, i) { return (i ? 'L' : 'M') + p[0].toFixed(1) + ' ' + p[1].toFixed(1) }).join(' ')
|
|
4673
|
+
var area = line + ' L' + (P + (days.length - 1) * step).toFixed(1) + ' ' + (H - P) + ' L' + P + ' ' + (H - P) + ' Z'
|
|
4674
|
+
return h('svg', {
|
|
4675
|
+
'data-dam-stat-spark': '', width: '100%', height: H, viewBox: '0 0 ' + W + ' ' + H,
|
|
4676
|
+
preserveAspectRatio: 'none', role: 'img', 'aria-label': props.label,
|
|
4677
|
+
style: { animation: 'dam-stat-reveal var(--dam-dur-slow) var(--dam-ease-inout) both' },
|
|
4678
|
+
},
|
|
4679
|
+
h('path', { d: area, fill: 'color-mix(in srgb, #5b8def 22%, transparent)', stroke: 'none' }),
|
|
4680
|
+
h('path', { d: line, fill: 'none', stroke: '#5b8def', strokeWidth: 2, strokeLinejoin: 'round', vectorEffect: 'non-scaling-stroke' }))
|
|
4681
|
+
}
|
|
4682
|
+
|
|
4683
|
+
/** 热力格:天 × 次数。一眼看出「哪天被想起来的次数多」。 */
|
|
4684
|
+
function DamHeat(props) {
|
|
4685
|
+
var days = (props.items || []).slice(-42)
|
|
4686
|
+
if (!days.length) return h('div', { style: { opacity: .6, fontSize: '12px' } }, props.empty)
|
|
4687
|
+
var max = days.reduce(function (n, d) { return Math.max(n, d.count || 0) }, 0) || 1
|
|
4688
|
+
return h('div', { 'data-dam-stat-heat': '', style: { display: 'flex', flexWrap: 'wrap', gap: 3 } },
|
|
4689
|
+
days.map(function (d, i) {
|
|
4690
|
+
var t = (d.count || 0) / max
|
|
4691
|
+
return h('div', {
|
|
4692
|
+
key: 'h' + i, title: d.day + ' · ' + d.count,
|
|
4693
|
+
style: {
|
|
4694
|
+
width: 12, height: 12, borderRadius: 3,
|
|
4695
|
+
background: 'color-mix(in srgb, #5b8def ' + Math.round(18 + t * 82) + '%, transparent)',
|
|
4696
|
+
animation: 'dam-stat-pop var(--dam-dur-fast) var(--dam-ease-out) both',
|
|
4697
|
+
animationDelay: Math.min(i * 12, 240) + 'ms',
|
|
4698
|
+
},
|
|
4699
|
+
})
|
|
4700
|
+
}))
|
|
4701
|
+
}
|
|
4702
|
+
|
|
4703
|
+
/** 数字卡:用途=「数字弹入」⇒ --dam-dur-slow + --dam-ease-out + 2px 模糊(skill number-pop-in 判据)。 */
|
|
4704
|
+
function DamStat(props) {
|
|
4705
|
+
return h('div', { 'data-dam-stat': '', style: { display: 'flex', flexDirection: 'column', gap: 2, minWidth: 92 } },
|
|
4706
|
+
h('div', {
|
|
4707
|
+
style: {
|
|
4708
|
+
fontSize: '22px', fontWeight: 600, fontVariantNumeric: 'tabular-nums',
|
|
4709
|
+
animation: 'dam-stat-num var(--dam-dur-slow) var(--dam-ease-out) both',
|
|
4710
|
+
},
|
|
4711
|
+
}, String(props.value)),
|
|
4712
|
+
h('div', { style: { fontSize: '11px', opacity: .7 } }, props.label))
|
|
4713
|
+
}
|
|
4714
|
+
|
|
4715
|
+
function StatsTab() {
|
|
4716
|
+
var s = useState(null)
|
|
4717
|
+
var data = s[0], setData = s[1]
|
|
4718
|
+
var e = useState('')
|
|
4719
|
+
var err = e[0], setErr = e[1]
|
|
4720
|
+
var r = useState(0)
|
|
4721
|
+
var tick = r[0], setTick = r[1]
|
|
4722
|
+
|
|
4723
|
+
useEffect(function () {
|
|
4724
|
+
var alive = true
|
|
4725
|
+
fetch(API.recallStats)
|
|
4726
|
+
.then(function (res) { return res.json() })
|
|
4727
|
+
.then(function (j) { if (alive) setData(j) })
|
|
4728
|
+
.catch(function (ex) { if (alive) setErr(String((ex && ex.message) || ex)) })
|
|
4729
|
+
return function () { alive = false }
|
|
4730
|
+
}, [tick])
|
|
4731
|
+
|
|
4732
|
+
function reset() {
|
|
4733
|
+
fetch(API.recallStats + '?reset=1', { method: 'POST' })
|
|
4734
|
+
.then(function () { setTick(function (n) { return n + 1 }) })
|
|
4735
|
+
.catch(function (ex) { setErr(String((ex && ex.message) || ex)) })
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4738
|
+
if (err) return h(Card, { title: t('statsTitle') }, h('div', { style: { opacity: .8 } }, err))
|
|
4739
|
+
// 骨架态:用途 =「骨架屏内容揭示」⇒ 脉冲用 --dam-dur-slow(与 skill 同用途判据一致)
|
|
4740
|
+
if (!data) return h(Card, { title: t('statsTitle') },
|
|
4741
|
+
h('div', { 'data-dam-stat-skeleton': '', style: { display: 'flex', flexDirection: 'column', gap: 8 } },
|
|
4742
|
+
[0, 1, 2].map(function (i) {
|
|
4743
|
+
return h('div', {
|
|
4744
|
+
key: 'sk' + i,
|
|
4745
|
+
style: {
|
|
4746
|
+
height: 14, borderRadius: 7, width: (70 - i * 12) + '%',
|
|
4747
|
+
background: 'color-mix(in srgb, currentColor 11%, transparent)',
|
|
4748
|
+
animation: 'dam-stat-pulse var(--dam-dur-slow) var(--dam-ease-linear) infinite',
|
|
4749
|
+
},
|
|
4750
|
+
})
|
|
4751
|
+
})))
|
|
4752
|
+
if (!data.enabled) return h(Card, { title: t('statsTitle') }, h('div', { style: { opacity: .75 } }, t('statsEmpty')))
|
|
4753
|
+
|
|
4754
|
+
var chs = data.channels || {}
|
|
4755
|
+
var chIds = data.channelIds || []
|
|
4756
|
+
// 三条通路各自一格;标签用中文/英文 i18n 键,绝不硬编码文案
|
|
4757
|
+
var META = {
|
|
4758
|
+
model: { name: t('statsChModel'), hint: t('statsChModelHint') },
|
|
4759
|
+
inject: { name: t('statsChInject'), hint: t('statsChInjectHint') },
|
|
4760
|
+
shadow: { name: t('statsChShadow'), hint: t('statsChShadowHint') },
|
|
4761
|
+
}
|
|
4762
|
+
var pick = function (id) { return chs[id] || {} }
|
|
4763
|
+
var model = pick('model'), inject = pick('inject'), shadow = pick('shadow')
|
|
4764
|
+
var totalEvents = chIds.reduce(function (n, id) { return n + (pick(id).events || 0) }, 0)
|
|
4765
|
+
var emptyAll = !totalEvents
|
|
4766
|
+
var since = data.since ? new Date(data.since).toLocaleString() : t('statsNever')
|
|
4767
|
+
|
|
4768
|
+
// 单条通路卡片:数字行 + 可选图表。**图表都走既有 Dam* 组件**(零新依赖)。
|
|
4769
|
+
function chCard(ch, id, extra) {
|
|
4770
|
+
var m = META[id] || {}
|
|
4771
|
+
var body = [
|
|
4772
|
+
h('div', { style: { opacity: .72, fontSize: '12px', marginBottom: 2 } }, m.hint),
|
|
4773
|
+
h('div', { style: { display: 'flex', gap: 18, flexWrap: 'wrap', marginTop: 4 } },
|
|
4774
|
+
h(DamStat, { value: ch.events || 0, label: t('statsEvents') }),
|
|
4775
|
+
h(DamStat, { value: ch.hits || 0, label: t('statsTotalHits') }),
|
|
4776
|
+
h(DamStat, { value: ch.distinct || 0, label: t('statsDistinct') }),
|
|
4777
|
+
h(DamStat, { value: ch.zeroHit || 0, label: t('statsZeroHit') })),
|
|
4778
|
+
]
|
|
4779
|
+
return h(Card, { title: m.name }, h('div', { style: { display: 'flex', flexDirection: 'column', gap: 12 } },
|
|
4780
|
+
extra ? extra(ch) : null,
|
|
4781
|
+
body,
|
|
4782
|
+
(ch.top && ch.top.length) ? h('div', { style: { marginTop: 4 } }, h(DamBars, { items: ch.top.slice(0, 12) }))
|
|
4783
|
+
: h('div', { style: { opacity: .5, fontSize: '12px' } }, t('statsNoInject'))))
|
|
4784
|
+
}
|
|
4785
|
+
|
|
4786
|
+
return h('div', null,
|
|
4787
|
+
h(Card, { title: t('statsTitle') },
|
|
4788
|
+
h('div', { style: { display: 'flex', flexDirection: 'column', gap: 12 } },
|
|
4789
|
+
h('div', { style: { opacity: .75, fontSize: '12px' } }, t('statsSubtitle')),
|
|
4790
|
+
emptyAll ? h('div', { style: { opacity: .7 } }, t('statsEmpty')) : null,
|
|
4791
|
+
// 总览:三条通路各占多少(饼图)—— 一眼看出"哪一条在动"
|
|
4792
|
+
emptyAll ? null : h(DamDonut, {
|
|
4793
|
+
items: chIds.map(function (id) { return { id: (META[id] || {}).name || id, count: pick(id).events || 0 } }),
|
|
4794
|
+
label: t('statsChOverview'),
|
|
4795
|
+
}),
|
|
4796
|
+
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 10 } },
|
|
4797
|
+
h('div', { style: { fontSize: '11px', opacity: .6 } }, t('statsSince') + ': ' + since),
|
|
4798
|
+
h('button', {
|
|
4799
|
+
type: 'button', onClick: reset,
|
|
4800
|
+
style: { fontSize: '12px', padding: '4px 10px', borderRadius: 8, cursor: 'pointer' },
|
|
4801
|
+
}, t('statsReset'))))),
|
|
4802
|
+
// ① 模型主动检索:高信号。附按层分布 + 按天走势
|
|
4803
|
+
chCard(model, 'model', function (ch) {
|
|
4804
|
+
return h('div', { style: { display: 'flex', flexDirection: 'column', gap: 10 } },
|
|
4805
|
+
(ch.byLayer && ch.byLayer.length) ? h(DamDonut, { items: ch.byLayer, label: t('statsByLayer') }) : null,
|
|
4806
|
+
(ch.byDay && ch.byDay.length) ? h(DamSpark, { items: ch.byDay, label: t('statsByDay'), empty: t('statsEmpty') }) : null,
|
|
4807
|
+
(ch.byDay && ch.byDay.length) ? h(DamHeat, { items: ch.byDay, empty: t('statsEmpty') }) : null)
|
|
4808
|
+
}),
|
|
4809
|
+
// ② 每轮注入:成本视角。**条长按字符数**(回答"哪一段最占预算")
|
|
4810
|
+
chCard(inject, 'inject', function (ch) {
|
|
4811
|
+
var segs = (ch.top || []).map(function (it) { return { id: it.id, count: it.score || 0, n: it.count || 0 } })
|
|
4812
|
+
segs.sort(function (a, b) { return b.count - a.count })
|
|
4813
|
+
if (!segs.length) return null
|
|
4814
|
+
return h('div', { style: { display: 'flex', flexDirection: 'column', gap: 6 } },
|
|
4815
|
+
h('div', { style: { opacity: .7, fontSize: '12px' } }, t('statsSegChars')),
|
|
4816
|
+
h(DamBars, { items: segs.slice(0, 14) }))
|
|
4817
|
+
}),
|
|
4818
|
+
// ③ 主动唤起:系统判断质量。没开 shadow 时该卡自然为 0,不谎报
|
|
4819
|
+
chCard(shadow, 'shadow', null))
|
|
4820
|
+
}
|
|
4821
|
+
|
|
4822
|
+
function WorkspaceTab() {
|
|
4348
4823
|
var dataPair = useState(null)
|
|
4349
4824
|
var data = dataPair[0]
|
|
4350
4825
|
var setData = dataPair[1]
|
|
@@ -4834,10 +5309,11 @@ window.__ModuleLoader__.load({
|
|
|
4834
5309
|
if (tab === 'connect') return h(ConnectTab)
|
|
4835
5310
|
if (tab === 'calendar') return h(CalendarTab)
|
|
4836
5311
|
if (tab === 'workspaces') return h(WorkspaceTab)
|
|
5312
|
+
if (tab === 'stats') return h(StatsTab)
|
|
4837
5313
|
return h(SearchTab)
|
|
4838
5314
|
}
|
|
4839
5315
|
function MEMORY_TABS() {
|
|
4840
|
-
return [['overview', t('overview')], ['logs', t('logs')], ['refine', t('refineTab')], ['hub', t('hubTab')], ['storage', t('storageTab')], ['notes', t('notes')], ['plan', t('planTab')], ['reflections', t('reflections')], ['connect', t('connect')], ['calendar', t('calendar')], ['search', t('search')], ['workspaces', t('workspaces')]]
|
|
5316
|
+
return [['overview', t('overview')], ['logs', t('logs')], ['refine', t('refineTab')], ['hub', t('hubTab')], ['storage', t('storageTab')], ['notes', t('notes')], ['plan', t('planTab')], ['reflections', t('reflections')], ['connect', t('connect')], ['calendar', t('calendar')], ['search', t('search')], ['workspaces', t('workspaces')], ['stats', t('statsTab')]]
|
|
4841
5317
|
}
|
|
4842
5318
|
|
|
4843
5319
|
// 「记忆」会话页(2026-09-21 二次修正) —— 注册在 conversation.view 槽位,与「对话轨迹」「上下文」「白板看板」并列。
|
|
@@ -5186,6 +5662,23 @@ window.__ModuleLoader__.load({
|
|
|
5186
5662
|
kv(t('kvWs'), currentWs() || t('kvWsUnknown'), false),
|
|
5187
5663
|
kv(t('kvApi'), probes ? Object.keys(probes).map(function (k) { return k + '=' + (probes[k].status !== undefined ? probes[k].status : probes[k].error) }).join(' ') : '…', apiBad),
|
|
5188
5664
|
hubIo ? kv(t('dbgHubIo'), hubIoText, hubIoWarn) : null,
|
|
5665
|
+
// ★2026-09-22(用户拍板):诊断日志只在**这里**看,不再往控制台刷。
|
|
5666
|
+
// 路径可选中复制;含「崩溃时附这个文件」的人话提示。只读投影,不含日志正文。
|
|
5667
|
+
(function () {
|
|
5668
|
+
var lg = data.logs && typeof data.logs === 'object' ? data.logs : null
|
|
5669
|
+
if (!lg) return null
|
|
5670
|
+
if (!lg.exists) return kv(t('dbgLogs'), t('dbgLogsNone'), false)
|
|
5671
|
+
var lgText = []
|
|
5672
|
+
if (lg.sizeText) lgText.push(lg.sizeText)
|
|
5673
|
+
if (lg.lines !== null && lg.lines !== undefined) lgText.push(lg.lines + ' ' + (locale === 'zh' ? '行' : 'lines'))
|
|
5674
|
+
if (lg.historyBytes > 0) lgText.push((locale === 'zh' ? '历史 ' : 'prev ') + fmtSize(lg.historyBytes))
|
|
5675
|
+
return [
|
|
5676
|
+
kv(t('dbgLogs'), lgText.join(' · '), false),
|
|
5677
|
+
h('div', { 'data-dam-hint': '', style: { marginLeft: '140px', wordBreak: 'break-all' } },
|
|
5678
|
+
(locale === 'zh' ? '路径:' : 'Path: ') + (lg.path || '—')),
|
|
5679
|
+
h('div', { 'data-dam-hint': '', style: { marginLeft: '140px', wordBreak: 'break-all' } }, t('dbgLogsHint')),
|
|
5680
|
+
]
|
|
5681
|
+
})(),
|
|
5189
5682
|
hubIoByFile ? h('div', { 'data-dam-hint': '', style: { marginLeft: '140px', wordBreak: 'break-all' } },
|
|
5190
5683
|
(locale === 'zh' ? '按层拆分(失败次数):' : 'per-store failures: ') + hubIoByFile) : null,
|
|
5191
5684
|
fp ? kv(t('dbgPrune'), fpText, fpWarn) : null,
|
|
@@ -5831,6 +6324,33 @@ window.__ModuleLoader__.load({
|
|
|
5831
6324
|
}
|
|
5832
6325
|
return null
|
|
5833
6326
|
}
|
|
6327
|
+
// ★P10-T1/T2(2026-09-22):13 个注入分区的**显示文案**。成员与顺序以宿主下发的
|
|
6328
|
+
// `promptSections` 为准(宿主常量 PROMPT_SECTION_KEYS_V1 是唯一真源),本表只提供文案;
|
|
6329
|
+
// 键集合必须与宿主常量相等 —— 由守卫 smoke-test-t1-prompt-sections-pre.mjs 断言。
|
|
6330
|
+
// offZh/offEn 只给 must 段(宿主 PROMPT_SECTION_MUST_V1),其余用通用提示。
|
|
6331
|
+
var PROMPT_SECTION_TEXT = {
|
|
6332
|
+
'rules-section': { zh: '规则段(用户级硬约束)', en: 'Rules section (hard constraints)' },
|
|
6333
|
+
'tier0-catalog': { zh: 'Tier-0 常驻目录(指引层)', en: 'Tier-0 catalog (index layer)' },
|
|
6334
|
+
'whiteboard-plan': { zh: '白板 PLAN 快照', en: 'Whiteboard (PLAN) snapshot' },
|
|
6335
|
+
'handoff-ledger': { zh: '交接账本', en: 'Handoff ledger' },
|
|
6336
|
+
'calendar': { zh: '日历与日程', en: 'Calendar & schedule' },
|
|
6337
|
+
'external-memory': { zh: '其他 AI 工具记忆', en: 'Other AI tools’ memory' },
|
|
6338
|
+
'external-sessions': { zh: '历史会话索引', en: 'Past session index' },
|
|
6339
|
+
'workspace-map': { zh: '其他工作区记忆索引', en: 'Other workspace index' },
|
|
6340
|
+
'welcome-title': { zh: '新工作区欢迎语(标题)', en: 'New-workspace greeting (title)' },
|
|
6341
|
+
'welcome-body': { zh: '新工作区欢迎语(正文)', en: 'New-workspace greeting (body)' },
|
|
6342
|
+
'plan-update-request': {
|
|
6343
|
+
zh: '白板更新请求(硬性要求)', en: 'Whiteboard update request (mandatory)',
|
|
6344
|
+
offZh: '关掉后:模型不再被要求更新白板 ⇒ 白板会逐渐与项目现实脱节。',
|
|
6345
|
+
offEn: 'When off: the model is no longer asked to update the whiteboard — it will drift from reality.',
|
|
6346
|
+
},
|
|
6347
|
+
'water-advisory': {
|
|
6348
|
+
zh: '上下文水位提醒(硬性要求)', en: 'Context water-level advisory (mandatory)',
|
|
6349
|
+
offZh: '关掉后:不再提醒收尾写账本/反思 ⇒ 长任务跨窗口续命会变差。',
|
|
6350
|
+
offEn: 'When off: no reminder to write the ledger/reflection at wrap-up — cross-window continuity suffers.',
|
|
6351
|
+
},
|
|
6352
|
+
'handoff-pointer': { zh: '未绑定工作区时的账本指针', en: 'Ledger pointer (unbound workspace)' },
|
|
6353
|
+
}
|
|
5834
6354
|
function SettingsPage() {
|
|
5835
6355
|
var tickPair = useTick()
|
|
5836
6356
|
var cfgPair = useState(null)
|
|
@@ -5988,7 +6508,13 @@ window.__ModuleLoader__.load({
|
|
|
5988
6508
|
useEffect(function () { return controller.subscribe(tickPair[1]) }, [])
|
|
5989
6509
|
useEffect(function () {
|
|
5990
6510
|
var alive = true
|
|
5991
|
-
apiGet(API.config).then(function (d) {
|
|
6511
|
+
apiGet(API.config).then(function (d) {
|
|
6512
|
+
if (!alive) return
|
|
6513
|
+
setCfg(d.config)
|
|
6514
|
+
// ★P10-T1:分区清单随 config 应答体下发(宿主唯一真源;取不到就留空 ⇒ 面板给 fail-soft 提示)
|
|
6515
|
+
setPsecKeys(Array.isArray(d.promptSections) ? d.promptSections : [])
|
|
6516
|
+
setPsecMust(Array.isArray(d.promptSectionMust) ? d.promptSectionMust : [])
|
|
6517
|
+
}).catch(function (e) { setErr(e.message) })
|
|
5992
6518
|
// 打开设置页自动检查更新(host 有 12h 缓存,不重复查网)
|
|
5993
6519
|
apiGet(API.updateCheck).then(function (d) { if (alive) setVerInfo(d) }).catch(function () {})
|
|
5994
6520
|
return function () { alive = false }
|
|
@@ -6018,6 +6544,18 @@ window.__ModuleLoader__.load({
|
|
|
6018
6544
|
var detBusyPair = useState(false)
|
|
6019
6545
|
var detBusy = detBusyPair[0]
|
|
6020
6546
|
var setDetBusy = detBusyPair[1]
|
|
6547
|
+
// ★P10-T1(2026-09-22):注入分区开关的**二级页**状态 + 清单。
|
|
6548
|
+
// 清单来自宿主常量(经 /config 应答体 promptSections 下发),前端不硬编码成员。
|
|
6549
|
+
// 同 det* 的理由:必须挂在下方 if (!cfg) 早退之前,否则 hooks 数量不一致 → error #310。
|
|
6550
|
+
var psecOpenPair = useState(false)
|
|
6551
|
+
var psecOpen = psecOpenPair[0]
|
|
6552
|
+
var setPsecOpen = psecOpenPair[1]
|
|
6553
|
+
var psecKeysPair = useState([])
|
|
6554
|
+
var psecKeys = psecKeysPair[0]
|
|
6555
|
+
var setPsecKeys = psecKeysPair[1]
|
|
6556
|
+
var psecMustPair = useState([])
|
|
6557
|
+
var psecMust = psecMustPair[0]
|
|
6558
|
+
var setPsecMust = psecMustPair[1]
|
|
6021
6559
|
useEffect(function () {
|
|
6022
6560
|
refreshSem(setSem, function () { setSem({ loaded: true, ready: false }) })
|
|
6023
6561
|
return function () {}
|
|
@@ -6179,117 +6717,6 @@ window.__ModuleLoader__.load({
|
|
|
6179
6717
|
h('button', { 'data-dam-btn': '', title: locale === 'zh' ? '打开/收起安装向导(按当前引擎选择:JS 模型 或 Python 引擎)' : 'Open/collapse setup wizard (follows the selected engine)', onClick: function () { setGuide(guide ? '' : ((cfg.semanticEngineMode === 'python') ? 'python' : 'js')) } },
|
|
6180
6718
|
guide ? (locale === 'zh' ? '收起向导' : 'Hide wizard') : (locale === 'zh' ? '🧩 安装向导' : '🧩 Setup')),
|
|
6181
6719
|
]), t('semModeHint')),
|
|
6182
|
-
sem.loaded ? h('div', { 'data-dam-hint': '', style: { marginTop: '-4px', marginBottom: '8px' } },
|
|
6183
|
-
t('semResolved') + ': ' + (sem.resolvedTier === 'c2' ? t('tierC2') + ' ✓' : sem.resolvedTier === 'c3' ? t('tierC3') + ' ✓' : t('tierC1')))
|
|
6184
|
-
: null,
|
|
6185
|
-
// 2026-09-08 检测联动:所选模式与资产就绪状态不符时显式标注(不再静默掉回词法兜底)
|
|
6186
|
-
(sem.loaded && cfg.semanticEngineMode === 'js' && sem.ready === false) ? h('div', { 'data-dam-error': '', style: { marginTop: '-4px', marginBottom: '8px' } },
|
|
6187
|
-
locale === 'zh' ? '⚠ 已选 JS 引擎,但本地模型未就绪——当前实际生效:词法兜底。下方安装引导卡可下载模型(约130MB),完成后自动启用。' : '⚠ JS engine selected but the local model is not ready — lexical fallback is active. Use the setup card below to download the model (~130MB); it engages automatically when done.') : null,
|
|
6188
|
-
(sem.loaded && cfg.semanticEngineMode === 'python' && sem.pythonInt8Present === false) ? h('div', { 'data-dam-error': '', style: { marginTop: '-4px', marginBottom: '8px' } },
|
|
6189
|
-
locale === 'zh' ? '⚠ 已选 Python 引擎,但 BGE-M3 int8 模型未就绪——当前实际生效:词法兜底。下方安装向导可引导安装;若本机无法安装(如架构不支持),请改用 JS 引擎或 auto。' : '⚠ Python engine selected but the BGE-M3 int8 model is not ready — lexical fallback is active. Use the wizard below; if this machine cannot install it, switch to the JS engine or auto.') : null,
|
|
6190
|
-
field(t('fReasoning'), h('input', { type: 'checkbox', checked: !!cfg.reasoningObserverEnabled, onChange: function (e) { set('reasoningObserverEnabled', e.target.checked) } }), t('fReasoningHint')),
|
|
6191
|
-
field(locale === 'zh' ? '唤起阈值(校准策略)' : 'Activation thresholds (calibrated)', h('div', null,
|
|
6192
|
-
h('span', null, 'tauHi 0.45 · tauLo 0.35 · deltaExp 0.03 · deltaPro 0.05' + (sem.loaded ? ((locale === 'zh' ? ' · 发射模式:' : ' · emit: ') + (sem.activationEmitMode || 'shadow')) : ''))),
|
|
6193
|
-
t('fTuningHint')),
|
|
6194
|
-
]),
|
|
6195
|
-
section('window', sectionLabels.window, [
|
|
6196
|
-
field(t('fInject'), h('input', { type: 'checkbox', checked: !!cfg.injectEnabled, onChange: function (e) { set('injectEnabled', e.target.checked) } }), t('fInjectHint')),
|
|
6197
|
-
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')),
|
|
6198
|
-
// 2026-09-22 补接线(设置页缺口审计 P0-3/P0-4/P0-6):目录层与规则分层此前只有后端实现。
|
|
6199
|
-
field(t('fTier0Catalog'), h('input', { type: 'checkbox', 'data-dam-key': 'tier0CatalogEnabled', checked: cfg.tier0CatalogEnabled !== false, onChange: function (e) { set('tier0CatalogEnabled', e.target.checked) } }), t('fTier0CatalogHint')),
|
|
6200
|
-
field(t('fTier0Max'), h('input', { 'data-dam-input': '', type: 'number', min: 100, value: cfg.tier0MaxTokens === undefined ? 400 : cfg.tier0MaxTokens, onChange: function (e) { set('tier0MaxTokens', Math.max(100, Number(e.target.value) || 400)) } }), t('fTier0MaxHint')),
|
|
6201
|
-
field(t('fRulesLayering'), h('input', { type: 'checkbox', 'data-dam-key': 'rulesLayeringMode', checked: String(cfg.rulesLayeringMode || 'self').toLowerCase() !== 'off', onChange: function (e) { set('rulesLayeringMode', e.target.checked ? 'self' : 'off') } }), t('fRulesLayeringHint')),
|
|
6202
|
-
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')),
|
|
6203
|
-
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')),
|
|
6204
|
-
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')),
|
|
6205
|
-
field(t('fReinjectOnCompact'), h('input', { type: 'checkbox', checked: cfg.snapshotReinjectOnCompact !== false, onChange: function (e) { set('snapshotReinjectOnCompact', e.target.checked) } }), t('fReinjectOnCompactHint')),
|
|
6206
|
-
field(t('fPromptCustom'), h('button', { 'data-dam-btn': '', onClick: function () { setPromptEditOpen(!promptEditOpen) } }, (promptEditOpen ? (locale === 'zh' ? '收起' : 'Collapse') : (locale === 'zh' ? '编辑 prompt 层' : 'Edit prompt layers'))), t('fPromptCustomHint')),
|
|
6207
|
-
promptEditOpen ? [
|
|
6208
|
-
h('div', { key: '__layers', style: { padding: '6px 0 2px', width: '100%' } },
|
|
6209
|
-
(Object.keys(DEFAULT_PROMPT_LAYERS_CLIENT)).map(function (k) {
|
|
6210
|
-
return h('div', { key: k, style: { marginBottom: '6px' } },
|
|
6211
|
-
h('div', { 'data-dam-hint': '', style: { fontWeight: 700, marginBottom: '2px' } }, k),
|
|
6212
|
-
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) } }))
|
|
6213
|
-
})),
|
|
6214
|
-
h('div', { key: '__reset', 'data-dam-row': '', style: { marginTop: '6px' } },
|
|
6215
|
-
h('button', { 'data-dam-btn': '', onClick: function () { set('promptLayerOverrides', {}) } }, locale === 'zh' ? '一键恢复默认' : 'Reset to defaults'))
|
|
6216
|
-
] : null,
|
|
6217
|
-
]),
|
|
6218
|
-
section('capacity', sectionLabels.capacity, [
|
|
6219
|
-
field(t('fChildObs'), h('input', { type: 'checkbox', checked: !!cfg.contextBridgeObserveChildSessions, onChange: function (e) { set('contextBridgeObserveChildSessions', e.target.checked) } }), t('fChildObsHint')),
|
|
6220
|
-
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')),
|
|
6221
|
-
// 2026-09-22 补接线(P0-4):facts 淘汰上限(fact-store.js pruneIfNeeded)。
|
|
6222
|
-
field(t('fFactRetention'), h('input', { 'data-dam-input': '', type: 'number', min: 10, value: cfg.factRetentionMax === undefined ? 1000 : cfg.factRetentionMax, onChange: function (e) { set('factRetentionMax', Math.max(10, Number(e.target.value) || 1000)) } }), t('fFactRetentionHint')),
|
|
6223
|
-
field(t('fNoteCap'), h('input', { 'data-dam-input': '', type: 'number', min: 500, value: cfg.noteCapacityChars === undefined ? 24000 : cfg.noteCapacityChars, onChange: function (e) { set('noteCapacityChars', Number(e.target.value) || 24000) } }), t('fNoteCapHint')),
|
|
6224
|
-
field(t('fUserCap'), h('input', { 'data-dam-input': '', type: 'number', min: 500, value: cfg.userCapacityChars === undefined ? 24000 : cfg.userCapacityChars, onChange: function (e) { set('userCapacityChars', Number(e.target.value) || 24000) } }), t('fUserCapHint')),
|
|
6225
|
-
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')),
|
|
6226
|
-
field(t('fAutoConsolidate'), h('input', { type: 'checkbox', checked: cfg.autoConsolidate !== false, onChange: function (e) { set('autoConsolidate', e.target.checked) } }), t('fAutoConsolidateHint')),
|
|
6227
|
-
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')),
|
|
6228
|
-
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')),
|
|
6229
|
-
field(t('fMemFileIndex'), h('input', { type: 'checkbox', 'data-dam-key': 'memoryFileIndexEnabled', checked: cfg.memoryFileIndexEnabled === true, onChange: function (e) { set('memoryFileIndexEnabled', e.target.checked) } }), t('fMemFileIndexHint')),
|
|
6230
|
-
// 2026-09-14 解耦:交接白板与自动接续各自独立成项(旧实现只在白板页有接续按钮,且 enabled 还被
|
|
6231
|
-
// handoffEnabled 二次与运算)。两者出厂默认均为 false(功能仍在测试期),这里与白板页读写完全相同的配置键。
|
|
6232
|
-
]),
|
|
6233
|
-
section('skills', sectionLabels.skills, [
|
|
6234
|
-
h('div', { 'data-dam-hint': '' }, t('secMemoryHubHint')),
|
|
6235
|
-
field(t('fMemoryHub'), h('input', { type: 'checkbox', checked: !!cfg.memoryHubEnabled, onChange: function (e) { set('memoryHubEnabled', e.target.checked) } }), t('fMemoryHubHint')),
|
|
6236
|
-
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')),
|
|
6237
|
-
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')),
|
|
6238
|
-
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')),
|
|
6239
|
-
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')),
|
|
6240
|
-
field(t('fProcRisk'), h('input', { type: 'checkbox', checked: cfg.procedureHighRiskApproval !== false, onChange: function (e) { set('procedureHighRiskApproval', e.target.checked) } }), t('fProcRiskHint')),
|
|
6241
|
-
// 2026-09-22 补接线(设置页缺口审计 P0-1):procedureInjectEnabled 才是宿主真读的总闸
|
|
6242
|
-
// (procedure-switch.js 先判它);此前设置页只有旧键 ⇒ 用户改了也无效(灰控件)。
|
|
6243
|
-
field(t('fProcInject'), h('input', { type: 'checkbox', 'data-dam-key': 'procedureInjectEnabled', checked: cfg.procedureInjectEnabled !== false, onChange: function (e) { set('procedureInjectEnabled', e.target.checked) } }), t('fProcInjectHint')),
|
|
6244
|
-
field(t('fProcLevel'), h('select', { 'data-dam-select': '', value: cfg.procedureActiveLevel || 'checklist', onChange: function (e) { set('procedureActiveLevel', e.target.value) } },
|
|
6245
|
-
h('option', { value: 'checklist' }, locale === 'zh' ? 'checklist 完整步骤' : 'checklist (full steps)'),
|
|
6246
|
-
h('option', { value: 'excerpt' }, locale === 'zh' ? 'excerpt 摘要' : 'excerpt (summary)'),
|
|
6247
|
-
h('option', { value: 'hint' }, locale === 'zh' ? 'hint 仅提示' : 'hint (hint only)')), t('fProcLevelHint')),
|
|
6248
|
-
h('div', { 'data-dam-hint': '', style: { marginTop: '4px' } }, t('memoryHubViewHint')),
|
|
6249
|
-
]),
|
|
6250
|
-
section('handoff', sectionLabels.handoff, [
|
|
6251
|
-
field(t('fHandoff'), h('input', { type: 'checkbox', 'data-dam-key': 'handoffEnabled', checked: cfg.handoffEnabled !== false, onChange: function (e) { set('handoffEnabled', e.target.checked) } }), t('fHandoffHint')),
|
|
6252
|
-
field(t('fAutoContinue'), h('input', { type: 'checkbox', 'data-dam-key': 'autoContinueEnabled', checked: cfg.autoContinueEnabled !== false, onChange: function (e) { set('autoContinueEnabled', e.target.checked) } }), t('fAutoContinueHint')),
|
|
6253
|
-
field(t('fHandoffPlan'), h('input', { 'data-dam-input': '', type: 'number', min: 200, value: cfg.handoffPlanChars === undefined ? 1200 : cfg.handoffPlanChars, onChange: function (e) { set('handoffPlanChars', Number(e.target.value) || 1200) } }), t('fHandoffPlanHint')),
|
|
6254
|
-
// 2026-09-22 补接线(设置页缺口审计 P0-5):criteriaGate 此前只能改文件才能退。
|
|
6255
|
-
field(t('fCriteriaGate'), h('input', { type: 'checkbox', 'data-dam-key': 'criteriaGate', checked: cfg.criteriaGate !== false, onChange: function (e) { set('criteriaGate', e.target.checked) } }), t('fCriteriaGateHint')),
|
|
6256
|
-
field(t('fHandoffLedger'), h('input', { 'data-dam-input': '', type: 'number', min: 100, value: cfg.handoffLedgerChars === undefined ? 800 : cfg.handoffLedgerChars, onChange: function (e) { set('handoffLedgerChars', Number(e.target.value) || 800) } }), t('fHandoffLedgerHint')),
|
|
6257
|
-
field(t('fWaterWindow'), h('input', { 'data-dam-input': '', type: 'number', min: 0, value: cfg.waterLevelWindowTokens === undefined ? 65536 : cfg.waterLevelWindowTokens, onChange: function (e) { set('waterLevelWindowTokens', Number(e.target.value) || 0) } }), t('fWaterWindowHint')),
|
|
6258
|
-
field(t('fWaterThreshold'), h('input', { 'data-dam-input': '', type: 'number', min: 0.1, max: 1.5, step: 0.05, value: cfg.waterLevelThreshold === undefined ? 0.75 : cfg.waterLevelThreshold, onChange: function (e) { set('waterLevelThreshold', Number(e.target.value) || 0.75) } }), t('fWaterThresholdHint')),
|
|
6259
|
-
field(t('fWaterAdvisory'), h('input', { type: 'checkbox', checked: cfg.waterLevelAdvisory !== false, onChange: function (e) { set('waterLevelAdvisory', e.target.checked) } }), t('fWaterAdvisoryHint')),
|
|
6260
|
-
field(t('fWaterAuto'), h('input', { type: 'checkbox', checked: cfg.waterLevelAutoHandoff !== false, onChange: function (e) { set('waterLevelAutoHandoff', e.target.checked) } }), t('fWaterAutoHint')),
|
|
6261
|
-
field(locale === 'zh' ? '子代理痕迹回收' : 'Subagent trace recycle', h('input', { type: 'checkbox', checked: cfg.subagentGcEnabled !== false, onChange: function (e) { set('subagentGcEnabled', e.target.checked) } }),
|
|
6262
|
-
locale === 'zh' ? '本插件的一次性子代理(自动沉淀 / 时段总结 / 问候 / 蒸馏)每跑一次都会在 ~/.dsh/sessions 留下一个持久化会话;数量上千后会话列表会明显变慢。开启后任务一结束就把这些痕迹移入 ~/.dsh/subagent-gc-backup 备份(可回滚),不影响子代理结果。默认开。' : 'Every one-shot subagent this plugin spawns (auto-consolidation, scheduled summary, greeting, distillation) leaves a persisted session under ~/.dsh/sessions; hundreds of them slow the session list down. When on, each trace is moved into ~/.dsh/subagent-gc-backup right after the task ends (reversible), never affecting the subagent result. On by default.'),
|
|
6263
|
-
field(locale === 'zh' ? '兜底回收保留天数' : 'Fallback recycle keep days', h('input', { 'data-dam-input': '', type: 'number', min: 0, value: cfg.subagentGcKeepDays === undefined ? 3 : cfg.subagentGcKeepDays, onChange: function (e) { set('subagentGcKeepDays', Number(e.target.value) || 0) } }),
|
|
6264
|
-
locale === 'zh' ? '每天巡检一次,回收超过该天数仍残留的痕迹(比如任务异常中断没删掉的);0 = 不按时间,只靠任务结束即删。' : 'A daily sweep recycles traces older than this many days (e.g. leftovers from interrupted tasks); 0 = rely on the end-of-task recycle only.'),
|
|
6265
|
-
]),
|
|
6266
|
-
section('auto', sectionLabels.auto, [
|
|
6267
|
-
// 欢迎向导:开关(首启自动播放)+ 立即重看按钮(闭包内直调 openDialog——同一作用域,点击立即弹;
|
|
6268
|
-
// 不走 window 全局入口,避免多实例时序导致"点了没反应要刷新")+ 查看更新日志(走 update 弹窗,
|
|
6269
|
-
// 带 Logo 开场动画;versions 取 CHANGELOG 最新一条)
|
|
6270
|
-
field(t('fWelcomeTour'), h('div', { 'data-dam-row': '' },
|
|
6271
|
-
h('input', { type: 'checkbox', checked: cfg.welcomeTourEnabled !== false, onChange: function (e) { set('welcomeTourEnabled', e.target.checked) } }),
|
|
6272
|
-
h('button', { 'data-dam-btn': '', onClick: function () { try { openDialog({ kind: 'welcomeTour' }) } catch (eTour) {} } }, t('tourReplay')),
|
|
6273
|
-
h('button', { 'data-dam-btn': '', onClick: function () {
|
|
6274
|
-
try {
|
|
6275
|
-
var keys = Object.keys(CHANGELOG)
|
|
6276
|
-
if (!keys.length) return
|
|
6277
|
-
var latest = keys.sort(cmpVersion)[keys.length - 1]
|
|
6278
|
-
openDialog({ kind: 'update', versions: [{ version: latest, items: CHANGELOG[latest] }], currentVersion: latest })
|
|
6279
|
-
} catch (eLog) {}
|
|
6280
|
-
} }, locale === 'zh' ? '查看更新日志' : 'View changelog')), t('fWelcomeTourHint')),
|
|
6281
|
-
field(t('fAutoPopup'), h('input', { type: 'checkbox', checked: cfg.autoPopupEnabled !== false, onChange: function (e) { set('autoPopupEnabled', e.target.checked) } }), t('fAutoPopupHint')),
|
|
6282
|
-
field(t('fUnattended'), h('input', { type: 'checkbox', checked: !!cfg.unattendedMode, onChange: function (e) { set('unattendedMode', e.target.checked) } }), t('fUnattendedHint')),
|
|
6283
|
-
field(t('fUnattendedAuto'), h('input', { type: 'checkbox', checked: !!cfg.unattendedAuto, onChange: function (e) { set('unattendedAuto', e.target.checked) } }), t('fUnattendedAutoHint')),
|
|
6284
|
-
field(t('fAway'), h('input', { 'data-dam-input': '', type: 'number', min: 0, value: (Number(cfg.awayMinutes) === 0 ? 0 : (cfg.awayMinutes || 60)), onChange: function (e) { var v = Number(e.target.value); set('awayMinutes', Number.isFinite(v) && v >= 0 ? v : 60) } }), t('fAwayHint')),
|
|
6285
|
-
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')),
|
|
6286
|
-
field(t('fConsSchedule'), h('input', { type: 'checkbox', checked: cfg.consolidateScheduleEnabled !== false, onChange: function (e) { set('consolidateScheduleEnabled', e.target.checked) } }), t('fConsScheduleHint')),
|
|
6287
|
-
field(t('fConsScheduleTime'), h('input', { 'data-dam-input': '', placeholder: '09:30', value: cfg.consolidateScheduleTime === undefined ? '09:30' : cfg.consolidateScheduleTime, onChange: function (e) { set('consolidateScheduleTime', String(e.target.value || '').trim()) } }), t('fConsScheduleTimeHint')),
|
|
6288
|
-
field(t('fConsScheduleDays'), h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 60, value: cfg.consolidateScheduleDays === undefined ? 7 : cfg.consolidateScheduleDays, onChange: function (e) { set('consolidateScheduleDays', Number(e.target.value) || 7) } }), t('fConsScheduleDaysHint')),
|
|
6289
|
-
field(t('fMaintSchedule'), h('input', { type: 'checkbox', checked: cfg.maintainScheduleEnabled !== false, onChange: function (e) { set('maintainScheduleEnabled', e.target.checked) } }), t('fMaintScheduleHint')),
|
|
6290
|
-
field(t('fMaintScheduleTime'), h('input', { 'data-dam-input': '', placeholder: '10:00', value: cfg.maintainScheduleTime === undefined ? '10:00' : cfg.maintainScheduleTime, onChange: function (e) { set('maintainScheduleTime', String(e.target.value || '').trim()) } }), t('fMaintScheduleTimeHint')),
|
|
6291
|
-
]),
|
|
6292
|
-
section('store', sectionLabels.store, [
|
|
6293
6720
|
// M-CM6-B v3·环境检测面板:⟳ 按钮触发,自动检索环境(快检+深扫+热接入),联动安装助手;美学对齐安装向导卡
|
|
6294
6721
|
detOpen ? (function () {
|
|
6295
6722
|
var panelStyle = { border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.25)) 60%, transparent)', borderRadius: '10px', padding: '12px', marginBottom: '10px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.06)) 55%, transparent)', backdropFilter: 'blur(14px) saturate(1.3)', WebkitBackdropFilter: 'blur(14px) saturate(1.3)' }
|
|
@@ -6405,6 +6832,153 @@ window.__ModuleLoader__.load({
|
|
|
6405
6832
|
h('button', { 'data-dam-btn': '', onClick: function () { setGuide('') } }, t('gotIt'))))
|
|
6406
6833
|
})()
|
|
6407
6834
|
: null,
|
|
6835
|
+
sem.loaded ? h('div', { 'data-dam-hint': '', style: { marginTop: '-4px', marginBottom: '8px' } },
|
|
6836
|
+
t('semResolved') + ': ' + (sem.resolvedTier === 'c2' ? t('tierC2') + ' ✓' : sem.resolvedTier === 'c3' ? t('tierC3') + ' ✓' : t('tierC1')))
|
|
6837
|
+
: null,
|
|
6838
|
+
// 2026-09-08 检测联动:所选模式与资产就绪状态不符时显式标注(不再静默掉回词法兜底)
|
|
6839
|
+
(sem.loaded && cfg.semanticEngineMode === 'js' && sem.ready === false) ? h('div', { 'data-dam-error': '', style: { marginTop: '-4px', marginBottom: '8px' } },
|
|
6840
|
+
locale === 'zh' ? '⚠ 已选 JS 引擎,但本地模型未就绪——当前实际生效:词法兜底。下方安装引导卡可下载模型(约130MB),完成后自动启用。' : '⚠ JS engine selected but the local model is not ready — lexical fallback is active. Use the setup card below to download the model (~130MB); it engages automatically when done.') : null,
|
|
6841
|
+
(sem.loaded && cfg.semanticEngineMode === 'python' && sem.pythonInt8Present === false) ? h('div', { 'data-dam-error': '', style: { marginTop: '-4px', marginBottom: '8px' } },
|
|
6842
|
+
locale === 'zh' ? '⚠ 已选 Python 引擎,但 BGE-M3 int8 模型未就绪——当前实际生效:词法兜底。下方安装向导可引导安装;若本机无法安装(如架构不支持),请改用 JS 引擎或 auto。' : '⚠ Python engine selected but the BGE-M3 int8 model is not ready — lexical fallback is active. Use the wizard below; if this machine cannot install it, switch to the JS engine or auto.') : null,
|
|
6843
|
+
field(t('fReasoning'), h('input', { type: 'checkbox', checked: !!cfg.reasoningObserverEnabled, onChange: function (e) { set('reasoningObserverEnabled', e.target.checked) } }), t('fReasoningHint')),
|
|
6844
|
+
field(locale === 'zh' ? '唤起阈值(校准策略)' : 'Activation thresholds (calibrated)', h('div', null,
|
|
6845
|
+
h('span', null, 'tauHi 0.45 · tauLo 0.35 · deltaExp 0.03 · deltaPro 0.05' + (sem.loaded ? ((locale === 'zh' ? ' · 发射模式:' : ' · emit: ') + (sem.activationEmitMode || 'shadow')) : ''))),
|
|
6846
|
+
t('fTuningHint')),
|
|
6847
|
+
]),
|
|
6848
|
+
section('window', sectionLabels.window, [
|
|
6849
|
+
// ★P10-T1/T2/T3(2026-09-22)注入分区开关:**一级只放入口**(中性文案,不推荐用户改),
|
|
6850
|
+
// 13 个开关全在二级页;成员/顺序取自宿主下发的 psecKeys(前端不硬编码)。
|
|
6851
|
+
field(t('fPromptSections'), h('button', { 'data-dam-btn': '', onClick: function () { setPsecOpen(!psecOpen) } }, psecOpen ? (locale === 'zh' ? '收起' : 'Collapse') : (locale === 'zh' ? '高级:逐段注入控制…' : 'Advanced: per-section injection…')), t('fPromptSectionsHint')),
|
|
6852
|
+
psecOpen ? (function () {
|
|
6853
|
+
var toggles = (cfg.promptSectionToggles && typeof cfg.promptSectionToggles === 'object') ? cfg.promptSectionToggles : {}
|
|
6854
|
+
var pText = function (k) { var m = PROMPT_SECTION_TEXT[k]; return m ? (locale === 'zh' ? m.zh : m.en) : k }
|
|
6855
|
+
var pOff = function (k) {
|
|
6856
|
+
var m = PROMPT_SECTION_TEXT[k]
|
|
6857
|
+
var z = m ? (locale === 'zh' ? m.offZh : m.offEn) : ''
|
|
6858
|
+
if (z) return z
|
|
6859
|
+
return locale === 'zh' ? '关掉后本轮不再注入该段落。' : 'When off, this section is not injected this round.'
|
|
6860
|
+
}
|
|
6861
|
+
return h('div', { 'data-dam-prompt-sections': '', style: { border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.25)) 60%, transparent)', borderRadius: '10px', padding: '12px', marginBottom: '10px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.06)) 55%, transparent)' } }, [
|
|
6862
|
+
h('div', { style: { display: 'flex', gap: '8px', alignItems: 'center', marginBottom: '6px' } }, [
|
|
6863
|
+
h('b', { style: { flex: 1, fontSize: 'calc(12px * var(--dam-scale))' } }, locale === 'zh' ? '逐段注入控制(高级)' : 'Per-section injection (advanced)'),
|
|
6864
|
+
h('button', { 'data-dam-btn': '', onClick: function () { set('promptSectionToggles', {}) } }, locale === 'zh' ? '全部恢复全开' : 'Restore all'),
|
|
6865
|
+
h('button', { 'data-dam-btn': '', onClick: function () { setPsecOpen(false) } }, t('close')),
|
|
6866
|
+
]),
|
|
6867
|
+
h('div', { 'data-dam-hint': '', style: { marginBottom: '8px' } }, locale === 'zh'
|
|
6868
|
+
? '控制每一轮注入里包含哪些段落。默认全部开启;不推荐修改 —— 关掉某段会让模型少掉对应的上下文或纪律。'
|
|
6869
|
+
: 'Which sections each round injects. All on by default; not recommended to change — turning one off drops the matching context or discipline.'),
|
|
6870
|
+
psecKeys.length ? psecKeys.map(function (k) {
|
|
6871
|
+
return h('div', { key: k, 'data-dam-row': '', style: { alignItems: 'flex-start' } }, [
|
|
6872
|
+
h('label', { style: { display: 'flex', gap: '6px', alignItems: 'center', flex: 1, cursor: 'pointer' } }, [
|
|
6873
|
+
h('input', { type: 'checkbox', 'data-dam-pswitch': k, checked: toggles[k] !== false, onChange: function (e) {
|
|
6874
|
+
var t2 = Object.assign({}, toggles)
|
|
6875
|
+
if (e.target.checked) delete t2[k]; else t2[k] = false
|
|
6876
|
+
set('promptSectionToggles', t2)
|
|
6877
|
+
} }),
|
|
6878
|
+
h('span', { style: { fontSize: 'calc(12px * var(--dam-scale))' } }, pText(k) + (psecMust.indexOf(k) >= 0 ? (locale === 'zh' ? ' · 硬性要求' : ' · mandatory') : '')),
|
|
6879
|
+
]),
|
|
6880
|
+
h('div', { 'data-dam-hint': '', style: { flex: 1, opacity: .8 } }, pOff(k)),
|
|
6881
|
+
])
|
|
6882
|
+
}) : h('div', { 'data-dam-hint': '' }, locale === 'zh' ? '未能从宿主取到分区清单(请重启宿主后重试)。' : 'Section list unavailable (restart the host and retry).'),
|
|
6883
|
+
])
|
|
6884
|
+
})() : null,
|
|
6885
|
+
field(t('fInject'), h('input', { type: 'checkbox', checked: !!cfg.injectEnabled, onChange: function (e) { set('injectEnabled', e.target.checked) } }), t('fInjectHint')),
|
|
6886
|
+
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')),
|
|
6887
|
+
// 2026-09-22 补接线(设置页缺口审计 P0-3/P0-4/P0-6):目录层与规则分层此前只有后端实现。
|
|
6888
|
+
field(t('fTier0Catalog'), h('input', { type: 'checkbox', 'data-dam-key': 'tier0CatalogEnabled', checked: cfg.tier0CatalogEnabled !== false, onChange: function (e) { set('tier0CatalogEnabled', e.target.checked) } }), t('fTier0CatalogHint')),
|
|
6889
|
+
field(t('fTier0Max'), h('input', { 'data-dam-input': '', type: 'number', min: 100, value: cfg.tier0MaxTokens === undefined ? 400 : cfg.tier0MaxTokens, onChange: function (e) { set('tier0MaxTokens', Math.max(100, Number(e.target.value) || 400)) } }), t('fTier0MaxHint')),
|
|
6890
|
+
field(t('fRulesLayering'), h('input', { type: 'checkbox', 'data-dam-key': 'rulesLayeringMode', checked: String(cfg.rulesLayeringMode || 'self').toLowerCase() !== 'off', onChange: function (e) { set('rulesLayeringMode', e.target.checked ? 'self' : 'off') } }), t('fRulesLayeringHint')),
|
|
6891
|
+
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')),
|
|
6892
|
+
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')),
|
|
6893
|
+
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')),
|
|
6894
|
+
field(t('fReinjectOnCompact'), h('input', { type: 'checkbox', checked: cfg.snapshotReinjectOnCompact !== false, onChange: function (e) { set('snapshotReinjectOnCompact', e.target.checked) } }), t('fReinjectOnCompactHint')),
|
|
6895
|
+
field(t('fPromptCustom'), h('button', { 'data-dam-btn': '', onClick: function () { setPromptEditOpen(!promptEditOpen) } }, (promptEditOpen ? (locale === 'zh' ? '收起' : 'Collapse') : (locale === 'zh' ? '编辑 prompt 层' : 'Edit prompt layers'))), t('fPromptCustomHint')),
|
|
6896
|
+
promptEditOpen ? [
|
|
6897
|
+
h('div', { key: '__layers', style: { padding: '6px 0 2px', width: '100%' } },
|
|
6898
|
+
(Object.keys(DEFAULT_PROMPT_LAYERS_CLIENT)).map(function (k) {
|
|
6899
|
+
return h('div', { key: k, style: { marginBottom: '6px' } },
|
|
6900
|
+
h('div', { 'data-dam-hint': '', style: { fontWeight: 700, marginBottom: '2px' } }, k),
|
|
6901
|
+
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) } }))
|
|
6902
|
+
})),
|
|
6903
|
+
h('div', { key: '__reset', 'data-dam-row': '', style: { marginTop: '6px' } },
|
|
6904
|
+
h('button', { 'data-dam-btn': '', onClick: function () { set('promptLayerOverrides', {}) } }, locale === 'zh' ? '一键恢复默认' : 'Reset to defaults'))
|
|
6905
|
+
] : null,
|
|
6906
|
+
]),
|
|
6907
|
+
section('capacity', sectionLabels.capacity, [
|
|
6908
|
+
field(t('fChildObs'), h('input', { type: 'checkbox', checked: !!cfg.contextBridgeObserveChildSessions, onChange: function (e) { set('contextBridgeObserveChildSessions', e.target.checked) } }), t('fChildObsHint')),
|
|
6909
|
+
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')),
|
|
6910
|
+
// 2026-09-22 补接线(P0-4):facts 淘汰上限(fact-store.js pruneIfNeeded)。
|
|
6911
|
+
field(t('fFactRetention'), h('input', { 'data-dam-input': '', type: 'number', min: 10, value: cfg.factRetentionMax === undefined ? 1000 : cfg.factRetentionMax, onChange: function (e) { set('factRetentionMax', Math.max(10, Number(e.target.value) || 1000)) } }), t('fFactRetentionHint')),
|
|
6912
|
+
field(t('fNoteCap'), h('input', { 'data-dam-input': '', type: 'number', min: 500, value: cfg.noteCapacityChars === undefined ? 24000 : cfg.noteCapacityChars, onChange: function (e) { set('noteCapacityChars', Number(e.target.value) || 24000) } }), t('fNoteCapHint')),
|
|
6913
|
+
field(t('fUserCap'), h('input', { 'data-dam-input': '', type: 'number', min: 500, value: cfg.userCapacityChars === undefined ? 24000 : cfg.userCapacityChars, onChange: function (e) { set('userCapacityChars', Number(e.target.value) || 24000) } }), t('fUserCapHint')),
|
|
6914
|
+
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')),
|
|
6915
|
+
field(t('fAutoConsolidate'), h('input', { type: 'checkbox', checked: cfg.autoConsolidate !== false, onChange: function (e) { set('autoConsolidate', e.target.checked) } }), t('fAutoConsolidateHint')),
|
|
6916
|
+
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')),
|
|
6917
|
+
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')),
|
|
6918
|
+
field(t('fMemFileIndex'), h('input', { type: 'checkbox', 'data-dam-key': 'memoryFileIndexEnabled', checked: cfg.memoryFileIndexEnabled === true, onChange: function (e) { set('memoryFileIndexEnabled', e.target.checked) } }), t('fMemFileIndexHint')),
|
|
6919
|
+
// 2026-09-14 解耦:交接白板与自动接续各自独立成项(旧实现只在白板页有接续按钮,且 enabled 还被
|
|
6920
|
+
// handoffEnabled 二次与运算)。两者出厂默认均为 false(功能仍在测试期),这里与白板页读写完全相同的配置键。
|
|
6921
|
+
]),
|
|
6922
|
+
section('skills', sectionLabels.skills, [
|
|
6923
|
+
h('div', { 'data-dam-hint': '' }, t('secMemoryHubHint')),
|
|
6924
|
+
field(t('fMemoryHub'), h('input', { type: 'checkbox', checked: !!cfg.memoryHubEnabled, onChange: function (e) { set('memoryHubEnabled', e.target.checked) } }), t('fMemoryHubHint')),
|
|
6925
|
+
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')),
|
|
6926
|
+
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')),
|
|
6927
|
+
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')),
|
|
6928
|
+
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')),
|
|
6929
|
+
field(t('fProcRisk'), h('input', { type: 'checkbox', checked: cfg.procedureHighRiskApproval !== false, onChange: function (e) { set('procedureHighRiskApproval', e.target.checked) } }), t('fProcRiskHint')),
|
|
6930
|
+
// 2026-09-22 补接线(设置页缺口审计 P0-1):procedureInjectEnabled 才是宿主真读的总闸
|
|
6931
|
+
// (procedure-switch.js 先判它);此前设置页只有旧键 ⇒ 用户改了也无效(灰控件)。
|
|
6932
|
+
field(t('fProcInject'), h('input', { type: 'checkbox', 'data-dam-key': 'procedureInjectEnabled', checked: cfg.procedureInjectEnabled !== false, onChange: function (e) { set('procedureInjectEnabled', e.target.checked) } }), t('fProcInjectHint')),
|
|
6933
|
+
field(t('fProcLevel'), h('select', { 'data-dam-select': '', value: cfg.procedureActiveLevel || 'checklist', onChange: function (e) { set('procedureActiveLevel', e.target.value) } },
|
|
6934
|
+
h('option', { value: 'checklist' }, locale === 'zh' ? 'checklist 完整步骤' : 'checklist (full steps)'),
|
|
6935
|
+
h('option', { value: 'excerpt' }, locale === 'zh' ? 'excerpt 摘要' : 'excerpt (summary)'),
|
|
6936
|
+
h('option', { value: 'hint' }, locale === 'zh' ? 'hint 仅提示' : 'hint (hint only)')), t('fProcLevelHint')),
|
|
6937
|
+
h('div', { 'data-dam-hint': '', style: { marginTop: '4px' } }, t('memoryHubViewHint')),
|
|
6938
|
+
]),
|
|
6939
|
+
section('handoff', sectionLabels.handoff, [
|
|
6940
|
+
field(t('fHandoff'), h('input', { type: 'checkbox', 'data-dam-key': 'handoffEnabled', checked: cfg.handoffEnabled !== false, onChange: function (e) { set('handoffEnabled', e.target.checked) } }), t('fHandoffHint')),
|
|
6941
|
+
field(t('fAutoContinue'), h('input', { type: 'checkbox', 'data-dam-key': 'autoContinueEnabled', checked: cfg.autoContinueEnabled !== false, onChange: function (e) { set('autoContinueEnabled', e.target.checked) } }), t('fAutoContinueHint')),
|
|
6942
|
+
field(t('fHandoffPlan'), h('input', { 'data-dam-input': '', type: 'number', min: 200, value: cfg.handoffPlanChars === undefined ? 1200 : cfg.handoffPlanChars, onChange: function (e) { set('handoffPlanChars', Number(e.target.value) || 1200) } }), t('fHandoffPlanHint')),
|
|
6943
|
+
// 2026-09-22 补接线(设置页缺口审计 P0-5):criteriaGate 此前只能改文件才能退。
|
|
6944
|
+
field(t('fCriteriaGate'), h('input', { type: 'checkbox', 'data-dam-key': 'criteriaGate', checked: cfg.criteriaGate !== false, onChange: function (e) { set('criteriaGate', e.target.checked) } }), t('fCriteriaGateHint')),
|
|
6945
|
+
field(t('fHandoffLedger'), h('input', { 'data-dam-input': '', type: 'number', min: 100, value: cfg.handoffLedgerChars === undefined ? 800 : cfg.handoffLedgerChars, onChange: function (e) { set('handoffLedgerChars', Number(e.target.value) || 800) } }), t('fHandoffLedgerHint')),
|
|
6946
|
+
field(t('fWaterWindow'), h('input', { 'data-dam-input': '', type: 'number', min: 0, value: cfg.waterLevelWindowTokens === undefined ? 65536 : cfg.waterLevelWindowTokens, onChange: function (e) { set('waterLevelWindowTokens', Number(e.target.value) || 0) } }), t('fWaterWindowHint')),
|
|
6947
|
+
field(t('fWaterThreshold'), h('input', { 'data-dam-input': '', type: 'number', min: 0.1, max: 1.5, step: 0.05, value: cfg.waterLevelThreshold === undefined ? 0.75 : cfg.waterLevelThreshold, onChange: function (e) { set('waterLevelThreshold', Number(e.target.value) || 0.75) } }), t('fWaterThresholdHint')),
|
|
6948
|
+
field(t('fWaterAdvisory'), h('input', { type: 'checkbox', checked: cfg.waterLevelAdvisory !== false, onChange: function (e) { set('waterLevelAdvisory', e.target.checked) } }), t('fWaterAdvisoryHint')),
|
|
6949
|
+
field(t('fWaterAuto'), h('input', { type: 'checkbox', checked: cfg.waterLevelAutoHandoff !== false, onChange: function (e) { set('waterLevelAutoHandoff', e.target.checked) } }), t('fWaterAutoHint')),
|
|
6950
|
+
field(locale === 'zh' ? '子代理痕迹回收' : 'Subagent trace recycle', h('input', { type: 'checkbox', checked: cfg.subagentGcEnabled !== false, onChange: function (e) { set('subagentGcEnabled', e.target.checked) } }),
|
|
6951
|
+
locale === 'zh' ? '本插件的一次性子代理(自动沉淀 / 时段总结 / 问候 / 蒸馏)每跑一次都会在 ~/.dsh/sessions 留下一个持久化会话;数量上千后会话列表会明显变慢。开启后任务一结束就把这些痕迹移入 ~/.dsh/subagent-gc-backup 备份(可回滚),不影响子代理结果。默认开。' : 'Every one-shot subagent this plugin spawns (auto-consolidation, scheduled summary, greeting, distillation) leaves a persisted session under ~/.dsh/sessions; hundreds of them slow the session list down. When on, each trace is moved into ~/.dsh/subagent-gc-backup right after the task ends (reversible), never affecting the subagent result. On by default.'),
|
|
6952
|
+
field(locale === 'zh' ? '兜底回收保留天数' : 'Fallback recycle keep days', h('input', { 'data-dam-input': '', type: 'number', min: 0, value: cfg.subagentGcKeepDays === undefined ? 3 : cfg.subagentGcKeepDays, onChange: function (e) { set('subagentGcKeepDays', Number(e.target.value) || 0) } }),
|
|
6953
|
+
locale === 'zh' ? '每天巡检一次,回收超过该天数仍残留的痕迹(比如任务异常中断没删掉的);0 = 不按时间,只靠任务结束即删。' : 'A daily sweep recycles traces older than this many days (e.g. leftovers from interrupted tasks); 0 = rely on the end-of-task recycle only.'),
|
|
6954
|
+
]),
|
|
6955
|
+
section('auto', sectionLabels.auto, [
|
|
6956
|
+
// 欢迎向导:开关(首启自动播放)+ 立即重看按钮(闭包内直调 openDialog——同一作用域,点击立即弹;
|
|
6957
|
+
// 不走 window 全局入口,避免多实例时序导致"点了没反应要刷新")+ 查看更新日志(走 update 弹窗,
|
|
6958
|
+
// 带 Logo 开场动画;versions 取 CHANGELOG 最新一条)
|
|
6959
|
+
field(t('fWelcomeTour'), h('div', { 'data-dam-row': '' },
|
|
6960
|
+
h('input', { type: 'checkbox', checked: cfg.welcomeTourEnabled !== false, onChange: function (e) { set('welcomeTourEnabled', e.target.checked) } }),
|
|
6961
|
+
h('button', { 'data-dam-btn': '', onClick: function () { try { openDialog({ kind: 'welcomeTour' }) } catch (eTour) {} } }, t('tourReplay')),
|
|
6962
|
+
h('button', { 'data-dam-btn': '', onClick: function () {
|
|
6963
|
+
try {
|
|
6964
|
+
var keys = Object.keys(CHANGELOG)
|
|
6965
|
+
if (!keys.length) return
|
|
6966
|
+
var latest = keys.sort(cmpVersion)[keys.length - 1]
|
|
6967
|
+
openDialog({ kind: 'update', versions: [{ version: latest, items: CHANGELOG[latest] }], currentVersion: latest })
|
|
6968
|
+
} catch (eLog) {}
|
|
6969
|
+
} }, locale === 'zh' ? '查看更新日志' : 'View changelog')), t('fWelcomeTourHint')),
|
|
6970
|
+
field(t('fAutoPopup'), h('input', { type: 'checkbox', checked: cfg.autoPopupEnabled !== false, onChange: function (e) { set('autoPopupEnabled', e.target.checked) } }), t('fAutoPopupHint')),
|
|
6971
|
+
field(t('fUnattended'), h('input', { type: 'checkbox', checked: !!cfg.unattendedMode, onChange: function (e) { set('unattendedMode', e.target.checked) } }), t('fUnattendedHint')),
|
|
6972
|
+
field(t('fUnattendedAuto'), h('input', { type: 'checkbox', checked: !!cfg.unattendedAuto, onChange: function (e) { set('unattendedAuto', e.target.checked) } }), t('fUnattendedAutoHint')),
|
|
6973
|
+
field(t('fAway'), h('input', { 'data-dam-input': '', type: 'number', min: 0, value: (Number(cfg.awayMinutes) === 0 ? 0 : (cfg.awayMinutes || 60)), onChange: function (e) { var v = Number(e.target.value); set('awayMinutes', Number.isFinite(v) && v >= 0 ? v : 60) } }), t('fAwayHint')),
|
|
6974
|
+
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')),
|
|
6975
|
+
field(t('fConsSchedule'), h('input', { type: 'checkbox', checked: cfg.consolidateScheduleEnabled !== false, onChange: function (e) { set('consolidateScheduleEnabled', e.target.checked) } }), t('fConsScheduleHint')),
|
|
6976
|
+
field(t('fConsScheduleTime'), h('input', { 'data-dam-input': '', placeholder: '09:30', value: cfg.consolidateScheduleTime === undefined ? '09:30' : cfg.consolidateScheduleTime, onChange: function (e) { set('consolidateScheduleTime', String(e.target.value || '').trim()) } }), t('fConsScheduleTimeHint')),
|
|
6977
|
+
field(t('fConsScheduleDays'), h('input', { 'data-dam-input': '', type: 'number', min: 1, max: 60, value: cfg.consolidateScheduleDays === undefined ? 7 : cfg.consolidateScheduleDays, onChange: function (e) { set('consolidateScheduleDays', Number(e.target.value) || 7) } }), t('fConsScheduleDaysHint')),
|
|
6978
|
+
field(t('fMaintSchedule'), h('input', { type: 'checkbox', checked: cfg.maintainScheduleEnabled !== false, onChange: function (e) { set('maintainScheduleEnabled', e.target.checked) } }), t('fMaintScheduleHint')),
|
|
6979
|
+
field(t('fMaintScheduleTime'), h('input', { 'data-dam-input': '', placeholder: '10:00', value: cfg.maintainScheduleTime === undefined ? '10:00' : cfg.maintainScheduleTime, onChange: function (e) { set('maintainScheduleTime', String(e.target.value || '').trim()) } }), t('fMaintScheduleTimeHint')),
|
|
6980
|
+
]),
|
|
6981
|
+
section('store', sectionLabels.store, [
|
|
6408
6982
|
field(t('fUserDir'), h('input', { 'data-dam-input': '', value: cfg.userMemoryDir, onChange: function (e) { set('userMemoryDir', e.target.value) } }), t('fUserDirHint')),
|
|
6409
6983
|
field(t('fProjectDir'), h('input', { 'data-dam-input': '', value: cfg.projectMemoryDir, onChange: function (e) { set('projectMemoryDir', e.target.value) } }), t('fProjectDirHint')),
|
|
6410
6984
|
field(t('fMemoryRoot'), h('div', { 'data-dam-row': '', style: { flex: 1 } },
|