@a9i5k4/dsh-auto-memory 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -13
- package/README.zh-CN.md +30 -13
- package/docs/FRONTEND-CO-CREATION.md +191 -0
- package/docs/GM53-HOMEPAGE-PROMPT.md +323 -0
- package/docs/HANDBOOK.md +88 -52
- package/docs/HOMEPAGE-CONTENT-FOR-GM53.md +299 -0
- package/docs/PROMO-PROMPT-3.0.md +100 -0
- package/docs/USER-GUIDE.en.md +11 -11
- package/docs/USER-GUIDE.zh-CN.md +11 -11
- package/docs/WHITEPAPER.md +207 -0
- package/docs/screenshots/promo/promo-0-banner-v3.png +0 -0
- package/docs/screenshots/promo/promo-0-banner-v4.png +0 -0
- package/docs/screenshots/promo/promo-1b-auto-recall.png +0 -0
- package/lib/activation-host.js +69 -10
- package/lib/board-mode.js +1 -1
- package/lib/client.js +1697 -285
- package/lib/config-io.js +156 -0
- package/lib/context-bridge.js +3 -0
- package/lib/context-host.js +23 -10
- package/lib/degrade.js +385 -0
- package/lib/dsh-home.js +143 -0
- package/lib/episodic-store.js +142 -18
- package/lib/evidence-store.js +8 -1
- package/lib/fact-store.js +484 -43
- package/lib/hub-io.js +217 -0
- package/lib/index-sync.js +13 -1
- package/lib/index.js +1730 -202
- package/lib/intent-clean-safe.js +258 -40
- package/lib/l0-extract.js +231 -16
- package/lib/m4-corpus.js +8 -2
- package/lib/m7-index-sync-host.js +8 -1
- package/lib/memory-envelope.js +6 -1
- package/lib/memory-hub.js +164 -17
- package/lib/memory-index.js +4 -2
- package/lib/note-status-apply.js +118 -0
- package/lib/note-status.js +204 -0
- package/lib/procedure-store.js +333 -31
- package/lib/procedure-switch.js +38 -0
- package/lib/python-sidecar-client.js +314 -11
- package/lib/recall-fusion.js +83 -12
- package/lib/rules-edit.js +159 -0
- package/lib/semantic-decide.js +41 -8
- package/lib/semantic-js.js +51 -6
- package/lib/shadow-host.js +3 -5
- package/lib/skill-export-host.js +153 -0
- package/lib/skill-export.js +239 -0
- package/lib/storage-manage.js +6 -0
- package/lib/temporal-parse.js +191 -159
- package/lib/tier0-catalog.js +45 -3
- package/lib/wb-contract.js +198 -2
- package/lib/wb-sidecar.js +54 -3
- package/package.json +6 -2
- package/docs/internal/ACCEPT-35-LIVE.md +0 -143
- package/docs/internal/ACCEPTANCE-20260914.md +0 -90
- package/docs/internal/ARCH-REVIEW-BRIEF.md +0 -411
- package/docs/internal/ARCH-REVIEW-REQUEST.md +0 -201
- package/docs/internal/ARCH-REVIEW-ROUND2.md +0 -169
- package/docs/internal/ARCH-REVIEW-ROUND3.md +0 -206
- package/docs/internal/ART-DIRECTION-WIREFRAME.md +0 -181
- package/docs/internal/AUDIT-WB-GRAPH-FULL-20260916.md +0 -314
- package/docs/internal/CONCURRENCY-INVESTIGATION-20260917.md +0 -192
- package/docs/internal/CROSS-SESSION-SEARCH-PATH-DECISION.md +0 -72
- package/docs/internal/CROSS-SESSION-SEARCH-RESEARCH.md +0 -131
- package/docs/internal/CUA-VISION-FIX-NOTES.md +0 -78
- package/docs/internal/DECISIONS-20260914-SESSION.md +0 -269
- package/docs/internal/DESIGN-OVERHAUL-PRE-RESEARCH.md +0 -292
- package/docs/internal/DESIGN-P1-STATE-COMMIT-20260915.md +0 -219
- package/docs/internal/DIRECTION-CHECK-WB-GRAPH-20260916.md +0 -132
- package/docs/internal/FEEDBACK-TO-DSHAPI-RELAY.md +0 -13
- package/docs/internal/GH-DISCUSSION-5732-COMMENT.md +0 -74
- package/docs/internal/GPT-ACCEPTANCE-PROMPT-20260916.md +0 -352
- package/docs/internal/GPT-REVIEW-PROMPT.md +0 -216
- package/docs/internal/GROUP-DIGEST-SETUP.md +0 -62
- package/docs/internal/GROUP-LISTENER-SETUP.md +0 -49
- package/docs/internal/GROUP-WEBHOOK-SETUP.md +0 -93
- package/docs/internal/HANDOFF-TO-ZCODE.md +0 -168
- package/docs/internal/KICKOFF-P0.md +0 -254
- package/docs/internal/MASTER-PLAN-3.0.md +0 -411
- package/docs/internal/MEMORY-MUTATION-AND-INDEX-DESIGN.md +0 -85
- package/docs/internal/MERGE-CONFLICT-SCAN-20260914.md +0 -222
- package/docs/internal/NEXT-VERSION-TODO.md +0 -95
- package/docs/internal/OFFICIAL-DISCUSSION-DRAFT.md +0 -80
- package/docs/internal/PENDING-FIXES-20260916.md +0 -289
- package/docs/internal/RAG-KARPATHY-PROGRAM.md +0 -229
- package/docs/internal/RELEASE-PROCESS.md +0 -99
- package/docs/internal/REPORT-P0-NIGHTLY.md +0 -212
- package/docs/internal/REPORT-P5-ACCEPTANCE.md +0 -31
- package/docs/internal/REPORT-WB-GRAPH-NIGHTLY.md +0 -153
- package/docs/internal/REVIEW-WB-GRAPH-SELF.md +0 -81
- package/docs/internal/ROADMAP-20260917-WEEK.md +0 -305
- package/docs/internal/ROADMAP.md +0 -106
- package/docs/internal/RUN-P0-NIGHTLY.md +0 -227
- package/docs/internal/S10-CONSTRUCTION-HANDOFF-20260917.md +0 -175
- package/docs/internal/S10-GAPS-PLAIN-20260917.md +0 -125
- package/docs/internal/SEMANTIC-ARCHITECTURE-SPEC.md +0 -360
- package/docs/internal/SESSION-FILE-REPAIR-PROTOCOL.md +0 -90
- package/docs/internal/SUBAGENT-REPORT-ROUTING-PRE-RESEARCH.md +0 -261
- package/docs/internal/THREE-LAYER-CONTRACT.md +0 -210
- package/docs/internal/TODO-BACKLOG.md +0 -263
- package/docs/internal/TODO-GRAPH.html +0 -715
- package/docs/internal/TODO-GRAPH.html.bak-20260914-v2 +0 -493
- package/docs/internal/TODO-GRAPH.html.bak-20260915-alsfix +0 -710
- package/docs/internal/TODO-GRAPH.html.bak-20260915-p1 +0 -710
- package/docs/internal/TODO-GRAPH.html.bak-20260915-p6a-rev +0 -703
- package/docs/internal/TODO-GRAPH.html.bak-20260915-wshint +0 -710
- package/docs/internal/TODO-GRAPH.html.bak-20260916-batch +0 -715
- package/docs/internal/WB-FORMAT-CONVENTION.md +0 -112
- package/docs/internal/WB-GRAPH-DECISIONS-20260914.md +0 -71
- package/docs/internal/WB-GRAPH-INTEGRATION-PLAN.md +0 -386
- package/docs/internal/WB-GRAPH-RESEARCH-BRIEF.md +0 -118
- package/docs/internal/WB-GRAPH-RESEARCH-EXTERNAL.md +0 -228
- package/docs/internal/WB-GRAPH-RESEARCH-LOCAL.md +0 -190
- package/docs/internal/reviews/CLAIM-VERIFICATION-20260914.md +0 -56
- package/docs/internal/reviews/PLAN-gpt6astra-round2-20260914.md +0 -787
- package/docs/internal/reviews/REVIEW-gpt6astra-20260914.md +0 -112
- package/docs/internal/reviews/ROUND3-REVIEW-INTEGRATION-20260914.md +0 -230
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.
|
|
10
|
+
console.log('[dsh-auto-memory] client v3.1.0 fingerprint: promotion-override')
|
|
11
11
|
window.__ModuleLoader__.load({
|
|
12
12
|
id: '@a9i5k4/dsh-auto-memory',
|
|
13
13
|
factory: (require) => {
|
|
@@ -43,6 +43,21 @@ window.__ModuleLoader__.load({
|
|
|
43
43
|
try { pinned = localStorage.getItem(PIN_KEY) === '1' } catch (ePin) {}
|
|
44
44
|
// 几何状态:left/top/width/height,持久化到 localStorage(用户可拖动/缩放)
|
|
45
45
|
var GEOM_KEY = 'dsh-auto-memory.panel.geom'
|
|
46
|
+
// 承载面(2026-09-21 用户拍板 · 二次修正):'bottom-left'(默认,左下角浮层) | 'page'(会话页) | 'both'(两者共存)。
|
|
47
|
+
// ★修正原因(用户实测反馈原话):「它现在是浮在整个页面上方的,而不是和对话轨迹、上下文、白板看板在一起,作为一个单独的一页」。
|
|
48
|
+
// 根因:首版把「顶部」做成 shell.overlay 覆盖条 —— 宿主只给插件 4 个槽位(sidebar/main/rightbar/shell.overlay),
|
|
49
|
+
// 浮层永远只能"盖"在界面上,做不出"并列的一页";要成为并列页必须走 conversation.view
|
|
50
|
+
// (官方「对话轨迹」dsh-client-ui-trajectory 与本插件「白板看板」都用它注册)。
|
|
51
|
+
// 两种承载面共享同一份内容/页签/数据(见下面的 panelTab 共享状态);本机偏好,仅 localStorage。
|
|
52
|
+
var POS_KEY = 'dsh-auto-memory.panel.pos'
|
|
53
|
+
var PANEL_POS_VALUES = { 'bottom-left': 1, page: 1, both: 1 }
|
|
54
|
+
var panelPos = 'both'
|
|
55
|
+
try {
|
|
56
|
+
var savedPos = localStorage.getItem(POS_KEY)
|
|
57
|
+
// 兼容上一版:旧值 'top'(覆盖条形态)已废弃 ⇒ 自动迁移为 'page'(会话页形态),用户不必手动重选
|
|
58
|
+
if (savedPos === 'top') savedPos = 'page'
|
|
59
|
+
if (PANEL_POS_VALUES[savedPos]) panelPos = savedPos
|
|
60
|
+
} catch (ePos) {}
|
|
46
61
|
var DEFAULT_W = 440
|
|
47
62
|
var DEFAULT_H = 560
|
|
48
63
|
var DEFAULT_GAP = 16
|
|
@@ -117,6 +132,16 @@ window.__ModuleLoader__.load({
|
|
|
117
132
|
return null
|
|
118
133
|
}
|
|
119
134
|
function emit() { listeners.forEach(function (fn) { try { fn() } catch (e) {} }) }
|
|
135
|
+
// 共享页签状态(2026-09-21):浮层与「记忆」会话页读同一个模块级变量 ⇒ 两处切页互相同步
|
|
136
|
+
// (用户诉求原话:「两者共存…要保证同步」)。不用两份 useState,从根上杜绝状态漂移。
|
|
137
|
+
// 两个承载面都订阅 emit()(走 useTick),所以 setPanelTab 只要 emit 就能让两处同时重渲染。
|
|
138
|
+
var panelTab = 'overview'
|
|
139
|
+
function setPanelTab(v) { if (panelTab !== v) { panelTab = v; emit() } }
|
|
140
|
+
// 承载面切换钩子(2026-09-21):conversation.view 的注册/注销在 apply 作用域内(refreshSurfaces),
|
|
141
|
+
// 而 setPanelPos 在本模块作用域 ⇒ 用一个模块级钩子搭桥:切到「会话页/两者」时当场把页签加上,
|
|
142
|
+
// 切回「左下角浮层」时把它摘掉(不必等重启或重进会话)。
|
|
143
|
+
var surfacesRefreshHook = null
|
|
144
|
+
function setSurfacesRefreshHook(fn) { surfacesRefreshHook = fn }
|
|
120
145
|
var controller = {
|
|
121
146
|
isOpen: function () { return panelOpen },
|
|
122
147
|
isClosing: function () { return panelClosing },
|
|
@@ -155,6 +180,22 @@ window.__ModuleLoader__.load({
|
|
|
155
180
|
try { localStorage.setItem(PIN_KEY, pinned ? '1' : '0') } catch (e) {}
|
|
156
181
|
emit()
|
|
157
182
|
},
|
|
183
|
+
// 承载面(2026-09-21):左下角浮层 / 会话页 / 两者共存。设置页改动立即生效(即时回显)。
|
|
184
|
+
panelPos: function () { return panelPos },
|
|
185
|
+
setPanelPos: function (v) {
|
|
186
|
+
panelPos = PANEL_POS_VALUES[v] ? v : 'both'
|
|
187
|
+
try { localStorage.setItem(POS_KEY, panelPos) } catch (e) {}
|
|
188
|
+
// 承载面切换要即时增删「记忆」会话页签(见 setSurfacesRefreshHook 的说明)
|
|
189
|
+
try { if (surfacesRefreshHook) surfacesRefreshHook() } catch (eRf) {}
|
|
190
|
+
// 即时回显(2026-09-21 用户实测:「切到两者共存后上面什么都没出现」):
|
|
191
|
+
// 位置改在设置页里,而面板若关着,整个 MemoryPanel 渲染 null ⇒ 用户看不到任何变化。
|
|
192
|
+
// 故切档时面板若关着就自动打开,让新位置当场可见(切回旧档同样打开,保证每次切换都可感知)。
|
|
193
|
+
if (!panelOpen) controller.open()
|
|
194
|
+
else emit()
|
|
195
|
+
},
|
|
196
|
+
// 共享页签(浮层 ↔ 会话页)
|
|
197
|
+
panelTab: function () { return panelTab },
|
|
198
|
+
setPanelTab: setPanelTab,
|
|
158
199
|
}
|
|
159
200
|
|
|
160
201
|
// ───────────────────────── 国际化 ─────────────────────────
|
|
@@ -216,6 +257,10 @@ window.__ModuleLoader__.load({
|
|
|
216
257
|
kvAvail: '可用', kvUnavail: '不可用', kvDup: '项目笔记重复标题', kvDupCount: ' 个',
|
|
217
258
|
kvMem: '记忆文件', kvMemUser: '用户', kvMemNotes: '笔记', kvMemLog: '日志', kvMemMissing: '缺失',
|
|
218
259
|
kvWs: '当前工作区', kvWsUnknown: '(未取到)', kvApi: 'API 连通', unknown: '(未知)',
|
|
260
|
+
// ★P0-2/P0-3(2026-09-22):把后端早已算好、前端零消费者的两组可观测性接到界面上。
|
|
261
|
+
dbgHubIo: '三层记忆落盘', dbgPrune: '事实淘汰(保留上限)',
|
|
262
|
+
kvPyTitle: 'Python 语义侧车', kvPyOff: '未启用(JS 端语义不受影响)', kvPyAlive: '运行中',
|
|
263
|
+
kvPyWatchdog: '看门狗击杀 / 重生', kvPyStderr: '最近 stderr 尾部', kvPyStderrTrunc: '(已截断)',
|
|
219
264
|
updateTitle: 'dsh-auto-memory 已更新', updateSub: '本次更新内容:', gotIt: '知道了', noticeOpen: '了解更多',
|
|
220
265
|
refresh: '刷新', refreshing: '刷新中…', close: '关闭', dragMove: '拖动移动', dragResize: '拖动调整大小', resetPos: '恢复默认位置', pinPanel: '钉住面板(点桌面其他地方不收起)', unpinPanel: '取消钉住(点面板外自动收起)',
|
|
221
266
|
generated: '已生成', failed: '失败: ',
|
|
@@ -237,6 +282,44 @@ window.__ModuleLoader__.load({
|
|
|
237
282
|
empty: '(空)', back: '← 返回', view: '查看',
|
|
238
283
|
clickDateViewLog: '点击日期查看当日日志(append-only):',
|
|
239
284
|
userMemoryBlock: '用户记忆(用户画像 · 跨项目):', userMemoryEmpty: '(空)', userMemoryTruncated: '(内容过长,已截断显示)',
|
|
285
|
+
// ★R7:硬性约束编辑面板文案
|
|
286
|
+
// ★R1–R6:审批界面可读性文案
|
|
287
|
+
hubStageObserved: '刚观察到',
|
|
288
|
+
hubStageCandidate: '够格待审',
|
|
289
|
+
hubStageValidated: '已通过验证',
|
|
290
|
+
hubStageActive: '已启用',
|
|
291
|
+
hubStageDeprecated: '已弃用',
|
|
292
|
+
hubWhyTitle: '为什么还不能晋升:',
|
|
293
|
+
hubWhyCanPromote: '条件已满足,可以晋升。',
|
|
294
|
+
hubWhyDiversity: function (d, need) { return '需要 ' + need + ' 个不同会话都用过它,目前只有 ' + d + ' 个 —— 换个场景再用一次它就能推进。' },
|
|
295
|
+
hubWhySuccess: function (n, need) { return '需要成功 ' + need + ' 次,目前 ' + n + ' 次 —— 再成功用一次它会自动变好。' },
|
|
296
|
+
hubWhyCorrectionRate: function (r, cap) { return '它的纠正率是 ' + r + ',超过了上限 ' + cap + ' —— 说明这套流程本身有问题,需要先改掉。' },
|
|
297
|
+
hubWhyHasCorrection: function (n) { return '它被纠正过 ' + n + ' 次 —— 有纠正记录说明这流程是错的,不能固化。' },
|
|
298
|
+
hubWhyNoCriteria: '它没写「怎么算成功」—— 没有验收标准的流程不算技能。',
|
|
299
|
+
hubWhyHighRisk: '它是高风险操作,需要你人工确认才会固化 —— 点下面这个「批准(人工)」即可。',
|
|
300
|
+
hubWhyObservationOnly: '它只是「看到过一件事」的线索,不是真正的流程 —— 不会自动晋升。',
|
|
301
|
+
// ★C(2026-09-22):三态说清 —— 「没有按钮」必须自己解释清楚,而不是让用户猜。
|
|
302
|
+
hubWhyStructural: '这道门是结构性的(内容本身不构成一条技能),人工也不能越过;补齐内容才会重新判定。',
|
|
303
|
+
hubWhyOverridable: '以上只是「证据还没攒够」的统计门,不是内容不合格 —— 你可以点右侧按钮人工越过。',
|
|
304
|
+
hubForcePromote: '强制晋升(人工)',
|
|
305
|
+
hubForcePromoteTitle: '人工越过跨会话数/成功次数两道统计门。结构门不会越过,且会在条目上留下「人工授权」痕迹。',
|
|
306
|
+
hubEvSeen: '见过', hubEvSuccess: '成功', hubEvSessions: '跨会话', hubEvReuse: '被复用', hubEvCorrection: '被纠正',
|
|
307
|
+
hubEvLine: function (ev) { return '见过 ' + (ev.seen || 0) + ' 次 · 成功 ' + (ev.success || 0) + ' 次 · 在 ' + (ev.sessions || 0) + ' 个会话里用过' + ((ev.correction || 0) ? ' · 被纠正过 ' + ev.correction + ' 次' : '') },
|
|
308
|
+
hubInjectPreview: '晋升后会注入这些(点开看)',
|
|
309
|
+
hubInjectCriteria: '验收标准:',
|
|
310
|
+
hubNoSteps: '(这条没有可用步骤——源头是机械截断,不是真流程)',
|
|
311
|
+
rulesTitle: '硬性约束(每轮必注入)',
|
|
312
|
+
rulesIntro: '这些条目每一轮都会原封不动发给我,优先级高于其它内容。它不走检索、不会自动淘汰,所以过时的、或 AI 自己加错的,都要你手动改。',
|
|
313
|
+
rulesPath: '来源文件:',
|
|
314
|
+
rulesEmpty: '(暂无条目)',
|
|
315
|
+
rulesSourceUser: '你写的',
|
|
316
|
+
rulesSourceAi: 'AI 加的',
|
|
317
|
+
rulesEdit: '编辑', rulesSave: '保存', rulesCancel: '取消', rulesDelete: '删除',
|
|
318
|
+
rulesConfirmDelete: '确认删除这条约束?\n\n删除后无法撤销——它下一轮就不再注入了。',
|
|
319
|
+
rulesAddHint: '新增一条约束:',
|
|
320
|
+
rulesAddPlaceholder: '例:未经我确认,不要自动修改生产环境的文件。',
|
|
321
|
+
rulesAdd: '添加',
|
|
322
|
+
rulesPreviewHint: '预览:下一轮注入时会变成这样',
|
|
240
323
|
appended: '已追加', notesPathLabel: '项目笔记: ',
|
|
241
324
|
notesPlaceholder: '想追加到项目笔记的内容…(保存时自动带日期标题)',
|
|
242
325
|
saving: '保存中…', append: '追加',
|
|
@@ -260,13 +343,15 @@ window.__ModuleLoader__.load({
|
|
|
260
343
|
saved: '已保存',
|
|
261
344
|
settingsHeader: '记忆存储与行为设置(保存到 DSH 主目录 dsh-auto-memory.json):',
|
|
262
345
|
fFontSize: '界面字号', fFontSizeHint: '记忆面板文字大小(立即生效,仅本机)', fsSm: '小', fsMd: '标准', fsLg: '大', fsXl: '特大',
|
|
346
|
+
fPanelPos: '记忆承载面', fPanelPosHint: '记忆内容的呈现方式:左下角浮层(可拖动缩放)/ 会话页(与「对话轨迹」「上下文」并列的一页,在会话页顶栏切换)/ 两者共存。两种承载面共享同一份内容与页签,实时同步;仅本机生效。',
|
|
347
|
+
posBottomLeft: '左下角(浮层)', posPage: '会话页(与对话轨迹并列)', posBoth: '两者共存',
|
|
263
348
|
fUserDir: '用户记忆目录', fUserDirHint: '跨项目规则存放处,支持 ~ 开头;需有文件写权限。',
|
|
264
349
|
fProjectDir: '项目记忆目录', fProjectDirHint: '相对各工作区的目录名(默认 .dsh-memory)。',
|
|
265
350
|
fInject: '注入记忆上下文', fInjectHint: '每次组装提示词时自动注入 <memory_system> 块。',
|
|
266
351
|
fBudget: '注入预算(字符)', fBudgetHint: '记忆块总预算,超出部分截断。默认 1600(≈400-600 token/轮);活跃会话每轮都注入这段背景,调低更省 token,调高保留更多记忆。',
|
|
267
352
|
fExclude: '排除来源(每行一条)', fExcludeHint: '这些来源不再进注入,避免坏记忆反复灌入把结果一路带偏。四种写法:记忆 id(mem_xxx)、整层(log/whiteboard/project/user/reflection)、目录前缀(以 / 或 \\ 结尾)、精确文件路径。与 supersede 互补:supersede 管"被新版替代",这里管"整条来源不可信"。被挡下的条数会在注入里以 [降级] 如实标注。',
|
|
268
|
-
fNoteCap: '项目笔记容量上限(字符)', fNoteCapHint: '项目笔记 MEMORY.md 的容量上限,默认
|
|
269
|
-
fUserCap: '用户级记忆容量上限(字符)', fUserCapHint: '用户级 MEMORY.md(跨项目规则)的容量上限,默认
|
|
353
|
+
fNoteCap: '项目笔记容量上限(字符)', fNoteCapHint: '项目笔记 MEMORY.md 的容量上限,默认 24000。超出时自动整理:先把较早内容交给模型折叠成要点,失败则退回整条归档(原文进 archive,不丢)——新记忆不会被堵在外面。注意与上面的「注入预算」不是一回事:这个管文件本体大小,注入预算管每轮往上下文塞多少摘要。',
|
|
354
|
+
fUserCap: '用户级记忆容量上限(字符)', fUserCapHint: '用户级 MEMORY.md(跨项目规则)的容量上限,默认 24000。整理方式与项目笔记相同(先折叠成要点,必要时整条归档)。',
|
|
270
355
|
fSnapGap: '快照最小间隔(轮)', fSnapGapHint: '动态记忆快照内容变化后,至少隔 N 轮才重新注入,避免每轮日志微变都追加快照导致历史膨胀。默认 5;0=每轮都尝试(仍受内容变化约束)。',
|
|
271
356
|
fReinjectOnCompact: '压缩后立即重注入快照', fReinjectOnCompactHint: '上下文被压缩/截断(通常伴随 contextVersion 重置)后,快照会被清掉;开启后强制立即重注入一次,确保记忆背景重建。默认开。',
|
|
272
357
|
fPromptCustom: '自定义记忆注入 prompt', fPromptCustomHint: '可覆盖各层提示文案(小众功能)。支持占位符 {date} {ws} {budget} {n}。改坏了可一键恢复默认。',
|
|
@@ -292,6 +377,14 @@ window.__ModuleLoader__.load({
|
|
|
292
377
|
fWaterAuto: '水位自动骨架账本', fWaterAutoHint: '越阈时自动写一篇系统骨架账本(每会话一次),防止模型忽视建议时交接材料缺失。',
|
|
293
378
|
fWaterThreshold: '水位建议阈值', fWaterThresholdHint: '水位比例 = 上下文实占 ÷ 官方声明窗口(不再扣预留输出);超过该值(0.1-1.5)即触发交接建议/骨架账本;默认 0.75——官方自动压缩阈值是 80%,必须留出余量才来得及走完交接。',
|
|
294
379
|
fHandoffPlan: '白板注入预算(字符)', fHandoffPlanHint: 'PLAN.md 全貌注入动态快照的硬截断预算;全文可经 memory_read 或面板白板页查看。默认 1200。',
|
|
380
|
+
fProcInject: '流程记忆注入(总闸)', fProcInjectHint: '★流程记忆(技能固化)的总闸:关闭后既不写入流程记忆,也不把它注入提示词。默认开。注意:设置页此前的旧键「技能固化与晋升」宿主已不再读取,要改请改这一项。',
|
|
381
|
+
fFactRetention: '事实条数上限', fFactRetentionHint: '事实层最多保留多少条,超出时自动淘汰:先淘汰已撤销的,再按写入时间从旧到新,重要项(置顶 / 用户级 / 显式写入 / 高置信度)最后淘汰。默认 1000。',
|
|
382
|
+
fTier0Catalog: '目录层(Tier-0 摘要)', fTier0CatalogHint: '开启时每轮注入一段「记忆目录」摘要(条目名 + 一句话结论),让模型先看到全局再按需下探;关闭则完全不注入目录层,省 token。默认开。',
|
|
383
|
+
fTier0Max: '目录层 token 上限', fTier0MaxHint: '目录层单次注入的 token 硬上限(与「注入预算 × 目录层占比」取小生效)。默认 400。需要装下更多语料时提高这里,而不是把注入预算整体推高。',
|
|
384
|
+
fRulesLayering: '规则段分层', fRulesLayeringHint: '开启后规则类记忆单独成段、每轮在场且不参与裁剪(旧行为是把规则混进记忆流一起裁)。关闭 = 完全回到旧行为。默认开。',
|
|
385
|
+
fCriteriaGate: '账本判据门', fCriteriaGateHint: '开启时白板/账本小节必须满足判据(H1-H4 / S1-S4)才被采纳,防止把思考过程当结论固化。关闭只退掉这道质量门,骨架 fail-soft 仍无条件生效。默认开。',
|
|
386
|
+
fWsDiscover: '工作区发现上限', fWsDiscoverHint: '扫描本机会话时最多识别多少个工作区(按最近会话时间降序取前 N 个)。默认 200;机器上历史工作区很多时可调小以缩短扫描时间。',
|
|
387
|
+
fMemFileIndex: '记忆文件索引快照', fMemFileIndexHint: '开启后额外维护一份只读的记忆文件索引快照(供外部查询),有少量 IO 开销;默认关,关时零额外开销。',
|
|
295
388
|
fHandoffLedger: '账本注入预算(字符)', fHandoffLedgerHint: '最新一篇交接账本注入动态快照的硬截断预算。默认 800。',
|
|
296
389
|
fConsSchedule: '定时做梦式固化', fConsScheduleHint: '每天到点自动读最近日志发散提炼长期要点,写入项目笔记/用户级记忆(等价 memory_consolidate)。',
|
|
297
390
|
fConsScheduleTime: '固化触发时间', fConsScheduleTimeHint: 'HH:MM,插件日界内每天一次;默认 09:30。命中时刻需宿主在线。',
|
|
@@ -370,6 +463,9 @@ window.__ModuleLoader__.load({
|
|
|
370
463
|
kvAvail: 'available', kvUnavail: 'unavailable', kvDup: 'Duplicate note headings', kvDupCount: '',
|
|
371
464
|
kvMem: 'Memory files', kvMemUser: 'user', kvMemNotes: 'notes', kvMemLog: 'log', kvMemMissing: 'missing',
|
|
372
465
|
kvWs: 'Current workspace', kvWsUnknown: '(unknown)', kvApi: 'API probes', unknown: '(unknown)',
|
|
466
|
+
dbgHubIo: 'Layer-3 memory persistence', dbgPrune: 'Fact eviction (retention cap)',
|
|
467
|
+
kvPyTitle: 'Python sidecar', kvPyOff: 'disabled (JS-side semantics unaffected)', kvPyAlive: 'running',
|
|
468
|
+
kvPyWatchdog: 'Watchdog kills / respawns', kvPyStderr: 'Last stderr tail', kvPyStderrTrunc: ' (truncated)',
|
|
373
469
|
updateTitle: 'dsh-auto-memory Updated', updateSub: 'What\'s new in this update:', gotIt: 'Got it', noticeOpen: 'Learn more',
|
|
374
470
|
refresh: 'Refresh', refreshing: 'Refreshing…', close: 'Close', dragMove: 'Drag to move', dragResize: 'Drag to resize', resetPos: 'Reset position', pinPanel: 'Pin panel (stays open when clicking elsewhere)', unpinPanel: 'Unpin (auto-hide on outside click)',
|
|
375
471
|
generated: 'Generated', failed: 'Failed: ',
|
|
@@ -391,6 +487,41 @@ window.__ModuleLoader__.load({
|
|
|
391
487
|
empty: '(empty)', back: '← Back', view: 'View',
|
|
392
488
|
clickDateViewLog: 'Click a date to view the daily log (append-only):',
|
|
393
489
|
userMemoryBlock: 'User memory (profile · cross-project):', userMemoryEmpty: '(empty)', userMemoryTruncated: '(content truncated for display)',
|
|
490
|
+
hubStageObserved: 'observed',
|
|
491
|
+
hubStageCandidate: 'candidate',
|
|
492
|
+
hubStageValidated: 'validated',
|
|
493
|
+
hubStageActive: 'active',
|
|
494
|
+
hubStageDeprecated: 'deprecated',
|
|
495
|
+
hubWhyTitle: 'Why it cannot be promoted yet: ',
|
|
496
|
+
hubWhyCanPromote: 'All conditions met — ready to promote.',
|
|
497
|
+
hubWhyDiversity: function (d, need) { return 'Needs ' + need + ' distinct sessions, currently ' + d + '.' },
|
|
498
|
+
hubWhySuccess: function (n, need) { return 'Needs ' + need + ' successes, currently ' + n + '.' },
|
|
499
|
+
hubWhyCorrectionRate: function (r, cap) { return 'Correction rate ' + r + ' exceeds cap ' + cap + '.' },
|
|
500
|
+
hubWhyHasCorrection: function (n) { return 'Has ' + n + ' correction(s) — a corrected flow must not be crystallized.' },
|
|
501
|
+
hubWhyNoCriteria: 'No success criteria — a flow without acceptance criteria is not a skill.',
|
|
502
|
+
hubWhyHighRisk: 'High risk — it needs your manual approval. Click "approve (manual)" below to crystallize it.',
|
|
503
|
+
hubWhyObservationOnly: 'This is an observation, not a real flow — it never auto-promotes.',
|
|
504
|
+
hubWhyStructural: 'This gate is structural (the entry is not a skill yet) — a human cannot override it either. Fill in the content and it gets re-evaluated.',
|
|
505
|
+
hubWhyOverridable: 'The above is only a statistical gate (not enough evidence yet), not a content failure — you can override it with the button on the right.',
|
|
506
|
+
hubForcePromote: 'Force promote (manual)',
|
|
507
|
+
hubForcePromoteTitle: 'Manually bypass the cross-session / success-count statistical gates. Structural gates are NOT bypassed, and a "manual authorization" trace is left on the entry.',
|
|
508
|
+
hubEvSeen: 'seen', hubEvSuccess: 'success', hubEvSessions: 'sessions', hubEvReuse: 'reused', hubEvCorrection: 'corrected',
|
|
509
|
+
hubEvLine: function (ev) { return 'seen ' + (ev.seen || 0) + ' · success ' + (ev.success || 0) + ' · used in ' + (ev.sessions || 0) + ' session(s)' + ((ev.correction || 0) ? ' · corrected ' + ev.correction + 'x' : '') },
|
|
510
|
+
hubInjectPreview: 'This is what gets injected once promoted',
|
|
511
|
+
hubInjectCriteria: 'Acceptance criteria: ',
|
|
512
|
+
hubNoSteps: '(no usable steps — mechanically truncated, not a real flow)',
|
|
513
|
+
rulesTitle: 'Hard constraints (injected every turn)',
|
|
514
|
+
rulesIntro: 'These entries are sent to the model verbatim on every turn and outrank other content. They bypass retrieval and are never auto-retired, so outdated or AI-added ones must be edited by hand.',
|
|
515
|
+
rulesPath: 'Source file: ',
|
|
516
|
+
rulesEmpty: '(none)',
|
|
517
|
+
rulesSourceUser: 'yours',
|
|
518
|
+
rulesSourceAi: 'added by AI',
|
|
519
|
+
rulesEdit: 'Edit', rulesSave: 'Save', rulesCancel: 'Cancel', rulesDelete: 'Delete',
|
|
520
|
+
rulesConfirmDelete: 'Delete this constraint?\n\nThis cannot be undone — it will stop being injected next turn.',
|
|
521
|
+
rulesAddHint: 'Add a constraint:',
|
|
522
|
+
rulesAddPlaceholder: 'e.g. Do not modify production files without my confirmation.',
|
|
523
|
+
rulesAdd: 'Add',
|
|
524
|
+
rulesPreviewHint: 'Preview: how it will be injected next turn',
|
|
394
525
|
appended: 'Appended', notesPathLabel: 'Project notes: ',
|
|
395
526
|
notesPlaceholder: 'Content to append to project notes…(date heading added on save)',
|
|
396
527
|
saving: 'Saving…', append: 'Append',
|
|
@@ -414,13 +545,15 @@ window.__ModuleLoader__.load({
|
|
|
414
545
|
saved: 'Saved',
|
|
415
546
|
settingsHeader: 'Memory storage & behavior (saved to ~/.dsh/dsh-auto-memory.json):',
|
|
416
547
|
fFontSize: 'Panel font size', fFontSizeHint: 'Memory panel text size (applies immediately, this device only)', fsSm: 'Small', fsMd: 'Normal', fsLg: 'Large', fsXl: 'Extra large',
|
|
548
|
+
fPanelPos: 'Memory surface', fPanelPosHint: 'How memory content is presented: bottom-left floating card (draggable/resizable), a session page (a tab alongside "Trajectory"/"Context" in the conversation header), or both. Both surfaces share the same content and tabs, live-synced; this device only.',
|
|
549
|
+
posBottomLeft: 'Bottom-left (floating)', posPage: 'Session page (alongside Trajectory)', posBoth: 'Both, side by side',
|
|
417
550
|
fUserDir: 'User memory dir', fUserDirHint: 'Cross-project rules; supports ~ prefix; needs write permission.',
|
|
418
551
|
fProjectDir: 'Project memory dir', fProjectDirHint: 'Directory name relative to each workspace (default .dsh-memory).',
|
|
419
552
|
fInject: 'Inject memory context', fInjectHint: 'Auto-inject <memory_system> block into every prompt.',
|
|
420
553
|
fBudget: 'Injection budget (chars)', fBudgetHint: 'Total budget for the memory block; excess is truncated. Default 1600 (~400-600 tokens/turn); this background is injected every turn, so lower = fewer tokens, higher = more memory retained.',
|
|
421
554
|
fExclude: 'Excluded sources (one per line)', fExcludeHint: 'These sources never enter injection again, so a bad memory cannot keep steering the result. Four forms: memory id (mem_xxx), whole layer (log/whiteboard/project/user/reflection), directory prefix (ending in / or \\), exact file path. Complements supersede: supersede covers "replaced by a newer version", this covers "this source is untrustworthy". Blocked counts are reported as a [降级] line, never silently.',
|
|
422
|
-
fNoteCap: 'Project note capacity (chars)', fNoteCapHint: 'Capacity cap for the project MEMORY.md, default
|
|
423
|
-
fUserCap: 'User memory capacity (chars)', fUserCapHint: 'Capacity cap for the user-level MEMORY.md (cross-project rules), default
|
|
555
|
+
fNoteCap: 'Project note capacity (chars)', fNoteCapHint: 'Capacity cap for the project MEMORY.md, default 24000. When exceeded the framework auto-consolidates: older content is first folded into bullet points by the model, falling back to whole-record archiving (originals go to archive, never lost) — new memories are never blocked. This is NOT the injection budget above: this caps the file itself, the injection budget caps the digest added to each turn.',
|
|
556
|
+
fUserCap: 'User memory capacity (chars)', fUserCapHint: 'Capacity cap for the user-level MEMORY.md (cross-project rules), default 24000. Same consolidation behaviour (fold into points first, whole-record archive as fallback).',
|
|
424
557
|
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).',
|
|
425
558
|
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.',
|
|
426
559
|
fPromptCustom: 'Customize memory-injection prompt', fPromptCustomHint: 'Override any prompt layer (power feature). Placeholders: {date} {ws} {budget} {n}. One-click reset restores defaults.',
|
|
@@ -446,6 +579,14 @@ window.__ModuleLoader__.load({
|
|
|
446
579
|
fWaterAuto: 'Auto skeleton ledger', fWaterAutoHint: 'Past the threshold, auto-writes one system skeleton ledger per session as a safety net if the model ignores the advisory.',
|
|
447
580
|
fWaterThreshold: 'Water-level threshold', fWaterThresholdHint: 'The ratio counts session tokens against the official declared window (the reserved output budget is no longer deducted); past this value (0.1-1.5) the handoff advisory / skeleton ledger fires; default 0.75 — the official auto-compaction threshold is 80%, so a 5% margin is needed to finish the handoff first.',
|
|
448
581
|
fHandoffPlan: 'Plan injection budget (chars)', fHandoffPlanHint: 'Hard truncation budget for injecting the PLAN.md snapshot into the dynamic memory; full text via memory_read or the Whiteboard tab. Default 1200.',
|
|
582
|
+
fProcInject: 'Procedure memory injection (master switch)', fProcInjectHint: 'Master switch for procedure memory (skill crystallization): when off, none is written and none is injected. On by default. The older "Skill crystallization" key is no longer read by the host, so change this one instead.',
|
|
583
|
+
fFactRetention: 'Fact retention limit', fFactRetentionHint: 'Maximum facts kept; when exceeded, revoked facts are pruned first, then oldest-first by write order, and important ones (pinned / user-level / explicit / high-confidence) last. Default 1000.',
|
|
584
|
+
fTier0Catalog: 'Tier-0 catalog layer', fTier0CatalogHint: 'When on, a short memory catalog (entry titles plus a one-line takeaway) is injected every round so the model sees the whole picture before drilling down. Off injects no catalog layer at all, saving tokens. On by default.',
|
|
585
|
+
fTier0Max: 'Tier-0 token cap', fTier0MaxHint: 'Hard per-injection token cap for the catalog layer, taking the minimum with (injection budget x catalog share). Default 400. Raise this rather than the whole injection budget when more corpus must fit.',
|
|
586
|
+
fRulesLayering: 'Layered rules section', fRulesLayeringHint: 'When on, rule-type memories form their own section that is always present and never trimmed (the legacy behavior mixed rules into the trimmable memory stream). Off is exactly the legacy behavior. On by default.',
|
|
587
|
+
fCriteriaGate: 'Ledger criteria gate', fCriteriaGateHint: 'When on, whiteboard/ledger sections must satisfy the criteria (H1-H4 / S1-S4) before being accepted, which prevents storing reasoning as conclusions. Off drops only this quality gate; the skeleton fail-soft path always applies. On by default.',
|
|
588
|
+
fWsDiscover: 'Workspace discovery cap', fWsDiscoverHint: 'Maximum workspaces identified when scanning local sessions (newest session time first). Default 200; lower it to speed up scanning when many historical workspaces exist.',
|
|
589
|
+
fMemFileIndex: 'Memory file index snapshot', fMemFileIndexHint: 'When on, an extra read-only index snapshot of memory files is maintained for external queries, at a small IO cost. Off by default, and off means zero overhead.',
|
|
449
590
|
fHandoffLedger: 'Ledger injection budget (chars)', fHandoffLedgerHint: 'Hard truncation budget for injecting the latest handoff ledger. Default 800.',
|
|
450
591
|
fConsSchedule: 'Scheduled deep consolidation', fConsScheduleHint: 'Once a day at the set time, read recent logs and distill long-term value into project notes / user memory (same flow as memory_consolidate).',
|
|
451
592
|
fConsScheduleTime: 'Consolidation time', fConsScheduleTimeHint: 'HH:MM, once per plugin day; default 09:30. The host must be online at that moment.',
|
|
@@ -555,7 +696,17 @@ window.__ModuleLoader__.load({
|
|
|
555
696
|
snapshotCalendarTitle: '[日历与日程(未完成)]',
|
|
556
697
|
snapshotWelcomeTitle: '[欢迎回来]',
|
|
557
698
|
snapshotWelcomeBody: '用户离开已超过 1 小时(暂离/下班后回来)。在本轮回复的开头,先用一句简短温暖的话欢迎用户回来…',
|
|
558
|
-
|
|
699
|
+
// ★T5(2026-09-20 用户拍板)同步服务端 snapshotInscription:铭文自 v0.1.30 起为空壳,
|
|
700
|
+
// 现恢复「收尾自检三大方向 + 分类别丢」正文。**此常量必须与服务端 lib/index.js 逐字一致**
|
|
701
|
+
// (设置页「提示词层级」展示的就是这份镜像,漂移会让用户看到的与实际注入不符)。
|
|
702
|
+
snapshotInscription: '[铭文 · 每轮提醒 {date}]\n'
|
|
703
|
+
+ '【收尾自检】本轮有实质产出才做;纯只读/闲聊轮跳过。三个方向 + 分类别丢:\n'
|
|
704
|
+
+ '① 写记忆文件 memory_log —— kind 按性质选,**不要一律 fact**:rule=用户约束/约定(会被规则层当硬约束注入)、preference=偏好、fact=事实记录、todo=待办;\n'
|
|
705
|
+
+ '② 更新白板与账本 —— ★**硬映射(满足即必须做,别自行判断"算不算"变化)**:\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'
|
|
707
|
+
+ '④ 结论失效/被取代 —— 传 `supersedes=` 旧条目 mem_id 标 **superseded(被更新结论取代)**;**当时就做错了要撤回**则传 `retract=` 标 **retracted(撤回)**并尽量附 `retractReason=` 说明错在哪(两者不同:前者有后继,后者本身就是教训);标错了用 `restore=` 撤回;\n'
|
|
708
|
+
+ '⑤ 看板落列 —— 白板/账本内容要进面板看板泳道,须在标题或正文写 tag:`type:goal` / `type:state` / `type:dead-end` / `type:progress`(5 条泳道含「版本归档」,靠归档动作而非 tag);不写 tag 系统只能按标题文字猜,常落空;\n'
|
|
709
|
+
+ '⑥ 语体 —— 客观陈述、第三人称,只留可复用的事实/决策/规则/路径(不写"我考虑/我排查/我想")。',
|
|
559
710
|
snapshotTail: '</memory_system>',
|
|
560
711
|
}
|
|
561
712
|
var FONT_SCALE_VALUES = { sm: '0.9', md: '1', lg: '1.15', xl: '1.3' }
|
|
@@ -585,10 +736,42 @@ window.__ModuleLoader__.load({
|
|
|
585
736
|
|
|
586
737
|
// ───────────────────────── 更新弹窗 / 首次指导 ─────────────────────────
|
|
587
738
|
var CHANGELOG = {
|
|
739
|
+
'3.1.0': { zh: [
|
|
740
|
+
'新增:记忆中枢「**人工越权晋升**」——给「证据还没攒够」被挡住的自沉淀条目一个真正可用的人工通道。两类门现在分得很清:**统计门**(跨会话数/成功次数没攒够)=内容没问题、只是证据不足 ⇒ 给按钮,你可以人工越过;**结构门**(已弃用/只是观察线索/有纠正记录/没写「怎么算成功」/高风险未批准)=内容本身还不构成技能 ⇒ 不给按钮,并直接写明为什么。所以不会再出现「点了没反应」的假按钮。越权过的条目会留下「人工授权」痕迹,可审计。',
|
|
741
|
+
'新增:设置页按用途重排为 9 个分区(语义记忆总开关 / 记忆窗口(注入什么) / 记忆容量与归档 / 自动沉淀成技能 / 长会话接续 / 自动化与免打扰 / 存储与外部记忆 / 外观与交互 / 关于与诊断)。**控件一字未改**,只是搬进对应分区并改了分组标题——此前「记忆中枢的判定参数」和「注入配置」混在一个分区里,找东西全靠翻。',
|
|
742
|
+
'修复:「手动晋升的通道被关掉了」的真相——判定函数对线上 30 条自沉淀条目**全部**返回「不晋升」(跨会话数不足 13 / 已弃用 8 / 只是观察线索 8 / 成功次数不足 1),而按钮此前只在「判定=可晋升」时出现,所以一条也点不亮。现已按上面的语义重做。',
|
|
743
|
+
'说明:更深一层的源头**本轮只缓解、未根治**——证据里的「跨会话数」恒为 0(唯一写入方依赖一个默认关闭的桥接开关),所以证据永远攒不够;人工通道解决的是「攒不够时能不能人工放行」,不是「证据永不累积」本身。',
|
|
744
|
+
'优化:宽屏下卡片流转为多列,记忆页改成左侧分区导航 + 右侧内容区(窄屏自动回到顶部横排);顶栏与浮层两种承载面**出厂即共存**——更新后直接在会话页顶栏就能看到记忆入口。',
|
|
745
|
+
'优化:日志列顺序调整——「硬性约束(每轮必注入)」与「用户笔记」提到最上面,每日日志折进可展开的折叠区。',
|
|
746
|
+
'说明:宿主侧(记忆中枢判定链)有改动,**建议重启 dsh web** 让其生效;界面半边刷新页面即可。',
|
|
747
|
+
], en: [
|
|
748
|
+
'New: manual promotion override in the memory hub — a genuinely working human channel for self-consolidated entries blocked by "not enough evidence yet". The two kinds of gates are now distinct: **statistical** (cross-session count / success count not yet reached) = the content is fine, only the evidence is thin ⇒ a button is shown and you may override; **structural** (deprecated / mere observation / has corrections / no success criteria / high-risk not approved) = the content is not a skill yet ⇒ no button, with a plain-language reason instead. So the UI no longer offers a fake button that does nothing. Overridden entries keep an auditable "manual authorization" trace.',
|
|
749
|
+
'New: the settings page is regrouped into 9 purpose-named sections (memory engine / memory window / capacity & retention / skills & promotion / handoff & continuation / automation / storage / appearance / about). **No control was changed** — they were only moved into the matching section with clearer group titles; previously promotion parameters and injection settings shared one section, so finding anything meant scrolling.',
|
|
750
|
+
'Fix: the real reason the manual promotion channel appeared "off" — the decision function returned "do not promote" for **all 30** self-consolidated entries (13 not enough cross-session usage / 8 deprecated / 8 observation-only / 1 not enough successes), and the button only appeared for "promotable" rows, so not one row could light up. Rebuilt around the semantics above.',
|
|
751
|
+
'Note: the deeper cause is **mitigated, not cured** in this release — the cross-session count in the evidence record is permanently 0 (its only writer depends on a bridge switch that defaults to off), so evidence never accumulates. The manual channel answers "can a human override when evidence is thin", not "why evidence never accumulates".',
|
|
752
|
+
'Improved: card flows are multi-column on wide screens, and the memory page uses a left section nav with content on the right (narrow screens fall back to a top strip). The titlebar and overlay surfaces now **coexist out of the box** — the memory entry is right there in the session titlebar after updating.',
|
|
753
|
+
'Improved: log column order — hard constraints (injected every turn) and user notes moved to the top, with the daily log folded into an expandable section.',
|
|
754
|
+
'Note: the host side (promotion decision chain) changed — **a dsh web restart is recommended**; the UI half only needs a page refresh.',
|
|
755
|
+
] },
|
|
756
|
+
'3.0.1': { zh: [
|
|
757
|
+
'修复:宿主控制台被 `restoreLastAgent: candidate rejected` **无限刷屏**(每 15 秒一条,打开就停不下来),同时「最近会话」恢复失效——刷新仪式与自动接续的兜底路径都受影响。根因是归属判据过宽:代码把「有父会话」等同于「是子代理」并一律排除,但**你点过一次接续后产生的新会话其实也有父会话**,它是你的正常会话,却被当成子代理拒掉了;列表里只剩它一个时,每个心跳周期都白试一次。现在改为按 `origin`/`delegationDepth` 精确识别子代理,接续会话正常放行;另给诊断信息加了 5 分钟节流兜底。',
|
|
758
|
+
'顺带修好两处同样的误判:接续会话里的对话此前**不会自动沉淀**、工作区状态也**不刷新**——都因为同一条过宽判据。',
|
|
759
|
+
'修复:日历「当天」视图里 **23:59 截止的事项永远不显示**。时段轴原先写死 7:00–22:00,凡落在该区间外(或时间格式解析不出)的事项都被静默丢掉——你自己的课表里就有 7 条这样的截止项。现改为按当天真实事项推算时段范围,并新增「未定时」段落兜住解析不出时间的条目。',
|
|
760
|
+
'新增:日历当天视图支持前后翻天,并显示 ISO 周序号。',
|
|
761
|
+
'优化:白板卡片的标题不再是小节名。原先「目标」这一节名重复出现上百次,读起来没有信息量;现在标题取正文首行、小节名降为小标签,并自动剥掉 `<!-- ... -->` 注释与 `- type:xxx | ` 这类前缀。**后端与数据文件零改动**,只在展示层清洗。',
|
|
762
|
+
'说明:白板「节点图画布」本轮**暂不开放**(默认隐藏)。它的数据源依赖工作区解析,当前在你的机器上解析不稳定时会显示「暂无内容」;代码保留在插件内,待解析路径稳定后再放出。原「白板看板」泳道视图完全不受影响。',
|
|
763
|
+
], en: [
|
|
764
|
+
'Fix: the host console was flooded by `restoreLastAgent: candidate rejected` — one line every 15 seconds, nonstop — and recent-session recovery stopped working (refresh ceremony and the auto-continue fallback both depend on it). Root cause was an over-broad ownership test: "has a parent session" was treated as "is a sub-agent" and excluded. But a session created by **continuing** an older one also has a parent — it is your normal session, and it was being rejected as if it were a sub-agent. When it was the only session in the list, every heartbeat retried in vain. Ownership is now decided by `origin`/`delegationDepth`, so continuation sessions pass; diagnostics are also throttled to one line per 5 minutes as a backstop.',
|
|
765
|
+
'Two more instances of the same misjudgement fixed along the way: conversations inside a continuation session were **never auto-consolidated**, and its workspace state **never refreshed** — both from the same over-broad test.',
|
|
766
|
+
'Fix: items due at **23:59 never appeared in the calendar day view**. The hour axis was hardcoded to 07:00–22:00, so anything outside it (or with an unparseable time) was silently dropped — 7 of your own deadlines were affected. The range is now derived from the actual events of the day, with a new "untimed" section for entries whose time cannot be parsed.',
|
|
767
|
+
'New: prev/next day navigation in the calendar day view, plus the ISO week number.',
|
|
768
|
+
'Improved: whiteboard card titles are no longer section names. The heading "目标" used to repeat over a hundred times — now the title is the first line of the body, the section name becomes a small kicker, and `<!-- ... -->` comments and `- type:xxx | ` prefixes are stripped. Backend and data files are untouched; cleaning happens in the render layer only.',
|
|
769
|
+
'Note: the whiteboard node-graph canvas is **not shipped in this release** (hidden by default). It depends on workspace resolution, which is unstable on your machine and shows an empty canvas; the code stays in the plugin and will be enabled once resolution is reliable. The existing swimlane whiteboard is unaffected.',
|
|
770
|
+
] },
|
|
588
771
|
'3.0.0': { zh: [
|
|
589
772
|
'★ 大版本:底层重建收官。检索、注入、容量、并发四条底层全部重做;白板与看板从「实验特性」变成出厂形态。',
|
|
590
773
|
'★ 白板默认开启(原来默认关)。交接白板 + 四段式交接账本是这一版的招牌能力,出厂关着等于新用户看不到它。想彻底关掉:设置 → 自动化 → 交接白板。',
|
|
591
|
-
'★ 白板默认「新版看板」:结构化索引(条目/标签/线索/版本链)+ 列式泳道视图 + 两个遍历工具(按标签展开
|
|
774
|
+
'★ 白板默认「新版看板」:结构化索引(条目/标签/线索/版本链)+ 列式泳道视图 + 两个遍历工具(按标签展开 memory_expand / 按 id 回溯 memory_trace)。旧的文字白板完整保留,设置页一键切回(「旧版白板」)。',
|
|
592
775
|
'说明:自动接续**仍然默认关闭**——水位判据还没在你的机器上实测到位,这一版特意不动它,等验证满意再考虑。',
|
|
593
776
|
'修复:语义唤回长期「查不到东西」的真因。语义索引因一次没收尾的同步被永久占住(重启也只能暂时缓解),此后每轮都退化成「关键词 + 常驻目录」。现已修好:索引能建起来,唤回恢复语义检索。',
|
|
594
777
|
'修复:升级弹窗原先只弹 2.1.0 的旧说明(版本号写死),所以你一直看不到新版更新日志。现在会自动展示当前版本的说明。',
|
|
@@ -1049,6 +1232,9 @@ window.__ModuleLoader__.load({
|
|
|
1049
1232
|
reflect: ROUTE_PREFIX + '/reflect',
|
|
1050
1233
|
reflectAuto: ROUTE_PREFIX + '/reflect-auto',
|
|
1051
1234
|
note: ROUTE_PREFIX + '/note',
|
|
1235
|
+
// ★R7:用户级硬性约束的条目级读写
|
|
1236
|
+
rulesList: ROUTE_PREFIX + '/rules',
|
|
1237
|
+
rulesApply: ROUTE_PREFIX + '/rules/apply',
|
|
1052
1238
|
external: ROUTE_PREFIX + '/external',
|
|
1053
1239
|
externalView: ROUTE_PREFIX + '/external-view',
|
|
1054
1240
|
externalRemove: ROUTE_PREFIX + '/external-remove',
|
|
@@ -1143,6 +1329,11 @@ window.__ModuleLoader__.load({
|
|
|
1143
1329
|
// 视觉:液态玻璃(毛玻璃)—— backdrop-filter + DSH 主题令牌(--dsw-alias-*),
|
|
1144
1330
|
// 跟随亮/暗主题与页面背景自适应;位置/尺寸由 JS 几何状态驱动(可拖动、可缩放)。
|
|
1145
1331
|
var CSS = [
|
|
1332
|
+
// ★2026-09-22 动效刻度作用域(MOTION-SPEC-20260922 §0.2):刻度此前只定义在 [data-dam-page] 内,
|
|
1333
|
+
// 而浮层 [data-dam-panel] 不是它的后代 ⇒ 浮层那组取不到变量,只能写字面量、无法与页面统一。
|
|
1334
|
+
// 本条把同一套刻度同时挂到两个承载面;page 侧原有的那份定义原样保留(不删不搬,避免层叠行为变化)。
|
|
1335
|
+
'[data-dam-panel], [data-dam-page] { --dam-dur-quick: 150ms; --dam-dur-fast: 250ms; --dam-dur-slow: 400ms; --dam-dur-stagger: 40ms;',
|
|
1336
|
+
' --dam-ease-out: cubic-bezier(.22, 1, .36, 1); --dam-ease-inout: cubic-bezier(.65, 0, .35, 1); --dam-ease-pop: cubic-bezier(.34, 1.36, .64, 1); }',
|
|
1146
1337
|
'[data-dam-panel] { position: fixed; left: 16px; width: 440px; height: 560px; --dam-scale: 1;',
|
|
1147
1338
|
' max-width: calc(100vw - 32px); max-height: calc(100vh - 32px);',
|
|
1148
1339
|
' display: flex; flex-direction: column; overflow: hidden; z-index: 3000; pointer-events: auto;',
|
|
@@ -1152,10 +1343,10 @@ window.__ModuleLoader__.load({
|
|
|
1152
1343
|
' border: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.35)) 65%, transparent);',
|
|
1153
1344
|
' box-shadow: 0 24px 64px rgba(0,0,0,.22), 0 4px 16px rgba(0,0,0,.10), inset 0 1px 0 rgba(255,255,255,.22);',
|
|
1154
1345
|
' color: var(--dsw-alias-label-primary, #1f2328);',
|
|
1155
|
-
' animation: dam-in
|
|
1346
|
+
' animation: dam-in var(--dam-dur-fast) cubic-bezier(.2,.9,.3,1.15) both; transform-origin: left bottom; }',
|
|
1156
1347
|
'@keyframes dam-in { from { opacity: 0; transform: scale(.92) translateY(10px); } to { opacity: 1; transform: none; } }',
|
|
1157
1348
|
'@keyframes dam-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: scale(.95) translateY(6px); } }',
|
|
1158
|
-
'[data-dam-panel][data-closing="true"] { animation: dam-out
|
|
1349
|
+
'[data-dam-panel][data-closing="true"] { animation: dam-out var(--dam-dur-quick) var(--dam-ease-out) both; }',
|
|
1159
1350
|
'[data-dam-panel][data-scale="sm"] { --dam-scale: .9; }',
|
|
1160
1351
|
'[data-dam-panel][data-scale="lg"] { --dam-scale: 1.15; }',
|
|
1161
1352
|
'[data-dam-panel][data-scale="xl"] { --dam-scale: 1.3; }',
|
|
@@ -1172,14 +1363,115 @@ window.__ModuleLoader__.load({
|
|
|
1172
1363
|
' opacity: .55; z-index: 2; border-radius: 0 0 16px 0;',
|
|
1173
1364
|
' background: linear-gradient(135deg, transparent 50%, color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 45%, transparent) 50%); }',
|
|
1174
1365
|
'[data-dam-resize]:hover { opacity: 1; }',
|
|
1366
|
+
// ★2026-09-21 二次修正:原「顶部通栏停靠条」CSS 已随该形态废弃而删除 —— 它曾把面板做成 shell.overlay 里的
|
|
1367
|
+
// 覆盖条(用户实测:「浮在整个页面上方,而不是和对话轨迹…并列的一页」)。现在第二承载面是 conversation.view 会话页,
|
|
1368
|
+
// 窄容器/宽容器两套样式见下面 [data-dam-page] 一组。
|
|
1369
|
+
// ── 会话页大屏适配(2026-09-22「大屏批」) ───────────────────────────────
|
|
1370
|
+
// 设计刻度:间距 4/8/12/16/24/32、圆角 8/12/16、字号 = 用户档位 × 视口台阶。
|
|
1371
|
+
// 动效刻度(transitions-motion token 表):150 / 250 / 400ms + smooth-out 曲线、错开 40ms。
|
|
1372
|
+
// 作用域只有 [data-dam-page](会话页) —— 浮层 [data-dam-panel] 保持逐字节不变。
|
|
1373
|
+
'[data-dam-page] { display: flex; flex-direction: column; width: 100%; height: 100%; min-height: 0;',
|
|
1374
|
+
' --dam-scale: var(--dam-user-scale, 1);',
|
|
1375
|
+
' --dam-measure: 1440px;',
|
|
1376
|
+
' --dam-pad-x: clamp(16px, 1.6vw + 6px, 44px);',
|
|
1377
|
+
' --dam-pad-y: clamp(14px, .8vw + 6px, 26px);',
|
|
1378
|
+
' --dam-gap: clamp(10px, .5vw + 6px, 18px);',
|
|
1379
|
+
' --dam-dur-quick: 150ms; --dam-dur-fast: 250ms; --dam-dur-slow: 400ms;',
|
|
1380
|
+
' --dam-ease-out: cubic-bezier(.22, 1, .36, 1);',
|
|
1381
|
+
' --dam-ease-inout: cubic-bezier(.65, 0, .35, 1);',
|
|
1382
|
+
' --dam-ease-pop: cubic-bezier(.34, 1.36, .64, 1);',
|
|
1383
|
+
' color: var(--dsw-alias-label-primary, #1f2328); font: 13px/1.55 system-ui, "Segoe UI", sans-serif; }',
|
|
1384
|
+
'[data-dam-page-head] { display: flex; align-items: center; gap: 8px; padding: 10px 4px;',
|
|
1385
|
+
' border-bottom: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.25)) 55%, transparent); }',
|
|
1386
|
+
'[data-dam-page-head] strong { font-size: calc(15px * var(--dam-scale)); }',
|
|
1387
|
+
'[data-dam-page-head] .dam-spacer { flex: 1; }',
|
|
1388
|
+
// ★2026-09-22 左侧侧栏(方向文档 PANEL-PAGE-MIGRATION-DIRECTION §2/§3):顶部横向滚动页签 → 左侧固定侧栏;
|
|
1389
|
+
// 内容区独立成列独立滚动。窄窗(<900px)自动降级为顶部横条(只换方向,不注销任何分区)。
|
|
1390
|
+
'[data-dam-page-main] { display: flex; flex: 1; min-height: 0; }',
|
|
1391
|
+
'[data-dam-page-nav] { flex: 0 0 auto; width: 170px; overflow: auto; padding: 10px 8px; display: flex; flex-direction: column; gap: 2px;',
|
|
1392
|
+
' border-right: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.25)) 55%, transparent); }',
|
|
1393
|
+
'[data-dam-page-nav] button { flex: 0 0 auto; border: 0; background: transparent; color: inherit; text-align: left; cursor: pointer;',
|
|
1394
|
+
' padding: 7px 10px; border-radius: 8px; font: inherit; font-size: calc(12.5px * var(--dam-scale)); opacity: .68; white-space: nowrap;',
|
|
1395
|
+
' transition: background-color var(--dam-dur-quick) var(--dam-ease-out), color var(--dam-dur-quick) var(--dam-ease-out), opacity var(--dam-dur-quick) var(--dam-ease-out); }',
|
|
1396
|
+
'[data-dam-page-nav] button[data-active="true"] { opacity: 1; font-weight: 650; color: var(--dam-accent, var(--dsw-alias-brand-primary, #4f7cff));',
|
|
1397
|
+
' background: color-mix(in srgb, var(--dam-accent, #4f7cff) 12%, transparent); }',
|
|
1398
|
+
'[data-dam-page-content] { flex: 1; min-width: 0; display: flex; flex-direction: column; min-height: 0; }',
|
|
1399
|
+
'[data-dam-page-nav], [data-dam-page-content], [data-dam-page-main] { flex-shrink: 1; }',
|
|
1400
|
+
'[data-dam-page] [data-dam-section] { min-width: 0; }',
|
|
1401
|
+
// 折叠标题(日志列表用):整行可点, 箭头用 transform 旋转(不换字符,避免宽度跳动)。
|
|
1402
|
+
'[data-dam-page] [data-dam-fold] { display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; border: 0; cursor: pointer;',
|
|
1403
|
+
' background: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 8%, transparent); color: inherit; font: inherit;',
|
|
1404
|
+
' font-size: calc(12.5px * var(--dam-scale)); font-weight: 650; padding: 9px 12px; border-radius: 9px;',
|
|
1405
|
+
' transition: background-color var(--dam-dur-quick) var(--dam-ease-out); }',
|
|
1406
|
+
'[data-dam-page] [data-dam-fold]:hover { background: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 14%, transparent); }',
|
|
1407
|
+
'[data-dam-page] [data-dam-fold] .dam-fold-caret { display: inline-block; transition: transform var(--dam-dur-fast) var(--dam-ease-inout); }',
|
|
1408
|
+
'[data-dam-page] [data-dam-fold][data-open="true"] .dam-fold-caret { transform: rotate(90deg); }',
|
|
1409
|
+
'[data-dam-page] [data-dam-fold] .dam-fold-count { margin-left: auto; opacity: .55; font-weight: 400; }',
|
|
1410
|
+
'@media (max-width: 900px) {',
|
|
1411
|
+
' [data-dam-page-main] { flex-direction: column; }',
|
|
1412
|
+
' [data-dam-page-nav] { width: auto; flex-direction: row; gap: 4px; overflow-x: auto; overflow-y: hidden; padding: 6px 8px;',
|
|
1413
|
+
' border-right: 0; border-bottom: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.25)) 55%, transparent); } }',
|
|
1414
|
+
'[data-dam-page] [data-dam-body] { flex: 1; min-height: 0; overflow: auto;',
|
|
1415
|
+
' padding: var(--dam-pad-y) var(--dam-pad-x) 28px;',
|
|
1416
|
+
' padding-inline: max(var(--dam-pad-x), calc((100% - var(--dam-measure)) / 2));',
|
|
1417
|
+
' scrollbar-width: thin;',
|
|
1418
|
+
' scrollbar-color: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 34%, transparent) transparent; }',
|
|
1419
|
+
'[data-dam-page] [data-dam-body]::-webkit-scrollbar { width: 10px; height: 10px; }',
|
|
1420
|
+
'[data-dam-page] [data-dam-body]::-webkit-scrollbar-track { background: transparent; }',
|
|
1421
|
+
'[data-dam-page] [data-dam-body]::-webkit-scrollbar-thumb { border: 3px solid transparent; background-clip: content-box;',
|
|
1422
|
+
' border-radius: 8px; background-color: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 28%, transparent); }',
|
|
1423
|
+
'[data-dam-page] [data-dam-body]::-webkit-scrollbar-thumb:hover {',
|
|
1424
|
+
' background-color: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 48%, transparent); }',
|
|
1425
|
+
// 大屏台阶:字号随视口抬两档, 与用户档位相乘, 不覆盖用户选择。
|
|
1426
|
+
'@media (min-width: 1400px) { [data-dam-page] { --dam-scale: calc(var(--dam-user-scale, 1) * 1.06); } }',
|
|
1427
|
+
'@media (min-width: 1800px) { [data-dam-page] { --dam-scale: calc(var(--dam-user-scale, 1) * 1.12); } }',
|
|
1428
|
+
// 首帧/页签切换:主体淡入 + 6px 上浮(只动 opacity/transform, 不触发布局与重绘)。
|
|
1429
|
+
'[data-dam-page] [data-dam-body] > :not([data-dam-flow]) { animation: dam-page-in var(--dam-dur-fast) var(--dam-ease-out) both; }',
|
|
1430
|
+
'@keyframes dam-page-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }',
|
|
1431
|
+
// ★卡片流分列(大屏核心收益):只对打了 [data-dam-flow] 标记的页签生效;
|
|
1432
|
+
// 非卡片子元素一律整行占据 ⇒ 不打标记的页签 = 逐字节旧行为, 打了标记也只有卡片进列。
|
|
1433
|
+
'[data-dam-page] [data-dam-flow] { display: grid; gap: var(--dam-gap); align-items: start; align-content: start;',
|
|
1434
|
+
' grid-template-columns: repeat(auto-fit, minmax(min(100%, 380px), 1fr)); }',
|
|
1435
|
+
'[data-dam-page] [data-dam-flow] > * { grid-column: 1 / -1; min-width: 0; animation: dam-card-in var(--dam-dur-fast) var(--dam-ease-out) both; }',
|
|
1436
|
+
// ★2026-09-22 动效统一(MOTION-SPEC-20260922 §1.2/§2.2/§2.3):
|
|
1437
|
+
// ① 时长 400ms → var(--dam-dur-fast) 250ms —— 用途是「页面前进」,400ms 是「面板打开」刻度,用在切页签会拖。
|
|
1438
|
+
// ② 补 both —— 原缺 animation-fill-mode,而下面 nth-child 带 40~200ms 延迟;fill-mode 默认 none 时
|
|
1439
|
+
// 延迟期内动画不生效 ⇒ 卡片按终态(opacity:1)渲染 ⇒ 第 2~6 张「先闪一下 → 跳空 → 再淡入」。
|
|
1440
|
+
// 同文件的 dam-rise 写了 both(:1466/:1472),属漏写不是设计。
|
|
1441
|
+
// ③ 错开值改走 --dam-dur-stagger(数值不变,只刻度化)。
|
|
1442
|
+
'[data-dam-page] [data-dam-flow] > [data-dam-card] { grid-column: auto; margin-bottom: 0;',
|
|
1443
|
+
' animation: dam-card-in var(--dam-dur-fast) var(--dam-ease-out) both; }',
|
|
1444
|
+
'@keyframes dam-card-in { from { opacity: 0; transform: translateY(8px) scale(.988); } to { opacity: 1; transform: none; } }',
|
|
1445
|
+
'[data-dam-page] [data-dam-flow] > [data-dam-card]:nth-child(2) { animation-delay: calc(var(--dam-dur-stagger) * 1); }',
|
|
1446
|
+
'[data-dam-page] [data-dam-flow] > [data-dam-card]:nth-child(3) { animation-delay: calc(var(--dam-dur-stagger) * 2); }',
|
|
1447
|
+
'[data-dam-page] [data-dam-flow] > [data-dam-card]:nth-child(4) { animation-delay: calc(var(--dam-dur-stagger) * 3); }',
|
|
1448
|
+
'[data-dam-page] [data-dam-flow] > [data-dam-card]:nth-child(5) { animation-delay: calc(var(--dam-dur-stagger) * 4); }',
|
|
1449
|
+
'[data-dam-page] [data-dam-flow] > [data-dam-card]:nth-child(n+6) { animation-delay: calc(var(--dam-dur-stagger) * 5); }',
|
|
1450
|
+
// 交互反馈:按压即时(150ms 刻度);悬停只在精确指针设备上生效, 避免触屏误触发。
|
|
1451
|
+
'[data-dam-page] [data-dam-btn], [data-dam-page] [data-dam-tab] {',
|
|
1452
|
+
' transition: background-color var(--dam-dur-quick) var(--dam-ease-out), color var(--dam-dur-quick) var(--dam-ease-out),',
|
|
1453
|
+
' opacity var(--dam-dur-quick) var(--dam-ease-out), transform var(--dam-dur-quick) var(--dam-ease-out); }',
|
|
1454
|
+
'[data-dam-page] [data-dam-btn]:active, [data-dam-page] [data-dam-tab]:active { transform: scale(.97); }',
|
|
1455
|
+
'[data-dam-page] [data-dam-tab] { padding: 8px 11px; }',
|
|
1456
|
+
'[data-dam-page] :focus-visible { outline: 2px solid color-mix(in srgb, var(--dam-accent, #2456c4) 72%, transparent); outline-offset: 2px; }',
|
|
1457
|
+
'@media (hover: hover) and (pointer: fine) { [data-dam-page] [data-dam-tab]:hover { opacity: .92; } }',
|
|
1458
|
+
// 减动效闸门:新增动效逐条归零(与文件既有那段同源, 此处只覆盖本批新增的选择器)。
|
|
1459
|
+
'@media (prefers-reduced-motion: reduce) {',
|
|
1460
|
+
' [data-dam-page] [data-dam-body] > *, [data-dam-page] [data-dam-flow] > [data-dam-card] { animation: none !important; }',
|
|
1461
|
+
' [data-dam-page] [data-dam-btn], [data-dam-page] [data-dam-tab] { transition: none !important; }',
|
|
1462
|
+
' [data-dam-page] [data-dam-btn]:active, [data-dam-page] [data-dam-tab]:active { transform: none; } }',
|
|
1175
1463
|
'[data-dam-btn] { border: none; background: transparent; cursor: pointer; color: inherit; opacity: .75; font-size: calc(13px * var(--dam-scale)); padding: 4px 8px; border-radius: 6px; }',
|
|
1176
1464
|
'[data-dam-btn]:hover { opacity: 1; background: color-mix(in srgb, var(--dsw-alias-label-secondary, #666) 16%, transparent); }',
|
|
1177
1465
|
'[data-dam-btn][data-pin="on"] { opacity: 1; background: color-mix(in srgb, var(--dam-accent, #4f7cff) 24%, transparent); }',
|
|
1178
1466
|
'[data-dam-tabs-wrap] { display: flex; align-items: center; min-width: 0; border-bottom: 1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.2)) 55%, transparent); }',
|
|
1179
1467
|
'[data-dam-tabs] { flex: 1; min-width: 0; overflow: hidden; padding: 5px 4px; scrollbar-width: none; -ms-overflow-style: none; }',
|
|
1180
1468
|
'[data-dam-tabs]::-webkit-scrollbar { display: none; }',
|
|
1469
|
+
// ★2026-09-22 取证记录(MOTION-SPEC-20260922 §3.3,本批**不改值**):下面这条 transition: transform 目前是
|
|
1470
|
+
// **死声明** —— 全文件只有 3 处提到 data-dam-tab-strip(CSS 定义 / 减动效闸门 / :4714 创建该 div),
|
|
1471
|
+
// 滚动由父容器原生 scrollTo/scrollBy 驱动(:4704-4705/:4710),没有任何代码写过它的 transform。
|
|
1472
|
+
// 保留原样:删它对本轮问题零贡献,却可能挡住「将来把滚动改回 transform 驱动」。要动它请单开一批。
|
|
1181
1473
|
'[data-dam-tab-strip] { display: flex; width: max-content; transition: transform .42s cubic-bezier(.22,.8,.2,1); will-change: transform; }',
|
|
1182
|
-
'[data-dam-tab] { flex: 0 0 auto; min-width: max-content; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border: none; background: transparent; cursor: pointer; color: inherit; opacity: .68; padding: 6px 9px; border-radius: 7px; font-size: calc(12.5px * var(--dam-scale)); transition: color
|
|
1474
|
+
'[data-dam-tab] { flex: 0 0 auto; min-width: max-content; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border: none; background: transparent; cursor: pointer; color: inherit; opacity: .68; padding: 6px 9px; border-radius: 7px; font-size: calc(12.5px * var(--dam-scale)); transition: color var(--dam-dur-quick) var(--dam-ease-out), background var(--dam-dur-quick) var(--dam-ease-out), opacity var(--dam-dur-quick) var(--dam-ease-out), transform var(--dam-dur-quick) var(--dam-ease-out); }',
|
|
1183
1475
|
'[data-dam-tabs-arrow] { flex: 0 0 26px; height: 30px; border: 0; background: transparent; color: inherit; cursor: pointer; opacity: .55; font-size: 18px; }',
|
|
1184
1476
|
'[data-dam-tabs-arrow]:hover:not(:disabled) { opacity: 1; background: color-mix(in srgb, var(--dam-accent, #2456c4) 16%, transparent); transform: scale(1.12); }',
|
|
1185
1477
|
'[data-dam-tabs-arrow]:active:not(:disabled) { transform: scale(.92); }',
|
|
@@ -1216,21 +1508,31 @@ window.__ModuleLoader__.load({
|
|
|
1216
1508
|
' background: color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.06)) 40%, transparent); }',
|
|
1217
1509
|
'[data-dam-card] .dam-date { font-weight: 600; margin-bottom: 4px; }',
|
|
1218
1510
|
'[data-dam-card] .dam-content { white-space: pre-wrap; word-break: break-word; max-height: 220px; overflow: auto; }',
|
|
1219
|
-
'[data-dam-disclosure] { overflow: hidden; opacity: 0; max-height: 0; transform: translateY(-5px) scale(.985); transition: max-height
|
|
1511
|
+
'[data-dam-disclosure] { overflow: hidden; opacity: 0; max-height: 0; transform: translateY(-5px) scale(.985); transition: max-height var(--dam-dur-fast) var(--dam-ease-out), opacity var(--dam-dur-fast) var(--dam-ease-out), transform var(--dam-dur-fast) var(--dam-ease-out); }',
|
|
1220
1512
|
'[data-dam-disclosure][data-phase="open"] { opacity: 1; max-height: 1800px; transform: translateY(0) scale(1); }',
|
|
1221
1513
|
'[data-dam-disclosure][data-phase="closing"] { opacity: 0; max-height: 0; transform: translateY(-5px) scale(.985); }',
|
|
1222
|
-
'[data-dam-card], [data-dam-banner] { animation: dam-rise
|
|
1514
|
+
'[data-dam-card], [data-dam-banner] { animation: dam-rise var(--dam-dur-fast) var(--dam-ease-out) both; }',
|
|
1223
1515
|
'@keyframes dam-rise { from { opacity: 0; transform: translateY(7px) scale(.988); } to { opacity: 1; transform: translateY(0) scale(1); } }',
|
|
1224
1516
|
'@media (prefers-reduced-motion: reduce) { [data-dam-tab-strip], [data-dam-tab], [data-dam-disclosure], [data-dam-card], [data-dam-banner], [data-dam-tour-orb-wrap] *, [data-dam-update-box] * { transition: none !important; animation: none !important; }',
|
|
1225
1517
|
' [data-dam-tour-bokeh], [data-dam-tour-slab], [data-dam-tour-orb-core] { opacity: 1 !important; transform: none !important; filter: none !important; }',
|
|
1226
1518
|
' [data-dam-update-stage] { display: none !important; }',
|
|
1227
1519
|
' [data-dam-update-content] { opacity: 1 !important; transform: none !important; animation: none !important; } }',
|
|
1228
|
-
|
|
1229
|
-
|
|
1520
|
+
// ★2026-09-22 减动效闸门(第二道) —— MOTION-SPEC-20260922 §1.6:上面那道只覆盖 8 个选择器,
|
|
1521
|
+
// 漏了浮层/日历三件/页签交互/折叠头/侧栏/保存条。本批新增与改动的每一条动效都必须在两道里被归零。
|
|
1522
|
+
'@media (prefers-reduced-motion: reduce) {',
|
|
1523
|
+
' [data-dam-panel], [data-dam-calendar], [data-dam-calendar-event], [data-dam-calendar-modal],',
|
|
1524
|
+
' [data-dam-calendar] [data-dam-calendar-day], [data-dam-tabs-arrow], [data-dam-btn],',
|
|
1525
|
+
' [data-dam-page-nav] button, [data-dam-page] [data-dam-fold], [data-dam-page] [data-dam-fold] .dam-fold-caret,',
|
|
1526
|
+
' [data-dam-savebar], [data-dam-savebar] button { transition: none !important; animation: none !important; }',
|
|
1527
|
+
' [data-dam-tab][data-active="true"], [data-dam-calendar] [data-dam-calendar-day]:hover,',
|
|
1528
|
+
' [data-dam-page] [data-dam-flow] > * { animation: none !important; }',
|
|
1529
|
+
' [data-dam-tab]:active, [data-dam-btn]:active, [data-dam-tabs-arrow]:active:not(:disabled) { transform: none !important; } }',
|
|
1530
|
+
'[data-dam-calendar] { animation: dam-rise var(--dam-dur-fast) var(--dam-ease-out) both; }',
|
|
1531
|
+
'[data-dam-calendar] [data-dam-calendar-day] { transition: transform var(--dam-dur-quick) var(--dam-ease-out), border-color .2s ease, background .2s ease, box-shadow .2s ease; }',
|
|
1230
1532
|
'[data-dam-calendar] [data-dam-calendar-day]:hover { transform: translateY(-1px); border-color: var(--dam-accent, #1d4ed8) !important; box-shadow: 0 4px 12px rgba(0,0,0,.08); }',
|
|
1231
|
-
'[data-dam-calendar-event] { animation: dam-event-in
|
|
1533
|
+
'[data-dam-calendar-event] { animation: dam-event-in var(--dam-dur-fast) var(--dam-ease-out) both; transition: transform var(--dam-dur-quick) var(--dam-ease-out), filter var(--dam-dur-quick) var(--dam-ease-out); }',
|
|
1232
1534
|
'[data-dam-calendar-event]:hover { transform: translateX(2px); filter: brightness(1.06); }',
|
|
1233
|
-
'[data-dam-calendar-modal] { animation: dam-modal-in
|
|
1535
|
+
'[data-dam-calendar-modal] { animation: dam-modal-in var(--dam-dur-fast) var(--dam-ease-out) both; }',
|
|
1234
1536
|
'@keyframes dam-event-in { from { opacity: 0; transform: translateX(-5px); } to { opacity: 1; transform: none; } }',
|
|
1235
1537
|
'@keyframes dam-modal-in { from { opacity: 0; transform: translateY(10px) scale(.98); } to { opacity: 1; transform: none; } }',
|
|
1236
1538
|
'[data-dam-banner] { border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary, #e6a23c) 55%, transparent);',
|
|
@@ -1509,6 +1811,99 @@ window.__ModuleLoader__.load({
|
|
|
1509
1811
|
}
|
|
1510
1812
|
function writeLs(key, val) { try { localStorage.setItem(key, String(val)) } catch (e) {} }
|
|
1511
1813
|
|
|
1814
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1815
|
+
// 白板内容清洗层(2026-09-20 重构批)
|
|
1816
|
+
//
|
|
1817
|
+
// 为什么需要: 卡片数据来自 Markdown 的 `## ` 小节直通(wb-sidecar.js:252→:666),
|
|
1818
|
+
// 全链路无摘要/清洗/标题正文分离 ⇒ 实测 868 张卡只有 195 个不同标题
|
|
1819
|
+
// (「目标」重复 109 次), 且正文首行常是未剥离的 `<!-- type:goal -->`。
|
|
1820
|
+
// title 与 body 因此互为镜像 —— 两个字段的信息量只有 1 个。
|
|
1821
|
+
//
|
|
1822
|
+
// 立场: 纯前端派生, 不改后端契约(wb-contract.js 的写入门与 sidecar 结构零改动)。
|
|
1823
|
+
// 清洗是**渲染层**职责: 磁盘上的字节保持原样, 只在显示时剥掉元数据。
|
|
1824
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1825
|
+
/** 剥 HTML 注释(锚点行 `<!-- memory:mem_xxx -->` 与 tag 行 `<!-- type:goal -->`)。 */
|
|
1826
|
+
function wbStripComment(s) { return String(s || '').replace(/<!--[\s\S]*?-->/g, '') }
|
|
1827
|
+
/** 剥行首的 tag 前缀(`- type:state | ` / `type:goal` 等)与列表符。 */
|
|
1828
|
+
function wbStripPrefix(s) {
|
|
1829
|
+
return String(s || '')
|
|
1830
|
+
.replace(/^\s*[-*+]\s+/, '')
|
|
1831
|
+
.replace(/^\s*(?:tag|type|topic)\s*[::]\s*[\w\u4e00-\u9fff-]+\s*[||]?\s*/i, '')
|
|
1832
|
+
.replace(/^\s*[||]\s*/, '')
|
|
1833
|
+
}
|
|
1834
|
+
/** 剥 Markdown 行内标记(粗体/斜体/行内代码/链接), 只留可读文字。 */
|
|
1835
|
+
function wbStripMarkup(s) {
|
|
1836
|
+
return String(s || '')
|
|
1837
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
1838
|
+
.replace(/(^|[^*])\*([^*]+)\*/g, '$1$2')
|
|
1839
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
1840
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
1841
|
+
.replace(/\s+/g, ' ')
|
|
1842
|
+
.trim()
|
|
1843
|
+
}
|
|
1844
|
+
/** 一条原文 → 可读文本(剥注释 → 逐行剥前缀 → 剥标记), 丢弃空行。 */
|
|
1845
|
+
function wbCleanLines(s) {
|
|
1846
|
+
var lines = wbStripComment(s).split(/\r?\n/)
|
|
1847
|
+
var out = []
|
|
1848
|
+
for (var i = 0; i < lines.length; i++) {
|
|
1849
|
+
var t = wbStripMarkup(wbStripPrefix(lines[i]))
|
|
1850
|
+
if (t) out.push(t)
|
|
1851
|
+
}
|
|
1852
|
+
return out
|
|
1853
|
+
}
|
|
1854
|
+
/** 标题清洗: 剥注释与前缀后取首行, 过长截断。空则回落到小节名。 */
|
|
1855
|
+
function wbCleanTitle(raw) {
|
|
1856
|
+
var ls = wbCleanLines(raw)
|
|
1857
|
+
var t = ls.length ? ls[0] : ''
|
|
1858
|
+
if (!t) return ''
|
|
1859
|
+
return t.length > 60 ? t.slice(0, 57) + '…' : t
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* 卡片 → { title, body, meta }
|
|
1863
|
+
* 标题优先级: 清洗后的正文首行 > card.title(小节名) > 来源文件名
|
|
1864
|
+
* 正文 = 剩余行拼接(已清洗), 与标题不重复 —— 这正是「标题+正文分离」。
|
|
1865
|
+
*/
|
|
1866
|
+
function wbShape(c) {
|
|
1867
|
+
var rawFull = c.full || c.preview || ''
|
|
1868
|
+
var ls = wbCleanLines(rawFull)
|
|
1869
|
+
var head = ls.length ? ls[0] : ''
|
|
1870
|
+
var title = head ? (head.length > 60 ? head.slice(0, 57) + '…' : head) : (c.title || c.short || '')
|
|
1871
|
+
// 副标题: 小节名(短小、可当分类标签), 若与标题相同则省略
|
|
1872
|
+
var kicker = c.title && c.title !== title ? c.title : ''
|
|
1873
|
+
var rest = ls.slice(1)
|
|
1874
|
+
// 小节名常重复出现在正文首行(如「任务状态」), 再剥一次
|
|
1875
|
+
if (kicker && rest.length && rest[0] === kicker) rest = rest.slice(1)
|
|
1876
|
+
var body = rest.join(' ')
|
|
1877
|
+
var isDup = body.indexOf(title) === 0 && body.length <= title.length + 2
|
|
1878
|
+
if (isDup) body = ''
|
|
1879
|
+
return {
|
|
1880
|
+
title: title,
|
|
1881
|
+
kicker: kicker,
|
|
1882
|
+
body: body,
|
|
1883
|
+
docTitle: c.docTitle || '',
|
|
1884
|
+
source: c.source || '',
|
|
1885
|
+
section: c.section || '',
|
|
1886
|
+
tags: c.tags || [],
|
|
1887
|
+
sectionTags: c.sectionTags || [],
|
|
1888
|
+
anchored: !!c.anchored,
|
|
1889
|
+
id: c.id,
|
|
1890
|
+
ts: c.ts,
|
|
1891
|
+
lineStart: c.lineStart,
|
|
1892
|
+
kind: c.kind || '',
|
|
1893
|
+
mtime: c.mtime,
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
/** 泳道 key → 色调(图上按类别着色, 与面板泳道语义一致)。 */
|
|
1897
|
+
function wbLaneTone(key) {
|
|
1898
|
+
var k = String(key || '')
|
|
1899
|
+
if (k === 'goal') return '#4d6bfe'
|
|
1900
|
+
if (k === 'state' || k === 'doing') return '#3fa96a'
|
|
1901
|
+
if (k === 'dead' || k === 'deadend') return '#c2603f'
|
|
1902
|
+
if (k === 'progress' || k === 'next') return '#d4a94f'
|
|
1903
|
+
if (k === 'archive') return '#8a7fbf'
|
|
1904
|
+
return 'rgba(128,128,128,.75)'
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1512
1907
|
/** 单张卡片: 标题 + 判据徽章 + 预览/全文 + 小节徽章 + 标签 + 来源。可点击展开。 */
|
|
1513
1908
|
function KanbanCard(props) {
|
|
1514
1909
|
var c = props.card || {}
|
|
@@ -1774,6 +2169,9 @@ window.__ModuleLoader__.load({
|
|
|
1774
2169
|
/** 整页矩阵看板: 行=日期分组, 列=小节类型。 */
|
|
1775
2170
|
function KanbanView(props) {
|
|
1776
2171
|
var st = useState(null), data = st[0], setData = st[1]
|
|
2172
|
+
// ★2026-09-17(L2 修 bug①): 额外保留**原始载荷** —— 旧实现 `setData(k && k.enabled ? k : null)`
|
|
2173
|
+
// 直接把宿主返回的 reason/error 丢掉了, 于是下面只能硬编码一句猜测式提示。
|
|
2174
|
+
var stPay = useState(null), pay = stPay[0], setPay = stPay[1]
|
|
1777
2175
|
var st2 = useState(null), drawer = st2[0], setDrawer = st2[1]
|
|
1778
2176
|
var st3 = useState({}), folded = st3[0], setFolded = st3[1]
|
|
1779
2177
|
var st4 = useState(''), q = st4[0], setQ = st4[1]
|
|
@@ -1783,8 +2181,8 @@ window.__ModuleLoader__.load({
|
|
|
1783
2181
|
useEffect(function () {
|
|
1784
2182
|
var alive = true
|
|
1785
2183
|
apiGet(API.kanbanBoard, { sessionId: currentSessionIdClient() })
|
|
1786
|
-
.then(function (k) { if (alive) setData(k && k.enabled ? k : null) })
|
|
1787
|
-
.catch(function () { if (alive) setData(null) })
|
|
2184
|
+
.then(function (k) { if (alive) { setPay(k || null); setData(k && k.enabled ? k : null) } })
|
|
2185
|
+
.catch(function (e) { if (alive) { setPay({ reason: 'error', error: String((e && e.message) || e) }); setData(null) } })
|
|
1788
2186
|
return function () { alive = false }
|
|
1789
2187
|
}, [tick])
|
|
1790
2188
|
|
|
@@ -1797,6 +2195,27 @@ window.__ModuleLoader__.load({
|
|
|
1797
2195
|
var total = (mx && mx.stats && mx.stats.totalCards) || 0
|
|
1798
2196
|
var missing = (mx && mx.stats && mx.stats.missingDeadend) || []
|
|
1799
2197
|
|
|
2198
|
+
// ★2026-09-17(L2 修 bug①): **按宿主返回的真实 reason 分支**, 不再硬编码猜测式提示。
|
|
2199
|
+
// 旧文案把「看板未启用」归因于 **handoff 未开启**, 把用户指向错误方向 —— 用户报告:
|
|
2200
|
+
// 「明明已经启用了, 却显示未启用或加载失败」。现消费 payload 里的 reason/error。
|
|
2201
|
+
var emptyMsg = (function () {
|
|
2202
|
+
var r = pay && pay.reason
|
|
2203
|
+
if (r === 'legacy-mode') {
|
|
2204
|
+
return zh ? '当前是旧版白板(legacy)档。切到新版看板: 设置 → 自动记忆引擎 → 看板模式 → 「新版看板(dsh-graph)」,重启 dsh web 后生效。'
|
|
2205
|
+
: 'Legacy board mode. Switch to graph board: Settings → Auto Memory Engine → Board mode → "Graph board (dsh-graph)", then restart dsh web.'
|
|
2206
|
+
}
|
|
2207
|
+
if (r === 'error') {
|
|
2208
|
+
var m = String((pay && pay.error) || '')
|
|
2209
|
+
return (zh ? '看板加载出错: ' : 'Board load error: ') + (m || (zh ? '(无错误详情)' : '(no detail)'))
|
|
2210
|
+
}
|
|
2211
|
+
if (r === 'handoff-disabled') {
|
|
2212
|
+
// 兼容极旧宿主(本版后端已不再返回该 reason), 如实说明而非误导
|
|
2213
|
+
return zh ? '看板数据源已关闭。请在设置里启用白板/账本(handoff)。' : 'Board data source is off. Enable whiteboard/ledger (handoff) in settings.'
|
|
2214
|
+
}
|
|
2215
|
+
return zh ? '看板尚未就绪或加载失败。请点上方「刷新」重试; 若持续如此, 检查是否已切到新版看板(dsh-graph)档。'
|
|
2216
|
+
: 'Board not ready or failed to load. Click "reload" above; if it persists, check the graph board mode.'
|
|
2217
|
+
})()
|
|
2218
|
+
|
|
1800
2219
|
return h('div', { 'data-dam-kanban-view': '', style: {
|
|
1801
2220
|
// position:relative 是**必需**的: 抽屉用 absolute 定位, 它的参照系就是这里 ⇒ 抽屉被裁剪在
|
|
1802
2221
|
// 看板承载面内, 不会像 fixed 那样越界盖住底部输入框。overflow:hidden 同时兜底裁剪。
|
|
@@ -1825,9 +2244,8 @@ window.__ModuleLoader__.load({
|
|
|
1825
2244
|
h('button', { key: 'zi', 'data-dam-btn': '', onClick: function () { setZoom(Math.min(1.6, zoom + 0.1)) } }, 'A+'),
|
|
1826
2245
|
h('button', { key: 'rf', 'data-dam-btn': '', onClick: function () { setData(null); bump(function (n) { return n + 1 }) } }, zh ? '刷新' : 'reload'),
|
|
1827
2246
|
]),
|
|
1828
|
-
// ── 空态 / 错误态(
|
|
1829
|
-
!data ? h('div', { key: 'ph', 'data-dam-kx-empty': 'nodata', 'data-dam-hint': '' },
|
|
1830
|
-
zh ? '看板未启用或加载失败(需 boardMode=graph 且 handoff 已开启)。' : 'Board unavailable.') : null,
|
|
2247
|
+
// ── 空态 / 错误态(不静默; ★2026-09-17 按真实 reason 分支, 见上方 emptyMsg) ──
|
|
2248
|
+
!data ? h('div', { key: 'ph', 'data-dam-kx-empty': 'nodata', 'data-dam-hint': '' }, emptyMsg) : null,
|
|
1831
2249
|
data && !mx ? h('div', { key: 'nm', 'data-dam-kx-empty': 'nomatrix', 'data-dam-hint': '' },
|
|
1832
2250
|
zh ? '本次载荷缺矩阵视图。' : 'No matrix in payload.') : null,
|
|
1833
2251
|
data && mx && !rows.length ? h('div', { key: 'nr', 'data-dam-kx-empty': 'norows', 'data-dam-hint': '' },
|
|
@@ -1950,6 +2368,412 @@ window.__ModuleLoader__.load({
|
|
|
1950
2368
|
])
|
|
1951
2369
|
}
|
|
1952
2370
|
|
|
2371
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
2372
|
+
// 白板节点图画布(2026-09-20 重构批 · 风格参考 kanghelyu/dsh-deepseek-flow)
|
|
2373
|
+
//
|
|
2374
|
+
// 立场: **只改渲染层**。数据源仍是同一个 /kanban-board 载荷, 后端与 sidecar 零改动。
|
|
2375
|
+
// 参考项目用 esbuild 打包 8 个模块, 本插件受「单文件 / 无构建链」约束
|
|
2376
|
+
// (package.json exports["./client"]='./lib/client.js' 直送浏览器) ⇒
|
|
2377
|
+
// 不引第三方图库, 布局算法手写。
|
|
2378
|
+
//
|
|
2379
|
+
// 与泳道看板的关系(共存, 非替换):
|
|
2380
|
+
// · 会话页整页宽(≥700px) = 本画布(节点 + 连线, 平移缩放);
|
|
2381
|
+
// · 侧边面板 440px = 原泳道看板(装不下画布, 保持不动)。
|
|
2382
|
+
//
|
|
2383
|
+
// 图要素(全部来自现有数据, §6 侦察报告结论):
|
|
2384
|
+
// · 节点 = 小节卡(WbShape 清洗后: title 为正文首行, kicker 为小节名)
|
|
2385
|
+
// · 结构边 = 同泳道内的时间/顺序链
|
|
2386
|
+
// · 分层 = 按**泳道**分列(有限 6 类), 列内按时间倒序排列
|
|
2387
|
+
// ⚠ 语义边(tag 关联/版本链)数据当前不可用(by_tag 全 sec: / versionCount 恒 0),
|
|
2388
|
+
// 故本版**不画**这类边 —— 宁可少画, 不画假的。
|
|
2389
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
2390
|
+
var WBG_NODE_W = 232
|
|
2391
|
+
var WBG_NODE_H = 104
|
|
2392
|
+
var WBG_GAP_X = 74
|
|
2393
|
+
var WBG_GAP_Y = 22
|
|
2394
|
+
var WBG_PAD = 40
|
|
2395
|
+
var WBG_ZOOM_KEY = 'dam-wbg-zoom'
|
|
2396
|
+
var WBG_PAN_KEY = 'dam-wbg-pan'
|
|
2397
|
+
// 每列最多排多少个节点, 其余折叠为「+N」汇总节点。
|
|
2398
|
+
//
|
|
2399
|
+
// ★2026-09-21 布局重做(实测驱动): 原实现「一篇文档 = 一列」在真实数据下**数学上不可用** ——
|
|
2400
|
+
// 本仓库实测 876 节点 / 189 篇文档 ⇒ 画布 57,840 × 1,318 px, 宽高比 **43.9:1**
|
|
2401
|
+
// (容器仅 1.72:1), 理想 fit 需缩到 **1.9%**, 而缩放下限 35% ⇒ **永远看不到全貌,
|
|
2402
|
+
// 要横向拖 52 屏**。按泳道分列同样不行(1,842 × 23,494 ⇒ 纵向 37 屏)。
|
|
2403
|
+
// 结论: 病根不是「按什么分组」, 而是**一屏塞不下 876 个节点**。
|
|
2404
|
+
// 人的看图逻辑是「先全局分类 → 再逐层下钻」⇒ 默认视图必须是**概览**而非全量平铺。
|
|
2405
|
+
var WBG_COL_CAP = 14
|
|
2406
|
+
var WBG_MIN_ZOOM = 0.08
|
|
2407
|
+
var WBG_HDR_H = 34
|
|
2408
|
+
|
|
2409
|
+
/**
|
|
2410
|
+
* 分层布局: 列 = 泳道(类型), 行 = 时间倒序。
|
|
2411
|
+
*
|
|
2412
|
+
* 为什么按泳道而非文档: 泳道是**有限的 6 类**(goal/state/deadend/progress/archive/misc),
|
|
2413
|
+
* 语义稳定、用户已知; 文档是 189 个且会持续增长 —— 用它当轴等于把画布宽度绑死在数据量上。
|
|
2414
|
+
* 每列内按时间倒序(最近的在最上面), 超出 WBG_COL_CAP 的折叠为汇总节点,
|
|
2415
|
+
* 既保证一屏可读, 又不隐藏数据(可搜索或切「白板看板」看全部)。
|
|
2416
|
+
*/
|
|
2417
|
+
function wbLayout(cards) {
|
|
2418
|
+
var byLane = {}
|
|
2419
|
+
for (var i = 0; i < cards.length; i++) {
|
|
2420
|
+
var k = cards[i].laneKey || cards[i].lane || 'misc'
|
|
2421
|
+
if (!byLane[k]) byLane[k] = []
|
|
2422
|
+
byLane[k].push(cards[i])
|
|
2423
|
+
}
|
|
2424
|
+
// 固定列序(与面板泳道语义一致), 未在表中出现的追加在后
|
|
2425
|
+
var ORDER = ['goal', 'state', 'deadend', 'progress', 'archive', 'misc']
|
|
2426
|
+
var keys = []
|
|
2427
|
+
for (var oi = 0; oi < ORDER.length; oi++) if (byLane[ORDER[oi]]) keys.push(ORDER[oi])
|
|
2428
|
+
for (var kk in byLane) if (keys.indexOf(kk) < 0) keys.push(kk)
|
|
2429
|
+
|
|
2430
|
+
var nodes = []
|
|
2431
|
+
var edges = []
|
|
2432
|
+
var groups = []
|
|
2433
|
+
var maxRows = 0
|
|
2434
|
+
for (var ci = 0; ci < keys.length; ci++) {
|
|
2435
|
+
var key = keys[ci]
|
|
2436
|
+
var list = byLane[key].slice().sort(function (a, b) {
|
|
2437
|
+
// 时间新的在上; 无 ts 时按 lineStart 兜底
|
|
2438
|
+
var ta = Number(a.ts) || 0
|
|
2439
|
+
var tb = Number(b.ts) || 0
|
|
2440
|
+
if (ta !== tb) return tb - ta
|
|
2441
|
+
return (Number(a.lineStart) || 0) - (Number(b.lineStart) || 0)
|
|
2442
|
+
})
|
|
2443
|
+
var shown = list.slice(0, WBG_COL_CAP)
|
|
2444
|
+
var hidden = list.length - shown.length
|
|
2445
|
+
var colX = WBG_PAD + ci * (WBG_NODE_W + WBG_GAP_X)
|
|
2446
|
+
groups.push({ key: key, x: colX, total: list.length, shownN: shown.length, hidden: hidden })
|
|
2447
|
+
var rows = shown.length + (hidden > 0 ? 1 : 0)
|
|
2448
|
+
if (rows > maxRows) maxRows = rows
|
|
2449
|
+
for (var ri = 0; ri < shown.length; ri++) {
|
|
2450
|
+
var nd = wbShape(shown[ri])
|
|
2451
|
+
nd.x = colX
|
|
2452
|
+
nd.y = WBG_PAD + WBG_HDR_H + ri * (WBG_NODE_H + WBG_GAP_Y)
|
|
2453
|
+
nd.laneKey = key
|
|
2454
|
+
nd.col = ci
|
|
2455
|
+
nd.row = ri
|
|
2456
|
+
nodes.push(nd)
|
|
2457
|
+
// 结构边: 同泳道内相邻节点相连(时间链)
|
|
2458
|
+
if (ri > 0) edges.push({ from: shown[ri - 1].id, to: shown[ri].id, key: key })
|
|
2459
|
+
}
|
|
2460
|
+
if (hidden > 0) {
|
|
2461
|
+
nodes.push({
|
|
2462
|
+
id: '__more__' + key, x: colX,
|
|
2463
|
+
y: WBG_PAD + WBG_HDR_H + shown.length * (WBG_NODE_H + WBG_GAP_Y),
|
|
2464
|
+
title: (zh ? '还有 ' + hidden + ' 条' : '+' + hidden + ' more'),
|
|
2465
|
+
body: zh ? '已折叠以免画布过长 —— 用上方搜索, 或切到「白板看板」看全部' : 'collapsed for readability',
|
|
2466
|
+
kicker: '', docTitle: '', laneKey: key, col: ci, row: shown.length, isMore: true,
|
|
2467
|
+
})
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2470
|
+
return {
|
|
2471
|
+
nodes: nodes,
|
|
2472
|
+
edges: edges,
|
|
2473
|
+
groups: groups,
|
|
2474
|
+
width: WBG_PAD * 2 + keys.length * WBG_NODE_W + Math.max(0, keys.length - 1) * WBG_GAP_X,
|
|
2475
|
+
height: WBG_PAD * 2 + WBG_HDR_H + maxRows * WBG_NODE_H + Math.max(0, maxRows - 1) * WBG_GAP_Y,
|
|
2476
|
+
keys: keys,
|
|
2477
|
+
totalCards: cards.length,
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
/** 节点卡: 标题(正文首行) + 小节名 kicker + 正文摘要。四段式。 */
|
|
2482
|
+
function WbgNode(props) {
|
|
2483
|
+
var n = props.node
|
|
2484
|
+
var zoom = Number(props.zoom) || 1
|
|
2485
|
+
var sel = !!props.selected
|
|
2486
|
+
var tone = wbLaneTone(n.laneKey || n.section || '')
|
|
2487
|
+
var body = n.body || ''
|
|
2488
|
+
if (body.length > 220) body = body.slice(0, 217) + '…'
|
|
2489
|
+
var anchor = props.anchor
|
|
2490
|
+
return h('div', {
|
|
2491
|
+
'data-dam-wbg-node': '',
|
|
2492
|
+
'data-selected': sel ? 'true' : 'false',
|
|
2493
|
+
onClick: function (e) { e.stopPropagation(); props.onPick(n.id) },
|
|
2494
|
+
style: {
|
|
2495
|
+
position: 'absolute',
|
|
2496
|
+
left: n.x + 'px', top: n.y + 'px',
|
|
2497
|
+
width: WBG_NODE_W + 'px', minHeight: WBG_NODE_H + 'px',
|
|
2498
|
+
boxSizing: 'border-box',
|
|
2499
|
+
padding: '9px 11px 10px',
|
|
2500
|
+
borderRadius: '10px',
|
|
2501
|
+
cursor: 'pointer',
|
|
2502
|
+
background: sel
|
|
2503
|
+
? 'color-mix(in srgb, ' + tone + ' 16%, var(--dsw-alias-bg-layer-2, rgba(22,22,26,.96)))'
|
|
2504
|
+
: 'var(--dsw-alias-bg-layer-2, rgba(22,22,26,.92))',
|
|
2505
|
+
border: '1px solid ' + (sel ? tone : 'color-mix(in srgb, ' + tone + ' 34%, transparent)'),
|
|
2506
|
+
boxShadow: sel ? ('0 0 0 1px ' + tone + ', 0 6px 22px color-mix(in srgb, ' + tone + ' 26%, transparent)') : '0 2px 10px rgba(0,0,0,.18)',
|
|
2507
|
+
transition: 'background .14s ease, border-color .14s ease',
|
|
2508
|
+
},
|
|
2509
|
+
}, [
|
|
2510
|
+
// ① 分类条(kicker): 小节名 —— 短、可扫读
|
|
2511
|
+
n.kicker ? h('div', {
|
|
2512
|
+
key: 'k', 'data-dam-hint': '',
|
|
2513
|
+
style: {
|
|
2514
|
+
fontSize: (9.5 * zoom) + 'px', letterSpacing: '.06em', textTransform: 'uppercase',
|
|
2515
|
+
color: tone, opacity: .95, marginBottom: '4px',
|
|
2516
|
+
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
|
|
2517
|
+
},
|
|
2518
|
+
}, n.kicker + (n.anchored ? ' ⚓' : '')) : null,
|
|
2519
|
+
// ② 标题: 清洗后的正文首行(不再是重复的小节名)
|
|
2520
|
+
h('div', {
|
|
2521
|
+
key: 't',
|
|
2522
|
+
style: {
|
|
2523
|
+
fontSize: (12.5 * zoom) + 'px', fontWeight: 700, lineHeight: '1.34',
|
|
2524
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
2525
|
+
display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden',
|
|
2526
|
+
},
|
|
2527
|
+
}, n.title),
|
|
2528
|
+
// ③ 正文: 与标题不重复的剩余内容
|
|
2529
|
+
body ? h('div', {
|
|
2530
|
+
key: 'b', 'data-dam-hint': '',
|
|
2531
|
+
style: {
|
|
2532
|
+
marginTop: '5px', fontSize: (10.5 * zoom) + 'px', lineHeight: '1.42',
|
|
2533
|
+
color: 'var(--dsw-alias-label-secondary)',
|
|
2534
|
+
display: '-webkit-box', WebkitLineClamp: 3, WebkitBoxOrient: 'vertical', overflow: 'hidden',
|
|
2535
|
+
},
|
|
2536
|
+
}, body) : null,
|
|
2537
|
+
// ④ 来源: 文档名(定位"这条来自哪篇")
|
|
2538
|
+
h('div', {
|
|
2539
|
+
key: 's', 'data-dam-hint': '',
|
|
2540
|
+
style: {
|
|
2541
|
+
marginTop: '6px', fontSize: (9.5 * zoom) + 'px', opacity: .55,
|
|
2542
|
+
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
|
|
2543
|
+
},
|
|
2544
|
+
}, String(n.docTitle || n.source || '').replace(/^.*[\\/]/, '')),
|
|
2545
|
+
])
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
/** 画布: 平移(拖拽) + 缩放(滚轮/按钮) + fit + 点选侧栏。零依赖、纯 SVG 连线。 */
|
|
2549
|
+
function WhiteboardGraphView(props) {
|
|
2550
|
+
var st = useState(null), data = st[0], setData = st[1]
|
|
2551
|
+
var stPay = useState(null), pay = stPay[0], setPay = stPay[1]
|
|
2552
|
+
var stTick = useState(0), tick = stTick[0], bump = stTick[1]
|
|
2553
|
+
var stZoom = useState(function () { return readLsNum(WBG_ZOOM_KEY, 1) }), zoom = stZoom[0], setZoom = stZoom[1]
|
|
2554
|
+
var stPan = useState(function () {
|
|
2555
|
+
try { var v = JSON.parse(localStorage.getItem(WBG_PAN_KEY) || 'null'); return v && typeof v.x === 'number' ? v : { x: 0, y: 0 } } catch (e) { return { x: 0, y: 0 } }
|
|
2556
|
+
}), pan = stPan[0], setPan = stPan[1]
|
|
2557
|
+
var stSel = useState(null), sel = stSel[0], setSel = stSel[1]
|
|
2558
|
+
var stQ = useState(''), q = stQ[0], setQ = stQ[1]
|
|
2559
|
+
var dragRef = useRef(null)
|
|
2560
|
+
var wrapRef = useRef(null)
|
|
2561
|
+
var zh = (locale || 'zh').indexOf('zh') === 0
|
|
2562
|
+
|
|
2563
|
+
useEffect(function () {
|
|
2564
|
+
var alive = true
|
|
2565
|
+
apiGet(API.kanbanBoard, { sessionId: currentSessionIdClient() })
|
|
2566
|
+
.then(function (k) { if (alive) { setPay(k || null); setData(k && k.enabled ? k : null) } })
|
|
2567
|
+
.catch(function (e) { if (alive) { setPay({ reason: 'error', error: String((e && e.message) || e) }); setData(null) } })
|
|
2568
|
+
return function () { alive = false }
|
|
2569
|
+
}, [tick])
|
|
2570
|
+
|
|
2571
|
+
// 把 lanes[].cards[] 摊平成一张卡列表(画布不按泳道分组, 泳道只当节点色调)
|
|
2572
|
+
var cards = []
|
|
2573
|
+
var laneOf = {}
|
|
2574
|
+
var lanes = (data && data.lanes) || []
|
|
2575
|
+
for (var li = 0; li < lanes.length; li++) {
|
|
2576
|
+
var lc = lanes[li].cards || []
|
|
2577
|
+
for (var ci = 0; ci < lc.length; ci++) {
|
|
2578
|
+
cards.push(lc[ci])
|
|
2579
|
+
laneOf[lc[ci].id] = lanes[li].key
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
var kg = wbLayout(cards.map(function (c) { return Object.assign({}, c, { laneKey: laneOf[c.id] }) }))
|
|
2583
|
+
|
|
2584
|
+
var kw = q.trim().toLowerCase()
|
|
2585
|
+
var matches = function (n) {
|
|
2586
|
+
if (!kw) return true
|
|
2587
|
+
return (n.title + ' ' + n.body + ' ' + n.docTitle + ' ' + n.kicker).toLowerCase().indexOf(kw) >= 0
|
|
2588
|
+
}
|
|
2589
|
+
var visibleNodes = kg.nodes.filter(matches)
|
|
2590
|
+
var visIds = {}
|
|
2591
|
+
for (var vi = 0; vi < visibleNodes.length; vi++) visIds[visibleNodes[vi].id] = 1
|
|
2592
|
+
|
|
2593
|
+
var nodeById = {}
|
|
2594
|
+
for (var ni = 0; ni < kg.nodes.length; ni++) nodeById[kg.nodes[ni].id] = kg.nodes[ni]
|
|
2595
|
+
|
|
2596
|
+
var selNode = sel ? nodeById[sel] : null
|
|
2597
|
+
|
|
2598
|
+
var setZoomBoth = function (v) {
|
|
2599
|
+
var nv = Math.min(2.0, Math.max(WBG_MIN_ZOOM, Math.round(v * 100) / 100))
|
|
2600
|
+
setZoom(nv); writeLs(WBG_ZOOM_KEY, nv)
|
|
2601
|
+
}
|
|
2602
|
+
var fit = function () {
|
|
2603
|
+
var el = wrapRef.current
|
|
2604
|
+
if (!el || !kg.width) return
|
|
2605
|
+
var w = el.clientWidth - 24
|
|
2606
|
+
var hgt = el.clientHeight - 24
|
|
2607
|
+
// 下限用 WBG_MIN_ZOOM(0.08)而非 0.35 —— 876 节点的概览视图本就是「缩小看结构」,
|
|
2608
|
+
// 用 0.35 当底会永远 fit 不到全貌(实测需 1.9%)。
|
|
2609
|
+
var z = Math.min(1.6, Math.max(WBG_MIN_ZOOM, Math.min(w / kg.width, hgt / kg.height)))
|
|
2610
|
+
setZoomBoth(z)
|
|
2611
|
+
setPanBoth({ x: 0, y: 0 })
|
|
2612
|
+
}
|
|
2613
|
+
var setPanBoth = function (p) { setPan(p); try { localStorage.setItem(WBG_PAN_KEY, JSON.stringify(p)) } catch (e) {} }
|
|
2614
|
+
|
|
2615
|
+
var onDown = function (e) {
|
|
2616
|
+
if (e.button !== 0) return
|
|
2617
|
+
dragRef.current = { sx: e.clientX, sy: e.clientY, px: pan.x, py: pan.y, moved: false }
|
|
2618
|
+
e.currentTarget.setPointerCapture && e.currentTarget.setPointerCapture(e.pointerId)
|
|
2619
|
+
}
|
|
2620
|
+
var onMove = function (e) {
|
|
2621
|
+
var d = dragRef.current
|
|
2622
|
+
if (!d) return
|
|
2623
|
+
var dx = e.clientX - d.sx, dy = e.clientY - d.sy
|
|
2624
|
+
if (Math.abs(dx) + Math.abs(dy) > 3) d.moved = true
|
|
2625
|
+
setPan({ x: d.px + dx, y: d.py + dy })
|
|
2626
|
+
}
|
|
2627
|
+
var onUp = function (e) {
|
|
2628
|
+
var d = dragRef.current
|
|
2629
|
+
dragRef.current = null
|
|
2630
|
+
if (d) { try { localStorage.setItem(WBG_PAN_KEY, JSON.stringify(pan)) } catch (e2) {} }
|
|
2631
|
+
e.currentTarget.releasePointerCapture && e.currentTarget.releasePointerCapture(e.pointerId)
|
|
2632
|
+
}
|
|
2633
|
+
var onWheel = function (e) {
|
|
2634
|
+
if (!e.ctrlKey && !e.metaKey && Math.abs(e.deltaY) < 2) return
|
|
2635
|
+
e.preventDefault()
|
|
2636
|
+
setZoomBoth(zoom + (e.deltaY < 0 ? 0.08 : -0.08))
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
// 空态/错误态: 与 KanbanView 同一套 reason 消费
|
|
2640
|
+
if (!data || !cards.length) {
|
|
2641
|
+
var reason = pay && pay.reason
|
|
2642
|
+
var msg = reason === 'legacy-mode'
|
|
2643
|
+
? (zh ? '当前是旧版白板(legacy)档。切到新版看板: 设置 → 自动记忆引擎 → 看板模式 → 「新版看板(dsh-graph)」。'
|
|
2644
|
+
: 'Legacy board mode. Switch to graph board in Settings.')
|
|
2645
|
+
: reason === 'error'
|
|
2646
|
+
? ((zh ? '加载出错: ' : 'Load error: ') + String((pay && pay.error) || ''))
|
|
2647
|
+
: (zh ? '画布暂无内容 —— 写一次白板或账本(memory_note)后这里会出现节点。'
|
|
2648
|
+
: 'No nodes yet — write a plan or ledger (memory_note) and nodes will appear.')
|
|
2649
|
+
return h('div', { 'data-dam-wbg-wrap': '', style: { padding: '18px' } }, [
|
|
2650
|
+
h('div', { 'data-dam-hint': '', key: 'm', style: { lineHeight: 1.7 } }, msg),
|
|
2651
|
+
h('button', { key: 'r', 'data-dam-btn': '', onClick: function () { bump(tick + 1) }, style: { marginTop: '10px' } }, zh ? '刷新' : 'reload'),
|
|
2652
|
+
])
|
|
2653
|
+
}
|
|
2654
|
+
|
|
2655
|
+
var btn = function (label, onClick, title) {
|
|
2656
|
+
return h('button', { 'data-dam-btn': '', onClick: onClick, title: title || '', style: { padding: '1px 8px', fontSize: '11px' } }, label)
|
|
2657
|
+
}
|
|
2658
|
+
var bar = h('div', {
|
|
2659
|
+
'data-dam-wbg-bar': '',
|
|
2660
|
+
style: { display: 'flex', flexWrap: 'wrap', gap: '6px', alignItems: 'center', padding: '8px 10px', borderBottom: '1px solid color-mix(in srgb, currentColor 12%, transparent)' },
|
|
2661
|
+
}, [
|
|
2662
|
+
btn('A-', function () { setZoomBoth(zoom - 0.1) }),
|
|
2663
|
+
h('span', { key: 'z', 'data-dam-hint': '', style: { fontSize: '10.5px', minWidth: '38px', textAlign: 'center' } }, Math.round(zoom * 100) + '%'),
|
|
2664
|
+
btn('A+', function () { setZoomBoth(zoom + 0.1) }),
|
|
2665
|
+
h('span', { key: 'g1', style: { width: '6px' } }),
|
|
2666
|
+
btn(zh ? '适应' : 'fit', fit, zh ? '缩放到刚好装下' : 'fit to view'),
|
|
2667
|
+
h('span', { key: 'g2', style: { width: '6px' } }),
|
|
2668
|
+
h('input', {
|
|
2669
|
+
key: 'q', 'data-dam-input': '', value: q, placeholder: zh ? '搜索节点…' : 'search nodes…',
|
|
2670
|
+
onChange: function (e) { setQ(e.target.value) },
|
|
2671
|
+
style: { flex: '1 1 120px', minWidth: '100px', fontSize: '11px', padding: '2px 7px' },
|
|
2672
|
+
}),
|
|
2673
|
+
h('span', { key: 'st', 'data-dam-hint': '', style: { fontSize: '10.5px', opacity: .7 } },
|
|
2674
|
+
(zh ? '节点 ' : 'nodes ') + visibleNodes.length + '/' + kg.nodes.length + (zh ? ' · 列 ' : ' · cols ') + (kg.keys || []).length + (zh ? ' · 共 ' : ' · total ') + kg.totalCards),
|
|
2675
|
+
])
|
|
2676
|
+
|
|
2677
|
+
// SVG 连线(在 transform 容器内, 与节点同一坐标系)
|
|
2678
|
+
var edgeEls = []
|
|
2679
|
+
for (var ei = 0; ei < kg.edges.length; ei++) {
|
|
2680
|
+
var ed = kg.edges[ei]
|
|
2681
|
+
var a = nodeById[ed.from], b = nodeById[ed.to]
|
|
2682
|
+
if (!a || !b || !visIds[a.id] || !visIds[b.id]) continue
|
|
2683
|
+
var x1 = a.x + WBG_NODE_W / 2, y1 = a.y + WBG_NODE_H
|
|
2684
|
+
var x2 = b.x + WBG_NODE_W / 2, y2 = b.y
|
|
2685
|
+
var my = (y1 + y2) / 2
|
|
2686
|
+
edgeEls.push(h('path', {
|
|
2687
|
+
key: 'e' + ei,
|
|
2688
|
+
d: 'M' + x1 + ' ' + y1 + ' C' + x1 + ' ' + my + ', ' + x2 + ' ' + my + ', ' + x2 + ' ' + y2,
|
|
2689
|
+
fill: 'none',
|
|
2690
|
+
stroke: 'color-mix(in srgb, ' + wbLaneTone(nodeById[ed.to].laneKey || '') + ' 46%, transparent)',
|
|
2691
|
+
'stroke-width': 1.4,
|
|
2692
|
+
}))
|
|
2693
|
+
}
|
|
2694
|
+
|
|
2695
|
+
var nodeEls = []
|
|
2696
|
+
for (var nk = 0; nk < kg.nodes.length; nk++) {
|
|
2697
|
+
var nn = kg.nodes[nk]
|
|
2698
|
+
if (!visIds[nn.id]) continue
|
|
2699
|
+
nodeEls.push(h(WbgNode, { key: nn.id, node: nn, zoom: zoom, selected: sel === nn.id, onPick: function (id) { setSel(function (p) { return p === id ? null : id }) } }))
|
|
2700
|
+
}
|
|
2701
|
+
|
|
2702
|
+
// 列标题: 泳道名 + 「已显示/总数」+ 色条。
|
|
2703
|
+
// 为什么必须有: 默认缩放到 fit 时节点字会小到读不清, **列标题是唯一还能读的锚点**;
|
|
2704
|
+
// 人的看图顺序是「先认这 6 列是什么 → 再看某一列里有什么」, 没有列标题就只剩一堆色块。
|
|
2705
|
+
var LANE_LABEL_ZH = { goal: '目标', state: '进行中', deadend: '失败与弯路', progress: '进度与下一步', archive: '版本归档', misc: '其它' }
|
|
2706
|
+
var groupEls = []
|
|
2707
|
+
for (var gi = 0; gi < (kg.groups || []).length; gi++) {
|
|
2708
|
+
var g = kg.groups[gi]
|
|
2709
|
+
var tone = wbLaneTone(g.key)
|
|
2710
|
+
var laneName = zh ? (LANE_LABEL_ZH[g.key] || g.key) : g.key
|
|
2711
|
+
groupEls.push(h('div', {
|
|
2712
|
+
key: 'gh' + g.key, 'data-dam-wbg-colhead': '',
|
|
2713
|
+
style: {
|
|
2714
|
+
position: 'absolute', left: g.x + 'px', top: WBG_PAD + 'px', width: WBG_NODE_W + 'px',
|
|
2715
|
+
boxSizing: 'border-box', paddingBottom: '6px',
|
|
2716
|
+
borderBottom: '2px solid color-mix(in srgb, ' + tone + ' 62%, transparent)',
|
|
2717
|
+
},
|
|
2718
|
+
}, [
|
|
2719
|
+
h('div', {
|
|
2720
|
+
key: 'n', style: {
|
|
2721
|
+
fontSize: (13.5 / Math.max(zoom, 0.4)) + 'px', fontWeight: 700, color: tone,
|
|
2722
|
+
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
|
|
2723
|
+
},
|
|
2724
|
+
}, laneName),
|
|
2725
|
+
h('div', { key: 'c', 'data-dam-hint': '', style: { fontSize: (10.5 / Math.max(zoom, 0.4)) + 'px', opacity: .7, marginTop: '1px' } },
|
|
2726
|
+
(g.shownN + '/' + g.total) + ' ' + (zh ? '条' : '')),
|
|
2727
|
+
]))
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
return h('div', { 'data-dam-wbg-wrap': '', style: { display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 } }, [
|
|
2731
|
+
bar,
|
|
2732
|
+
h('div', { key: 'body', style: { display: 'flex', flex: '1 1 auto', minHeight: 0 } }, [
|
|
2733
|
+
h('div', {
|
|
2734
|
+
key: 'canvas', ref: wrapRef,
|
|
2735
|
+
'data-dam-wbg-canvas': '',
|
|
2736
|
+
onPointerDown: onDown, onPointerMove: onMove, onPointerUp: onUp, onPointerCancel: onUp,
|
|
2737
|
+
onWheel: onWheel,
|
|
2738
|
+
onClick: function () { setSel(null) },
|
|
2739
|
+
style: {
|
|
2740
|
+
position: 'relative', flex: '1 1 auto', minWidth: 0, overflow: 'hidden',
|
|
2741
|
+
cursor: dragRef.current ? 'grabbing' : 'grab',
|
|
2742
|
+
backgroundImage: 'radial-gradient(color-mix(in srgb, currentColor 9%, transparent) 1px, transparent 1px)',
|
|
2743
|
+
backgroundSize: '22px 22px',
|
|
2744
|
+
},
|
|
2745
|
+
}, h('div', {
|
|
2746
|
+
'data-dam-wbg-stage': '',
|
|
2747
|
+
style: {
|
|
2748
|
+
position: 'absolute', left: 0, top: 0,
|
|
2749
|
+
width: kg.width + 'px', height: kg.height + 'px',
|
|
2750
|
+
transform: 'translate(' + pan.x + 'px,' + pan.y + 'px) scale(' + zoom + ')',
|
|
2751
|
+
transformOrigin: '0 0',
|
|
2752
|
+
willChange: 'transform',
|
|
2753
|
+
},
|
|
2754
|
+
}, [
|
|
2755
|
+
h('svg', { key: 'svg', width: kg.width, height: kg.height, style: { position: 'absolute', left: 0, top: 0, pointerEvents: 'none' } }, edgeEls),
|
|
2756
|
+
].concat(groupEls).concat(nodeEls))),
|
|
2757
|
+
// 侧栏: 选中节点的完整信息(宽屏才放得下全文 —— 这才是画布相对泳道的增量价值)
|
|
2758
|
+
selNode ? h('div', {
|
|
2759
|
+
key: 'side', 'data-dam-wbg-side': '',
|
|
2760
|
+
style: {
|
|
2761
|
+
flex: '0 0 320px', maxWidth: '44%', overflow: 'auto', padding: '12px 14px',
|
|
2762
|
+
borderLeft: '1px solid color-mix(in srgb, currentColor 12%, transparent)',
|
|
2763
|
+
},
|
|
2764
|
+
}, [
|
|
2765
|
+
h('div', { key: 'x', style: { display: 'flex', alignItems: 'flex-start', gap: '8px' } }, [
|
|
2766
|
+
h('div', { key: 'h', style: { flex: 1, fontWeight: 700, fontSize: '14px', lineHeight: 1.4 } }, selNode.title),
|
|
2767
|
+
h('button', { key: 'c', 'data-dam-btn': '', onClick: function () { setSel(null) } }, '✕'),
|
|
2768
|
+
]),
|
|
2769
|
+
selNode.kicker ? h('div', { key: 'k', 'data-dam-hint': '', style: { marginTop: '4px', fontSize: '11px', opacity: .75 } }, selNode.kicker + (selNode.anchored ? ' · ⚓ ' + (zh ? '有锚点' : 'anchored') : '')) : null,
|
|
2770
|
+
selNode.docTitle ? h('div', { key: 'd', 'data-dam-hint': '', style: { marginTop: '2px', fontSize: '11px', opacity: .6, wordBreak: 'break-all' } }, selNode.docTitle) : null,
|
|
2771
|
+
selNode.body ? h('div', { key: 'b', style: { marginTop: '12px', fontSize: '12px', lineHeight: 1.72, whiteSpace: 'pre-wrap', wordBreak: 'break-word' } }, selNode.body) : h('div', { key: 'b2', 'data-dam-hint': '', style: { marginTop: '12px', fontSize: '11.5px', opacity: .6 } }, zh ? '(这一节没有更多正文)' : '(no further body)'),
|
|
2772
|
+
]) : null,
|
|
2773
|
+
]),
|
|
2774
|
+
])
|
|
2775
|
+
}
|
|
2776
|
+
|
|
1953
2777
|
function Loading(props) {
|
|
1954
2778
|
var label = props && props.label ? props.label : t('loading')
|
|
1955
2779
|
return h('div', { 'data-dam-loading': '' }, h('span', { 'data-dam-spinner': '', 'aria-hidden': 'true' }), h('span', null, label))
|
|
@@ -2413,12 +3237,15 @@ window.__ModuleLoader__.load({
|
|
|
2413
3237
|
})
|
|
2414
3238
|
) : null,
|
|
2415
3239
|
groups.map(function (grp) {
|
|
2416
|
-
|
|
3240
|
+
// ★大屏分列(2026-09-22):判定队列每天一组的卡片在宽屏排成多列。
|
|
3241
|
+
// CSS 规则是 `[data-dam-flow] > [data-dam-card]`(直接子元素) ⇒ 标记必须打在**分组容器**上,
|
|
3242
|
+
// 打在页签根上卡片不是直接子元素,只会退化成整行占位(无效接线)。
|
|
3243
|
+
return h('div', { key: grp.day, 'data-dam-flow': '', style: { marginBottom: '16px' } },
|
|
2417
3244
|
h('div', { style: { fontSize: 'calc(11.5px * var(--dam-scale))', fontWeight: 700, opacity: .7, margin: '2px 2px 8px' } }, grp.day),
|
|
2418
3245
|
grp.rows.map(function (row, i) {
|
|
2419
3246
|
var rc = row.reasonCodes || []
|
|
2420
3247
|
var del = row.delivery
|
|
2421
|
-
return h('div', { key: row.observationId || i, style: card },
|
|
3248
|
+
return h('div', { key: row.observationId || i, 'data-dam-card': '', style: card },
|
|
2422
3249
|
h('div', { style: { display: 'flex', alignItems: 'center', gap: '7px', marginBottom: '5px' } },
|
|
2423
3250
|
(function () { var lk = row.lane === 'explicit' ? 'explicit' : 'proactive'; var c = laneBg[lk] || ['rgba(128,128,128,.16)', null]; return badge(locale === 'zh' ? (lk === 'explicit' ? '明确召回' : '主动观测') : lk, c[0], c[1]) })(),
|
|
2424
3251
|
(function () { var dk = String(row.decision); var c = decBg[dk] || ['rgba(128,128,128,.14)', null]; return badge(dk.toUpperCase(), c[0], c[1]) })(),
|
|
@@ -2450,6 +3277,11 @@ window.__ModuleLoader__.load({
|
|
|
2450
3277
|
var userTruncPair = useState(false)
|
|
2451
3278
|
var userTruncated = userTruncPair[0]
|
|
2452
3279
|
var setUserTruncated = userTruncPair[1]
|
|
3280
|
+
// ★2026-09-22:日志列表改折叠(用户要求「日志可以做成折叠的形式,可以展开」)。默认收起 ——
|
|
3281
|
+
// 首屏留给「硬性约束 + 用户笔记」这两块每轮无条件注入的稳定内容,日志属于翻查型内容。
|
|
3282
|
+
var logsOpenPair = useState(false)
|
|
3283
|
+
var logsOpen = logsOpenPair[0]
|
|
3284
|
+
var setLogsOpen = logsOpenPair[1]
|
|
2453
3285
|
useEffect(function () {
|
|
2454
3286
|
var alive = true
|
|
2455
3287
|
apiGet(API.list).then(function (d) { if (alive) setData(d) }).catch(function () {})
|
|
@@ -2469,23 +3301,181 @@ window.__ModuleLoader__.load({
|
|
|
2469
3301
|
h('div', { 'data-dam-content': '' }, content || t('empty')),
|
|
2470
3302
|
h('button', { 'data-dam-btn': '', onClick: function () { setOpen(null); setContent('') } }, t('back'))))
|
|
2471
3303
|
} else {
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
3304
|
+
// ★2026-09-22 版式调整(用户要求「把上面的那一堆东西和下面的对调」):
|
|
3305
|
+
// ① 硬性约束 与 用户笔记 提到最上面 —— 两者都是**每轮无条件注入**的稳定内容,使用者最该先看到;
|
|
3306
|
+
// ② 日志列表下移并改折叠 —— 日志是翻查型内容,不该占据首屏。
|
|
3307
|
+
// 两块都用 [data-dam-section] 包裹:它在 [data-dam-flow] 栅格里整行占位,不会被分列。
|
|
3308
|
+
rows.push(h('div', { 'data-dam-section': '' }, h(RulesEditPanel, {})))
|
|
3309
|
+
rows.push(h('div', { 'data-dam-section': '', style: { marginTop: '14px' } },
|
|
3310
|
+
h('div', { 'data-dam-hint': '', style: { marginBottom: '6px' } }, t('userMemoryBlock')),
|
|
3311
|
+
h(Card, { title: 'MEMORY.md (' + (locale === 'zh' ? '用户级' : 'user-level') + ')' },
|
|
3312
|
+
h('div', { 'data-dam-content': '' }, userText || t('userMemoryEmpty')),
|
|
3313
|
+
userTruncated ? h('div', { 'data-dam-hint': '' }, t('userMemoryTruncated')) : null)))
|
|
3314
|
+
rows.push(h('div', { 'data-dam-section': '', style: { marginTop: '14px' } }, h(FoldableLogs, {
|
|
3315
|
+
logs: data.logs || [], open: logsOpen, setOpen: setLogsOpen, onOpenFile: setOpen,
|
|
3316
|
+
})))
|
|
2484
3317
|
}
|
|
2485
|
-
|
|
3318
|
+
// 大屏分列:卡片流页签(见 CSS [data-dam-flow]);非卡片子元素仍整行。
|
|
3319
|
+
return h('div', { 'data-dam-flow': '' }, rows)
|
|
3320
|
+
}
|
|
3321
|
+
// ★2026-09-22 日志折叠块(从 LogsTab 抽出, 便于独立校验括号):标题可点, 内容用既有 AnimatedDisclosure。
|
|
3322
|
+
function FoldableLogs(props) {
|
|
3323
|
+
var open = !!props.open
|
|
3324
|
+
var list = props.logs || []
|
|
3325
|
+
return h('div', null,
|
|
3326
|
+
h('button', {
|
|
3327
|
+
'data-dam-fold': '', 'data-open': open ? 'true' : 'false', 'aria-expanded': open ? 'true' : 'false',
|
|
3328
|
+
onClick: function () { props.setOpen(!open) },
|
|
3329
|
+
},
|
|
3330
|
+
h('span', { className: 'dam-fold-caret' }, '▸'),
|
|
3331
|
+
h('span', null, t('logs')),
|
|
3332
|
+
h('span', { className: 'dam-fold-count' }, list.length + (locale === 'zh' ? ' 篇' : ' files'))),
|
|
3333
|
+
h(AnimatedDisclosure, { open: open },
|
|
3334
|
+
h('div', { style: { paddingTop: '8px' } },
|
|
3335
|
+
h('div', { 'data-dam-hint': '' }, t('clickDateViewLog')),
|
|
3336
|
+
list.map(function (log) {
|
|
3337
|
+
return h(Card, { key: log.date, title: log.date + ' · ' + fmtSize(log.size) },
|
|
3338
|
+
h('button', { 'data-dam-btn': '', onClick: function () { props.onOpenFile(log.date + '.md') } }, t('view')))
|
|
3339
|
+
}))))
|
|
2486
3340
|
}
|
|
3341
|
+
|
|
2487
3342
|
function pathName(p) { var parts = String(p).split(/[\\/]/); return parts[parts.length - 1] }
|
|
2488
3343
|
|
|
3344
|
+
// ── ★R1–R6(2026-09-20 用户「看不懂只能盲确认」)审批面人话化辅助 ────────
|
|
3345
|
+
// R3:stage 枚举 → 中文。
|
|
3346
|
+
function stageLabel(stage) {
|
|
3347
|
+
var m = { observed: 'hubStageObserved', candidate: 'hubStageCandidate', validated: 'hubStageValidated', active: 'hubStageActive', deprecated: 'hubStageDeprecated' }
|
|
3348
|
+
return m[stage] ? t(m[stage]) : String(stage || '')
|
|
3349
|
+
}
|
|
3350
|
+
// R2:reasonCodes → 人话。**为什么不能晋升**必须看得见。
|
|
3351
|
+
// 后端只回传机器码(如 diversity-below-3),这里翻成具体数字与下一步动作。
|
|
3352
|
+
function whyNotPromotable(promotion) {
|
|
3353
|
+
if (!promotion) return ''
|
|
3354
|
+
var d = promotion.detail || {}
|
|
3355
|
+
var codes = promotion.reasonCodes || []
|
|
3356
|
+
for (var i = 0; i < codes.length; i++) {
|
|
3357
|
+
var c = codes[i]
|
|
3358
|
+
if (c === 'observation-only') return t('hubWhyObservationOnly')
|
|
3359
|
+
if (c === 'deprecated') return t('hubStageDeprecated')
|
|
3360
|
+
if (c === 'no-success-criteria') return t('hubWhyNoCriteria')
|
|
3361
|
+
if (c === 'has-correction') return t('hubWhyHasCorrection')(d.corrections || 0)
|
|
3362
|
+
if (c.indexOf('high-risk-awaiting-approval') === 0) return t('hubWhyHighRisk')
|
|
3363
|
+
if (c.indexOf('diversity-below-') === 0) return t('hubWhyDiversity')(d.diversity != null ? d.diversity : 0, d.need != null ? d.need : 0)
|
|
3364
|
+
if (c.indexOf('success-below-') === 0) return t('hubWhySuccess')(d.successCount != null ? d.successCount : 0, d.need != null ? d.need : 0)
|
|
3365
|
+
if (c.indexOf('correction-rate-') === 0) return t('hubWhyCorrectionRate')(String(c).replace('correction-rate-', ''), d.cap != null ? d.cap : '')
|
|
3366
|
+
if (c === 'model-authorized') continue
|
|
3367
|
+
}
|
|
3368
|
+
if (promotion.decision === 'promote') return t('hubWhyCanPromote')
|
|
3369
|
+
// ★C(2026-09-22):把「关在哪一类门」说透 —— 统计门可人工越过,结构门不可。
|
|
3370
|
+
// 没有这一段时,界面上"一个按钮都没有",用户只能猜(这正是"手动通道被关掉"的观感来源)。
|
|
3371
|
+
if (promotion.decision === 'keep') {
|
|
3372
|
+
var head = codes.length ? codes.join(' · ') : ''
|
|
3373
|
+
if (promotion.overridable === true) return head + ' | ' + t('hubWhyOverridable')
|
|
3374
|
+
if (promotion.gateKind === 'structural') return head + ' | ' + t('hubWhyStructural')
|
|
3375
|
+
return head
|
|
3376
|
+
}
|
|
3377
|
+
return codes.length ? codes.join(' · ') : ''
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
// ── ★R7(2026-09-20 用户要求「用户级硬性约束须能自行增删改」)─────────────
|
|
3381
|
+
// 这一段规则**每轮无条件注入、不走语义层** ⇒ 过时条目不会被自动淘汰、
|
|
3382
|
+
// AI(memory_user)也可能写错 ⇒ 必须让用户能自己改。
|
|
3383
|
+
// 接口与将来换风格无关:本面板只用 data-dam-* 语义属性,样式由外层决定。
|
|
3384
|
+
function RulesEditPanel(props) {
|
|
3385
|
+
var nonce = props && props.nonce ? props.nonce : 0
|
|
3386
|
+
var dataPair = useState(null); var data = dataPair[0]; var setData = dataPair[1]
|
|
3387
|
+
var errPair = useState(''); var err = errPair[0]; var setErr = errPair[1]
|
|
3388
|
+
var busyPair = useState(false); var busy = busyPair[0]; var setBusy = busyPair[1]
|
|
3389
|
+
var msgPair = useState(''); var msg = msgPair[0]; var setMsg = msgPair[1]
|
|
3390
|
+
var draftPair = useState({}); var draft = draftPair[0]; var setDraft = draftPair[1]
|
|
3391
|
+
var newPair = useState(''); var newText = newPair[0]; var setNewText = newPair[1]
|
|
3392
|
+
var tickPair = useState(0); var tick = tickPair[0]; var setTick = tickPair[1]
|
|
3393
|
+
useEffect(function () {
|
|
3394
|
+
var alive = true
|
|
3395
|
+
apiGet(API.rulesList, {}).then(function (r) {
|
|
3396
|
+
if (!alive) return
|
|
3397
|
+
if (r && r.error) { setErr(r.error) } else { setData(r); setErr('') }
|
|
3398
|
+
}).catch(function (e) { if (alive) setErr(String(e && e.message ? e.message : e)) })
|
|
3399
|
+
return function () { alive = false }
|
|
3400
|
+
}, [nonce, tick])
|
|
3401
|
+
function apply(op, payload) {
|
|
3402
|
+
setBusy(true); setMsg('')
|
|
3403
|
+
apiPost(API.rulesApply, Object.assign({ op: op }, payload || {})).then(function (r) {
|
|
3404
|
+
setBusy(false)
|
|
3405
|
+
if (r && r.error) { setMsg('❌ ' + r.error); return }
|
|
3406
|
+
// ★即时回显(用户既有硬性偏好):直接用餐后端返回的新列表,不等重新拉整页
|
|
3407
|
+
setData({ path: data && data.path, items: r.items, preview: r.preview })
|
|
3408
|
+
setMsg('✅ ' + (r.result || 'ok'))
|
|
3409
|
+
setDraft({})
|
|
3410
|
+
}).catch(function (e) { setBusy(false); setMsg('❌ ' + String(e && e.message ? e.message : e)) })
|
|
3411
|
+
}
|
|
3412
|
+
if (err) return h(Card, { title: t('rulesTitle') }, h('div', { 'data-dam-hint': '' }, err))
|
|
3413
|
+
if (!data) return h(Card, { title: t('rulesTitle') }, h('div', { 'data-dam-hint': '' }, '…'))
|
|
3414
|
+
var items = data.items || []
|
|
3415
|
+
var rows = []
|
|
3416
|
+
rows.push(h('div', { 'data-dam-hint': '' }, t('rulesIntro')))
|
|
3417
|
+
rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '4px' } }, t('rulesPath') + (data.path || '')))
|
|
3418
|
+
if (msg) rows.push(h('div', { key: 'msg', 'data-dam-hint': '', style: { marginTop: '6px' } }, msg))
|
|
3419
|
+
if (!items.length) rows.push(h('div', { key: 'empty', 'data-dam-hint': '', style: { marginTop: '8px' } }, t('rulesEmpty')))
|
|
3420
|
+
for (var i = 0; i < items.length; i++) {
|
|
3421
|
+
(function (it, idx) {
|
|
3422
|
+
var editing = draft[idx] != null
|
|
3423
|
+
var srcTag = it.source === 'ai' ? t('rulesSourceAi') : t('rulesSourceUser')
|
|
3424
|
+
var head = h('div', { 'data-dam-hint': '', style: { fontSize: '12px', opacity: .75 } },
|
|
3425
|
+
'#' + (idx + 1) + ' · ' + srcTag + (it.dateSection ? ' · ' + it.dateSection : ''))
|
|
3426
|
+
var bodyEl
|
|
3427
|
+
if (editing) {
|
|
3428
|
+
bodyEl = h('div', null,
|
|
3429
|
+
h('textarea', {
|
|
3430
|
+
'data-dam-input': '',
|
|
3431
|
+
value: draft[idx],
|
|
3432
|
+
rows: 3,
|
|
3433
|
+
style: { width: '100%', boxSizing: 'border-box' },
|
|
3434
|
+
onInput: function (e) { var d = Object.assign({}, draft); d[idx] = e.target.value; setDraft(d) },
|
|
3435
|
+
}),
|
|
3436
|
+
h('button', { 'data-dam-btn': '', disabled: busy, onClick: function () { apply('update', { index: idx, text: draft[idx] }) } }, t('rulesSave')),
|
|
3437
|
+
' ',
|
|
3438
|
+
h('button', { 'data-dam-btn': '', onClick: function () { var d = Object.assign({}, draft); delete d[idx]; setDraft(d) } }, t('rulesCancel')))
|
|
3439
|
+
} else {
|
|
3440
|
+
bodyEl = h('div', null,
|
|
3441
|
+
h('div', { 'data-dam-content': '' }, it.text),
|
|
3442
|
+
h('button', { 'data-dam-btn': '', onClick: function () { var d = Object.assign({}, draft); d[idx] = it.text; setDraft(d) } }, t('rulesEdit')),
|
|
3443
|
+
' ',
|
|
3444
|
+
h('button', {
|
|
3445
|
+
'data-dam-btn': '',
|
|
3446
|
+
disabled: busy,
|
|
3447
|
+
onClick: function () {
|
|
3448
|
+
// ★R7-4:该层渲染器(renderRulesSectionPre)**不认任何状态标记** ⇒
|
|
3449
|
+
// 软删标记会被当正文注入模型。故删除**真删**且不可撤销 ⇒ 二次确认。
|
|
3450
|
+
if (typeof window !== 'undefined' && !window.confirm(t('rulesConfirmDelete'))) return
|
|
3451
|
+
apply('remove', { index: idx })
|
|
3452
|
+
},
|
|
3453
|
+
}, t('rulesDelete')))
|
|
3454
|
+
}
|
|
3455
|
+
rows.push(h('div', { key: 'r' + idx, 'data-dam-rule': '', style: { marginTop: '8px', paddingTop: '8px', borderTop: '1px solid rgba(128,128,128,.25)' } }, head, bodyEl))
|
|
3456
|
+
})(items[i], i)
|
|
3457
|
+
}
|
|
3458
|
+
rows.push(h('div', { key: 'add', style: { marginTop: '12px' } },
|
|
3459
|
+
h('div', { 'data-dam-hint': '' }, t('rulesAddHint')),
|
|
3460
|
+
h('textarea', {
|
|
3461
|
+
'data-dam-input': '',
|
|
3462
|
+
value: newText,
|
|
3463
|
+
rows: 2,
|
|
3464
|
+
placeholder: t('rulesAddPlaceholder'),
|
|
3465
|
+
style: { width: '100%', boxSizing: 'border-box' },
|
|
3466
|
+
onInput: function (e) { setNewText(e.target.value) },
|
|
3467
|
+
}),
|
|
3468
|
+
h('button', {
|
|
3469
|
+
'data-dam-btn': '',
|
|
3470
|
+
disabled: busy || !String(newText || '').trim(),
|
|
3471
|
+
onClick: function () { apply('add', { text: newText }); setNewText('') },
|
|
3472
|
+
}, t('rulesAdd'))))
|
|
3473
|
+
// ★R7-6:预览「下一轮注入会变成什么样」
|
|
3474
|
+
rows.push(h('div', { key: 'pvh', 'data-dam-hint': '', style: { marginTop: '12px' } }, t('rulesPreviewHint')))
|
|
3475
|
+
rows.push(h('pre', { key: 'pv', 'data-dam-content': '', style: { whiteSpace: 'pre-wrap' } }, data.preview || ''))
|
|
3476
|
+
return h(Card, { title: t('rulesTitle') }, h('div', null, rows))
|
|
3477
|
+
}
|
|
3478
|
+
|
|
2489
3479
|
// M8 记忆中枢页签:展示三层记忆(经历/事实/技能)概览。数据源=/memory-hub 只读投影。
|
|
2490
3480
|
function MemoryHubTab(props) {
|
|
2491
3481
|
var nonce = props && props.nonce ? props.nonce : 0
|
|
@@ -2551,12 +3541,57 @@ window.__ModuleLoader__.load({
|
|
|
2551
3541
|
pipeline.map(function (p) {
|
|
2552
3542
|
var ev = p.evidence || {}
|
|
2553
3543
|
return h('div', { 'data-dam-content': '', key: p.procedureId, style: { padding: '5px 0', borderBottom: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(255,255,255,.16)) 40%, transparent)' } },
|
|
2554
|
-
|
|
2555
|
-
|
|
3544
|
+
// ★R1:技能名 + 可读标注;★R3:stage 给中文
|
|
3545
|
+
h('div', null, h('b', null, p.title), h('span', { 'data-dam-hint': '', style: { marginLeft: '6px' } },
|
|
3546
|
+
'[' + stageLabel(p.stage) + ']'
|
|
3547
|
+
+ (p.observationOnly ? (locale === 'zh' ? ' · 观察(不参与自动晋升)' : ' · observation-only') : '')
|
|
3548
|
+
+ (p.pinned ? ' 📌' : '') + (p.riskLevel === 'high' ? ' ⚠' : '')),
|
|
3549
|
+
// ★R5:evidence 给人话(不是裸数字)
|
|
3550
|
+
h('span', { 'data-dam-hint': '', style: { marginLeft: '6px' } }, t('hubEvLine')(ev))),
|
|
3551
|
+
// ★R2:**为什么不能晋升** —— 必须在界面上说清楚
|
|
3552
|
+
(function () {
|
|
3553
|
+
var why = whyNotPromotable(p.promotion)
|
|
3554
|
+
if (!why) return null
|
|
3555
|
+
var ok = p.promotion && p.promotion.decision === 'promote'
|
|
3556
|
+
return h('div', { 'data-dam-hint': '', style: { marginTop: '4px', color: ok ? 'inherit' : 'var(--dsw-alias-text-warning, #d90)', opacity: .95 } },
|
|
3557
|
+
h('b', null, t('hubWhyTitle')), why)
|
|
3558
|
+
})(),
|
|
3559
|
+
// ★R4:预览「晋升后会注入什么」(真实 steps / successCriteria)
|
|
3560
|
+
((p.steps && p.steps.length) || (p.successCriteria && p.successCriteria.length))
|
|
3561
|
+
? h('details', { style: { marginTop: '4px' } },
|
|
3562
|
+
h('summary', { 'data-dam-hint': '', style: { cursor: 'pointer' } }, t('hubInjectPreview')),
|
|
3563
|
+
h('div', { 'data-dam-content': '', style: { marginTop: '4px', paddingLeft: '10px' } },
|
|
3564
|
+
(p.steps || []).map(function (s, si) { return h('div', { key: 's' + si }, (si + 1) + '. ' + s) }),
|
|
3565
|
+
(p.successCriteria && p.successCriteria.length)
|
|
3566
|
+
? h('div', { style: { marginTop: '4px' } }, h('b', null, t('hubInjectCriteria')), (p.successCriteria || []).join(' / '))
|
|
3567
|
+
: null))
|
|
3568
|
+
: h('div', { 'data-dam-hint': '', style: { marginTop: '4px', opacity: .7 } }, t('hubNoSteps')),
|
|
2556
3569
|
h('div', { style: { display: 'flex', gap: '6px', marginTop: '5px' } },
|
|
2557
3570
|
// issue #30:纯 episode 观察行**结构上**不可能晋升(无 successCriteria),
|
|
2558
3571
|
// 继续显示"晋升"按钮只会让人反复点击却看不到变化 ⇒ 直接隐藏并打观察标。
|
|
2559
|
-
|
|
3572
|
+
// ★ 2026-09-21 bugfix(A-9):原条件只挡 observationOnly,与 store 真实门限**不一致** ——
|
|
3573
|
+
// 缺 successCriteria / 有 correction / correctionRate 超限的条目照样显示按钮,
|
|
3574
|
+
// 用户点下去只得到一行 keep 灰字,感受即「批准不了」。
|
|
3575
|
+
// 现改为**直接依赖只读投影** `p.promotion`(宿主 memory-hub 用 evaluatePromotion 生成,
|
|
3576
|
+
// 纯只读、不写盘):decision==='promote' 才给按钮,否则界面已由 R2 区块说明原因。
|
|
3577
|
+
// fail-soft:投影缺失(旧宿主/异常)时退回原 observationOnly 判据,不做过度拦截。
|
|
3578
|
+
(!p.observationOnly && (!p.promotion || p.promotion.decision === 'promote'))
|
|
3579
|
+
&& h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px' }, onClick: function () { hubAct('promote', p.procedureId) } }, locale === 'zh' ? '晋升' : 'promote'),
|
|
3580
|
+
// ★2026-09-22 补接线(前端接线审计 P0-1):宿主已提供 approve 原语
|
|
3581
|
+
// (index.js 白名单含 action:'approve' + procedure-store-pre approve('user')),
|
|
3582
|
+
// 而上面「晋升」按钮门控在 decision==='promote' ⇒ 高风险条目(decision==='ask')
|
|
3583
|
+
// 在界面上是死路:用户看得到「待人工批准」却没有任何按钮可点。
|
|
3584
|
+
(p.promotion && p.promotion.decision === 'ask')
|
|
3585
|
+
&& h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px', fontWeight: 650 }, onClick: function () { hubAct('approve', p.procedureId) } }, locale === 'zh' ? '批准(人工)' : 'approve (manual)'),
|
|
3586
|
+
// ★B/C(2026-09-22):人工**强制晋升** —— 只在「拦它的门**全是统计门**」时给出。
|
|
3587
|
+
// 判据来自宿主只读投影 p.promotion.overridable(memory-hub 用 promotionOverrideView 生成,
|
|
3588
|
+
// 只读、零副作用、且**不重复实现门限** ⇒ 结构上不可能与 promote() 漂移)。
|
|
3589
|
+
// 结构门拦(已弃用 / 观察型 / 无 successCriteria / 有纠正记录)时**不给按钮** ——
|
|
3590
|
+
// 否则就是再造一个「点了没反应」的假通道(A-9 收掉旧按钮的正是这个原因)。
|
|
3591
|
+
// 动作走 index.js 白名单 `force-promote` → promote(pid, {}, { authorizedBy:'user' }),
|
|
3592
|
+
// 只越过 diversity/success 两道统计门,并在条目上留 `authorizedBy:'user'` 痕迹。
|
|
3593
|
+
(p.promotion && p.promotion.overridable === true)
|
|
3594
|
+
&& h('button', { 'data-dam-btn': '', title: t('hubForcePromoteTitle'), style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px', fontWeight: 650 }, onClick: function () { hubAct('force-promote', p.procedureId) } }, t('hubForcePromote')),
|
|
2560
3595
|
!p.observationOnly && p.stage === 'validated' && h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px' }, onClick: function () { hubAct('activate', p.procedureId) } }, locale === 'zh' ? '直接激活' : 'activate'),
|
|
2561
3596
|
h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px', opacity: .75 }, onClick: function () { hubAct('deprecate', p.procedureId) } }, locale === 'zh' ? '弃用' : 'deprecate'),
|
|
2562
3597
|
h('button', { 'data-dam-btn': '', style: { fontSize: 'calc(10.5px * var(--dam-scale))', padding: '3px 10px', opacity: .75 }, onClick: function () { hubAct('pin', p.procedureId, !p.pinned) } }, p.pinned ? (locale === 'zh' ? '取消置顶' : 'unpin') : (locale === 'zh' ? '置顶' : 'pin'))))
|
|
@@ -2570,6 +3605,18 @@ window.__ModuleLoader__.load({
|
|
|
2570
3605
|
factList.length ? factList.slice(0, 6).map(function (f) {
|
|
2571
3606
|
return h('div', { 'data-dam-content': '', key: f.factId, style: { padding: '3px 0' } }, f.subject + ' · ' + f.predicate + (f.object ? ' · ' + f.object : ''))
|
|
2572
3607
|
}) : h('div', { 'data-dam-content': '' }, t('hubFactsEmpty'))))
|
|
3608
|
+
// ★P0-3a 补接线(2026-09-22):事实保留上限淘汰台账。宿主早已把它放进 /memory-hub 应答
|
|
3609
|
+
// (memory-hub.js:245 → stores.facts.getStats()),前端此前零渲染
|
|
3610
|
+
// ⇒「事实被悄悄淘汰了」在界面上无处可查。pruneProtected>0 表示已轮到删「重要项」⇒ 上限偏低。
|
|
3611
|
+
var factStats = (facts && facts.stats) || null
|
|
3612
|
+
if (factStats && (Number(factStats.pruned) > 0 || Number(factStats.pruneProtected) > 0)) {
|
|
3613
|
+
var fpPruned = Number(factStats.pruned) || 0
|
|
3614
|
+
var fpProtected = Number(factStats.pruneProtected) || 0
|
|
3615
|
+
rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '6px', color: fpProtected > 0 ? 'var(--dsw-alias-state-warn-primary, #e8c584)' : undefined } },
|
|
3616
|
+
locale === 'zh'
|
|
3617
|
+
? '已按保留上限淘汰 ' + fpPruned + ' 条事实' + (fpProtected > 0 ? ',其中 ' + fpProtected + ' 条属重要/未撤销条目 —— 上限偏低,建议到 设置 → 自动记忆引擎 调高「事实保留上限」' : '')
|
|
3618
|
+
: fpPruned + ' fact(s) evicted by the retention cap' + (fpProtected > 0 ? ', incl. ' + fpProtected + ' important/unrevoked — cap too low; raise "Fact retention limit" under Settings → Semantic engine' : '')))
|
|
3619
|
+
}
|
|
2573
3620
|
if (facts && facts.pendingConflicts && facts.pendingConflicts.length) {
|
|
2574
3621
|
rows.push(h('div', { 'data-dam-hint': '', style: { color: 'var(--dsw-alias-state-warn-primary, #e8c584)' } }, t('hubConflicts') + ': ' + facts.pendingConflicts.length))
|
|
2575
3622
|
}
|
|
@@ -2584,7 +3631,8 @@ window.__ModuleLoader__.load({
|
|
|
2584
3631
|
// 统计
|
|
2585
3632
|
rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '12px', opacity: .7 } },
|
|
2586
3633
|
(locale === 'zh' ? '中枢统计: ' : 'Hub stats: ') + (data.stats ? JSON.stringify(data.stats) : '')))
|
|
2587
|
-
|
|
3634
|
+
// 大屏分列:卡片流页签(见 CSS [data-dam-flow]);非卡片子元素仍整行。
|
|
3635
|
+
return h('div', { 'data-dam-flow': '' }, rows)
|
|
2588
3636
|
}
|
|
2589
3637
|
|
|
2590
3638
|
// M10 存储管理页签(2026-08-30 P3):数据源=/storage-manage(loopback 只读投影 + 三类动作)。
|
|
@@ -2673,7 +3721,8 @@ window.__ModuleLoader__.load({
|
|
|
2673
3721
|
rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '12px', opacity: .7 } },
|
|
2674
3722
|
(locale === 'zh' ? '最近动作: ' : 'recent: ') + audit.map(function (a) { return a.action }).join(', ')))
|
|
2675
3723
|
}
|
|
2676
|
-
|
|
3724
|
+
// 大屏分列:卡片流页签(见 CSS [data-dam-flow]);非卡片子元素仍整行。
|
|
3725
|
+
return h('div', { 'data-dam-flow': '' }, rows)
|
|
2677
3726
|
}
|
|
2678
3727
|
|
|
2679
3728
|
function NotesTab() {
|
|
@@ -2992,7 +4041,21 @@ window.__ModuleLoader__.load({
|
|
|
2992
4041
|
autoCfg ? h('label', { 'data-dam-hint': '', style: { marginLeft: '8px' } }, t('autoContThreshold') + ' ', h('input', { 'data-dam-input': '', type: 'number', min: 0.5, max: 0.95, step: 0.05, value: String(autoCfg.threshold), onChange: function (e) { var v = Number(e.target.value); if (v >= 0.5 && v <= 0.95) autoSave({ autoContinueThreshold: v }) } })) : null,
|
|
2993
4042
|
]),
|
|
2994
4043
|
]))
|
|
2995
|
-
|
|
4044
|
+
// ★2026-09-17(L2 修 bug①): 面板侧同样**按真实 reason 分支** —— 旧实现一律显示 t('planDisabled')
|
|
4045
|
+
// (「白板未启用」), 与看板那句同源的误导。后端现在会带 reason: 'legacy-mode' / 'error'。
|
|
4046
|
+
if (!data.enabled) {
|
|
4047
|
+
var disMsg = (function () {
|
|
4048
|
+
var r = data && data.reason
|
|
4049
|
+
if (r === 'legacy-mode') {
|
|
4050
|
+
return locale === 'zh'
|
|
4051
|
+
? '当前是旧版白板(legacy)档。看板需要切到「新版看板(dsh-graph)」——用上方开关切换, 重启 dsh web 后生效。'
|
|
4052
|
+
: 'Legacy board mode. The kanban needs the graph board — toggle it above, then restart dsh web.'
|
|
4053
|
+
}
|
|
4054
|
+
if (r === 'error') return (locale === 'zh' ? '白板加载出错: ' : 'Whiteboard load error: ') + String((data && data.error) || '')
|
|
4055
|
+
return t('planDisabled')
|
|
4056
|
+
})()
|
|
4057
|
+
return h('div', null, [switchCard, h(Card, { title: t('planTitle') }, h('div', { 'data-dam-hint': '' }, disMsg))])
|
|
4058
|
+
}
|
|
2996
4059
|
var wl = data.waterLevel || {}
|
|
2997
4060
|
// 2026-09-13(工作区切换修复):宿主对当前会话解析不出工作区身份时(wsBound:false),如实提示
|
|
2998
4061
|
// 而不是渲染另一个会话/启动目录下的白板内容(旧全局单值行为的残余观感)。
|
|
@@ -3027,7 +4090,7 @@ window.__ModuleLoader__.load({
|
|
|
3027
4090
|
tg.tag + ' ×' + tg.count)
|
|
3028
4091
|
})))
|
|
3029
4092
|
tagKids.push(h('div', { 'data-dam-hint': '', style: { marginTop: '4px' } },
|
|
3030
|
-
locale === 'zh' ? '标签来自账本/白板行内标记(tag:xxx / type:xxx / topic:xxx)。模型侧可用
|
|
4093
|
+
locale === 'zh' ? '标签来自账本/白板行内标记(tag:xxx / type:xxx / topic:xxx)。模型侧可用 memory_expand(tag) 按标签展开条目、memory_trace(id) 回溯来源与版本链。' : 'Tags come from inline markers (tag:xxx / type:xxx / topic:xxx). The model can use memory_expand(tag) to expand by tag and memory_trace(id) to trace origin and version chain.'))
|
|
3031
4094
|
} else {
|
|
3032
4095
|
tagKids.push(h('div', { 'data-dam-muted': '' }, locale === 'zh' ? '尚无标签——在账本/白板里写 tag:xxx 后重新写入即出现。' : 'No tags yet — write tag:xxx in a ledger/plan and re-save.'))
|
|
3033
4096
|
}
|
|
@@ -3087,7 +4150,8 @@ window.__ModuleLoader__.load({
|
|
|
3087
4150
|
}
|
|
3088
4151
|
// 2026-09-14 修复:开关卡此前只在「未启用」分支被返回 ⇒ 白板一旦开启,白板页就再也看不到开关(开得了、关不掉)。
|
|
3089
4152
|
// 开关卡必须在**两种**状态下都渲染 —— 它是这个页面上唯一的开关入口。
|
|
3090
|
-
|
|
4153
|
+
// 大屏分列:卡片流页签(见 CSS [data-dam-flow]);非卡片子元素仍整行。
|
|
4154
|
+
return h('div', { 'data-dam-flow': '' }, [switchCard].concat(rows))
|
|
3091
4155
|
}
|
|
3092
4156
|
|
|
3093
4157
|
function ReflectionsTab() {
|
|
@@ -3143,7 +4207,8 @@ window.__ModuleLoader__.load({
|
|
|
3143
4207
|
})(data.reflections[i])
|
|
3144
4208
|
}
|
|
3145
4209
|
}
|
|
3146
|
-
|
|
4210
|
+
// 大屏分列:卡片流页签(见 CSS [data-dam-flow]);非卡片子元素仍整行。
|
|
4211
|
+
return h('div', { 'data-dam-flow': '' }, rows)
|
|
3147
4212
|
}
|
|
3148
4213
|
|
|
3149
4214
|
function SearchTab() {
|
|
@@ -3171,7 +4236,8 @@ window.__ModuleLoader__.load({
|
|
|
3171
4236
|
apiPost(API.smartRecall, { query: q.trim() }).then(function (d) { setSmart(d); setBusy(false) })
|
|
3172
4237
|
.catch(function (e) { setSmart({ answer: t('searchFailed') + e.message, keywords: [], hits: [] }); setBusy(false) })
|
|
3173
4238
|
}
|
|
3174
|
-
|
|
4239
|
+
// 大屏分列:卡片流页签(见 CSS [data-dam-flow]);非卡片子元素仍整行。
|
|
4240
|
+
return h('div', { 'data-dam-flow': '' },
|
|
3175
4241
|
h('div', { 'data-dam-row': '' },
|
|
3176
4242
|
h('input', { 'data-dam-input': '', placeholder: t('searchPlaceholder'), value: q, onChange: function (e) { setQ(e.target.value) }, onKeyDown: function (e) { if (e.key === 'Enter') search() } }),
|
|
3177
4243
|
h('button', { 'data-dam-btn': '', onClick: search, disabled: busy }, t('searchBtn')),
|
|
@@ -3181,7 +4247,16 @@ window.__ModuleLoader__.load({
|
|
|
3181
4247
|
h('div', { 'data-dam-content': '' }, smart.answer || t('empty')),
|
|
3182
4248
|
smart.keywords && smart.keywords.length ? h('div', { 'data-dam-hint': '', style: { marginTop: '6px' } }, t('keywordsLabel') + smart.keywords.join(' / ')) : null,
|
|
3183
4249
|
Array.isArray(smart.hits) && smart.hits.length ? h('div', { 'data-dam-hint': '', style: { marginTop: '6px' } },
|
|
3184
|
-
|
|
4250
|
+
// ★P1-2 补接线(2026-09-22):宿主**早已**把结论层标记放进 smartRecall 的 hits 投影
|
|
4251
|
+
// (index.js:6469 → l0-extract.js:295+,文案形如「⚠已撤回」),前端此前零渲染
|
|
4252
|
+
// ⇒「这条结论已失效」在界面上完全看不出来,用户会把它当有效结论读。mark 自带文案 ⇒ 零 i18n。
|
|
4253
|
+
smart.hits.map(function (hit, i) {
|
|
4254
|
+
if (!hit || typeof hit !== 'object') return null
|
|
4255
|
+
var mark = hit.mark === undefined || hit.mark === null ? '' : String(hit.mark).trim()
|
|
4256
|
+
return h('div', { key: i, style: { marginTop: '3px', overflowWrap: 'anywhere' } },
|
|
4257
|
+
mark ? h('span', { 'data-dam-hint': '', style: { marginRight: '5px', fontWeight: 600, color: 'var(--dsw-alias-state-warn-primary, #e8c584)' } }, mark) : null,
|
|
4258
|
+
'· [' + String(hit.where || '') + '] ' + String(hit.line || ''))
|
|
4259
|
+
})) : null)
|
|
3185
4260
|
: null,
|
|
3186
4261
|
result ? h(Card, { title: t('resultTitle') }, h('div', { 'data-dam-content': '' }, result)) : null)
|
|
3187
4262
|
}
|
|
@@ -3303,7 +4378,8 @@ window.__ModuleLoader__.load({
|
|
|
3303
4378
|
h('div', { 'data-dam-graph-toolbar': '' }, h('span', { 'data-dam-hint': '' }, locale === 'zh' ? '思维导图大小' : 'Map size'), h('input', { type: 'range', min: '0.55', max: '1.75', step: '0.05', value: graphScale, 'aria-label': locale === 'zh' ? '调整思维导图大小' : 'Adjust map size', onChange: function (e) { setGraphScale(Number(e.target.value) || 1.0) } }), h('span', { 'data-dam-hint': '' }, Math.round(graphScale * 100) + '%'), h('button', { 'data-dam-btn': '', title: locale === 'zh' ? '重置视图' : 'Reset view', onClick: function () { setGraphScale(1.0) } }, '⌂')),
|
|
3304
4379
|
h('div', { 'data-dam-hint': '' }, locale === 'zh' ? '点击工作区卡片查看摘要;拖动滑块调整图的大小。' : 'Select a workspace card for details; use the slider to resize the map.'),
|
|
3305
4380
|
h(WorkspaceGraph, { workspaces: workspaces, graph: data.graph, scale: graphScale, onSelect: function (ws) { setSelected(ws); var n = Object.assign({}, collapsedByPath); n[ws.path] = false; setCollapsedByPath(n) } }),
|
|
3306
|
-
|
|
4381
|
+
// ★大屏分列(2026-09-22):工作区卡片(已带 [data-dam-card])在宽屏排成多列。
|
|
4382
|
+
h('div', { 'data-dam-flow': '', style: { marginTop: '12px' } }, workspaces.map(function (ws) {
|
|
3307
4383
|
var open = collapsedByPath[ws.path] === false
|
|
3308
4384
|
return h('div', { key: ws.path, 'data-dam-card': '', style: { marginBottom: '8px', padding: '0' } },
|
|
3309
4385
|
h('button', { 'data-dam-btn': '', 'aria-expanded': open ? 'true' : 'false', onClick: function () { var n = Object.assign({}, collapsedByPath); n[ws.path] = !open; setCollapsedByPath(n); setSelected(ws) }, style: { width: '100%', textAlign: 'left', padding: '10px 12px', fontWeight: 650 } }, (open ? '▾ ' : '▸ ') + ws.name + (ws.dateRange ? ' · ' + ws.dateRange : '')),
|
|
@@ -3321,6 +4397,7 @@ window.__ModuleLoader__.load({
|
|
|
3321
4397
|
'未分类': { color: 'var(--dsw-alias-label-secondary, #8a94a6)' },
|
|
3322
4398
|
}
|
|
3323
4399
|
function CalendarTab() {
|
|
4400
|
+
var zh = (locale || 'zh').indexOf('zh') === 0
|
|
3324
4401
|
var dataPair = useState(null)
|
|
3325
4402
|
var data = dataPair[0]
|
|
3326
4403
|
var setData = dataPair[1]
|
|
@@ -3447,16 +4524,61 @@ window.__ModuleLoader__.load({
|
|
|
3447
4524
|
// 日期格先打开当天时间轴;点击某个时间槽才创建事件。
|
|
3448
4525
|
if (dayView) {
|
|
3449
4526
|
var timeline = []
|
|
3450
|
-
|
|
4527
|
+
var dayAll = dayEntries(dayView)
|
|
4528
|
+
// ★2026-09-20 修 bug: 旧实现把时间轴写死为 `for (hourSlot = 7; hourSlot <= 22; ...)`。
|
|
4529
|
+
// 后果: 任何 23:00–06:59 或 time='--:--' 的事件**在当天视图里永不显示**——
|
|
4530
|
+
// 而用户的真实数据里就有 7 条 23:59 截止的作业(AMA1751 / BME1D02)。
|
|
4531
|
+
// 现改为**由数据驱动**: 先扫当天事件的真实小时, 用其 min/max 决定时间轴范围
|
|
4532
|
+
// (无事件时才回落到 07:00–22:00), 且任何落在范围外的事件单独成"时间轴外"一段。
|
|
4533
|
+
var hourNums = []
|
|
4534
|
+
var untimed = []
|
|
4535
|
+
for (var di = 0; di < dayAll.length; di++) {
|
|
4536
|
+
var tm = String(dayAll[di].time || '')
|
|
4537
|
+
var hh = parseInt(tm.slice(0, 2), 10)
|
|
4538
|
+
if (isNaN(hh)) { untimed.push(dayAll[di]); continue }
|
|
4539
|
+
hourNums.push(hh)
|
|
4540
|
+
}
|
|
4541
|
+
var hFrom = hourNums.length ? Math.min.apply(null, hourNums) : 7
|
|
4542
|
+
var hTo = hourNums.length ? Math.max.apply(null, hourNums) : 22
|
|
4543
|
+
if (hFrom > 7) hFrom = 7
|
|
4544
|
+
if (hTo < 22) hTo = 22
|
|
4545
|
+
// 每个小时槽: 支持同名 key 去重(同小时多事件)
|
|
4546
|
+
for (var hourSlot = hFrom; hourSlot <= hTo; hourSlot++) {
|
|
3451
4547
|
(function (hourValue) {
|
|
3452
4548
|
var hourText = String(hourValue).padStart(2, '0') + ':00'
|
|
3453
|
-
var
|
|
4549
|
+
var pad = String(hourValue).padStart(2, '0')
|
|
4550
|
+
var hourEvents = dayAll.filter(function (en) { return String(en.time || '').slice(0, 2) === pad })
|
|
3454
4551
|
timeline.push(h('div', { key: hourText, 'data-dam-calendar-slot': '', onClick: function () { setDayView(null); setDraft({ date: dayView, time: hourText, quadrant: '重要不紧急', title: '', location: '', reminder: '', note: '' }) } },
|
|
3455
4552
|
h('div', { style: { flex: '0 0 50px', fontSize: 'calc(11px * var(--dam-scale))', opacity: .65 } }, hourText),
|
|
3456
|
-
h('div', { style: { minHeight: '34px', flex: 1, borderLeft: '2px solid color-mix(in srgb, var(--dam-accent, #1d4ed8) 24%, transparent)', paddingLeft: '9px' } }, hourEvents.length ? hourEvents.map(function (en) { var qs = QUADRANT_STYLE[en.quadrant] || QUADRANT_STYLE['未分类']; return h('div', { key: en.time +
|
|
4553
|
+
h('div', { style: { minHeight: '34px', flex: 1, borderLeft: '2px solid color-mix(in srgb, var(--dam-accent, #1d4ed8) 24%, transparent)', paddingLeft: '9px' } }, hourEvents.length ? hourEvents.map(function (en, k) { var qs = QUADRANT_STYLE[en.quadrant] || QUADRANT_STYLE['未分类']; return h('div', { key: String(en.time) + '#' + k, 'data-dam-calendar-event': '', onClick: function (ev) { ev.stopPropagation(); toggleDone(en) }, style: { marginBottom: '4px', padding: '4px 7px', borderRadius: 6, background: 'color-mix(in srgb, ' + qs.color + ' 18%, transparent)', color: qs.color, textDecoration: en.done ? 'line-through' : 'none', opacity: en.done ? .55 : 1, cursor: 'pointer' } }, (en.time || '') + ' ' + en.title + (en.note ? ' · ' + en.note : '')) }) : h('span', { 'data-dam-hint': '' }, locale === 'zh' ? '点击添加事件' : 'Click to add event'))))
|
|
3457
4554
|
})(hourSlot)
|
|
3458
4555
|
}
|
|
3459
|
-
|
|
4556
|
+
// 时间轴外(无 time 或 time 无法解析)的事件单独列出 —— 不再静默丢失
|
|
4557
|
+
if (untimed.length) {
|
|
4558
|
+
timeline.push(h('div', { key: '__untimed', 'data-dam-calendar-slot': '', onClick: function () { setDayView(null); setDraft({ date: dayView, time: '23:59', quadrant: '重要不紧急', title: '', location: '', reminder: '', note: '' }) } },
|
|
4559
|
+
h('div', { style: { flex: '0 0 50px', fontSize: 'calc(11px * var(--dam-scale))', opacity: .65 } }, zh ? '未定时' : 'n/a'),
|
|
4560
|
+
h('div', { style: { minHeight: '34px', flex: 1, borderLeft: '2px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary, #d4a94f) 55%, transparent)', paddingLeft: '9px' } }, untimed.map(function (en, k) { var qs = QUADRANT_STYLE[en.quadrant] || QUADRANT_STYLE['未分类']; return h('div', { key: 'u' + k, 'data-dam-calendar-event': '', onClick: function (ev) { ev.stopPropagation(); toggleDone(en) }, style: { marginBottom: '4px', padding: '4px 7px', borderRadius: 6, background: 'color-mix(in srgb, ' + qs.color + ' 18%, transparent)', color: qs.color, textDecoration: en.done ? 'line-through' : 'none', opacity: en.done ? .55 : 1, cursor: 'pointer' } }, en.title + (en.note ? ' · ' + en.note : '')) }))))
|
|
4561
|
+
}
|
|
4562
|
+
// 当天前后翻页(原实现只能靠「先关掉浮层 → 再点另一个日期」, 多一次无效操作)
|
|
4563
|
+
var dPrev = new Date(dayView + 'T00:00:00'); dPrev.setDate(dPrev.getDate() - 1)
|
|
4564
|
+
var dNext = new Date(dayView + 'T00:00:00'); dNext.setDate(dNext.getDate() + 1)
|
|
4565
|
+
var fmtD = function (dt) { return dt.getFullYear() + '-' + String(dt.getMonth() + 1).padStart(2, '0') + '-' + String(dt.getDate()).padStart(2, '0') }
|
|
4566
|
+
var wk = (function (ds) {
|
|
4567
|
+
// ISO-8601 周序号(周一为一周之始, 第 1 周含当年首个周四)
|
|
4568
|
+
var dt = new Date(ds + 'T00:00:00')
|
|
4569
|
+
var t = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate())
|
|
4570
|
+
t.setDate(t.getDate() + 3 - ((t.getDay() + 6) % 7))
|
|
4571
|
+
var ws = new Date(t.getFullYear(), 0, 4)
|
|
4572
|
+
ws.setDate(ws.getDate() + 3 - ((ws.getDay() + 6) % 7))
|
|
4573
|
+
return 1 + Math.round((t - ws) / 604800000)
|
|
4574
|
+
})(dayView)
|
|
4575
|
+
rows.push(h('div', { 'data-dam-calendar-modal': '', style: { position: 'absolute', inset: 0, zIndex: 10, overflow: 'auto', borderRadius: '16px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-2, rgba(255,255,255,.9)) 78%, transparent)', backdropFilter: 'blur(20px) saturate(1.4)', WebkitBackdropFilter: 'blur(20px) saturate(1.4)', padding: '16px' } },
|
|
4576
|
+
h('div', { 'data-dam-row': '' },
|
|
4577
|
+
h('button', { 'data-dam-btn': '', title: zh ? '前一天' : 'previous day', onClick: function () { setDayView(fmtD(dPrev)) } }, '‹'),
|
|
4578
|
+
h('div', { style: { fontWeight: 700, flex: 1, textAlign: 'center' } }, dayView + ' · ' + (zh ? '第 ' + wk + ' 周' : 'W' + wk) + ' · ' + dayAll.length + (zh ? ' 项' : ' items')),
|
|
4579
|
+
h('button', { 'data-dam-btn': '', title: zh ? '后一天' : 'next day', onClick: function () { setDayView(fmtD(dNext)) } }, '›'),
|
|
4580
|
+
h('button', { 'data-dam-btn': '', onClick: function () { setDayView(null) } }, '✕')),
|
|
4581
|
+
timeline))
|
|
3460
4582
|
}
|
|
3461
4583
|
// 添加/编辑浮层(液态玻璃)
|
|
3462
4584
|
if (draft) {
|
|
@@ -3607,7 +4729,9 @@ window.__ModuleLoader__.load({
|
|
|
3607
4729
|
: null)))
|
|
3608
4730
|
})(sources[i])
|
|
3609
4731
|
}
|
|
3610
|
-
|
|
4732
|
+
// ★大屏分列(2026-09-22):本组件的 Card 由 `cards)` 直接铺进根节点 ⇒ 是根的**直接子元素**,
|
|
4733
|
+
// 在根上打 [data-dam-flow] 即可生效(与日志/中枢页签同款)。非卡片子元素(hint/按钮行/msg/err)仍整行。
|
|
4734
|
+
return h('div', { 'data-dam-flow': '' },
|
|
3611
4735
|
h('div', { 'data-dam-hint': '' }, t('connectHint')),
|
|
3612
4736
|
h('div', { 'data-dam-row': '' },
|
|
3613
4737
|
h('button', { 'data-dam-btn': '', onClick: importAll }, t('importAll')),
|
|
@@ -3693,12 +4817,97 @@ window.__ModuleLoader__.load({
|
|
|
3693
4817
|
h('button', { 'data-dam-tabs-arrow': '', title: locale === 'zh' ? '向右滚动模块' : 'Scroll modules right', 'aria-label': locale === 'zh' ? '向右滚动模块' : 'Scroll modules right', disabled: !canRight, onClick: function () { step(1) } }, '›'))
|
|
3694
4818
|
}
|
|
3695
4819
|
|
|
4820
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
4821
|
+
// 共享渲染件(2026-09-21):浮层与「记忆」会话页复用同一份页签表与内容体。
|
|
4822
|
+
// 抽出来的动机:两种承载面必须"同内容、同页签、同数据"(用户要求同步),
|
|
4823
|
+
// 若各写一份 body 分支,页签一多必然漂移 ⇒ 只留一处事实。
|
|
4824
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
4825
|
+
function MemoryTabBody(tab, nonce) {
|
|
4826
|
+
if (tab === 'overview') return h(OverviewTab, { nonce: nonce })
|
|
4827
|
+
if (tab === 'logs') return h(LogsTab)
|
|
4828
|
+
if (tab === 'refine') return h(RefineTab)
|
|
4829
|
+
if (tab === 'hub') return h(MemoryHubTab, { nonce: nonce })
|
|
4830
|
+
if (tab === 'storage') return h(StorageTab, { nonce: nonce })
|
|
4831
|
+
if (tab === 'notes') return h(NotesTab)
|
|
4832
|
+
if (tab === 'plan') return h(PlanTab)
|
|
4833
|
+
if (tab === 'reflections') return h(ReflectionsTab)
|
|
4834
|
+
if (tab === 'connect') return h(ConnectTab)
|
|
4835
|
+
if (tab === 'calendar') return h(CalendarTab)
|
|
4836
|
+
if (tab === 'workspaces') return h(WorkspaceTab)
|
|
4837
|
+
return h(SearchTab)
|
|
4838
|
+
}
|
|
4839
|
+
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')]]
|
|
4841
|
+
}
|
|
4842
|
+
|
|
4843
|
+
// 「记忆」会话页(2026-09-21 二次修正) —— 注册在 conversation.view 槽位,与「对话轨迹」「上下文」「白板看板」并列。
|
|
4844
|
+
// 用户诉求原话:「它现在是浮在整个页面上方的,而不是和对话轨迹、上下文、白板看板在一起,作为一个单独的一页」。
|
|
4845
|
+
// 宿主只给 shell.overlay 这类"盖在界面上"的槽位,做不出并列页 ⇒ 必须走 conversation.view。
|
|
4846
|
+
// 与浮层的关系:同一份 panelTab/数据/刷新 nonce,切页签两边同步;只在承载面上不同(页更宽)。
|
|
4847
|
+
function MemoryPageView(props) {
|
|
4848
|
+
var tick = useTick()
|
|
4849
|
+
var tab = controller.panelTab()
|
|
4850
|
+
var noncePair = useState(0)
|
|
4851
|
+
var nonce = noncePair[0]
|
|
4852
|
+
var setNonce = noncePair[1]
|
|
4853
|
+
var verPair = useState(null)
|
|
4854
|
+
var ver = verPair[0]
|
|
4855
|
+
var setVer = verPair[1]
|
|
4856
|
+
useEffect(function () { return controller.subscribe(tick[1]) }, [])
|
|
4857
|
+
useEffect(function () {
|
|
4858
|
+
var alive = true
|
|
4859
|
+
apiGet(API.updateCheck).then(function (d) { if (alive && d && d.current) setVer(d.current) }).catch(function () {})
|
|
4860
|
+
return function () { alive = false }
|
|
4861
|
+
}, [])
|
|
4862
|
+
var zh = locale === 'zh'
|
|
4863
|
+
var verBadge = ver ? 'v' + ver + '-ui' : ''
|
|
4864
|
+
var body = MemoryTabBody(tab, nonce)
|
|
4865
|
+
// 浮层开关(page ↔ both 互切):两个方向都不注销本页签(修自毁按钮)。
|
|
4866
|
+
var floatOn = controller.panelPos() === 'both'
|
|
4867
|
+
var floatLabel = floatOn ? (zh ? '收起浮层' : 'Hide card') : (zh ? '打开浮层' : 'Open card')
|
|
4868
|
+
var floatTitle = floatOn ? (zh ? '收起左下角浮层(保留本页)' : 'Hide bottom-left card (keep this page)') : (zh ? '同时打开左下角浮层(保留本页)' : 'Also open bottom-left card (keep this page)')
|
|
4869
|
+
var onFloatToggle = function () { controller.setPanelPos(floatOn ? 'page' : 'both') }
|
|
4870
|
+
var scaleAttr = fontScale in FONT_SCALES ? fontScale : 'md'
|
|
4871
|
+
return h('div', {
|
|
4872
|
+
'data-dam-page': '',
|
|
4873
|
+
'data-scale': scaleAttr,
|
|
4874
|
+
style: {
|
|
4875
|
+
'--dam-user-scale': FONT_SCALE_VALUES[fontScale] || '1',
|
|
4876
|
+
'--dam-accent': ACCENT_VALUES[accentTheme] || ACCENT_VALUES.deepseek,
|
|
4877
|
+
},
|
|
4878
|
+
},
|
|
4879
|
+
h('header', { 'data-dam-page-head': '' },
|
|
4880
|
+
h('strong', null, t('autoMemory')),
|
|
4881
|
+
h('span', { 'data-dam-hint': '', style: { fontSize: '10px', opacity: .55 } }, verBadge),
|
|
4882
|
+
h('span', { className: 'dam-spacer' }),
|
|
4883
|
+
h('button', { 'data-dam-btn': '', title: t('refresh'), onClick: function () { setNonce(nonce + 1) } }, '⟳'),
|
|
4884
|
+
// ★2026-09-22 修「自毁按钮」(方向文档 §3.1 硬性要求 1):原实现在 page 档点一下即把本页签注销
|
|
4885
|
+
// (conversation.view 仅在 page/both 档注册)⇒ 用户实测「上面的东西连按钮一起消失」。
|
|
4886
|
+
// 现改为 page ↔ both 互切:both 档页签始终保留,两个方向都不注销当前页。
|
|
4887
|
+
h('button', {
|
|
4888
|
+
'data-dam-btn': '', 'data-dam-float-toggle': floatOn ? 'on' : 'off',
|
|
4889
|
+
title: floatTitle,
|
|
4890
|
+
onClick: onFloatToggle,
|
|
4891
|
+
}, floatLabel)),
|
|
4892
|
+
// ★2026-09-22 版式:顶部横向滚动页签 → 左侧固定侧栏(用户原话「把原本放在上面的那个切换横条
|
|
4893
|
+
// 放到左侧,这样排版会更科学一点」)。浮层仍用 TabScroller(窄容器保持原版式),
|
|
4894
|
+
// 两者共享同一份 panelTab 状态 ⇒ 切分区两边同步(既有契约不变)。
|
|
4895
|
+
h('div', { 'data-dam-page-main': '' },
|
|
4896
|
+
h('nav', { 'data-dam-page-nav': '', 'aria-label': zh ? '记忆分区' : 'Memory sections' },
|
|
4897
|
+
MEMORY_TABS().map(function (item) {
|
|
4898
|
+
return h('button', { key: item[0], 'data-dam-nav-item': '', 'data-active': tab === item[0] ? 'true' : undefined, onClick: function () { controller.setPanelTab(item[0]) } }, item[1])
|
|
4899
|
+
})),
|
|
4900
|
+
h('div', { 'data-dam-page-content': '' },
|
|
4901
|
+
h('div', { 'data-dam-body': '' }, body))))
|
|
4902
|
+
}
|
|
4903
|
+
|
|
3696
4904
|
function MemoryPanel() {
|
|
3697
4905
|
var tick = useTick()
|
|
3698
4906
|
var panelRef = useRef(null)
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
var
|
|
4907
|
+
// 页签状态走模块级共享(2026-09-21):浮层与「记忆」会话页读同一个 panelTab ⇒ 两处切页互相同步。
|
|
4908
|
+
// (用户诉求原话:「两者共存…要保证同步」。不用两份 useState,从根上杜绝状态漂移。)
|
|
4909
|
+
var tab = controller.panelTab()
|
|
4910
|
+
var setTab = controller.setPanelTab
|
|
3702
4911
|
// 刷新 nonce:每次打开面板 / 点 ⟳ 时递增,驱动各页签重拉数据
|
|
3703
4912
|
var noncePair = useState(0)
|
|
3704
4913
|
var nonce = noncePair[0]
|
|
@@ -3707,6 +4916,7 @@ window.__ModuleLoader__.load({
|
|
|
3707
4916
|
var ver = verPair[0]
|
|
3708
4917
|
var setVer = verPair[1]
|
|
3709
4918
|
var g = controller.geom()
|
|
4919
|
+
var pos = controller.panelPos()
|
|
3710
4920
|
useEffect(function () { return controller.subscribe(tick[1]) }, [])
|
|
3711
4921
|
// 真实版本号(#15):面板头徽标不再硬编码,从 update-check 的 current(读包 version)取,失败留空
|
|
3712
4922
|
useEffect(function () {
|
|
@@ -3718,81 +4928,91 @@ window.__ModuleLoader__.load({
|
|
|
3718
4928
|
// 主题令牌 --dsw-alias-bg-layer-2 本身就是半透明的,面板文字几乎不可读。判别信号用
|
|
3719
4929
|
// 「令牌自身的 alpha」(增强模式显著低于普通模式的 0.86-0.9):< 0.65 时把面板背景提升到
|
|
3720
4930
|
// 0.96(保留色相)并弱化顶部高光。普通模式令牌不透明 → 不触发,液态玻璃观感零变化。
|
|
4931
|
+
// 2026-09-21:两种形态(浮层/顶部停靠条)各查一次 —— panelRef 只绑浮层,停靠条用选择器兜底,
|
|
4932
|
+
// 否则增强模式下顶栏模式会保持半透明、可读性退化。
|
|
3721
4933
|
useEffect(function () {
|
|
3722
4934
|
if (!panelOpen) return
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
var
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
4935
|
+
// 2026-09-21 二次修正:原「顶部停靠条」第二形态已废弃(改走 conversation.view 会话页),
|
|
4936
|
+
// 故此处只剩浮层一个元素需要兜底 —— 会话页在正常文档流里,背景由宿主给,无需提升不透明度。
|
|
4937
|
+
var els = panelRef.current ? [panelRef.current] : []
|
|
4938
|
+
for (var j = 0; j < els.length; j++) {
|
|
4939
|
+
var el = els[j]
|
|
4940
|
+
if (!el) continue
|
|
4941
|
+
try {
|
|
4942
|
+
var cs = getComputedStyle(el)
|
|
4943
|
+
var token = cs.getPropertyValue('--dsw-alias-bg-layer-2')
|
|
4944
|
+
var col = parseCssColor((token || '').trim() || cs.backgroundColor)
|
|
4945
|
+
if (col && col.a < 0.65) {
|
|
4946
|
+
el.style.background = 'rgba(' + col.r + ', ' + col.g + ', ' + col.b + ', 0.96)'
|
|
4947
|
+
el.setAttribute('data-solid', 'true')
|
|
4948
|
+
} else {
|
|
4949
|
+
el.style.background = ''
|
|
4950
|
+
el.removeAttribute('data-solid')
|
|
4951
|
+
}
|
|
4952
|
+
} catch (eEl) {}
|
|
4953
|
+
}
|
|
3737
4954
|
}, [panelOpen])
|
|
3738
4955
|
if (!panelOpen && !panelClosing) return null
|
|
3739
|
-
var body
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
else if (tab === 'hub') body = h(MemoryHubTab, { nonce: nonce })
|
|
3744
|
-
else if (tab === 'storage') body = h(StorageTab, { nonce: nonce })
|
|
3745
|
-
else if (tab === 'notes') body = h(NotesTab)
|
|
3746
|
-
else if (tab === 'plan') body = h(PlanTab)
|
|
3747
|
-
else if (tab === 'reflections') body = h(ReflectionsTab)
|
|
3748
|
-
else if (tab === 'connect') body = h(ConnectTab)
|
|
3749
|
-
else if (tab === 'calendar') body = h(CalendarTab)
|
|
3750
|
-
else if (tab === 'workspaces') body = h(WorkspaceTab)
|
|
3751
|
-
else body = h(SearchTab)
|
|
3752
|
-
var tabs = [['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')]]
|
|
3753
|
-
var style = {
|
|
3754
|
-
left: g.left + 'px',
|
|
3755
|
-
top: g.top + 'px',
|
|
3756
|
-
width: g.width + 'px',
|
|
3757
|
-
height: g.height + 'px',
|
|
3758
|
-
'--dam-scale': FONT_SCALE_VALUES[fontScale] || '1',
|
|
3759
|
-
'--dam-accent': ACCENT_VALUES[accentTheme] || ACCENT_VALUES.deepseek,
|
|
3760
|
-
}
|
|
4956
|
+
var body = MemoryTabBody(tab, nonce)
|
|
4957
|
+
var tabs = MEMORY_TABS()
|
|
4958
|
+
// 承载面(2026-09-21):①左下角浮层(可拖拽/缩放) ②「记忆」会话页(conversation.view,与「对话轨迹」并列)。
|
|
4959
|
+
// 两者共用同一份 tab(模块级 panelTab)/数据/刷新 nonce ⇒ 天然同步(用户要求);本函数只负责浮层节点。
|
|
3761
4960
|
var dragMove = startPointerDrag(function (dx, dy) {
|
|
3762
4961
|
controller.setGeom({ left: g.left + dx, top: g.top + dy })
|
|
3763
4962
|
}, '[data-dam-btn], [data-dam-tab], [data-dam-input], [data-dam-select], textarea')
|
|
3764
4963
|
var resizeMove = startPointerDrag(function (dx, dy) {
|
|
3765
4964
|
controller.setGeom({ width: g.width + dx, height: g.height + dy })
|
|
3766
4965
|
})
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
4966
|
+
var scaleAttr = fontScale in FONT_SCALES ? fontScale : 'md'
|
|
4967
|
+
var verBadge = ver ? 'v' + ver + '-ui' : ''
|
|
4968
|
+
var nodes = []
|
|
4969
|
+
if (pos === 'bottom-left' || pos === 'both') {
|
|
4970
|
+
var floatStyle = {
|
|
4971
|
+
left: g.left + 'px',
|
|
4972
|
+
top: g.top + 'px',
|
|
4973
|
+
width: g.width + 'px',
|
|
4974
|
+
height: g.height + 'px',
|
|
4975
|
+
'--dam-scale': FONT_SCALE_VALUES[fontScale] || '1',
|
|
4976
|
+
'--dam-accent': ACCENT_VALUES[accentTheme] || ACCENT_VALUES.deepseek,
|
|
4977
|
+
}
|
|
4978
|
+
nodes.push(h('div', {
|
|
4979
|
+
key: 'float',
|
|
4980
|
+
'data-dam-panel': '',
|
|
4981
|
+
'data-pos': 'float',
|
|
4982
|
+
'data-scale': scaleAttr,
|
|
4983
|
+
ref: panelRef,
|
|
4984
|
+
style: floatStyle,
|
|
4985
|
+
'data-closing': panelClosing ? 'true' : undefined,
|
|
4986
|
+
'data-dragging': dragActive ? 'true' : undefined,
|
|
3778
4987
|
},
|
|
3779
|
-
h('
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
'data-dam-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
4988
|
+
h('header', {
|
|
4989
|
+
title: t('dragMove'),
|
|
4990
|
+
onPointerDown: dragMove,
|
|
4991
|
+
},
|
|
4992
|
+
h('strong', null, t('autoMemory')),
|
|
4993
|
+
h('span', { 'data-dam-hint': '', style: { fontSize: '10px', opacity: .55 } }, verBadge),
|
|
4994
|
+
h('span', { className: 'dam-spacer' }),
|
|
4995
|
+
// 悬浮钉(2026-09-08):钉住后点面板外不再自动收起;图标为线描 SVG,与 ⤾ ⟳ ✕ 同画风
|
|
4996
|
+
h('button', {
|
|
4997
|
+
'data-dam-btn': '', 'data-pin': pinned ? 'on' : 'off',
|
|
4998
|
+
title: pinned ? t('unpinPanel') : t('pinPanel'),
|
|
4999
|
+
'aria-pressed': pinned ? 'true' : 'false',
|
|
5000
|
+
style: { display: 'inline-flex', alignItems: 'center', justifyContent: 'center', padding: '4px 6px' },
|
|
5001
|
+
onClick: function () { controller.togglePin() },
|
|
5002
|
+
}, h(PinIcon, { filled: pinned })),
|
|
5003
|
+
h('button', { 'data-dam-btn': '', title: t('resetPos'), onClick: function () { controller.resetGeom() } }, '⤾'),
|
|
5004
|
+
h('button', { 'data-dam-btn': '', title: t('refresh'), onClick: function () { setNonce(nonce + 1) } }, '⟳'),
|
|
5005
|
+
h('button', { 'data-dam-btn': '', title: t('close'), onClick: function () { controller.close() } }, '✕')),
|
|
5006
|
+
h(TabScroller, { key: 'ftabs', tabs: tabs, tab: tab, setTab: setTab }),
|
|
5007
|
+
h('div', { key: 'fbody', 'data-dam-body': '' }, body),
|
|
5008
|
+
h('div', { 'data-dam-resize': '', title: t('dragResize'), onPointerDown: resizeMove })))
|
|
5009
|
+
}
|
|
5010
|
+
// ★2026-09-21 二次修正:「顶部通栏覆盖条」形态已**废弃**。
|
|
5011
|
+
// 用户实测反馈原话:「它现在是浮在整个页面上方的,而不是和对话轨迹、上下文、白板看板在一起,作为一个单独的一页」。
|
|
5012
|
+
// 根因:宿主只给插件 4 个槽位(sidebar/main/rightbar/shell.overlay),放在 overlay 里的东西**只能盖在界面上**,
|
|
5013
|
+
// 做不出「并列的一页」;要成为并列页必须注册到 conversation.view(见下面 MemoryPageView + registerSurfaces)。
|
|
5014
|
+
// 故此处只剩浮层一个节点;'page' / 'both' 档的「会话页」由 conversation.view 槽位承载。
|
|
5015
|
+
return nodes.length === 1 ? nodes[0] : h('div', { 'data-dam-panel-host': '' }, nodes)
|
|
3796
5016
|
}
|
|
3797
5017
|
|
|
3798
5018
|
// ───────────────────────── 调试中心(为提 issue 提供诊断) ─────────────────────────
|
|
@@ -3855,6 +5075,100 @@ window.__ModuleLoader__.load({
|
|
|
3855
5075
|
var hb = data.heartbeat || {}
|
|
3856
5076
|
var hbAge = hb.exists && hb.heartbeatAt ? Math.round((Date.now() - hb.heartbeatAt) / 1000) : -1
|
|
3857
5077
|
var apiBad = probes && Object.keys(probes).some(function (k) { return probes[k].status === 404 || probes[k].error })
|
|
5078
|
+
// ★P0-2(2026-09-22):hub 三层记忆(episodes/facts/procedures)落盘 IO 健康度。
|
|
5079
|
+
// 宿主 debugInfo().associativeMemory.hubIo 早已产出(lib/index.js:6959 → _hubIoViewSnapshot),
|
|
5080
|
+
// 但前端零消费者 ⇒ save 失败被静默吞掉时,面板/日志/返回值三处都看不出「记忆到底写进磁盘了没有」。
|
|
5081
|
+
// 防御式取值:宿主未升级 / 字段缺失时整行不渲染,绝不因此报错。
|
|
5082
|
+
var am = data.associativeMemory && typeof data.associativeMemory === 'object' ? data.associativeMemory : {}
|
|
5083
|
+
var hubIo = am.hubIo && typeof am.hubIo === 'object' ? am.hubIo : null
|
|
5084
|
+
var hubIoWarn = false
|
|
5085
|
+
var hubIoText = ''
|
|
5086
|
+
if (hubIo) {
|
|
5087
|
+
var hubErrs = Number(hubIo.errors) || 0
|
|
5088
|
+
var hubLoads = Number(hubIo.loads) || 0
|
|
5089
|
+
var hubSaves = Number(hubIo.saves) || 0
|
|
5090
|
+
var hubClears = Number(hubIo.clears) || 0
|
|
5091
|
+
hubIoWarn = hubErrs > 0 || hubIo.verdict === 'io-error'
|
|
5092
|
+
if (hubErrs > 0) {
|
|
5093
|
+
hubIoText = (locale === 'zh' ? '⚠ 累计失败 ' + hubErrs + ' 次 · 最近: ' : '⚠ ' + hubErrs + ' failure(s) · latest: ') +
|
|
5094
|
+
(hubIo.lastError || t('unknown')) +
|
|
5095
|
+
(hubIo.lastErrorAt ? ' (' + new Date(hubIo.lastErrorAt).toLocaleTimeString() + ')' : '')
|
|
5096
|
+
} else if (hubLoads + hubSaves + hubClears > 0) {
|
|
5097
|
+
hubIoText = locale === 'zh'
|
|
5098
|
+
? '✓ 正常(读 ' + hubLoads + ' · 写 ' + hubSaves + ' · 清 ' + hubClears + ',共 ' + (hubLoads + hubSaves + hubClears) + ' 次落盘操作无失败)'
|
|
5099
|
+
: '✓ healthy (load ' + hubLoads + ' · save ' + hubSaves + ' · clear ' + hubClears + ', ' + (hubLoads + hubSaves + hubClears) + ' ops, 0 failures)'
|
|
5100
|
+
} else {
|
|
5101
|
+
hubIoText = locale === 'zh' ? '尚无落盘动作(本次运行还没有写入/读取)' : 'no disk activity yet this run'
|
|
5102
|
+
}
|
|
5103
|
+
// 无失败时优先采用宿主给出的中文整句 summary(如「三层记忆已正常落盘(本轮无写入失败)」)——
|
|
5104
|
+
// 不在前端重拼一遍人话,避免两侧口径分叉。有失败时保留上面的详细行(含条数与人话原因 + 时间)。
|
|
5105
|
+
if (hubErrs === 0 && hubIo.summary) hubIoText = String(hubIo.summary)
|
|
5106
|
+
}
|
|
5107
|
+
var hubIoByFile = hubIo && hubIoWarn && hubIo.byFile && typeof hubIo.byFile === 'object'
|
|
5108
|
+
? Object.keys(hubIo.byFile).map(function (k) {
|
|
5109
|
+
var rec = hubIo.byFile[k] || {}
|
|
5110
|
+
return k + ' × ' + (Number(rec.count) || 0)
|
|
5111
|
+
}).join(' ')
|
|
5112
|
+
: ''
|
|
5113
|
+
// ★P0-3(2026-09-22):事实保留上限淘汰台账。
|
|
5114
|
+
// getLastPrune()/retentionLimit()(fact-store.js:830/834)此前在宿主与前端**双侧**零调用方,
|
|
5115
|
+
// 「事实被淘汰了」无处可查。只展示聚合数(条数/上限/超额/最旧时间),不展示任何事实正文。
|
|
5116
|
+
var fp = am.factsPrune && typeof am.factsPrune === 'object' ? am.factsPrune : null
|
|
5117
|
+
var fpWarn = false
|
|
5118
|
+
var fpText = ''
|
|
5119
|
+
if (fp) {
|
|
5120
|
+
var fpPruned = Number(fp.pruned) || 0
|
|
5121
|
+
var fpLimit = fp.limit === undefined || fp.limit === null ? '—' : fp.limit
|
|
5122
|
+
fpWarn = fpPruned > 0
|
|
5123
|
+
if (fpPruned > 0) {
|
|
5124
|
+
fpText = locale === 'zh'
|
|
5125
|
+
? '上次淘汰 ' + fpPruned + ' 条(上限 ' + fpLimit + (fp.over ? ',超出 ' + fp.over : '') + ')' +
|
|
5126
|
+
(fp.protected ? ' · 另有 ' + fp.protected + ' 条重要/未撤销条目受保护未删' : '') +
|
|
5127
|
+
(fp.at ? ' · ' + new Date(fp.at).toLocaleTimeString() : '')
|
|
5128
|
+
: 'last prune removed ' + fpPruned + ' (cap ' + fpLimit + (fp.over ? ', over ' + fp.over : '') + ')' +
|
|
5129
|
+
(fp.protected ? ' · ' + fp.protected + ' protected (important/unrevoked) kept' : '') +
|
|
5130
|
+
(fp.at ? ' · ' + new Date(fp.at).toLocaleTimeString() : '')
|
|
5131
|
+
} else {
|
|
5132
|
+
fpText = locale === 'zh'
|
|
5133
|
+
? '✓ 本次运行未发生淘汰(上限 ' + fpLimit + ')'
|
|
5134
|
+
: '✓ no eviction yet this run (cap ' + fpLimit + ')'
|
|
5135
|
+
}
|
|
5136
|
+
}
|
|
5137
|
+
// ★P1-1 补接线(2026-09-22):Python 语义侧车看门狗 + stderr 尾部。
|
|
5138
|
+
// 宿主 debugInfo().associativeMemory.pythonBackend 早已产出(lib/index.js:6944 →
|
|
5139
|
+
// Object.assign({ enabled }, client.debugView())),前端此前零渲染 ⇒ 侧车被看门狗
|
|
5140
|
+
// 击杀/重生、以及 Python traceback 原文,用户一概看不到;现象只剩「语义唤回突然不好使了」,
|
|
5141
|
+
// 无法归因。★铁律:Python 档是可选进阶项,未启用**不影响** JS 端语义 —— 文案必须写明,
|
|
5142
|
+
// 否则用户会误判成「记忆坏了」。防御式取值:宿主未升级/字段缺失时整块不渲染,绝不报错。
|
|
5143
|
+
var pySide = am.pythonBackend && typeof am.pythonBackend === 'object' ? am.pythonBackend : null
|
|
5144
|
+
var pySideWarn = false
|
|
5145
|
+
var pySideState = ''
|
|
5146
|
+
var pySideWd = ''
|
|
5147
|
+
var pySideTail = ''
|
|
5148
|
+
var pySideHasTail = false
|
|
5149
|
+
var pySideTrunc = false
|
|
5150
|
+
if (pySide) {
|
|
5151
|
+
var pyKills = Number(pySide.watchdog && pySide.watchdog.kills) || 0
|
|
5152
|
+
var pyRespawns = Number(pySide.watchdog && pySide.watchdog.respawns) || 0
|
|
5153
|
+
var pyGen = Number(pySide.generation) || 0
|
|
5154
|
+
var pyReason = pySide.watchdog && pySide.watchdog.lastReason ? String(pySide.watchdog.lastReason) : ''
|
|
5155
|
+
var pyBytes = Number(pySide.stderrTailBytes) || 0
|
|
5156
|
+
pySideWarn = pyKills > 0
|
|
5157
|
+
pySideState = pySide.enabled !== true
|
|
5158
|
+
? t('kvPyOff')
|
|
5159
|
+
: (pySide.started === true
|
|
5160
|
+
? t('kvPyAlive') + (pyGen > 0 ? (locale === 'zh' ? '(第 ' + pyGen + ' 代)' : ' (gen ' + pyGen + ')') : '')
|
|
5161
|
+
: (locale === 'zh' ? '未运行(惰性启动:首次请求才拉起进程)' : 'not running (lazy: spawned on first request)'))
|
|
5162
|
+
pySideWd = pyKills === 0
|
|
5163
|
+
? (locale === 'zh' ? '无(未触发)' : 'none')
|
|
5164
|
+
: (locale === 'zh'
|
|
5165
|
+
? '击杀 ' + pyKills + ' 次 · 重生 ' + pyRespawns + ' 次' + (pyReason ? '(最近:' + pyReason + ')' : '')
|
|
5166
|
+
: 'kills ' + pyKills + ' · respawns ' + pyRespawns + (pyReason ? ' (latest: ' + pyReason + ')' : ''))
|
|
5167
|
+
if (pySide.lastStderrTail) { pySideTail = String(pySide.lastStderrTail); pySideHasTail = true }
|
|
5168
|
+
else if (pyBytes === 0) pySideTail = locale === 'zh' ? '确实没有输出(不是被截断)' : 'no output at all (not truncated)'
|
|
5169
|
+
else pySideTail = locale === 'zh' ? '有 stderr 输出但无可显示行(全为空白行)' : 'stderr output present but no printable line (blank only)'
|
|
5170
|
+
pySideTrunc = pySide.lastStderrTailTruncated === true
|
|
5171
|
+
}
|
|
3858
5172
|
return h('div', null,
|
|
3859
5173
|
h('div', { 'data-dam-row': '' },
|
|
3860
5174
|
h('button', { 'data-dam-btn': '', onClick: refresh, disabled: busy }, busy ? t('dbgRefreshing') : t('dbgRefresh')),
|
|
@@ -3871,6 +5185,18 @@ window.__ModuleLoader__.load({
|
|
|
3871
5185
|
kv(t('kvMem'), t('kvMemUser') + ' ' + (data.memoryFiles.user.exists ? fmtSize(data.memoryFiles.user.size) : t('kvMemMissing')) + ' · ' + t('kvMemNotes') + ' ' + (data.memoryFiles.notes.exists ? fmtSize(data.memoryFiles.notes.size) : t('kvMemMissing')) + ' · ' + t('kvMemLog') + ' ' + (data.memoryFiles.log.exists ? fmtSize(data.memoryFiles.log.size) : t('kvMemMissing')), !data.memoryFiles.log.exists),
|
|
3872
5186
|
kv(t('kvWs'), currentWs() || t('kvWsUnknown'), false),
|
|
3873
5187
|
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
|
+
hubIo ? kv(t('dbgHubIo'), hubIoText, hubIoWarn) : null,
|
|
5189
|
+
hubIoByFile ? h('div', { 'data-dam-hint': '', style: { marginLeft: '140px', wordBreak: 'break-all' } },
|
|
5190
|
+
(locale === 'zh' ? '按层拆分(失败次数):' : 'per-store failures: ') + hubIoByFile) : null,
|
|
5191
|
+
fp ? kv(t('dbgPrune'), fpText, fpWarn) : null,
|
|
5192
|
+
pySide ? h('div', { 'data-dam-row': '', style: { marginTop: '3px', flexDirection: 'column', alignItems: 'stretch' } },
|
|
5193
|
+
kv(t('kvPyTitle'), pySideState, false),
|
|
5194
|
+
kv(t('kvPyWatchdog'), pySideWd, pySideWarn),
|
|
5195
|
+
h('div', { 'data-dam-hint': '', style: { marginTop: '3px' } },
|
|
5196
|
+
t('kvPyStderr') + (pySideTrunc ? t('kvPyStderrTrunc') : '') + ':' + pySideTail,
|
|
5197
|
+
pySideHasTail
|
|
5198
|
+
? h('pre', { style: { margin: '3px 0 0 0', padding: '6px 8px', maxHeight: '200px', overflow: 'auto', whiteSpace: 'pre-wrap', wordBreak: 'break-all', background: 'rgba(127,127,127,.10)', borderRadius: '6px', fontSize: 'calc(11px * var(--dam-scale))' } }, pySideTail)
|
|
5199
|
+
: null)) : null,
|
|
3874
5200
|
h('div', { 'data-dam-row': '', style: { marginTop: '6px', alignItems: 'flex-start', flexDirection: 'column' } },
|
|
3875
5201
|
h('div', { 'data-dam-row': '' },
|
|
3876
5202
|
h('button', { 'data-dam-btn': '', title: 'prion-scan 式四类启发式,只报位置不含正文', onClick: runScan, disabled: scanBusy }, scanBusy ? (locale === 'zh' ? '扫描中…' : 'Scanning…') : (locale === 'zh' ? '扫描脏 token' : 'Scan dirty tokens')),
|
|
@@ -4047,7 +5373,15 @@ window.__ModuleLoader__.load({
|
|
|
4047
5373
|
toggles: [
|
|
4048
5374
|
{ key: 'activationEmitMode', name: locale === 'zh' ? '记忆唤起(链中检测→插入)' : 'Memory recall (detect → inject)', sub: locale === 'zh' ? '开=canary 档(显式回忆注入,推荐)/ 关=shadow 档(只记录不注入)' : 'On = canary (inject on explicit recall, recommended) / Off = shadow (record only)', mode: true, rec: true, where: locale === 'zh' ? '自动记忆引擎' : 'Semantic engine' },
|
|
4049
5375
|
{ key: 'autoConsolidate', name: locale === 'zh' ? '记忆固化(自动沉淀)' : 'Memory consolidation', sub: locale === 'zh' ? '每轮对话结束自动把结论沉淀进每日日志与记忆店(推荐开)' : 'Consolidate conclusions into the log & stores each turn (recommended)', rec: true, where: locale === 'zh' ? '自动化' : 'Automation' },
|
|
4050
|
-
|
|
5376
|
+
// ★2026-09-22 改键(设置页缺口审计 P0-2):旧键 procedurePromotionEnabled 因 DEFAULT_CONFIG
|
|
5377
|
+
// 恒供给布尔 procedureInjectEnabled 而**结构性不可达** ⇒ 该开关此前是摆设(会翻面、写盘成功、宿主读不到)。
|
|
5378
|
+
{ key: 'procedureInjectEnabled', name: locale === 'zh' ? '技能固化与注入' : 'Skill crystallization', sub: locale === 'zh' ? '重复流程固化为 checklist 自动附上;跨会话验证后晋升(「记忆中枢」页审批)' : 'Turn repeated flows into auto-attached checklists; promote after validation (Memory Hub tab)', where: locale === 'zh' ? '记忆中枢' : 'Memory Hub' },
|
|
5379
|
+
// ★T10(2026-09-20):机械 procedure 切片开关。
|
|
5380
|
+
// 背景:`crossFeed()` 会把 episode 意图**机械截断**成技能名与步骤(无模型介入),
|
|
5381
|
+
// 产出的观察行「既不像技能名也不像步骤」——用户报「审批里看不懂」的来源。
|
|
5382
|
+
// 默认关:procedural 线只留模型直写(memory_procedure)与人工两条正经通路。
|
|
5383
|
+
// ⚠️ 解耦:本项**只**影响 procedure 切片,fact 分支/episode 巩固/judgement 消费不动。
|
|
5384
|
+
{ key: 'hubMechanicalProcedureFeedEnabled', name: locale === 'zh' ? '机械流程切片(不推荐)' : 'Mechanical flow slicing (not recommended)', sub: locale === 'zh' ? '把情节意图截断成"技能",无模型介入、可读性差;关=只保留模型自己写的技能(推荐关)' : 'Truncates episode intent into "skills" with no model involvement; off = only model-authored skills (recommended)', where: locale === 'zh' ? '记忆中枢' : 'Memory Hub' },
|
|
4051
5385
|
] },
|
|
4052
5386
|
{ art: 'rocket', core: '', kicker: locale === 'zh' ? '完成' : 'READY', final: true,
|
|
4053
5387
|
title: locale === 'zh' ? '一切就绪' : 'All set',
|
|
@@ -4443,7 +5777,8 @@ window.__ModuleLoader__.load({
|
|
|
4443
5777
|
// ring/wall 必须一起带进来:卡片读的是 acConfirm.ring / acConfirm.wall,
|
|
4444
5778
|
// 宿主透出了但这里不拷 → 双口径那行永远不渲染(2026-09-10 自查发现)
|
|
4445
5779
|
setAcConfirm({ ratio: Number(arm.ratio) || 0, tokens: Number(arm.tokens) || 0, window: Number(arm.window) || 0, ring: Number(arm.ring) || 0, wall: Number(arm.wall) || 0, edgeAt: Number(arm.edgeAt) || 0 })
|
|
4446
|
-
|
|
5780
|
+
// ★2026-09-20 移植(issue #94⑦ / PR #100):ceil 作用于**毫秒**再除 1000 ⇒ 显示小数秒。
|
|
5781
|
+
setAcCd(Math.max(0, Math.ceil(((Number(arm.expiresAt) || 0) - Date.now()) / 1000)))
|
|
4447
5782
|
} else {
|
|
4448
5783
|
setAcConfirm(null)
|
|
4449
5784
|
setAcCd(0)
|
|
@@ -4516,7 +5851,7 @@ window.__ModuleLoader__.load({
|
|
|
4516
5851
|
var dbgOpenPair = useState(false)
|
|
4517
5852
|
var dbgOpen = dbgOpenPair[0]
|
|
4518
5853
|
var setDbgOpen = dbgOpenPair[1]
|
|
4519
|
-
var settingsSectionPair = useState('
|
|
5854
|
+
var settingsSectionPair = useState('engine')
|
|
4520
5855
|
var settingsSection = settingsSectionPair[0]
|
|
4521
5856
|
var setSettingsSection = settingsSectionPair[1]
|
|
4522
5857
|
var browseOpenPair = useState(false)
|
|
@@ -4797,12 +6132,15 @@ window.__ModuleLoader__.load({
|
|
|
4797
6132
|
} catch (_) {}
|
|
4798
6133
|
}
|
|
4799
6134
|
var sectionLabels = {
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
6135
|
+
engine: locale === 'zh' ? '语义记忆总开关' : 'Memory engine',
|
|
6136
|
+
window: locale === 'zh' ? '记忆窗口(注入什么)' : 'Memory window',
|
|
6137
|
+
capacity: locale === 'zh' ? '记忆容量与归档' : 'Capacity & retention',
|
|
6138
|
+
skills: locale === 'zh' ? '自动沉淀成技能' : 'Skills & promotion',
|
|
6139
|
+
handoff: locale === 'zh' ? '长会话接续' : 'Handoff & continuation',
|
|
6140
|
+
auto: locale === 'zh' ? '自动化与免打扰' : 'Automation',
|
|
6141
|
+
store: locale === 'zh' ? '存储与外部记忆' : 'Storage',
|
|
6142
|
+
look: locale === 'zh' ? '外观与交互' : 'Appearance',
|
|
6143
|
+
about: locale === 'zh' ? '关于与诊断' : 'About',
|
|
4806
6144
|
}
|
|
4807
6145
|
function section(key, title, content) { return h('section', { id: 'dam-settings-' + key, 'data-dam-settings-group': '' }, h('h3', null, title), content) }
|
|
4808
6146
|
function jumpToSection(key) {
|
|
@@ -4814,7 +6152,7 @@ window.__ModuleLoader__.load({
|
|
|
4814
6152
|
return h('button', { key: key, 'data-dam-btn': '', 'data-active': settingsSection === key ? 'true' : undefined, onClick: function () { jumpToSection(key) } }, sectionLabels[key])
|
|
4815
6153
|
})),
|
|
4816
6154
|
h('div', { 'data-dam-settings-content': '' },
|
|
4817
|
-
section('
|
|
6155
|
+
section('engine', sectionLabels.engine, [
|
|
4818
6156
|
field(t('fAssocEngine'), h('input', { type: 'checkbox', checked: !!cfg.associativeMemoryEnabled, onChange: function (e) { set('associativeMemoryEnabled', e.target.checked) } }), t('fAssocEngineHint')),
|
|
4819
6157
|
field(t('fAnchorIndex'), h('input', { type: 'checkbox', checked: !!cfg.memoryAnchorEnabled, onChange: function (e) { set('memoryAnchorEnabled', e.target.checked) } }), t('fAnchorIndexHint')),
|
|
4820
6158
|
field(t('fJsCooldown'), h('input', { 'data-dam-input': '', type: 'number', min: 0, max: 60, value: cfg.jsDecideCooldownRounds === undefined ? 1 : cfg.jsDecideCooldownRounds, onChange: function (e) { set('jsDecideCooldownRounds', (function () { var v = Number(e.target.value); return (Number.isFinite(v) && v >= 0) ? v : 1 })()) /* #19: 0=不冷却 是合法值,|| 1 会吃掉 0 */ } }), t('fJsCooldownHint')),
|
|
@@ -4849,6 +6187,109 @@ window.__ModuleLoader__.load({
|
|
|
4849
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,
|
|
4850
6188
|
(sem.loaded && cfg.semanticEngineMode === 'python' && sem.pythonInt8Present === false) ? h('div', { 'data-dam-error': '', style: { marginTop: '-4px', marginBottom: '8px' } },
|
|
4851
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, [
|
|
4852
6293
|
// M-CM6-B v3·环境检测面板:⟳ 按钮触发,自动检索环境(快检+深扫+热接入),联动安装助手;美学对齐安装向导卡
|
|
4853
6294
|
detOpen ? (function () {
|
|
4854
6295
|
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)' }
|
|
@@ -4909,6 +6350,9 @@ window.__ModuleLoader__.load({
|
|
|
4909
6350
|
else if (dl.phase === 'error') { stateTxt = t('dlError'); stateBg = 'rgba(196,74,74,.22)' }
|
|
4910
6351
|
else if (dl.phase === 'cancelled') { stateTxt = t('dlCancelled'); stateBg = 'rgba(196,138,42,.2)' }
|
|
4911
6352
|
else if (isJs && dlActive) { stateTxt = dl.phase === 'verifying' ? t('dlVerifying') : t('dlDownloading'); stateBg = 'rgba(36,86,196,.2)' }
|
|
6353
|
+
// ★ issue #70(2026-09-19):资产齐备但引擎**已降级** —— 必须与「未下载」区分开。
|
|
6354
|
+
// 旧实现只看文件存在性 ⇒ 这种状态会显示「已就绪 ✓」而实际每次检索都在词法兜底。
|
|
6355
|
+
else if (isJs && sem.degraded) { stateTxt = locale === 'zh' ? ('引擎降级(词法兜底)— ' + String(sem.degraded).slice(0, 60)) : ('Engine degraded (lexical fallback) — ' + String(sem.degraded).slice(0, 60)); stateBg = 'rgba(196,74,74,.22)' }
|
|
4912
6356
|
else if (isJs && sem.assetPresent && !sem.peerPresent) { stateTxt = locale === 'zh' ? '模型已存在,缺推理库——pnpm approve-builds 后 pnpm add @huggingface/transformers' : 'Model present, runtime missing — pnpm approve-builds && pnpm add @huggingface/transformers'; stateBg = 'rgba(196,138,42,.2)' }
|
|
4913
6357
|
else { stateTxt = locale === 'zh' ? '未下载' : 'Not downloaded'; stateBg = 'rgba(196,138,42,.2)' }
|
|
4914
6358
|
var progress = dlActive || (isJs && dl.phase === 'done')
|
|
@@ -4922,14 +6366,17 @@ window.__ModuleLoader__.load({
|
|
|
4922
6366
|
: (dl.phase === 'cancelled' ? t('dlCancelled')
|
|
4923
6367
|
: (isJs && dl.phase === 'done' && !ready ? t('dlDone') + (locale === 'zh' ? '(缺运行库时需安装 @huggingface/transformers)' : ' (install @huggingface/transformers if runtime missing)')
|
|
4924
6368
|
: (locale === 'zh' ? '下载进度' : 'Download progress'))))
|
|
4925
|
-
|
|
6369
|
+
// ★2026-09-20 移植(issue #89 / PR #95):局部 var refreshSem 遮蔽模块级
|
|
6370
|
+
// refreshSem(apply, onError) ⇒ 自递归爆栈被 .catch 吞 ⇒ 点「开始下载/取消」后
|
|
6371
|
+
// sem 永不刷新、轮询闸也永不启动。改名引用模块级函数。
|
|
6372
|
+
var refreshSemNow = function () {
|
|
4926
6373
|
refreshSem(setSem)
|
|
4927
6374
|
}
|
|
4928
6375
|
var startDl = function () {
|
|
4929
|
-
apiPost(API.semanticDownload, { action: 'start', mirror: mirror }).then(
|
|
6376
|
+
apiPost(API.semanticDownload, { action: 'start', mirror: mirror }).then(refreshSemNow).catch(function () {})
|
|
4930
6377
|
}
|
|
4931
6378
|
var cancelDl = function () {
|
|
4932
|
-
apiPost(API.semanticDownload, { action: 'cancel', mirror: mirror }).then(
|
|
6379
|
+
apiPost(API.semanticDownload, { action: 'cancel', mirror: mirror }).then(refreshSemNow).catch(function () {})
|
|
4933
6380
|
}
|
|
4934
6381
|
return h('div', { style: { border: '1px solid color-mix(in srgb, var(--dam-accent, #2456c4) 40%, transparent)', borderRadius: '10px', padding: '10px 12px', marginBottom: '8px', fontSize: 'calc(11.5px * var(--dam-scale))', lineHeight: 1.6 } },
|
|
4935
6382
|
h('div', { 'data-dam-row': '', style: { alignItems: 'center' } },
|
|
@@ -4950,175 +6397,102 @@ window.__ModuleLoader__.load({
|
|
|
4950
6397
|
h('option', { value: 'auto' }, t('mAuto')),
|
|
4951
6398
|
h('option', { value: 'cn' }, t('mCn')),
|
|
4952
6399
|
h('option', { value: 'intl' }, t('mIntl'))) : null,
|
|
4953
|
-
|
|
6400
|
+
// ★2026-09-22 删死键(设置页缺口审计 P0-7):pythonGpu 不在 DEFAULT_CONFIG 白名单,
|
|
6401
|
+
// /config 会静默丢弃、宿主 lib/ 零读取 ⇒ 写入无任何效果,故移除该段(行为零变化)。
|
|
6402
|
+
ready ? h('button', { 'data-dam-btn': '', onClick: function () { setGuide(''); var n2 = Object.assign({}, cfg); n2.semanticEngineMode = guide; if (guide === 'js') { n2.activationSource = 'js'; n2.contextSinkMode = 'null' } else if (guide === 'python') { n2.activationSource = 'python'; n2.contextSinkMode = 'python' } setCfg(n2); setDirty(true) } }, locale === 'zh' ? '启用并继续' : 'Enable & continue') : null,
|
|
4954
6403
|
!ready && isJs && !dlActive ? h('button', { 'data-dam-btn': '', onClick: startDl }, (dl.phase === 'error' || dl.phase === 'cancelled') ? t('semDlRetry') : t('semDlStart')) : null,
|
|
4955
6404
|
!ready && isJs && dlActive ? h('button', { 'data-dam-btn': '', onClick: cancelDl }, t('semDlCancel')) : null,
|
|
4956
6405
|
h('button', { 'data-dam-btn': '', onClick: function () { setGuide('') } }, t('gotIt'))))
|
|
4957
6406
|
})()
|
|
4958
6407
|
: null,
|
|
4959
|
-
|
|
4960
|
-
field(t('
|
|
4961
|
-
field(
|
|
4962
|
-
h('
|
|
4963
|
-
t('
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
h('
|
|
6408
|
+
field(t('fUserDir'), h('input', { 'data-dam-input': '', value: cfg.userMemoryDir, onChange: function (e) { set('userMemoryDir', e.target.value) } }), t('fUserDirHint')),
|
|
6409
|
+
field(t('fProjectDir'), h('input', { 'data-dam-input': '', value: cfg.projectMemoryDir, onChange: function (e) { set('projectMemoryDir', e.target.value) } }), t('fProjectDirHint')),
|
|
6410
|
+
field(t('fMemoryRoot'), h('div', { 'data-dam-row': '', style: { flex: 1 } },
|
|
6411
|
+
h('input', { 'data-dam-input': '', style: { flex: 1 }, value: cfg.memoryRoot || '', onChange: function (e) { set('memoryRoot', e.target.value) } }),
|
|
6412
|
+
h('button', { 'data-dam-btn': '', onClick: function () { openBrowser() } }, t('fBrowse'))), t('fMemoryRootHint')),
|
|
6413
|
+
browseOpen ? h('div', { style: { border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.25)) 60%, transparent)', borderRadius: '8px', padding: '8px', marginBottom: '8px', background: 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.06)) 40%, transparent)' } },
|
|
6414
|
+
h('div', { 'data-dam-row': '' },
|
|
6415
|
+
h('b', { style: { fontSize: 'calc(12px * var(--dam-scale))', wordBreak: 'break-all', flex: 1 } }, browsePath || '…'),
|
|
6416
|
+
h('button', { 'data-dam-btn': '', onClick: function () { browseTo(browseParent) }, disabled: browseParent === browsePath }, t('fUp'))),
|
|
6417
|
+
h('div', { style: { maxHeight: '160px', overflow: 'auto', marginTop: '4px' } },
|
|
6418
|
+
(browseDirs || []).map(function (d) {
|
|
6419
|
+
return h('button', { key: d.path, 'data-dam-btn': '', style: { display: 'block', width: '100%', textAlign: 'left', padding: '3px 6px' }, onClick: function () { browseTo(d.path) } }, '📁 ' + d.name)
|
|
6420
|
+
}).concat(browseDirs && !browseDirs.length ? [h('div', { key: 'e', 'data-dam-hint': '' }, t('empty'))] : [])),
|
|
6421
|
+
h('div', { 'data-dam-row': '', style: { marginTop: '6px' } },
|
|
6422
|
+
h('button', { 'data-dam-btn': '', onClick: function () { set('memoryRoot', browsePath); setBrowseOpen(false) } }, t('fSelectDir')),
|
|
6423
|
+
h('button', { 'data-dam-btn': '', onClick: function () { setBrowseOpen(false) } }, t('close'))))
|
|
6424
|
+
: null,
|
|
6425
|
+
field(t('fDayBoundary'), h('input', { 'data-dam-input': '', type: 'number', min: 0, max: 1439, value: (cfg.dayBoundaryMinutes === undefined ? 450 : cfg.dayBoundaryMinutes), onChange: function (e) { set('dayBoundaryMinutes', parseInt(e.target.value || '0', 10)) } }), t('fDayBoundaryHint')),
|
|
6426
|
+
field(t('fReflect'), h('input', { type: 'checkbox', checked: !!cfg.reflectEnabled, onChange: function (e) { set('reflectEnabled', e.target.checked) } }), t('fReflectHint')),
|
|
6427
|
+
field(t('fStyle'), h('select', { 'data-dam-select': '', value: cfg.reflectStyle, onChange: function (e) { set('reflectStyle', e.target.value) } },
|
|
6428
|
+
STYLE_IDS.map(function (id) { return h('option', { key: id, value: id }, t('style' + id.charAt(0).toUpperCase() + id.slice(1))) })), t('fStyleHint')),
|
|
6429
|
+
// 2026-09-22 补接线(设置页缺口审计 P1/P2):扫描上限与索引快照前端零命中。
|
|
6430
|
+
field(t('fWsDiscover'), h('input', { 'data-dam-input': '', type: 'number', min: 5, max: 2000, value: cfg.workspaceDiscoverMax === undefined ? 200 : cfg.workspaceDiscoverMax, onChange: function (e) { set('workspaceDiscoverMax', Math.max(5, Math.min(2000, Number(e.target.value) || 200))) } }), t('fWsDiscoverHint')),
|
|
4979
6431
|
]),
|
|
4980
|
-
section('
|
|
6432
|
+
section('look', sectionLabels.look, [
|
|
4981
6433
|
h('div', { 'data-dam-hint': '' }, t('settingsHeader')),
|
|
4982
|
-
// 欢迎向导:开关(首启自动播放)+ 立即重看按钮(闭包内直调 openDialog——同一作用域,点击立即弹;
|
|
4983
|
-
// 不走 window 全局入口,避免多实例时序导致"点了没反应要刷新")+ 查看更新日志(走 update 弹窗,
|
|
4984
|
-
// 带 Logo 开场动画;versions 取 CHANGELOG 最新一条)
|
|
4985
|
-
field(t('fWelcomeTour'), h('div', { 'data-dam-row': '' },
|
|
4986
|
-
h('input', { type: 'checkbox', checked: cfg.welcomeTourEnabled !== false, onChange: function (e) { set('welcomeTourEnabled', e.target.checked) } }),
|
|
4987
|
-
h('button', { 'data-dam-btn': '', onClick: function () { try { openDialog({ kind: 'welcomeTour' }) } catch (eTour) {} } }, t('tourReplay')),
|
|
4988
|
-
h('button', { 'data-dam-btn': '', onClick: function () {
|
|
4989
|
-
try {
|
|
4990
|
-
var keys = Object.keys(CHANGELOG)
|
|
4991
|
-
if (!keys.length) return
|
|
4992
|
-
var latest = keys.sort(cmpVersion)[keys.length - 1]
|
|
4993
|
-
openDialog({ kind: 'update', versions: [{ version: latest, items: CHANGELOG[latest] }], currentVersion: latest })
|
|
4994
|
-
} catch (eLog) {}
|
|
4995
|
-
} }, locale === 'zh' ? '查看更新日志' : 'View changelog')), t('fWelcomeTourHint')),
|
|
4996
6434
|
field(t('fLocale'), h('select', { 'data-dam-select': '', value: cfg.locale || 'system', onChange: function (e) { set('locale', e.target.value) } },
|
|
4997
6435
|
LOCALE_IDS_LIST.map(function (id) { return h('option', { key: id, value: id }, id === 'system' ? t('followSystem') : t(id)) })), t('fLocaleHint')),
|
|
4998
6436
|
field(t('fFontSize'), h('select', { 'data-dam-select': '', value: fontScale, onChange: function (e) { fontScale = e.target.value; try { localStorage.setItem('dsh-auto-memory.fontScale.v2', fontScale) } catch (ee) {}; try { var pp = document.querySelector('[data-dam-panel]'); if (pp) pp.style.setProperty('--dam-scale', FONT_SCALE_VALUES[fontScale] || '1') } catch (ee2) {}; emit() } },
|
|
4999
6437
|
Object.keys(FONT_SCALES).map(function (k) { return h('option', { key: k, value: k }, t('fs' + k.charAt(0).toUpperCase() + k.slice(1))) })), t('fFontSizeHint')),
|
|
6438
|
+
// 面板位置(2026-09-21):左下角 / 顶部 / 两者共存。改动立即生效并同步两处形态(用户硬规则:即时回显)。
|
|
6439
|
+
field(t('fPanelPos'), h('select', { 'data-dam-select': '', value: controller.panelPos(), onChange: function (e) { controller.setPanelPos(e.target.value) } },
|
|
6440
|
+
h('option', { value: 'bottom-left' }, t('posBottomLeft')), h('option', { value: 'page' }, t('posPage')), h('option', { value: 'both' }, t('posBoth'))), t('fPanelPosHint')),
|
|
5000
6441
|
field(locale === 'zh' ? '强调色' : 'Accent color', h('select', { 'data-dam-select': '', value: accentTheme, onChange: function (e) { setAccent(e.target.value) } },
|
|
5001
6442
|
h('option', { value: 'deepseek' }, locale === 'zh' ? 'DeepSeek 蓝' : 'DeepSeek blue'), h('option', { value: 'graphite' }, locale === 'zh' ? '石墨灰' : 'Graphite'), h('option', { value: 'violet' }, locale === 'zh' ? '雾紫' : 'Violet')), locale === 'zh' ? '默认使用 DeepSeek 蓝;日历与状态颜色保持语义色。' : 'DeepSeek blue by default; calendar and status colors stay semantic.'),
|
|
5002
6443
|
field(locale === 'zh' ? '关系图密度' : 'Graph density', h('select', { 'data-dam-select': '', value: graphDensity, onChange: function (e) { setDensity(e.target.value) } },
|
|
5003
|
-
h('option', { value: 'relaxed' }, locale === 'zh' ? '舒展' : 'Relaxed'), h('option', { value: 'compact' }, locale === 'zh' ? '紧凑' : 'Compact')), locale === 'zh' ? '影响工作区关系图的节点间距和显示数量。' : 'Controls node spacing and detail in the workspace graph.')
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
: null]),
|
|
5022
|
-
section('injection', sectionLabels.injection, [field(t('fInject'), h('input', { type: 'checkbox', checked: !!cfg.injectEnabled, onChange: function (e) { set('injectEnabled', e.target.checked) } }), t('fInjectHint')),
|
|
5023
|
-
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')),
|
|
5024
|
-
// 群反馈第 4 条:排除来源(每行一条)。textarea 便于粘贴多行路径;
|
|
5025
|
-
// 空行/纯空白在写回时被过滤,避免"看起来配了其实没配"。
|
|
5026
|
-
field(t('fExclude'), h('textarea', {
|
|
5027
|
-
'data-dam-input': '', rows: 3,
|
|
5028
|
-
style: { width: '100%', resize: 'vertical', fontFamily: 'inherit' },
|
|
5029
|
-
value: (Array.isArray(cfg.injectExcludeSources) ? cfg.injectExcludeSources : []).join('\n'),
|
|
5030
|
-
placeholder: 'mem_0123456789abcdef0123456789abcdef\nD:\\ws\\.dsh-memory\\archive\\\nlog',
|
|
5031
|
-
onChange: function (e) {
|
|
5032
|
-
var lines = String(e.target.value || '').split('\n').map(function (x) { return x.trim() }).filter(Boolean)
|
|
5033
|
-
set('injectExcludeSources', lines)
|
|
5034
|
-
},
|
|
5035
|
-
}), t('fExcludeHint')),
|
|
5036
|
-
field(t('fNoteCap'), h('input', { 'data-dam-input': '', type: 'number', min: 500, value: cfg.noteCapacityChars === undefined ? 12000 : cfg.noteCapacityChars, onChange: function (e) { set('noteCapacityChars', Number(e.target.value) || 12000) } }), t('fNoteCapHint')),
|
|
5037
|
-
field(t('fUserCap'), h('input', { 'data-dam-input': '', type: 'number', min: 500, value: cfg.userCapacityChars === undefined ? 12000 : cfg.userCapacityChars, onChange: function (e) { set('userCapacityChars', Number(e.target.value) || 12000) } }), t('fUserCapHint')),
|
|
5038
|
-
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')),
|
|
5039
|
-
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')),
|
|
5040
|
-
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')),
|
|
5041
|
-
field(t('fReinjectOnCompact'), h('input', { type: 'checkbox', checked: cfg.snapshotReinjectOnCompact !== false, onChange: function (e) { set('snapshotReinjectOnCompact', e.target.checked) } }), t('fReinjectOnCompactHint')),
|
|
5042
|
-
field(t('fPromptCustom'), h('button', { 'data-dam-btn': '', onClick: function () { setPromptEditOpen(!promptEditOpen) } }, (promptEditOpen ? (locale === 'zh' ? '收起' : 'Collapse') : (locale === 'zh' ? '编辑 prompt 层' : 'Edit prompt layers'))), t('fPromptCustomHint')),
|
|
5043
|
-
promptEditOpen ? [
|
|
5044
|
-
h('div', { key: '__layers', style: { padding: '6px 0 2px', width: '100%' } },
|
|
5045
|
-
(Object.keys(DEFAULT_PROMPT_LAYERS_CLIENT)).map(function (k) {
|
|
5046
|
-
return h('div', { key: k, style: { marginBottom: '6px' } },
|
|
5047
|
-
h('div', { 'data-dam-hint': '', style: { fontWeight: 700, marginBottom: '2px' } }, k),
|
|
5048
|
-
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) } }))
|
|
5049
|
-
})),
|
|
5050
|
-
h('div', { key: '__reset', 'data-dam-row': '', style: { marginTop: '6px' } },
|
|
5051
|
-
h('button', { 'data-dam-btn': '', onClick: function () { set('promptLayerOverrides', {}) } }, locale === 'zh' ? '一键恢复默认' : 'Reset to defaults'))
|
|
5052
|
-
] : null]),
|
|
5053
|
-
section('automation', sectionLabels.automation, [field(t('fConsolidateMin'), h('input', { 'data-dam-input': '', type: 'number', min: 80, value: cfg.autoConsolidateMinChars === undefined ? 240 : cfg.autoConsolidateMinChars, onChange: function (e) { set('autoConsolidateMinChars', Number(e.target.value) || 240) } }), t('fConsolidateMinHint')),
|
|
5054
|
-
field(t('fAutoConsolidate'), h('input', { type: 'checkbox', checked: cfg.autoConsolidate !== false, onChange: function (e) { set('autoConsolidate', e.target.checked) } }), t('fAutoConsolidateHint')),
|
|
5055
|
-
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')),
|
|
5056
|
-
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')),
|
|
5057
|
-
field(t('fAutoPopup'), h('input', { type: 'checkbox', checked: cfg.autoPopupEnabled !== false, onChange: function (e) { set('autoPopupEnabled', e.target.checked) } }), t('fAutoPopupHint')),
|
|
5058
|
-
field(t('fUnattended'), h('input', { type: 'checkbox', checked: !!cfg.unattendedMode, onChange: function (e) { set('unattendedMode', e.target.checked) } }), t('fUnattendedHint')),
|
|
5059
|
-
field(t('fUnattendedAuto'), h('input', { type: 'checkbox', checked: !!cfg.unattendedAuto, onChange: function (e) { set('unattendedAuto', e.target.checked) } }), t('fUnattendedAutoHint')),
|
|
5060
|
-
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')),
|
|
5061
|
-
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')),
|
|
5062
|
-
field(t('fDayBoundary'), h('input', { 'data-dam-input': '', type: 'number', min: 0, max: 1439, value: (cfg.dayBoundaryMinutes === undefined ? 450 : cfg.dayBoundaryMinutes), onChange: function (e) { set('dayBoundaryMinutes', parseInt(e.target.value || '0', 10)) } }), t('fDayBoundaryHint')),
|
|
5063
|
-
field(t('fReflect'), h('input', { type: 'checkbox', checked: !!cfg.reflectEnabled, onChange: function (e) { set('reflectEnabled', e.target.checked) } }), t('fReflectHint')),
|
|
5064
|
-
field(t('fConsSchedule'), h('input', { type: 'checkbox', checked: cfg.consolidateScheduleEnabled !== false, onChange: function (e) { set('consolidateScheduleEnabled', e.target.checked) } }), t('fConsScheduleHint')),
|
|
5065
|
-
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')),
|
|
5066
|
-
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')),
|
|
5067
|
-
field(t('fMaintSchedule'), h('input', { type: 'checkbox', checked: cfg.maintainScheduleEnabled !== false, onChange: function (e) { set('maintainScheduleEnabled', e.target.checked) } }), t('fMaintScheduleHint')),
|
|
5068
|
-
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')),
|
|
5069
|
-
field(t('fStyle'), h('select', { 'data-dam-select': '', value: cfg.reflectStyle, onChange: function (e) { set('reflectStyle', e.target.value) } },
|
|
5070
|
-
STYLE_IDS.map(function (id) { return h('option', { key: id, value: id }, t('style' + id.charAt(0).toUpperCase() + id.slice(1))) })), t('fStyleHint')),
|
|
5071
|
-
field(locale === 'zh' ? '子代理模型 / 思考强度' : 'Subagent model & reasoning effort', h('div', { 'data-dam-row': '', style: { flex: 1 } },
|
|
5072
|
-
h('span', { style: { flex: 1, fontSize: 'calc(12px * var(--dam-scale))', wordBreak: 'break-all', opacity: cfg.subagentModel ? 1 : 0.6 } }, (cfg.subagentModel || (locale === 'zh' ? '跟随路由默认' : 'routing default')) + (String(cfg.subagentReasoningEffort || '') ? ' · ' + String(cfg.subagentReasoningEffort) : '')),
|
|
5073
|
-
h('button', { 'data-dam-btn': '', onClick: openModels }, locale === 'zh' ? '选择模型 / 强度' : 'Pick model / effort')),
|
|
5074
|
-
locale === 'zh' ? '用于时段总结、问候语、自动沉淀、蒸馏等 subagent 功能;模型与思考强度(off/low/high/max)都经 DSH 的 agentOptions 下发,留空跟随路由与模型默认。保存后生效。' : 'For scheduled summaries, greetings, auto-consolidation and distillation subagents; model and reasoning effort (off/low/high/max) are delivered via DSH agentOptions. Empty follows routing and model defaults. Applies after saving.'),
|
|
5075
|
-
mdlOpen ? buildModelDrawer() : null,
|
|
6444
|
+
h('option', { value: 'relaxed' }, locale === 'zh' ? '舒展' : 'Relaxed'), h('option', { value: 'compact' }, locale === 'zh' ? '紧凑' : 'Compact')), locale === 'zh' ? '影响工作区关系图的节点间距和显示数量。' : 'Controls node spacing and detail in the workspace graph.'),
|
|
6445
|
+
// 群反馈第 4 条:排除来源(每行一条)。textarea 便于粘贴多行路径;
|
|
6446
|
+
// 空行/纯空白在写回时被过滤,避免"看起来配了其实没配"。
|
|
6447
|
+
field(t('fExclude'), h('textarea', {
|
|
6448
|
+
'data-dam-input': '', rows: 3,
|
|
6449
|
+
style: { width: '100%', resize: 'vertical', fontFamily: 'inherit' },
|
|
6450
|
+
value: (Array.isArray(cfg.injectExcludeSources) ? cfg.injectExcludeSources : []).join('\n'),
|
|
6451
|
+
placeholder: 'mem_0123456789abcdef0123456789abcdef\nD:\\ws\\.dsh-memory\\archive\\\nlog',
|
|
6452
|
+
onChange: function (e) {
|
|
6453
|
+
var lines = String(e.target.value || '').split('\n').map(function (x) { return x.trim() }).filter(Boolean)
|
|
6454
|
+
set('injectExcludeSources', lines)
|
|
6455
|
+
},
|
|
6456
|
+
}), t('fExcludeHint')),
|
|
6457
|
+
field(locale === 'zh' ? '子代理模型 / 思考强度' : 'Subagent model & reasoning effort', h('div', { 'data-dam-row': '', style: { flex: 1 } },
|
|
6458
|
+
h('span', { style: { flex: 1, fontSize: 'calc(12px * var(--dam-scale))', wordBreak: 'break-all', opacity: cfg.subagentModel ? 1 : 0.6 } }, (cfg.subagentModel || (locale === 'zh' ? '跟随路由默认' : 'routing default')) + (String(cfg.subagentReasoningEffort || '') ? ' · ' + String(cfg.subagentReasoningEffort) : '')),
|
|
6459
|
+
h('button', { 'data-dam-btn': '', onClick: openModels }, locale === 'zh' ? '选择模型 / 强度' : 'Pick model / effort')),
|
|
6460
|
+
locale === 'zh' ? '用于时段总结、问候语、自动沉淀、蒸馏等 subagent 功能;模型与思考强度(off/low/high/max)都经 DSH 的 agentOptions 下发,留空跟随路由与模型默认。保存后生效。' : 'For scheduled summaries, greetings, auto-consolidation and distillation subagents; model and reasoning effort (off/low/high/max) are delivered via DSH agentOptions. Empty follows routing and model defaults. Applies after saving.'),
|
|
6461
|
+
mdlOpen ? buildModelDrawer() : null,
|
|
5076
6462
|
]),
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
})
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
h('button', { 'data-dam-btn': '', 'data-dam-key': 'boardModeGraph', style: { marginLeft: '6px', fontWeight: cur === 'graph' ? 700 : 400, opacity: cur === 'graph' ? 1 : 0.65 }, onClick: function () { if (cur !== 'graph') pick('graph') } }, locale === 'zh' ? '新版看板(dsh-graph)' : 'Graph board (dsh-graph)'))
|
|
5106
|
-
})(),
|
|
5107
|
-
locale === 'zh' ? '一键切换白板形态。默认「旧版白板」=一切行为不变;切到「新版看板」后启用 dsh-graph 看板、白板结构化索引(handoff/index.json)与两个遍历工具(memory_expand_pre / memory_trace_pre,工具数 14→16)。切换后需重启 dsh web 生效;再点回旧版即完全回滚。' : 'One-click switch for the board form. Default "Legacy board" keeps everything unchanged; "Graph board" enables the dsh-graph kanban, the structured handoff index (handoff/index.json) and two traversal tools (memory_expand_pre / memory_trace_pre, tool count 14→16). Restart dsh web after switching; switch back to fully roll back.'),
|
|
5108
|
-
field(locale === 'zh' ? '子代理痕迹回收' : 'Subagent trace recycle', h('input', { type: 'checkbox', checked: cfg.subagentGcEnabled !== false, onChange: function (e) { set('subagentGcEnabled', e.target.checked) } }),
|
|
5109
|
-
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.'),
|
|
5110
|
-
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) } }),
|
|
5111
|
-
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.')]),
|
|
5112
|
-
section('maintenance', sectionLabels.maintenance, [field(t('fVersion'), h('div', { 'data-dam-row': '' },
|
|
5113
|
-
h('span', { style: { flex: 1 } }, verInfo ? (verInfo.current || '?') + (verInfo.latest ? ' → ' + verInfo.latest + (verInfo.upToDate ? ' ' + t('upToDate') : ' ' + t('hasUpdate')) : '') : (checkingUpdate ? t('checking') : '—')),
|
|
5114
|
-
h('button', { 'data-dam-btn': '', onClick: checkUpdate, disabled: checkingUpdate }, checkingUpdate ? t('checking') : t('checkUpdate')),
|
|
5115
|
-
(verInfo && verInfo.latest && !verInfo.upToDate && verInfo.installKind === 'registry') ? h('button', { 'data-dam-btn': '', onClick: doUpdate, disabled: upBusy, style: { marginLeft: '4px' } }, upBusy ? t('updating') : t('updateNow')) : null,
|
|
5116
|
-
upMsg ? h('span', { 'data-dam-hint': '' }, upMsg) : null),
|
|
5117
|
-
t('versionCmdHint') + (verInfo && verInfo.error ? ' ' + t('versionError') + verInfo.error : '') + (verInfo && verInfo.installKind === 'dev-link' ? ' ' + t('devLinkHint') : '') + (verInfo && !verInfo.installKind ? ' ' + t('noProfileHint') : '')),
|
|
6463
|
+
section('about', sectionLabels.about, [
|
|
6464
|
+
// WB-GRAPH 白板线一键切换(2026-09-16, board_mode_v1): 旧版白板(默认) / 新版看板(dsh-graph vendor
|
|
6465
|
+
// + sidecar + 遍历工具, 工具数 14→16)。开关解耦: 只管白板线; 切换需重启 dsh web 生效(工具注册在启动期)。
|
|
6466
|
+
field(locale === 'zh' ? '白板模式(旧版白板 / 新版看板)' : 'Board mode (legacy / graph)', (function () {
|
|
6467
|
+
var cur = cfg.boardMode === 'graph' ? 'graph' : 'legacy'
|
|
6468
|
+
// ★2026-09-16 修 BUG-3(设置页切换不落盘): 旧实现 `set('boardMode', m)` 只改**本地 React 态**
|
|
6469
|
+
// 然后 reload —— 配置从未写到服务端, 刷新后仍是旧值(界面回跳原档), 用户会判定"开关坏了"。
|
|
6470
|
+
// 正解: 走与接续面板同一个写入路径 `saveConfigPatch(API.config, patch)`(见 :999 定义),
|
|
6471
|
+
// 写盘成功后再 reload; 失败则在界面提示, 不静默假装成功。
|
|
6472
|
+
// 同时满足用户硬性偏好: 开关类改动必须**即时回显**(写盘后立即 reload 呈现新档)。
|
|
6473
|
+
function pick(m) {
|
|
6474
|
+
set('boardMode', m) // 先本地回显(即时反馈)
|
|
6475
|
+
saveConfigPatch({ boardMode: m }, {
|
|
6476
|
+
onSaved: function () { window.setTimeout(function () { window.location.reload() }, 350) },
|
|
6477
|
+
onError: function (e) { setErr(String((e && e.message) || e || 'save failed')) },
|
|
6478
|
+
})
|
|
6479
|
+
}
|
|
6480
|
+
return h('div', { 'data-dam-row': '' },
|
|
6481
|
+
h('button', { 'data-dam-btn': '', 'data-dam-key': 'boardMode', style: { fontWeight: cur === 'legacy' ? 700 : 400, opacity: cur === 'legacy' ? 1 : 0.65 }, onClick: function () { if (cur !== 'legacy') pick('legacy') } }, locale === 'zh' ? '旧版白板' : 'Legacy board'),
|
|
6482
|
+
h('button', { 'data-dam-btn': '', 'data-dam-key': 'boardModeGraph', style: { marginLeft: '6px', fontWeight: cur === 'graph' ? 700 : 400, opacity: cur === 'graph' ? 1 : 0.65 }, onClick: function () { if (cur !== 'graph') pick('graph') } }, locale === 'zh' ? '新版看板(dsh-graph)' : 'Graph board (dsh-graph)'))
|
|
6483
|
+
})(),
|
|
6484
|
+
locale === 'zh' ? '一键切换白板形态。默认「旧版白板」=一切行为不变;切到「新版看板」后启用 dsh-graph 看板、白板结构化索引(handoff/index.json)与两个遍历工具(memory_expand / memory_trace,工具数 14→16)。切换后需重启 dsh web 生效;再点回旧版即完全回滚。' : 'One-click switch for the board form. Default "Legacy board" keeps everything unchanged; "Graph board" enables the dsh-graph kanban, the structured handoff index (handoff/index.json) and two traversal tools (memory_expand / memory_trace, tool count 14→16). Restart dsh web after switching; switch back to fully roll back.'),
|
|
6485
|
+
field(t('fVersion'), h('div', { 'data-dam-row': '' },
|
|
6486
|
+
h('span', { style: { flex: 1 } }, verInfo ? (verInfo.current || '?') + (verInfo.latest ? ' → ' + verInfo.latest + (verInfo.upToDate ? ' ' + t('upToDate') : ' ' + t('hasUpdate')) : '') : (checkingUpdate ? t('checking') : '—')),
|
|
6487
|
+
h('button', { 'data-dam-btn': '', onClick: checkUpdate, disabled: checkingUpdate }, checkingUpdate ? t('checking') : t('checkUpdate')),
|
|
6488
|
+
(verInfo && verInfo.latest && !verInfo.upToDate && verInfo.installKind === 'registry') ? h('button', { 'data-dam-btn': '', onClick: doUpdate, disabled: upBusy, style: { marginLeft: '4px' } }, upBusy ? t('updating') : t('updateNow')) : null,
|
|
6489
|
+
upMsg ? h('span', { 'data-dam-hint': '' }, upMsg) : null),
|
|
6490
|
+
t('versionCmdHint') + (verInfo && verInfo.error ? ' ' + t('versionError') + verInfo.error : '') + (verInfo && verInfo.installKind === 'dev-link' ? ' ' + t('devLinkHint') : '') + (verInfo && !verInfo.installKind ? ' ' + t('noProfileHint') : '')),
|
|
5118
6491
|
field(locale === 'zh' ? '交流群' : 'Community', h('div', { 'data-dam-row': '' },
|
|
5119
6492
|
h('span', { style: { flex: 1 } }, locale === 'zh' ? '反馈问题、交流使用技巧——加群响应比 issue 更快。' : 'Share feedback and tips with other users — the group responds faster than GitHub issues.'),
|
|
5120
6493
|
h('a', { href: 'https://qm.qq.com/q/v7Asxn6vPa', target: '_blank', rel: 'noreferrer', style: { textDecoration: 'none', color: 'var(--dam-accent, #4f7cff)' } }, locale === 'zh' ? '点击加入 QQ 交流群' : 'Join the QQ community group')),
|
|
5121
|
-
locale === 'zh' ? '点击链接一键加群。' : 'One-click join via the QQ invite link.')
|
|
6494
|
+
locale === 'zh' ? '点击链接一键加群。' : 'One-click join via the QQ invite link.'),
|
|
6495
|
+
]),
|
|
5122
6496
|
h('div', { 'data-dam-savebar': '' },
|
|
5123
6497
|
h('button', { 'data-dam-btn': '', 'data-dirty': dirty ? 'true' : undefined, onClick: save, disabled: busy }, busy ? t('saving') : (dirty ? (locale === 'zh' ? '保存更改' : 'Save changes') : t('saveSettings'))),
|
|
5124
6498
|
dirty ? h('span', { 'data-dam-hint': '' }, locale === 'zh' ? '有未保存的更改' : 'Unsaved changes') : null,
|
|
@@ -5392,6 +6766,19 @@ window.__ModuleLoader__.load({
|
|
|
5392
6766
|
surfaceDisposers.push(slots.inject('settings.section', function () {
|
|
5393
6767
|
return slots.register({ name: 'settings.section', id: 'auto-memory', order: 25, label: t('autoMemory') + ' (pre)' }, function (props) { return h(SettingsPage, { close: props && props.close }) })
|
|
5394
6768
|
}))
|
|
6769
|
+
// ★2026-09-21 二次修正:「记忆」会话页 —— 与「对话轨迹」「上下文」「白板看板」并列的一页。
|
|
6770
|
+
// 用户实测反馈:「它现在是浮在整个页面上方的,而不是和对话轨迹、上下文、白板看板在一起,作为一个单独的一页」。
|
|
6771
|
+
// 根因:宿主只给 4 个槽位(sidebar/main/rightbar/shell.overlay),overlay 里的东西只能"盖"在界面上;
|
|
6772
|
+
// 要成为并列页必须走 conversation.view(官方对话轨迹与本插件白板看板都注册在这里)。
|
|
6773
|
+
// 出现条件:承载面 = 会话页 / 两者共存;切回「左下角浮层」时移除该页签(由 refreshSurfaces 重注册)。
|
|
6774
|
+
if (controller.panelPos() === 'page' || controller.panelPos() === 'both') {
|
|
6775
|
+
surfaceDisposers.push(slots.inject('conversation.view', function () {
|
|
6776
|
+
return slots.register(
|
|
6777
|
+
{ name: 'conversation.view', id: 'auto-memory-panel', order: 78, label: function () { return locale === 'zh' ? '记忆' : 'Memory' } },
|
|
6778
|
+
function (props) { return h(MemoryPageView, props || {}) },
|
|
6779
|
+
)
|
|
6780
|
+
}))
|
|
6781
|
+
}
|
|
5395
6782
|
// 2026-09-16「双承载面」: 会话页顶栏再挂一个**整页白板看板**(与侧边面板共存)。
|
|
5396
6783
|
// 面板 440px 装不下矩阵, 整页 ~1000px+ 可以 ⇒ 两者按容器宽度各用其形态。
|
|
5397
6784
|
// 与 boardMode 闸门解耦: 组件内部自己取数, 非 graph 档拿到 enabled:false 就显示提示,
|
|
@@ -5402,6 +6789,27 @@ window.__ModuleLoader__.load({
|
|
|
5402
6789
|
function (props) { return h(KanbanView, props || {}) },
|
|
5403
6790
|
)
|
|
5404
6791
|
}))
|
|
6792
|
+
// 2026-09-21「白板画布」**暂时隐藏**(用户拍板:功能未达可用标准,先发版收尾)。
|
|
6793
|
+
//
|
|
6794
|
+
// 为什么不删代码: 画布本身是完整实现(清洗层 + 分列布局 + 平移缩放 + 侧栏),
|
|
6795
|
+
// 只是**数据源受限** —— 实测 D:\dsh-auto-memory 下 880 张卡、189 篇文档,
|
|
6796
|
+
// 当前布局是「泳道分列 + 每列上限 14 + 折叠」, 但它依赖 kanban-board 在当前
|
|
6797
|
+
// 会话下能解析出**正确的工作区**; 实测 sessionId 解析失败时 API 返回
|
|
6798
|
+
// enabled:true 但 **ws 为空 + 0 张卡** ⇒ 画布显示"暂无内容"。
|
|
6799
|
+
// ⇒ 保留代码 + 默认关闭, 待工作区解析路径稳定后一行开启。
|
|
6800
|
+
//
|
|
6801
|
+
// 开启方式: 把下面的 false 改成 true(或设为 localStorage['dam-wbg-enabled']='1')。
|
|
6802
|
+
var WBG_ENABLED = (function () {
|
|
6803
|
+
try { return localStorage.getItem('dam-wbg-enabled') === '1' } catch (e) { return false }
|
|
6804
|
+
})()
|
|
6805
|
+
if (WBG_ENABLED) {
|
|
6806
|
+
surfaceDisposers.push(slots.inject('conversation.view', function () {
|
|
6807
|
+
return slots.register(
|
|
6808
|
+
{ name: 'conversation.view', id: 'auto-memory-graph', order: 81, label: function () { return locale === 'zh' ? '白板画布' : 'Graph' } },
|
|
6809
|
+
function (props) { return h(WhiteboardGraphView, props || {}) },
|
|
6810
|
+
)
|
|
6811
|
+
}))
|
|
6812
|
+
}
|
|
5405
6813
|
} catch (e) {
|
|
5406
6814
|
console.warn('[dsh-auto-memory] slot registration failed', e)
|
|
5407
6815
|
}
|
|
@@ -5411,8 +6819,12 @@ window.__ModuleLoader__.load({
|
|
|
5411
6819
|
surfaceDisposers = []
|
|
5412
6820
|
registerSurfaces()
|
|
5413
6821
|
}
|
|
6822
|
+
// 把「重注册承载面」暴露给模块作用域:设置页切换承载面时,「记忆」会话页签要当场增/删(见 setSurfacesRefreshHook)
|
|
6823
|
+
setSurfacesRefreshHook(refreshSurfaces)
|
|
6824
|
+
// ★2026-09-21 二次修正:syncTitlebarVar() 及其调用点已一并删除 —— 它只为「顶部通栏覆盖条」让位 Windows
|
|
6825
|
+
// 自绘标题栏而存在,该形态废弃后无消费者(会话页在正常文档流里,由宿主布局负责)。
|
|
5414
6826
|
registerSurfaces()
|
|
5415
|
-
console.log('[dsh-auto-memory] client ready: sidebar entry +
|
|
6827
|
+
console.log('[dsh-auto-memory] client ready: sidebar entry + floating card / memory session page (switchable) + settings page')
|
|
5416
6828
|
}
|
|
5417
6829
|
|
|
5418
6830
|
exports.inject = ['slots', 'sessions', 'remote', 'remote.session']
|