From c87e545c88970fd2caea3d3ef18bd1d44cee3d2f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 29 Jul 2026 12:51:37 -0400 Subject: [PATCH] fix(ui): rename the Memory Palace browser to Memory Wiki and document it (#115954) * fix(ui): rename the Memory Palace browser to Memory Wiki and document it * refactor(memory-wiki): drop the old memory-palace module files * docs: refresh generated docs map * test(ui): align dream-diary hub-tab assertion with the wiki sub-tab id * docs: describe memory wiki clustering behavior accurately * docs(memory-wiki): record the wiki.overview rename decision at the registration site * test(ui): query the Agents channels hub tab by tab role after the hub-tabs refactor --- docs/concepts/dreaming.md | 13 ++ docs/concepts/memory.md | 4 +- docs/docs_map.md | 1 + docs/plugins/memory-wiki.md | 17 +++ docs/web/control-ui.md | 2 +- extensions/memory-wiki/index.test.ts | 2 +- extensions/memory-wiki/src/gateway.test.ts | 20 +-- extensions/memory-wiki/src/gateway.ts | 9 +- ...y-palace.test.ts => wiki-overview.test.ts} | 10 +- .../{memory-palace.ts => wiki-overview.ts} | 48 +++---- .../agent-channel-runtime-status.e2e.test.ts | 2 +- ui/src/i18n/locales/en.ts | 14 +- ui/src/pages/agents/memory/dreaming.test.ts | 120 ++++++++-------- ui/src/pages/agents/memory/dreaming.ts | 128 +++++++++--------- ui/src/pages/agents/memory/memory-panel.ts | 12 +- ui/src/pages/agents/memory/view.test.ts | 36 ++--- ui/src/pages/agents/memory/view.ts | 94 ++++++------- ui/src/styles/dreams.css | 2 +- 18 files changed, 282 insertions(+), 252 deletions(-) rename extensions/memory-wiki/src/{memory-palace.test.ts => wiki-overview.test.ts} (94%) rename extensions/memory-wiki/src/{memory-palace.ts => wiki-overview.ts} (75%) diff --git a/docs/concepts/dreaming.md b/docs/concepts/dreaming.md index 89bfc55be2d1..6b39fccda7bf 100644 --- a/docs/concepts/dreaming.md +++ b/docs/concepts/dreaming.md @@ -283,6 +283,19 @@ When enabled, the Gateway **Dreams** tab shows: - a distinct grounded Scene lane for staged historical replay entries - an expandable Dream Diary reader backed by `doctor.memory.dreamDiary` +When the bundled [`memory-wiki`](/plugins/memory-wiki) plugin is enabled, the +Diary view gains two more sub-tabs next to Dreams: + +- **Imported Insights**: clustered insights surfaced by external-history + imports (for example `openclaw wiki chatgpt import`), for review before any + of it graduates into durable memory +- **Memory Wiki**: the compiled wiki the memory system can search and reason + over — synthesis, entity, and concept pages (plus sources and reports that + carry claims, open questions, or contradictions) with per-page counts, a + full-vault breakdown, and inline page previews + +Both sub-tabs show an enable hint instead when `memory-wiki` is off. + ## Related - [Memory](/concepts/memory) diff --git a/docs/concepts/memory.md b/docs/concepts/memory.md index a67f84e13ae6..6916b6cf80e0 100644 --- a/docs/concepts/memory.md +++ b/docs/concepts/memory.md @@ -216,7 +216,9 @@ dashboards, compiled digests, and wiki-native tools (`wiki_status`, `memory-wiki` does not replace the active memory plugin; the active memory plugin still owns recall, promotion, and dreaming. `memory-wiki` adds a -provenance-rich knowledge layer beside it. +provenance-rich knowledge layer beside it. You can browse the compiled wiki +in the Control UI under Memory → Dreams → Diary → **Memory Wiki** +([details](/plugins/memory-wiki#browsing-the-wiki-in-the-control-ui)). diff --git a/docs/docs_map.md b/docs/docs_map.md index 591f3062b9a4..7e5390af9122 100644 --- a/docs/docs_map.md +++ b/docs/docs_map.md @@ -6219,6 +6219,7 @@ Do not edit it by hand; run `pnpm docs:map:gen`. - H2: Dashboards and health reports - H2: Search and retrieval - H2: Agent tools + - H2: Browsing the wiki in the Control UI - H2: Prompt and context behavior - H2: Configuration - H3: Per-agent vaults diff --git a/docs/plugins/memory-wiki.md b/docs/plugins/memory-wiki.md index 8f1d8a8779e2..c07fb3862017 100644 --- a/docs/plugins/memory-wiki.md +++ b/docs/plugins/memory-wiki.md @@ -283,6 +283,23 @@ The plugin also registers a non-exclusive memory corpus supplement, so shared `memory_search` and `memory_get` can reach the wiki when the active memory plugin supports corpus selection. +## Browsing the wiki in the Control UI + +The [Control UI](/web/control-ui) can browse the compiled wiki directly: open +the Memory page, then **Dreams → Diary → Memory Wiki**. The tab clusters +synthesis, entity, and concept pages — plus source and report pages that +carry claims, open questions, or contradictions — with per-page counts and a +full-vault page breakdown, and opens full page content inline. Raw sources +and reports without that metadata count toward the breakdown but are not +listed as cards; open them from the **Imported Insights** sub-tab, which +reviews what external-history imports surfaced before promotion. + +Both sub-tabs appear once the plugin is enabled; in agent-scoped vault setups +they show the selected agent's own vault. The UI reads through the plugin's +gateway methods (`wiki.overview`, `wiki.get`, `wiki.importInsights`); inline +page previews use `wiki.get`, the same lookup agents reach through the +`wiki_get` tool. + ## Prompt and context behavior When `context.includeCompiledDigestPrompt` is enabled, memory prompt sections diff --git a/docs/web/control-ui.md b/docs/web/control-ui.md index 56b2ecb61d9d..af67dc163b19 100644 --- a/docs/web/control-ui.md +++ b/docs/web/control-ui.md @@ -266,7 +266,7 @@ select it to open the owning Approvals page. - Threads (a workspace page at `/sessions`, with a **Worktrees** tab alongside it): list configured-agent sessions by default, pin frequent sessions, rename them, archive or restore inactive sessions, fall back from stale unconfigured agent session keys, and apply per-session model/thinking/fast/verbose/trace/reasoning overrides (`sessions.list`, `sessions.patch`). A three-way **Active / Archived / All** filter controls both this page and the sidebar; All dims archived rows and labels them explicitly. Archived sessions keep their transcripts, are never auto-pruned, and remain shelved until explicitly unarchived or deleted. Rows show an unread dot for active sessions with activity since they were last read, with mark-unread/mark-read actions (`sessions.patch { unread }`), and a Fork action that branches the transcript into a new session (`sessions.create { parentSessionKey, fork: true }`). Overview tiles above the table summarize the loaded roster (session count, live runs, unread sessions, total tokens, and archived count when available), each row carries a kind glyph with a live-run dot, status renders as a plain dot plus label, and the Tokens column shows a context-window usage meter when the session reports token and context sizes. Row management actions live in a per-row menu (kebab button or right-click) mirroring the sidebar's session menu, and the row drawer carries the agent runtime and run duration alongside the other session details. - Native Claude and Codex sidebar catalogs stream one host at a time, then reconcile after node connectivity changes, on page focus, and at most every 30 seconds while visible. Catalog changes trigger a faster follow-up pass, so sessions created in the native tools appear without reloading the Control UI. Claude Desktop rows also retain their local custom-group label when present; OpenClaw reads that mapping from Desktop's local store and never writes it. - Session grouping: a Group by control organizes the sessions table into sections by custom groups, channel, kind, agent, or date. Custom groups persist per session via `sessions.patch` (`category`), so sessions started from message channels (Discord, Telegram, WhatsApp, ...) can be categorized too; assign groups by dragging rows onto a section, or with the per-row group selector, and create groups with the New group action. - - Memory (a tab on the Agents page, scoped to the selected agent): dreaming status, enable/disable toggle, and Dream Diary reader (`doctor.memory.status`, `doctor.memory.dreamDiary`, `config.patch`). + - Memory (a tab on the Agents page, scoped to the selected agent): dreaming status, enable/disable toggle, and Dream Diary reader (`doctor.memory.status`, `doctor.memory.dreamDiary`, `config.patch`). When the `memory-wiki` plugin is enabled, the Diary view adds **Imported Insights** and **Memory Wiki** sub-tabs that browse imported source chats and the compiled wiki — clustered synthesis, entity, and concept pages plus annotated sources and reports, with claims, open questions, contradictions, and inline page previews (`wiki.importInsights`, `wiki.overview`, `wiki.get`). - Import Memory (`/memory-import`, reached from the Agents page's Memory tab): preview and copy local Claude Code auto-memory, Codex consolidated memory, or Hermes memory files into the selected agent workspace (`migrations.memory.plan`, `migrations.memory.apply`). - Onboarding memory offer: when the Control UI opens in [onboarding mode](/web/urls#special-documents-and-startup-modes), a one-page dialog offers to import detected memories with the same plan/apply flow; skipping leaves the settings page as the later entry point. diff --git a/extensions/memory-wiki/index.test.ts b/extensions/memory-wiki/index.test.ts index 77b8e9968779..3706ef21f8e4 100644 --- a/extensions/memory-wiki/index.test.ts +++ b/extensions/memory-wiki/index.test.ts @@ -65,7 +65,7 @@ describe("memory-wiki plugin", () => { "wiki.status", "wiki.importRuns", "wiki.importInsights", - "wiki.palace", + "wiki.overview", "wiki.init", "wiki.doctor", "wiki.compile", diff --git a/extensions/memory-wiki/src/gateway.test.ts b/extensions/memory-wiki/src/gateway.test.ts index b12c9e628269..cfae1a6e6630 100644 --- a/extensions/memory-wiki/src/gateway.test.ts +++ b/extensions/memory-wiki/src/gateway.test.ts @@ -6,11 +6,11 @@ import { registerMemoryWikiGatewayMethods } from "./gateway.js"; import { listMemoryWikiImportInsights } from "./import-insights.js"; import { listMemoryWikiImportRuns } from "./import-runs.js"; import { ingestMemoryWikiSource } from "./ingest.js"; -import { listMemoryWikiPalace } from "./memory-palace.js"; import { searchMemoryWiki } from "./query.js"; import { syncMemoryWikiImportedSources } from "./source-sync.js"; import { resolveMemoryWikiStatus } from "./status.js"; import { createMemoryWikiTestHarness } from "./test-helpers.js"; +import { listMemoryWikiOverview } from "./wiki-overview.js"; type ApplyMemoryWikiMutation = ReturnType; @@ -39,8 +39,8 @@ vi.mock("./lint.js", () => ({ lintMemoryWikiVault: vi.fn(), })); -vi.mock("./memory-palace.js", () => ({ - listMemoryWikiPalace: vi.fn(), +vi.mock("./wiki-overview.js", () => ({ + listMemoryWikiOverview: vi.fn(), })); vi.mock("./obsidian.js", () => ({ @@ -108,7 +108,7 @@ const VAULT_BACKED_GATEWAY_CASES = [ ["wiki.status", {}], ["wiki.importRuns", {}], ["wiki.importInsights", {}], - ["wiki.palace", {}], + ["wiki.overview", {}], ["wiki.init", {}], ["wiki.doctor", {}], ["wiki.compile", {}], @@ -164,7 +164,7 @@ describe("memory-wiki gateway methods", () => { totalClusters: 0, clusters: [], } as never); - vi.mocked(listMemoryWikiPalace).mockResolvedValue({ + vi.mocked(listMemoryWikiOverview).mockResolvedValue({ totalItems: 0, totalClaims: 0, totalQuestions: 0, @@ -540,10 +540,10 @@ describe("memory-wiki gateway methods", () => { }); }); - it("returns memory palace overview over the gateway", async () => { + it("returns the wiki overview over the gateway", async () => { const { config } = await createVault({ prefix: "memory-wiki-gateway-" }); const { api, registerGatewayMethod } = createPluginApi(); - vi.mocked(listMemoryWikiPalace).mockResolvedValue({ + vi.mocked(listMemoryWikiOverview).mockResolvedValue({ totalItems: 1, totalPages: 3, pageCounts: { @@ -582,9 +582,9 @@ describe("memory-wiki gateway methods", () => { } as never); registerMemoryWikiGatewayMethods({ api, config }); - const handler = findGatewayHandler(registerGatewayMethod, "wiki.palace"); + const handler = findGatewayHandler(registerGatewayMethod, "wiki.overview"); if (!handler) { - throw new Error("wiki.palace handler missing"); + throw new Error("wiki.overview handler missing"); } const respond = vi.fn(); @@ -594,7 +594,7 @@ describe("memory-wiki gateway methods", () => { }); expect(syncMemoryWikiImportedSources).toHaveBeenCalledWith({ config, appConfig: undefined }); - expect(listMemoryWikiPalace).toHaveBeenCalledWith(config); + expect(listMemoryWikiOverview).toHaveBeenCalledWith(config); expect(readRespondPayload(respond)).toEqual({ totalItems: 1, totalPages: 3, diff --git a/extensions/memory-wiki/src/gateway.ts b/extensions/memory-wiki/src/gateway.ts index d47e99bae02f..5329164ae3b3 100644 --- a/extensions/memory-wiki/src/gateway.ts +++ b/extensions/memory-wiki/src/gateway.ts @@ -15,7 +15,6 @@ import { listMemoryWikiImportInsights } from "./import-insights.js"; import { listMemoryWikiImportRuns } from "./import-runs.js"; import { ingestMemoryWikiSource } from "./ingest.js"; import { lintMemoryWikiVault } from "./lint.js"; -import { listMemoryWikiPalace } from "./memory-palace.js"; import { probeObsidianCli, runObsidianCommand, @@ -27,6 +26,7 @@ import { getMemoryWikiPage, searchMemoryWiki, WIKI_SEARCH_MODES } from "./query. import { syncMemoryWikiImportedSources } from "./source-sync.js"; import { buildMemoryWikiDoctorReport, resolveMemoryWikiStatus } from "./status.js"; import { initializeMemoryWikiVault } from "./vault.js"; +import { listMemoryWikiOverview } from "./wiki-overview.js"; const READ_SCOPE = "operator.read" as const; const WRITE_SCOPE = "operator.write" as const; @@ -175,13 +175,16 @@ export function registerMemoryWikiGatewayMethods(params: { { scope: READ_SCOPE }, ); + // Renamed from wiki.palace without an alias by maintainer decision: the method was + // undocumented, its only known consumer is the version-locked Control UI, and stale + // callers get an explicit unknown-method error rather than a silent failure. api.registerGatewayMethod( - "wiki.palace", + "wiki.overview", async ({ params: requestParams, respond }) => { try { const { appConfig, config } = resolveRequestContext(requestParams); await syncImportedSourcesIfNeeded(config, appConfig); - respond(true, await listMemoryWikiPalace(config)); + respond(true, await listMemoryWikiOverview(config)); } catch (error) { respondError(respond, error); } diff --git a/extensions/memory-wiki/src/memory-palace.test.ts b/extensions/memory-wiki/src/wiki-overview.test.ts similarity index 94% rename from extensions/memory-wiki/src/memory-palace.test.ts rename to extensions/memory-wiki/src/wiki-overview.test.ts index 263082d98bc6..273f491318e0 100644 --- a/extensions/memory-wiki/src/memory-palace.test.ts +++ b/extensions/memory-wiki/src/wiki-overview.test.ts @@ -1,17 +1,17 @@ -// Memory Wiki tests cover memory palace plugin behavior. +// Memory Wiki tests cover the wiki overview plugin behavior. import fs from "node:fs/promises"; import path from "node:path"; import { describe, expect, it } from "vitest"; import { renderWikiMarkdown } from "./markdown.js"; -import { listMemoryWikiPalace } from "./memory-palace.js"; import { createMemoryWikiTestHarness } from "./test-helpers.js"; +import { listMemoryWikiOverview } from "./wiki-overview.js"; const { createVault } = createMemoryWikiTestHarness(); -describe("listMemoryWikiPalace", () => { +describe("listMemoryWikiOverview", () => { it("groups wiki pages by kind and surfaces claims, questions, and contradictions", async () => { const { rootDir, config } = await createVault({ - prefix: "memory-wiki-palace-", + prefix: "memory-wiki-overview-", initialize: true, }); @@ -73,7 +73,7 @@ describe("listMemoryWikiPalace", () => { "utf8", ); - const result = await listMemoryWikiPalace(config); + const result = await listMemoryWikiOverview(config); expect(result.totalItems).toBe(2); expect(result.totalPages).toBe(3); diff --git a/extensions/memory-wiki/src/memory-palace.ts b/extensions/memory-wiki/src/wiki-overview.ts similarity index 75% rename from extensions/memory-wiki/src/memory-palace.ts rename to extensions/memory-wiki/src/wiki-overview.ts index ddbe996f4964..4155ef87a1ab 100644 --- a/extensions/memory-wiki/src/memory-palace.ts +++ b/extensions/memory-wiki/src/wiki-overview.ts @@ -1,11 +1,11 @@ -// Memory Wiki plugin module implements memory palace behavior. +// Memory Wiki plugin module implements the memory wiki overview. import type { ResolvedMemoryWikiConfig } from "./config.js"; import { parseWikiMarkdown, type WikiPageKind } from "./markdown.js"; import { readQueryableWikiPages } from "./query.js"; -const PALACE_KIND_ORDER: WikiPageKind[] = ["synthesis", "entity", "concept", "source", "report"]; -const PRIMARY_PALACE_KINDS = new Set(["synthesis", "entity", "concept"]); -const PALACE_KIND_LABELS: Record = { +const OVERVIEW_KIND_ORDER: WikiPageKind[] = ["synthesis", "entity", "concept", "source", "report"]; +const PRIMARY_OVERVIEW_KINDS = new Set(["synthesis", "entity", "concept"]); +const OVERVIEW_KIND_LABELS: Record = { synthesis: "Syntheses", entity: "Entities", concept: "Concepts", @@ -13,7 +13,7 @@ const PALACE_KIND_LABELS: Record = { report: "Reports", }; -type MemoryWikiPalaceItem = { +type MemoryWikiOverviewItem = { pagePath: string; title: string; kind: WikiPageKind; @@ -29,7 +29,7 @@ type MemoryWikiPalaceItem = { snippet?: string; }; -type MemoryWikiPalaceCluster = { +type MemoryWikiOverviewCluster = { key: WikiPageKind; label: string; itemCount: number; @@ -37,22 +37,22 @@ type MemoryWikiPalaceCluster = { questionCount: number; contradictionCount: number; updatedAt?: string; - items: MemoryWikiPalaceItem[]; + items: MemoryWikiOverviewItem[]; }; -type MemoryWikiPalacePageCounts = Record; +type MemoryWikiOverviewPageCounts = Record; -type MemoryWikiPalaceStatus = { +type MemoryWikiOverviewStatus = { totalItems: number; totalPages: number; - pageCounts: MemoryWikiPalacePageCounts; + pageCounts: MemoryWikiOverviewPageCounts; totalClaims: number; totalQuestions: number; totalContradictions: number; - clusters: MemoryWikiPalaceCluster[]; + clusters: MemoryWikiOverviewCluster[]; }; -function createEmptyPalacePageCounts(): MemoryWikiPalacePageCounts { +function createEmptyOverviewPageCounts(): MemoryWikiOverviewPageCounts { return { synthesis: 0, entity: 0, @@ -88,7 +88,7 @@ function extractSnippet(body: string): string | undefined { return undefined; } -function comparePalaceItems(left: MemoryWikiPalaceItem, right: MemoryWikiPalaceItem): number { +function compareOverviewItems(left: MemoryWikiOverviewItem, right: MemoryWikiOverviewItem): number { const leftKey = left.updatedAt ?? ""; const rightKey = right.updatedAt ?? ""; if (rightKey !== leftKey) { @@ -100,14 +100,14 @@ function comparePalaceItems(left: MemoryWikiPalaceItem, right: MemoryWikiPalaceI return left.title.localeCompare(right.title); } -export async function listMemoryWikiPalace( +export async function listMemoryWikiOverview( config: ResolvedMemoryWikiConfig, -): Promise { +): Promise { const pages = await readQueryableWikiPages(config.vault.path); - const pageCounts = pages.reduce((counts, page) => { + const pageCounts = pages.reduce((counts, page) => { counts[page.kind] += 1; return counts; - }, createEmptyPalacePageCounts()); + }, createEmptyOverviewPageCounts()); const totalClaims = pages.reduce((sum, page) => sum + page.claims.length, 0); const totalQuestions = pages.reduce((sum, page) => sum + page.questions.length, 0); const totalContradictions = pages.reduce((sum, page) => sum + page.contradictions.length, 0); @@ -130,18 +130,18 @@ export async function listMemoryWikiPalace( contradictions: page.contradictions.slice(0, 3), }, extractSnippet(parsed.body) ? { snippet: extractSnippet(parsed.body) } : {}, - ) satisfies MemoryWikiPalaceItem; + ) satisfies MemoryWikiOverviewItem; }) .filter( (item) => - PRIMARY_PALACE_KINDS.has(item.kind) || + PRIMARY_OVERVIEW_KINDS.has(item.kind) || item.claimCount > 0 || item.questionCount > 0 || item.contradictionCount > 0, ) - .toSorted(comparePalaceItems); + .toSorted(compareOverviewItems); - const clusters = PALACE_KIND_ORDER.map((kind) => { + const clusters = OVERVIEW_KIND_ORDER.map((kind) => { const clusterItems = items.filter((item) => item.kind === kind); if (clusterItems.length === 0) { return null; @@ -149,7 +149,7 @@ export async function listMemoryWikiPalace( return Object.assign( { key: kind, - label: PALACE_KIND_LABELS[kind], + label: OVERVIEW_KIND_LABELS[kind], itemCount: clusterItems.length, claimCount: clusterItems.reduce((sum, item) => sum + item.claimCount, 0), questionCount: clusterItems.reduce((sum, item) => sum + item.questionCount, 0), @@ -157,8 +157,8 @@ export async function listMemoryWikiPalace( }, clusterItems[0]?.updatedAt ? { updatedAt: clusterItems[0].updatedAt } : {}, { items: clusterItems }, - ) satisfies MemoryWikiPalaceCluster; - }).filter((entry): entry is MemoryWikiPalaceCluster => entry !== null); + ) satisfies MemoryWikiOverviewCluster; + }).filter((entry): entry is MemoryWikiOverviewCluster => entry !== null); return { totalItems: items.length, diff --git a/ui/src/e2e/agent-channel-runtime-status.e2e.test.ts b/ui/src/e2e/agent-channel-runtime-status.e2e.test.ts index c5d5bdca0bd8..a73770a9e194 100644 --- a/ui/src/e2e/agent-channel-runtime-status.e2e.test.ts +++ b/ui/src/e2e/agent-channel-runtime-status.e2e.test.ts @@ -97,7 +97,7 @@ describeControlUiE2e("Control UI Agents channel status", () => { try { const response = await page.goto(`${server.baseUrl}agents`); expect(response?.status()).toBe(200); - await page.getByRole("button", { name: /^Channels/ }).click(); + await page.getByRole("tab", { name: /^Channels/ }).click(); const discordRow = page.locator(".settings-row").filter({ hasText: "Discord" }); await expect.poll(() => discordRow.count()).toBe(1); diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index d4ce0365a14d..b2e2affdb3f9 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -3718,12 +3718,12 @@ export const en: TranslationMap = { loadingPage: "Loading wiki page…", dreamsTab: "Dreams", insightsTab: "Imported Insights", - palaceTab: "Memory Palace", + wikiTab: "Memory Wiki", dreamsExplainer: "This is the raw dream diary the system writes while replaying and consolidating memory; use it to inspect what the memory system is noticing, and where it still looks noisy or thin.", insightsExplainer: "These are imported insights clustered from external history; use them to review what imports surfaced before any of it graduates into durable memory.", - palaceExplainer: + wikiExplainer: "This is the compiled memory wiki surface the system can search and reason over; use it to inspect actual memory pages, claims, open questions, and contradictions rather than raw imported source chats.", copyArchivePath: "Copy archive path", loadingInsights: "Loading imported insights…", @@ -3739,9 +3739,9 @@ export const en: TranslationMap = { riskReasons: "Risk reasons:", labels: "Labels:", openSourcePage: "Open source page", - loadingPalace: "Loading memory palace…", - emptyPalace: "Memory palace is not populated yet", - emptyPalaceHint: + loadingWiki: "Loading memory wiki…", + emptyWiki: "Memory wiki is not populated yet", + emptyWikiHint: "Right now the wiki mostly has raw source imports and operational reports. This tab becomes useful once syntheses, entities, or concepts start getting written.", claims: "Claims", openQuestions: "Open questions", @@ -3751,7 +3751,7 @@ export const en: TranslationMap = { id: "Id:", openWikiPage: "Open wiki page", unavailable: "Memory Wiki is not enabled", - unavailablePluginPrefix: "Imported Insights and Memory Palace are provided by the bundled", + unavailablePluginPrefix: "Imported Insights and Memory Wiki are provided by the bundled", unavailablePluginSuffix: "plugin.", enablePrefix: "Enable", enableSuffix: ", then reload this tab.", @@ -3817,7 +3817,7 @@ export const en: TranslationMap = { tidyingKnowledgeGraph: "tidying the knowledge graph…", replayingConversations: "replaying today's conversations…", weavingShortTerm: "weaving short-term into long-term…", - defragmentingMindPalace: "defragmenting the mind palace…", + defragmentingMemoryLane: "defragmenting memory lane…", filingLooseThoughts: "filing away loose thoughts…", connectingDots: "connecting distant dots…", compostingContext: "composting old context windows…", diff --git a/ui/src/pages/agents/memory/dreaming.test.ts b/ui/src/pages/agents/memory/dreaming.test.ts index 282e662b70a4..de25febdfdaa 100644 --- a/ui/src/pages/agents/memory/dreaming.test.ts +++ b/ui/src/pages/agents/memory/dreaming.test.ts @@ -12,7 +12,7 @@ import { loadDreamDiary, loadDreamingStatus, loadWikiImportInsights, - loadWikiMemoryPalace, + loadWikiOverview, repairDreamingArtifacts, resetGroundedShortTerm, resetDreamDiary, @@ -580,13 +580,13 @@ describe("dreaming controller", () => { expect(state.wikiImportInsightsLoading).toBe(false); }); - it("loads and normalizes the wiki memory palace", async () => { + it("loads and normalizes the wiki wiki overview", async () => { const { state, request } = createState(); state.hello = { type: "hello-ok", protocol: 4, auth: { role: "operator", scopes: [] }, - features: { methods: ["wiki.palace"] }, + features: { methods: ["wiki.overview"] }, }; state.configSnapshot = { hash: "hash-1", @@ -639,43 +639,41 @@ describe("dreaming controller", () => { ], }); - await loadWikiMemoryPalace(state); + await loadWikiOverview(state); - expect(request).toHaveBeenCalledWith("wiki.palace", {}); - expect(state.wikiMemoryPalace?.totalItems).toBe(1); - expect(state.wikiMemoryPalace?.totalPages).toBe(2); - expect(state.wikiMemoryPalace?.pageCounts.source).toBe(1); - expect(state.wikiMemoryPalace?.pageCounts.synthesis).toBe(1); - expect(state.wikiMemoryPalace?.totalClaims).toBe(2); - expect(state.wikiMemoryPalace?.clusters).toHaveLength(1); - expect(state.wikiMemoryPalace?.clusters[0]?.key).toBe("synthesis"); - expect(state.wikiMemoryPalace?.clusters[0]?.label).toBe("Syntheses"); - expect(state.wikiMemoryPalace?.clusters[0]?.items).toHaveLength(1); - expect(state.wikiMemoryPalace?.clusters[0]?.items[0]?.title).toBe("Travel system"); - expect(state.wikiMemoryPalace?.clusters[0]?.items[0]?.claims).toEqual([ - "prefers direct receipts", - ]); - expect(state.wikiMemoryPalaceError).toBeNull(); - expect(state.wikiMemoryPalaceLoading).toBe(false); + expect(request).toHaveBeenCalledWith("wiki.overview", {}); + expect(state.wikiOverview?.totalItems).toBe(1); + expect(state.wikiOverview?.totalPages).toBe(2); + expect(state.wikiOverview?.pageCounts.source).toBe(1); + expect(state.wikiOverview?.pageCounts.synthesis).toBe(1); + expect(state.wikiOverview?.totalClaims).toBe(2); + expect(state.wikiOverview?.clusters).toHaveLength(1); + expect(state.wikiOverview?.clusters[0]?.key).toBe("synthesis"); + expect(state.wikiOverview?.clusters[0]?.label).toBe("Syntheses"); + expect(state.wikiOverview?.clusters[0]?.items).toHaveLength(1); + expect(state.wikiOverview?.clusters[0]?.items[0]?.title).toBe("Travel system"); + expect(state.wikiOverview?.clusters[0]?.items[0]?.claims).toEqual(["prefers direct receipts"]); + expect(state.wikiOverviewError).toBeNull(); + expect(state.wikiOverviewLoading).toBe(false); }); - it("loads the wiki memory palace for the selected agent", async () => { + it("loads the wiki wiki overview for the selected agent", async () => { const { state, request } = createState(); state.selectedAgentId = "marketing"; state.hello = { type: "hello-ok", protocol: 4, auth: { role: "operator", scopes: [] }, - features: { methods: ["wiki.palace"] }, + features: { methods: ["wiki.overview"] }, }; request.mockResolvedValue({ totalItems: 1, clusters: [] }); - await loadWikiMemoryPalace(state); + await loadWikiOverview(state); - expect(request).toHaveBeenCalledWith("wiki.palace", { agentId: "marketing" }); + expect(request).toHaveBeenCalledWith("wiki.overview", { agentId: "marketing" }); }); - it("starts a new selected-agent palace load and ignores stale completions", async () => { + it("starts a new selected-agent wiki overview load and ignores stale completions", async () => { const { state, request } = createState(); const agentA = createDeferred(); const agentB = createDeferred(); @@ -683,7 +681,7 @@ describe("dreaming controller", () => { type: "hello-ok", protocol: 4, auth: { role: "operator", scopes: [] }, - features: { methods: ["wiki.palace"] }, + features: { methods: ["wiki.overview"] }, }; request.mockImplementation(async (_method: string, payload?: unknown) => { const agentId = @@ -694,29 +692,29 @@ describe("dreaming controller", () => { }); state.selectedAgentId = "agent-a"; - const firstLoad = loadWikiMemoryPalace(state); + const firstLoad = loadWikiOverview(state); state.selectedAgentId = "agent-b"; - const secondLoad = loadWikiMemoryPalace(state); + const secondLoad = loadWikiOverview(state); agentB.resolve({ totalItems: 2, clusters: [] }); await secondLoad; agentA.resolve({ totalItems: 1, clusters: [] }); await firstLoad; - expect(request).toHaveBeenCalledWith("wiki.palace", { agentId: "agent-a" }); - expect(request).toHaveBeenCalledWith("wiki.palace", { agentId: "agent-b" }); - expect(state.wikiMemoryPalace?.totalItems).toBe(2); - expect(state.wikiMemoryPalaceLoading).toBe(false); - expect(state.wikiMemoryPalaceError).toBeNull(); + expect(request).toHaveBeenCalledWith("wiki.overview", { agentId: "agent-a" }); + expect(request).toHaveBeenCalledWith("wiki.overview", { agentId: "agent-b" }); + expect(state.wikiOverview?.totalItems).toBe(2); + expect(state.wikiOverviewLoading).toBe(false); + expect(state.wikiOverviewError).toBeNull(); }); - it("derives legacy wiki memory palace page counts from clusters", async () => { + it("derives legacy wiki wiki overview page counts from clusters", async () => { const { state, request } = createState(); state.hello = { type: "hello-ok", protocol: 4, auth: { role: "operator", scopes: [] }, - features: { methods: ["wiki.palace"] }, + features: { methods: ["wiki.overview"] }, }; state.configSnapshot = { hash: "hash-1", @@ -748,10 +746,10 @@ describe("dreaming controller", () => { ], }); - await loadWikiMemoryPalace(state); + await loadWikiOverview(state); - expect(state.wikiMemoryPalace?.totalPages).toBe(1); - expect(state.wikiMemoryPalace?.pageCounts).toEqual({ + expect(state.wikiOverview?.totalPages).toBe(1); + expect(state.wikiOverview?.pageCounts).toEqual({ synthesis: 1, entity: 0, concept: 0, @@ -760,7 +758,7 @@ describe("dreaming controller", () => { }); }); - it("falls back to config gating for wiki memory palace when methods are not advertised", async () => { + it("falls back to config gating for wiki wiki overview when methods are not advertised", async () => { const { state, request } = createState(); state.configSnapshot = { hash: "hash-1", @@ -782,24 +780,24 @@ describe("dreaming controller", () => { clusters: [], }); - await loadWikiMemoryPalace(state); + await loadWikiOverview(state); - expect(request).toHaveBeenCalledWith("wiki.palace", {}); - expect(state.wikiMemoryPalace?.totalItems).toBe(1); - expect(state.wikiMemoryPalace?.totalPages).toBe(1); - expect(state.wikiMemoryPalace?.pageCounts).toEqual({ + expect(request).toHaveBeenCalledWith("wiki.overview", {}); + expect(state.wikiOverview?.totalItems).toBe(1); + expect(state.wikiOverview?.totalPages).toBe(1); + expect(state.wikiOverview?.pageCounts).toEqual({ synthesis: 0, entity: 0, concept: 0, source: 0, report: 0, }); - expect(state.wikiMemoryPalace?.totalClaims).toBe(2); - expect(state.wikiMemoryPalaceError).toBeNull(); - expect(state.wikiMemoryPalaceLoading).toBe(false); + expect(state.wikiOverview?.totalClaims).toBe(2); + expect(state.wikiOverviewError).toBeNull(); + expect(state.wikiOverviewLoading).toBe(false); }); - it("skips wiki memory palace when memory-wiki is not enabled", async () => { + it("skips wiki wiki overview when memory-wiki is not enabled", async () => { const { state, request } = createState(); state.configSnapshot = { hash: "hash-1", @@ -807,7 +805,7 @@ describe("dreaming controller", () => { plugins: {}, }, }; - state.wikiMemoryPalace = { + state.wikiOverview = { totalItems: 1, totalPages: 1, pageCounts: { @@ -822,17 +820,17 @@ describe("dreaming controller", () => { totalContradictions: 0, clusters: [], }; - state.wikiMemoryPalaceError = "unknown method: wiki.palace"; + state.wikiOverviewError = "unknown method: wiki.overview"; - await loadWikiMemoryPalace(state); + await loadWikiOverview(state); expect(request).not.toHaveBeenCalled(); - expect(state.wikiMemoryPalace).toBeNull(); - expect(state.wikiMemoryPalaceError).toBeNull(); - expect(state.wikiMemoryPalaceLoading).toBe(false); + expect(state.wikiOverview).toBeNull(); + expect(state.wikiOverviewError).toBeNull(); + expect(state.wikiOverviewLoading).toBe(false); }); - it("skips wiki memory palace when the gateway does not advertise the method", async () => { + it("skips wiki wiki overview when the gateway does not advertise the method", async () => { const { state, request } = createState(); state.hello = { type: "hello-ok", @@ -852,7 +850,7 @@ describe("dreaming controller", () => { }, }, }; - state.wikiMemoryPalace = { + state.wikiOverview = { totalItems: 1, totalPages: 1, pageCounts: { @@ -867,14 +865,14 @@ describe("dreaming controller", () => { totalContradictions: 0, clusters: [], }; - state.wikiMemoryPalaceError = "unknown method: wiki.palace"; + state.wikiOverviewError = "unknown method: wiki.overview"; - await loadWikiMemoryPalace(state); + await loadWikiOverview(state); expect(request).not.toHaveBeenCalled(); - expect(state.wikiMemoryPalace).toBeNull(); - expect(state.wikiMemoryPalaceError).toBeNull(); - expect(state.wikiMemoryPalaceLoading).toBe(false); + expect(state.wikiOverview).toBeNull(); + expect(state.wikiOverviewError).toBeNull(); + expect(state.wikiOverviewLoading).toBe(false); }); it("patches config to update global dreaming enablement", async () => { diff --git a/ui/src/pages/agents/memory/dreaming.ts b/ui/src/pages/agents/memory/dreaming.ts index cb920d6edd7c..6117ee1b06b1 100644 --- a/ui/src/pages/agents/memory/dreaming.ts +++ b/ui/src/pages/agents/memory/dreaming.ts @@ -127,7 +127,7 @@ export type WikiImportInsights = { clusters: WikiImportInsightCluster[]; }; -type WikiMemoryPalaceItem = { +type WikiOverviewItem = { pagePath: string; title: string; kind: "entity" | "concept" | "source" | "synthesis" | "report"; @@ -143,27 +143,27 @@ type WikiMemoryPalaceItem = { snippet?: string; }; -type WikiMemoryPalaceCluster = { - key: WikiMemoryPalaceItem["kind"]; +type WikiOverviewCluster = { + key: WikiOverviewItem["kind"]; label: string; itemCount: number; claimCount: number; questionCount: number; contradictionCount: number; updatedAt?: string; - items: WikiMemoryPalaceItem[]; + items: WikiOverviewItem[]; }; -type WikiMemoryPalacePageCounts = Record; +type WikiOverviewPageCounts = Record; -export type WikiMemoryPalace = { +export type WikiOverview = { totalItems: number; totalPages: number; - pageCounts: WikiMemoryPalacePageCounts; + pageCounts: WikiOverviewPageCounts; totalClaims: number; totalQuestions: number; totalContradictions: number; - clusters: WikiMemoryPalaceCluster[]; + clusters: WikiOverviewCluster[]; }; type DoctorMemoryStatusPayload = { @@ -199,7 +199,7 @@ type WikiImportInsightsPayload = { clusters?: unknown; }; -type WikiMemoryPalacePayload = { +type WikiOverviewPayload = { totalItems?: unknown; totalPages?: unknown; pageCounts?: unknown; @@ -244,13 +244,13 @@ export type DreamingState = { wikiImportInsightsLoading: boolean; wikiImportInsightsError: string | null; wikiImportInsights: WikiImportInsights | null; - wikiMemoryPalaceRequestAgentId?: string | null; - wikiMemoryPalaceRequestGeneration?: number; - wikiMemoryPalaceActiveRequestGeneration?: number | null; - wikiMemoryPalaceAgentId?: string | null; - wikiMemoryPalaceLoading: boolean; - wikiMemoryPalaceError: string | null; - wikiMemoryPalace: WikiMemoryPalace | null; + wikiOverviewRequestAgentId?: string | null; + wikiOverviewRequestGeneration?: number; + wikiOverviewActiveRequestGeneration?: number | null; + wikiOverviewAgentId?: string | null; + wikiOverviewLoading: boolean; + wikiOverviewError: string | null; + wikiOverview: WikiOverview | null; lastError: string | null; }; @@ -283,9 +283,9 @@ export function createDreamingState( wikiImportInsightsLoading: false, wikiImportInsightsError: null, wikiImportInsights: null, - wikiMemoryPalaceLoading: false, - wikiMemoryPalaceError: null, - wikiMemoryPalace: null, + wikiOverviewLoading: false, + wikiOverviewError: null, + wikiOverview: null, lastError: null, }; } @@ -636,7 +636,7 @@ function normalizeWikiImportInsights(raw: unknown): WikiImportInsights { }; } -function normalizeWikiPageKind(value: unknown): WikiMemoryPalaceItem["kind"] | undefined { +function normalizeWikiPageKind(value: unknown): WikiOverviewItem["kind"] | undefined { return value === "entity" || value === "concept" || value === "source" || @@ -646,7 +646,7 @@ function normalizeWikiPageKind(value: unknown): WikiMemoryPalaceItem["kind"] | u : undefined; } -function createEmptyWikiMemoryPalacePageCounts(): WikiMemoryPalacePageCounts { +function createEmptyWikiOverviewPageCounts(): WikiOverviewPageCounts { return { synthesis: 0, entity: 0, @@ -656,10 +656,10 @@ function createEmptyWikiMemoryPalacePageCounts(): WikiMemoryPalacePageCounts { }; } -function normalizeWikiMemoryPalacePageCounts( +function normalizeWikiOverviewPageCounts( raw: unknown, - fallback: WikiMemoryPalacePageCounts, -): WikiMemoryPalacePageCounts { + fallback: WikiOverviewPageCounts, +): WikiOverviewPageCounts { const record = asRecord(raw); return { synthesis: normalizeFiniteInt(record?.synthesis, fallback.synthesis), @@ -670,7 +670,7 @@ function normalizeWikiMemoryPalacePageCounts( }; } -function sumWikiMemoryPalacePageCounts(pageCounts: WikiMemoryPalacePageCounts): number { +function sumWikiOverviewPageCounts(pageCounts: WikiOverviewPageCounts): number { return ( pageCounts.synthesis + pageCounts.entity + @@ -680,7 +680,7 @@ function sumWikiMemoryPalacePageCounts(pageCounts: WikiMemoryPalacePageCounts): ); } -function normalizeWikiMemoryPalaceItem(raw: unknown): WikiMemoryPalaceItem | null { +function normalizeWikiOverviewItem(raw: unknown): WikiOverviewItem | null { const record = asRecord(raw); const pagePath = normalizeTrimmedString(record?.pagePath); const title = normalizeTrimmedString(record?.title); @@ -711,7 +711,7 @@ function normalizeWikiMemoryPalaceItem(raw: unknown): WikiMemoryPalaceItem | nul }; } -function normalizeWikiMemoryPalaceCluster(raw: unknown): WikiMemoryPalaceCluster | null { +function normalizeWikiOverviewCluster(raw: unknown): WikiOverviewCluster | null { const record = asRecord(raw); const key = normalizeWikiPageKind(record?.key); const label = normalizeTrimmedString(record?.label); @@ -720,8 +720,8 @@ function normalizeWikiMemoryPalaceCluster(raw: unknown): WikiMemoryPalaceCluster } const items = Array.isArray(record?.items) ? record.items - .map((entry) => normalizeWikiMemoryPalaceItem(entry)) - .filter((entry): entry is WikiMemoryPalaceItem => entry !== null) + .map((entry) => normalizeWikiOverviewItem(entry)) + .filter((entry): entry is WikiOverviewItem => entry !== null) : []; return { key, @@ -746,23 +746,23 @@ function normalizeWikiMemoryPalaceCluster(raw: unknown): WikiMemoryPalaceCluster }; } -function normalizeWikiMemoryPalace(raw: unknown): WikiMemoryPalace { +function normalizeWikiOverview(raw: unknown): WikiOverview { const record = asRecord(raw); const clusters = Array.isArray(record?.clusters) ? record.clusters - .map((entry) => normalizeWikiMemoryPalaceCluster(entry)) - .filter((entry): entry is WikiMemoryPalaceCluster => entry !== null) + .map((entry) => normalizeWikiOverviewCluster(entry)) + .filter((entry): entry is WikiOverviewCluster => entry !== null) : []; const totalItems = normalizeFiniteInt( record?.totalItems, clusters.reduce((sum, cluster) => sum + cluster.itemCount, 0), ); - const fallbackPageCounts = createEmptyWikiMemoryPalacePageCounts(); + const fallbackPageCounts = createEmptyWikiOverviewPageCounts(); for (const cluster of clusters) { fallbackPageCounts[cluster.key] += cluster.itemCount; } - const pageCounts = normalizeWikiMemoryPalacePageCounts(record?.pageCounts, fallbackPageCounts); - const fallbackTotalPages = sumWikiMemoryPalacePageCounts(pageCounts) || totalItems; + const pageCounts = normalizeWikiOverviewPageCounts(record?.pageCounts, fallbackPageCounts); + const fallbackTotalPages = sumWikiOverviewPageCounts(pageCounts) || totalItems; return { totalItems, totalPages: normalizeFiniteInt(record?.totalPages, fallbackTotalPages), @@ -1022,58 +1022,58 @@ export async function loadWikiImportInsights(state: DreamingState): Promise { +export async function loadWikiOverview(state: DreamingState): Promise { if (!state.client || !state.connected) { return; } const agentId = resolveSelectedAgentId(state); - if (state.wikiMemoryPalaceLoading && state.wikiMemoryPalaceRequestAgentId === agentId) { + if (state.wikiOverviewLoading && state.wikiOverviewRequestAgentId === agentId) { return; } - if (state.wikiMemoryPalaceAgentId !== agentId) { - state.wikiMemoryPalace = null; + if (state.wikiOverviewAgentId !== agentId) { + state.wikiOverview = null; } - if (!canCallMemoryWikiMethod(state, "wiki.palace")) { - state.wikiMemoryPalaceActiveRequestGeneration = null; - state.wikiMemoryPalaceRequestAgentId = null; - state.wikiMemoryPalaceLoading = false; - state.wikiMemoryPalace = null; - state.wikiMemoryPalaceError = null; + if (!canCallMemoryWikiMethod(state, "wiki.overview")) { + state.wikiOverviewActiveRequestGeneration = null; + state.wikiOverviewRequestAgentId = null; + state.wikiOverviewLoading = false; + state.wikiOverview = null; + state.wikiOverviewError = null; return; } - const requestGeneration = (state.wikiMemoryPalaceRequestGeneration ?? 0) + 1; - state.wikiMemoryPalaceRequestGeneration = requestGeneration; - state.wikiMemoryPalaceActiveRequestGeneration = requestGeneration; - state.wikiMemoryPalaceRequestAgentId = agentId; - state.wikiMemoryPalaceLoading = true; - state.wikiMemoryPalaceError = null; + const requestGeneration = (state.wikiOverviewRequestGeneration ?? 0) + 1; + state.wikiOverviewRequestGeneration = requestGeneration; + state.wikiOverviewActiveRequestGeneration = requestGeneration; + state.wikiOverviewRequestAgentId = agentId; + state.wikiOverviewLoading = true; + state.wikiOverviewError = null; try { - const payload = await state.client.request( - "wiki.palace", + const payload = await state.client.request( + "wiki.overview", buildSelectedAgentPayloadForAgentId(agentId), ); if ( - state.wikiMemoryPalaceActiveRequestGeneration !== requestGeneration || - state.wikiMemoryPalaceRequestAgentId !== agentId || + state.wikiOverviewActiveRequestGeneration !== requestGeneration || + state.wikiOverviewRequestAgentId !== agentId || resolveSelectedAgentId(state) !== agentId ) { return; } - state.wikiMemoryPalace = normalizeWikiMemoryPalace(payload); - state.wikiMemoryPalaceAgentId = agentId; + state.wikiOverview = normalizeWikiOverview(payload); + state.wikiOverviewAgentId = agentId; } catch (err) { if ( - state.wikiMemoryPalaceActiveRequestGeneration === requestGeneration && - state.wikiMemoryPalaceRequestAgentId === agentId && + state.wikiOverviewActiveRequestGeneration === requestGeneration && + state.wikiOverviewRequestAgentId === agentId && resolveSelectedAgentId(state) === agentId ) { - state.wikiMemoryPalaceError = String(err); + state.wikiOverviewError = String(err); } } finally { - if (state.wikiMemoryPalaceActiveRequestGeneration === requestGeneration) { - state.wikiMemoryPalaceLoading = false; - state.wikiMemoryPalaceRequestAgentId = null; - state.wikiMemoryPalaceActiveRequestGeneration = null; + if (state.wikiOverviewActiveRequestGeneration === requestGeneration) { + state.wikiOverviewLoading = false; + state.wikiOverviewRequestAgentId = null; + state.wikiOverviewActiveRequestGeneration = null; } } } diff --git a/ui/src/pages/agents/memory/memory-panel.ts b/ui/src/pages/agents/memory/memory-panel.ts index 914e679512dc..9bcdec7833c4 100644 --- a/ui/src/pages/agents/memory/memory-panel.ts +++ b/ui/src/pages/agents/memory/memory-panel.ts @@ -21,7 +21,7 @@ import { loadDreamDiary, loadDreamingStatus, loadWikiImportInsights, - loadWikiMemoryPalace, + loadWikiOverview, repairDreamingArtifacts, resetGroundedShortTerm, resetDreamDiary, @@ -298,7 +298,7 @@ class AgentMemoryPanel extends OpenClawLightDomElement { this.runDreamingTask(loadDreamingStatus, scope), this.runDreamingTask(loadDreamDiary, scope), this.runDreamingTask(loadWikiImportInsights, scope), - this.runDreamingTask(loadWikiMemoryPalace, scope), + this.runDreamingTask(loadWikiOverview, scope), ]); } @@ -470,13 +470,13 @@ class AgentMemoryPanel extends OpenClawLightDomElement { wikiImportInsightsLoading: dreaming.wikiImportInsightsLoading, wikiImportInsightsError: dreaming.wikiImportInsightsError, wikiImportInsights: dreaming.wikiImportInsights, - wikiMemoryPalaceLoading: dreaming.wikiMemoryPalaceLoading, - wikiMemoryPalaceError: dreaming.wikiMemoryPalaceError, - wikiMemoryPalace: dreaming.wikiMemoryPalace, + wikiOverviewLoading: dreaming.wikiOverviewLoading, + wikiOverviewError: dreaming.wikiOverviewError, + wikiOverview: dreaming.wikiOverview, onRefresh: () => void this.loadAll(true), onRefreshDiary: () => void this.runDreamingTask(loadDreamDiary), onRefreshImports: () => void this.refreshWikiData(loadWikiImportInsights), - onRefreshMemoryPalace: () => void this.refreshWikiData(loadWikiMemoryPalace), + onRefreshWikiOverview: () => void this.refreshWikiData(loadWikiOverview), onOpenConfig: () => void this.context.runtimeConfig.openFile(), onOpenWikiPage: (lookup) => this.openWikiPage(lookup), onBackfillDiary: () => void this.runDreamingTask(backfillDreamDiary), diff --git a/ui/src/pages/agents/memory/view.test.ts b/ui/src/pages/agents/memory/view.test.ts index 62a46b84774c..d316f6cecef1 100644 --- a/ui/src/pages/agents/memory/view.test.ts +++ b/ui/src/pages/agents/memory/view.test.ts @@ -231,9 +231,9 @@ function buildProps(overrides?: Partial): DreamingProps { }, ], }, - wikiMemoryPalaceLoading: false, - wikiMemoryPalaceError: null, - wikiMemoryPalace: { + wikiOverviewLoading: false, + wikiOverviewError: null, + wikiOverview: { totalItems: 1, totalPages: 2, pageCounts: { @@ -278,7 +278,7 @@ function buildProps(overrides?: Partial): DreamingProps { onRefresh: () => {}, onRefreshDiary: () => {}, onRefreshImports: () => {}, - onRefreshMemoryPalace: () => {}, + onRefreshWikiOverview: () => {}, onOpenConfig: () => {}, onOpenWikiPage: async () => null, onBackfillDiary: () => {}, @@ -430,12 +430,12 @@ describe("dreaming view", () => { expect(subtabs).toEqual([ { label: "Dreams", active: false }, { label: "Imported Insights", active: true }, - { label: "Memory Palace", active: false }, + { label: "Memory Wiki", active: false }, ]); container - .querySelector("#dream-diary-tab-palace") + .querySelector("#dream-diary-tab-wiki") ?.dispatchEvent(new MouseEvent("click", { detail: 1, bubbles: true })); - expect(viewState.activeDiarySubTab).toBe("palace"); + expect(viewState.activeDiarySubTab).toBe("wiki"); expect(onViewStateChange).toHaveBeenCalledOnce(); expect(compactText(container.querySelector(".dreams-diary__date"))).toBe( "Travel · 1 chats · 1 signals", @@ -550,9 +550,9 @@ describe("dreaming view", () => { setDreamSubTab("scene"); }); - it("renders the memory palace inside the diary tab", () => { + it("renders the wiki overview inside the diary tab", () => { setDreamSubTab("diary"); - setDreamDiarySubTab("palace"); + setDreamDiarySubTab("wiki"); const container = renderInto(buildProps()); expect(compactText(container.querySelector(".dreams-diary__date"))).toBe( "Vault · 2 pages · 2 claim rows · 1 open question · 1 contradiction", @@ -583,15 +583,15 @@ describe("dreaming view", () => { setDreamSubTab("scene"); }); - it("keeps non-report memory palace card clicks on details", () => { + it("keeps non-report wiki overview card clicks on details", () => { setDreamSubTab("diary"); - setDreamDiarySubTab("palace"); + setDreamDiarySubTab("wiki"); const container = document.createElement("div"); const rerender = () => render(renderDreaming(props), container); const props: DreamingProps = buildProps({ onViewStateChange: rerender }); rerender(); - const card = expectElement(container, "[data-palace-page='syntheses/travel-system.md']"); + const card = expectElement(container, "[data-wiki-page='syntheses/travel-system.md']"); card.dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(textItems(container, ".dreams-diary__insight-list strong")).toContain("Page details"); @@ -599,9 +599,9 @@ describe("dreaming view", () => { setDreamSubTab("scene"); }); - it("opens report memory palace cards on primary click", async () => { + it("opens report wiki overview cards on primary click", async () => { setDreamSubTab("diary"); - setDreamDiarySubTab("palace"); + setDreamDiarySubTab("wiki"); const onOpenWikiPage = vi.fn().mockResolvedValue({ title: "Weekly stock report", path: "reports/weekly-stock.md", @@ -614,7 +614,7 @@ describe("dreaming view", () => { const props: DreamingProps = buildProps({ onOpenWikiPage, onViewStateChange: rerender, - wikiMemoryPalace: { + wikiOverview: { totalItems: 1, totalPages: 1, pageCounts: { @@ -656,7 +656,7 @@ describe("dreaming view", () => { }); rerender(); - const card = expectElement(container, "[data-palace-page='reports/weekly-stock.md']"); + const card = expectElement(container, "[data-wiki-page='reports/weekly-stock.md']"); card.dispatchEvent(new MouseEvent("click", { bubbles: true })); await Promise.resolve(); await Promise.resolve(); @@ -683,7 +683,7 @@ describe("dreaming view", () => { it("shows a memory-wiki enablement CTA when wiki subtabs are selected but the plugin is disabled", () => { setDreamSubTab("diary"); - setDreamDiarySubTab("palace"); + setDreamDiarySubTab("wiki"); const onOpenConfig = vi.fn(); const container = renderInto( buildProps({ @@ -697,7 +697,7 @@ describe("dreaming view", () => { expect( [...container.querySelectorAll(".dreams-diary__empty-hint")].map((node) => compactText(node)), ).toEqual([ - "Imported Insights and Memory Palace are provided by the bundled memory-wiki plugin.", + "Imported Insights and Memory Wiki are provided by the bundled memory-wiki plugin.", "Enable plugins.entries.memory-wiki.enabled = true, then reload this tab.", ]); diff --git a/ui/src/pages/agents/memory/view.ts b/ui/src/pages/agents/memory/view.ts index 9e2428fb456a..231bcec76d76 100644 --- a/ui/src/pages/agents/memory/view.ts +++ b/ui/src/pages/agents/memory/view.ts @@ -13,7 +13,7 @@ import "../../../components/modal-dialog.ts"; import { toSanitizedMarkdownHtml } from "../../../components/markdown.ts"; import { t } from "../../../i18n/index.ts"; import "../../../styles/dreams.css"; -import type { DreamingEntry, WikiImportInsights, WikiMemoryPalace } from "./dreaming.ts"; +import type { DreamingEntry, WikiImportInsights, WikiOverview } from "./dreaming.ts"; // ── Diary entry parser ───────────────────────────────────────────────── @@ -130,13 +130,13 @@ type DreamingProps = { wikiImportInsightsLoading: boolean; wikiImportInsightsError: string | null; wikiImportInsights: WikiImportInsights | null; - wikiMemoryPalaceLoading: boolean; - wikiMemoryPalaceError: string | null; - wikiMemoryPalace: WikiMemoryPalace | null; + wikiOverviewLoading: boolean; + wikiOverviewError: string | null; + wikiOverview: WikiOverview | null; onRefresh: () => void; onRefreshDiary: () => void; onRefreshImports: () => void; - onRefreshMemoryPalace: () => void; + onRefreshWikiOverview: () => void; onOpenConfig: () => void; onOpenWikiPage: (lookup: string) => Promise<{ title: string; @@ -160,7 +160,7 @@ const DREAM_PHRASE_KEYS = [ "dreaming.phrases.tidyingKnowledgeGraph", "dreaming.phrases.replayingConversations", "dreaming.phrases.weavingShortTerm", - "dreaming.phrases.defragmentingMindPalace", + "dreaming.phrases.defragmentingMemoryLane", "dreaming.phrases.filingLooseThoughts", "dreaming.phrases.connectingDots", "dreaming.phrases.compostingContext", @@ -189,10 +189,10 @@ export type DreamingViewState = { dreamIndex: number; dreamLastSwap: number; activeSubTab: "scene" | "diary" | "advanced"; - activeDiarySubTab: "dreams" | "insights" | "palace"; + activeDiarySubTab: "dreams" | "insights" | "wiki"; advancedWaitingSort: "recent" | "signals"; expandedInsightCards: Set; - expandedPalaceCards: Set; + expandedWikiCards: Set; diaryPage: number; wikiPreviewRequestId: number; wikiPreviewOpen: boolean; @@ -214,7 +214,7 @@ export function createDreamingViewState(): DreamingViewState { activeDiarySubTab: "dreams", advancedWaitingSort: "recent", expandedInsightCards: new Set(), - expandedPalaceCards: new Set(), + expandedWikiCards: new Set(), diaryPage: 0, wikiPreviewRequestId: 0, wikiPreviewOpen: false, @@ -516,7 +516,7 @@ function formatMessageCount(count: number): string { return t("dreaming.wiki.counts.messages", { count: String(count) }); } -const MEMORY_PALACE_PAGE_COUNT_ORDER: Array = [ +const WIKI_OVERVIEW_PAGE_COUNT_ORDER: Array = [ "source", "synthesis", "report", @@ -524,7 +524,7 @@ const MEMORY_PALACE_PAGE_COUNT_ORDER: Array { +function formatWikiOverviewPageBreakdown(pageCounts: WikiOverview["pageCounts"]): string { + const parts = WIKI_OVERVIEW_PAGE_COUNT_ORDER.map((kind) => { const count = pageCounts[kind]; return count > 0 ? t("dreaming.wiki.pageGroupSummary", { - label: formatMemoryPalacePageCountLabel(kind), + label: formatWikiOverviewPageCountLabel(kind), count: formatPageCount(count), }) : null; @@ -553,7 +553,7 @@ function formatMemoryPalacePageBreakdown(pageCounts: WikiMemoryPalace["pageCount return parts.length > 0 ? parts.join("; ") : t("dreaming.wiki.noPagesYet"); } -function formatMemoryPalaceClusterSummary(cluster: WikiMemoryPalace["clusters"][number]): string { +function formatWikiOverviewClusterSummary(cluster: WikiOverview["clusters"][number]): string { const parts = [ t("dreaming.wiki.sectionPageSummary", { label: cluster.label, @@ -730,8 +730,8 @@ function renderDiarySubtabExplainer(activeDiarySubTab: DreamingViewState["active return html`

${t("dreaming.wiki.dreamsExplainer")}

`; case "insights": return html`

${t("dreaming.wiki.insightsExplainer")}

`; - case "palace": - return html`

${t("dreaming.wiki.palaceExplainer")}

`; + case "wiki": + return html`

${t("dreaming.wiki.wikiExplainer")}

`; } return nothing; } @@ -1242,15 +1242,15 @@ function renderDiaryImportsSection(props: DreamingProps) { `; } -function renderMemoryPalaceSection(props: DreamingProps) { +function renderWikiOverviewSection(props: DreamingProps) { const state = props.viewState; - const palace = props.wikiMemoryPalace; - const clusters = palace?.clusters ?? []; + const overview = props.wikiOverview; + const clusters = overview?.clusters ?? []; - if (props.wikiMemoryPalaceLoading && clusters.length === 0) { + if (props.wikiOverviewLoading && clusters.length === 0) { return html`
-
${t("dreaming.wiki.loadingPalace")}
+
${t("dreaming.wiki.loadingWiki")}
`; } @@ -1258,22 +1258,22 @@ function renderMemoryPalaceSection(props: DreamingProps) { if (clusters.length === 0) { return html`
-
${t("dreaming.wiki.emptyPalace")}
-
${t("dreaming.wiki.emptyPalaceHint")}
+
${t("dreaming.wiki.emptyWiki")}
+
${t("dreaming.wiki.emptyWikiHint")}
`; } const clusterIndex = Math.max(0, Math.min(state.diaryPage, clusters.length - 1)); - const cluster = expectDefined(clusters[clusterIndex], "selected memory palace cluster"); - const totalPages = palace?.totalPages ?? palace?.totalItems ?? 0; - const totalClaims = palace?.totalClaims ?? 0; - const totalQuestions = palace?.totalQuestions ?? 0; - const totalContradictions = palace?.totalContradictions ?? 0; - const pageBreakdown = palace - ? formatMemoryPalacePageBreakdown(palace.pageCounts) + const cluster = expectDefined(clusters[clusterIndex], "selected memory overview cluster"); + const totalPages = overview?.totalPages ?? overview?.totalItems ?? 0; + const totalClaims = overview?.totalClaims ?? 0; + const totalQuestions = overview?.totalQuestions ?? 0; + const totalContradictions = overview?.totalContradictions ?? 0; + const pageBreakdown = overview + ? formatWikiOverviewPageBreakdown(overview.pageCounts) : t("dreaming.wiki.noPagesYet"); - const clusterSummary = formatMemoryPalaceClusterSummary(cluster); + const clusterSummary = formatWikiOverviewClusterSummary(cluster); const vaultMeta = [ formatPageCount(totalPages), ...(totalClaims > 0 ? [formatClaimRowCount(totalClaims)] : []), @@ -1300,7 +1300,7 @@ function renderMemoryPalaceSection(props: DreamingProps) { )} -
+
${t("dreaming.wiki.vault")} · ${vaultMeta.join(" · ")}
@@ -1318,26 +1318,22 @@ function renderMemoryPalaceSection(props: DreamingProps) {
${cluster.items.map((item) => { - const expanded = state.expandedPalaceCards.has(item.pagePath); + const expanded = state.expandedWikiCards.has(item.pagePath); return html`
{ if (item.kind === "report") { void openWikiPreview(item.pagePath, props); return; } - toggleExpandedCard( - state.expandedPalaceCards, - item.pagePath, - props.onViewStateChange, - ); + toggleExpandedCard(state.expandedWikiCards, item.pagePath, props.onViewStateChange); }} >
${item.title}
- + ${formatKindLabel(item.kind)}
@@ -1403,7 +1399,7 @@ function renderMemoryPalaceSection(props: DreamingProps) { @click=${(event: Event) => { event.stopPropagation(); toggleExpandedCard( - state.expandedPalaceCards, + state.expandedWikiCards, item.pagePath, props.onViewStateChange, ); @@ -1498,14 +1494,14 @@ function renderDreamDiaryEntries(props: DreamingProps) { function renderDiarySection(props: DreamingProps) { const state = props.viewState; const activeDiarySubTab = state.activeDiarySubTab; - const wikiTabSelected = activeDiarySubTab === "insights" || activeDiarySubTab === "palace"; + const wikiTabSelected = activeDiarySubTab === "insights" || activeDiarySubTab === "wiki"; const memoryWikiUnavailable = wikiTabSelected && !props.memoryWikiEnabled; const diaryError = activeDiarySubTab === "dreams" ? props.dreamDiaryError : activeDiarySubTab === "insights" ? props.wikiImportInsightsError - : props.wikiMemoryPalaceError; + : props.wikiOverviewError; if (diaryError && !memoryWikiUnavailable) { return html`
@@ -1525,7 +1521,7 @@ function renderDiarySection(props: DreamingProps) { tabs: [ { value: "dreams", label: t("dreaming.wiki.dreamsTab") }, { value: "insights", label: t("dreaming.wiki.insightsTab") }, - { value: "palace", label: t("dreaming.wiki.palaceTab") }, + { value: "wiki", label: t("dreaming.wiki.wikiTab") }, ], ariaLabel: t("dreaming.diary.title"), panelId: "dream-diary-panel", @@ -1546,7 +1542,7 @@ function renderDiarySection(props: DreamingProps) { ? props.dreamDiaryLoading : activeDiarySubTab === "insights" ? props.wikiImportInsightsLoading - : props.wikiMemoryPalaceLoading)} + : props.wikiOverviewLoading)} @click=${() => { state.diaryPage = 0; if (memoryWikiUnavailable) { @@ -1556,7 +1552,7 @@ function renderDiarySection(props: DreamingProps) { } else if (activeDiarySubTab === "insights") { props.onRefreshImports(); } else { - props.onRefreshMemoryPalace(); + props.onRefreshWikiOverview(); } }} > @@ -1570,7 +1566,7 @@ function renderDiarySection(props: DreamingProps) { ? props.wikiImportInsightsLoading ? "Reloading…" : "Reload" - : props.wikiMemoryPalaceLoading + : props.wikiOverviewLoading ? "Reloading…" : "Reload"} @@ -1608,7 +1604,7 @@ function renderDiarySection(props: DreamingProps) { ? renderDreamDiaryEntries(props) : activeDiarySubTab === "insights" ? renderDiaryImportsSection(props) - : renderMemoryPalaceSection(props)} + : renderWikiOverviewSection(props)}
${renderWikiPreviewOverlay(props)} diff --git a/ui/src/styles/dreams.css b/ui/src/styles/dreams.css index 8b76d6822796..b4b10f511da0 100644 --- a/ui/src/styles/dreams.css +++ b/ui/src/styles/dreams.css @@ -930,7 +930,7 @@ color: var(--muted); } -.dreams-diary__insight-badge--palace { +.dreams-diary__insight-badge--wiki { color: var(--accent); background: color-mix(in oklab, var(--accent-subtle) 72%, transparent); border-color: color-mix(in oklab, var(--accent) 24%, transparent);