memory-eternal 0.10.15 → 0.10.16
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.en.md +3 -2
- package/README.md +3 -2
- package/bin/dsh-memory.mjs +4 -2
- package/lib/client.js +1 -1
- package/lib/watchdog.js +26 -1
- package/package.json +1 -1
- package/web/app.js +1 -1
|
@@ -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.
|
|
4
|
+
"version": "0.10.16",
|
|
5
5
|
"description": "记忆核心(Memory Eternal):对话结束后自动沉淀知识卡到本地记忆库(~/.dsh/memory-vault),支持检索 / 召回 / 审核中心。",
|
|
6
6
|
"author": {
|
|
7
7
|
"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.16** | 2026-10-09 | **`--reap` no longer says "nothing was reaped" when it did reap something** (reported by alario-tang, measured on macOS with 0.10.13). The scene: after `kill -9` on the watchdog leaves a `PPID=1` orphan web, `dsh-memory watchdog --reap --port 7999` really freed the port and really killed the web, yet the screen read "**scanned 1 watchdog process, cleaned 0 orphans**" — because 0.10.13 made `scanned` include webs while `webKilled` was never printed. Fix: a pure `formatReapSummary()` unifies the wording (scan counts split into `watchdog A / web B`, cleaned entries listed per kind with pids), `reapStaleWatchdogs` gains `scannedWatchdogs` / `scannedWebs` (`scanned` stays their sum for older callers), and the CLI uses it. Two checks added (never say "cleaned 0 orphans" when a web was reaped / scan counts must be split), and `tests/watchdog-orphan-web.test.mjs` now records the reproduction note: **such an orphan can only be created with `kill -9` — SIGTERM runs the watchdog's graceful exit, which also stops the web it spawned** (the same coin as #19's "on Windows SIGTERM is unconditional termination"). `npm test` now runs **308 checks** |
|
|
364
365
|
| **v0.10.15** | 2026-10-09 | **Three loose ends left by the upgrades.** ① **Recommended plan A no longer reinstalls the call we just saved**: v0.10.12 raised the default distiller output cap to 4000 (removing the wasted "hit 2000, then re-run the same candidate at 4000"), but the panel one-click preset "A (light)" still hard-coded `captureMaxTokens: 2000` — one click brought the retry back; it now matches the default, guarded by a source check (`tests/csrf-origin.test.mjs` asserts planA cap === default). ② **The README "stall watchdog" description now matches the new rule** (both languages): the old wording said "if no turn-stopping event arrives for 15 minutes", while it is actually "**zero stops since this process started** plus turns still starting inside the last 20 minutes → record a `warn`", and it now states that an in-flight **long turn is never mistaken for a stall**. ③ While at it, the other log wording `轮次收尾事件未触发(agent/turn-stopping 没到)` was verified to have last fired on **2026-09-24** — a historical remnant of the old heuristic with no such code path left today (nothing to fix). `npm test` now runs **306 checks** |
|
|
365
|
-
| **v0.10.14** | 2026-10-09 | **The "auto-capture anomaly: turn-stop event may be broken" false alarm is fixed at the root.** The scene: during a long turn lasting tens of minutes, `agent/inbox/claimed` fires **repeatedly inside one turn**, while `turn-stopping` only arrives when the turn really ends — so any 20-minute window necessarily shows "≥3 starts, 0 stops", and the check mistook an **in-flight** turn for a renamed event, pushing a red alert at the user (the "39/18" line measured in this session came from exactly that). Fix: one hard precondition — "the event was renamed/removed" is a **process-lifetime** property, not a window property: if this run has seen even one stop, the wiring works and 0 stops in the window merely means the current turn is still running (`evaluateStall` gains `everStopped` and reports `reason: wiring-proven | no-stop-ever`); only "zero stops since this process started **and** continuous starts inside the window" alerts. At the same time the alert is downgraded from `fail` to `warn` — an inference no longer paints capture health red nor masquerades as an auto-capture anomaly (real failures still turn it red on their own). Three checks in `tests/stall.test.mjs` lock the old and new behaviour (long turn stays quiet / a real rename alerts / `everStopped` injectable). `npm test` now runs **305 checks** |
|
|
366
366
|
|
|
367
367
|
<details>
|
|
368
|
-
<summary>Older release notes (v0.10.
|
|
368
|
+
<summary>Older release notes (v0.10.14 and earlier · 41 releases, click to expand)</summary>
|
|
369
369
|
|
|
370
370
|
| Version | Date | Highlights |
|
|
371
371
|
|---|---|---|
|
|
372
|
+
| **v0.10.14** | 2026-10-09 | **The "auto-capture anomaly: turn-stop event may be broken" false alarm is fixed at the root.** The scene: during a long turn lasting tens of minutes, `agent/inbox/claimed` fires **repeatedly inside one turn**, while `turn-stopping` only arrives when the turn really ends — so any 20-minute window necessarily shows "≥3 starts, 0 stops", and the check mistook an **in-flight** turn for a renamed event, pushing a red alert at the user (the "39/18" line measured in this session came from exactly that). Fix: one hard precondition — "the event was renamed/removed" is a **process-lifetime** property, not a window property: if this run has seen even one stop, the wiring works and 0 stops in the window merely means the current turn is still running (`evaluateStall` gains `everStopped` and reports `reason: wiring-proven | no-stop-ever`); only "zero stops since this process started **and** continuous starts inside the window" alerts. At the same time the alert is downgraded from `fail` to `warn` — an inference no longer paints capture health red nor masquerades as an auto-capture anomaly (real failures still turn it red on their own). Three checks in `tests/stall.test.mjs` lock the old and new behaviour (long turn stays quiet / a real rename alerts / `everStopped` injectable). `npm test` now runs **305 checks** |
|
|
372
373
|
| **v0.10.13** | 2026-10-09 | **Orphan `web.js` processes are finally reachable (#11 item 1, the POSIX branch)**. The scene (alario-tang, macOS arm64): the watchdog died first and the lock file was gone, leaving a stale web on port 7999 with `PPID=1`; `stop`/`restart` read lock slots and found no target, `watchdog --reap` only matched `watchdog.js`, and the panel button rides the same path — so drift could be reported but never healed, and the user had to `kill` by hand. Two concrete gaps: ① **on POSIX the port occupant only came back as a process name** — `lsof -Fpcn` yields `node` for the `c` field rather than a command line, while the "should we reap it" test (`looksLikeOurWeb`) requires `memory-eternal` or this install web.js absolute path in the command line → always false → the fallback loops in `stopWatchdogs` / `clearResidentForTakeover` **never killed anything** (Windows uses `Get-CimInstance`, which returns the full command line, so this box never showed it); now a follow-up `ps -p <pid> -o args=` fills in the command line, falling back to the old behaviour when unavailable (better to miss than to misfire), with an injectable `platform` for tests; ② **`--reap` now covers our own webs**: a `listWebs` branch reaps "same port + our web + **its keeper is gone** (that slot watchdog is not alive) + outside the protected set (keepPid / current pid / parent / a `--reap` sibling)", while **a web whose keeper is alive is never touched**, and other ports or foreign `web.js` processes are never killed. Adds `tests/watchdog-orphan-web.test.mjs` (5 checks) and makes two watchdog-only cases state `listWebs: []` explicitly (otherwise reap enumerates the real process table). `npm test` now runs **302 checks** |
|
|
373
374
|
| **v0.10.12** | 2026-10-09 | **Two quiet things that either burn money or leave a hole every day.** ① **Default distiller output cap: 2000 → 4000 (the hard ceiling)**: the logs kept showing "hit the maxTokens=2000 cap → retry the same candidate at 4000", and each occurrence **wasted one LLM call** (hit the cap, then re-run the same candidate at 4000). `maxTokens` is only a ceiling and billing follows what is actually generated, so the default now sits at the hard ceiling: those turns finish in one call; `maxTokenLadder` stays for users who **explicitly** lower `captureMaxTokens`. ② **A cross-site write gate on the plugin API**: `/memory-eternal/api/*` is unauthenticated, so any local web page could POST `/config`, `/audit/approve`, `/write`… New `crossOriginError()` — state-changing requests (non-GET/HEAD/OPTIONS) must be **same-origin with the Host of the request itself**: browsers always send `Origin` on cross-site requests (a mismatch returns 403 `CROSS_ORIGIN_BLOCKED`), while same-origin pages (DSH settings/memory page, the standalone page) and LAN access stay unaffected, curl / CLI / MCP calls without `Origin` keep working, and desktop-shell schemes like `app://` are allowed so nothing breaks. Both the intercepted host routes (`/config` POST, `/restart-self`, `/mcp/action`…) and the shared api entry carry the gate. Adds `tests/csrf-origin.test.mjs` (4 checks: decision matrix incl. LAN / no Origin / null / app://, a cross-site POST getting 403 without reaching business logic, both entry points present, and the default cap equal to the hard ceiling with no ladder retry). `npm test` now runs **297 checks** |
|
|
374
375
|
| **v0.10.11** | 2026-10-09 | **One more layer of tolerance in distiller-output parsing: unescaped quotes inside string values (`Expected ',' or '}' after property value`)**. The scene: `UNPARSEABLE_OUTPUT` dominates the `fail` log (103 of the last 600 entries); the freshest one carries the full picture — `JSON.parse 原始报错:Expected ',' or '}' after property value in JSON at position 1913`, with an excerpt whose body reads `运行 "npm test" 时…`: the model wrote a quote into the string as content, so the parser took it for the string terminator. That is a **different** class from the bare control characters fixed in #24 (a control character is illegal inside a string, a quote merely needs escaping). Fix: new `repairUnescapedQuotes()` — a `"` only ends the string when the next non-space character is `:`, `,`, `}`, `]` or end-of-text, otherwise it is escaped as content; it participates **only as the last candidate** and the result must still pass the card health check (usable title + body ≥20 chars) — better to fail than to write a bad card. Guarded by `tests/capture-json-repair.test.mjs` (4 checks: identity on valid JSON, the live unescaped-quote case, control chars + quotes combined, truncation still failing honestly). `npm test` now runs **293 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.16** | 2026-10-09 | **`--reap` 的输出不再「明明收了却说没收」**(alario-tang 反馈,macOS + 0.10.13 实测)。现场:`kill -9` 掉 watchdog 造出 `PPID=1` 的孤儿 web 后跑 `dsh-memory watchdog --reap --port 7999`,端口确实被释放、web 确实被收掉,屏幕上却写着「**扫描到 1 个 watchdog 进程,清理孤儿 0 个**」—— 因为 0.10.13 让 `scanned` 含了 web、而 `webKilled` 一个字都没打印。修法:新增纯函数 `formatReapSummary()` 统一口径(扫描数拆成 `watchdog A / web B`、清理项按类分别列出 pid),`reapStaleWatchdogs` 返回值补 `scannedWatchdogs` / `scannedWebs`(`scanned` 仍是两者之和,兼容旧调用方),CLI 改用它。测试补 2 项(收了 web 时不许再说「清理孤儿 0 个」/ 扫描计数必须拆开),并在 `tests/watchdog-orphan-web.test.mjs` 头部记下复现要点:**这类孤儿只能用 `kill -9` 造 —— SIGTERM 会走 watchdog 的优雅退出、把它拉起的 web 一起停掉**(与 #19 里「Windows 上 SIGTERM 即无条件终止」是同一枚硬币的两面)。`npm test` 共 **308 项** |
|
|
396
397
|
| **v0.10.15** | 2026-10-09 | **三处「升级留下的尾巴」**。① **推荐方案 A 不再把刚省掉的调用装回来**:v0.10.12 把「蒸馏输出上限」默认提到 4000(省掉「先撞 2000、再以 4000 重跑同一候选」那次白烧),但面板里「一键推荐配置 A(轻量)」仍写死 `captureMaxTokens: 2000` —— 用户点一下就又把重试装回来;现已与默认对齐,并加源码守卫(`tests/csrf-origin.test.mjs` 断言 planA 的上限 === 默认值)。② **README 的「停滞兜底」描述与新判据对齐**(中英两份):旧文案写「15 分钟收不到收尾事件就报」,现在是「**本次运行至今 0 收尾** + 最近 20 分钟内持续有轮次开始 → 记一条 `warn`」,并写明正在进行的**长回合不会被误判**。③ 顺带核实日志里另一种措辞 `轮次收尾事件未触发(agent/turn-stopping 没到)` 最后出现于 **2026-09-24**,属于旧判据的历史残留,现行代码里已无这条路径(无需修复)。`npm test` 共 **306 项** |
|
|
397
|
-
| **v0.10.14** | 2026-10-09 | **「自动沉淀异常:轮次收尾事件疑似失效」误报根治**。现场:几十分钟的长回合里,`agent/inbox/claimed` 在**一轮对话中途**也会多次触发,而 `turn-stopping` 要等回合真的结束才来 —— 于是 20 分钟窗口必然「开始 ≥3、收尾 0」,判据把**还在进行中**的轮次当成「事件被改名」,直接把红警报推给用户(本会话实测那条「累计 39/18」就是这么来的)。修法:加一条硬前提 —— 「事件被改名/移除」是**进程生命周期**属性,不是窗口属性:本次运行里只要收到过哪怕一次收尾,通路就是好的,窗口内 0 收尾只说明当前轮次还没结束(`evaluateStall` 新增 `everStopped`,并在返回值里给出 `reason: wiring-proven | no-stop-ever`);只有「本次运行至今 **0** 收尾 + 窗口内持续开始」才报警。同时把这条**推断**从 `fail` 降级为 `warn` —— 不再由它把沉淀健康状态翻红、更不冒充「自动沉淀异常」(真故障会由写卡失败自己报红)。`tests/stall.test.mjs` 新增 3 项锁住新旧行为(长回合不报 / 真改名要报 / `everStopped` 可注入)。`npm test` 共 **305 项** |
|
|
398
398
|
|
|
399
399
|
<details>
|
|
400
|
-
<summary>更早的更新日志(v0.10.
|
|
400
|
+
<summary>更早的更新日志(v0.10.14 及以前 · 共 41 版,点开查看)</summary>
|
|
401
401
|
|
|
402
402
|
| 版本 | 日期 | 关键改动 |
|
|
403
403
|
|---|---|---|
|
|
404
|
+
| **v0.10.14** | 2026-10-09 | **「自动沉淀异常:轮次收尾事件疑似失效」误报根治**。现场:几十分钟的长回合里,`agent/inbox/claimed` 在**一轮对话中途**也会多次触发,而 `turn-stopping` 要等回合真的结束才来 —— 于是 20 分钟窗口必然「开始 ≥3、收尾 0」,判据把**还在进行中**的轮次当成「事件被改名」,直接把红警报推给用户(本会话实测那条「累计 39/18」就是这么来的)。修法:加一条硬前提 —— 「事件被改名/移除」是**进程生命周期**属性,不是窗口属性:本次运行里只要收到过哪怕一次收尾,通路就是好的,窗口内 0 收尾只说明当前轮次还没结束(`evaluateStall` 新增 `everStopped`,并在返回值里给出 `reason: wiring-proven | no-stop-ever`);只有「本次运行至今 **0** 收尾 + 窗口内持续开始」才报警。同时把这条**推断**从 `fail` 降级为 `warn` —— 不再由它把沉淀健康状态翻红、更不冒充「自动沉淀异常」(真故障会由写卡失败自己报红)。`tests/stall.test.mjs` 新增 3 项锁住新旧行为(长回合不报 / 真改名要报 / `everStopped` 可注入)。`npm test` 共 **305 项** |
|
|
404
405
|
| **v0.10.13** | 2026-10-09 | **孤儿 `web.js` 终于收得掉了(#11「待做 1」的 POSIX 分支)**。现场(alario-tang,macOS arm64):watchdog 先死 + 锁文件丢失 → 端口 7999 上留着一个 `PPID=1` 的旧 web;`stop`/`restart` 读锁槽找不到目标、`watchdog --reap` 只认 `watchdog.js`、面板按钮又走同一条链路 → 漂移只能报不能自愈,用户被迫手动 `kill`。查出**两个具体缺口**:① **POSIX 侧端口占用者只拿到进程名** —— `lsof -Fpcn` 的 `c` 字段是 `node` 而不是命令行,而「要不要收」的判据(`looksLikeOurWeb`)要求命令行含 `memory-eternal` 或本机 `web.js` 绝对路径 → 判据恒假 → `stopWatchdogs` / `clearResidentForTakeover` 的兜底循环**一次都不杀**(Windows 走 `Get-CimInstance` 拿完整命令行,所以本机从未暴露);现在补一次 `ps -p <pid> -o args=`,拿不到才退回旧行为(宁可不杀、也不误杀),并让 `platform` 可注入以便单测。② **`--reap` 扩到自家 web**:新增 `listWebs` 分支,把「同端口 + 是自家 web + **托管者已死**(该槽 watchdog 不存活)+ 不在保护集合(keepPid / 当前 pid / 父进程 / 带 `--reap` 的同类)」的 web 一并回收;**托管者还活着的 web 一律不动**,别的端口、别人的 `web.js` 一个不杀。新增 `tests/watchdog-orphan-web.test.mjs`(5 项),并把两条只谈 watchdog 的旧用例显式声明 `listWebs: []`(否则 reap 会去列本机真实进程表)。`npm test` 共 **302 项** |
|
|
405
406
|
| **v0.10.12** | 2026-10-09 | **两件「不显眼但每天都在扣钱 / 留洞」的事**。① **蒸馏输出上限默认 2000 → 4000(= 硬顶)**:日志实测高频出现「输出撞到 maxTokens=2000 上限 → 以 4000 重试同一候选」,每一次都是**白烧一次 LLM 调用**(先撞顶、再拿 4000 重跑同一个候选)。maxTokens 只是上限、按实际生成量计费,所以默认直接放到硬顶:同样的轮次一次调用就够;`maxTokenLadder` 仍保留给**显式**把 `captureMaxTokens` 调小的用户。② **插件 API 加跨站写入闸门**:`/memory-eternal/api/*` 无鉴权,本机任意网页都能 POST `/config`、`/audit/approve`、`/write`… 新增 `crossOriginError()` —— 状态变更请求(非 GET/HEAD/OPTIONS)必须与请求自身的 `Host` **同源**:浏览器跨站请求一定带 `Origin`(与 Host 不同源即 403 `CROSS_ORIGIN_BLOCKED`);同源页面(DSH 设置页/记忆页、独立页)与局域网访问都同源不受影响;没有 Origin 的 curl / CLI / MCP 照常放行;`app://` 这类桌面壳自定义 scheme 也放行以免误伤。宿主自己的拦截路由(`/config` POST、`/restart-self`、`/mcp/action`…)与共享 api 入口**都**装了这道闸门。新增 `tests/csrf-origin.test.mjs`(4 项:判定矩阵含局域网 / 无 Origin / null / app://、跨站 POST 被 403 且不进业务逻辑、两处入口都在、默认上限=硬顶且无 ladder 重试)。`npm test` 共 **297 项** |
|
|
406
407
|
| **v0.10.11** | 2026-10-09 | **蒸馏输出解析再补一层容错:字符串里的裸引号(`Expected ',' or '}' after property value`)**。现场:`fail` 日志里 `UNPARSEABLE_OUTPUT` 占大头(最近 600 条里 103 条),最近一条带全了现场 —— `JSON.parse 原始报错:Expected ',' or '}' after property value in JSON at position 1913`,摘录里能看到正文写着 `运行 "npm test" 时…`:模型把引号当正文直接写进字符串,于是它被当成字符串结束符。这与 #24 修过的「裸控制字符」是**不同**的一类(控制字符在字符串内非法,引号只是需要转义)。修法:新增 `repairUnescapedQuotes()` —— 一个 `"` 后面(跳过空白)紧跟 `:` `,` `}` `]` 或文本结尾才算字符串结束,否则当正文补 `\"`;它**只作为最后一个候选**参与解析,且必须仍通过卡片体检(标题合格 + 正文 ≥20 字)才被接受 —— 宁可不修,也不写坏卡。回归守卫 `tests/capture-json-repair.test.mjs`(4 项:合法 JSON 恒等变换、裸引号现场、控制字符与裸引号叠加、截断仍如实失败)。`npm test` 共 **293 项** |
|
package/bin/dsh-memory.mjs
CHANGED
|
@@ -292,12 +292,14 @@ MCP 侧只提供只读的 memory_audit_list —— 批准/驳回必须由人显
|
|
|
292
292
|
return
|
|
293
293
|
}
|
|
294
294
|
case 'watchdog': {
|
|
295
|
-
const { startWatchdog, reapStaleWatchdogs } = await import('../lib/watchdog.js')
|
|
295
|
+
const { startWatchdog, reapStaleWatchdogs, formatReapSummary } = await import('../lib/watchdog.js')
|
|
296
296
|
const { defaultVaultDir } = await import('../lib/capture-run.js')
|
|
297
297
|
const port = Number(argOf('--port')) || 7999
|
|
298
298
|
if (has('--reap')) {
|
|
299
299
|
const out = await reapStaleWatchdogs({ port, keepPid: 0 })
|
|
300
|
-
|
|
300
|
+
// 计数口径与文案必须一起看齐:scanned 含 web、清理要分开报 watchdog / web(0.10.13 的文案漏报
|
|
301
|
+
// webKilled,读者会把「真收了一个 web」读成「什么都没收」)
|
|
302
|
+
console.log(formatReapSummary(out))
|
|
301
303
|
return
|
|
302
304
|
}
|
|
303
305
|
const interval = Number(argOf('--interval')) || 5000
|
package/lib/client.js
CHANGED
|
@@ -298,7 +298,7 @@ Option A, because:
|
|
|
298
298
|
height: calc(100vh - var(--dsh-windows-titlebar-height, 0px));
|
|
299
299
|
max-height: calc(100vh - var(--dsh-windows-titlebar-height, 0px));
|
|
300
300
|
}
|
|
301
|
-
`;function Sa(t){t.effect(()=>t.locale.register(lt,{zh:Xt,en:Jt}),"memory-eternal: locale");let o=t.locale.bind(lt);t.effect(()=>t.slots.inject("settings.section",()=>t.slots.register({name:"settings.section",id:lt,order:25,label:()=>o("nav"),locale:lt,inject:()=>({})},()=>n.default.createElement(Na,{t:o}))),"memory-eternal: settings section"),t.effect(()=>t.slots.inject("sidebar.footer.action",()=>t.slots.register({name:"sidebar.footer.action",id:`${lt}:footer`,order:100,label:()=>o("nav"),locale:lt,inject:()=>({})},r=>n.default.createElement(Ma,{t:o,locale:t.locale,wide:!(r&&r.wide===!1)}))),"memory-eternal: sidebar footer action")}function Ca(){let[t,o]=(0,n.useState)(_+"/ui/app");return(0,n.useEffect)(()=>{let r=!0,m=()=>fetch("/memory-eternal/api/web-info").then(d=>d.json()).then(d=>{r&&d&&d.ok&&d.alive!==!1&&d.url&&o(d.url.endsWith("/")?d.url:d.url+"/")}).catch(()=>{});return fetch(_+"/ui/app",{method:"HEAD"}).then(d=>{if(r&&!d.ok)return m()}).catch(()=>r?m():void 0),()=>{r=!1}},[]),t}function Na({t}){return(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0},children:[(0,e.jsx)("style",{children:Fe}),(0,e.jsx)(ta,{t,onReload:()=>{},version:0})]})}function Ma({t,locale:o,wide:r}){let[m,d]=(0,n.useState)(!1);return(0,e.jsxs)("div",{className:`me-footer${r?"":" rail"}`,children:[(0,e.jsx)("style",{children:Fe}),(0,e.jsxs)("button",{type:"button",className:"me-footer-btn",onClick:()=>d(!0),"aria-label":t("nav"),title:t("nav"),children:[(0,e.jsx)("span",{className:"me-footer-ico","aria-hidden":"true",children:(0,e.jsx)(Qt,{})}),(0,e.jsx)("span",{className:"me-footer-label",children:t("nav")})]}),m&&(0,e.jsx)(Da,{t,locale:o,onClose:()=>d(!1)})]})}function Da({t,locale:o,onClose:r,tab:m}){let[d,i]=(0,n.useState)(!1),w=(0,n.useRef)(null),C=Ca(),x=(0,n.useCallback)(I=>o&&typeof o.subscribe=="function"?o.subscribe(I):()=>{},[o]),A=(0,n.useCallback)(()=>o&&typeof o.getSnapshot=="function"?o.getSnapshot():null,[o]),k=(0,n.useSyncExternalStore)(x,A),h=k&&(k.active==="zh"||k.active==="en")?k.active:null,u=(0,n.useRef)(null);u.current===null&&h&&(u.current=h);let D=(0,n.useMemo)(()=>{let I=u.current?`lang=${u.current}`:"";if(m==="config")return`${_}/ui/config?tab=config${I?`&${I}`:""}`;let v=C.replace(/\/$/,""),T=v.endsWith("/ui/app")?"?":"/?";return m?`${v}${T}tab=${m}${I?`&${I}`:""}`:I?`${v}${T}${I}`:C},[C,m]);return(0,n.useEffect)(()=>{let I=w.current&&w.current.contentWindow;I&&h&&I.postMessage({source:"memory-eternal",type:"locale",lang:h},"*")},[h]),(0,n.useEffect)(()=>{let I=v=>{v.key==="Escape"&&r()};return window.addEventListener("keydown",I),()=>window.removeEventListener("keydown",I)},[r]),(0,e.jsxs)("div",{className:"me-overlay-top",onClick:r,children:[(0,e.jsx)("style",{children:Fe}),(0,e.jsx)("div",{className:"me-modal"+(d?" 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:()=>i(I=>!I),"aria-label":t(d?"exitFullscreen":"enterFullscreen"),title:t(d?"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:d?"\u2921":"\u2922"}),(0,e.jsx)("button",{type:"button",onClick:r,"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:w,src:D,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 Qt(){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 bt="0.10.
|
|
301
|
+
`;function Sa(t){t.effect(()=>t.locale.register(lt,{zh:Xt,en:Jt}),"memory-eternal: locale");let o=t.locale.bind(lt);t.effect(()=>t.slots.inject("settings.section",()=>t.slots.register({name:"settings.section",id:lt,order:25,label:()=>o("nav"),locale:lt,inject:()=>({})},()=>n.default.createElement(Na,{t:o}))),"memory-eternal: settings section"),t.effect(()=>t.slots.inject("sidebar.footer.action",()=>t.slots.register({name:"sidebar.footer.action",id:`${lt}:footer`,order:100,label:()=>o("nav"),locale:lt,inject:()=>({})},r=>n.default.createElement(Ma,{t:o,locale:t.locale,wide:!(r&&r.wide===!1)}))),"memory-eternal: sidebar footer action")}function Ca(){let[t,o]=(0,n.useState)(_+"/ui/app");return(0,n.useEffect)(()=>{let r=!0,m=()=>fetch("/memory-eternal/api/web-info").then(d=>d.json()).then(d=>{r&&d&&d.ok&&d.alive!==!1&&d.url&&o(d.url.endsWith("/")?d.url:d.url+"/")}).catch(()=>{});return fetch(_+"/ui/app",{method:"HEAD"}).then(d=>{if(r&&!d.ok)return m()}).catch(()=>r?m():void 0),()=>{r=!1}},[]),t}function Na({t}){return(0,e.jsxs)("div",{style:{display:"flex",flexDirection:"column",height:"100%",minHeight:0},children:[(0,e.jsx)("style",{children:Fe}),(0,e.jsx)(ta,{t,onReload:()=>{},version:0})]})}function Ma({t,locale:o,wide:r}){let[m,d]=(0,n.useState)(!1);return(0,e.jsxs)("div",{className:`me-footer${r?"":" rail"}`,children:[(0,e.jsx)("style",{children:Fe}),(0,e.jsxs)("button",{type:"button",className:"me-footer-btn",onClick:()=>d(!0),"aria-label":t("nav"),title:t("nav"),children:[(0,e.jsx)("span",{className:"me-footer-ico","aria-hidden":"true",children:(0,e.jsx)(Qt,{})}),(0,e.jsx)("span",{className:"me-footer-label",children:t("nav")})]}),m&&(0,e.jsx)(Da,{t,locale:o,onClose:()=>d(!1)})]})}function Da({t,locale:o,onClose:r,tab:m}){let[d,i]=(0,n.useState)(!1),w=(0,n.useRef)(null),C=Ca(),x=(0,n.useCallback)(I=>o&&typeof o.subscribe=="function"?o.subscribe(I):()=>{},[o]),A=(0,n.useCallback)(()=>o&&typeof o.getSnapshot=="function"?o.getSnapshot():null,[o]),k=(0,n.useSyncExternalStore)(x,A),h=k&&(k.active==="zh"||k.active==="en")?k.active:null,u=(0,n.useRef)(null);u.current===null&&h&&(u.current=h);let D=(0,n.useMemo)(()=>{let I=u.current?`lang=${u.current}`:"";if(m==="config")return`${_}/ui/config?tab=config${I?`&${I}`:""}`;let v=C.replace(/\/$/,""),T=v.endsWith("/ui/app")?"?":"/?";return m?`${v}${T}tab=${m}${I?`&${I}`:""}`:I?`${v}${T}${I}`:C},[C,m]);return(0,n.useEffect)(()=>{let I=w.current&&w.current.contentWindow;I&&h&&I.postMessage({source:"memory-eternal",type:"locale",lang:h},"*")},[h]),(0,n.useEffect)(()=>{let I=v=>{v.key==="Escape"&&r()};return window.addEventListener("keydown",I),()=>window.removeEventListener("keydown",I)},[r]),(0,e.jsxs)("div",{className:"me-overlay-top",onClick:r,children:[(0,e.jsx)("style",{children:Fe}),(0,e.jsx)("div",{className:"me-modal"+(d?" 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:()=>i(I=>!I),"aria-label":t(d?"exitFullscreen":"enterFullscreen"),title:t(d?"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:d?"\u2921":"\u2922"}),(0,e.jsx)("button",{type:"button",onClick:r,"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:w,src:D,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 Qt(){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 bt="0.10.16";function za({t,inModal:o,onClose:r,onFull:m,full:d}){let[i,w]=(0,n.useState)(null),[C,x]=(0,n.useState)([]),[A,k]=(0,n.useState)(!0),[h,u]=(0,n.useState)(null),[D,I]=(0,n.useState)(""),[v,T]=(0,n.useState)("all"),[N,j]=(0,n.useState)(""),[Q,xe]=(0,n.useState)("all"),[q,Re]=(0,n.useState)("all"),[U,me]=(0,n.useState)(()=>{try{let b=new URLSearchParams(typeof location<"u"?location.search:"").get("tab"),P={cards:"cards",graph:"graph",usage:"stats",stats:"stats",audit:"audit",recycle:"optimize",optimize:"optimize",config:"config"};if(b&&P[b])return P[b]}catch{}return"cards"}),[le,F]=(0,n.useState)(()=>new Set([U])),y=(0,n.useCallback)(b=>{F(P=>P.has(b)?P:new Set(P).add(b)),me(b)},[]),[H,M]=(0,n.useState)("recent"),[ne,K]=(0,n.useState)(null),X=(0,n.useRef)(null),re=(0,n.useRef)(null),[f,W]=(0,n.useState)(!1),[de,Y]=(0,n.useState)(null),[ie,we]=(0,n.useState)(!1),[Ae,Me]=(0,n.useState)(!1),[ve,Ce]=(0,n.useState)(!0),[et,Ze]=(0,n.useState)(null),[Ve,nt]=(0,n.useState)(0),tt=(0,n.useCallback)(()=>nt(b=>b+1),[]),Te=i?.quarantine?.pending??0,Be=i?.quarantine?.deleted??0,pe=(0,n.useRef)(null),[Ye,Ne]=(0,n.useState)(0),[c,s]=(0,n.useState)(!1),E=(0,n.useRef)(null),se=(0,n.useRef)(null),$=(0,n.useCallback)(async(b=v,P=N,Z=q,ke={})=>{let G=ke.append===!0;try{G?s(!0):(k(!0),I(""));let he=new URLSearchParams;b&&b!=="all"&&he.set("kind",b),P.trim()&&he.set("q",P.trim()),he.set("status",Z||"all"),he.set("limit",String(va)),he.set("offset",String(ke.offset||0)),H==="title"&&he.set("sort","title");let Ie=await(await fetch(`${_}/cards?${he.toString()}`)).json();if(Ie.ok){let Ue=Ie.cards||[];x(He=>G?[...He,...Ue.filter(Ke=>!He.some(vt=>vt.path===Ke.path))]:Ue),Ne(Number(Ie.total)||Ue.length)}}catch(he){I(String(he&&he.message?he.message:he))}finally{G?s(!1):k(!1)}},[v,N,q,H]),ge=(0,n.useCallback)(async()=>{try{let[b,P]=await Promise.all([fetch(`${_}/overview`).then(Z=>Z.json()),fetch(`${_}/capture-log`).then(Z=>Z.json()).catch(()=>null)]);b.ok&&w(b),u(P&&P.ok&&P.available&&P.health||null)}catch(b){I(String(b&&b.message?b.message:b))}finally{k(!1)}},[]),a=async b=>{try{let Z=await(await fetch(`${_}/delete?path=${encodeURIComponent(b)}`)).json();Z.ok?(K({ok:!0,msg:t("deleted")}),await ge(),tt()):K({ok:!1,msg:Z.error||t("deleteFail")})}catch{K({ok:!1,msg:t("deleteFail")})}X.current&&clearTimeout(X.current),X.current=setTimeout(()=>K(null),2e3)};(0,n.useEffect)(()=>(ge(),()=>{pe.current&&clearTimeout(pe.current)}),[ge]);let l=b=>{j(b),pe.current&&clearTimeout(pe.current),pe.current=setTimeout(()=>$(v,b,q),280)},L=b=>{T(b),$(b,N,q)};(0,n.useEffect)(()=>{U==="cards"&&$(v,N,q)},[q,U,Ve,H]),(0,n.useEffect)(()=>{if(U!=="cards"||A||c||C.length>=Ye)return;let b=E.current,P=se.current;if(!b)return;let Z=new IntersectionObserver(ke=>{ke[0]&&ke[0].isIntersecting&&$(v,N,q,{append:!0,offset:C.length})},{root:P||null,rootMargin:"400px"});return Z.observe(b),()=>Z.disconnect()},[U,C,Ye,A,c,v,N,q,$]);let B=async b=>{try{let P=b.status;if(!P&&b.path)try{let ze=((await fetch(`${_}/cards?status=all&q=${encodeURIComponent(b.title||"")}&limit=50`).then(Ie=>Ie.json())).cards||[]).find(Ie=>Ie.path===b.path);ze&&(P=ze.status)}catch{}let Z=P&&P!=="approved"?`&status=${encodeURIComponent(P)}`:"",ke=await fetch(`${_}/card?path=${encodeURIComponent(b.path||b.id)}${Z}`),G=await ke.json();G.ok?Y({path:G.path,title:b.title,text:G.text}):I(G.error||`HTTP ${ke.status}`)}catch(P){I(String(P&&P.message?P.message:P))}},J=async(b,P)=>{W(!0);try{let Z=await fetch(`${_}/export`);if(!Z.ok)throw new Error("HTTP "+Z.status);let ke=await Z.json();if(!ke.ok)throw new Error(ke.error||"export failed");let G,he,ze;if(b==="json"){he="memory-vault.json",ze="application/json";let He=ke.cards.map(Ke=>({path:Ke.path,title:Ke.title,kind:Ke.kind,status:Ke.status,store:Ke.store,text:Ke.text}));G=JSON.stringify({format:ke.format||"memory-eternal-vault",formatVersion:ke.formatVersion||1,exportedAt:ke.exportedAt||new Date().toISOString(),count:He.length,cards:He},null,2)}else he="memory-vault.md",ze="text/markdown;charset=utf-8",G=ke.cards.map(He=>He.text.trim()).filter(Boolean).join(`
|
|
302
302
|
|
|
303
303
|
---
|
|
304
304
|
|
package/lib/watchdog.js
CHANGED
|
@@ -377,7 +377,32 @@ export async function reapStaleWatchdogs({ port = 7999, keepPid = process.pid, e
|
|
|
377
377
|
if (!isPidAlive(w.pid)) { skipped.push(w.pid); continue }
|
|
378
378
|
if (killFn(w.pid)) webKilled.push(w.pid); else skipped.push(w.pid)
|
|
379
379
|
}
|
|
380
|
-
return { scanned: procs.length + webs.length, killed, webKilled, skipped }
|
|
380
|
+
return { scanned: procs.length + webs.length, scannedWatchdogs: procs.length, scannedWebs: webs.length, killed, webKilled, skipped }
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* `--reap` 的输出摘要(纯函数,便于单测)。
|
|
385
|
+
*
|
|
386
|
+
* 为什么单独抽出来:0.10.13 加了 web 回收,但 CLI 文案还在说「扫描到 N 个 watchdog 进程、
|
|
387
|
+
* 清理孤儿 killed.length 个」—— `scanned` 已经含 web、`webKilled` 又完全没打印,
|
|
388
|
+
* 于是**真收了 web 却显示「清理孤儿 0 个」**(实测反馈:alario-tang 在 macOS 上看到
|
|
389
|
+
* 「扫描到 1 个 watchdog 进程,清理孤儿 0 个」而端口事实上已经释放)。计数口径与文案必须一起改。
|
|
390
|
+
*
|
|
391
|
+
* @param {{scanned?:number, scannedWatchdogs?:number, scannedWebs?:number, killed?:number[], webKilled?:number[], skipped?:number[]}} out
|
|
392
|
+
*/
|
|
393
|
+
export function formatReapSummary(out = {}) {
|
|
394
|
+
const scanned = Number(out.scanned) || 0
|
|
395
|
+
const wd = Number.isFinite(out.scannedWatchdogs) ? Number(out.scannedWatchdogs) : scanned
|
|
396
|
+
const webs = Number.isFinite(out.scannedWebs) ? Number(out.scannedWebs) : 0
|
|
397
|
+
const killed = Array.isArray(out.killed) ? out.killed : []
|
|
398
|
+
const webKilled = Array.isArray(out.webKilled) ? out.webKilled : []
|
|
399
|
+
const skipped = Array.isArray(out.skipped) ? out.skipped : []
|
|
400
|
+
const cleaned = []
|
|
401
|
+
if (killed.length) cleaned.push('watchdog ' + killed.length + ' 个(pid ' + killed.join(', ') + ')')
|
|
402
|
+
if (webKilled.length) cleaned.push('web ' + webKilled.length + ' 个(pid ' + webKilled.join(', ') + ')')
|
|
403
|
+
return '扫描 ' + scanned + ' 个进程(watchdog ' + wd + ' / web ' + webs + '),清理孤儿 '
|
|
404
|
+
+ (cleaned.length ? cleaned.join(' / ') : '0 个')
|
|
405
|
+
+ ',保留 ' + skipped.length + ' 个(锁中登记的活跃实例 / 其它端口 / 无关进程不会被动)'
|
|
381
406
|
}
|
|
382
407
|
|
|
383
408
|
const ts = () => new Date().toISOString().slice(11, 19) // HH:MM:SS
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory-eternal",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.16",
|
|
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
|
@@ -300,7 +300,7 @@ Option A, because:
|
|
|
300
300
|
height: calc(100vh - var(--dsh-windows-titlebar-height, 0px));
|
|
301
301
|
max-height: calc(100vh - var(--dsh-windows-titlebar-height, 0px));
|
|
302
302
|
}
|
|
303
|
-
`;function Vg(){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 Ko="0.10.
|
|
303
|
+
`;function Vg(){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 Ko="0.10.16";function ef({t:e,inModal:t,onClose:n,onFull:r,full:o}){let[l,a]=(0,v.useState)(null),[s,u]=(0,v.useState)([]),[f,y]=(0,v.useState)(!0),[h,m]=(0,v.useState)(null),[k,M]=(0,v.useState)(""),[w,I]=(0,v.useState)("all"),[d,c]=(0,v.useState)(""),[g,N]=(0,v.useState)("all"),[P,j]=(0,v.useState)("all"),[D,O]=(0,v.useState)(()=>{try{let L=new URLSearchParams(typeof location<"u"?location.search:"").get("tab"),V={cards:"cards",graph:"graph",usage:"stats",stats:"stats",audit:"audit",recycle:"optimize",optimize:"optimize",config:"config"};if(L&&V[L])return V[L]}catch{}return"cards"}),[Y,_]=(0,v.useState)(()=>new Set([D])),T=(0,v.useCallback)(L=>{_(V=>V.has(L)?V:new Set(V).add(L)),O(L)},[]),[K,A]=(0,v.useState)("recent"),[ce,Z]=(0,v.useState)(null),re=(0,v.useRef)(null),me=(0,v.useRef)(null),[z,G]=(0,v.useState)(!1),[he,te]=(0,v.useState)(null),[pe,Fe]=(0,v.useState)(!1),[ht,Qe]=(0,v.useState)(!1),[_e,He]=(0,v.useState)(!0),[bn,un]=(0,v.useState)(null),[Gt,Kn]=(0,v.useState)(0),Sn=(0,v.useCallback)(()=>Kn(L=>L+1),[]),ot=l?.quarantine?.pending??0,yt=l?.quarantine?.deleted??0,xe=(0,v.useRef)(null),[qt,We]=(0,v.useState)(0),[S,x]=(0,v.useState)(!1),Q=(0,v.useRef)(null),ge=(0,v.useRef)(null),q=(0,v.useCallback)(async(L=w,V=d,le=P,Ae={})=>{let ne=Ae.append===!0;try{ne?x(!0):(y(!0),M(""));let Le=new URLSearchParams;L&&L!=="all"&&Le.set("kind",L),V.trim()&&Le.set("q",V.trim()),Le.set("status",le||"all"),Le.set("limit",String(Wg)),Le.set("offset",String(Ae.offset||0)),K==="title"&&Le.set("sort","title");let lt=await(await fetch(`${J}/cards?${Le.toString()}`)).json();if(lt.ok){let jt=lt.cards||[];u(Et=>ne?[...Et,...jt.filter(Bt=>!Et.some(Xo=>Xo.path===Bt.path))]:jt),We(Number(lt.total)||jt.length)}}catch(Le){M(String(Le&&Le.message?Le.message:Le))}finally{ne?x(!1):y(!1)}},[w,d,P,K]),ke=(0,v.useCallback)(async()=>{try{let[L,V]=await Promise.all([fetch(`${J}/overview`).then(le=>le.json()),fetch(`${J}/capture-log`).then(le=>le.json()).catch(()=>null)]);L.ok&&a(L),m(V&&V.ok&&V.available&&V.health||null)}catch(L){M(String(L&&L.message?L.message:L))}finally{y(!1)}},[]),p=async L=>{try{let le=await(await fetch(`${J}/delete?path=${encodeURIComponent(L)}`)).json();le.ok?(Z({ok:!0,msg:e("deleted")}),await ke(),Sn()):Z({ok:!1,msg:le.error||e("deleteFail")})}catch{Z({ok:!1,msg:e("deleteFail")})}re.current&&clearTimeout(re.current),re.current=setTimeout(()=>Z(null),2e3)};(0,v.useEffect)(()=>(ke(),()=>{xe.current&&clearTimeout(xe.current)}),[ke]);let b=L=>{c(L),xe.current&&clearTimeout(xe.current),xe.current=setTimeout(()=>q(w,L,P),280)},W=L=>{I(L),q(L,d,P)};(0,v.useEffect)(()=>{D==="cards"&&q(w,d,P)},[P,D,Gt,K]),(0,v.useEffect)(()=>{if(D!=="cards"||f||S||s.length>=qt)return;let L=Q.current,V=ge.current;if(!L)return;let le=new IntersectionObserver(Ae=>{Ae[0]&&Ae[0].isIntersecting&&q(w,d,P,{append:!0,offset:s.length})},{root:V||null,rootMargin:"400px"});return le.observe(L),()=>le.disconnect()},[D,s,qt,f,S,w,d,P,q]);let U=async L=>{try{let V=L.status;if(!V&&L.path)try{let Ze=((await fetch(`${J}/cards?status=all&q=${encodeURIComponent(L.title||"")}&limit=50`).then(lt=>lt.json())).cards||[]).find(lt=>lt.path===L.path);Ze&&(V=Ze.status)}catch{}let le=V&&V!=="approved"?`&status=${encodeURIComponent(V)}`:"",Ae=await fetch(`${J}/card?path=${encodeURIComponent(L.path||L.id)}${le}`),ne=await Ae.json();ne.ok?te({path:ne.path,title:L.title,text:ne.text}):M(ne.error||`HTTP ${Ae.status}`)}catch(V){M(String(V&&V.message?V.message:V))}},oe=async(L,V)=>{G(!0);try{let le=await fetch(`${J}/export`);if(!le.ok)throw new Error("HTTP "+le.status);let Ae=await le.json();if(!Ae.ok)throw new Error(Ae.error||"export failed");let ne,Le,Ze;if(L==="json"){Le="memory-vault.json",Ze="application/json";let Et=Ae.cards.map(Bt=>({path:Bt.path,title:Bt.title,kind:Bt.kind,status:Bt.status,store:Bt.store,text:Bt.text}));ne=JSON.stringify({format:Ae.format||"memory-eternal-vault",formatVersion:Ae.formatVersion||1,exportedAt:Ae.exportedAt||new Date().toISOString(),count:Et.length,cards:Et},null,2)}else Le="memory-vault.md",Ze="text/markdown;charset=utf-8",ne=Ae.cards.map(Et=>Et.text.trim()).filter(Boolean).join(`
|
|
304
304
|
|
|
305
305
|
---
|
|
306
306
|
|