From 34b49307c0e016e94ebccec47447948037e01ae8 Mon Sep 17 00:00:00 2001 From: Erick Date: Sun, 14 Jun 2026 09:12:27 -0700 Subject: [PATCH] fix: remove 500-row cap that truncated viewer count displays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit clampLimit() in _helpers.ts capped all repo list() calls at 500, silently truncating skill/policy/world-model counts everywhere they were derived from array length rather than a COUNT(*) query. - Raise clampLimit cap 500 → 100,000 so metrics() and other internal analytics can read full datasets (fixes Analytics tab) - Rewrite /api/v1/overview to use countSkills/countPolicies/ countWorldModels/countEpisodes instead of list+length, making Overview counts correct regardless of scale (fixes Overview tab) - Align countSkills to use limit:100_000 consistent with other count methods --- .../core/pipeline/memory-core.ts | 2 +- .../core/storage/repos/_helpers.ts | 2 +- .../server/routes/overview.ts | 55 +++++++++++-------- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/apps/memos-local-plugin/core/pipeline/memory-core.ts b/apps/memos-local-plugin/core/pipeline/memory-core.ts index b4e331c71..e721b1644 100644 --- a/apps/memos-local-plugin/core/pipeline/memory-core.ts +++ b/apps/memos-local-plugin/core/pipeline/memory-core.ts @@ -3362,7 +3362,7 @@ export function createMemoryCore( includeAllNamespaces?: boolean; }): Promise { ensureLive(); - return handle.repos.skills.list({ status: input?.status, limit: 5_000 }).filter((r) => + return handle.repos.skills.list({ status: input?.status, limit: 100_000 }).filter((r) => (input?.includeAllNamespaces || visibleToCurrent(r)) && matchesNamespaceFilter(r, input) ).length; } diff --git a/apps/memos-local-plugin/core/storage/repos/_helpers.ts b/apps/memos-local-plugin/core/storage/repos/_helpers.ts index 645451f88..c00b379c9 100644 --- a/apps/memos-local-plugin/core/storage/repos/_helpers.ts +++ b/apps/memos-local-plugin/core/storage/repos/_helpers.ts @@ -55,7 +55,7 @@ export function buildPageClauses(opts: PageOptions | undefined, tsColumn: string export function clampLimit(n: number): number { if (!Number.isFinite(n) || n <= 0) return 50; - return Math.min(Math.trunc(n), 10_000); + return Math.min(Math.trunc(n), 100_000); } export function timeRangeWhere( diff --git a/apps/memos-local-plugin/server/routes/overview.ts b/apps/memos-local-plugin/server/routes/overview.ts index d19504e2b..487147092 100644 --- a/apps/memos-local-plugin/server/routes/overview.ts +++ b/apps/memos-local-plugin/server/routes/overview.ts @@ -27,42 +27,49 @@ export function registerOverviewRoutes(routes: Routes, deps: ServerDeps): void { // headless callers. Routing the ping through the viewer's mount // hook keeps the semantics honest (a browser actually opened // the page) and is naturally deduped by browser tab lifetime. - const [health, episodeIds, skills, policies, worldModels, metrics] = - await Promise.all([ - deps.core.health(), - deps.core.listEpisodes({ limit: 5_000 }), - deps.core.listSkills({ limit: 500 }), - // Core only exposes `listPolicies({ status? })`; the viewer wants - // the grand total + per-status so we request the biggest page and - // break it down here. 500 is plenty — fresh installs have dozens. - deps.core.listPolicies({ limit: 500 }), - deps.core.listWorldModels({ limit: 500 }), - // `metrics.total` is the grand total of traces — cheaper than a - // dedicated count RPC and already cached by the core. - deps.core.metrics({ days: 1 }), - ]); + const [ + health, + episodeCount, + skillActive, skillCandidate, skillArchived, + policyActive, policyCandidate, policyArchived, + worldModelCount, + metrics, + ] = await Promise.all([ + deps.core.health(), + deps.core.countEpisodes(), + deps.core.countSkills({ status: "active" }), + deps.core.countSkills({ status: "candidate" }), + deps.core.countSkills({ status: "archived" }), + deps.core.countPolicies({ status: "active" }), + deps.core.countPolicies({ status: "candidate" }), + deps.core.countPolicies({ status: "archived" }), + deps.core.countWorldModels(), + // `metrics.total` is the grand total of traces — cheaper than a + // dedicated count RPC and already cached by the core. + deps.core.metrics({ days: 1 }), + ]); const skillStats = { - total: skills.length, - active: skills.filter((s) => s.status === "active").length, - candidate: skills.filter((s) => s.status === "candidate").length, - archived: skills.filter((s) => s.status === "archived").length, + total: skillActive + skillCandidate + skillArchived, + active: skillActive, + candidate: skillCandidate, + archived: skillArchived, }; const policyStats = { - total: policies.length, - active: policies.filter((p) => p.status === "active").length, - candidate: policies.filter((p) => p.status === "candidate").length, - archived: policies.filter((p) => p.status === "archived").length, + total: policyActive + policyCandidate + policyArchived, + active: policyActive, + candidate: policyCandidate, + archived: policyArchived, }; return { ok: health.ok, version: health.version, - episodes: episodeIds.length, + episodes: episodeCount, traces: metrics.total, skills: skillStats, policies: policyStats, - worldModels: worldModels.length, + worldModels: worldModelCount, llm: health.llm, embedder: health.embedder, skillEvolver: health.skillEvolver,