memory-eternal 0.10.51 → 0.10.52

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "memory-eternal",
4
- "version": "0.10.51",
4
+ "version": "0.10.52",
5
5
  "description": "记忆核心(Memory Eternal):对话结束后自动沉淀知识卡到本地记忆库(~/.dsh/memory-vault),支持检索 / 召回 / 审核中心。",
6
6
  "author": {
7
7
  "name": "EternalNight996",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-eternal",
3
- "version": "0.10.51",
3
+ "version": "0.10.52",
4
4
  "description": "记忆核心(Memory Eternal):对话结束后自动沉淀知识卡到本地记忆库(~/.dsh/memory-vault),支持检索 / 召回 / 审核中心。",
5
5
  "author": {
6
6
  "name": "EternalNight996",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-eternal",
3
- "version": "0.10.51",
3
+ "version": "0.10.52",
4
4
  "description": "记忆核心(Memory Eternal):对话结束后自动沉淀知识卡到本地记忆库(~/.dsh/memory-vault),支持检索 / 召回 / 审核中心。",
5
5
  "author": {
6
6
  "name": "EternalNight996",
package/README.en.md CHANGED
@@ -361,14 +361,15 @@ curl http://127.0.0.1:7999/memory-eternal/api/web-info # is the standalone we
361
361
 
362
362
  | Version | Date | Highlights |
363
363
  |---|---|---|
364
+ | **v0.10.52** | 2026-10-19 | **Redaction hole in the diagnostic bundle (privacy fix; upgrade recommended)**. The 0.10.51 one-command bundle had a gap: the home directory appears in **JSON-escaped form** (C:\\Users\\x) inside doctor/align --json output and meta.json, while only the plain and forward-slash variants were replaced, so the bundle could still be grepped for the home path. All three variants (plain, forward slash, JSON escaped) now become <home>, with a test assert added for the escaped form; a freshly generated bundle on this machine verifies as **no home path and no sk- keys**. If you already generated and shared a bundle with 0.10.51, please regenerate it before sharing. npm test now runs **433 checks** |
364
365
  | **v0.10.51** | 2026-10-19 | **One-command diagnostic bundle: dsh-memory support** (reliability line; in #47 the user-pasted context was truncated by GitHub so I could not see the full picture — this exists for exactly that kind of exchange). A single command produces a **zip** with meta (version matrix / platform / node / vault), doctor, align, status, the watchdog lock, the host heartbeat, the shared config, the tail of the capture log (200 lines by default) and a README; everything is **redacted before writing** (home directory to <home>, sk-/ghp_/github_pat/Bearer and key-like config fields to ***). Implemented with **no dependencies**: a hand-written store/deflate ZIP writer (lib/zip.js) whose output opens in Windows Explorer (verified by extracting with Expand-Archive). In restricted environments --no-spawn skips the sub-processes, while --out and --tail N control destination and log length. Adds tests/support-bundle.test.mjs (redaction, zip structure, no plaintext secrets inside the bundle, CLI wiring). npm test now runs **433 checks** |
365
- | **v0.10.50** | 2026-10-19 | **Models that ignore JSON no longer burn a call every turn (a follow-up to #47)**. ① "The model replied with prose and produced no JSON object at all" is now separated from the generic UNPARSEABLE_OUTPUT failure and gets its own code **MODEL_NOT_JSON**, with wording that says the model likely does not follow structured output. ② New `lib/model-strikes.js` records which provider/model recently ignored the format (24h window, at most 50 entries) and the host moves those to the end of the candidate list when routing (never removing them; if every candidate was struck the original order is kept, so we never run out), logging a "candidates reordered" line. ③ Effect: models such as stepfun/step-api, which answer distillation prompts conversationally, stop being tried first from the second turn on, saving a pointless call each time. Adds `tests/model-strikes.test.mjs` (prose detection, stable ranking, record window and trimming, wiring guards). npm test now runs **429 checks** |
366
366
 
367
367
  <details>
368
- <summary>Older release notes (v0.10.49 and earlier · 76 releases, click to expand)</summary>
368
+ <summary>Older release notes (v0.10.50 and earlier · 77 releases, click to expand)</summary>
369
369
 
370
370
  | Version | Date | Highlights |
371
371
  |---|---|---|
372
+ | **v0.10.50** | 2026-10-19 | **Models that ignore JSON no longer burn a call every turn (a follow-up to #47)**. ① "The model replied with prose and produced no JSON object at all" is now separated from the generic UNPARSEABLE_OUTPUT failure and gets its own code **MODEL_NOT_JSON**, with wording that says the model likely does not follow structured output. ② New `lib/model-strikes.js` records which provider/model recently ignored the format (24h window, at most 50 entries) and the host moves those to the end of the candidate list when routing (never removing them; if every candidate was struck the original order is kept, so we never run out), logging a "candidates reordered" line. ③ Effect: models such as stepfun/step-api, which answer distillation prompts conversationally, stop being tried first from the second turn on, saving a pointless call each time. Adds `tests/model-strikes.test.mjs` (prose detection, stable ranking, record window and trimming, wiring guards). npm test now runs **429 checks** |
372
373
  | **v0.10.49** | 2026-10-19 | **Fixes "cannot choose the distillation model" in the desktop app (#47): the standalone page now proxies /llm-routes and /llm-test to the host, plus one more state in the matrix**. Root cause: the desktop memory popup is a **standalone page inside an iframe** with no DSH llm service, so /llm-routes always failed and provider/model had to be typed by hand. The standalone page now reads the host heartbeat (the port in <config>.host.json, 127.0.0.1 only, valid for 120s) and forwards those two routes to the host, marking the reply as proxied; when the host is offline the panel shows the concrete reason instead of silently falling back to text inputs. ② The version-card matrix gains restartRepair: when versions already match but nothing keeps the instance alive it says "re-register and keep it alive", no longer the self-contradictory "switch to v0.10.48 (currently v0.10.48)". npm test now runs **425 checks** |
373
374
  | **v0.10.48** | 2026-10-19 | **A visual gate plus exhaustive state coverage (the option we agreed on)**. ① **New `npm run panel-check`** (scripts/panel-check.mjs, zero dependencies): drives local headless Chrome over CDP, asserts **no horizontal overflow** on each screen and saves screenshots (list / reader / config / feedback modal). On its very first run it caught two real bugs: an unbreakable long run in a card title (`(begin/chunk/probe/complete/abort)`) overflowing by 45px, and the **feedback textarea overflowing by 22px** (width:100% plus padding under content-box) — exactly the layout/scrollbar anomaly reported. ② Fixes: word-breaking on `.mc-cardrow h4` and a shared border-box rule for form controls. ③ **The version-card decision is now a pure function** `src/client/version-state.js`: versionCardState returns {showButton, emphasis, warn, hint} and the component only maps keys to text; a table-driven test **enumerates 324 combinations** and enforces the invariant that the button is shown whenever version info exists — structurally preventing the 0.10.45 regression where a third state made it disappear. npm test now runs **420 checks** |
374
375
  | **v0.10.47** | 2026-10-19 | **The panel version probe now refreshes itself, and the "resident not running" wording is fixed (user report: the panel kept saying "resident instance: not running" after a restart)**. ① The instance was in fact alive (status showed 7999 listening and serving v0.10.46), but the probe ran only once when the panel mounted, so a run that happened to land inside the restart window was cached as "not running" and stuck. It now **re-checks silently every 60 seconds** (without lighting up the Check-for-updates button) and **immediately when the tab becomes visible** (visibilitychange); a restart already triggers a re-check six seconds later. ② When the resident really is down, the hint used to interpolate an empty "currently v"; it now uses vcRestartStart: "Starts the resident instance on the current disk version v{disk} (no DSH restart needed)". npm test now runs **408 checks** |
package/README.md CHANGED
@@ -393,14 +393,15 @@ curl http://127.0.0.1:7999/memory-eternal/api/web-info # 独立 web 是否活
393
393
 
394
394
  | 版本 | 日期 | 关键改动 |
395
395
  |---|---|---|
396
+ | **v0.10.52** | 2026-10-19 | **诊断包脱敏补漏(隐私修复,建议尽快升级)**。0.10.51 的「一键诊断包」有个漏点:doctor/align --json 与 meta.json 里的家目录是 **JSON 转义形态**(C:\\Users\\x),而我只替换了原样/正斜杠两种写法 → 包里仍能 grep 到家目录明文。现在三种写法(原样、正斜杠、JSON 转义)都替换成 <home>,并在测试里加了一条「JSON 转义形态不能漏」的断言;本机重新生成诊断包实测:**无家目录明文、无 sk- 密钥**。**如果你已经用 0.10.51 生成过诊断包并分享出去,建议重新生成一次再分享。** npm test 共 **433 项** |
396
397
  | **v0.10.51** | 2026-10-19 | **一键诊断包 dsh-memory support**(可靠性线;#47 里用户贴的现场被 GitHub 截断,我拿不到完整信息,这功能就是为这类沟通做的)。一条命令生成 **zip**:meta(版本矩阵/平台/node/vault)/ doctor / align / status / 看门狗锁 / 宿主心跳 / 共享配置 / 沉淀日志尾巴(默认 200 行)+ README;写入前**统一脱敏**(家目录→<home>,sk-/ghp_/github_pat/Bearer/配置里 key 类字段→***)。实现为**零依赖**:自写 store/deflate ZIP 写入器(lib/zip.js,Windows 资源管理器可直接打开,实测 Expand-Archive 解压正常)。受限环境可加 --no-spawn 跳过子进程;--out 指定输出、--tail N 控制日志条数。新增 tests/support-bundle.test.mjs(4 项:脱敏、zip 结构、包内不含明文密钥、CLI 接线)。npm test 共 **433 项** |
397
- | **v0.10.50** | 2026-10-19 | **「模型不守 JSON」不再每轮白烧调用(#47 衍生改进)**。① 把「模型回了散文、输出里根本没有 JSON 对象」从笼统的 UNPARSEABLE_OUTPUT 里拆出来,给独立错误码 **MODEL_NOT_JSON**,文案直接说清「该模型可能不遵守结构化输出」。② 新增 `lib/model-strikes.js`:记下「哪个 provider/model 最近不守格式」(24h 窗口、最多 50 条),宿主选路时把它们**排到候选末尾**(不删除;全部命中过则按原顺序兜底,绝不没候选),并留一条「蒸馏候选已调整」日志。③ 效果:像 stepfun/step-api 这种会把蒸馏当聊天回的模型,第二轮起就不再优先尝试,省掉每次的无效调用。新增 `tests/model-strikes.test.mjs`(4 项:散文识别 / 排序稳定性 / 记录窗口与裁剪 / 接线守卫)。npm test 共 **429 项** |
398
398
 
399
399
  <details>
400
- <summary>更早的更新日志(v0.10.49 及以前 · 共 76 版,点开查看)</summary>
400
+ <summary>更早的更新日志(v0.10.50 及以前 · 共 77 版,点开查看)</summary>
401
401
 
402
402
  | 版本 | 日期 | 关键改动 |
403
403
  |---|---|---|
404
+ | **v0.10.50** | 2026-10-19 | **「模型不守 JSON」不再每轮白烧调用(#47 衍生改进)**。① 把「模型回了散文、输出里根本没有 JSON 对象」从笼统的 UNPARSEABLE_OUTPUT 里拆出来,给独立错误码 **MODEL_NOT_JSON**,文案直接说清「该模型可能不遵守结构化输出」。② 新增 `lib/model-strikes.js`:记下「哪个 provider/model 最近不守格式」(24h 窗口、最多 50 条),宿主选路时把它们**排到候选末尾**(不删除;全部命中过则按原顺序兜底,绝不没候选),并留一条「蒸馏候选已调整」日志。③ 效果:像 stepfun/step-api 这种会把蒸馏当聊天回的模型,第二轮起就不再优先尝试,省掉每次的无效调用。新增 `tests/model-strikes.test.mjs`(4 项:散文识别 / 排序稳定性 / 记录窗口与裁剪 / 接线守卫)。npm test 共 **429 项** |
404
405
  | **v0.10.49** | 2026-10-19 | **桌面版里「无法选择蒸馏模型」修复(#47):独立页把 /llm-routes 与 /llm-test 代理给宿主 + 文案补一个状态**。根因:桌面版的「记忆」弹窗是 iframe 里的**独立页**,它没有 DSH 的 llm 服务 → /llm-routes 恒失败 → provider/model 只能手填。现在独立页读宿主心跳(<config>.host.json 里的端口,仅 127.0.0.1、心跳 120s 内有效),把这两个路由**转发给宿主**执行并标记 proxied;宿主不在线时给明确原因(面板直接显示,不再静默回退成手填)。② 版本卡状态矩阵补 restartRepair:**版本已对但没人保活**时提示「重新托管并保活」,不再出现「换成 v0.10.48(当前 v0.10.48)」这种自相矛盾的文案(截图实测)。npm test 共 **425 项** |
405
406
  | **v0.10.48** | 2026-10-19 | **界面验收手段 + 状态穷举(方案一落地)**。① **新增 `npm run panel-check`**(scripts/panel-check.mjs,零依赖):用本机 Chrome headless + CDP 打开面板,逐屏断言**没有横向溢出**并截图(列表 / 阅读器 / 配置 / 反馈弹窗)。它第一次跑就抓到两个真 bug:卡片标题里的无断点长串(`(begin/chunk/probe/complete/abort)`)溢出 45px;**反馈弹窗 textarea 溢出 22px**(width:100% + padding 在 content-box 下)—— 正是用户报的「排版不对 / 滚动条异常」。② 修复:`.mc-cardrow h4` 断词、`.me-overlay input/textarea/select` 统一 border-box。③ **版本卡判定抽成纯函数** `src/client/version-state.js`:`versionCardState(...)` 返回 `{showButton, emphasis, warn, hint}`,组件只做 key→文案;新增表驱动测试**穷举 324 种组合**,并守住不变式「只要拿到版本信息,按钮永远显示」—— 0.10.45「第三种状态导致按钮消失」从结构上不可能再发生。npm test 共 **420 项** |
406
407
  | **v0.10.47** | 2026-10-19 | **面板版本探针自动复查 + 「常驻未运行」文案修正(用户反馈:重启后面板一直显示「常驻实例 未运行」)**。① 现场其实**是活的**(status 显示 7999 在听、服务 v0.10.46),但版本探针只在面板挂载时跑一次 —— 恰好撞上重启窗口就会被缓存成「未运行」并一直显示。现在**每 60 秒静默复查**(不点亮「检查更新」按钮)+ **切回页面立刻复查**(visibilitychange),重启后 6 秒本来就会复查一次。② 文案:常驻实例真没跑时,原来那句会拼出「当前 v」(空值);现在改走 vcRestartStart:**「点击会用当前磁盘版本 v{disk} 把常驻实例拉起来(不用重启 DSH)」**。npm test 共 **408 项** |
package/lib/client.js CHANGED
@@ -347,7 +347,7 @@ Option A, because:
347
347
  height: calc(100vh - var(--dsh-frame-overlay-top, 68px));
348
348
  max-height: calc(100vh - var(--dsh-frame-overlay-top, 68px));
349
349
  }
350
- `;function Ta(t){t.effect(()=>t.locale.register(pt,{zh:ta,en:aa}),"memory-eternal: locale");let i=t.locale.bind(pt);t.effect(()=>t.slots.inject("settings.section",()=>t.slots.register({name:"settings.section",id:pt,order:25,label:()=>i("nav"),locale:pt,inject:()=>({})},()=>o.default.createElement(za,{t:i}))),"memory-eternal: settings section"),t.effect(()=>t.slots.inject("sidebar.footer.action",()=>t.slots.register({name:"sidebar.footer.action",id:`${pt}:footer`,order:100,label:()=>i("nav"),locale:pt,inject:()=>({})},s=>o.default.createElement(Ia,{t:i,locale:t.locale,wide:!(s&&s.wide===!1)}))),"memory-eternal: sidebar footer action")}function Da(){let[t,i]=(0,o.useState)(K+"/ui/app");return(0,o.useEffect)(()=>{let s=!0,m=()=>fetch("/memory-eternal/api/web-info").then(p=>p.json()).then(p=>{s&&p&&p.ok&&p.alive!==!1&&p.url&&i(p.url.endsWith("/")?p.url:p.url+"/")}).catch(()=>{});return fetch(K+"/ui/app",{method:"HEAD"}).then(p=>{if(s&&!p.ok)return m()}).catch(()=>s?m():void 0),()=>{s=!1}},[]),t}function za({t}){return(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0},children:[(0,e.jsx)("style",{children:Ee}),(0,e.jsx)(ra,{t,onReload:()=>{},version:0})]})}function Ia({t,locale:i,wide:s}){let[m,p]=(0,o.useState)(!1);return(0,e.jsxs)("div",{className:`me-footer${s?"":" rail"}`,children:[(0,e.jsx)("style",{children:Ee}),(0,e.jsxs)("button",{type:"button",className:"me-footer-btn",onClick:()=>p(!0),"aria-label":t("nav"),title:t("nav"),children:[(0,e.jsx)("span",{className:"me-footer-ico","aria-hidden":"true",children:(0,e.jsx)(oa,{})}),(0,e.jsx)("span",{className:"me-footer-label",children:t("nav")})]}),m&&(0,e.jsx)(Ba,{t,locale:i,onClose:()=>p(!1)})]})}function Ba({t,locale:i,onClose:s,tab:m}){let[p,r]=(0,o.useState)(!1),v=(0,o.useRef)(null),R=Da(),N=(0,o.useCallback)(I=>i&&typeof i.subscribe=="function"?i.subscribe(I):()=>{},[i]),P=(0,o.useCallback)(()=>i&&typeof i.getSnapshot=="function"?i.getSnapshot():null,[i]),C=(0,o.useSyncExternalStore)(N,P),k=C&&(C.active==="zh"||C.active==="en")?C.active:null,g=(0,o.useRef)(null);g.current===null&&k&&(g.current=k);let B=(0,o.useMemo)(()=>{let I=g.current?`lang=${g.current}`:"";if(m==="config")return`${K}/ui/config?tab=config${I?`&${I}`:""}`;let S=R.replace(/\/$/,""),q=S.endsWith("/ui/app")?"?":"/?";return m?`${S}${q}tab=${m}${I?`&${I}`:""}`:I?`${S}${q}${I}`:R},[R,m]);return(0,o.useEffect)(()=>{let I=v.current&&v.current.contentWindow;I&&k&&I.postMessage({source:"memory-eternal",type:"locale",lang:k},"*")},[k]),(0,o.useEffect)(()=>{let I=S=>{S.key==="Escape"&&s()};return window.addEventListener("keydown",I),()=>window.removeEventListener("keydown",I)},[s]),(0,e.jsxs)("div",{className:"me-overlay-top",onClick:s,children:[(0,e.jsx)("style",{children:Ee}),(0,e.jsx)("div",{className:"me-modal"+(p?" me-modal-full":""),onClick:I=>I.stopPropagation(),children:(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0,position:"relative"},children:[(0,e.jsxs)("div",{style:{position:"absolute",top:8,right:8,zIndex:10,display:"flex",gap:4,padding:"4px 6px",background:"var(--dsw-alias-bg-overlay, rgba(17,24,39,0.45))",borderRadius:8,backdropFilter:"blur(4px)"},children:[(0,e.jsx)("button",{type:"button",onClick:()=>r(I=>!I),"aria-label":t(p?"exitFullscreen":"enterFullscreen"),title:t(p?"exitFullscreen":"enterFullscreen"),style:{width:26,height:26,borderRadius:6,border:"1px solid rgba(255,255,255,0.18)",background:"rgba(255,255,255,0.08)",color:"#fff",cursor:"pointer",fontSize:14,lineHeight:1,display:"flex",alignItems:"center",justifyContent:"center"},children:p?"\u2921":"\u2922"}),(0,e.jsx)("button",{type:"button",onClick:s,"aria-label":t("close"),title:t("close"),style:{width:26,height:26,borderRadius:6,border:"1px solid rgba(255,255,255,0.18)",background:"rgba(255,255,255,0.08)",color:"#fff",cursor:"pointer",fontSize:16,lineHeight:1,display:"flex",alignItems:"center",justifyContent:"center"},children:"\xD7"})]}),(0,e.jsx)("iframe",{ref:v,src:B,title:"memory-eternal",allow:"clipboard-write; clipboard-read",style:{width:"100%",flex:1,border:0,borderRadius:"inherit",background:"var(--dsw-alias-bg-base, #fff)"}})]})})]})}function oa(){return(0,e.jsxs)("svg",{viewBox:"0 0 24 24","aria-hidden":"true",children:[(0,e.jsx)("defs",{children:(0,e.jsxs)("linearGradient",{id:"mg-brain",x1:"0",y1:"0",x2:"1",y2:"1",children:[(0,e.jsx)("stop",{offset:"0%",stopColor:"#FFC46B"}),(0,e.jsx)("stop",{offset:"50%",stopColor:"#F97316"}),(0,e.jsx)("stop",{offset:"100%",stopColor:"#EA580C"})]})}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 0-6.4.1 4.1 4.1 0 0 0-2.7 5.9 4.1 4.1 0 0 0 .6 6.7A4.2 4.2 0 0 0 12 18.2Z"}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 1 6.4.1 4.1 4.1 0 0 1 2.7 5.9 4.1 4.1 0 0 1-.6 6.7A4.2 4.2 0 0 1 12 18.2Z"}),(0,e.jsx)("path",{d:"M12 9.2c-.8-1.4-1.7-2-3.1-2.2M12 9.2c.8-1.4 1.7-2 3.1-2.2M12 12.4c-1.2 1-3.2 1.6-5 1.4M12 12.4c1.2 1 3.2 1.6 5 1.4M12 13.2v4",fill:"none",stroke:"rgba(255,255,255,0.62)",strokeWidth:"1",strokeLinecap:"round"}),(0,e.jsx)("circle",{cx:"12",cy:"8.6",r:"1.1",fill:"rgba(255,255,255,0.85)"})]})}var vt="0.10.51";function La({t,inModal:i,onClose:s,onFull:m,full:p}){let[r,v]=(0,o.useState)(null),[R,N]=(0,o.useState)([]),[P,C]=(0,o.useState)(!0),[k,g]=(0,o.useState)(null),[B,I]=(0,o.useState)(""),[S,q]=(0,o.useState)("all"),[L,_]=(0,o.useState)(""),[J,H]=(0,o.useState)("all"),[D,Te]=(0,o.useState)("all"),[ue,se]=(0,o.useState)(""),[F,M]=(0,o.useState)(()=>{try{let h=new URLSearchParams(typeof location<"u"?location.search:"").get("tab"),n={cards:"cards",graph:"graph",usage:"stats",stats:"stats",audit:"audit",recycle:"optimize",optimize:"optimize",config:"config"};if(h&&n[h])return n[h]}catch{}return"cards"}),[$,de]=(0,o.useState)(()=>new Set([F])),Ce=(0,o.useCallback)(h=>{de(n=>n.has(h)?n:new Set(n).add(h)),M(h)},[]),[fe,u]=(0,o.useState)("recent"),[Ie,re]=(0,o.useState)(null),y=(0,o.useRef)(null),Y=(0,o.useRef)(null),[te,ye]=(0,o.useState)(!1),[Z,X]=(0,o.useState)(null),[Fe,Me]=(0,o.useState)(!1),[Qe,De]=(0,o.useState)(!1),[l,T]=(0,o.useState)(!0),[j,Q]=(0,o.useState)(null),[Ne,ge]=(0,o.useState)(0),le=(0,o.useCallback)(()=>ge(h=>h+1),[]),ae=r?.quarantine?.pending??0,he=r?.quarantine?.deleted??0,ve=(0,o.useRef)(null),[c,d]=(0,o.useState)(0),[V,ce]=(0,o.useState)(!1),xe=(0,o.useRef)(null),Be=(0,o.useRef)(null),a=(0,o.useCallback)(async(h=S,n=L,x=D,w={})=>{let E=w.append===!0;try{E?ce(!0):(C(!0),I(""));let U=new URLSearchParams;h&&h!=="all"&&U.set("kind",h),n.trim()&&U.set("q",n.trim());let O=w.tag!==void 0?w.tag:ue;O&&U.set("tag",O),U.set("status",x||"all"),U.set("limit",String(Ca)),U.set("offset",String(w.offset||0)),fe==="title"&&U.set("sort","title");let we=await(await fetch(`${K}/cards?${U.toString()}`)).json();if(we.ok){let Ae=we.cards||[];N(Pe=>E?[...Pe,...Ae.filter(wt=>!Pe.some(St=>St.path===wt.path))]:Ae),d(Number(we.total)||Ae.length)}}catch(U){I(String(U&&U.message?U.message:U))}finally{E?ce(!1):C(!1)}},[S,L,D,fe,ue]),ze=(0,o.useCallback)(async()=>{try{let[h,n]=await Promise.all([fetch(`${K}/overview`).then(x=>x.json()),fetch(`${K}/capture-log`).then(x=>x.json()).catch(()=>null)]);h.ok&&v(h),g(n&&n.ok&&n.available&&n.health||null)}catch(h){I(String(h&&h.message?h.message:h))}finally{C(!1)}},[]),nt=async h=>{try{let x=await(await fetch(`${K}/delete?path=${encodeURIComponent(h)}`)).json();x.ok?(re({ok:!0,msg:t("deleted")}),await ze(),le()):re({ok:!1,msg:x.error||t("deleteFail")})}catch{re({ok:!1,msg:t("deleteFail")})}y.current&&clearTimeout(y.current),y.current=setTimeout(()=>re(null),2e3)};(0,o.useEffect)(()=>(ze(),()=>{ve.current&&clearTimeout(ve.current)}),[ze]);let lt=h=>{_(h),ve.current&&clearTimeout(ve.current),ve.current=setTimeout(()=>a(S,h,D),280)},We=h=>{q(h),a(h,L,D)},qe=h=>{let n=String(h||"");se(n),X(null),a(S,L,D,{tag:n})};(0,o.useEffect)(()=>{F==="cards"&&a(S,L,D)},[D,F,Ne,fe,ue]),(0,o.useEffect)(()=>{if(F!=="cards"||P||V||R.length>=c)return;let h=xe.current,n=Be.current;if(!h)return;let x=new IntersectionObserver(w=>{w[0]&&w[0].isIntersecting&&a(S,L,D,{append:!0,offset:R.length})},{root:n||null,rootMargin:"400px"});return x.observe(h),()=>x.disconnect()},[F,R,c,P,V,S,L,D,a]);let ot=async h=>{try{let n=h.status;if(!n&&h.path)try{let O=((await fetch(`${K}/cards?status=all&q=${encodeURIComponent(h.title||"")}&limit=50`).then(ee=>ee.json())).cards||[]).find(ee=>ee.path===h.path);O&&(n=O.status)}catch{}let x=n&&n!=="approved"?`&status=${encodeURIComponent(n)}`:"",w=await fetch(`${K}/card?path=${encodeURIComponent(h.path||h.id)}${x}`),E=await w.json();E.ok?X({path:E.path,title:h.title,text:E.text}):I(E.error||`HTTP ${w.status}`)}catch(n){I(String(n&&n.message?n.message:n))}},Ge=async(h,n)=>{ye(!0);try{let x=await fetch(`${K}/export`);if(!x.ok)throw new Error("HTTP "+x.status);let w=await x.json();if(!w.ok)throw new Error(w.error||"export failed");let E,U,O;if(h==="json"){U="memory-vault.json",O="application/json";let Ae=w.cards.map(Pe=>({path:Pe.path,title:Pe.title,kind:Pe.kind,status:Pe.status,store:Pe.store,text:Pe.text}));E=JSON.stringify({format:w.format||"memory-eternal-vault",formatVersion:w.formatVersion||1,exportedAt:w.exportedAt||new Date().toISOString(),count:Ae.length,cards:Ae},null,2)}else U="memory-vault.md",O="text/markdown;charset=utf-8",E=w.cards.map(Ae=>Ae.text.trim()).filter(Boolean).join(`
350
+ `;function Ta(t){t.effect(()=>t.locale.register(pt,{zh:ta,en:aa}),"memory-eternal: locale");let i=t.locale.bind(pt);t.effect(()=>t.slots.inject("settings.section",()=>t.slots.register({name:"settings.section",id:pt,order:25,label:()=>i("nav"),locale:pt,inject:()=>({})},()=>o.default.createElement(za,{t:i}))),"memory-eternal: settings section"),t.effect(()=>t.slots.inject("sidebar.footer.action",()=>t.slots.register({name:"sidebar.footer.action",id:`${pt}:footer`,order:100,label:()=>i("nav"),locale:pt,inject:()=>({})},s=>o.default.createElement(Ia,{t:i,locale:t.locale,wide:!(s&&s.wide===!1)}))),"memory-eternal: sidebar footer action")}function Da(){let[t,i]=(0,o.useState)(K+"/ui/app");return(0,o.useEffect)(()=>{let s=!0,m=()=>fetch("/memory-eternal/api/web-info").then(p=>p.json()).then(p=>{s&&p&&p.ok&&p.alive!==!1&&p.url&&i(p.url.endsWith("/")?p.url:p.url+"/")}).catch(()=>{});return fetch(K+"/ui/app",{method:"HEAD"}).then(p=>{if(s&&!p.ok)return m()}).catch(()=>s?m():void 0),()=>{s=!1}},[]),t}function za({t}){return(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0},children:[(0,e.jsx)("style",{children:Ee}),(0,e.jsx)(ra,{t,onReload:()=>{},version:0})]})}function Ia({t,locale:i,wide:s}){let[m,p]=(0,o.useState)(!1);return(0,e.jsxs)("div",{className:`me-footer${s?"":" rail"}`,children:[(0,e.jsx)("style",{children:Ee}),(0,e.jsxs)("button",{type:"button",className:"me-footer-btn",onClick:()=>p(!0),"aria-label":t("nav"),title:t("nav"),children:[(0,e.jsx)("span",{className:"me-footer-ico","aria-hidden":"true",children:(0,e.jsx)(oa,{})}),(0,e.jsx)("span",{className:"me-footer-label",children:t("nav")})]}),m&&(0,e.jsx)(Ba,{t,locale:i,onClose:()=>p(!1)})]})}function Ba({t,locale:i,onClose:s,tab:m}){let[p,r]=(0,o.useState)(!1),v=(0,o.useRef)(null),R=Da(),N=(0,o.useCallback)(I=>i&&typeof i.subscribe=="function"?i.subscribe(I):()=>{},[i]),P=(0,o.useCallback)(()=>i&&typeof i.getSnapshot=="function"?i.getSnapshot():null,[i]),C=(0,o.useSyncExternalStore)(N,P),k=C&&(C.active==="zh"||C.active==="en")?C.active:null,g=(0,o.useRef)(null);g.current===null&&k&&(g.current=k);let B=(0,o.useMemo)(()=>{let I=g.current?`lang=${g.current}`:"";if(m==="config")return`${K}/ui/config?tab=config${I?`&${I}`:""}`;let S=R.replace(/\/$/,""),q=S.endsWith("/ui/app")?"?":"/?";return m?`${S}${q}tab=${m}${I?`&${I}`:""}`:I?`${S}${q}${I}`:R},[R,m]);return(0,o.useEffect)(()=>{let I=v.current&&v.current.contentWindow;I&&k&&I.postMessage({source:"memory-eternal",type:"locale",lang:k},"*")},[k]),(0,o.useEffect)(()=>{let I=S=>{S.key==="Escape"&&s()};return window.addEventListener("keydown",I),()=>window.removeEventListener("keydown",I)},[s]),(0,e.jsxs)("div",{className:"me-overlay-top",onClick:s,children:[(0,e.jsx)("style",{children:Ee}),(0,e.jsx)("div",{className:"me-modal"+(p?" me-modal-full":""),onClick:I=>I.stopPropagation(),children:(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0,position:"relative"},children:[(0,e.jsxs)("div",{style:{position:"absolute",top:8,right:8,zIndex:10,display:"flex",gap:4,padding:"4px 6px",background:"var(--dsw-alias-bg-overlay, rgba(17,24,39,0.45))",borderRadius:8,backdropFilter:"blur(4px)"},children:[(0,e.jsx)("button",{type:"button",onClick:()=>r(I=>!I),"aria-label":t(p?"exitFullscreen":"enterFullscreen"),title:t(p?"exitFullscreen":"enterFullscreen"),style:{width:26,height:26,borderRadius:6,border:"1px solid rgba(255,255,255,0.18)",background:"rgba(255,255,255,0.08)",color:"#fff",cursor:"pointer",fontSize:14,lineHeight:1,display:"flex",alignItems:"center",justifyContent:"center"},children:p?"\u2921":"\u2922"}),(0,e.jsx)("button",{type:"button",onClick:s,"aria-label":t("close"),title:t("close"),style:{width:26,height:26,borderRadius:6,border:"1px solid rgba(255,255,255,0.18)",background:"rgba(255,255,255,0.08)",color:"#fff",cursor:"pointer",fontSize:16,lineHeight:1,display:"flex",alignItems:"center",justifyContent:"center"},children:"\xD7"})]}),(0,e.jsx)("iframe",{ref:v,src:B,title:"memory-eternal",allow:"clipboard-write; clipboard-read",style:{width:"100%",flex:1,border:0,borderRadius:"inherit",background:"var(--dsw-alias-bg-base, #fff)"}})]})})]})}function oa(){return(0,e.jsxs)("svg",{viewBox:"0 0 24 24","aria-hidden":"true",children:[(0,e.jsx)("defs",{children:(0,e.jsxs)("linearGradient",{id:"mg-brain",x1:"0",y1:"0",x2:"1",y2:"1",children:[(0,e.jsx)("stop",{offset:"0%",stopColor:"#FFC46B"}),(0,e.jsx)("stop",{offset:"50%",stopColor:"#F97316"}),(0,e.jsx)("stop",{offset:"100%",stopColor:"#EA580C"})]})}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 0-6.4.1 4.1 4.1 0 0 0-2.7 5.9 4.1 4.1 0 0 0 .6 6.7A4.2 4.2 0 0 0 12 18.2Z"}),(0,e.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 1 6.4.1 4.1 4.1 0 0 1 2.7 5.9 4.1 4.1 0 0 1-.6 6.7A4.2 4.2 0 0 1 12 18.2Z"}),(0,e.jsx)("path",{d:"M12 9.2c-.8-1.4-1.7-2-3.1-2.2M12 9.2c.8-1.4 1.7-2 3.1-2.2M12 12.4c-1.2 1-3.2 1.6-5 1.4M12 12.4c1.2 1 3.2 1.6 5 1.4M12 13.2v4",fill:"none",stroke:"rgba(255,255,255,0.62)",strokeWidth:"1",strokeLinecap:"round"}),(0,e.jsx)("circle",{cx:"12",cy:"8.6",r:"1.1",fill:"rgba(255,255,255,0.85)"})]})}var vt="0.10.52";function La({t,inModal:i,onClose:s,onFull:m,full:p}){let[r,v]=(0,o.useState)(null),[R,N]=(0,o.useState)([]),[P,C]=(0,o.useState)(!0),[k,g]=(0,o.useState)(null),[B,I]=(0,o.useState)(""),[S,q]=(0,o.useState)("all"),[L,_]=(0,o.useState)(""),[J,H]=(0,o.useState)("all"),[D,Te]=(0,o.useState)("all"),[ue,se]=(0,o.useState)(""),[F,M]=(0,o.useState)(()=>{try{let h=new URLSearchParams(typeof location<"u"?location.search:"").get("tab"),n={cards:"cards",graph:"graph",usage:"stats",stats:"stats",audit:"audit",recycle:"optimize",optimize:"optimize",config:"config"};if(h&&n[h])return n[h]}catch{}return"cards"}),[$,de]=(0,o.useState)(()=>new Set([F])),Ce=(0,o.useCallback)(h=>{de(n=>n.has(h)?n:new Set(n).add(h)),M(h)},[]),[fe,u]=(0,o.useState)("recent"),[Ie,re]=(0,o.useState)(null),y=(0,o.useRef)(null),Y=(0,o.useRef)(null),[te,ye]=(0,o.useState)(!1),[Z,X]=(0,o.useState)(null),[Fe,Me]=(0,o.useState)(!1),[Qe,De]=(0,o.useState)(!1),[l,T]=(0,o.useState)(!0),[j,Q]=(0,o.useState)(null),[Ne,ge]=(0,o.useState)(0),le=(0,o.useCallback)(()=>ge(h=>h+1),[]),ae=r?.quarantine?.pending??0,he=r?.quarantine?.deleted??0,ve=(0,o.useRef)(null),[c,d]=(0,o.useState)(0),[V,ce]=(0,o.useState)(!1),xe=(0,o.useRef)(null),Be=(0,o.useRef)(null),a=(0,o.useCallback)(async(h=S,n=L,x=D,w={})=>{let E=w.append===!0;try{E?ce(!0):(C(!0),I(""));let U=new URLSearchParams;h&&h!=="all"&&U.set("kind",h),n.trim()&&U.set("q",n.trim());let O=w.tag!==void 0?w.tag:ue;O&&U.set("tag",O),U.set("status",x||"all"),U.set("limit",String(Ca)),U.set("offset",String(w.offset||0)),fe==="title"&&U.set("sort","title");let we=await(await fetch(`${K}/cards?${U.toString()}`)).json();if(we.ok){let Ae=we.cards||[];N(Pe=>E?[...Pe,...Ae.filter(wt=>!Pe.some(St=>St.path===wt.path))]:Ae),d(Number(we.total)||Ae.length)}}catch(U){I(String(U&&U.message?U.message:U))}finally{E?ce(!1):C(!1)}},[S,L,D,fe,ue]),ze=(0,o.useCallback)(async()=>{try{let[h,n]=await Promise.all([fetch(`${K}/overview`).then(x=>x.json()),fetch(`${K}/capture-log`).then(x=>x.json()).catch(()=>null)]);h.ok&&v(h),g(n&&n.ok&&n.available&&n.health||null)}catch(h){I(String(h&&h.message?h.message:h))}finally{C(!1)}},[]),nt=async h=>{try{let x=await(await fetch(`${K}/delete?path=${encodeURIComponent(h)}`)).json();x.ok?(re({ok:!0,msg:t("deleted")}),await ze(),le()):re({ok:!1,msg:x.error||t("deleteFail")})}catch{re({ok:!1,msg:t("deleteFail")})}y.current&&clearTimeout(y.current),y.current=setTimeout(()=>re(null),2e3)};(0,o.useEffect)(()=>(ze(),()=>{ve.current&&clearTimeout(ve.current)}),[ze]);let lt=h=>{_(h),ve.current&&clearTimeout(ve.current),ve.current=setTimeout(()=>a(S,h,D),280)},We=h=>{q(h),a(h,L,D)},qe=h=>{let n=String(h||"");se(n),X(null),a(S,L,D,{tag:n})};(0,o.useEffect)(()=>{F==="cards"&&a(S,L,D)},[D,F,Ne,fe,ue]),(0,o.useEffect)(()=>{if(F!=="cards"||P||V||R.length>=c)return;let h=xe.current,n=Be.current;if(!h)return;let x=new IntersectionObserver(w=>{w[0]&&w[0].isIntersecting&&a(S,L,D,{append:!0,offset:R.length})},{root:n||null,rootMargin:"400px"});return x.observe(h),()=>x.disconnect()},[F,R,c,P,V,S,L,D,a]);let ot=async h=>{try{let n=h.status;if(!n&&h.path)try{let O=((await fetch(`${K}/cards?status=all&q=${encodeURIComponent(h.title||"")}&limit=50`).then(ee=>ee.json())).cards||[]).find(ee=>ee.path===h.path);O&&(n=O.status)}catch{}let x=n&&n!=="approved"?`&status=${encodeURIComponent(n)}`:"",w=await fetch(`${K}/card?path=${encodeURIComponent(h.path||h.id)}${x}`),E=await w.json();E.ok?X({path:E.path,title:h.title,text:E.text}):I(E.error||`HTTP ${w.status}`)}catch(n){I(String(n&&n.message?n.message:n))}},Ge=async(h,n)=>{ye(!0);try{let x=await fetch(`${K}/export`);if(!x.ok)throw new Error("HTTP "+x.status);let w=await x.json();if(!w.ok)throw new Error(w.error||"export failed");let E,U,O;if(h==="json"){U="memory-vault.json",O="application/json";let Ae=w.cards.map(Pe=>({path:Pe.path,title:Pe.title,kind:Pe.kind,status:Pe.status,store:Pe.store,text:Pe.text}));E=JSON.stringify({format:w.format||"memory-eternal-vault",formatVersion:w.formatVersion||1,exportedAt:w.exportedAt||new Date().toISOString(),count:Ae.length,cards:Ae},null,2)}else U="memory-vault.md",O="text/markdown;charset=utf-8",E=w.cards.map(Ae=>Ae.text.trim()).filter(Boolean).join(`
351
351
 
352
352
  ---
353
353
 
package/lib/support.js CHANGED
@@ -17,9 +17,12 @@ export function redactText(text, { home = os.homedir() } = {}) {
17
17
  let s = String(text ?? '')
18
18
  const h = String(home || '')
19
19
  if (h) {
20
- // 同时处理 Windows 反斜杠与正斜杠两种写法
21
- s = s.split(h).join('<home>')
22
- s = s.split(h.replace(/\\/g, '/')).join('<home>')
20
+ // 三种写法都要替换:原样、正斜杠、**JSON 转义**(C:\\Users\\x 在 JSON 文本里是 C:\\\\Users\\\\x)。
21
+ // 最后一种是最容易漏的:doctor/align 的 --json 输出、meta.json 都是转义形态
22
+ // (实测:只替换原样时,包里仍能 grep 到家目录)。
23
+ for (const variant of [h.replace(/\\/g, '\\\\'), h.replace(/\\/g, '/'), h]) {
24
+ if (variant) s = s.split(variant).join('<home>')
25
+ }
23
26
  }
24
27
  s = s.replace(/\bsk-[A-Za-z0-9_-]{12,}/g, 'sk-***')
25
28
  s = s.replace(/\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}/g, 'gh*_***')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-eternal",
3
- "version": "0.10.51",
3
+ "version": "0.10.52",
4
4
  "description": "记忆核心(Memory Eternal):自研的 DeepSeek Harness 记忆插件,不移植任何既有记忆框架——对话结束后自动沉淀知识卡到本地 Markdown Vault(自研去重、自研 CJK 检索、可 git 管理),设置页提供图形化知识库(统计 / 搜索 / 知识图谱 + 侧边栏一键弹窗),Agent 通过 memory_recall 工具按需召回历史上下文。零人工干预。",
5
5
  "license": "MIT",
6
6
  "repository": {
package/web/app.js CHANGED
@@ -349,7 +349,7 @@ Option A, because:
349
349
  height: calc(100vh - var(--dsh-frame-overlay-top, 68px));
350
350
  max-height: calc(100vh - var(--dsh-frame-overlay-top, 68px));
351
351
  }
352
- `;function Gg(){return(0,i.jsxs)("svg",{viewBox:"0 0 24 24","aria-hidden":"true",children:[(0,i.jsx)("defs",{children:(0,i.jsxs)("linearGradient",{id:"mg-brain",x1:"0",y1:"0",x2:"1",y2:"1",children:[(0,i.jsx)("stop",{offset:"0%",stopColor:"#FFC46B"}),(0,i.jsx)("stop",{offset:"50%",stopColor:"#F97316"}),(0,i.jsx)("stop",{offset:"100%",stopColor:"#EA580C"})]})}),(0,i.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 0-6.4.1 4.1 4.1 0 0 0-2.7 5.9 4.1 4.1 0 0 0 .6 6.7A4.2 4.2 0 0 0 12 18.2Z"}),(0,i.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 1 6.4.1 4.1 4.1 0 0 1 2.7 5.9 4.1 4.1 0 0 1-.6 6.7A4.2 4.2 0 0 1 12 18.2Z"}),(0,i.jsx)("path",{d:"M12 9.2c-.8-1.4-1.7-2-3.1-2.2M12 9.2c.8-1.4 1.7-2 3.1-2.2M12 12.4c-1.2 1-3.2 1.6-5 1.4M12 12.4c1.2 1 3.2 1.6 5 1.4M12 13.2v4",fill:"none",stroke:"rgba(255,255,255,0.62)",strokeWidth:"1",strokeLinecap:"round"}),(0,i.jsx)("circle",{cx:"12",cy:"8.6",r:"1.1",fill:"rgba(255,255,255,0.85)"})]})}var Xo="0.10.51";function lf({t:e,inModal:t,onClose:n,onFull:r,full:o}){let[l,a]=(0,x.useState)(null),[s,c]=(0,x.useState)([]),[p,y]=(0,x.useState)(!0),[v,g]=(0,x.useState)(null),[C,M]=(0,x.useState)(""),[S,U]=(0,x.useState)("all"),[d,u]=(0,x.useState)(""),[h,k]=(0,x.useState)("all"),[E,W]=(0,x.useState)("all"),[O,B]=(0,x.useState)(""),[j,R]=(0,x.useState)(()=>{try{let L=new URLSearchParams(typeof location<"u"?location.search:"").get("tab"),m={cards:"cards",graph:"graph",usage:"stats",stats:"stats",audit:"audit",recycle:"optimize",optimize:"optimize",config:"config"};if(L&&m[L])return m[L]}catch{}return"cards"}),[K,ge]=(0,x.useState)(()=>new Set([j])),_e=(0,x.useCallback)(L=>{ge(m=>m.has(L)?m:new Set(m).add(L)),R(L)},[]),[Se,z]=(0,x.useState)("recent"),[Xe,pe]=(0,x.useState)(null),P=(0,x.useRef)(null),ee=(0,x.useRef)(null),[ae,Ce]=(0,x.useState)(!1),[re,ne]=(0,x.useState)(null),[ct,He]=(0,x.useState)(!1),[un,Ve]=(0,x.useState)(!1),[w,A]=(0,x.useState)(!0),[Q,oe]=(0,x.useState)(null),[Fe,we]=(0,x.useState)(0),me=(0,x.useCallback)(()=>we(L=>L+1),[]),se=l?.quarantine?.pending??0,Me=l?.quarantine?.deleted??0,Te=(0,x.useRef)(null),[N,b]=(0,x.useState)(0),[J,he]=(0,x.useState)(!1),Re=(0,x.useRef)(null),Ze=(0,x.useRef)(null),f=(0,x.useCallback)(async(L=S,m=d,_=E,F={})=>{let X=F.append===!0;try{X?he(!0):(y(!0),M(""));let q=new URLSearchParams;L&&L!=="all"&&q.set("kind",L),m.trim()&&q.set("q",m.trim());let Y=F.tag!==void 0?F.tag:O;Y&&q.set("tag",Y),q.set("status",_||"all"),q.set("limit",String(Qg)),q.set("offset",String(F.offset||0)),Se==="title"&&q.set("sort","title");let De=await(await fetch(`${Z}/cards?${q.toString()}`)).json();if(De.ok){let rt=De.cards||[];c(mt=>X?[...mt,...rt.filter(Zo=>!mt.some(Jo=>Jo.path===Zo.path))]:rt),b(Number(De.total)||rt.length)}}catch(q){M(String(q&&q.message?q.message:q))}finally{X?he(!1):y(!1)}},[S,d,E,Se,O]),Ke=(0,x.useCallback)(async()=>{try{let[L,m]=await Promise.all([fetch(`${Z}/overview`).then(_=>_.json()),fetch(`${Z}/capture-log`).then(_=>_.json()).catch(()=>null)]);L.ok&&a(L),g(m&&m.ok&&m.available&&m.health||null)}catch(L){M(String(L&&L.message?L.message:L))}finally{y(!1)}},[]),Kn=async L=>{try{let _=await(await fetch(`${Z}/delete?path=${encodeURIComponent(L)}`)).json();_.ok?(pe({ok:!0,msg:e("deleted")}),await Ke(),me()):pe({ok:!1,msg:_.error||e("deleteFail")})}catch{pe({ok:!1,msg:e("deleteFail")})}P.current&&clearTimeout(P.current),P.current=setTimeout(()=>pe(null),2e3)};(0,x.useEffect)(()=>(Ke(),()=>{Te.current&&clearTimeout(Te.current)}),[Ke]);let fr=L=>{u(L),Te.current&&clearTimeout(Te.current),Te.current=setTimeout(()=>f(S,L,E),280)},Tt=L=>{U(L),f(L,d,E)},Wt=L=>{let m=String(L||"");B(m),ne(null),f(S,d,E,{tag:m})};(0,x.useEffect)(()=>{j==="cards"&&f(S,d,E)},[E,j,Fe,Se,O]),(0,x.useEffect)(()=>{if(j!=="cards"||p||J||s.length>=N)return;let L=Re.current,m=Ze.current;if(!L)return;let _=new IntersectionObserver(F=>{F[0]&&F[0].isIntersecting&&f(S,d,E,{append:!0,offset:s.length})},{root:m||null,rootMargin:"400px"});return _.observe(L),()=>_.disconnect()},[j,s,N,p,J,S,d,E,f]);let Qn=async L=>{try{let m=L.status;if(!m&&L.path)try{let Y=((await fetch(`${Z}/cards?status=all&q=${encodeURIComponent(L.title||"")}&limit=50`).then(le=>le.json())).cards||[]).find(le=>le.path===L.path);Y&&(m=Y.status)}catch{}let _=m&&m!=="approved"?`&status=${encodeURIComponent(m)}`:"",F=await fetch(`${Z}/card?path=${encodeURIComponent(L.path||L.id)}${_}`),X=await F.json();X.ok?ne({path:X.path,title:L.title,text:X.text}):M(X.error||`HTTP ${F.status}`)}catch(m){M(String(m&&m.message?m.message:m))}},Zt=async(L,m)=>{Ce(!0);try{let _=await fetch(`${Z}/export`);if(!_.ok)throw new Error("HTTP "+_.status);let F=await _.json();if(!F.ok)throw new Error(F.error||"export failed");let X,q,Y;if(L==="json"){q="memory-vault.json",Y="application/json";let rt=F.cards.map(mt=>({path:mt.path,title:mt.title,kind:mt.kind,status:mt.status,store:mt.store,text:mt.text}));X=JSON.stringify({format:F.format||"memory-eternal-vault",formatVersion:F.formatVersion||1,exportedAt:F.exportedAt||new Date().toISOString(),count:rt.length,cards:rt},null,2)}else q="memory-vault.md",Y="text/markdown;charset=utf-8",X=F.cards.map(rt=>rt.text.trim()).filter(Boolean).join(`
352
+ `;function Gg(){return(0,i.jsxs)("svg",{viewBox:"0 0 24 24","aria-hidden":"true",children:[(0,i.jsx)("defs",{children:(0,i.jsxs)("linearGradient",{id:"mg-brain",x1:"0",y1:"0",x2:"1",y2:"1",children:[(0,i.jsx)("stop",{offset:"0%",stopColor:"#FFC46B"}),(0,i.jsx)("stop",{offset:"50%",stopColor:"#F97316"}),(0,i.jsx)("stop",{offset:"100%",stopColor:"#EA580C"})]})}),(0,i.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 0-6.4.1 4.1 4.1 0 0 0-2.7 5.9 4.1 4.1 0 0 0 .6 6.7A4.2 4.2 0 0 0 12 18.2Z"}),(0,i.jsx)("path",{fill:"url(#mg-brain)",d:"M12 4.5a3.2 3.2 0 0 1 6.4.1 4.1 4.1 0 0 1 2.7 5.9 4.1 4.1 0 0 1-.6 6.7A4.2 4.2 0 0 1 12 18.2Z"}),(0,i.jsx)("path",{d:"M12 9.2c-.8-1.4-1.7-2-3.1-2.2M12 9.2c.8-1.4 1.7-2 3.1-2.2M12 12.4c-1.2 1-3.2 1.6-5 1.4M12 12.4c1.2 1 3.2 1.6 5 1.4M12 13.2v4",fill:"none",stroke:"rgba(255,255,255,0.62)",strokeWidth:"1",strokeLinecap:"round"}),(0,i.jsx)("circle",{cx:"12",cy:"8.6",r:"1.1",fill:"rgba(255,255,255,0.85)"})]})}var Xo="0.10.52";function lf({t:e,inModal:t,onClose:n,onFull:r,full:o}){let[l,a]=(0,x.useState)(null),[s,c]=(0,x.useState)([]),[p,y]=(0,x.useState)(!0),[v,g]=(0,x.useState)(null),[C,M]=(0,x.useState)(""),[S,U]=(0,x.useState)("all"),[d,u]=(0,x.useState)(""),[h,k]=(0,x.useState)("all"),[E,W]=(0,x.useState)("all"),[O,B]=(0,x.useState)(""),[j,R]=(0,x.useState)(()=>{try{let L=new URLSearchParams(typeof location<"u"?location.search:"").get("tab"),m={cards:"cards",graph:"graph",usage:"stats",stats:"stats",audit:"audit",recycle:"optimize",optimize:"optimize",config:"config"};if(L&&m[L])return m[L]}catch{}return"cards"}),[K,ge]=(0,x.useState)(()=>new Set([j])),_e=(0,x.useCallback)(L=>{ge(m=>m.has(L)?m:new Set(m).add(L)),R(L)},[]),[Se,z]=(0,x.useState)("recent"),[Xe,pe]=(0,x.useState)(null),P=(0,x.useRef)(null),ee=(0,x.useRef)(null),[ae,Ce]=(0,x.useState)(!1),[re,ne]=(0,x.useState)(null),[ct,He]=(0,x.useState)(!1),[un,Ve]=(0,x.useState)(!1),[w,A]=(0,x.useState)(!0),[Q,oe]=(0,x.useState)(null),[Fe,we]=(0,x.useState)(0),me=(0,x.useCallback)(()=>we(L=>L+1),[]),se=l?.quarantine?.pending??0,Me=l?.quarantine?.deleted??0,Te=(0,x.useRef)(null),[N,b]=(0,x.useState)(0),[J,he]=(0,x.useState)(!1),Re=(0,x.useRef)(null),Ze=(0,x.useRef)(null),f=(0,x.useCallback)(async(L=S,m=d,_=E,F={})=>{let X=F.append===!0;try{X?he(!0):(y(!0),M(""));let q=new URLSearchParams;L&&L!=="all"&&q.set("kind",L),m.trim()&&q.set("q",m.trim());let Y=F.tag!==void 0?F.tag:O;Y&&q.set("tag",Y),q.set("status",_||"all"),q.set("limit",String(Qg)),q.set("offset",String(F.offset||0)),Se==="title"&&q.set("sort","title");let De=await(await fetch(`${Z}/cards?${q.toString()}`)).json();if(De.ok){let rt=De.cards||[];c(mt=>X?[...mt,...rt.filter(Zo=>!mt.some(Jo=>Jo.path===Zo.path))]:rt),b(Number(De.total)||rt.length)}}catch(q){M(String(q&&q.message?q.message:q))}finally{X?he(!1):y(!1)}},[S,d,E,Se,O]),Ke=(0,x.useCallback)(async()=>{try{let[L,m]=await Promise.all([fetch(`${Z}/overview`).then(_=>_.json()),fetch(`${Z}/capture-log`).then(_=>_.json()).catch(()=>null)]);L.ok&&a(L),g(m&&m.ok&&m.available&&m.health||null)}catch(L){M(String(L&&L.message?L.message:L))}finally{y(!1)}},[]),Kn=async L=>{try{let _=await(await fetch(`${Z}/delete?path=${encodeURIComponent(L)}`)).json();_.ok?(pe({ok:!0,msg:e("deleted")}),await Ke(),me()):pe({ok:!1,msg:_.error||e("deleteFail")})}catch{pe({ok:!1,msg:e("deleteFail")})}P.current&&clearTimeout(P.current),P.current=setTimeout(()=>pe(null),2e3)};(0,x.useEffect)(()=>(Ke(),()=>{Te.current&&clearTimeout(Te.current)}),[Ke]);let fr=L=>{u(L),Te.current&&clearTimeout(Te.current),Te.current=setTimeout(()=>f(S,L,E),280)},Tt=L=>{U(L),f(L,d,E)},Wt=L=>{let m=String(L||"");B(m),ne(null),f(S,d,E,{tag:m})};(0,x.useEffect)(()=>{j==="cards"&&f(S,d,E)},[E,j,Fe,Se,O]),(0,x.useEffect)(()=>{if(j!=="cards"||p||J||s.length>=N)return;let L=Re.current,m=Ze.current;if(!L)return;let _=new IntersectionObserver(F=>{F[0]&&F[0].isIntersecting&&f(S,d,E,{append:!0,offset:s.length})},{root:m||null,rootMargin:"400px"});return _.observe(L),()=>_.disconnect()},[j,s,N,p,J,S,d,E,f]);let Qn=async L=>{try{let m=L.status;if(!m&&L.path)try{let Y=((await fetch(`${Z}/cards?status=all&q=${encodeURIComponent(L.title||"")}&limit=50`).then(le=>le.json())).cards||[]).find(le=>le.path===L.path);Y&&(m=Y.status)}catch{}let _=m&&m!=="approved"?`&status=${encodeURIComponent(m)}`:"",F=await fetch(`${Z}/card?path=${encodeURIComponent(L.path||L.id)}${_}`),X=await F.json();X.ok?ne({path:X.path,title:L.title,text:X.text}):M(X.error||`HTTP ${F.status}`)}catch(m){M(String(m&&m.message?m.message:m))}},Zt=async(L,m)=>{Ce(!0);try{let _=await fetch(`${Z}/export`);if(!_.ok)throw new Error("HTTP "+_.status);let F=await _.json();if(!F.ok)throw new Error(F.error||"export failed");let X,q,Y;if(L==="json"){q="memory-vault.json",Y="application/json";let rt=F.cards.map(mt=>({path:mt.path,title:mt.title,kind:mt.kind,status:mt.status,store:mt.store,text:mt.text}));X=JSON.stringify({format:F.format||"memory-eternal-vault",formatVersion:F.formatVersion||1,exportedAt:F.exportedAt||new Date().toISOString(),count:rt.length,cards:rt},null,2)}else q="memory-vault.md",Y="text/markdown;charset=utf-8",X=F.cards.map(rt=>rt.text.trim()).filter(Boolean).join(`
353
353
 
354
354
  ---
355
355