@a9i5k4/dsh-auto-memory 0.1.36 → 0.1.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -191,14 +191,6 @@ window.__ModuleLoader__.load({
191
191
  kvAvail: '可用', kvUnavail: '不可用', kvDup: '项目笔记重复标题', kvDupCount: ' 个',
192
192
  kvMem: '记忆文件', kvMemUser: '用户', kvMemNotes: '笔记', kvMemLog: '日志', kvMemMissing: '缺失',
193
193
  kvWs: '当前工作区', kvWsUnknown: '(未取到)', kvApi: 'API 连通', unknown: '(未知)',
194
- guideTitle: '欢迎使用 dsh-auto-memory', guideSub: '这个插件能干什么:',
195
- gFeat1: '三层记忆自动注入与检索:用户级 / 项目笔记 / 每日日志,每轮对话自动读、自动写',
196
- gFeat2: '每轮对话结束自动沉淀:修了什么 bug、做了哪些决策,自动按主题写进今日日志',
197
- gFeat3: 'AI 时段问候与三级抽屉:打开「记忆」面板,AI 按时段向你汇报当天进展',
198
- gFeat4: '智能检索与日历:自然语言提问找记忆;AI 自动把 deadline 记进日历并提醒',
199
- gFeat5: '接续其他 AI 工具的记忆:CodeBuddy / Claude Code / Codex 等的历史记忆一键接入',
200
- gFeat6: '自动检查更新:启动时检测新版本,设置页可一键更新',
201
- guideTip: '打开侧边栏「记忆」按钮即可使用;所有记忆都是本地明文文件,可随时查看修改。',
202
194
  updateTitle: 'dsh-auto-memory 已更新', updateSub: '本次更新内容:', gotIt: '知道了', noticeOpen: '了解更多',
203
195
  refresh: '刷新', refreshing: '刷新中…', close: '关闭', dragMove: '拖动移动', dragResize: '拖动调整大小', resetPos: '恢复默认位置',
204
196
  generated: '已生成', failed: '失败: ',
@@ -330,14 +322,6 @@ window.__ModuleLoader__.load({
330
322
  kvAvail: 'available', kvUnavail: 'unavailable', kvDup: 'Duplicate note headings', kvDupCount: '',
331
323
  kvMem: 'Memory files', kvMemUser: 'user', kvMemNotes: 'notes', kvMemLog: 'log', kvMemMissing: 'missing',
332
324
  kvWs: 'Current workspace', kvWsUnknown: '(unknown)', kvApi: 'API probes', unknown: '(unknown)',
333
- guideTitle: 'Welcome to dsh-auto-memory (pre)', guideSub: 'What this plugin does:',
334
- gFeat1: 'Three-layer memory with automatic injection & retrieval: user / project notes / daily logs',
335
- gFeat2: 'Auto-consolidation after every turn: bugs fixed and decisions made are written into today\'s log automatically',
336
- gFeat3: 'AI period greetings with three-level drawers: open the Memory panel for a period-by-period digest',
337
- gFeat4: 'Smart search & calendar: ask in natural language; AI files deadlines into the calendar and reminds you',
338
- gFeat5: 'Inherit memories from other AI tools: CodeBuddy / Claude Code / Codex etc. in one click',
339
- gFeat6: 'Auto update check at boot, one-click update in settings',
340
- guideTip: 'Click the Memory button in the sidebar to start; all memories are plain local Markdown files you can inspect anytime.',
341
325
  updateTitle: 'dsh-auto-memory Updated', updateSub: 'What\'s new in this update:', gotIt: 'Got it', noticeOpen: 'Learn more',
342
326
  refresh: 'Refresh', refreshing: 'Refreshing…', close: 'Close', dragMove: 'Drag to move', dragResize: 'Drag to resize', resetPos: 'Reset position',
343
327
  generated: 'Generated', failed: 'Failed: ',
@@ -495,6 +479,9 @@ window.__ModuleLoader__.load({
495
479
  '补丁 0.1.32:「记忆唤起」开关与设置页双向联动(导览与设置走同一 semantic-emit 接口);语义引擎分级引导(无引擎自动装 JS 档,发烧友可选 Python)。',
496
480
  '补丁 0.1.33:修复语义引擎下载 404(镜像 URL 重复前缀);transformers 推理库声明为可选依赖并随包分发。',
497
481
  '补丁 0.1.34:修复推理库检测——pnpm 把 transformers 提升到 profile 根 node_modules,检测补该路径,新用户装完即用,无需手动操作。',
482
+ '补丁 0.1.37:修复「弹窗关不掉」(#13,感谢 @eclgo 报告)——暂离回归面板只在 away→active 翻转时弹一次;0.1.36 起回归检测全局化后,回归前(尚未发消息)面板会被每 30 秒强制重开,现已根治。',
483
+ '补丁 0.1.37:修复语义引擎「推理库缺失」误报 (#14,感谢 @moonltppt 报告)——推理库探测改用 Node 真实解析,覆盖 npm/pnpm(hoisted/isolated)全部安装布局;并新增「打开即自动检测」:检测不到自动深度扫描(~/.dsh/profiles 全家 + pnpm 虚拟存储),命中即热接入(无需重启),仍缺才推荐安装/一键下载,推荐卡 24 小时免打扰。',
484
+ '补丁 0.1.38:修复新装首启弹的不是启动向导——删除旧的静态欢迎卡(内容与完整向导重复、且小卡形态无关闭按钮);全新安装首次打开直接进入完整欢迎向导(分步功能开关+引擎检测/下载引导),老用户按版本与向导完成态自动补看。',
498
485
  ], en: [
499
486
  '★ MAJOR: Brand-new Welcome Tour — after first launch or upgrade, every feature is introduced step by step with per-feature switches right in the tour (association / snapshot / greeting / night unattended / reflection / summaries / external memory / skill promotion…); engine detection, download, self-test and live external-source scanning are all inline.',
500
487
  '★ New brand visuals: an Office/Fluent-style liquid-glass app icon family — each step gets its own colored glass squircle icon (cyan inject / amber greeting / green calendar / violet engine / sky radar / coral finish) with dedicated looping motion (bell sway, page flip, linked rings, prism spin, radar sweep, rising spark); the signature three-slab frosted-glass logo stays on the Core step.',
@@ -508,6 +495,9 @@ window.__ModuleLoader__.load({
508
495
  'Patch 0.1.32: the "Memory recall" switch now syncs bidirectionally with Settings (tour and settings share the semantic-emit endpoint); tiered engine onboarding (auto-install JS tier, Python optional for enthusiasts).',
509
496
  'Patch 0.1.33: fixed semantic-engine download 404 (duplicate prefix in mirror URL); transformers inference lib declared as an optional dependency and shipped with the package.',
510
497
  'Patch 0.1.34: fixed inference-lib detection — pnpm hoists transformers to the profile-root node_modules; detection now covers that path, so fresh installs work out of the box.',
498
+ 'Patch 0.1.37: fixed the "popup cannot be closed" loop (#13, thanks @eclgo) — the welcome-back panel now opens only on the away→active transition; since 0.1.36 globalised away detection, the panel was force-reopened every 30s until the first message after returning. Root-caused and fixed.',
499
+ 'Patch 0.1.37: fixed the false "inference runtime missing" state (#14, thanks @moonltppt) — runtime detection now uses real Node resolution, covering npm/pnpm (hoisted/isolated) layouts; plus a new check-on-open flow: when not detected, a deep scan (~/.dsh/profiles + pnpm virtual store) runs automatically and hot-wires hits with no restart; only if still missing does it recommend installing / one-click download, with a 24h snooze.',
500
+ 'Patch 0.1.38: fixed first-run opening the legacy static welcome card instead of the tour (duplicate content, no close button in the compact card) — the card is removed; fresh installs now go straight into the full welcome tour (per-feature switches + engine detect/download), and existing users get the tour replayed based on version and tour state.',
511
501
  ] },
512
502
  '0.1.29': { zh: [
513
503
  '修复:工作区总览一直显示「未发现带记忆的工作区」——DSH 新版会话持久化改为 session.jsonl.zstd 压缩帧,旧逻辑只认 .jsonl 扫描不到任何会话;现已用 zstd 解压读取首行提取 cwd,并让空的 summary 缓存短时失效,避免空结果被永久固化。',
@@ -648,12 +638,12 @@ window.__ModuleLoader__.load({
648
638
  function dialogPriority(d) {
649
639
  if (!d) return 0
650
640
  if (d.kind === 'update') return 100
651
- if (d.kind === 'first') return 90
652
641
  if (d.kind === 'welcomeTour') return 85
653
642
  if (d.kind === 'modelDownload') return 85
654
643
  if (d.kind === 'notice') return d.notice && d.notice.level === 'urgent' ? 80 : 70
655
644
  if (d.kind === 'summary') return 60
656
645
  if (d.kind === 'welcomeBack') return 50
646
+ if (d.kind === 'semSetup') return 45
657
647
  return 10
658
648
  }
659
649
  function dialogKey(d) {
@@ -721,6 +711,8 @@ window.__ModuleLoader__.load({
721
711
  greet: '/api/dsh-auto-memory/greet',
722
712
  notices: '/api/dsh-auto-memory/notices',
723
713
  models: '/api/dsh-auto-memory/models',
714
+ semanticDeepDetect: '/api/dsh-auto-memory/semantic-deep-detect',
715
+ semanticDownload: '/api/dsh-auto-memory/semantic-download',
724
716
  }
725
717
  function query(params) {
726
718
  var search = new URLSearchParams()
@@ -2590,7 +2582,6 @@ window.__ModuleLoader__.load({
2590
2582
  // 通用开场舞台必须在 first/notice/update 三个分支之前完成赋值;var 只提升声明、不提升赋值。
2591
2583
  var skipUpdateIntro = function () { setUpdateSkip(true) }
2592
2584
  var introEligible = dialogState.kind === 'update' ||
2593
- dialogState.kind === 'first' ||
2594
2585
  (dialogState.kind === 'notice' && !((dialogState.notice || {}).level === 'urgent'))
2595
2586
  var DamIntroBox = function (children) {
2596
2587
  if (!introEligible) return children
@@ -2608,56 +2599,6 @@ window.__ModuleLoader__.load({
2608
2599
  !updateSkip ? h('div', { 'data-dam-update-click': '', onClick: skipUpdateIntro }) : null,
2609
2600
  h('div', { 'data-dam-update-content': '' }, children))
2610
2601
  }
2611
- if (dialogState.kind === 'first') {
2612
- var feats = [t('gFeat1'), t('gFeat2'), t('gFeat3'), t('gFeat4'), t('gFeat5'), t('gFeat6')]
2613
- // M7.5:语义引擎资产检测(模块级缓存,避免重复请求;SLIDES 扩展点见下方渲染块)
2614
- var semStatus = window['dsh-auto-memory.semStatus']
2615
- if (!semStatus) {
2616
- semStatus = { status: 'checking' }
2617
- window['dsh-auto-memory.semStatus'] = semStatus
2618
- fetch('/api/dsh-auto-memory/semantic-status').then(function (r) { return r.json() }).then(function (j) {
2619
- semStatus.status = j.ready ? 'ready' : 'missing'
2620
- try { dlgTick() } catch (e9) {}
2621
- }).catch(function () { semStatus.status = 'unknown' })
2622
- }
2623
- var firstButton = h('button', { 'data-dam-btn': '', style: close, onClick: function () {
2624
- try {
2625
- localStorage.setItem('dsh-auto-memory.firstRunDone', '1')
2626
- if (dialogState && dialogState.currentVersion) localStorage.setItem('dsh-auto-memory.seenVersion', dialogState.currentVersion)
2627
- // 规范 F2(RELEASE-SEMANTIC-OPTION.md):首启关闭后进入「欢迎向导」
2628
- // (分步介绍+内联检测/下载;受设置 welcomeTourEnabled 门控,默认开)。
2629
- fetch('/api/dsh-auto-memory/config').then(function (r) { return r.json() }).then(function (cj) {
2630
- var cc = (cj && cj.config) || cj || {}
2631
- if (cc.welcomeTourEnabled !== false && !localStorage.getItem('dsh-auto-memory.semWizardDone')) {
2632
- openDialog({ kind: 'welcomeTour' })
2633
- } else {
2634
- try { localStorage.setItem('dsh-auto-memory.semWizardDone', '1') } catch (e5) {}
2635
- }
2636
- }).catch(function () {
2637
- if (!localStorage.getItem('dsh-auto-memory.semWizardDone')) openDialog({ kind: 'welcomeTour' })
2638
- })
2639
- } catch (e3) {}
2640
- closeDialog()
2641
- } }, t('gotIt'))
2642
- var firstChildren = [
2643
- h('div', { style: head }, t('guideTitle')),
2644
- h('div', { style: sub }, t('guideSub')),
2645
- feats.map(function (f) { return h('div', { style: item }, h('span', { style: dot }), f) }),
2646
- // M7.5 首启扩展点(SLIDES):新功能引导在此按序追加;当前块=内置语义引擎自动检测。
2647
- (function () {
2648
- var boxS = { margin: '8px 0 4px', padding: '8px 10px', borderRadius: '10px', border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.35)) 50%, transparent)', fontSize: 'calc(11px * var(--dam-scale))', lineHeight: 1.55 }
2649
- var st = semStatus
2650
- if (!st || st.status === 'checking') return h('div', { style: boxS }, locale === 'zh' ? '正在检测内置语义引擎…' : 'Detecting built-in semantic engine…')
2651
- if (st.status === 'ready') return h('div', { style: boxS }, '✓ ' + (locale === 'zh' ? '内置语义引擎已就绪(本地运行,记忆不出电脑)。可在「记忆」面板设置中切换检索模式。' : 'Built-in semantic engine ready (local-only). Switchable in Memory panel settings.'))
2652
- return h('div', { style: boxS }, locale === 'zh' ? '可选:内置语义引擎未下载(约130MB)。在「记忆」面板设置中可随时启用;未启用时词法检索照常可用。' : 'Optional: built-in semantic engine not downloaded (~130MB). Enable anytime in Memory panel settings; lexical search keeps working.')
2653
- })(),
2654
- h('div', { style: { fontSize: 'calc(12px * var(--dam-scale))', opacity: .8, marginTop: '4px' } }, t('guideTip')),
2655
- firstButton,
2656
- ]
2657
- return h('div', { style: overlay },
2658
- h('div', { style: box },
2659
- DamIntroBox(firstChildren)))
2660
- }
2661
2602
  if (dialogState.kind === 'welcomeTour' || dialogState.kind === 'modelDownload') {
2662
2603
  // 首启引导向导 v2(2026-08-31):分步功能全覆盖 + 步内功能开关(点击即时写配置)
2663
2604
  // + 完成步提醒「随时可在设置的哪个分区重新打开」。跳过/✕ 也先落到完成步(提醒)。
@@ -2961,6 +2902,50 @@ window.__ModuleLoader__.load({
2961
2902
  h('div', { style: { display: 'flex', justifyContent: 'flex-end', marginTop: '6px' } },
2962
2903
  h('button', { 'data-dam-btn': '', style: close, onClick: closeDialog }, t('gotIt')))))
2963
2904
  }
2905
+ if (dialogState.kind === 'semSetup') {
2906
+ // 打开即自动检测引导卡(0.1.37,#14 后续):快检+深扫仍未就绪时推荐补装/一键下载;
2907
+ // 深扫命中已热接入则报喜(即时生效)。关闭 = 24h 免打扰,不反复打扰。
2908
+ var sd = dialogState.result || {}
2909
+ var sdZh = locale === 'zh'
2910
+ var sdBody = { fontSize: 'calc(12px * var(--dam-scale))', lineHeight: 1.6, opacity: .92, whiteSpace: 'pre-wrap', marginTop: '4px' }
2911
+ var sdRows = []
2912
+ var sdActions = []
2913
+ var sdCmd = 'pnpm approve-builds\npnpm add @huggingface/transformers\n# 然后重启 dsh web'
2914
+ var sdSnooze = function () {
2915
+ try { localStorage.setItem('dsh-auto-memory.semDetectSnoozeUntil', String(Date.now() + 24 * 3600 * 1000)) } catch (eSn) {}
2916
+ closeDialog()
2917
+ }
2918
+ if (sd.deep && sd.deep.integrated) {
2919
+ sdRows.push(h('div', { style: sdBody }, sdZh
2920
+ ? '检测到你已安装推理库 @huggingface/transformers(常规路径此前未识别,深度扫描已命中并自动接入)。语义检索 C2 现在即可用,无需重启。'
2921
+ : 'Your installed @huggingface/transformers was found by the deep scan and hot-wired automatically. The C2 semantic tier is ready now — no restart needed.'))
2922
+ } else {
2923
+ if (sd.recommendation === 'install-peer' || sd.recommendation === 'setup-both') {
2924
+ sdRows.push(h('div', { style: sdBody }, sdZh
2925
+ ? (sd.recommendation === 'setup-both' ? '语义模型与推理库都未检测到。推理库请在安装 dsh-auto-memory 的同一目录(dsh profile)执行:' : '语义模型已下载,但推理库缺失(已含深度扫描)。请在安装 dsh-auto-memory 的同一目录(dsh profile)执行:')
2926
+ : (sd.recommendation === 'setup-both' ? 'Neither the model assets nor the inference runtime were found. For the runtime, run in the directory where dsh-auto-memory is installed (the dsh profile):' : 'Model assets found, but the inference runtime is missing (deep scan included). Run in the directory where dsh-auto-memory is installed (the dsh profile):')))
2927
+ sdActions.push(h('button', { 'data-dam-btn': '', style: close, onClick: function () {
2928
+ try { if (navigator.clipboard && navigator.clipboard.writeText) navigator.clipboard.writeText(sdCmd) } catch (eCp) {}
2929
+ } }, sdZh ? '复制命令' : 'Copy commands'))
2930
+ }
2931
+ if (sd.recommendation === 'download-model' || sd.recommendation === 'setup-both') {
2932
+ sdRows.push(h('div', { style: sdBody }, sdZh
2933
+ ? '语义模型(内置 JS 档,约 135MB,一次即可)可一键后台下载,下载完成后语义检索自动启用:'
2934
+ : 'The built-in JS semantic model (~135MB, one-time) can be downloaded in the background; the semantic tier enables itself once done:'))
2935
+ sdActions.push(h('button', { 'data-dam-btn': '', 'data-primary': 'true', style: close, onClick: function () {
2936
+ apiPost(API.semanticDownload, { action: 'start', mirror: 'auto' }).catch(function () {})
2937
+ closeDialog()
2938
+ } }, sdZh ? '立即下载' : 'Download now'))
2939
+ }
2940
+ }
2941
+ return h('div', { style: overlay },
2942
+ h('div', { style: box },
2943
+ h('div', { style: head }, sdZh ? '自动记忆引擎检测' : 'Memory engine check'),
2944
+ sdRows,
2945
+ h('div', { style: { display: 'flex', gap: '8px', justifyContent: 'flex-end', marginTop: '6px' } },
2946
+ sdActions,
2947
+ h('button', { 'data-dam-btn': '', style: close, onClick: (sd.deep && sd.deep.integrated) ? closeDialog : sdSnooze }, t('gotIt')))))
2948
+ }
2964
2949
  var versions = dialogState.versions || []
2965
2950
  var lastV = versions.length ? versions[versions.length - 1].version : ''
2966
2951
  return h('div', { style: overlay },
@@ -3505,8 +3490,10 @@ window.__ModuleLoader__.load({
3505
3490
  })
3506
3491
  // 语言切换时通知所有订阅者重渲染
3507
3492
  onLocale(function () { emit() })
3508
- // 暂离回来自动弹开记忆窗口:host 判定暂离(阈值可配 awayMinutes),页面加载/切回标签页/轮询发现回归时自动打开
3509
- // 回归检测:host away 状态从 true false(暂离结束)时打开并欢迎
3493
+ // 暂离回来自动弹开记忆窗口:host 判定暂离(阈值可配 awayMinutes),轮询发现回归(true→false)时自动打开。
3494
+ // v0.1.37 修复(#13):本函数不得做「away 且面板关着就再弹开」——≤0.1.35 host away 无写入点恒 false
3495
+ // 掩盖了旧行,0.1.36 全局 away 激活后它变成 30s 重开死循环:用户回到机器但还没发消息时 host 恒判 away,
3496
+ // 关掉即被重弹(=「弹窗关不掉」)。契约以设置页文案为准:回归(away true→false)才弹,away 挂着绝不强弹。
3510
3497
  var prevAwayState = null
3511
3498
  function autoOpenOnReturn() {
3512
3499
  try {
@@ -3517,11 +3504,8 @@ window.__ModuleLoader__.load({
3517
3504
  var pend = lastPendingSummary
3518
3505
  if (pend) { openDialog({ kind: 'summary', summary: pend }); lastPendingSummary = null }
3519
3506
  else if (!document.hidden) openDialog({ kind: 'welcomeBack' })
3520
- prevAwayState = hostAway
3521
- return
3522
3507
  }
3523
3508
  prevAwayState = hostAwayReady ? hostAway : prevAwayState
3524
- if (isAway() && !controller.isOpen()) controller.open()
3525
3509
  } catch (e) {}
3526
3510
  }
3527
3511
  autoOpenOnReturn()
@@ -3530,33 +3514,39 @@ window.__ModuleLoader__.load({
3530
3514
  if (!document.hidden) autoOpenOnReturn()
3531
3515
  })
3532
3516
  } catch (e) {}
3533
- // 更新弹窗 / 首次指导:对比本地记录的已见版本,有更新或首次安装时弹窗(host 12h 缓存,不重复查网)
3534
- // v0.1.30 大更新触达:一次性标记 majorTour——所有用户(新老)升级后首次打开都先播放完整欢迎向导,
3535
- // 向导结束后接 CHANGELOG(含大更新内容+社区致谢);此后永不再弹(标记落 localStorage)
3517
+ // 更新弹窗 / 启动分发:对比本地记录的已见版本,有更新或首次安装时弹窗(host 12h 缓存,不重复查网)
3518
+ // v0.1.38:旧 first 卡已删除 → 全新安装直接完整欢迎向导;老用户按 seen/向导完成态补大更新。
3519
+ // 抽成具名函数便于状态机单测(startup-dispatch)。
3536
3520
  var MAJOR_TOUR_KEY = 'dsh-auto-memory.majorTourV130'
3537
- apiGet(API.updateCheck).then(function (d) {
3521
+ function dispatchStartupDialog(d) {
3538
3522
  if (d && d.current) {
3539
3523
  try {
3540
3524
  var seen = localStorage.getItem('dsh-auto-memory.seenVersion')
3541
3525
  var majorPending = !localStorage.getItem(MAJOR_TOUR_KEY)
3542
- // 老用户(有使用痕迹)seen < 0.1.30(大更新前版本)升级上来 → 强制触发一次欢迎向导(含引擎下载引导)。
3543
- // 不依赖一次性标记:0.1.29→0.1.34 升级即使曾看过旧版向导也要重放,确保模型下载引导不漏。
3544
- var isExisting = !!seen || !!localStorage.getItem('dsh-auto-memory.firstRunDone') || !!localStorage.getItem('dsh-auto-memory.semWizardDone')
3545
- var seenBeforeMajor = isExisting && (!seen || cmpVersion(seen, '0.1.30') < 0)
3546
- if (seenBeforeMajor && !localStorage.getItem('dsh-auto-memory.semWizardDone')) {
3526
+ var wizDone = localStorage.getItem('dsh-auto-memory.semWizardDone')
3527
+ var firstDone = localStorage.getItem('dsh-auto-memory.firstRunDone')
3528
+ // 0.1.38:旧的 first 欢迎卡已删除(内容被 welcomeTour 完整覆盖,且小卡形态无 关闭)
3529
+ // 全新安装直接完整欢迎向导(分步功能开关+引擎检测/下载引导)
3530
+ var freshInstall = !seen && !firstDone && !wizDone
3531
+ var isExisting = !!seen || !!firstDone || !!wizDone
3532
+ if (freshInstall) {
3533
+ openDialog({ kind: 'welcomeTour' })
3534
+ try { localStorage.setItem(MAJOR_TOUR_KEY, '1') } catch (eF) {}
3535
+ return
3536
+ }
3537
+ // 老用户(有使用痕迹)seen < 0.1.30(大更新前版本)或从未看过向导 → 补一次完整欢迎向导
3538
+ // (含引擎下载引导;0.1.29→0.1.34 直升即使曾看过旧版也重放,确保引导不漏)
3539
+ if (isExisting && !wizDone && (!seen || cmpVersion(seen, '0.1.30') < 0)) {
3547
3540
  openDialog({ kind: 'welcomeTour' })
3548
3541
  try { localStorage.setItem(MAJOR_TOUR_KEY, '1') } catch (eM) {}
3549
3542
  return
3550
3543
  }
3551
- if (majorPending && (d.current === '0.1.30' || seen)) {
3552
- // 大更新触达(老用户:seen<current 或已有使用痕迹;新装用户走 first 流程不重复打扰)
3544
+ if (majorPending && isExisting && !wizDone) {
3553
3545
  openDialog({ kind: 'welcomeTour' })
3554
3546
  try { localStorage.setItem(MAJOR_TOUR_KEY, '1') } catch (eM) {}
3555
3547
  return
3556
3548
  }
3557
- if (!seen && !localStorage.getItem('dsh-auto-memory.firstRunDone')) {
3558
- openDialog({ kind: 'first', currentVersion: d.current })
3559
- } else if (seen && seen !== d.current) {
3549
+ if (seen && seen !== d.current) {
3560
3550
  // 只弹 0.1.30 大更新卡(补丁并入其尾部),不逐个弹历史版本——大更新内容才是要传达的
3561
3551
  var bigKey = '0.1.30'
3562
3552
  if (CHANGELOG[bigKey]) {
@@ -3567,14 +3557,15 @@ window.__ModuleLoader__.load({
3567
3557
  if (versions.length) openDialog({ kind: 'update', versions: versions, currentVersion: d.current })
3568
3558
  else try { localStorage.setItem('dsh-auto-memory.seenVersion', d.current) } catch (e3) {}
3569
3559
  }
3570
- } else if (!seen && localStorage.getItem('dsh-auto-memory.firstRunDone')) {
3560
+ } else if (!seen && firstDone) {
3571
3561
  // 老用户但 seen 缺失(旧版升级/缓存清理):只看当前版本一条,不补历史链,避免把旧 log 塞给用户
3572
3562
  try { localStorage.setItem('dsh-auto-memory.seenVersion', d.current) } catch (e3) {}
3573
3563
  if (CHANGELOG[d.current]) openDialog({ kind: 'update', versions: [{ version: d.current, items: CHANGELOG[d.current] }], currentVersion: d.current })
3574
3564
  }
3575
3565
  } catch (e) {}
3576
3566
  }
3577
- }).catch(function () {})
3567
+ }
3568
+ apiGet(API.updateCheck).then(dispatchStartupDialog).catch(function () {})
3578
3569
 
3579
3570
  // 动态通知(发布者→用户:重大 bug 提醒,不依赖发版):启动检查 + 每小时刷新;按 id 去重,urgent 优先
3580
3571
  function checkNotices() {
@@ -3595,6 +3586,23 @@ window.__ModuleLoader__.load({
3595
3586
  checkNotices()
3596
3587
  var noticesTimer = setInterval(checkNotices, 3600 * 1000)
3597
3588
 
3589
+ // 打开即自动检测语义引擎(0.1.37,#14 后续):每次打开页面一次「快检 → 失败自动深扫
3590
+ // (~/.dsh/profiles 全家 + pnpm 虚拟存储) → 仍缺才推荐」。深扫命中即热接入(即时生效);
3591
+ // 推荐卡关闭 = 24h 免打扰。延迟 12s 错开开屏的向导/changelog 弹窗(优先级本身也压它一头)。
3592
+ setTimeout(function () {
3593
+ apiGet(API.semanticDeepDetect).then(function (d) {
3594
+ try {
3595
+ if (!d || d.ready) return
3596
+ if (!(d.deep && d.deep.integrated)) {
3597
+ var sdUntil = 0
3598
+ try { sdUntil = Number(localStorage.getItem('dsh-auto-memory.semDetectSnoozeUntil') || 0) } catch (eSn) {}
3599
+ if (Date.now() < sdUntil) return
3600
+ }
3601
+ openDialog({ kind: 'semSetup', result: d })
3602
+ } catch (eSD) {}
3603
+ }).catch(function () {})
3604
+ }, 12000)
3605
+
3598
3606
  // 时间检测轮询(30s):拉 host 的暂离状态/待展示总结,检测暂离回归并自动弹窗
3599
3607
  var lastPendingSummary = null
3600
3608
  var awayPollTimer = null
package/lib/index.js CHANGED
@@ -32,7 +32,7 @@ import { createContextHost } from './context-host.js'
32
32
  import { createSuccessEvidencePre } from './context-bridge.js'
33
33
  import { createIndexSyncHostPre } from './m7-index-sync-host.js'
34
34
  import { createActivationHost } from './activation-host.js'
35
- import { createJsSemanticEnginePre, createSemanticDownloaderPre, fuseD6Pre, E5_SMALL_Q8_MANIFEST_V1 } from './semantic-js.js'
35
+ import { createJsSemanticEnginePre, createSemanticDownloaderPre, fuseD6Pre, probeJsSemanticAssets, deepScanPeerTransformers, E5_SMALL_Q8_MANIFEST_V1 } from './semantic-js.js'
36
36
  import { loadAndVerifyPolicy, decideActivationV2, lexicalContainment } from './semantic-decide.js'
37
37
  import { createPythonSidecarClientPre, defaultWorkerScriptPathPre } from './python-sidecar-client.js'
38
38
  import { createEpisodicStorePre } from './episodic-store.js'
@@ -93,6 +93,7 @@ export const API = {
93
93
  notices: '/api/dsh-auto-memory/notices',
94
94
  'activation-inbox': '/api/dsh-auto-memory/activation-inbox',
95
95
  'semantic-status': '/api/dsh-auto-memory/semantic-status',
96
+ 'semantic-deep-detect': '/api/dsh-auto-memory/semantic-deep-detect',
96
97
  'semantic-download': '/api/dsh-auto-memory/semantic-download',
97
98
  'semantic-emit': '/api/dsh-auto-memory/semantic-emit',
98
99
  'shadow-recent': '/api/dsh-auto-memory/shadow-recent',
@@ -4352,28 +4353,45 @@ export function apply(ctx, config) {
4352
4353
  const pluginDir = path.dirname(fileURLToPath(import.meta.url))
4353
4354
  engine._jsSemantic = createJsSemanticEnginePre({ pluginDir })
4354
4355
  engine._jsDownload = createSemanticDownloaderPre({ modelsRoot: path.join(pluginDir, 'models') })
4355
- // 资产探测(semantic-status / 档位解析 / 引导卡共用):发行包 models/ 优先,其次开发树
4356
- engine.semanticAssetProbe = async () => {
4357
- const fsMod = await import('node:fs')
4358
- const cands = [
4359
- path.join(pluginDir, 'models', 'multilingual-e5-small', 'onnx', 'model_quantized.onnx'),
4360
- path.join(pluginDir, '..', 'artifacts', 'm7-live-pre', 'js-semantic-trial', 'models', 'multilingual-e5-small', 'onnx', 'model_quantized.onnx'),
4361
- ]
4362
- const modelOnnx = cands.find((c) => fsMod.existsSync(c)) || cands[0]
4363
- const peerCands = [
4364
- path.join(pluginDir, 'node_modules', '@huggingface', 'transformers'),
4365
- // pnpm 默认把 peer/optional 依赖提升到 profile node_modules(pluginDir 上两级)
4366
- path.join(pluginDir, '..', '..', '..', 'node_modules', '@huggingface', 'transformers'),
4367
- path.join(pluginDir, '..', 'artifacts', 'm7-live-pre', 'js-semantic-trial', 'node_modules', '@huggingface', 'transformers'),
4368
- ]
4369
- const peerDir = peerCands.find((c) => fsMod.existsSync(c)) || peerCands[0]
4370
- const assetPresent = fsMod.existsSync(modelOnnx)
4356
+ // 资产探测(semantic-status / 档位解析 / 引导卡共用):共享实现见 semantic-js-pre
4357
+ // probeJsSemanticAssets —— peer 探测与引擎加载走同一套 Node 解析(2026-09-02 issue 修正)。
4358
+ // _peerExtraDirs = 深度扫描热接入位(semanticDeepDetect 命中后 probe/加载/档位即时生效)
4359
+ engine._peerExtraDirs = []
4360
+ engine.semanticAssetProbe = async () => probeJsSemanticAssets(pluginDir, engine._peerExtraDirs)
4361
+ // 打开即自动检测(0.1.37,#14 后续):快检 模型在场但推理库缺失时深度扫描
4362
+ // (~/.dsh/profiles/* 全家 + pnpm 虚拟存储) → 命中即热接入(probe/加载双注入,无需重启),
4363
+ // 并给出 recommendation 供引导卡分流(none/setup-both/download-model/install-peer)
4364
+ engine.semanticDeepDetect = async () => {
4365
+ const quick = await engine.semanticAssetProbe()
4366
+ const deep = { scanned: false, foundDirs: [], integrated: false }
4367
+ if (!quick.ready && quick.assetPresent) {
4368
+ deep.scanned = true
4369
+ try {
4370
+ const osMod = await import('node:os')
4371
+ const profilesRoot = path.join(osMod.homedir(), '.dsh', 'profiles')
4372
+ const found = deepScanPeerTransformers([path.join(pluginDir, '..'), profilesRoot])
4373
+ deep.foundDirs = found
4374
+ for (const d of found) {
4375
+ deep.integrated = true
4376
+ if (!engine._peerExtraDirs.includes(d)) engine._peerExtraDirs.push(d)
4377
+ try { if (engine._jsSemantic && engine._jsSemantic.addPeerDirCandidates) engine._jsSemantic.addPeerDirCandidates([d]) } catch (_) {}
4378
+ }
4379
+ } catch (_) { /* 扫描失败按未命中处理 */ }
4380
+ }
4381
+ const after = await engine.semanticAssetProbe()
4382
+ const recommendation = after.ready ? 'none'
4383
+ : (!after.assetPresent && !after.peerPresent) ? 'setup-both'
4384
+ : (!after.peerPresent) ? 'install-peer'
4385
+ : 'download-model'
4371
4386
  return {
4372
- assetPresent,
4373
- peerPresent: fsMod.existsSync(peerDir),
4374
- ready: Boolean(assetPresent && fsMod.existsSync(peerDir)),
4375
- assetBytes: assetPresent ? fsMod.statSync(modelOnnx).size : 0,
4376
- assetPath: modelOnnx,
4387
+ assetPresent: after.assetPresent,
4388
+ peerPresent: after.peerPresent,
4389
+ ready: after.ready,
4390
+ assetBytes: after.assetBytes,
4391
+ assetPath: after.assetPath,
4392
+ resolvedTier: await engine.resolveSemanticTier(),
4393
+ deep,
4394
+ recommendation,
4377
4395
  }
4378
4396
  }
4379
4397
  // 档位解析:lexical→C1 强制保底;其余(auto/js/python)在资产就绪时启用 C2 臂
@@ -4552,12 +4570,13 @@ export function apply(ctx, config) {
4552
4570
  // 审查修复轮3:有效性判断必须是严格 session 身份 —— !!agent.session 会放行无身份对象
4553
4571
  const hasAgent = !!(agent && engine.hasReliableSessionIdentity(agent))
4554
4572
  if (hasAgent) {
4573
+ // 套娃防护(问题②):本插件 spawn 的子代理事件零处理——其 turn 结束不得再触发沉淀/路由,
4574
+ // 也不得刷新活动戳(v0.1.37 修正:后台沉淀/定时总结若刷新活动戳,会把真暂离误翻成「回归」)
4575
+ try { if (engine._ownSubagents && engine._ownSubagents.has(agent)) return } catch (e) {}
4555
4576
  // 最后活动时间写入该 agent 自己的 runtime(不再落到 currentRuntime()/default)
4556
4577
  try { engine.runtimeFor(agent).lastActiveAt = Date.now() } catch (e) {}
4557
4578
  // 全局活动戳同步更新(暂离检测统揽全局,见 tickTime)
4558
4579
  engine._globalLastActiveAt = Date.now()
4559
- // 套娃防护(问题②):本插件 spawn 的子代理事件零处理——其 turn 结束不得再触发沉淀/路由
4560
- try { if (engine._ownSubagents && engine._ownSubagents.has(agent)) return } catch (e) {}
4561
4580
  try { engine.ingestAgentLifecycle(agent, 'agent/turn-stopping', { turn: payload && payload.turn, payload: { turn: payload && payload.turn } }) } catch (e) {}
4562
4581
  }
4563
4582
  diag('turn-stopping fired: turn=' + JSON.stringify(payload && payload.turn) + ' hasAgent=' + hasAgent + ' payloadKeys=' + (payload ? Object.keys(payload).join(',') : 'null'))
@@ -4946,6 +4965,15 @@ export function apply(ctx, config) {
4946
4965
  } catch (e) { return writeJson(res, 500, { error: String(e && e.message ? e.message : e) }) }
4947
4966
  },
4948
4967
  },
4968
+ {
4969
+ kind: 'exact',
4970
+ path: API['semantic-deep-detect'],
4971
+ handler: async (req, res) => {
4972
+ // 打开即自动检测(0.1.37;GET 只读;loopback):快检+失败自动深扫+命中热接入+引导分流。
4973
+ if (!isLoopbackRequest(req)) return writeJson(res, 403, { error: 'forbidden: loopback-only' })
4974
+ try { return writeJson(res, 200, await engine.semanticDeepDetect()) } catch (e) { return writeJson(res, 500, { error: String(e && e.message ? e.message : e) }) }
4975
+ },
4976
+ },
4949
4977
  {
4950
4978
  kind: 'exact',
4951
4979
  path: API['semantic-download'],
@@ -13,8 +13,9 @@
13
13
  * 「要不要打断」仍属两车道策略(Python sidecar 在场时)。全部函数对非法输入 fail closed。
14
14
  */
15
15
  import { createHash } from 'node:crypto'
16
- import { existsSync, readFileSync, mkdirSync, renameSync, unlinkSync, writeFileSync, rmSync } from 'node:fs'
16
+ import { existsSync, readFileSync, mkdirSync, readdirSync, renameSync, statSync, unlinkSync, writeFileSync, rmSync } from 'node:fs'
17
17
  import { fileURLToPath, pathToFileURL } from 'node:url'
18
+ import { createRequire } from 'node:module'
18
19
  import path from 'node:path'
19
20
 
20
21
  export const JS_SEMANTIC_ENGINE_VERSION = 'js_semantic_engine_v1'
@@ -74,14 +75,106 @@ function defaultModelsDirCandidates(pluginDir) {
74
75
  }
75
76
 
76
77
  function defaultPeerDirCandidates(pluginDir) {
78
+ // pluginDir = <pkg>/lib(2026-09-02 issue 复核修正:此前上溯层级错位 → peerPresent 恒 false)。
79
+ // 静态候选仅作兜底,主路径是 resolvePeerTransformersDir 的真实解析:
80
+ // 1) lib/node_modules —— issue 临时 junction 绕过位(向后兼容)
81
+ // 2) <pkg>/node_modules —— 包内邻接位(lib 上 1 级再进 node_modules)
82
+ // 3) lib 上 3 级直拼 @huggingface —— 标准布局即 <root>/node_modules(pnpm hoisted/npm 提升),
83
+ // pnpm isolated 下即虚拟存储包内位 .pnpm/<hash>/node_modules
84
+ // 4) 开发树 artifacts
77
85
  return [
78
86
  path.join(pluginDir, 'node_modules', '@huggingface', 'transformers'),
79
- // pnpm 默认把 peer/optional 依赖提升到 profile 根 node_modules(pluginDir 上两级)
80
- path.join(pluginDir, '..', '..', '..', 'node_modules', '@huggingface', 'transformers'),
87
+ path.join(pluginDir, '..', 'node_modules', '@huggingface', 'transformers'),
88
+ path.join(pluginDir, '..', '..', '..', '@huggingface', 'transformers'),
81
89
  path.join(pluginDir, '..', 'artifacts', 'm7-live-pre', 'js-semantic-trial', 'node_modules', '@huggingface', 'transformers'),
82
90
  ]
83
91
  }
84
92
 
93
+ /**
94
+ * peer 探测(2026-09-02 issue 修正):真实解析优先 —— createRequire 从 lib/ 起走 Node 标准
95
+ * 解析,天然覆盖 npm/pnpm(hoisted/isolated 虚拟存储)/junction 全部布局,且不执行模块本体
96
+ * (require.resolve 只解析不加载);静态候选仅作 exports 损坏等极端情形的兜底;
97
+ * extraDirs = 深度扫描(semanticDeepDetect)热接入的额外目录,最后兜底检查。
98
+ * 返回命中的包目录,''=未找到。
99
+ */
100
+ export function resolvePeerTransformersDir(pluginDir, extraDirs) {
101
+ try {
102
+ const req = createRequire(path.join(pluginDir, 'noop.js'))
103
+ const entry = req.resolve('@huggingface/transformers')
104
+ // entry 是包 main 文件(可能在任意子目录,如 dist/transformers.node.cjs):
105
+ // 从 entry 向上走到 package.json 所在层 = 包目录
106
+ if (entry && existsSync(entry)) {
107
+ let dir = path.dirname(entry)
108
+ while (dir && !existsSync(path.join(dir, 'package.json'))) {
109
+ const up = path.dirname(dir)
110
+ if (up === dir) { dir = ''; break }
111
+ dir = up
112
+ }
113
+ if (dir) return dir
114
+ }
115
+ } catch (_) { /* 静态候选兜底 */ }
116
+ const hit = defaultPeerDirCandidates(pluginDir).find((c) => existsSync(c))
117
+ if (hit) return hit
118
+ for (const d of Array.isArray(extraDirs) ? extraDirs : []) {
119
+ if (typeof d === 'string' && d && existsSync(d)) return d
120
+ }
121
+ return ''
122
+ }
123
+
124
+ /**
125
+ * 深度扫描(0.1.37「打开即自动检测」;issue #14 后续):零依赖手扫常见安装根。
126
+ * 每个 root 探两层:root 自身的 node_modules(直传 profile 根/包根时命中)+ 一级子目录
127
+ * (直传 ~/.dsh/profiles 时枚举各 profile);每个落位再探 node_modules/@huggingface/
128
+ * transformers、pnpm 虚拟存储 .pnpm/node_modules/... 与 .pnpm/@huggingface+transformers@版本
129
+ * /node_modules/...(isolated 模式包体落位)。返回存在的包目录列表(去重,保持扫描序)。
130
+ */
131
+ export function deepScanPeerTransformers(roots) {
132
+ const out = []
133
+ const seen = new Set()
134
+ const push = (p) => { if (p && !seen.has(p) && existsSync(p)) { seen.add(p); out.push(p) } }
135
+ const probeDir = (base) => {
136
+ if (!base || typeof base !== 'string') return
137
+ push(path.join(base, 'node_modules', '@huggingface', 'transformers'))
138
+ push(path.join(base, 'node_modules', '.pnpm', 'node_modules', '@huggingface', 'transformers'))
139
+ const pnpmDir = path.join(base, 'node_modules', '.pnpm')
140
+ let entries = []
141
+ try { entries = readdirSync(pnpmDir) } catch (_) { /* base 无 .pnpm 虚拟存储 */ }
142
+ for (const e of entries) {
143
+ if (/^@huggingface\+transformers@/.test(e)) push(path.join(pnpmDir, e, 'node_modules', '@huggingface', 'transformers'))
144
+ }
145
+ }
146
+ for (const root of Array.isArray(roots) ? roots : []) {
147
+ if (!root || typeof root !== 'string') continue
148
+ probeDir(root)
149
+ let children = []
150
+ try { children = readdirSync(root) } catch (_) { /* root 不可枚举 */ }
151
+ for (const c of children.slice(0, 64)) probeDir(path.join(root, c))
152
+ }
153
+ return out
154
+ }
155
+
156
+ /**
157
+ * semanticAssetProbe 共享实现(index.js 的 status API / 档位解析 / 引导卡同源):
158
+ * 模型资产=发行包 lib/models 优先,其次开发树;peer=resolvePeerTransformersDir。
159
+ * extraDirs 透传给 peer 探测(深度扫描热接入位)。返回形状与 v0.1.36 完全一致。
160
+ */
161
+ export function probeJsSemanticAssets(pluginDir, extraDirs) {
162
+ const modelCands = [
163
+ path.join(pluginDir, 'models', E5_MODELS_SUBDIR, 'onnx', 'model_quantized.onnx'),
164
+ path.join(pluginDir, '..', 'artifacts', 'm7-live-pre', 'js-semantic-trial', 'models', E5_MODELS_SUBDIR, 'onnx', 'model_quantized.onnx'),
165
+ ]
166
+ const modelOnnx = modelCands.find((c) => existsSync(c)) || modelCands[0]
167
+ const assetPresent = existsSync(modelOnnx)
168
+ const peerDir = resolvePeerTransformersDir(pluginDir, extraDirs)
169
+ return {
170
+ assetPresent,
171
+ peerPresent: Boolean(peerDir),
172
+ ready: Boolean(assetPresent && peerDir),
173
+ assetBytes: assetPresent ? statSync(modelOnnx).size : 0,
174
+ assetPath: modelOnnx,
175
+ }
176
+ }
177
+
85
178
  async function importPeerTransformers(peerDirs) {
86
179
  try {
87
180
  return await import('@huggingface/transformers') // 发行包:peer 邻接安装
@@ -213,6 +306,12 @@ export function createJsSemanticEnginePre(opts = {}) {
213
306
  embedding: !!rebuilding,
214
307
  }
215
308
  },
309
+ /** 深度扫描热接入(0.1.37):把扫描发现的额外 peer 目录并入加载候选(去重,立即生效)。 */
310
+ addPeerDirCandidates(dirs) {
311
+ for (const d of Array.isArray(dirs) ? dirs : []) {
312
+ if (typeof d === 'string' && d && !peerDirCands.includes(d)) peerDirCands.push(d)
313
+ }
314
+ },
216
315
  /**
217
316
  * 对语料做稠密排名。返回 {miv, scores:Map(memoryId→cosine)};失败返回 null(调用方回退词法)。
218
317
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@a9i5k4/dsh-auto-memory",
3
3
  "description": "主动联想记忆插件:记忆不靠模型调用,自己被唤回——Host 观察情境,NLP+向量化双轨检索,固定边界注入不破坏前缀缓存。三层记忆自动沉淀、欢迎向导、唤起回顾、无人值守、AI 问候与反思、日历、跨工具记忆继承。Proactive associative memory for DSH: zero-call recall, three-layer auto-consolidation, welcome tour, unattended mode.",
4
- "version": "0.1.36",
4
+ "version": "0.1.38",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {