fix: memory page shows indexed status correctly (#24)

Add --json flag to `openclaw memory status --deep` call and handle the
new array-of-agents response format. Previously the missing --json flag
caused JSON.parse to fail, always falling back to indexed: false. Now
correctly derives indexed state from status.files > 0 && !dirty across
all agents, sums chunks for totalEntries, and reads provider/vector
from the primary agent. Bumps to v0.8.9.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
JohnRiceML
2026-03-24 10:57:13 -05:00
co-authored by Claude Opus 4.6
parent a51ac3cbe0
commit 40db84d69b
4 changed files with 83 additions and 11 deletions
+47
View File
@@ -331,6 +331,53 @@ describe('getMemoryStatus', () => {
expect(status.lastIndexed).toBeNull()
expect(status.totalEntries).toBeNull()
})
it('parses new array-of-agents format from openclaw memory status --deep --json', () => {
mockExecSync.mockReturnValue(JSON.stringify([
{
agentId: 'main',
status: { files: 9, chunks: 13, dirty: false, provider: 'gemini', vector: { available: true } },
},
{
agentId: 'helper',
status: { files: 3, chunks: 5, dirty: false, provider: 'gemini', vector: { available: true } },
},
]))
const status = getMemoryStatus()
expect(status.indexed).toBe(true)
expect(status.totalEntries).toBe(18) // 13 + 5 chunks
expect(status.vectorAvailable).toBe(true)
expect(status.embeddingProvider).toBe('gemini')
})
it('reports not indexed when any agent has dirty files', () => {
mockExecSync.mockReturnValue(JSON.stringify([
{
agentId: 'main',
status: { files: 9, chunks: 13, dirty: false, provider: 'gemini', vector: { available: true } },
},
{
agentId: 'helper',
status: { files: 3, chunks: 5, dirty: true, provider: 'gemini', vector: { available: true } },
},
]))
const status = getMemoryStatus()
expect(status.indexed).toBe(false)
})
it('reports not indexed when an agent has zero files', () => {
mockExecSync.mockReturnValue(JSON.stringify([
{
agentId: 'main',
status: { files: 0, chunks: 0, dirty: false, provider: 'gemini', vector: { available: true } },
},
]))
const status = getMemoryStatus()
expect(status.indexed).toBe(false)
})
})
// ── computeMemoryStats ──────────────────────────────────────────
+33 -8
View File
@@ -3,6 +3,7 @@ import { readFileSync, existsSync, statSync, readdirSync } from 'fs'
import { join, basename, dirname } from 'path'
import { execSync } from 'child_process'
import { requireEnv } from '@/lib/env'
import { extractJson } from '@/lib/cli-utils'
// ── Date pattern for daily logs ─────────────────────────────────
@@ -213,21 +214,45 @@ export function getMemoryStatus(): MemoryStatus {
}
try {
const output = execSync(`${bin} memory status --deep`, {
const output = execSync(`${bin} memory status --deep --json`, {
timeout: 15000,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
}).trim()
// Try JSON parse first
try {
const data = JSON.parse(output)
const data = extractJson(output)
// New format: array of per-agent status objects
// [{ agentId, status: { files, chunks, dirty, provider, vector } }, ...]
if (Array.isArray(data) && data.length > 0) {
const agents = data as { agentId?: string; status?: Record<string, unknown> }[]
const primary = agents[0].status ?? {}
const totalEntries = agents.reduce(
(sum, a) => sum + (typeof a.status?.chunks === 'number' ? a.status.chunks : 0), 0,
)
const allIndexed = agents.every(
a => (typeof a.status?.files === 'number' && a.status.files > 0) && !a.status?.dirty,
)
const vec = primary.vector as Record<string, unknown> | undefined
return {
indexed: allIndexed,
lastIndexed: null,
totalEntries,
vectorAvailable: typeof vec?.available === 'boolean' ? vec.available : null,
embeddingProvider: typeof primary.provider === 'string' ? primary.provider : null,
raw: output,
}
}
// Legacy flat object format
const flat = data as Record<string, unknown>
return {
indexed: data.indexed ?? false,
lastIndexed: data.lastIndexed ?? null,
totalEntries: data.totalEntries ?? null,
vectorAvailable: data.vectorAvailable ?? null,
embeddingProvider: data.embeddingProvider ?? null,
indexed: flat.indexed === true,
lastIndexed: typeof flat.lastIndexed === 'string' ? flat.lastIndexed : null,
totalEntries: typeof flat.totalEntries === 'number' ? flat.totalEntries : null,
vectorAvailable: typeof flat.vectorAvailable === 'boolean' ? flat.vectorAvailable : null,
embeddingProvider: typeof flat.embeddingProvider === 'string' ? flat.embeddingProvider : null,
raw: output,
}
} catch {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "clawport-ui",
"version": "0.8.8",
"version": "0.8.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "clawport-ui",
"version": "0.8.8",
"version": "0.8.9",
"license": "MIT",
"dependencies": {
"@dagrejs/dagre": "^2.0.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "clawport-ui",
"version": "0.8.8",
"version": "0.8.9",
"description": "Open-source dashboard for managing, monitoring, and chatting with your OpenClaw AI agents.",
"homepage": "https://clawport.dev",
"repository": {