Files
openclaw/extensions/memory-core/src/cli.ts
T
Vignesh 4c1022c73b feat(memory-core): add dreaming promotion with weighted recall thresholds (#60569)
* memory-core: add dreaming promotion flow with weighted thresholds

* docs(memory): mark dreaming as experimental

* memory-core: address dreaming promotion review feedback

* memory-core: harden short-term promotion concurrency

* acpx: make abort-process test timer-independent

* memory-core: simplify dreaming config with mode presets

* memory-core: add /dreaming command and tighten recall tracking

* ui: add Dreams tab with sleeping lobster animation

Adds a new Dreams tab to the gateway UI under the Agent group.
The tab is gated behind the memory-core dreaming config — it only
appears in the sidebar when dreaming.mode is not 'off'.

Features:
- Sleeping vector lobster with breathing animation
- Floating Z's, twinkling starfield, moon glow
- Rotating dream phrase bubble (17 whimsical phrases)
- Memory stats bar (short-term, long-term, promoted)
- Active/idle visual states
- 14 unit tests

* plugins: fix --json stdout pollution from hook runner log

The hook runner initialization message was using log.info() which
writes to stdout via console.log, breaking JSON.parse() in the
Docker smoke test for 'openclaw plugins list --json'. Downgrade to
log.debug() so it only appears when debugging is enabled.

* ui: keep Dreams tab visible when dreaming is off

* tests: fix contracts and stabilize extension shards

* memory-core: harden dreaming recall persistence and locking

* fix: stabilize dreaming PR gates (#60569) (thanks @vignesh07)

* test: fix rebase drift in telegram and plugin guards
2026-04-03 20:26:53 -07:00

137 lines
5.1 KiB
TypeScript

import type { Command } from "commander";
import {
formatDocsLink,
formatHelpExamples,
theme,
} from "openclaw/plugin-sdk/memory-core-host-runtime-cli";
import type {
MemoryCommandOptions,
MemoryPromoteCommandOptions,
MemorySearchCommandOptions,
} from "./cli.types.js";
import {
DEFAULT_PROMOTION_MIN_RECALL_COUNT,
DEFAULT_PROMOTION_MIN_SCORE,
DEFAULT_PROMOTION_MIN_UNIQUE_QUERIES,
} from "./short-term-promotion.js";
type MemoryCliRuntime = typeof import("./cli.runtime.js");
let memoryCliRuntimePromise: Promise<MemoryCliRuntime> | null = null;
async function loadMemoryCliRuntime(): Promise<MemoryCliRuntime> {
memoryCliRuntimePromise ??= import("./cli.runtime.js");
return await memoryCliRuntimePromise;
}
export async function runMemoryStatus(opts: MemoryCommandOptions) {
const runtime = await loadMemoryCliRuntime();
await runtime.runMemoryStatus(opts);
}
async function runMemoryIndex(opts: MemoryCommandOptions) {
const runtime = await loadMemoryCliRuntime();
await runtime.runMemoryIndex(opts);
}
async function runMemorySearch(queryArg: string | undefined, opts: MemorySearchCommandOptions) {
const runtime = await loadMemoryCliRuntime();
await runtime.runMemorySearch(queryArg, opts);
}
async function runMemoryPromote(opts: MemoryPromoteCommandOptions) {
const runtime = await loadMemoryCliRuntime();
await runtime.runMemoryPromote(opts);
}
export function registerMemoryCli(program: Command) {
const memory = program
.command("memory")
.description("Search, inspect, and reindex memory files")
.addHelpText(
"after",
() =>
`\n${theme.heading("Examples:")}\n${formatHelpExamples([
["openclaw memory status", "Show index and provider status."],
["openclaw memory status --deep", "Probe embedding provider readiness."],
["openclaw memory index --force", "Force a full reindex."],
['openclaw memory search "meeting notes"', "Quick search using positional query."],
[
'openclaw memory search --query "deployment" --max-results 20',
"Limit results for focused troubleshooting.",
],
[
`openclaw memory promote --limit 10 --min-score ${DEFAULT_PROMOTION_MIN_SCORE}`,
"Review weighted short-term candidates for long-term memory.",
],
[
"openclaw memory promote --apply",
"Append top-ranked short-term candidates into MEMORY.md.",
],
["openclaw memory status --json", "Output machine-readable JSON (good for scripts)."],
])}\n\n${theme.muted("Docs:")} ${formatDocsLink("/cli/memory", "docs.openclaw.ai/cli/memory")}\n`,
);
memory
.command("status")
.description("Show memory search index status")
.option("--agent <id>", "Agent id (default: default agent)")
.option("--json", "Print JSON")
.option("--deep", "Probe embedding provider availability")
.option("--index", "Reindex if dirty (implies --deep)")
.option("--verbose", "Verbose logging", false)
.action(async (opts: MemoryCommandOptions & { force?: boolean }) => {
await runMemoryStatus(opts);
});
memory
.command("index")
.description("Reindex memory files")
.option("--agent <id>", "Agent id (default: default agent)")
.option("--force", "Force full reindex", false)
.option("--verbose", "Verbose logging", false)
.action(async (opts: MemoryCommandOptions) => {
await runMemoryIndex(opts);
});
memory
.command("search")
.description("Search memory files")
.argument("[query]", "Search query")
.option("--query <text>", "Search query (alternative to positional argument)")
.option("--agent <id>", "Agent id (default: default agent)")
.option("--max-results <n>", "Max results", (value: string) => Number(value))
.option("--min-score <n>", "Minimum score", (value: string) => Number(value))
.option("--json", "Print JSON")
.action(async (queryArg: string | undefined, opts: MemorySearchCommandOptions) => {
await runMemorySearch(queryArg, opts);
});
memory
.command("promote")
.description("Rank short-term recalls and optionally append top entries to MEMORY.md")
.option("--agent <id>", "Agent id (default: default agent)")
.option("--limit <n>", "Max candidates", (value: string) => Number(value))
.option(
"--min-score <n>",
`Minimum weighted score (default: ${DEFAULT_PROMOTION_MIN_SCORE})`,
(value: string) => Number(value),
)
.option(
"--min-recall-count <n>",
`Minimum recall count (default: ${DEFAULT_PROMOTION_MIN_RECALL_COUNT})`,
(value: string) => Number(value),
)
.option(
"--min-unique-queries <n>",
`Minimum distinct query count (default: ${DEFAULT_PROMOTION_MIN_UNIQUE_QUERIES})`,
(value: string) => Number(value),
)
.option("--apply", "Append selected candidates to MEMORY.md", false)
.option("--include-promoted", "Include already promoted candidates", false)
.option("--json", "Print JSON")
.action(async (opts: MemoryPromoteCommandOptions) => {
await runMemoryPromote(opts);
});
}