diff --git a/CHANGELOG.md b/CHANGELOG.md index f993aa6..5fc44d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## v2.7.3 — 2026-04-07 + +### Fixed +- **ContextEngine: `ownsCompaction: true`** — palaia now declares compaction ownership, preventing OpenClaw's built-in Pi auto-compaction from running in parallel with `palaia gc`. Without this flag, both compaction mechanisms could interfere with each other, leading to unpredictable context truncation. +- **Doctor phantom stale-tasks warning** — `_check_stale_unassigned_tasks` now reads entries through `Store.all_entries_unfiltered()` instead of scanning `.md` files directly. The previous approach could report entries invisible to `palaia list` (e.g. entries with empty/invalid scope), causing the doctor to warn about tasks that the user cannot see or act on. +- **Scope: empty/unknown scope no longer hides entries** — `can_access()` now treats empty or unrecognized scope values as `"team"` instead of returning `False`. Entries with missing or malformed scope are no longer silently invisible. + +--- + ## v2.7.2 — 2026-04-06 ### New Features diff --git a/SKILL.md b/SKILL.md index 05a1c16..99c6824 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,6 +1,6 @@ --- name: palaia -version: "2.7.2" +version: "2.7.3" description: > Local, crash-safe persistent memory for OpenClaw agents. SQLite-backed by default. Semantic search, projects, scopes, auto-capture. diff --git a/packages/openclaw-plugin/package.json b/packages/openclaw-plugin/package.json index 24d8706..8524c88 100644 --- a/packages/openclaw-plugin/package.json +++ b/packages/openclaw-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@byte5ai/palaia", - "version": "2.7.2", + "version": "2.7.3", "description": "palaia memory backend for OpenClaw", "main": "index.ts", "openclaw": { diff --git a/packages/openclaw-plugin/skill/SKILL.md b/packages/openclaw-plugin/skill/SKILL.md index 05a1c16..99c6824 100644 --- a/packages/openclaw-plugin/skill/SKILL.md +++ b/packages/openclaw-plugin/skill/SKILL.md @@ -1,6 +1,6 @@ --- name: palaia -version: "2.7.2" +version: "2.7.3" description: > Local, crash-safe persistent memory for OpenClaw agents. SQLite-backed by default. Semantic search, projects, scopes, auto-capture. diff --git a/packages/openclaw-plugin/src/context-engine.ts b/packages/openclaw-plugin/src/context-engine.ts index 481997e..b9d4c0a 100644 --- a/packages/openclaw-plugin/src/context-engine.ts +++ b/packages/openclaw-plugin/src/context-engine.ts @@ -394,6 +394,7 @@ export function createPalaiaContextEngine( id: "palaia", name: "palaia Memory", version: "2.3", + ownsCompaction: true, }, /** diff --git a/palaia/SKILL.md b/palaia/SKILL.md index 05a1c16..99c6824 100644 --- a/palaia/SKILL.md +++ b/palaia/SKILL.md @@ -1,6 +1,6 @@ --- name: palaia -version: "2.7.2" +version: "2.7.3" description: > Local, crash-safe persistent memory for OpenClaw agents. SQLite-backed by default. Semantic search, projects, scopes, auto-capture. diff --git a/palaia/__init__.py b/palaia/__init__.py index 6c66de3..3b84a47 100644 --- a/palaia/__init__.py +++ b/palaia/__init__.py @@ -4,5 +4,5 @@ from __future__ import annotations -__version__ = "2.7.2" +__version__ = "2.7.3" __author__ = "byte5 GmbH" diff --git a/palaia/doctor/checks.py b/palaia/doctor/checks.py index 697c187..efca47f 100644 --- a/palaia/doctor/checks.py +++ b/palaia/doctor/checks.py @@ -1862,7 +1862,12 @@ def _check_claude_code_config(palaia_root: Path | None) -> dict[str, Any]: def _check_stale_unassigned_tasks(palaia_root: Path | None) -> dict[str, Any]: - """Check for auto-captured tasks without assignee/due_date older than 7 days.""" + """Check for auto-captured tasks without assignee/due_date older than 7 days. + + Uses Store.all_entries_unfiltered() to ensure consistent entry discovery + with `palaia list`. Previous implementation scanned .md files directly, + which could report entries invisible to the user (scope filtering mismatch). + """ if palaia_root is None: return { "name": "stale_unassigned_tasks", @@ -1873,36 +1878,43 @@ def _check_stale_unassigned_tasks(palaia_root: Path | None) -> dict[str, Any]: from datetime import datetime, timezone - from palaia.entry import parse_entry + from palaia.store import Store now = datetime.now(tz=timezone.utc) stale_ids: list[str] = [] - for tier in ("hot", "warm"): - tier_dir = palaia_root / tier - if not tier_dir.exists(): - continue - for p in tier_dir.glob("*.md"): - try: - text = p.read_text(encoding="utf-8") - meta, _body = parse_entry(text) - if meta.get("type") != "task": - continue - if meta.get("assignee") or meta.get("due_date"): - continue - raw_tags = meta.get("tags", []) - tags = raw_tags if isinstance(raw_tags, list) else str(raw_tags).split(",") - tags = [t.strip() for t in tags] - if "auto-capture" not in tags: - continue - created = meta.get("created", "") - if not created: - continue - created_dt = datetime.fromisoformat(created) - if (now - created_dt).days >= 7: - stale_ids.append(p.stem) - except Exception: + try: + store = Store(palaia_root) + all_entries = store.all_entries_unfiltered(include_cold=False) + except Exception: + return { + "name": "stale_unassigned_tasks", + "label": "Stale unassigned tasks", + "status": "ok", + "message": "Could not read store", + } + + for meta, _body, _tier in all_entries: + try: + if meta.get("type") != "task": + continue + if meta.get("assignee") or meta.get("due_date"): + continue + raw_tags = meta.get("tags", []) + tags = raw_tags if isinstance(raw_tags, list) else str(raw_tags).split(",") + tags = [t.strip() for t in tags] + if "auto-capture" not in tags: continue + created = meta.get("created", "") + if not created: + continue + created_dt = datetime.fromisoformat(created) + if (now - created_dt).days >= 7: + entry_id = meta.get("id", "") + if entry_id: + stale_ids.append(entry_id) + except Exception: + continue if not stale_ids: return { diff --git a/palaia/scope.py b/palaia/scope.py index 0df310d..631a975 100644 --- a/palaia/scope.py +++ b/palaia/scope.py @@ -74,6 +74,11 @@ def can_access( else: return False + # Normalize empty/missing scope to "team" — entries must never become + # invisible just because scope was written as an empty string. + if not entry_scope: + entry_scope = "team" + if entry_scope == "team": return True if entry_scope == "public": @@ -87,7 +92,8 @@ def can_access( # Legacy shared:X entries are accessible like team entries if entry_scope.startswith(_LEGACY_SHARED_PREFIX): return True - return False + # Unknown scope: treat as team (safe default, never hide entries) + return True def is_exportable(scope: str) -> bool: diff --git a/pyproject.toml b/pyproject.toml index 84ef84b..6ad96b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "palaia" -version = "2.7.2" +version = "2.7.3" description = "Local, cloud-free memory for OpenClaw agents." readme = "README.md" license = {text = "MIT"} diff --git a/skills/palaia/SKILL.md b/skills/palaia/SKILL.md index 05a1c16..99c6824 100644 --- a/skills/palaia/SKILL.md +++ b/skills/palaia/SKILL.md @@ -1,6 +1,6 @@ --- name: palaia -version: "2.7.2" +version: "2.7.3" description: > Local, crash-safe persistent memory for OpenClaw agents. SQLite-backed by default. Semantic search, projects, scopes, auto-capture.