@a9i5k4/dsh-auto-memory 3.0.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -7
- package/README.zh-CN.md +19 -7
- package/docs/FRONTEND-CO-CREATION.md +191 -0
- package/docs/GM53-HOMEPAGE-PROMPT.md +323 -0
- package/docs/HOMEPAGE-CONTENT-FOR-GM53.md +299 -0
- package/docs/PROMO-PROMPT-3.0.md +100 -0
- package/docs/USER-GUIDE.en.md +2 -2
- package/docs/USER-GUIDE.zh-CN.md +2 -2
- package/docs/WHITEPAPER.md +207 -0
- package/docs/internal/ARCHITECTURE-FOR-ZCODE-20260920.md +397 -0
- package/docs/internal/ART-DIRECTION-DEEPSEEK-20260920.md +351 -0
- package/docs/internal/ART-DIRECTION-WIREFRAME.md +191 -181
- package/docs/internal/ART-DIRECTION-WIREFRAME.md.bak-superseded +181 -0
- package/docs/internal/BATTLE-PLAN-20260917.md +871 -0
- package/docs/internal/FEATURE-INVENTORY.md +531 -0
- package/docs/internal/G-SERIES-EXECUTION-20260917.md +248 -0
- package/docs/internal/G3-DESIGN-20260918.md +82 -0
- package/docs/internal/G3-DISK-FORMAT-GAP-20260919.md +92 -0
- package/docs/internal/HANDOFF-TO-ZCODE-20260920.md +309 -0
- package/docs/internal/HERMES-DATA-VERIFICATION-20260919.md +120 -0
- package/docs/internal/HERMES-LEGACY-STATUS-20260919.md +74 -0
- package/docs/internal/ISSUE-55-58-VERIFICATION-20260918.md +175 -0
- package/docs/internal/ISSUE10-FIX-EXECUTION-20260919.md +389 -0
- package/docs/internal/ISSUE10-PLAN-20260919.md +254 -0
- package/docs/internal/ISSUE10B-FORENSICS-20260919.md +468 -0
- package/docs/internal/ISSUE9-PURGE-AND-R1-PLAIN-20260919.md +150 -0
- package/docs/internal/ISSUE9-RESIDUAL-FORENSICS-20260919.md +114 -0
- package/docs/internal/LESSON-TO-CANDIDATE-STATUS-20260919.md +79 -0
- package/docs/internal/MEMORY-GOVERNANCE-20260917.md +309 -0
- package/docs/internal/PRE-FRONTEND-CHECKLIST-20260919.md +705 -0
- package/docs/internal/PRE-FRONTEND-CHECKLIST-20260919.md.bak-s10 +649 -0
- package/docs/internal/PROCEDURAL-MEMORY-AND-APPROVAL-DESIGN-20260918.md +225 -0
- package/docs/internal/PROGRESS-20260917.md +93 -0
- package/docs/internal/PROMPT-GAP-AUDIT-20260920.md +128 -0
- package/docs/internal/R1-DEGRADE-AUDIT-20260918.md +163 -0
- package/docs/internal/R1-READABILITY-FORENSICS-20260919.md +127 -0
- package/docs/internal/R2-EVIDENCE-DEEP-AUDIT-20260918.md +140 -0
- package/docs/internal/R3-DEGRADE-LEDGER-DESIGN-20260918.md +138 -0
- package/docs/internal/R4-RECALL-QUOTA-PLAN-20260918.md +218 -0
- package/docs/internal/RESUME-20260918.md +171 -0
- package/docs/internal/RESUME-20260919.md +104 -0
- package/docs/internal/RHINELAB-TO-DEEPSEEK-FEASIBILITY.md +198 -0
- package/docs/internal/ROADMAP-20260917-WEEK.md +134 -0
- package/docs/internal/S10-CONSTRUCTION-HANDOFF-20260917.md +13 -3
- package/docs/internal/S10-GAP-INVENTORY-20260917.md +239 -0
- package/docs/internal/T6-EXECUTION-20260920.md +130 -0
- package/docs/internal/TELEMETRY-EFFECT-REPORT-DESIGN-20260918.md +146 -0
- package/docs/internal/THESIS-GAP-ANALYSIS-20260918.md +89 -0
- package/docs/internal/THESIS-OUTLINE-20260918.md +147 -0
- package/docs/internal/THREE-LAYER-CONTRACT.md +10 -1
- package/docs/internal/UPSTREAM-ISSUE-PR-TRIAGE-20260919.md +297 -0
- package/docs/internal/UPSTREAM-ISSUES-3RD-AUDIT-20260920.md +104 -0
- package/docs/screenshots/promo/promo-0-banner-v3.png +0 -0
- package/lib/activation-host.js +63 -9
- package/lib/board-mode.js +1 -1
- package/lib/client.js +892 -27
- package/lib/config-io.js +156 -0
- package/lib/context-bridge.js +3 -0
- package/lib/context-host.js +16 -9
- package/lib/degrade.js +385 -0
- package/lib/dsh-home.js +143 -0
- package/lib/episodic-store.js +52 -2
- package/lib/evidence-store.js +8 -1
- package/lib/fact-store.js +21 -2
- package/lib/index-sync.js +13 -1
- package/lib/index.js +1507 -158
- 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 +127 -12
- package/lib/memory-index.js +4 -2
- package/lib/note-status-apply.js +118 -0
- package/lib/note-status.js +196 -0
- package/lib/procedure-store.js +84 -3
- package/lib/python-sidecar-client.js +29 -3
- 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 +1 -1
package/lib/client.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 3. settings.section — 自动记忆设置页(存储位置、注入、反思风格)
|
|
8
8
|
* Data flows over /api/dsh-auto-memory/* (loopback-only host routes).
|
|
9
9
|
*/
|
|
10
|
-
console.log('[dsh-auto-memory] client v3.0.
|
|
10
|
+
console.log('[dsh-auto-memory] client v3.0.1 fingerprint: index-self-heal')
|
|
11
11
|
window.__ModuleLoader__.load({
|
|
12
12
|
id: '@a9i5k4/dsh-auto-memory',
|
|
13
13
|
factory: (require) => {
|
|
@@ -237,6 +237,39 @@ window.__ModuleLoader__.load({
|
|
|
237
237
|
empty: '(空)', back: '← 返回', view: '查看',
|
|
238
238
|
clickDateViewLog: '点击日期查看当日日志(append-only):',
|
|
239
239
|
userMemoryBlock: '用户记忆(用户画像 · 跨项目):', userMemoryEmpty: '(空)', userMemoryTruncated: '(内容过长,已截断显示)',
|
|
240
|
+
// ★R7:硬性约束编辑面板文案
|
|
241
|
+
// ★R1–R6:审批界面可读性文案
|
|
242
|
+
hubStageObserved: '刚观察到',
|
|
243
|
+
hubStageCandidate: '够格待审',
|
|
244
|
+
hubStageValidated: '已通过验证',
|
|
245
|
+
hubStageActive: '已启用',
|
|
246
|
+
hubStageDeprecated: '已弃用',
|
|
247
|
+
hubWhyTitle: '为什么还不能晋升:',
|
|
248
|
+
hubWhyCanPromote: '条件已满足,可以晋升。',
|
|
249
|
+
hubWhyDiversity: function (d, need) { return '需要 ' + need + ' 个不同会话都用过它,目前只有 ' + d + ' 个 —— 换个场景再用一次它就能推进。' },
|
|
250
|
+
hubWhySuccess: function (n, need) { return '需要成功 ' + need + ' 次,目前 ' + n + ' 次 —— 再成功用一次它会自动变好。' },
|
|
251
|
+
hubWhyCorrectionRate: function (r, cap) { return '它的纠正率是 ' + r + ',超过了上限 ' + cap + ' —— 说明这套流程本身有问题,需要先改掉。' },
|
|
252
|
+
hubWhyHasCorrection: function (n) { return '它被纠正过 ' + n + ' 次 —— 有纠正记录说明这流程是错的,不能固化。' },
|
|
253
|
+
hubWhyNoCriteria: '它没写「怎么算成功」—— 没有验收标准的流程不算技能。',
|
|
254
|
+
hubWhyHighRisk: '它是高风险操作,需要你确认后才会固化。',
|
|
255
|
+
hubWhyObservationOnly: '它只是「看到过一件事」的线索,不是真正的流程 —— 不会自动晋升。',
|
|
256
|
+
hubEvSeen: '见过', hubEvSuccess: '成功', hubEvSessions: '跨会话', hubEvReuse: '被复用', hubEvCorrection: '被纠正',
|
|
257
|
+
hubEvLine: function (ev) { return '见过 ' + (ev.seen || 0) + ' 次 · 成功 ' + (ev.success || 0) + ' 次 · 在 ' + (ev.sessions || 0) + ' 个会话里用过' + ((ev.correction || 0) ? ' · 被纠正过 ' + ev.correction + ' 次' : '') },
|
|
258
|
+
hubInjectPreview: '晋升后会注入这些(点开看)',
|
|
259
|
+
hubInjectCriteria: '验收标准:',
|
|
260
|
+
hubNoSteps: '(这条没有可用步骤——源头是机械截断,不是真流程)',
|
|
261
|
+
rulesTitle: '硬性约束(每轮必注入)',
|
|
262
|
+
rulesIntro: '这些条目每一轮都会原封不动发给我,优先级高于其它内容。它不走检索、不会自动淘汰,所以过时的、或 AI 自己加错的,都要你手动改。',
|
|
263
|
+
rulesPath: '来源文件:',
|
|
264
|
+
rulesEmpty: '(暂无条目)',
|
|
265
|
+
rulesSourceUser: '你写的',
|
|
266
|
+
rulesSourceAi: 'AI 加的',
|
|
267
|
+
rulesEdit: '编辑', rulesSave: '保存', rulesCancel: '取消', rulesDelete: '删除',
|
|
268
|
+
rulesConfirmDelete: '确认删除这条约束?\n\n删除后无法撤销——它下一轮就不再注入了。',
|
|
269
|
+
rulesAddHint: '新增一条约束:',
|
|
270
|
+
rulesAddPlaceholder: '例:未经我确认,不要自动修改生产环境的文件。',
|
|
271
|
+
rulesAdd: '添加',
|
|
272
|
+
rulesPreviewHint: '预览:下一轮注入时会变成这样',
|
|
240
273
|
appended: '已追加', notesPathLabel: '项目笔记: ',
|
|
241
274
|
notesPlaceholder: '想追加到项目笔记的内容…(保存时自动带日期标题)',
|
|
242
275
|
saving: '保存中…', append: '追加',
|
|
@@ -265,8 +298,8 @@ window.__ModuleLoader__.load({
|
|
|
265
298
|
fInject: '注入记忆上下文', fInjectHint: '每次组装提示词时自动注入 <memory_system> 块。',
|
|
266
299
|
fBudget: '注入预算(字符)', fBudgetHint: '记忆块总预算,超出部分截断。默认 1600(≈400-600 token/轮);活跃会话每轮都注入这段背景,调低更省 token,调高保留更多记忆。',
|
|
267
300
|
fExclude: '排除来源(每行一条)', fExcludeHint: '这些来源不再进注入,避免坏记忆反复灌入把结果一路带偏。四种写法:记忆 id(mem_xxx)、整层(log/whiteboard/project/user/reflection)、目录前缀(以 / 或 \\ 结尾)、精确文件路径。与 supersede 互补:supersede 管"被新版替代",这里管"整条来源不可信"。被挡下的条数会在注入里以 [降级] 如实标注。',
|
|
268
|
-
fNoteCap: '项目笔记容量上限(字符)', fNoteCapHint: '项目笔记 MEMORY.md 的容量上限,默认
|
|
269
|
-
fUserCap: '用户级记忆容量上限(字符)', fUserCapHint: '用户级 MEMORY.md(跨项目规则)的容量上限,默认
|
|
301
|
+
fNoteCap: '项目笔记容量上限(字符)', fNoteCapHint: '项目笔记 MEMORY.md 的容量上限,默认 24000。超出时自动整理:先把较早内容交给模型折叠成要点,失败则退回整条归档(原文进 archive,不丢)——新记忆不会被堵在外面。注意与上面的「注入预算」不是一回事:这个管文件本体大小,注入预算管每轮往上下文塞多少摘要。',
|
|
302
|
+
fUserCap: '用户级记忆容量上限(字符)', fUserCapHint: '用户级 MEMORY.md(跨项目规则)的容量上限,默认 24000。整理方式与项目笔记相同(先折叠成要点,必要时整条归档)。',
|
|
270
303
|
fSnapGap: '快照最小间隔(轮)', fSnapGapHint: '动态记忆快照内容变化后,至少隔 N 轮才重新注入,避免每轮日志微变都追加快照导致历史膨胀。默认 5;0=每轮都尝试(仍受内容变化约束)。',
|
|
271
304
|
fReinjectOnCompact: '压缩后立即重注入快照', fReinjectOnCompactHint: '上下文被压缩/截断(通常伴随 contextVersion 重置)后,快照会被清掉;开启后强制立即重注入一次,确保记忆背景重建。默认开。',
|
|
272
305
|
fPromptCustom: '自定义记忆注入 prompt', fPromptCustomHint: '可覆盖各层提示文案(小众功能)。支持占位符 {date} {ws} {budget} {n}。改坏了可一键恢复默认。',
|
|
@@ -391,6 +424,37 @@ window.__ModuleLoader__.load({
|
|
|
391
424
|
empty: '(empty)', back: '← Back', view: 'View',
|
|
392
425
|
clickDateViewLog: 'Click a date to view the daily log (append-only):',
|
|
393
426
|
userMemoryBlock: 'User memory (profile · cross-project):', userMemoryEmpty: '(empty)', userMemoryTruncated: '(content truncated for display)',
|
|
427
|
+
hubStageObserved: 'observed',
|
|
428
|
+
hubStageCandidate: 'candidate',
|
|
429
|
+
hubStageValidated: 'validated',
|
|
430
|
+
hubStageActive: 'active',
|
|
431
|
+
hubStageDeprecated: 'deprecated',
|
|
432
|
+
hubWhyTitle: 'Why it cannot be promoted yet: ',
|
|
433
|
+
hubWhyCanPromote: 'All conditions met — ready to promote.',
|
|
434
|
+
hubWhyDiversity: function (d, need) { return 'Needs ' + need + ' distinct sessions, currently ' + d + '.' },
|
|
435
|
+
hubWhySuccess: function (n, need) { return 'Needs ' + need + ' successes, currently ' + n + '.' },
|
|
436
|
+
hubWhyCorrectionRate: function (r, cap) { return 'Correction rate ' + r + ' exceeds cap ' + cap + '.' },
|
|
437
|
+
hubWhyHasCorrection: function (n) { return 'Has ' + n + ' correction(s) — a corrected flow must not be crystallized.' },
|
|
438
|
+
hubWhyNoCriteria: 'No success criteria — a flow without acceptance criteria is not a skill.',
|
|
439
|
+
hubWhyHighRisk: 'High risk — needs your confirmation before crystallizing.',
|
|
440
|
+
hubWhyObservationOnly: 'This is an observation, not a real flow — it never auto-promotes.',
|
|
441
|
+
hubEvSeen: 'seen', hubEvSuccess: 'success', hubEvSessions: 'sessions', hubEvReuse: 'reused', hubEvCorrection: 'corrected',
|
|
442
|
+
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' : '') },
|
|
443
|
+
hubInjectPreview: 'This is what gets injected once promoted',
|
|
444
|
+
hubInjectCriteria: 'Acceptance criteria: ',
|
|
445
|
+
hubNoSteps: '(no usable steps — mechanically truncated, not a real flow)',
|
|
446
|
+
rulesTitle: 'Hard constraints (injected every turn)',
|
|
447
|
+
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.',
|
|
448
|
+
rulesPath: 'Source file: ',
|
|
449
|
+
rulesEmpty: '(none)',
|
|
450
|
+
rulesSourceUser: 'yours',
|
|
451
|
+
rulesSourceAi: 'added by AI',
|
|
452
|
+
rulesEdit: 'Edit', rulesSave: 'Save', rulesCancel: 'Cancel', rulesDelete: 'Delete',
|
|
453
|
+
rulesConfirmDelete: 'Delete this constraint?\n\nThis cannot be undone — it will stop being injected next turn.',
|
|
454
|
+
rulesAddHint: 'Add a constraint:',
|
|
455
|
+
rulesAddPlaceholder: 'e.g. Do not modify production files without my confirmation.',
|
|
456
|
+
rulesAdd: 'Add',
|
|
457
|
+
rulesPreviewHint: 'Preview: how it will be injected next turn',
|
|
394
458
|
appended: 'Appended', notesPathLabel: 'Project notes: ',
|
|
395
459
|
notesPlaceholder: 'Content to append to project notes…(date heading added on save)',
|
|
396
460
|
saving: 'Saving…', append: 'Append',
|
|
@@ -419,8 +483,8 @@ window.__ModuleLoader__.load({
|
|
|
419
483
|
fInject: 'Inject memory context', fInjectHint: 'Auto-inject <memory_system> block into every prompt.',
|
|
420
484
|
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
485
|
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
|
|
486
|
+
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.',
|
|
487
|
+
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
488
|
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
489
|
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
490
|
fPromptCustom: 'Customize memory-injection prompt', fPromptCustomHint: 'Override any prompt layer (power feature). Placeholders: {date} {ws} {budget} {n}. One-click reset restores defaults.',
|
|
@@ -555,7 +619,17 @@ window.__ModuleLoader__.load({
|
|
|
555
619
|
snapshotCalendarTitle: '[日历与日程(未完成)]',
|
|
556
620
|
snapshotWelcomeTitle: '[欢迎回来]',
|
|
557
621
|
snapshotWelcomeBody: '用户离开已超过 1 小时(暂离/下班后回来)。在本轮回复的开头,先用一句简短温暖的话欢迎用户回来…',
|
|
558
|
-
|
|
622
|
+
// ★T5(2026-09-20 用户拍板)同步服务端 snapshotInscription:铭文自 v0.1.30 起为空壳,
|
|
623
|
+
// 现恢复「收尾自检三大方向 + 分类别丢」正文。**此常量必须与服务端 lib/index.js 逐字一致**
|
|
624
|
+
// (设置页「提示词层级」展示的就是这份镜像,漂移会让用户看到的与实际注入不符)。
|
|
625
|
+
snapshotInscription: '[铭文 · 每轮提醒 {date}]\n'
|
|
626
|
+
+ '【收尾自检】本轮有实质产出才做;纯只读/闲聊轮跳过。三个方向 + 分类别丢:\n'
|
|
627
|
+
+ '① 写记忆文件 memory_log —— kind 按性质选,**不要一律 fact**:rule=用户约束/约定(会被规则层当硬约束注入)、preference=偏好、fact=事实记录、todo=待办;\n'
|
|
628
|
+
+ '② 更新白板与账本 —— ★**硬映射(满足即必须做,别自行判断"算不算"变化)**:\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'
|
|
629
|
+
+ '③ 长期记忆判断(三个去处)—— 跨会话有用的 → memory_note / 跨项目规则 → memory_user / **跑通且可复用的多步流程 → memory_procedure**(技能库唯一模型入口,不写就没有;建议填 successCriteria);\n'
|
|
630
|
+
+ '④ 结论失效/被取代 —— 传 `supersedes=` 旧条目 mem_id 标 **superseded(被更新结论取代)**;**当时就做错了要撤回**则传 `retract=` 标 **retracted(撤回)**并尽量附 `retractReason=` 说明错在哪(两者不同:前者有后继,后者本身就是教训);标错了用 `restore=` 撤回;\n'
|
|
631
|
+
+ '⑤ 看板落列 —— 白板/账本内容要进面板看板泳道,须在标题或正文写 tag:`type:goal` / `type:state` / `type:dead-end` / `type:progress`(5 条泳道含「版本归档」,靠归档动作而非 tag);不写 tag 系统只能按标题文字猜,常落空;\n'
|
|
632
|
+
+ '⑥ 语体 —— 客观陈述、第三人称,只留可复用的事实/决策/规则/路径(不写"我考虑/我排查/我想")。',
|
|
559
633
|
snapshotTail: '</memory_system>',
|
|
560
634
|
}
|
|
561
635
|
var FONT_SCALE_VALUES = { sm: '0.9', md: '1', lg: '1.15', xl: '1.3' }
|
|
@@ -585,10 +659,25 @@ window.__ModuleLoader__.load({
|
|
|
585
659
|
|
|
586
660
|
// ───────────────────────── 更新弹窗 / 首次指导 ─────────────────────────
|
|
587
661
|
var CHANGELOG = {
|
|
662
|
+
'3.0.1': { zh: [
|
|
663
|
+
'修复:宿主控制台被 `restoreLastAgent: candidate rejected` **无限刷屏**(每 15 秒一条,打开就停不下来),同时「最近会话」恢复失效——刷新仪式与自动接续的兜底路径都受影响。根因是归属判据过宽:代码把「有父会话」等同于「是子代理」并一律排除,但**你点过一次接续后产生的新会话其实也有父会话**,它是你的正常会话,却被当成子代理拒掉了;列表里只剩它一个时,每个心跳周期都白试一次。现在改为按 `origin`/`delegationDepth` 精确识别子代理,接续会话正常放行;另给诊断信息加了 5 分钟节流兜底。',
|
|
664
|
+
'顺带修好两处同样的误判:接续会话里的对话此前**不会自动沉淀**、工作区状态也**不刷新**——都因为同一条过宽判据。',
|
|
665
|
+
'修复:日历「当天」视图里 **23:59 截止的事项永远不显示**。时段轴原先写死 7:00–22:00,凡落在该区间外(或时间格式解析不出)的事项都被静默丢掉——你自己的课表里就有 7 条这样的截止项。现改为按当天真实事项推算时段范围,并新增「未定时」段落兜住解析不出时间的条目。',
|
|
666
|
+
'新增:日历当天视图支持前后翻天,并显示 ISO 周序号。',
|
|
667
|
+
'优化:白板卡片的标题不再是小节名。原先「目标」这一节名重复出现上百次,读起来没有信息量;现在标题取正文首行、小节名降为小标签,并自动剥掉 `<!-- ... -->` 注释与 `- type:xxx | ` 这类前缀。**后端与数据文件零改动**,只在展示层清洗。',
|
|
668
|
+
'说明:白板「节点图画布」本轮**暂不开放**(默认隐藏)。它的数据源依赖工作区解析,当前在你的机器上解析不稳定时会显示「暂无内容」;代码保留在插件内,待解析路径稳定后再放出。原「白板看板」泳道视图完全不受影响。',
|
|
669
|
+
], en: [
|
|
670
|
+
'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.',
|
|
671
|
+
'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.',
|
|
672
|
+
'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.',
|
|
673
|
+
'New: prev/next day navigation in the calendar day view, plus the ISO week number.',
|
|
674
|
+
'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.',
|
|
675
|
+
'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.',
|
|
676
|
+
] },
|
|
588
677
|
'3.0.0': { zh: [
|
|
589
678
|
'★ 大版本:底层重建收官。检索、注入、容量、并发四条底层全部重做;白板与看板从「实验特性」变成出厂形态。',
|
|
590
679
|
'★ 白板默认开启(原来默认关)。交接白板 + 四段式交接账本是这一版的招牌能力,出厂关着等于新用户看不到它。想彻底关掉:设置 → 自动化 → 交接白板。',
|
|
591
|
-
'★ 白板默认「新版看板」:结构化索引(条目/标签/线索/版本链)+ 列式泳道视图 + 两个遍历工具(按标签展开
|
|
680
|
+
'★ 白板默认「新版看板」:结构化索引(条目/标签/线索/版本链)+ 列式泳道视图 + 两个遍历工具(按标签展开 memory_expand / 按 id 回溯 memory_trace)。旧的文字白板完整保留,设置页一键切回(「旧版白板」)。',
|
|
592
681
|
'说明:自动接续**仍然默认关闭**——水位判据还没在你的机器上实测到位,这一版特意不动它,等验证满意再考虑。',
|
|
593
682
|
'修复:语义唤回长期「查不到东西」的真因。语义索引因一次没收尾的同步被永久占住(重启也只能暂时缓解),此后每轮都退化成「关键词 + 常驻目录」。现已修好:索引能建起来,唤回恢复语义检索。',
|
|
594
683
|
'修复:升级弹窗原先只弹 2.1.0 的旧说明(版本号写死),所以你一直看不到新版更新日志。现在会自动展示当前版本的说明。',
|
|
@@ -1049,6 +1138,9 @@ window.__ModuleLoader__.load({
|
|
|
1049
1138
|
reflect: ROUTE_PREFIX + '/reflect',
|
|
1050
1139
|
reflectAuto: ROUTE_PREFIX + '/reflect-auto',
|
|
1051
1140
|
note: ROUTE_PREFIX + '/note',
|
|
1141
|
+
// ★R7:用户级硬性约束的条目级读写
|
|
1142
|
+
rulesList: ROUTE_PREFIX + '/rules',
|
|
1143
|
+
rulesApply: ROUTE_PREFIX + '/rules/apply',
|
|
1052
1144
|
external: ROUTE_PREFIX + '/external',
|
|
1053
1145
|
externalView: ROUTE_PREFIX + '/external-view',
|
|
1054
1146
|
externalRemove: ROUTE_PREFIX + '/external-remove',
|
|
@@ -1509,6 +1601,99 @@ window.__ModuleLoader__.load({
|
|
|
1509
1601
|
}
|
|
1510
1602
|
function writeLs(key, val) { try { localStorage.setItem(key, String(val)) } catch (e) {} }
|
|
1511
1603
|
|
|
1604
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1605
|
+
// 白板内容清洗层(2026-09-20 重构批)
|
|
1606
|
+
//
|
|
1607
|
+
// 为什么需要: 卡片数据来自 Markdown 的 `## ` 小节直通(wb-sidecar.js:252→:666),
|
|
1608
|
+
// 全链路无摘要/清洗/标题正文分离 ⇒ 实测 868 张卡只有 195 个不同标题
|
|
1609
|
+
// (「目标」重复 109 次), 且正文首行常是未剥离的 `<!-- type:goal -->`。
|
|
1610
|
+
// title 与 body 因此互为镜像 —— 两个字段的信息量只有 1 个。
|
|
1611
|
+
//
|
|
1612
|
+
// 立场: 纯前端派生, 不改后端契约(wb-contract.js 的写入门与 sidecar 结构零改动)。
|
|
1613
|
+
// 清洗是**渲染层**职责: 磁盘上的字节保持原样, 只在显示时剥掉元数据。
|
|
1614
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
1615
|
+
/** 剥 HTML 注释(锚点行 `<!-- memory:mem_xxx -->` 与 tag 行 `<!-- type:goal -->`)。 */
|
|
1616
|
+
function wbStripComment(s) { return String(s || '').replace(/<!--[\s\S]*?-->/g, '') }
|
|
1617
|
+
/** 剥行首的 tag 前缀(`- type:state | ` / `type:goal` 等)与列表符。 */
|
|
1618
|
+
function wbStripPrefix(s) {
|
|
1619
|
+
return String(s || '')
|
|
1620
|
+
.replace(/^\s*[-*+]\s+/, '')
|
|
1621
|
+
.replace(/^\s*(?:tag|type|topic)\s*[::]\s*[\w\u4e00-\u9fff-]+\s*[||]?\s*/i, '')
|
|
1622
|
+
.replace(/^\s*[||]\s*/, '')
|
|
1623
|
+
}
|
|
1624
|
+
/** 剥 Markdown 行内标记(粗体/斜体/行内代码/链接), 只留可读文字。 */
|
|
1625
|
+
function wbStripMarkup(s) {
|
|
1626
|
+
return String(s || '')
|
|
1627
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
1628
|
+
.replace(/(^|[^*])\*([^*]+)\*/g, '$1$2')
|
|
1629
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
1630
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
1631
|
+
.replace(/\s+/g, ' ')
|
|
1632
|
+
.trim()
|
|
1633
|
+
}
|
|
1634
|
+
/** 一条原文 → 可读文本(剥注释 → 逐行剥前缀 → 剥标记), 丢弃空行。 */
|
|
1635
|
+
function wbCleanLines(s) {
|
|
1636
|
+
var lines = wbStripComment(s).split(/\r?\n/)
|
|
1637
|
+
var out = []
|
|
1638
|
+
for (var i = 0; i < lines.length; i++) {
|
|
1639
|
+
var t = wbStripMarkup(wbStripPrefix(lines[i]))
|
|
1640
|
+
if (t) out.push(t)
|
|
1641
|
+
}
|
|
1642
|
+
return out
|
|
1643
|
+
}
|
|
1644
|
+
/** 标题清洗: 剥注释与前缀后取首行, 过长截断。空则回落到小节名。 */
|
|
1645
|
+
function wbCleanTitle(raw) {
|
|
1646
|
+
var ls = wbCleanLines(raw)
|
|
1647
|
+
var t = ls.length ? ls[0] : ''
|
|
1648
|
+
if (!t) return ''
|
|
1649
|
+
return t.length > 60 ? t.slice(0, 57) + '…' : t
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* 卡片 → { title, body, meta }
|
|
1653
|
+
* 标题优先级: 清洗后的正文首行 > card.title(小节名) > 来源文件名
|
|
1654
|
+
* 正文 = 剩余行拼接(已清洗), 与标题不重复 —— 这正是「标题+正文分离」。
|
|
1655
|
+
*/
|
|
1656
|
+
function wbShape(c) {
|
|
1657
|
+
var rawFull = c.full || c.preview || ''
|
|
1658
|
+
var ls = wbCleanLines(rawFull)
|
|
1659
|
+
var head = ls.length ? ls[0] : ''
|
|
1660
|
+
var title = head ? (head.length > 60 ? head.slice(0, 57) + '…' : head) : (c.title || c.short || '')
|
|
1661
|
+
// 副标题: 小节名(短小、可当分类标签), 若与标题相同则省略
|
|
1662
|
+
var kicker = c.title && c.title !== title ? c.title : ''
|
|
1663
|
+
var rest = ls.slice(1)
|
|
1664
|
+
// 小节名常重复出现在正文首行(如「任务状态」), 再剥一次
|
|
1665
|
+
if (kicker && rest.length && rest[0] === kicker) rest = rest.slice(1)
|
|
1666
|
+
var body = rest.join(' ')
|
|
1667
|
+
var isDup = body.indexOf(title) === 0 && body.length <= title.length + 2
|
|
1668
|
+
if (isDup) body = ''
|
|
1669
|
+
return {
|
|
1670
|
+
title: title,
|
|
1671
|
+
kicker: kicker,
|
|
1672
|
+
body: body,
|
|
1673
|
+
docTitle: c.docTitle || '',
|
|
1674
|
+
source: c.source || '',
|
|
1675
|
+
section: c.section || '',
|
|
1676
|
+
tags: c.tags || [],
|
|
1677
|
+
sectionTags: c.sectionTags || [],
|
|
1678
|
+
anchored: !!c.anchored,
|
|
1679
|
+
id: c.id,
|
|
1680
|
+
ts: c.ts,
|
|
1681
|
+
lineStart: c.lineStart,
|
|
1682
|
+
kind: c.kind || '',
|
|
1683
|
+
mtime: c.mtime,
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
/** 泳道 key → 色调(图上按类别着色, 与面板泳道语义一致)。 */
|
|
1687
|
+
function wbLaneTone(key) {
|
|
1688
|
+
var k = String(key || '')
|
|
1689
|
+
if (k === 'goal') return '#4d6bfe'
|
|
1690
|
+
if (k === 'state' || k === 'doing') return '#3fa96a'
|
|
1691
|
+
if (k === 'dead' || k === 'deadend') return '#c2603f'
|
|
1692
|
+
if (k === 'progress' || k === 'next') return '#d4a94f'
|
|
1693
|
+
if (k === 'archive') return '#8a7fbf'
|
|
1694
|
+
return 'rgba(128,128,128,.75)'
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1512
1697
|
/** 单张卡片: 标题 + 判据徽章 + 预览/全文 + 小节徽章 + 标签 + 来源。可点击展开。 */
|
|
1513
1698
|
function KanbanCard(props) {
|
|
1514
1699
|
var c = props.card || {}
|
|
@@ -1774,6 +1959,9 @@ window.__ModuleLoader__.load({
|
|
|
1774
1959
|
/** 整页矩阵看板: 行=日期分组, 列=小节类型。 */
|
|
1775
1960
|
function KanbanView(props) {
|
|
1776
1961
|
var st = useState(null), data = st[0], setData = st[1]
|
|
1962
|
+
// ★2026-09-17(L2 修 bug①): 额外保留**原始载荷** —— 旧实现 `setData(k && k.enabled ? k : null)`
|
|
1963
|
+
// 直接把宿主返回的 reason/error 丢掉了, 于是下面只能硬编码一句猜测式提示。
|
|
1964
|
+
var stPay = useState(null), pay = stPay[0], setPay = stPay[1]
|
|
1777
1965
|
var st2 = useState(null), drawer = st2[0], setDrawer = st2[1]
|
|
1778
1966
|
var st3 = useState({}), folded = st3[0], setFolded = st3[1]
|
|
1779
1967
|
var st4 = useState(''), q = st4[0], setQ = st4[1]
|
|
@@ -1783,8 +1971,8 @@ window.__ModuleLoader__.load({
|
|
|
1783
1971
|
useEffect(function () {
|
|
1784
1972
|
var alive = true
|
|
1785
1973
|
apiGet(API.kanbanBoard, { sessionId: currentSessionIdClient() })
|
|
1786
|
-
.then(function (k) { if (alive) setData(k && k.enabled ? k : null) })
|
|
1787
|
-
.catch(function () { if (alive) setData(null) })
|
|
1974
|
+
.then(function (k) { if (alive) { setPay(k || null); setData(k && k.enabled ? k : null) } })
|
|
1975
|
+
.catch(function (e) { if (alive) { setPay({ reason: 'error', error: String((e && e.message) || e) }); setData(null) } })
|
|
1788
1976
|
return function () { alive = false }
|
|
1789
1977
|
}, [tick])
|
|
1790
1978
|
|
|
@@ -1797,6 +1985,27 @@ window.__ModuleLoader__.load({
|
|
|
1797
1985
|
var total = (mx && mx.stats && mx.stats.totalCards) || 0
|
|
1798
1986
|
var missing = (mx && mx.stats && mx.stats.missingDeadend) || []
|
|
1799
1987
|
|
|
1988
|
+
// ★2026-09-17(L2 修 bug①): **按宿主返回的真实 reason 分支**, 不再硬编码猜测式提示。
|
|
1989
|
+
// 旧文案把「看板未启用」归因于 **handoff 未开启**, 把用户指向错误方向 —— 用户报告:
|
|
1990
|
+
// 「明明已经启用了, 却显示未启用或加载失败」。现消费 payload 里的 reason/error。
|
|
1991
|
+
var emptyMsg = (function () {
|
|
1992
|
+
var r = pay && pay.reason
|
|
1993
|
+
if (r === 'legacy-mode') {
|
|
1994
|
+
return zh ? '当前是旧版白板(legacy)档。切到新版看板: 设置 → 自动记忆引擎 → 看板模式 → 「新版看板(dsh-graph)」,重启 dsh web 后生效。'
|
|
1995
|
+
: 'Legacy board mode. Switch to graph board: Settings → Auto Memory Engine → Board mode → "Graph board (dsh-graph)", then restart dsh web.'
|
|
1996
|
+
}
|
|
1997
|
+
if (r === 'error') {
|
|
1998
|
+
var m = String((pay && pay.error) || '')
|
|
1999
|
+
return (zh ? '看板加载出错: ' : 'Board load error: ') + (m || (zh ? '(无错误详情)' : '(no detail)'))
|
|
2000
|
+
}
|
|
2001
|
+
if (r === 'handoff-disabled') {
|
|
2002
|
+
// 兼容极旧宿主(本版后端已不再返回该 reason), 如实说明而非误导
|
|
2003
|
+
return zh ? '看板数据源已关闭。请在设置里启用白板/账本(handoff)。' : 'Board data source is off. Enable whiteboard/ledger (handoff) in settings.'
|
|
2004
|
+
}
|
|
2005
|
+
return zh ? '看板尚未就绪或加载失败。请点上方「刷新」重试; 若持续如此, 检查是否已切到新版看板(dsh-graph)档。'
|
|
2006
|
+
: 'Board not ready or failed to load. Click "reload" above; if it persists, check the graph board mode.'
|
|
2007
|
+
})()
|
|
2008
|
+
|
|
1800
2009
|
return h('div', { 'data-dam-kanban-view': '', style: {
|
|
1801
2010
|
// position:relative 是**必需**的: 抽屉用 absolute 定位, 它的参照系就是这里 ⇒ 抽屉被裁剪在
|
|
1802
2011
|
// 看板承载面内, 不会像 fixed 那样越界盖住底部输入框。overflow:hidden 同时兜底裁剪。
|
|
@@ -1825,9 +2034,8 @@ window.__ModuleLoader__.load({
|
|
|
1825
2034
|
h('button', { key: 'zi', 'data-dam-btn': '', onClick: function () { setZoom(Math.min(1.6, zoom + 0.1)) } }, 'A+'),
|
|
1826
2035
|
h('button', { key: 'rf', 'data-dam-btn': '', onClick: function () { setData(null); bump(function (n) { return n + 1 }) } }, zh ? '刷新' : 'reload'),
|
|
1827
2036
|
]),
|
|
1828
|
-
// ── 空态 / 错误态(
|
|
1829
|
-
!data ? h('div', { key: 'ph', 'data-dam-kx-empty': 'nodata', 'data-dam-hint': '' },
|
|
1830
|
-
zh ? '看板未启用或加载失败(需 boardMode=graph 且 handoff 已开启)。' : 'Board unavailable.') : null,
|
|
2037
|
+
// ── 空态 / 错误态(不静默; ★2026-09-17 按真实 reason 分支, 见上方 emptyMsg) ──
|
|
2038
|
+
!data ? h('div', { key: 'ph', 'data-dam-kx-empty': 'nodata', 'data-dam-hint': '' }, emptyMsg) : null,
|
|
1831
2039
|
data && !mx ? h('div', { key: 'nm', 'data-dam-kx-empty': 'nomatrix', 'data-dam-hint': '' },
|
|
1832
2040
|
zh ? '本次载荷缺矩阵视图。' : 'No matrix in payload.') : null,
|
|
1833
2041
|
data && mx && !rows.length ? h('div', { key: 'nr', 'data-dam-kx-empty': 'norows', 'data-dam-hint': '' },
|
|
@@ -1950,6 +2158,412 @@ window.__ModuleLoader__.load({
|
|
|
1950
2158
|
])
|
|
1951
2159
|
}
|
|
1952
2160
|
|
|
2161
|
+
// ══════════════════════════════════════════════════════════════════════════
|
|
2162
|
+
// 白板节点图画布(2026-09-20 重构批 · 风格参考 kanghelyu/dsh-deepseek-flow)
|
|
2163
|
+
//
|
|
2164
|
+
// 立场: **只改渲染层**。数据源仍是同一个 /kanban-board 载荷, 后端与 sidecar 零改动。
|
|
2165
|
+
// 参考项目用 esbuild 打包 8 个模块, 本插件受「单文件 / 无构建链」约束
|
|
2166
|
+
// (package.json exports["./client"]='./lib/client.js' 直送浏览器) ⇒
|
|
2167
|
+
// 不引第三方图库, 布局算法手写。
|
|
2168
|
+
//
|
|
2169
|
+
// 与泳道看板的关系(共存, 非替换):
|
|
2170
|
+
// · 会话页整页宽(≥700px) = 本画布(节点 + 连线, 平移缩放);
|
|
2171
|
+
// · 侧边面板 440px = 原泳道看板(装不下画布, 保持不动)。
|
|
2172
|
+
//
|
|
2173
|
+
// 图要素(全部来自现有数据, §6 侦察报告结论):
|
|
2174
|
+
// · 节点 = 小节卡(WbShape 清洗后: title 为正文首行, kicker 为小节名)
|
|
2175
|
+
// · 结构边 = 同泳道内的时间/顺序链
|
|
2176
|
+
// · 分层 = 按**泳道**分列(有限 6 类), 列内按时间倒序排列
|
|
2177
|
+
// ⚠ 语义边(tag 关联/版本链)数据当前不可用(by_tag 全 sec: / versionCount 恒 0),
|
|
2178
|
+
// 故本版**不画**这类边 —— 宁可少画, 不画假的。
|
|
2179
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
2180
|
+
var WBG_NODE_W = 232
|
|
2181
|
+
var WBG_NODE_H = 104
|
|
2182
|
+
var WBG_GAP_X = 74
|
|
2183
|
+
var WBG_GAP_Y = 22
|
|
2184
|
+
var WBG_PAD = 40
|
|
2185
|
+
var WBG_ZOOM_KEY = 'dam-wbg-zoom'
|
|
2186
|
+
var WBG_PAN_KEY = 'dam-wbg-pan'
|
|
2187
|
+
// 每列最多排多少个节点, 其余折叠为「+N」汇总节点。
|
|
2188
|
+
//
|
|
2189
|
+
// ★2026-09-21 布局重做(实测驱动): 原实现「一篇文档 = 一列」在真实数据下**数学上不可用** ——
|
|
2190
|
+
// 本仓库实测 876 节点 / 189 篇文档 ⇒ 画布 57,840 × 1,318 px, 宽高比 **43.9:1**
|
|
2191
|
+
// (容器仅 1.72:1), 理想 fit 需缩到 **1.9%**, 而缩放下限 35% ⇒ **永远看不到全貌,
|
|
2192
|
+
// 要横向拖 52 屏**。按泳道分列同样不行(1,842 × 23,494 ⇒ 纵向 37 屏)。
|
|
2193
|
+
// 结论: 病根不是「按什么分组」, 而是**一屏塞不下 876 个节点**。
|
|
2194
|
+
// 人的看图逻辑是「先全局分类 → 再逐层下钻」⇒ 默认视图必须是**概览**而非全量平铺。
|
|
2195
|
+
var WBG_COL_CAP = 14
|
|
2196
|
+
var WBG_MIN_ZOOM = 0.08
|
|
2197
|
+
var WBG_HDR_H = 34
|
|
2198
|
+
|
|
2199
|
+
/**
|
|
2200
|
+
* 分层布局: 列 = 泳道(类型), 行 = 时间倒序。
|
|
2201
|
+
*
|
|
2202
|
+
* 为什么按泳道而非文档: 泳道是**有限的 6 类**(goal/state/deadend/progress/archive/misc),
|
|
2203
|
+
* 语义稳定、用户已知; 文档是 189 个且会持续增长 —— 用它当轴等于把画布宽度绑死在数据量上。
|
|
2204
|
+
* 每列内按时间倒序(最近的在最上面), 超出 WBG_COL_CAP 的折叠为汇总节点,
|
|
2205
|
+
* 既保证一屏可读, 又不隐藏数据(可搜索或切「白板看板」看全部)。
|
|
2206
|
+
*/
|
|
2207
|
+
function wbLayout(cards) {
|
|
2208
|
+
var byLane = {}
|
|
2209
|
+
for (var i = 0; i < cards.length; i++) {
|
|
2210
|
+
var k = cards[i].laneKey || cards[i].lane || 'misc'
|
|
2211
|
+
if (!byLane[k]) byLane[k] = []
|
|
2212
|
+
byLane[k].push(cards[i])
|
|
2213
|
+
}
|
|
2214
|
+
// 固定列序(与面板泳道语义一致), 未在表中出现的追加在后
|
|
2215
|
+
var ORDER = ['goal', 'state', 'deadend', 'progress', 'archive', 'misc']
|
|
2216
|
+
var keys = []
|
|
2217
|
+
for (var oi = 0; oi < ORDER.length; oi++) if (byLane[ORDER[oi]]) keys.push(ORDER[oi])
|
|
2218
|
+
for (var kk in byLane) if (keys.indexOf(kk) < 0) keys.push(kk)
|
|
2219
|
+
|
|
2220
|
+
var nodes = []
|
|
2221
|
+
var edges = []
|
|
2222
|
+
var groups = []
|
|
2223
|
+
var maxRows = 0
|
|
2224
|
+
for (var ci = 0; ci < keys.length; ci++) {
|
|
2225
|
+
var key = keys[ci]
|
|
2226
|
+
var list = byLane[key].slice().sort(function (a, b) {
|
|
2227
|
+
// 时间新的在上; 无 ts 时按 lineStart 兜底
|
|
2228
|
+
var ta = Number(a.ts) || 0
|
|
2229
|
+
var tb = Number(b.ts) || 0
|
|
2230
|
+
if (ta !== tb) return tb - ta
|
|
2231
|
+
return (Number(a.lineStart) || 0) - (Number(b.lineStart) || 0)
|
|
2232
|
+
})
|
|
2233
|
+
var shown = list.slice(0, WBG_COL_CAP)
|
|
2234
|
+
var hidden = list.length - shown.length
|
|
2235
|
+
var colX = WBG_PAD + ci * (WBG_NODE_W + WBG_GAP_X)
|
|
2236
|
+
groups.push({ key: key, x: colX, total: list.length, shownN: shown.length, hidden: hidden })
|
|
2237
|
+
var rows = shown.length + (hidden > 0 ? 1 : 0)
|
|
2238
|
+
if (rows > maxRows) maxRows = rows
|
|
2239
|
+
for (var ri = 0; ri < shown.length; ri++) {
|
|
2240
|
+
var nd = wbShape(shown[ri])
|
|
2241
|
+
nd.x = colX
|
|
2242
|
+
nd.y = WBG_PAD + WBG_HDR_H + ri * (WBG_NODE_H + WBG_GAP_Y)
|
|
2243
|
+
nd.laneKey = key
|
|
2244
|
+
nd.col = ci
|
|
2245
|
+
nd.row = ri
|
|
2246
|
+
nodes.push(nd)
|
|
2247
|
+
// 结构边: 同泳道内相邻节点相连(时间链)
|
|
2248
|
+
if (ri > 0) edges.push({ from: shown[ri - 1].id, to: shown[ri].id, key: key })
|
|
2249
|
+
}
|
|
2250
|
+
if (hidden > 0) {
|
|
2251
|
+
nodes.push({
|
|
2252
|
+
id: '__more__' + key, x: colX,
|
|
2253
|
+
y: WBG_PAD + WBG_HDR_H + shown.length * (WBG_NODE_H + WBG_GAP_Y),
|
|
2254
|
+
title: (zh ? '还有 ' + hidden + ' 条' : '+' + hidden + ' more'),
|
|
2255
|
+
body: zh ? '已折叠以免画布过长 —— 用上方搜索, 或切到「白板看板」看全部' : 'collapsed for readability',
|
|
2256
|
+
kicker: '', docTitle: '', laneKey: key, col: ci, row: shown.length, isMore: true,
|
|
2257
|
+
})
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
return {
|
|
2261
|
+
nodes: nodes,
|
|
2262
|
+
edges: edges,
|
|
2263
|
+
groups: groups,
|
|
2264
|
+
width: WBG_PAD * 2 + keys.length * WBG_NODE_W + Math.max(0, keys.length - 1) * WBG_GAP_X,
|
|
2265
|
+
height: WBG_PAD * 2 + WBG_HDR_H + maxRows * WBG_NODE_H + Math.max(0, maxRows - 1) * WBG_GAP_Y,
|
|
2266
|
+
keys: keys,
|
|
2267
|
+
totalCards: cards.length,
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
/** 节点卡: 标题(正文首行) + 小节名 kicker + 正文摘要。四段式。 */
|
|
2272
|
+
function WbgNode(props) {
|
|
2273
|
+
var n = props.node
|
|
2274
|
+
var zoom = Number(props.zoom) || 1
|
|
2275
|
+
var sel = !!props.selected
|
|
2276
|
+
var tone = wbLaneTone(n.laneKey || n.section || '')
|
|
2277
|
+
var body = n.body || ''
|
|
2278
|
+
if (body.length > 220) body = body.slice(0, 217) + '…'
|
|
2279
|
+
var anchor = props.anchor
|
|
2280
|
+
return h('div', {
|
|
2281
|
+
'data-dam-wbg-node': '',
|
|
2282
|
+
'data-selected': sel ? 'true' : 'false',
|
|
2283
|
+
onClick: function (e) { e.stopPropagation(); props.onPick(n.id) },
|
|
2284
|
+
style: {
|
|
2285
|
+
position: 'absolute',
|
|
2286
|
+
left: n.x + 'px', top: n.y + 'px',
|
|
2287
|
+
width: WBG_NODE_W + 'px', minHeight: WBG_NODE_H + 'px',
|
|
2288
|
+
boxSizing: 'border-box',
|
|
2289
|
+
padding: '9px 11px 10px',
|
|
2290
|
+
borderRadius: '10px',
|
|
2291
|
+
cursor: 'pointer',
|
|
2292
|
+
background: sel
|
|
2293
|
+
? 'color-mix(in srgb, ' + tone + ' 16%, var(--dsw-alias-bg-layer-2, rgba(22,22,26,.96)))'
|
|
2294
|
+
: 'var(--dsw-alias-bg-layer-2, rgba(22,22,26,.92))',
|
|
2295
|
+
border: '1px solid ' + (sel ? tone : 'color-mix(in srgb, ' + tone + ' 34%, transparent)'),
|
|
2296
|
+
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)',
|
|
2297
|
+
transition: 'background .14s ease, border-color .14s ease',
|
|
2298
|
+
},
|
|
2299
|
+
}, [
|
|
2300
|
+
// ① 分类条(kicker): 小节名 —— 短、可扫读
|
|
2301
|
+
n.kicker ? h('div', {
|
|
2302
|
+
key: 'k', 'data-dam-hint': '',
|
|
2303
|
+
style: {
|
|
2304
|
+
fontSize: (9.5 * zoom) + 'px', letterSpacing: '.06em', textTransform: 'uppercase',
|
|
2305
|
+
color: tone, opacity: .95, marginBottom: '4px',
|
|
2306
|
+
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
|
|
2307
|
+
},
|
|
2308
|
+
}, n.kicker + (n.anchored ? ' ⚓' : '')) : null,
|
|
2309
|
+
// ② 标题: 清洗后的正文首行(不再是重复的小节名)
|
|
2310
|
+
h('div', {
|
|
2311
|
+
key: 't',
|
|
2312
|
+
style: {
|
|
2313
|
+
fontSize: (12.5 * zoom) + 'px', fontWeight: 700, lineHeight: '1.34',
|
|
2314
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
2315
|
+
display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden',
|
|
2316
|
+
},
|
|
2317
|
+
}, n.title),
|
|
2318
|
+
// ③ 正文: 与标题不重复的剩余内容
|
|
2319
|
+
body ? h('div', {
|
|
2320
|
+
key: 'b', 'data-dam-hint': '',
|
|
2321
|
+
style: {
|
|
2322
|
+
marginTop: '5px', fontSize: (10.5 * zoom) + 'px', lineHeight: '1.42',
|
|
2323
|
+
color: 'var(--dsw-alias-label-secondary)',
|
|
2324
|
+
display: '-webkit-box', WebkitLineClamp: 3, WebkitBoxOrient: 'vertical', overflow: 'hidden',
|
|
2325
|
+
},
|
|
2326
|
+
}, body) : null,
|
|
2327
|
+
// ④ 来源: 文档名(定位"这条来自哪篇")
|
|
2328
|
+
h('div', {
|
|
2329
|
+
key: 's', 'data-dam-hint': '',
|
|
2330
|
+
style: {
|
|
2331
|
+
marginTop: '6px', fontSize: (9.5 * zoom) + 'px', opacity: .55,
|
|
2332
|
+
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
|
|
2333
|
+
},
|
|
2334
|
+
}, String(n.docTitle || n.source || '').replace(/^.*[\\/]/, '')),
|
|
2335
|
+
])
|
|
2336
|
+
}
|
|
2337
|
+
|
|
2338
|
+
/** 画布: 平移(拖拽) + 缩放(滚轮/按钮) + fit + 点选侧栏。零依赖、纯 SVG 连线。 */
|
|
2339
|
+
function WhiteboardGraphView(props) {
|
|
2340
|
+
var st = useState(null), data = st[0], setData = st[1]
|
|
2341
|
+
var stPay = useState(null), pay = stPay[0], setPay = stPay[1]
|
|
2342
|
+
var stTick = useState(0), tick = stTick[0], bump = stTick[1]
|
|
2343
|
+
var stZoom = useState(function () { return readLsNum(WBG_ZOOM_KEY, 1) }), zoom = stZoom[0], setZoom = stZoom[1]
|
|
2344
|
+
var stPan = useState(function () {
|
|
2345
|
+
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 } }
|
|
2346
|
+
}), pan = stPan[0], setPan = stPan[1]
|
|
2347
|
+
var stSel = useState(null), sel = stSel[0], setSel = stSel[1]
|
|
2348
|
+
var stQ = useState(''), q = stQ[0], setQ = stQ[1]
|
|
2349
|
+
var dragRef = useRef(null)
|
|
2350
|
+
var wrapRef = useRef(null)
|
|
2351
|
+
var zh = (locale || 'zh').indexOf('zh') === 0
|
|
2352
|
+
|
|
2353
|
+
useEffect(function () {
|
|
2354
|
+
var alive = true
|
|
2355
|
+
apiGet(API.kanbanBoard, { sessionId: currentSessionIdClient() })
|
|
2356
|
+
.then(function (k) { if (alive) { setPay(k || null); setData(k && k.enabled ? k : null) } })
|
|
2357
|
+
.catch(function (e) { if (alive) { setPay({ reason: 'error', error: String((e && e.message) || e) }); setData(null) } })
|
|
2358
|
+
return function () { alive = false }
|
|
2359
|
+
}, [tick])
|
|
2360
|
+
|
|
2361
|
+
// 把 lanes[].cards[] 摊平成一张卡列表(画布不按泳道分组, 泳道只当节点色调)
|
|
2362
|
+
var cards = []
|
|
2363
|
+
var laneOf = {}
|
|
2364
|
+
var lanes = (data && data.lanes) || []
|
|
2365
|
+
for (var li = 0; li < lanes.length; li++) {
|
|
2366
|
+
var lc = lanes[li].cards || []
|
|
2367
|
+
for (var ci = 0; ci < lc.length; ci++) {
|
|
2368
|
+
cards.push(lc[ci])
|
|
2369
|
+
laneOf[lc[ci].id] = lanes[li].key
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
var kg = wbLayout(cards.map(function (c) { return Object.assign({}, c, { laneKey: laneOf[c.id] }) }))
|
|
2373
|
+
|
|
2374
|
+
var kw = q.trim().toLowerCase()
|
|
2375
|
+
var matches = function (n) {
|
|
2376
|
+
if (!kw) return true
|
|
2377
|
+
return (n.title + ' ' + n.body + ' ' + n.docTitle + ' ' + n.kicker).toLowerCase().indexOf(kw) >= 0
|
|
2378
|
+
}
|
|
2379
|
+
var visibleNodes = kg.nodes.filter(matches)
|
|
2380
|
+
var visIds = {}
|
|
2381
|
+
for (var vi = 0; vi < visibleNodes.length; vi++) visIds[visibleNodes[vi].id] = 1
|
|
2382
|
+
|
|
2383
|
+
var nodeById = {}
|
|
2384
|
+
for (var ni = 0; ni < kg.nodes.length; ni++) nodeById[kg.nodes[ni].id] = kg.nodes[ni]
|
|
2385
|
+
|
|
2386
|
+
var selNode = sel ? nodeById[sel] : null
|
|
2387
|
+
|
|
2388
|
+
var setZoomBoth = function (v) {
|
|
2389
|
+
var nv = Math.min(2.0, Math.max(WBG_MIN_ZOOM, Math.round(v * 100) / 100))
|
|
2390
|
+
setZoom(nv); writeLs(WBG_ZOOM_KEY, nv)
|
|
2391
|
+
}
|
|
2392
|
+
var fit = function () {
|
|
2393
|
+
var el = wrapRef.current
|
|
2394
|
+
if (!el || !kg.width) return
|
|
2395
|
+
var w = el.clientWidth - 24
|
|
2396
|
+
var hgt = el.clientHeight - 24
|
|
2397
|
+
// 下限用 WBG_MIN_ZOOM(0.08)而非 0.35 —— 876 节点的概览视图本就是「缩小看结构」,
|
|
2398
|
+
// 用 0.35 当底会永远 fit 不到全貌(实测需 1.9%)。
|
|
2399
|
+
var z = Math.min(1.6, Math.max(WBG_MIN_ZOOM, Math.min(w / kg.width, hgt / kg.height)))
|
|
2400
|
+
setZoomBoth(z)
|
|
2401
|
+
setPanBoth({ x: 0, y: 0 })
|
|
2402
|
+
}
|
|
2403
|
+
var setPanBoth = function (p) { setPan(p); try { localStorage.setItem(WBG_PAN_KEY, JSON.stringify(p)) } catch (e) {} }
|
|
2404
|
+
|
|
2405
|
+
var onDown = function (e) {
|
|
2406
|
+
if (e.button !== 0) return
|
|
2407
|
+
dragRef.current = { sx: e.clientX, sy: e.clientY, px: pan.x, py: pan.y, moved: false }
|
|
2408
|
+
e.currentTarget.setPointerCapture && e.currentTarget.setPointerCapture(e.pointerId)
|
|
2409
|
+
}
|
|
2410
|
+
var onMove = function (e) {
|
|
2411
|
+
var d = dragRef.current
|
|
2412
|
+
if (!d) return
|
|
2413
|
+
var dx = e.clientX - d.sx, dy = e.clientY - d.sy
|
|
2414
|
+
if (Math.abs(dx) + Math.abs(dy) > 3) d.moved = true
|
|
2415
|
+
setPan({ x: d.px + dx, y: d.py + dy })
|
|
2416
|
+
}
|
|
2417
|
+
var onUp = function (e) {
|
|
2418
|
+
var d = dragRef.current
|
|
2419
|
+
dragRef.current = null
|
|
2420
|
+
if (d) { try { localStorage.setItem(WBG_PAN_KEY, JSON.stringify(pan)) } catch (e2) {} }
|
|
2421
|
+
e.currentTarget.releasePointerCapture && e.currentTarget.releasePointerCapture(e.pointerId)
|
|
2422
|
+
}
|
|
2423
|
+
var onWheel = function (e) {
|
|
2424
|
+
if (!e.ctrlKey && !e.metaKey && Math.abs(e.deltaY) < 2) return
|
|
2425
|
+
e.preventDefault()
|
|
2426
|
+
setZoomBoth(zoom + (e.deltaY < 0 ? 0.08 : -0.08))
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2429
|
+
// 空态/错误态: 与 KanbanView 同一套 reason 消费
|
|
2430
|
+
if (!data || !cards.length) {
|
|
2431
|
+
var reason = pay && pay.reason
|
|
2432
|
+
var msg = reason === 'legacy-mode'
|
|
2433
|
+
? (zh ? '当前是旧版白板(legacy)档。切到新版看板: 设置 → 自动记忆引擎 → 看板模式 → 「新版看板(dsh-graph)」。'
|
|
2434
|
+
: 'Legacy board mode. Switch to graph board in Settings.')
|
|
2435
|
+
: reason === 'error'
|
|
2436
|
+
? ((zh ? '加载出错: ' : 'Load error: ') + String((pay && pay.error) || ''))
|
|
2437
|
+
: (zh ? '画布暂无内容 —— 写一次白板或账本(memory_note)后这里会出现节点。'
|
|
2438
|
+
: 'No nodes yet — write a plan or ledger (memory_note) and nodes will appear.')
|
|
2439
|
+
return h('div', { 'data-dam-wbg-wrap': '', style: { padding: '18px' } }, [
|
|
2440
|
+
h('div', { 'data-dam-hint': '', key: 'm', style: { lineHeight: 1.7 } }, msg),
|
|
2441
|
+
h('button', { key: 'r', 'data-dam-btn': '', onClick: function () { bump(tick + 1) }, style: { marginTop: '10px' } }, zh ? '刷新' : 'reload'),
|
|
2442
|
+
])
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
var btn = function (label, onClick, title) {
|
|
2446
|
+
return h('button', { 'data-dam-btn': '', onClick: onClick, title: title || '', style: { padding: '1px 8px', fontSize: '11px' } }, label)
|
|
2447
|
+
}
|
|
2448
|
+
var bar = h('div', {
|
|
2449
|
+
'data-dam-wbg-bar': '',
|
|
2450
|
+
style: { display: 'flex', flexWrap: 'wrap', gap: '6px', alignItems: 'center', padding: '8px 10px', borderBottom: '1px solid color-mix(in srgb, currentColor 12%, transparent)' },
|
|
2451
|
+
}, [
|
|
2452
|
+
btn('A-', function () { setZoomBoth(zoom - 0.1) }),
|
|
2453
|
+
h('span', { key: 'z', 'data-dam-hint': '', style: { fontSize: '10.5px', minWidth: '38px', textAlign: 'center' } }, Math.round(zoom * 100) + '%'),
|
|
2454
|
+
btn('A+', function () { setZoomBoth(zoom + 0.1) }),
|
|
2455
|
+
h('span', { key: 'g1', style: { width: '6px' } }),
|
|
2456
|
+
btn(zh ? '适应' : 'fit', fit, zh ? '缩放到刚好装下' : 'fit to view'),
|
|
2457
|
+
h('span', { key: 'g2', style: { width: '6px' } }),
|
|
2458
|
+
h('input', {
|
|
2459
|
+
key: 'q', 'data-dam-input': '', value: q, placeholder: zh ? '搜索节点…' : 'search nodes…',
|
|
2460
|
+
onChange: function (e) { setQ(e.target.value) },
|
|
2461
|
+
style: { flex: '1 1 120px', minWidth: '100px', fontSize: '11px', padding: '2px 7px' },
|
|
2462
|
+
}),
|
|
2463
|
+
h('span', { key: 'st', 'data-dam-hint': '', style: { fontSize: '10.5px', opacity: .7 } },
|
|
2464
|
+
(zh ? '节点 ' : 'nodes ') + visibleNodes.length + '/' + kg.nodes.length + (zh ? ' · 列 ' : ' · cols ') + (kg.keys || []).length + (zh ? ' · 共 ' : ' · total ') + kg.totalCards),
|
|
2465
|
+
])
|
|
2466
|
+
|
|
2467
|
+
// SVG 连线(在 transform 容器内, 与节点同一坐标系)
|
|
2468
|
+
var edgeEls = []
|
|
2469
|
+
for (var ei = 0; ei < kg.edges.length; ei++) {
|
|
2470
|
+
var ed = kg.edges[ei]
|
|
2471
|
+
var a = nodeById[ed.from], b = nodeById[ed.to]
|
|
2472
|
+
if (!a || !b || !visIds[a.id] || !visIds[b.id]) continue
|
|
2473
|
+
var x1 = a.x + WBG_NODE_W / 2, y1 = a.y + WBG_NODE_H
|
|
2474
|
+
var x2 = b.x + WBG_NODE_W / 2, y2 = b.y
|
|
2475
|
+
var my = (y1 + y2) / 2
|
|
2476
|
+
edgeEls.push(h('path', {
|
|
2477
|
+
key: 'e' + ei,
|
|
2478
|
+
d: 'M' + x1 + ' ' + y1 + ' C' + x1 + ' ' + my + ', ' + x2 + ' ' + my + ', ' + x2 + ' ' + y2,
|
|
2479
|
+
fill: 'none',
|
|
2480
|
+
stroke: 'color-mix(in srgb, ' + wbLaneTone(nodeById[ed.to].laneKey || '') + ' 46%, transparent)',
|
|
2481
|
+
'stroke-width': 1.4,
|
|
2482
|
+
}))
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
var nodeEls = []
|
|
2486
|
+
for (var nk = 0; nk < kg.nodes.length; nk++) {
|
|
2487
|
+
var nn = kg.nodes[nk]
|
|
2488
|
+
if (!visIds[nn.id]) continue
|
|
2489
|
+
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 }) } }))
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
// 列标题: 泳道名 + 「已显示/总数」+ 色条。
|
|
2493
|
+
// 为什么必须有: 默认缩放到 fit 时节点字会小到读不清, **列标题是唯一还能读的锚点**;
|
|
2494
|
+
// 人的看图顺序是「先认这 6 列是什么 → 再看某一列里有什么」, 没有列标题就只剩一堆色块。
|
|
2495
|
+
var LANE_LABEL_ZH = { goal: '目标', state: '进行中', deadend: '失败与弯路', progress: '进度与下一步', archive: '版本归档', misc: '其它' }
|
|
2496
|
+
var groupEls = []
|
|
2497
|
+
for (var gi = 0; gi < (kg.groups || []).length; gi++) {
|
|
2498
|
+
var g = kg.groups[gi]
|
|
2499
|
+
var tone = wbLaneTone(g.key)
|
|
2500
|
+
var laneName = zh ? (LANE_LABEL_ZH[g.key] || g.key) : g.key
|
|
2501
|
+
groupEls.push(h('div', {
|
|
2502
|
+
key: 'gh' + g.key, 'data-dam-wbg-colhead': '',
|
|
2503
|
+
style: {
|
|
2504
|
+
position: 'absolute', left: g.x + 'px', top: WBG_PAD + 'px', width: WBG_NODE_W + 'px',
|
|
2505
|
+
boxSizing: 'border-box', paddingBottom: '6px',
|
|
2506
|
+
borderBottom: '2px solid color-mix(in srgb, ' + tone + ' 62%, transparent)',
|
|
2507
|
+
},
|
|
2508
|
+
}, [
|
|
2509
|
+
h('div', {
|
|
2510
|
+
key: 'n', style: {
|
|
2511
|
+
fontSize: (13.5 / Math.max(zoom, 0.4)) + 'px', fontWeight: 700, color: tone,
|
|
2512
|
+
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
|
|
2513
|
+
},
|
|
2514
|
+
}, laneName),
|
|
2515
|
+
h('div', { key: 'c', 'data-dam-hint': '', style: { fontSize: (10.5 / Math.max(zoom, 0.4)) + 'px', opacity: .7, marginTop: '1px' } },
|
|
2516
|
+
(g.shownN + '/' + g.total) + ' ' + (zh ? '条' : '')),
|
|
2517
|
+
]))
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
return h('div', { 'data-dam-wbg-wrap': '', style: { display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 } }, [
|
|
2521
|
+
bar,
|
|
2522
|
+
h('div', { key: 'body', style: { display: 'flex', flex: '1 1 auto', minHeight: 0 } }, [
|
|
2523
|
+
h('div', {
|
|
2524
|
+
key: 'canvas', ref: wrapRef,
|
|
2525
|
+
'data-dam-wbg-canvas': '',
|
|
2526
|
+
onPointerDown: onDown, onPointerMove: onMove, onPointerUp: onUp, onPointerCancel: onUp,
|
|
2527
|
+
onWheel: onWheel,
|
|
2528
|
+
onClick: function () { setSel(null) },
|
|
2529
|
+
style: {
|
|
2530
|
+
position: 'relative', flex: '1 1 auto', minWidth: 0, overflow: 'hidden',
|
|
2531
|
+
cursor: dragRef.current ? 'grabbing' : 'grab',
|
|
2532
|
+
backgroundImage: 'radial-gradient(color-mix(in srgb, currentColor 9%, transparent) 1px, transparent 1px)',
|
|
2533
|
+
backgroundSize: '22px 22px',
|
|
2534
|
+
},
|
|
2535
|
+
}, h('div', {
|
|
2536
|
+
'data-dam-wbg-stage': '',
|
|
2537
|
+
style: {
|
|
2538
|
+
position: 'absolute', left: 0, top: 0,
|
|
2539
|
+
width: kg.width + 'px', height: kg.height + 'px',
|
|
2540
|
+
transform: 'translate(' + pan.x + 'px,' + pan.y + 'px) scale(' + zoom + ')',
|
|
2541
|
+
transformOrigin: '0 0',
|
|
2542
|
+
willChange: 'transform',
|
|
2543
|
+
},
|
|
2544
|
+
}, [
|
|
2545
|
+
h('svg', { key: 'svg', width: kg.width, height: kg.height, style: { position: 'absolute', left: 0, top: 0, pointerEvents: 'none' } }, edgeEls),
|
|
2546
|
+
].concat(groupEls).concat(nodeEls))),
|
|
2547
|
+
// 侧栏: 选中节点的完整信息(宽屏才放得下全文 —— 这才是画布相对泳道的增量价值)
|
|
2548
|
+
selNode ? h('div', {
|
|
2549
|
+
key: 'side', 'data-dam-wbg-side': '',
|
|
2550
|
+
style: {
|
|
2551
|
+
flex: '0 0 320px', maxWidth: '44%', overflow: 'auto', padding: '12px 14px',
|
|
2552
|
+
borderLeft: '1px solid color-mix(in srgb, currentColor 12%, transparent)',
|
|
2553
|
+
},
|
|
2554
|
+
}, [
|
|
2555
|
+
h('div', { key: 'x', style: { display: 'flex', alignItems: 'flex-start', gap: '8px' } }, [
|
|
2556
|
+
h('div', { key: 'h', style: { flex: 1, fontWeight: 700, fontSize: '14px', lineHeight: 1.4 } }, selNode.title),
|
|
2557
|
+
h('button', { key: 'c', 'data-dam-btn': '', onClick: function () { setSel(null) } }, '✕'),
|
|
2558
|
+
]),
|
|
2559
|
+
selNode.kicker ? h('div', { key: 'k', 'data-dam-hint': '', style: { marginTop: '4px', fontSize: '11px', opacity: .75 } }, selNode.kicker + (selNode.anchored ? ' · ⚓ ' + (zh ? '有锚点' : 'anchored') : '')) : null,
|
|
2560
|
+
selNode.docTitle ? h('div', { key: 'd', 'data-dam-hint': '', style: { marginTop: '2px', fontSize: '11px', opacity: .6, wordBreak: 'break-all' } }, selNode.docTitle) : null,
|
|
2561
|
+
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)'),
|
|
2562
|
+
]) : null,
|
|
2563
|
+
]),
|
|
2564
|
+
])
|
|
2565
|
+
}
|
|
2566
|
+
|
|
1953
2567
|
function Loading(props) {
|
|
1954
2568
|
var label = props && props.label ? props.label : t('loading')
|
|
1955
2569
|
return h('div', { 'data-dam-loading': '' }, h('span', { 'data-dam-spinner': '', 'aria-hidden': 'true' }), h('span', null, label))
|
|
@@ -2481,11 +3095,145 @@ window.__ModuleLoader__.load({
|
|
|
2481
3095
|
rows.push(h(Card, { title: 'MEMORY.md (' + (locale === 'zh' ? '用户级' : 'user-level') + ')' },
|
|
2482
3096
|
h('div', { 'data-dam-content': '' }, userText || t('userMemoryEmpty')),
|
|
2483
3097
|
userTruncated ? h('div', { 'data-dam-hint': '' }, t('userMemoryTruncated')) : null))
|
|
3098
|
+
// ★R7:硬性约束编辑面板(用户要求「能自行增删改」)
|
|
3099
|
+
// ★2026-09-20 崩溃修复:本处原写 `{ nonce: nonce }`,但 **LogsTab 不接收任何 props**
|
|
3100
|
+
// (`function LogsTab()`,调用点 `h(LogsTab)` 未传参),而 `nonce` 只在 MemoryPanel
|
|
3101
|
+
// 作用域内定义(L3969-3970)⇒ 打开日志页签时求值 nonce 抛
|
|
3102
|
+
// ReferenceError: nonce is not defined,整个窗格崩溃。
|
|
3103
|
+
// RulesEditPanel 本就把它当可选(`props && props.nonce ? … : 0`),故传空对象即可。
|
|
3104
|
+
rows.push(h(RulesEditPanel, {}))
|
|
2484
3105
|
}
|
|
2485
3106
|
return h('div', null, rows)
|
|
2486
3107
|
}
|
|
2487
3108
|
function pathName(p) { var parts = String(p).split(/[\\/]/); return parts[parts.length - 1] }
|
|
2488
3109
|
|
|
3110
|
+
// ── ★R1–R6(2026-09-20 用户「看不懂只能盲确认」)审批面人话化辅助 ────────
|
|
3111
|
+
// R3:stage 枚举 → 中文。
|
|
3112
|
+
function stageLabel(stage) {
|
|
3113
|
+
var m = { observed: 'hubStageObserved', candidate: 'hubStageCandidate', validated: 'hubStageValidated', active: 'hubStageActive', deprecated: 'hubStageDeprecated' }
|
|
3114
|
+
return m[stage] ? t(m[stage]) : String(stage || '')
|
|
3115
|
+
}
|
|
3116
|
+
// R2:reasonCodes → 人话。**为什么不能晋升**必须看得见。
|
|
3117
|
+
// 后端只回传机器码(如 diversity-below-3),这里翻成具体数字与下一步动作。
|
|
3118
|
+
function whyNotPromotable(promotion) {
|
|
3119
|
+
if (!promotion) return ''
|
|
3120
|
+
var d = promotion.detail || {}
|
|
3121
|
+
var codes = promotion.reasonCodes || []
|
|
3122
|
+
for (var i = 0; i < codes.length; i++) {
|
|
3123
|
+
var c = codes[i]
|
|
3124
|
+
if (c === 'observation-only') return t('hubWhyObservationOnly')
|
|
3125
|
+
if (c === 'deprecated') return t('hubStageDeprecated')
|
|
3126
|
+
if (c === 'no-success-criteria') return t('hubWhyNoCriteria')
|
|
3127
|
+
if (c === 'has-correction') return t('hubWhyHasCorrection')(d.corrections || 0)
|
|
3128
|
+
if (c.indexOf('high-risk-awaiting-approval') === 0) return t('hubWhyHighRisk')
|
|
3129
|
+
if (c.indexOf('diversity-below-') === 0) return t('hubWhyDiversity')(d.diversity != null ? d.diversity : 0, d.need != null ? d.need : 0)
|
|
3130
|
+
if (c.indexOf('success-below-') === 0) return t('hubWhySuccess')(d.successCount != null ? d.successCount : 0, d.need != null ? d.need : 0)
|
|
3131
|
+
if (c.indexOf('correction-rate-') === 0) return t('hubWhyCorrectionRate')(String(c).replace('correction-rate-', ''), d.cap != null ? d.cap : '')
|
|
3132
|
+
if (c === 'model-authorized') continue
|
|
3133
|
+
}
|
|
3134
|
+
if (promotion.decision === 'promote') return t('hubWhyCanPromote')
|
|
3135
|
+
return codes.length ? codes.join(' · ') : ''
|
|
3136
|
+
}
|
|
3137
|
+
|
|
3138
|
+
// ── ★R7(2026-09-20 用户要求「用户级硬性约束须能自行增删改」)─────────────
|
|
3139
|
+
// 这一段规则**每轮无条件注入、不走语义层** ⇒ 过时条目不会被自动淘汰、
|
|
3140
|
+
// AI(memory_user)也可能写错 ⇒ 必须让用户能自己改。
|
|
3141
|
+
// 接口与将来换风格无关:本面板只用 data-dam-* 语义属性,样式由外层决定。
|
|
3142
|
+
function RulesEditPanel(props) {
|
|
3143
|
+
var nonce = props && props.nonce ? props.nonce : 0
|
|
3144
|
+
var dataPair = useState(null); var data = dataPair[0]; var setData = dataPair[1]
|
|
3145
|
+
var errPair = useState(''); var err = errPair[0]; var setErr = errPair[1]
|
|
3146
|
+
var busyPair = useState(false); var busy = busyPair[0]; var setBusy = busyPair[1]
|
|
3147
|
+
var msgPair = useState(''); var msg = msgPair[0]; var setMsg = msgPair[1]
|
|
3148
|
+
var draftPair = useState({}); var draft = draftPair[0]; var setDraft = draftPair[1]
|
|
3149
|
+
var newPair = useState(''); var newText = newPair[0]; var setNewText = newPair[1]
|
|
3150
|
+
var tickPair = useState(0); var tick = tickPair[0]; var setTick = tickPair[1]
|
|
3151
|
+
useEffect(function () {
|
|
3152
|
+
var alive = true
|
|
3153
|
+
apiGet(API.rulesList, {}).then(function (r) {
|
|
3154
|
+
if (!alive) return
|
|
3155
|
+
if (r && r.error) { setErr(r.error) } else { setData(r); setErr('') }
|
|
3156
|
+
}).catch(function (e) { if (alive) setErr(String(e && e.message ? e.message : e)) })
|
|
3157
|
+
return function () { alive = false }
|
|
3158
|
+
}, [nonce, tick])
|
|
3159
|
+
function apply(op, payload) {
|
|
3160
|
+
setBusy(true); setMsg('')
|
|
3161
|
+
apiPost(API.rulesApply, Object.assign({ op: op }, payload || {})).then(function (r) {
|
|
3162
|
+
setBusy(false)
|
|
3163
|
+
if (r && r.error) { setMsg('❌ ' + r.error); return }
|
|
3164
|
+
// ★即时回显(用户既有硬性偏好):直接用餐后端返回的新列表,不等重新拉整页
|
|
3165
|
+
setData({ path: data && data.path, items: r.items, preview: r.preview })
|
|
3166
|
+
setMsg('✅ ' + (r.result || 'ok'))
|
|
3167
|
+
setDraft({})
|
|
3168
|
+
}).catch(function (e) { setBusy(false); setMsg('❌ ' + String(e && e.message ? e.message : e)) })
|
|
3169
|
+
}
|
|
3170
|
+
if (err) return h(Card, { title: t('rulesTitle') }, h('div', { 'data-dam-hint': '' }, err))
|
|
3171
|
+
if (!data) return h(Card, { title: t('rulesTitle') }, h('div', { 'data-dam-hint': '' }, '…'))
|
|
3172
|
+
var items = data.items || []
|
|
3173
|
+
var rows = []
|
|
3174
|
+
rows.push(h('div', { 'data-dam-hint': '' }, t('rulesIntro')))
|
|
3175
|
+
rows.push(h('div', { 'data-dam-hint': '', style: { marginTop: '4px' } }, t('rulesPath') + (data.path || '')))
|
|
3176
|
+
if (msg) rows.push(h('div', { key: 'msg', 'data-dam-hint': '', style: { marginTop: '6px' } }, msg))
|
|
3177
|
+
if (!items.length) rows.push(h('div', { key: 'empty', 'data-dam-hint': '', style: { marginTop: '8px' } }, t('rulesEmpty')))
|
|
3178
|
+
for (var i = 0; i < items.length; i++) {
|
|
3179
|
+
(function (it, idx) {
|
|
3180
|
+
var editing = draft[idx] != null
|
|
3181
|
+
var srcTag = it.source === 'ai' ? t('rulesSourceAi') : t('rulesSourceUser')
|
|
3182
|
+
var head = h('div', { 'data-dam-hint': '', style: { fontSize: '12px', opacity: .75 } },
|
|
3183
|
+
'#' + (idx + 1) + ' · ' + srcTag + (it.dateSection ? ' · ' + it.dateSection : ''))
|
|
3184
|
+
var bodyEl
|
|
3185
|
+
if (editing) {
|
|
3186
|
+
bodyEl = h('div', null,
|
|
3187
|
+
h('textarea', {
|
|
3188
|
+
'data-dam-input': '',
|
|
3189
|
+
value: draft[idx],
|
|
3190
|
+
rows: 3,
|
|
3191
|
+
style: { width: '100%', boxSizing: 'border-box' },
|
|
3192
|
+
onInput: function (e) { var d = Object.assign({}, draft); d[idx] = e.target.value; setDraft(d) },
|
|
3193
|
+
}),
|
|
3194
|
+
h('button', { 'data-dam-btn': '', disabled: busy, onClick: function () { apply('update', { index: idx, text: draft[idx] }) } }, t('rulesSave')),
|
|
3195
|
+
' ',
|
|
3196
|
+
h('button', { 'data-dam-btn': '', onClick: function () { var d = Object.assign({}, draft); delete d[idx]; setDraft(d) } }, t('rulesCancel')))
|
|
3197
|
+
} else {
|
|
3198
|
+
bodyEl = h('div', null,
|
|
3199
|
+
h('div', { 'data-dam-content': '' }, it.text),
|
|
3200
|
+
h('button', { 'data-dam-btn': '', onClick: function () { var d = Object.assign({}, draft); d[idx] = it.text; setDraft(d) } }, t('rulesEdit')),
|
|
3201
|
+
' ',
|
|
3202
|
+
h('button', {
|
|
3203
|
+
'data-dam-btn': '',
|
|
3204
|
+
disabled: busy,
|
|
3205
|
+
onClick: function () {
|
|
3206
|
+
// ★R7-4:该层渲染器(renderRulesSectionPre)**不认任何状态标记** ⇒
|
|
3207
|
+
// 软删标记会被当正文注入模型。故删除**真删**且不可撤销 ⇒ 二次确认。
|
|
3208
|
+
if (typeof window !== 'undefined' && !window.confirm(t('rulesConfirmDelete'))) return
|
|
3209
|
+
apply('remove', { index: idx })
|
|
3210
|
+
},
|
|
3211
|
+
}, t('rulesDelete')))
|
|
3212
|
+
}
|
|
3213
|
+
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))
|
|
3214
|
+
})(items[i], i)
|
|
3215
|
+
}
|
|
3216
|
+
rows.push(h('div', { key: 'add', style: { marginTop: '12px' } },
|
|
3217
|
+
h('div', { 'data-dam-hint': '' }, t('rulesAddHint')),
|
|
3218
|
+
h('textarea', {
|
|
3219
|
+
'data-dam-input': '',
|
|
3220
|
+
value: newText,
|
|
3221
|
+
rows: 2,
|
|
3222
|
+
placeholder: t('rulesAddPlaceholder'),
|
|
3223
|
+
style: { width: '100%', boxSizing: 'border-box' },
|
|
3224
|
+
onInput: function (e) { setNewText(e.target.value) },
|
|
3225
|
+
}),
|
|
3226
|
+
h('button', {
|
|
3227
|
+
'data-dam-btn': '',
|
|
3228
|
+
disabled: busy || !String(newText || '').trim(),
|
|
3229
|
+
onClick: function () { apply('add', { text: newText }); setNewText('') },
|
|
3230
|
+
}, t('rulesAdd'))))
|
|
3231
|
+
// ★R7-6:预览「下一轮注入会变成什么样」
|
|
3232
|
+
rows.push(h('div', { key: 'pvh', 'data-dam-hint': '', style: { marginTop: '12px' } }, t('rulesPreviewHint')))
|
|
3233
|
+
rows.push(h('pre', { key: 'pv', 'data-dam-content': '', style: { whiteSpace: 'pre-wrap' } }, data.preview || ''))
|
|
3234
|
+
return h(Card, { title: t('rulesTitle') }, h('div', null, rows))
|
|
3235
|
+
}
|
|
3236
|
+
|
|
2489
3237
|
// M8 记忆中枢页签:展示三层记忆(经历/事实/技能)概览。数据源=/memory-hub 只读投影。
|
|
2490
3238
|
function MemoryHubTab(props) {
|
|
2491
3239
|
var nonce = props && props.nonce ? props.nonce : 0
|
|
@@ -2551,8 +3299,31 @@ window.__ModuleLoader__.load({
|
|
|
2551
3299
|
pipeline.map(function (p) {
|
|
2552
3300
|
var ev = p.evidence || {}
|
|
2553
3301
|
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
|
-
|
|
3302
|
+
// ★R1:技能名 + 可读标注;★R3:stage 给中文
|
|
3303
|
+
h('div', null, h('b', null, p.title), h('span', { 'data-dam-hint': '', style: { marginLeft: '6px' } },
|
|
3304
|
+
'[' + stageLabel(p.stage) + ']'
|
|
3305
|
+
+ (p.observationOnly ? (locale === 'zh' ? ' · 观察(不参与自动晋升)' : ' · observation-only') : '')
|
|
3306
|
+
+ (p.pinned ? ' 📌' : '') + (p.riskLevel === 'high' ? ' ⚠' : '')),
|
|
3307
|
+
// ★R5:evidence 给人话(不是裸数字)
|
|
3308
|
+
h('span', { 'data-dam-hint': '', style: { marginLeft: '6px' } }, t('hubEvLine')(ev))),
|
|
3309
|
+
// ★R2:**为什么不能晋升** —— 必须在界面上说清楚
|
|
3310
|
+
(function () {
|
|
3311
|
+
var why = whyNotPromotable(p.promotion)
|
|
3312
|
+
if (!why) return null
|
|
3313
|
+
var ok = p.promotion && p.promotion.decision === 'promote'
|
|
3314
|
+
return h('div', { 'data-dam-hint': '', style: { marginTop: '4px', color: ok ? 'inherit' : 'var(--dsw-alias-text-warning, #d90)', opacity: .95 } },
|
|
3315
|
+
h('b', null, t('hubWhyTitle')), why)
|
|
3316
|
+
})(),
|
|
3317
|
+
// ★R4:预览「晋升后会注入什么」(真实 steps / successCriteria)
|
|
3318
|
+
((p.steps && p.steps.length) || (p.successCriteria && p.successCriteria.length))
|
|
3319
|
+
? h('details', { style: { marginTop: '4px' } },
|
|
3320
|
+
h('summary', { 'data-dam-hint': '', style: { cursor: 'pointer' } }, t('hubInjectPreview')),
|
|
3321
|
+
h('div', { 'data-dam-content': '', style: { marginTop: '4px', paddingLeft: '10px' } },
|
|
3322
|
+
(p.steps || []).map(function (s, si) { return h('div', { key: 's' + si }, (si + 1) + '. ' + s) }),
|
|
3323
|
+
(p.successCriteria && p.successCriteria.length)
|
|
3324
|
+
? h('div', { style: { marginTop: '4px' } }, h('b', null, t('hubInjectCriteria')), (p.successCriteria || []).join(' / '))
|
|
3325
|
+
: null))
|
|
3326
|
+
: h('div', { 'data-dam-hint': '', style: { marginTop: '4px', opacity: .7 } }, t('hubNoSteps')),
|
|
2556
3327
|
h('div', { style: { display: 'flex', gap: '6px', marginTop: '5px' } },
|
|
2557
3328
|
// issue #30:纯 episode 观察行**结构上**不可能晋升(无 successCriteria),
|
|
2558
3329
|
// 继续显示"晋升"按钮只会让人反复点击却看不到变化 ⇒ 直接隐藏并打观察标。
|
|
@@ -2992,7 +3763,21 @@ window.__ModuleLoader__.load({
|
|
|
2992
3763
|
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
3764
|
]),
|
|
2994
3765
|
]))
|
|
2995
|
-
|
|
3766
|
+
// ★2026-09-17(L2 修 bug①): 面板侧同样**按真实 reason 分支** —— 旧实现一律显示 t('planDisabled')
|
|
3767
|
+
// (「白板未启用」), 与看板那句同源的误导。后端现在会带 reason: 'legacy-mode' / 'error'。
|
|
3768
|
+
if (!data.enabled) {
|
|
3769
|
+
var disMsg = (function () {
|
|
3770
|
+
var r = data && data.reason
|
|
3771
|
+
if (r === 'legacy-mode') {
|
|
3772
|
+
return locale === 'zh'
|
|
3773
|
+
? '当前是旧版白板(legacy)档。看板需要切到「新版看板(dsh-graph)」——用上方开关切换, 重启 dsh web 后生效。'
|
|
3774
|
+
: 'Legacy board mode. The kanban needs the graph board — toggle it above, then restart dsh web.'
|
|
3775
|
+
}
|
|
3776
|
+
if (r === 'error') return (locale === 'zh' ? '白板加载出错: ' : 'Whiteboard load error: ') + String((data && data.error) || '')
|
|
3777
|
+
return t('planDisabled')
|
|
3778
|
+
})()
|
|
3779
|
+
return h('div', null, [switchCard, h(Card, { title: t('planTitle') }, h('div', { 'data-dam-hint': '' }, disMsg))])
|
|
3780
|
+
}
|
|
2996
3781
|
var wl = data.waterLevel || {}
|
|
2997
3782
|
// 2026-09-13(工作区切换修复):宿主对当前会话解析不出工作区身份时(wsBound:false),如实提示
|
|
2998
3783
|
// 而不是渲染另一个会话/启动目录下的白板内容(旧全局单值行为的残余观感)。
|
|
@@ -3027,7 +3812,7 @@ window.__ModuleLoader__.load({
|
|
|
3027
3812
|
tg.tag + ' ×' + tg.count)
|
|
3028
3813
|
})))
|
|
3029
3814
|
tagKids.push(h('div', { 'data-dam-hint': '', style: { marginTop: '4px' } },
|
|
3030
|
-
locale === 'zh' ? '标签来自账本/白板行内标记(tag:xxx / type:xxx / topic:xxx)。模型侧可用
|
|
3815
|
+
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
3816
|
} else {
|
|
3032
3817
|
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
3818
|
}
|
|
@@ -3321,6 +4106,7 @@ window.__ModuleLoader__.load({
|
|
|
3321
4106
|
'未分类': { color: 'var(--dsw-alias-label-secondary, #8a94a6)' },
|
|
3322
4107
|
}
|
|
3323
4108
|
function CalendarTab() {
|
|
4109
|
+
var zh = (locale || 'zh').indexOf('zh') === 0
|
|
3324
4110
|
var dataPair = useState(null)
|
|
3325
4111
|
var data = dataPair[0]
|
|
3326
4112
|
var setData = dataPair[1]
|
|
@@ -3447,16 +4233,61 @@ window.__ModuleLoader__.load({
|
|
|
3447
4233
|
// 日期格先打开当天时间轴;点击某个时间槽才创建事件。
|
|
3448
4234
|
if (dayView) {
|
|
3449
4235
|
var timeline = []
|
|
3450
|
-
|
|
4236
|
+
var dayAll = dayEntries(dayView)
|
|
4237
|
+
// ★2026-09-20 修 bug: 旧实现把时间轴写死为 `for (hourSlot = 7; hourSlot <= 22; ...)`。
|
|
4238
|
+
// 后果: 任何 23:00–06:59 或 time='--:--' 的事件**在当天视图里永不显示**——
|
|
4239
|
+
// 而用户的真实数据里就有 7 条 23:59 截止的作业(AMA1751 / BME1D02)。
|
|
4240
|
+
// 现改为**由数据驱动**: 先扫当天事件的真实小时, 用其 min/max 决定时间轴范围
|
|
4241
|
+
// (无事件时才回落到 07:00–22:00), 且任何落在范围外的事件单独成"时间轴外"一段。
|
|
4242
|
+
var hourNums = []
|
|
4243
|
+
var untimed = []
|
|
4244
|
+
for (var di = 0; di < dayAll.length; di++) {
|
|
4245
|
+
var tm = String(dayAll[di].time || '')
|
|
4246
|
+
var hh = parseInt(tm.slice(0, 2), 10)
|
|
4247
|
+
if (isNaN(hh)) { untimed.push(dayAll[di]); continue }
|
|
4248
|
+
hourNums.push(hh)
|
|
4249
|
+
}
|
|
4250
|
+
var hFrom = hourNums.length ? Math.min.apply(null, hourNums) : 7
|
|
4251
|
+
var hTo = hourNums.length ? Math.max.apply(null, hourNums) : 22
|
|
4252
|
+
if (hFrom > 7) hFrom = 7
|
|
4253
|
+
if (hTo < 22) hTo = 22
|
|
4254
|
+
// 每个小时槽: 支持同名 key 去重(同小时多事件)
|
|
4255
|
+
for (var hourSlot = hFrom; hourSlot <= hTo; hourSlot++) {
|
|
3451
4256
|
(function (hourValue) {
|
|
3452
4257
|
var hourText = String(hourValue).padStart(2, '0') + ':00'
|
|
3453
|
-
var
|
|
4258
|
+
var pad = String(hourValue).padStart(2, '0')
|
|
4259
|
+
var hourEvents = dayAll.filter(function (en) { return String(en.time || '').slice(0, 2) === pad })
|
|
3454
4260
|
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
4261
|
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 +
|
|
4262
|
+
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
4263
|
})(hourSlot)
|
|
3458
4264
|
}
|
|
3459
|
-
|
|
4265
|
+
// 时间轴外(无 time 或 time 无法解析)的事件单独列出 —— 不再静默丢失
|
|
4266
|
+
if (untimed.length) {
|
|
4267
|
+
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: '' }) } },
|
|
4268
|
+
h('div', { style: { flex: '0 0 50px', fontSize: 'calc(11px * var(--dam-scale))', opacity: .65 } }, zh ? '未定时' : 'n/a'),
|
|
4269
|
+
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 : '')) }))))
|
|
4270
|
+
}
|
|
4271
|
+
// 当天前后翻页(原实现只能靠「先关掉浮层 → 再点另一个日期」, 多一次无效操作)
|
|
4272
|
+
var dPrev = new Date(dayView + 'T00:00:00'); dPrev.setDate(dPrev.getDate() - 1)
|
|
4273
|
+
var dNext = new Date(dayView + 'T00:00:00'); dNext.setDate(dNext.getDate() + 1)
|
|
4274
|
+
var fmtD = function (dt) { return dt.getFullYear() + '-' + String(dt.getMonth() + 1).padStart(2, '0') + '-' + String(dt.getDate()).padStart(2, '0') }
|
|
4275
|
+
var wk = (function (ds) {
|
|
4276
|
+
// ISO-8601 周序号(周一为一周之始, 第 1 周含当年首个周四)
|
|
4277
|
+
var dt = new Date(ds + 'T00:00:00')
|
|
4278
|
+
var t = new Date(dt.getFullYear(), dt.getMonth(), dt.getDate())
|
|
4279
|
+
t.setDate(t.getDate() + 3 - ((t.getDay() + 6) % 7))
|
|
4280
|
+
var ws = new Date(t.getFullYear(), 0, 4)
|
|
4281
|
+
ws.setDate(ws.getDate() + 3 - ((ws.getDay() + 6) % 7))
|
|
4282
|
+
return 1 + Math.round((t - ws) / 604800000)
|
|
4283
|
+
})(dayView)
|
|
4284
|
+
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' } },
|
|
4285
|
+
h('div', { 'data-dam-row': '' },
|
|
4286
|
+
h('button', { 'data-dam-btn': '', title: zh ? '前一天' : 'previous day', onClick: function () { setDayView(fmtD(dPrev)) } }, '‹'),
|
|
4287
|
+
h('div', { style: { fontWeight: 700, flex: 1, textAlign: 'center' } }, dayView + ' · ' + (zh ? '第 ' + wk + ' 周' : 'W' + wk) + ' · ' + dayAll.length + (zh ? ' 项' : ' items')),
|
|
4288
|
+
h('button', { 'data-dam-btn': '', title: zh ? '后一天' : 'next day', onClick: function () { setDayView(fmtD(dNext)) } }, '›'),
|
|
4289
|
+
h('button', { 'data-dam-btn': '', onClick: function () { setDayView(null) } }, '✕')),
|
|
4290
|
+
timeline))
|
|
3460
4291
|
}
|
|
3461
4292
|
// 添加/编辑浮层(液态玻璃)
|
|
3462
4293
|
if (draft) {
|
|
@@ -4048,6 +4879,12 @@ window.__ModuleLoader__.load({
|
|
|
4048
4879
|
{ 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
4880
|
{ 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
4881
|
{ key: 'procedurePromotionEnabled', name: locale === 'zh' ? '技能固化与晋升' : 'Skill crystallization', sub: locale === 'zh' ? '重复流程固化为 checklist 自动附上;跨会话验证后晋升(「记忆中枢」页审批)' : 'Turn repeated flows into auto-attached checklists; promote after validation (Memory Hub tab)', where: locale === 'zh' ? '记忆中枢' : 'Memory Hub' },
|
|
4882
|
+
// ★T10(2026-09-20):机械 procedure 切片开关。
|
|
4883
|
+
// 背景:`crossFeed()` 会把 episode 意图**机械截断**成技能名与步骤(无模型介入),
|
|
4884
|
+
// 产出的观察行「既不像技能名也不像步骤」——用户报「审批里看不懂」的来源。
|
|
4885
|
+
// 默认关:procedural 线只留模型直写(memory_procedure)与人工两条正经通路。
|
|
4886
|
+
// ⚠️ 解耦:本项**只**影响 procedure 切片,fact 分支/episode 巩固/judgement 消费不动。
|
|
4887
|
+
{ 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
4888
|
] },
|
|
4052
4889
|
{ art: 'rocket', core: '', kicker: locale === 'zh' ? '完成' : 'READY', final: true,
|
|
4053
4890
|
title: locale === 'zh' ? '一切就绪' : 'All set',
|
|
@@ -4443,7 +5280,8 @@ window.__ModuleLoader__.load({
|
|
|
4443
5280
|
// ring/wall 必须一起带进来:卡片读的是 acConfirm.ring / acConfirm.wall,
|
|
4444
5281
|
// 宿主透出了但这里不拷 → 双口径那行永远不渲染(2026-09-10 自查发现)
|
|
4445
5282
|
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
|
-
|
|
5283
|
+
// ★2026-09-20 移植(issue #94⑦ / PR #100):ceil 作用于**毫秒**再除 1000 ⇒ 显示小数秒。
|
|
5284
|
+
setAcCd(Math.max(0, Math.ceil(((Number(arm.expiresAt) || 0) - Date.now()) / 1000)))
|
|
4447
5285
|
} else {
|
|
4448
5286
|
setAcConfirm(null)
|
|
4449
5287
|
setAcCd(0)
|
|
@@ -4909,6 +5747,9 @@ window.__ModuleLoader__.load({
|
|
|
4909
5747
|
else if (dl.phase === 'error') { stateTxt = t('dlError'); stateBg = 'rgba(196,74,74,.22)' }
|
|
4910
5748
|
else if (dl.phase === 'cancelled') { stateTxt = t('dlCancelled'); stateBg = 'rgba(196,138,42,.2)' }
|
|
4911
5749
|
else if (isJs && dlActive) { stateTxt = dl.phase === 'verifying' ? t('dlVerifying') : t('dlDownloading'); stateBg = 'rgba(36,86,196,.2)' }
|
|
5750
|
+
// ★ issue #70(2026-09-19):资产齐备但引擎**已降级** —— 必须与「未下载」区分开。
|
|
5751
|
+
// 旧实现只看文件存在性 ⇒ 这种状态会显示「已就绪 ✓」而实际每次检索都在词法兜底。
|
|
5752
|
+
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
5753
|
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
5754
|
else { stateTxt = locale === 'zh' ? '未下载' : 'Not downloaded'; stateBg = 'rgba(196,138,42,.2)' }
|
|
4914
5755
|
var progress = dlActive || (isJs && dl.phase === 'done')
|
|
@@ -4922,14 +5763,17 @@ window.__ModuleLoader__.load({
|
|
|
4922
5763
|
: (dl.phase === 'cancelled' ? t('dlCancelled')
|
|
4923
5764
|
: (isJs && dl.phase === 'done' && !ready ? t('dlDone') + (locale === 'zh' ? '(缺运行库时需安装 @huggingface/transformers)' : ' (install @huggingface/transformers if runtime missing)')
|
|
4924
5765
|
: (locale === 'zh' ? '下载进度' : 'Download progress'))))
|
|
4925
|
-
|
|
5766
|
+
// ★2026-09-20 移植(issue #89 / PR #95):局部 var refreshSem 遮蔽模块级
|
|
5767
|
+
// refreshSem(apply, onError) ⇒ 自递归爆栈被 .catch 吞 ⇒ 点「开始下载/取消」后
|
|
5768
|
+
// sem 永不刷新、轮询闸也永不启动。改名引用模块级函数。
|
|
5769
|
+
var refreshSemNow = function () {
|
|
4926
5770
|
refreshSem(setSem)
|
|
4927
5771
|
}
|
|
4928
5772
|
var startDl = function () {
|
|
4929
|
-
apiPost(API.semanticDownload, { action: 'start', mirror: mirror }).then(
|
|
5773
|
+
apiPost(API.semanticDownload, { action: 'start', mirror: mirror }).then(refreshSemNow).catch(function () {})
|
|
4930
5774
|
}
|
|
4931
5775
|
var cancelDl = function () {
|
|
4932
|
-
apiPost(API.semanticDownload, { action: 'cancel', mirror: mirror }).then(
|
|
5776
|
+
apiPost(API.semanticDownload, { action: 'cancel', mirror: mirror }).then(refreshSemNow).catch(function () {})
|
|
4933
5777
|
}
|
|
4934
5778
|
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
5779
|
h('div', { 'data-dam-row': '', style: { alignItems: 'center' } },
|
|
@@ -5033,8 +5877,8 @@ window.__ModuleLoader__.load({
|
|
|
5033
5877
|
set('injectExcludeSources', lines)
|
|
5034
5878
|
},
|
|
5035
5879
|
}), t('fExcludeHint')),
|
|
5036
|
-
field(t('fNoteCap'), h('input', { 'data-dam-input': '', type: 'number', min: 500, value: cfg.noteCapacityChars === undefined ?
|
|
5037
|
-
field(t('fUserCap'), h('input', { 'data-dam-input': '', type: 'number', min: 500, value: cfg.userCapacityChars === undefined ?
|
|
5880
|
+
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')),
|
|
5881
|
+
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')),
|
|
5038
5882
|
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
5883
|
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
5884
|
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')),
|
|
@@ -5104,7 +5948,7 @@ window.__ModuleLoader__.load({
|
|
|
5104
5948
|
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'),
|
|
5105
5949
|
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
5950
|
})(),
|
|
5107
|
-
locale === 'zh' ? '一键切换白板形态。默认「旧版白板」=一切行为不变;切到「新版看板」后启用 dsh-graph 看板、白板结构化索引(handoff/index.json)与两个遍历工具(
|
|
5951
|
+
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.'),
|
|
5108
5952
|
field(locale === 'zh' ? '子代理痕迹回收' : 'Subagent trace recycle', h('input', { type: 'checkbox', checked: cfg.subagentGcEnabled !== false, onChange: function (e) { set('subagentGcEnabled', e.target.checked) } }),
|
|
5109
5953
|
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
5954
|
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) } }),
|
|
@@ -5402,6 +6246,27 @@ window.__ModuleLoader__.load({
|
|
|
5402
6246
|
function (props) { return h(KanbanView, props || {}) },
|
|
5403
6247
|
)
|
|
5404
6248
|
}))
|
|
6249
|
+
// 2026-09-21「白板画布」**暂时隐藏**(用户拍板:功能未达可用标准,先发版收尾)。
|
|
6250
|
+
//
|
|
6251
|
+
// 为什么不删代码: 画布本身是完整实现(清洗层 + 分列布局 + 平移缩放 + 侧栏),
|
|
6252
|
+
// 只是**数据源受限** —— 实测 D:\dsh-auto-memory 下 880 张卡、189 篇文档,
|
|
6253
|
+
// 当前布局是「泳道分列 + 每列上限 14 + 折叠」, 但它依赖 kanban-board 在当前
|
|
6254
|
+
// 会话下能解析出**正确的工作区**; 实测 sessionId 解析失败时 API 返回
|
|
6255
|
+
// enabled:true 但 **ws 为空 + 0 张卡** ⇒ 画布显示"暂无内容"。
|
|
6256
|
+
// ⇒ 保留代码 + 默认关闭, 待工作区解析路径稳定后一行开启。
|
|
6257
|
+
//
|
|
6258
|
+
// 开启方式: 把下面的 false 改成 true(或设为 localStorage['dam-wbg-enabled']='1')。
|
|
6259
|
+
var WBG_ENABLED = (function () {
|
|
6260
|
+
try { return localStorage.getItem('dam-wbg-enabled') === '1' } catch (e) { return false }
|
|
6261
|
+
})()
|
|
6262
|
+
if (WBG_ENABLED) {
|
|
6263
|
+
surfaceDisposers.push(slots.inject('conversation.view', function () {
|
|
6264
|
+
return slots.register(
|
|
6265
|
+
{ name: 'conversation.view', id: 'auto-memory-graph', order: 81, label: function () { return locale === 'zh' ? '白板画布' : 'Graph' } },
|
|
6266
|
+
function (props) { return h(WhiteboardGraphView, props || {}) },
|
|
6267
|
+
)
|
|
6268
|
+
}))
|
|
6269
|
+
}
|
|
5405
6270
|
} catch (e) {
|
|
5406
6271
|
console.warn('[dsh-auto-memory] slot registration failed', e)
|
|
5407
6272
|
}
|