mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 11:00:42 +00:00
fix(status): report custom memory plugin status
This commit is contained in:
@@ -27,6 +27,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- CLI/status: show skipped fast-path memory checks as `not checked` and report active custom memory plugin runtime status from `status --json --all` without requiring built-in `agents.defaults.memorySearch`, so plugins such as memory-lancedb-pro and memory-cms no longer look unavailable when their own runtime is healthy. Fixes #56968. Thanks @Tony-ooo and @aderius.
|
||||
- Memory/LanceDB: let embedding config use provider-backed auth profiles, environment credentials, or provider config without a separate plugin `embedding.apiKey`, so OAuth-capable embedding providers can power auto-recall/capture. Fixes #68950. Thanks @malshaalan-ai.
|
||||
- Plugins/hooks: time out never-settling `agent_end` observation hooks after 30 seconds and log the plugin failure, so hung embedding endpoints no longer leave memory capture silently pending forever. Fixes #65544. Thanks @ghoc0099.
|
||||
- Gateway/config: serve runtime config schemas from the current plugin metadata snapshot and generated bundled channel schema metadata instead of rebuilding plugin channel config modules on every `config.get`/`config.schema`, preventing idle plugin-discovery CPU churn after upgrades. Fixes #73088. Thanks @sleitor and @geovansb.
|
||||
|
||||
@@ -20,7 +20,8 @@ openclaw status --usage
|
||||
Notes:
|
||||
|
||||
- `--deep` runs live probes (WhatsApp Web + Telegram + Discord + Slack + Signal).
|
||||
- Plain `openclaw status` stays on the fast read-only path. Heavy security audit, plugin compatibility, and memory-vector probes are left to `openclaw status --all`, `openclaw status --deep`, `openclaw security audit`, and `openclaw memory status --deep`.
|
||||
- Plain `openclaw status` stays on the fast read-only path and marks memory as `not checked` instead of unavailable when it skips memory inspection. Heavy security audit, plugin compatibility, and memory-vector probes are left to `openclaw status --all`, `openclaw status --deep`, `openclaw security audit`, and `openclaw memory status --deep`.
|
||||
- `status --json --all` reports memory details from the active memory plugin runtime selected by `plugins.slots.memory`. Custom memory plugins can leave built-in `agents.defaults.memorySearch.enabled` disabled and still report their own files, chunks, vector, and FTS state.
|
||||
- `--usage` prints normalized provider usage windows as `X% left`.
|
||||
- Session status output separates `Execution:` from `Runtime:`. `Execution` is the sandbox path (`direct`, `docker/*`), while `Runtime` tells you whether the session is using `OpenClaw Pi Default`, `OpenAI Codex`, a CLI backend, or an ACP backend such as `codex (acp/acpx)`. See [Agent runtimes](/concepts/agent-runtimes) for the provider/model/runtime distinction.
|
||||
- MiniMax's raw `usage_percent` / `usagePercent` fields are remaining quota, so OpenClaw inverts them before display; count-based fields win when present. `model_remains` responses prefer the chat-model entry, derive the window label from timestamps when needed, and include the model name in the plan label.
|
||||
|
||||
@@ -24,6 +24,24 @@ describe("status-overview-rows", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("marks skipped memory inspection as not checked in fast status output", () => {
|
||||
expect(
|
||||
buildStatusCommandOverviewRows(
|
||||
createStatusCommandOverviewRowsParams({
|
||||
memory: null,
|
||||
memoryPlugin: { enabled: true, slot: "memory-lancedb-pro" },
|
||||
}),
|
||||
),
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
{
|
||||
Item: "Memory",
|
||||
Value: "muted(enabled (plugin memory-lancedb-pro) · not checked)",
|
||||
},
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("builds status-all overview rows from the shared surface", () => {
|
||||
expect(
|
||||
buildStatusAllOverviewRows({
|
||||
|
||||
@@ -89,6 +89,7 @@ export function buildStatusCommandOverviewRows(
|
||||
resolveMemoryVectorState: params.resolveMemoryVectorState,
|
||||
resolveMemoryFtsState: params.resolveMemoryFtsState,
|
||||
resolveMemoryCacheSummary: params.resolveMemoryCacheSummary,
|
||||
memoryUnavailableLabel: "not checked",
|
||||
});
|
||||
const pluginCompatibilityValue = buildStatusPluginCompatibilityValue({
|
||||
notices: params.pluginCompatibility,
|
||||
|
||||
@@ -144,6 +144,7 @@ export function buildStatusMemoryValue(
|
||||
ok: (value: string) => string;
|
||||
warn: (value: string) => string;
|
||||
muted: (value: string) => string;
|
||||
memoryUnavailableLabel?: string;
|
||||
} & StatusMemoryStateResolvers,
|
||||
) {
|
||||
if (!params.memoryPlugin.enabled) {
|
||||
@@ -152,7 +153,7 @@ export function buildStatusMemoryValue(
|
||||
}
|
||||
if (!params.memory) {
|
||||
const slot = params.memoryPlugin.slot ? `plugin ${params.memoryPlugin.slot}` : "plugin";
|
||||
return params.muted(`enabled (${slot}) · unavailable`);
|
||||
return params.muted(`enabled (${slot}) · ${params.memoryUnavailableLabel ?? "unavailable"}`);
|
||||
}
|
||||
const parts: string[] = [];
|
||||
const dirtySuffix = params.memory.dirty ? ` · ${params.warn("dirty")}` : "";
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { resolveGatewayProbeSnapshot } from "./status.scan.shared.js";
|
||||
import {
|
||||
resolveGatewayProbeSnapshot,
|
||||
resolveSharedMemoryStatusSnapshot,
|
||||
} from "./status.scan.shared.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
buildGatewayConnectionDetailsWithResolvers: vi.fn(),
|
||||
@@ -140,3 +143,83 @@ describe("resolveGatewayProbeSnapshot", () => {
|
||||
expect(result.gatewayProbeAuthWarning).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveSharedMemoryStatusSnapshot", () => {
|
||||
it("asks custom memory-slot runtimes for status without requiring built-in memorySearch", async () => {
|
||||
const manager = {
|
||||
probeVectorAvailability: vi.fn(async () => true),
|
||||
status: vi.fn(() => ({
|
||||
backend: "builtin" as const,
|
||||
provider: "memory-lancedb-pro",
|
||||
files: 66,
|
||||
chunks: 128,
|
||||
vector: { enabled: true, available: true },
|
||||
fts: { enabled: true, available: true },
|
||||
})),
|
||||
close: vi.fn(async () => {}),
|
||||
};
|
||||
const resolveMemoryConfig = vi.fn(() => null);
|
||||
const getMemorySearchManager = vi.fn(async () => ({ manager }));
|
||||
const requireDefaultStore = vi.fn(() => `/tmp/openclaw-missing-memory-${process.pid}.sqlite`);
|
||||
|
||||
const result = await resolveSharedMemoryStatusSnapshot({
|
||||
cfg: {
|
||||
plugins: {
|
||||
slots: { memory: "memory-lancedb-pro" },
|
||||
},
|
||||
agents: {
|
||||
defaults: {
|
||||
memorySearch: { enabled: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
agentStatus: { defaultId: "main" },
|
||||
memoryPlugin: { enabled: true, slot: "memory-lancedb-pro" },
|
||||
resolveMemoryConfig,
|
||||
getMemorySearchManager,
|
||||
requireDefaultStore,
|
||||
});
|
||||
|
||||
expect(resolveMemoryConfig).not.toHaveBeenCalled();
|
||||
expect(requireDefaultStore).not.toHaveBeenCalled();
|
||||
expect(getMemorySearchManager).toHaveBeenCalledWith({
|
||||
cfg: expect.objectContaining({
|
||||
plugins: expect.objectContaining({
|
||||
slots: { memory: "memory-lancedb-pro" },
|
||||
}),
|
||||
}),
|
||||
agentId: "main",
|
||||
purpose: "status",
|
||||
});
|
||||
expect(manager.probeVectorAvailability).toHaveBeenCalled();
|
||||
expect(manager.status).toHaveBeenCalled();
|
||||
expect(manager.close).toHaveBeenCalled();
|
||||
expect(result).toEqual({
|
||||
agentId: "main",
|
||||
backend: "builtin",
|
||||
provider: "memory-lancedb-pro",
|
||||
files: 66,
|
||||
chunks: 128,
|
||||
vector: { enabled: true, available: true },
|
||||
fts: { enabled: true, available: true },
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps default memory-core on the cold-start store shortcut", async () => {
|
||||
const resolveMemoryConfig = vi.fn(() => null);
|
||||
const getMemorySearchManager = vi.fn(async () => ({ manager: null }));
|
||||
|
||||
const result = await resolveSharedMemoryStatusSnapshot({
|
||||
cfg: {},
|
||||
agentStatus: { defaultId: "main" },
|
||||
memoryPlugin: { enabled: true, slot: "memory-core" },
|
||||
resolveMemoryConfig,
|
||||
getMemorySearchManager,
|
||||
requireDefaultStore: () => `/tmp/openclaw-missing-memory-${process.pid}.sqlite`,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
expect(resolveMemoryConfig).not.toHaveBeenCalled();
|
||||
expect(getMemorySearchManager).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,6 +55,20 @@ export type GatewayProbeSnapshot = {
|
||||
};
|
||||
};
|
||||
|
||||
type StatusMemorySearchManager = {
|
||||
probeVectorAvailability(): Promise<boolean>;
|
||||
status(): MemoryProviderStatus;
|
||||
close?(): Promise<void>;
|
||||
};
|
||||
|
||||
type StatusMemorySearchManagerResolver = (params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentId: string;
|
||||
purpose: "status";
|
||||
}) => Promise<{
|
||||
manager: StatusMemorySearchManager | null;
|
||||
}>;
|
||||
|
||||
export function hasExplicitMemorySearchConfig(cfg: OpenClawConfig, agentId: string): boolean {
|
||||
if (
|
||||
cfg.agents?.defaults &&
|
||||
@@ -168,17 +182,7 @@ export async function resolveSharedMemoryStatusSnapshot(params: {
|
||||
agentStatus: { defaultId?: string | null };
|
||||
memoryPlugin: MemoryPluginStatus;
|
||||
resolveMemoryConfig: (cfg: OpenClawConfig, agentId: string) => { store: { path: string } } | null;
|
||||
getMemorySearchManager: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
agentId: string;
|
||||
purpose: "status";
|
||||
}) => Promise<{
|
||||
manager: {
|
||||
probeVectorAvailability(): Promise<boolean>;
|
||||
status(): MemoryProviderStatus;
|
||||
close?(): Promise<void>;
|
||||
} | null;
|
||||
}>;
|
||||
getMemorySearchManager: StatusMemorySearchManagerResolver;
|
||||
requireDefaultStore?: (agentId: string) => string | null;
|
||||
}): Promise<MemoryStatusSnapshot | null> {
|
||||
const { cfg, agentStatus, memoryPlugin } = params;
|
||||
@@ -186,6 +190,11 @@ export async function resolveSharedMemoryStatusSnapshot(params: {
|
||||
return null;
|
||||
}
|
||||
const agentId = agentStatus.defaultId ?? "main";
|
||||
|
||||
if (memoryPlugin.slot !== defaultSlotIdForKey("memory")) {
|
||||
return await resolveMemoryManagerStatusSnapshot(params, agentId);
|
||||
}
|
||||
|
||||
const defaultStorePath = params.requireDefaultStore?.(agentId);
|
||||
if (
|
||||
defaultStorePath &&
|
||||
@@ -203,14 +212,31 @@ export async function resolveSharedMemoryStatusSnapshot(params: {
|
||||
if (!shouldInspectStore) {
|
||||
return null;
|
||||
}
|
||||
const { manager } = await params.getMemorySearchManager({ cfg, agentId, purpose: "status" });
|
||||
return await resolveMemoryManagerStatusSnapshot(params, agentId);
|
||||
}
|
||||
|
||||
async function resolveMemoryManagerStatusSnapshot(
|
||||
params: {
|
||||
cfg: OpenClawConfig;
|
||||
getMemorySearchManager: StatusMemorySearchManagerResolver;
|
||||
},
|
||||
agentId: string,
|
||||
): Promise<MemoryStatusSnapshot | null> {
|
||||
const { manager } = await params.getMemorySearchManager({
|
||||
cfg: params.cfg,
|
||||
agentId,
|
||||
purpose: "status",
|
||||
});
|
||||
if (!manager) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
await manager.probeVectorAvailability();
|
||||
} catch {}
|
||||
const status = manager.status();
|
||||
await manager.close?.().catch(() => {});
|
||||
return { agentId, ...status };
|
||||
try {
|
||||
await manager.probeVectorAvailability();
|
||||
} catch {}
|
||||
const status = manager.status();
|
||||
return { agentId, ...status };
|
||||
} finally {
|
||||
await manager.close?.().catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user