From 5b2ff6e712dead108ce7ac586b149f0d569913e6 Mon Sep 17 00:00:00 2001 From: Christian Wendler Date: Wed, 8 Apr 2026 11:01:31 +0000 Subject: [PATCH 1/3] fix: WebUI form consistency, task visibility, markdown rendering - Fix select/input height mismatch in New Entry form (explicit 38px) - Convert Project + Agent fields to datalist with autocomplete from API - Add /api/tags endpoint + tags datalist for autocomplete - Move Active Tasks from invisible page-bottom to sidebar (always visible) - Render detail body as formatted Markdown instead of raw monospace - Simplify layout from 3-column to 2-column (tasks now in sidebar) Co-Authored-By: Claude Opus 4.6 --- palaia/web/routes/status.py | 17 +++++++++ palaia/web/static/app.js | 51 ++++++++++++++++++++++++++- palaia/web/static/index.html | 33 ++++++++++-------- palaia/web/static/style.css | 67 +++++++++++++++++++++++++----------- 4 files changed, 132 insertions(+), 36 deletions(-) diff --git a/palaia/web/routes/status.py b/palaia/web/routes/status.py index b03bb1d..fe8abb1 100644 --- a/palaia/web/routes/status.py +++ b/palaia/web/routes/status.py @@ -92,6 +92,23 @@ def list_agents(request: Request) -> dict: return {"agents": sorted(agents)} +@router.get("/tags") +def list_tags(request: Request) -> dict: + """List distinct tags found in entries.""" + from palaia.store import Store + + root = request.app.state.palaia_root + store = Store(root) + store.recover() + tags: set[str] = set() + for meta, _body, _tier in store.all_entries_unfiltered(include_cold=True): + for tag in meta.get("tags") or []: + tag = tag.strip() + if tag and tag != "auto-capture": + tags.add(tag) + return {"tags": sorted(tags)} + + @router.get("/doctor") def run_doctor(request: Request) -> dict: """Run palaia doctor and return results for the UI banner. diff --git a/palaia/web/static/app.js b/palaia/web/static/app.js index 68ea577..694f544 100644 --- a/palaia/web/static/app.js +++ b/palaia/web/static/app.js @@ -80,6 +80,39 @@ toast._timer = setTimeout(() => { t.className = "toast"; }, 3000); } + // ── Simple Markdown → HTML ──────────────────────────────────────────────── + function renderMarkdown(text) { + if (!text) return ""; + // Escape HTML first (security) + let html = esc(text); + // Code blocks (``` ... ```) + html = html.replace(/```[\s\S]*?```/g, (m) => { + const code = m.slice(3, -3).replace(/^\w*\n/, ""); + return "
" + code + "
"; + }); + // Inline code + html = html.replace(/`([^`]+)`/g, "$1"); + // Headings + html = html.replace(/^### (.+)$/gm, "

$1

"); + html = html.replace(/^## (.+)$/gm, "

$1

"); + html = html.replace(/^# (.+)$/gm, "

$1

"); + // Bold + italic + html = html.replace(/\*\*(.+?)\*\*/g, "$1"); + html = html.replace(/\*(.+?)\*/g, "$1"); + // Blockquotes + html = html.replace(/^> (.+)$/gm, "
$1
"); + // Unordered lists + html = html.replace(/^- (.+)$/gm, "
  • $1
  • "); + html = html.replace(/((?:
  • .*<\/li>\n?)+)/g, "
      $1
    "); + // Ordered lists + html = html.replace(/^\d+\. (.+)$/gm, "
  • $1
  • "); + // Paragraphs (double newline) + html = html.replace(/\n\n/g, "

    "); + // Single newlines within paragraphs →
    + html = html.replace(/\n/g, "
    "); + return "

    " + html + "

    "; + } + // ── Date formatting ────────────────────────────────────────────────────── function fmtDate(iso) { if (!iso) return "—"; @@ -103,8 +136,10 @@ try { const d = await apiGet("/api/projects"); const sel = $("filter-project"); + const dl = $("project-options"); for (const name of Object.keys(d.projects || {})) { sel.appendChild(el("option", { value: name }, name)); + dl.appendChild(el("option", { value: name })); } } catch (e) { /* non-fatal */ } } @@ -113,8 +148,20 @@ try { const d = await apiGet("/api/agents"); const sel = $("filter-agent"); + const dl = $("agent-options"); for (const name of d.agents || []) { sel.appendChild(el("option", { value: name }, name)); + dl.appendChild(el("option", { value: name })); + } + } catch (e) { /* non-fatal */ } + } + + async function loadTags() { + try { + const d = await apiGet("/api/tags"); + const dl = $("tag-options"); + for (const tag of d.tags || []) { + dl.appendChild(el("option", { value: tag })); } } catch (e) { /* non-fatal */ } } @@ -347,7 +394,8 @@ ), ); - const body = el("pre", { class: "detail-body" }, d.content || ""); + const body = el("div", { class: "detail-body" }); + body.innerHTML = renderMarkdown(d.content || ""); return el("div", { id: "inline-detail-" + id, class: "inline-detail" }, toolbar, dl, body); } @@ -598,6 +646,7 @@ loadStatus(); loadProjects(); loadAgents(); + loadTags(); loadDoctor(); loadEntries(); loadTasks(); diff --git a/palaia/web/static/index.html b/palaia/web/static/index.html index be7b42a..6c66a90 100644 --- a/palaia/web/static/index.html +++ b/palaia/web/static/index.html @@ -39,6 +39,9 @@ @@ -115,18 +130,6 @@ - - - @@ -168,18 +171,18 @@
    - +
    - +
    - +
    diff --git a/palaia/web/static/style.css b/palaia/web/static/style.css index 2386adc..136fd13 100644 --- a/palaia/web/static/style.css +++ b/palaia/web/static/style.css @@ -144,17 +144,13 @@ body { .doctor-warn .doctor-label { color: var(--yellow); } .doctor-error .doctor-label { color: var(--red); } -/* ── 3-column layout ─────────────────────────────────────────────────── */ +/* ── 2-column layout ─────────────────────────────────────────────────── */ .layout { display: grid; - grid-template-columns: 240px 1fr 300px; + grid-template-columns: 260px 1fr; gap: 20px; align-items: start; } -@media (max-width: 1200px) { - .layout { grid-template-columns: 220px 1fr; } - .tasks-panel { grid-column: 1 / -1; } -} @media (max-width: 800px) { .layout { grid-template-columns: 1fr; } } @@ -379,25 +375,54 @@ body { background: var(--bg); padding: 14px; border-radius: var(--radius); - font-family: var(--mono); - font-size: 0.82em; - white-space: pre-wrap; + font-size: 0.85em; word-wrap: break-word; - line-height: 1.5; + line-height: 1.6; max-height: 500px; overflow-y: auto; } - -/* ── Tasks panel ─────────────────────────────────────────────────────── */ -.tasks-panel { +.detail-body h1, .detail-body h2, .detail-body h3 { + color: var(--text-bright); + margin: 12px 0 6px; + line-height: 1.3; +} +.detail-body h1 { font-size: 1.2em; } +.detail-body h2 { font-size: 1.05em; } +.detail-body h3 { font-size: 0.95em; } +.detail-body p { margin: 6px 0; } +.detail-body ul, .detail-body ol { margin: 6px 0; padding-left: 20px; } +.detail-body li { margin: 3px 0; } +.detail-body code { background: var(--bg-card); - border: 1px solid var(--border); + padding: 1px 5px; + border-radius: 3px; + font-family: var(--mono); + font-size: 0.9em; +} +.detail-body pre { + background: var(--bg-card); + padding: 10px; border-radius: var(--radius); - padding: 16px; - position: sticky; - top: 16px; - max-height: calc(100vh - 32px); - overflow-y: auto; + font-family: var(--mono); + font-size: 0.88em; + white-space: pre-wrap; + margin: 8px 0; + border: 1px solid var(--border); +} +.detail-body strong { color: var(--text-bright); } +.detail-body em { color: var(--text); font-style: italic; } +.detail-body blockquote { + border-left: 3px solid var(--accent-dim); + padding-left: 12px; + color: var(--text-dim); + margin: 8px 0; +} + +/* ── Tasks (in sidebar) ──────────────────────────────────────────────── */ +.sidebar-tasks { + border-top: 1px solid var(--border); + margin-top: 14px; + padding-top: 14px; } .tasks-header { display: flex; @@ -529,8 +554,10 @@ body { color: var(--text); font-family: var(--font); font-size: 0.9em; + height: 38px; + line-height: 1.4; } -#entry-form textarea { font-family: var(--mono); resize: vertical; min-height: 120px; } +#entry-form textarea { font-family: var(--mono); resize: vertical; min-height: 120px; height: auto; } #entry-form textarea:focus, #entry-form input:focus, #entry-form select:focus { border-color: var(--accent); outline: none; } From 05c20f9ad6e2c1cb2a1b44caab5ae358e0d02817 Mon Sep 17 00:00:00 2001 From: Christian Wendler Date: Thu, 9 Apr 2026 00:28:02 +0000 Subject: [PATCH 2/3] feat: source tags (webui/cli/auto-capture) for entry provenance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Entries now carry a source tag indicating how they were created: - webui: created via WebUI (human, 1.3× boost) - cli: created via palaia add/write (human, 1.3× boost) - auto-capture: passively captured (no boost) - no source tag: agent-created via MCP/tool (no boost) Updates badge display, detail panel, and OpenClaw recall ranking. Co-Authored-By: Claude Opus 4.6 --- packages/openclaw-plugin/src/hooks/recall.ts | 7 ++-- palaia/cli.py | 7 +++- palaia/web/routes/entries.py | 42 +++++++++++++++----- palaia/web/routes/search.py | 10 +++-- palaia/web/routes/status.py | 2 +- palaia/web/static/app.js | 22 +++++++--- palaia/web/static/style.css | 1 + 7 files changed, 65 insertions(+), 26 deletions(-) diff --git a/packages/openclaw-plugin/src/hooks/recall.ts b/packages/openclaw-plugin/src/hooks/recall.ts index 148977a..44046c1 100644 --- a/packages/openclaw-plugin/src/hooks/recall.ts +++ b/packages/openclaw-plugin/src/hooks/recall.ts @@ -374,10 +374,11 @@ export function rerankByTypeWeight( const type = r.type || "memory"; const weight = weights[type] ?? 1.0; const recency = calcRecencyBoost(r.created, recencyBoost); - // Manual entries (no auto-capture tag) get a boost over auto-captured ones. + // Human-created entries (webui/cli tags) get a boost over auto-captured and agent entries. // This ensures intentionally stored knowledge ranks higher than conversation noise. - const isAutoCapture = r.tags?.includes("auto-capture") ?? false; - const sourceBoost = isAutoCapture ? 1.0 : manualEntryBoost; + const tags = r.tags ?? []; + const isHumanCreated = tags.includes("webui") || tags.includes("cli"); + const sourceBoost = isHumanCreated ? manualEntryBoost : 1.0; return { id: r.id, body: r.content || r.body || "", diff --git a/palaia/cli.py b/palaia/cli.py index 2703ec5..9f72518 100644 --- a/palaia/cli.py +++ b/palaia/cli.py @@ -212,14 +212,17 @@ def cmd_write(args): root = get_root() agent = _resolve_agent(args) instance = _resolve_instance_for_write(args) - tags = args.tags.split(",") if args.tags else None + tags = args.tags.split(",") if args.tags else [] + # Tag entries created via CLI so recall can distinguish source + if "cli" not in tags: + tags.append("cli") result = write_entry( root, body=args.text, scope=args.scope, agent=agent, - tags=tags, + tags=tags or None, title=args.title, project=getattr(args, "project", None), entry_type=getattr(args, "type", None), diff --git a/palaia/web/routes/entries.py b/palaia/web/routes/entries.py index e32ebdc..fcee801 100644 --- a/palaia/web/routes/entries.py +++ b/palaia/web/routes/entries.py @@ -1,9 +1,10 @@ """Entry CRUD routes. -v2.6 semantics: +v2.6+ semantics: - Tasks are post-its: setting status=done/wontfix on a task deletes it. -- Auto-capture entries carry the 'auto-capture' tag; manual entries do not. - Manual entries rank 30% higher in recall (visible via is_manual flag). +- Source tags: 'webui' (created in browser), 'cli' (palaia add/write), + 'auto-capture' (passive capture). No source tag = agent (MCP/tool). + Human-created entries (webui, cli) rank 30% higher in recall. """ from __future__ import annotations @@ -57,10 +58,21 @@ def _validate_enum(value: str | None, valid: set[str], field: str) -> str | None return value +def _detect_source(tags: list[str]) -> str: + """Detect entry source from tags: webui, cli, auto-capture, or agent.""" + if "webui" in tags: + return "webui" + if "cli" in tags: + return "cli" + if "auto-capture" in tags: + return "auto" + return "agent" + + def _entry_to_dict(meta: dict, body: str, tier: str, *, preview: bool = True) -> dict: - """Convert store entry to JSON-serializable dict with v2.6 flags.""" + """Convert store entry to JSON-serializable dict with source flags.""" tags = meta.get("tags", []) or [] - is_auto = "auto-capture" in tags + source = _detect_source(tags) return { "id": meta.get("id", ""), "title": meta.get("title", ""), @@ -78,8 +90,9 @@ def _entry_to_dict(meta: dict, body: str, tier: str, *, preview: bool = True) -> "accessed": meta.get("accessed", ""), "access_count": meta.get("access_count", 0), "decay_score": meta.get("decay_score", 0), - "is_auto_capture": is_auto, - "is_manual": not is_auto, + "source": source, + "is_auto_capture": source == "auto", + "is_manual": source in ("webui", "cli"), "body_preview": (body[:200] + "…") if preview and len(body) > 200 else body, } @@ -164,11 +177,13 @@ def get_entry(request: Request, entry_id: str) -> dict: if "error" in result: return JSONResponse(status_code=404, content=result) - # Augment with v2.6 flags + # Augment with source flags meta = result.get("meta", {}) or {} tags = meta.get("tags", []) or [] - result["is_auto_capture"] = "auto-capture" in tags - result["is_manual"] = not result["is_auto_capture"] + source = _detect_source(tags) + result["source"] = source + result["is_auto_capture"] = source == "auto" + result["is_manual"] = source in ("webui", "cli") return result @@ -196,13 +211,18 @@ def create_entry(request: Request, payload: EntryCreate) -> dict: store = Store(root) store.recover() + # Tag entries created via WebUI so recall can distinguish source + tags = list(payload.tags) if payload.tags else [] + if "webui" not in tags: + tags.append("webui") + try: entry_id = store.write( body=payload.body, title=payload.title, entry_type=payload.type, scope=payload.scope, - tags=payload.tags or None, + tags=tags or None, project=payload.project, status=payload.status, priority=payload.priority, diff --git a/palaia/web/routes/search.py b/palaia/web/routes/search.py index d89184c..b05c7b0 100644 --- a/palaia/web/routes/search.py +++ b/palaia/web/routes/search.py @@ -78,11 +78,15 @@ def _run(): logger.error("BM25 fallback failed: %s", exc) result = {"results": [], "has_embeddings": False, "bm25_only": True} - # Augment results with manual/auto flags + # Augment results with source flags + from palaia.web.routes.entries import _detect_source + for r in result.get("results", []): tags = r.get("tags", []) or [] - r["is_auto_capture"] = "auto-capture" in tags - r["is_manual"] = not r["is_auto_capture"] + source = _detect_source(tags) + r["source"] = source + r["is_auto_capture"] = source == "auto" + r["is_manual"] = source in ("webui", "cli") return { "query": q, diff --git a/palaia/web/routes/status.py b/palaia/web/routes/status.py index fe8abb1..49d99a7 100644 --- a/palaia/web/routes/status.py +++ b/palaia/web/routes/status.py @@ -104,7 +104,7 @@ def list_tags(request: Request) -> dict: for meta, _body, _tier in store.all_entries_unfiltered(include_cold=True): for tag in meta.get("tags") or []: tag = tag.strip() - if tag and tag != "auto-capture": + if tag and tag not in ("auto-capture", "webui", "cli"): tags.add(tag) return {"tags": sorted(tags)} diff --git a/palaia/web/static/app.js b/palaia/web/static/app.js index 694f544..7f7490d 100644 --- a/palaia/web/static/app.js +++ b/palaia/web/static/app.js @@ -308,15 +308,17 @@ const meta = el("div", { class: "entry-meta" }); meta.appendChild(el("span", { class: "badge badge-tier badge-" + (e.tier || "hot") }, e.tier || "hot")); meta.appendChild(el("span", { class: "badge badge-type badge-" + (e.type || "memory") }, e.type || "memory")); - meta.appendChild(el("span", { class: "badge badge-source " + (e.is_manual ? "badge-manual" : "badge-auto") }, - e.is_manual ? "manual ✦" : "auto")); + const sourceLabel = { webui: "webui", cli: "cli", auto: "auto", agent: "agent" }[e.source] || "auto"; + const sourceBadge = e.is_manual ? "badge-manual" : (e.source === "agent" ? "badge-agent-source" : "badge-auto"); + meta.appendChild(el("span", { class: "badge badge-source " + sourceBadge }, sourceLabel)); if (e.priority) meta.appendChild(el("span", { class: "badge badge-priority-" + e.priority }, e.priority)); if (e.status) meta.appendChild(el("span", { class: "badge badge-status" }, e.status)); if (e.scope && e.scope !== "team") meta.appendChild(el("span", { class: "badge badge-scope" }, e.scope)); if (e.project) meta.appendChild(el("span", { class: "badge badge-project" }, e.project)); if (e.agent) meta.appendChild(el("span", { class: "badge badge-agent" }, "@" + e.agent)); + const sourceTags = new Set(["auto-capture", "webui", "cli"]); for (const tag of (e.tags || []).slice(0, 4)) { - if (tag === "auto-capture") continue; // already shown via source badge + if (sourceTags.has(tag)) continue; // already shown via source badge meta.appendChild(el("span", { class: "tag" }, tag)); } card.appendChild(meta); @@ -360,17 +362,25 @@ function renderDetailPanel(id, d) { const m = d.meta || {}; - const isManual = d.is_manual ?? !(m.tags || []).includes("auto-capture"); + const source = d.source || (d.is_manual ? "cli" : "auto"); + const sourceLabels = { + webui: "webui (1.3× boost)", + cli: "cli (1.3× boost)", + auto: "auto-capture", + agent: "agent", + }; + const sourceTags = new Set(["auto-capture", "webui", "cli"]); + const displayTags = (m.tags || []).filter(t => !sourceTags.has(t)); const rows = [ ["Type", m.type || "memory"], ["Scope", m.scope || "team"], ["Tier", m.tier || "—"], - ["Source", isManual ? "manual (1.3× boost)" : "auto-capture"], + ["Source", sourceLabels[source] || source], ["Created", fmtDate(m.created)], ["Accessed", fmtDate(m.accessed)], ["Decay", Number(m.decay_score || 0).toFixed(4)], - ["Tags", (m.tags || []).join(", ") || "—"], + ["Tags", displayTags.join(", ") || "—"], ["Agent", m.agent || "—"], ]; if (m.priority) rows.push(["Priority", m.priority]); diff --git a/palaia/web/static/style.css b/palaia/web/static/style.css index 136fd13..cc21f3a 100644 --- a/palaia/web/static/style.css +++ b/palaia/web/static/style.css @@ -328,6 +328,7 @@ body { .badge-process { background: rgba(255,158,100,0.15); color: var(--orange); } .badge-manual { background: rgba(240,198,116,0.2); color: var(--gold); } .badge-auto { background: transparent; color: var(--text-dim); border: 1px solid var(--border); } +.badge-agent-source { background: rgba(122,162,247,0.12); color: var(--accent); } .badge-scope { background: var(--bg); color: var(--text-dim); border: 1px solid var(--border); } .badge-agent { background: rgba(122,162,247,0.08); color: var(--text-dim); border: 1px solid var(--border); } .badge-project { background: rgba(158,206,106,0.1); color: var(--green); } From 7112ca289464d1370d41bbb87567e5786739afac Mon Sep 17 00:00:00 2001 From: Christian Wendler Date: Thu, 9 Apr 2026 10:22:47 +0000 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20codex=20review=20=E2=80=94=20markdow?= =?UTF-8?q?n=20renderer,=20source=20tag=20leaks,=20search=20field?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0: Fix renderMarkdown double-encoding in code blocks (extract before esc), resolve merge conflict from stash/pop. P0: Filter webui/cli/auto-capture from edit form tag input. P1: Add source field to search result JS mapping. P1: Re-apply cli.py source tag injection (lost during stash/pop). Co-Authored-By: Claude Opus 4.6 --- palaia/web/static/app.js | 57 ++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/palaia/web/static/app.js b/palaia/web/static/app.js index 7f7490d..6dcd89b 100644 --- a/palaia/web/static/app.js +++ b/palaia/web/static/app.js @@ -83,34 +83,45 @@ // ── Simple Markdown → HTML ──────────────────────────────────────────────── function renderMarkdown(text) { if (!text) return ""; - // Escape HTML first (security) - let html = esc(text); - // Code blocks (``` ... ```) - html = html.replace(/```[\s\S]*?```/g, (m) => { - const code = m.slice(3, -3).replace(/^\w*\n/, ""); - return "
    " + code + "
    "; + // Extract code blocks BEFORE escaping so their content stays raw. + const codeBlocks = []; + let safe = text.replace(/```(\w*)\n([\s\S]*?)```/g, (_m, _lang, code) => { + const idx = codeBlocks.length; + codeBlocks.push("
    " + esc(code) + "
    "); + return "\x00CB" + idx + "\x00"; }); - // Inline code - html = html.replace(/`([^`]+)`/g, "$1"); + // Extract inline code before escaping + const inlineCode = []; + safe = safe.replace(/`([^`]+)`/g, (_m, code) => { + const idx = inlineCode.length; + inlineCode.push("" + esc(code) + ""); + return "\x00IC" + idx + "\x00"; + }); + // Now escape remaining HTML + safe = esc(safe); // Headings - html = html.replace(/^### (.+)$/gm, "

    $1

    "); - html = html.replace(/^## (.+)$/gm, "

    $1

    "); - html = html.replace(/^# (.+)$/gm, "

    $1

    "); + safe = safe.replace(/^### (.+)$/gm, "

    $1

    "); + safe = safe.replace(/^## (.+)$/gm, "

    $1

    "); + safe = safe.replace(/^# (.+)$/gm, "

    $1

    "); // Bold + italic - html = html.replace(/\*\*(.+?)\*\*/g, "$1"); - html = html.replace(/\*(.+?)\*/g, "$1"); + safe = safe.replace(/\*\*(.+?)\*\*/g, "$1"); + safe = safe.replace(/\*(.+?)\*/g, "$1"); // Blockquotes - html = html.replace(/^> (.+)$/gm, "
    $1
    "); + safe = safe.replace(/^> (.+)$/gm, "
    $1
    "); // Unordered lists - html = html.replace(/^- (.+)$/gm, "
  • $1
  • "); - html = html.replace(/((?:
  • .*<\/li>\n?)+)/g, "
      $1
    "); + safe = safe.replace(/^- (.+)$/gm, "
  • $1
  • "); + safe = safe.replace(/((?:
  • .*<\/li>\n?)+)/g, "
      $1
    "); // Ordered lists - html = html.replace(/^\d+\. (.+)$/gm, "
  • $1
  • "); + safe = safe.replace(/^(\d+)\. (.+)$/gm, "
  • $2
  • "); + safe = safe.replace(/((?:
  • .*<\/li>\n?)+)/g, (m) => m.includes("
      ") ? m : "
        " + m + "
      "); // Paragraphs (double newline) - html = html.replace(/\n\n/g, "

      "); - // Single newlines within paragraphs →
      - html = html.replace(/\n/g, "
      "); - return "

      " + html + "

      "; + safe = safe.replace(/\n\n/g, "

      "); + // Single newlines →
      + safe = safe.replace(/\n/g, "
      "); + // Restore code blocks and inline code + safe = safe.replace(/\x00CB(\d+)\x00/g, (_m, idx) => codeBlocks[idx]); + safe = safe.replace(/\x00IC(\d+)\x00/g, (_m, idx) => inlineCode[idx]); + return "

      " + safe + "

      "; } // ── Date formatting ────────────────────────────────────────────────────── @@ -243,7 +254,7 @@ id: r.id, title: r.title, type: r.type, scope: r.scope, tier: r.tier, tags: r.tags || [], project: r.project, status: r.status, priority: r.priority, decay_score: r.decay_score, body_preview: r.body || r.content, - score: r.score, is_manual: r.is_manual, is_auto_capture: r.is_auto_capture, + score: r.score, source: r.source, is_manual: r.is_manual, is_auto_capture: r.is_auto_capture, agent: r.agent, })); state.entries = entries; @@ -501,7 +512,7 @@ $("form-type").value = m.type || "memory"; $("form-scope").value = m.scope || "team"; $("form-project").value = m.project || ""; - $("form-tags").value = (m.tags || []).filter(t => t !== "auto-capture").join(", "); + $("form-tags").value = (m.tags || []).filter(t => !["auto-capture", "webui", "cli"].includes(t)).join(", "); $("form-agent").value = m.agent || ""; $("form-priority").value = m.priority || ""; $("form-status").value = m.status || "";