Skip to content

feat(viewer): UI improvements, global search, Hermes daemon mode - #1

Closed
hijzy wants to merge 3 commits into
fix/host-bridge-missingfrom
feat/viewer-improvements-v2
Closed

feat(viewer): UI improvements, global search, Hermes daemon mode#1
hijzy wants to merge 3 commits into
fix/host-bridge-missingfrom
feat/viewer-improvements-v2

Conversation

@hijzy

@hijzy hijzy commented Apr 28, 2026

Copy link
Copy Markdown
Owner

Summary

  • Global search: real-time categorized dropdown showing top 3 results per category (memories, tasks, skills, experiences, env knowledge) as the user types
  • Hermes daemon mode: Memory Viewer now stays alive persistently — install.sh keeps it running, config save auto-restarts via self-respawn (like OpenClaw's launchd)
  • i18n fixes: Help page fully bilingual, team sharing subtitle, task skill pipeline reasons all localized
  • Markdown chat: user/assistant/thinking bubbles in task drawer now render as Markdown
  • Bug fixes: memory_add log content for tool sub-steps, version mismatch (bridge.cts reads package.json), health endpoint reads disk config for model names

Test plan

  • Install via install.sh --version ./pkg.tgz for Hermes — verify daemon stays up after install
  • Open Memory Viewer at :18800 — verify global search dropdown shows results
  • Switch language to English — verify Help page, task skill reasons display in English
  • Open task drawer — verify chat bubbles render markdown (code blocks, bold, links)
  • Modify model config in Settings → Save — verify viewer auto-restarts without manual intervention
  • Run hermes chat while daemon is running — verify it operates in headless mode

jiang added 3 commits April 28, 2026 15:18
- Global search: real-time categorized dropdown (memories, tasks, skills,
  experiences, env knowledge) with top 3 results per category
- Added backend `q` parameter support for skills and episodes APIs
- Help page: full bilingual (en/zh) support for all sections
- Settings: translated team sharing subtitle via i18n
- Tasks page: skill pipeline reasons now localized via reasonKey/reasonParams
- Chat bubbles: render user/assistant/thinking as Markdown (new component)
- Header brand: simplified to "MemOS / 记忆面板"
- Search bar: expanded to fill full topbar width
- memory_add logs: fix empty content for tool sub-steps, fix role inference
- Version: bridge.cts reads from package.json (no more alpha/beta mismatch)
- Health endpoint: read model names from disk config (reflects unsaved changes)
- Admin restart: Hermes bridge now exits on restart (like OpenClaw)
- bridge.cts: implement --daemon flag (pure HTTP, no stdio) so the
  Memory Viewer can run as a standalone daemon process
- admin/restart: Hermes now spawns a fresh daemon bridge before exiting,
  ensuring the viewer port comes back up automatically (like OpenClaw)
- install.sh: keep bridge running after install as a daemon instead of
  killing it after smoke test
- restart.ts: unified restart flow for all agents — both OpenClaw and
  Hermes show spinner overlay + poll-until-up + auto-reload
- admin/restart: exit first to release port, then bash sleeps 1s and
  spawns the new daemon (avoids EADDRINUSE race condition)
- restart.ts: Hermes uses quickPollUp (800ms intervals, 8s max) for
  fast recovery; OpenClaw keeps the slower launchd poll cycle
- install.sh: revert spinner frames to original braille characters
@hijzy hijzy closed this Apr 28, 2026
@hijzy
hijzy deleted the feat/viewer-improvements-v2 branch April 29, 2026 02:43
hijzy pushed a commit that referenced this pull request Aug 24, 2026
…enClaw 2026.3.31 (MemTensor#2037)

* fix(memos-local-openclaw): migrate to OpenClaw 2026.3.31 memory API

OpenClaw 2026.3.31 replaced the umbrella `registerMemoryCapability`
with three focused registrars (`registerMemoryPromptSection`,
`registerMemoryFlushPlan`, `registerMemoryRuntime`), so the plugin's
call at index.ts:163 failed with `TypeError: api.registerMemoryCapability
is not a function` and the whole plugin refused to load.

The register() entry now feature-detects `registerMemoryPromptSection`
and falls back to the legacy `registerMemoryCapability` for older
gateways, warning if neither exists. Tests stub both entry points and a
new tests/memory-registration-api.test.ts locks in the preference order,
the fallback path, and the no-throw safeguard.

Fixes MemTensor#1559

* refactor(memos-local-openclaw): hoist memory-registration API narrow + escalate missing-registrar log to error

Address OpenClaw review feedback on the MemTensor#1559 fix:

* Hoist the inline `{ registerMemoryPromptSection?, registerMemoryCapability? }`
  cast type to a named `MemoryRegistrationApi` interface at module scope so
  future maintainers see which host methods the feature detection covers and
  don't accidentally reach for other `OpenClawPluginApi` members through the
  narrowed reference. (OCR #1, MemTensor#3)

* Escalate the "neither registrar available" log from `warn` to `error` so the
  fatal misconfiguration surfaces immediately in production dashboards rather
  than silently manifesting as broken memory recall. Falls back to `warn` when
  the host logger lacks `.error` for compatibility with legacy `HostLogger`
  shapes. (OCR MemTensor#2, MemTensor#4)

* Expand `tests/memory-registration-api.test.ts` accordingly: assert `error`
  is called on the modern path and add a legacy-logger case that verifies the
  warn fallback still fires when `.error` is absent.

* refactor(memos-local-openclaw): safer casts + fail-fast on missing memory registrar

Address the second round of OpenClaw review feedback on the MemTensor#1559 fix:

* Replace `api as unknown as MemoryRegistrationApi` with the safer
  intersection cast `api as OpenClawPluginApi & MemoryRegistrationApi`.
  This keeps every other typed member of `api` intact and communicates
  the actual intent — "the host may additionally expose these optional
  registrars" — instead of throwing away the whole type. (OCR #1)

* Throw after logging in the "no registrar found" branch. Previously
  register() would keep spinning up stores, workers and tools even
  though the recall path was permanently broken, producing a plugin
  that *looks* healthy while silently never surfacing memories. Now
  the failure is visible at startup and the host can react. (OCR MemTensor#2)

* Hoist the inline `{ error?: (msg: string) => void }` cast on
  `api.logger` into a named `ExtendedLogger` interface alongside
  `MemoryRegistrationApi`, so all remaining type-escape hatches live
  in one place and can be dropped once the SDK publishes matching
  types. (OCR MemTensor#3)

* Update `tests/memory-registration-api.test.ts` accordingly: the
  "neither method" cases now assert both the error/warn log and that
  `register()` throws with a matching message, so the fail-fast
  contract is locked in.

* test(memos-local-openclaw): flush res.end callback + advance past delayMs in update-install test

The mock `res.end(payload)` in the update-install test ignored the optional
flush callback, so `jsonResponseAndRestart` never scheduled its 1500ms
SIGUSR1 setTimeout under `vi.useFakeTimers()`. Also bump the timer advance
from 500ms to 2000ms to cover the actual default `delayMs=1500`.

Fixes: "keeps the new version and restarts only after a successful postinstall"

---------

Co-authored-by: MemOS AutoDev <autodev@memtensor.local>
Co-authored-by: jiachengzhen <jiacz@memtensor.cn>
Co-authored-by: zhaxi <syzsunshine219@gmail.com>
hijzy pushed a commit that referenced this pull request Aug 24, 2026
…us full-table vector scan (scan (MemTensor#2077)

* fix(local-plugin): stop unbounded trace re-insertion + 100% CPU vector scan (MemTensor#2076)

Two coupled root causes made the gateway Node process pin one CPU core
at 100% with 4.2 GB RSS on startup for 40+ minutes on a 518k-row DB.

Bug #1 — synchronous full-table vector scan (`core/storage/vector.ts`):
`scanAndTopK` used `db.prepare(sql).all(params)`, materialising up to
`hardCap` (default 100_000) rows in one synchronous step. Each row
carries a multi-KB vector BLOB, so peak RSS was `O(hardCap × dim)` and
the event loop was blocked while `topKCosine` walked the whole array.
Fix: stream via `.iterate()`, maintain the top-K min-heap on hits
directly (only k entries + one just-decoded vector live in JS memory
at a time), and lower the default `hardCap` to 5_000 as a safer default
(all production callers pass their own value explicitly).

Bug MemTensor#2 — dedup pagination cap (`core/capture/capture.ts`):
The four dedup call sites in `runLite` / `runLightweight` /
`runReflect` / `persistRows` all used `tracesRepo.list({ episodeId })`,
which is paginated and silently truncates to 500 rows via
`_helpers.ts::clampLimit`. Once an episode exceeded 500 traces the
older rows became invisible to dedup and the tail was re-inserted
on every cycle. In the reporter's DB this had grown the `traces`
table to 518_375 rows of which 84% were exact duplicates by
`(episode_id, turn_id, user_text, agent_text, tool_calls_json)`.
Fix: add `tracesRepo.listAllForEpisode(episodeId)`, an uncapped read
ordered by `ts ASC`, and switch the four dedup call sites to it.
Every other paginated `list({ episodeId, limit })` caller keeps its
explicit page-size contract.

Tests
  - `tests/unit/storage/traces-listall.test.ts` — 4 cases: 750-row
    episode returns in full, strict episode scoping, empty episode,
    `ts ASC` ordering.
  - `tests/unit/storage/vector-stream.test.ts` — 3 cases: parity
    with brute-force top-K on live DB, streaming keeps only top-K in
    memory yet still considers every row within the cap, explicit
    `hardCap` still truncates the candidate window.
  - Full plugin unit suite: 1128 pass, 1 skipped. Two pre-existing
    failures on `dev-v2.0.23` (startup-recovery source-string check,
    migrator schema-drift) are unrelated to this fix.

Closes MemTensor#2076

* refactor(local-plugin): apply MemTensor#2077 open code review fixes

Address the five findings from open code review on PR MemTensor#2077:

1. `traces.ts::listAllForEpisode` was hydrating all 25 columns including
   `vec_summary`/`vec_action` BLOBs while dedup callers only ever read
   the identity fields. Added `listDedupRowsForEpisode` — a streaming
   sibling that projects only the five dedup columns
   (ts/turn_id/user_text/agent_text/tool_calls_json) and uses
   `.iterate()` so peak RSS scales with scalar payload rather than
   total embedding footprint. `listAllForEpisode` kept as-is for
   `runReflect`, which still needs the full row (id/tags/vecSummary/
   vecAction).

2. `vector.ts::scanAndTopK` streaming loop silently `continue`d on
   dimension mismatches, losing the `search.dim_mismatch` warning the
   old `topKCosine` path emitted. Restored the warn log with
   {expected,got,rowId} — the primary operator signal for detecting
   schema drift (re-embedding with a new model dimension) in
   production. Empty vectors keep the silent-skip semantics as before.

3. `vector.ts::scanAndTopK` had the meta-object construction
   duplicated verbatim in both heap-push branches. Hoisted into a
   single `buildMeta()` closure above the branch so future edits to
   the projection cannot drift between the two paths.

4. `capture.ts::persistRows` unconditionally called
   `listAllForEpisode` even when its caller (`runLite` /
   `runLightweight` / `runReflect`) had just scanned the same
   episode. Added an optional `existingSignatures?: Set<string>` param;
   all three call-sites now pass the pre-built signature set derived
   from their own dedup scan, eliminating the second full-episode
   scan. The set is cloned inside `persistRows` so the intra-batch
   dedup doesn't leak new signatures back to the caller.

5. `capture.ts::runLite` (and `runLightweight`) previously fetched
   full `TraceRow[]` via `listAllForEpisode` and threw everything
   away except the `ts` / `turnId` field. Switched both to
   `listDedupRowsForEpisode` so no BLOBs load for the extraction
   dedup pass.

`traceIdentitySignature` widened to `Pick<TraceRow, "toolCalls" |
"turnId" | "ts" | "agentText" | "userText">` so it can accept either
`TraceRow` (runReflect path) or the narrow `TraceDedupRow` produced
by the streaming helper.

Tests
  - tests/unit/storage/traces-listall.test.ts — 4 new cases pin the
    narrow-projection contract (fields returned, empty episode,
    750-row uncapped stream, strict episode scoping + ts ASC).
  - tests/unit/storage/vector-stream.test.ts — 2 new cases pin the
    dim-mismatch warning fires for non-empty mismatched vectors and
    stays silent for zero-length vectors.

Verification
  - `npx tsc -p tsconfig.json --noEmit` → clean.
  - `npx vitest run tests/unit/storage/traces-listall.test.ts
     tests/unit/storage/vector-stream.test.ts` → 13/13 pass.
  - `npx vitest run tests/unit/capture` → 110/110 pass.
  - Full `npx vitest run tests/unit` → 1134/1137 pass; the two
    failures (storage/migrator, startup-recovery) reproduce on the
    unmodified PR head, so they are pre-existing and unrelated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(local-plugin): apply MemTensor#2077 open code review fixes (round 2)

Second-pass open-code-review fixes on top of 3e25bc3. All three are
minimal-diff / non-behavior-changing.

1. `vector.ts::scanAndTopK` was building the `buildMeta` closure
   inside the per-row streaming loop. On a hot search over the full
   `hardCap` this allocated one closure per iteration — pure GC
   pressure since only the two heap-push branches consume it.
   Hoisted the projection above the loop as a plain function taking
   the current row as an argument. Two variants pre-selected once
   based on `selectExtra.length` so filtered-out rows still pay
   nothing.

2. `traces.ts::listDedupRowsForEpisode` JSDoc claimed the helper
   "streams via `.iterate()` so peak memory scales with the scalar
   payload". That was misleading: the method uses `stmt.iterate()`
   internally but still accumulates every row into a `TraceDedupRow[]`
   before returning. The genuine saving is the narrow scalar-only
   projection (never touches `vec_summary` / `vec_action`), not
   streaming. Rewrote the JSDoc to describe the real cost model and
   name the `.iterate()` benefit accurately (avoids an intermediate
   `.all()` allocation, does not stream to caller).

3. `traces.ts::listAllForEpisode` JSDoc said "all hot fields required
   by dedup are projected, so a caller that only needs `ts`, `turnId`,
   or the identity signature can still iterate at full speed". That
   accidentally invited dedup callers back onto this method, which
   loads every BLOB column. Rewrote the JSDoc to state explicitly
   that vec_summary/vec_action BLOBs are loaded and redirect
   dedup-only callers to `listDedupRowsForEpisode`. Preserved the
   MemTensor#2076 context on why the uncapped variant exists.

No behavioral change. No test changes needed.

Verification
  - `npx tsc -p tsconfig.json --noEmit` → clean.
  - `npx vitest run tests/unit/storage/traces-listall.test.ts \
     tests/unit/storage/vector-stream.test.ts` → 13/13 pass.
  - `npx vitest run tests/unit/capture` → 110/110 pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: avoid MemTensor#2077 capture merge conflict

---------

Co-authored-by: MemOS AutoDev <autodev@memtensor.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: jiachengzhen <jiacz@memtensor.cn>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant