diff --git a/CHANGELOG.md b/CHANGELOG.md index dab8e62b..1693520a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## [Unreleased] +### Fixed + +- The third-party notes drawer's “Recently used by AI” Joplin list now follows the WebUI-specific `webui_prefill_messages_script` hook when configured, including argv-style hooks such as `[python3, /path/to/recall.py]`, before falling back to the legacy generic `prefill_messages_script`. + ## [v0.51.150] — 2026-05-28 — Release DV (stage-batch32 — single-PR reasoning-effort agent metadata) ### Fixed diff --git a/api/routes.py b/api/routes.py index 7e829475..dda7f6d1 100644 --- a/api/routes.py +++ b/api/routes.py @@ -12879,17 +12879,39 @@ _JOPLIN_AI_RECALL_NOTE_PRIORITY = [ ] -def _joplin_prefill_script_path() -> Path | None: - cfg = get_config() - path_value = cfg.get("prefill_messages_script") if isinstance(cfg, dict) else None +def _script_path_from_config_value(path_value) -> Path | None: + """Return the likely recall script path from a string or argv-style hook.""" if not path_value: return None try: + if isinstance(path_value, (list, tuple)): + candidates = [str(part).strip() for part in path_value if str(part).strip()] + # Hooks commonly use [python, /path/to/script.py]. Prefer the first + # Python-ish script argument over the interpreter so AI-recent notes + # reflect the configured recall source rather than "python3". + for candidate in candidates: + if candidate.endswith((".py", ".sh", ".bash")): + return Path(candidate).expanduser() + if candidates: + return Path(candidates[-1]).expanduser() + return None return Path(str(path_value)).expanduser() except Exception: return None +def _joplin_prefill_script_path() -> Path | None: + cfg = get_config() + if not isinstance(cfg, dict): + return None + # The browser notes drawer should mirror the WebUI-specific recall hook when + # configured. Fall back to the legacy generic session prefill script only for + # deployments that have not opted into WebUI dynamic recall. + return _script_path_from_config_value( + cfg.get("webui_prefill_messages_script") or cfg.get("prefill_messages_script") + ) + + def _joplin_recall_note_refs(script_path: Path | None = None) -> list[dict]: """Find stable Joplin note IDs referenced by the configured recall script. diff --git a/tests/test_webui_notes_sources.py b/tests/test_webui_notes_sources.py index 1e9e1959..7e4d038b 100644 --- a/tests/test_webui_notes_sources.py +++ b/tests/test_webui_notes_sources.py @@ -200,6 +200,39 @@ def test_joplin_recent_ai_notes_uses_configured_prefill_script(monkeypatch, tmp_ assert all(note["used_reason"] == "automatic_recall" for note in notes) +def test_joplin_recent_ai_notes_prefers_webui_prefill_script_hook(monkeypatch, tmp_path): + from api import routes + + legacy_script = tmp_path / "legacy_context.py" + legacy_script.write_text('CURRENT_CONTEXT_ID = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"\n', encoding="utf-8") + webui_script = tmp_path / "webui_context.py" + webui_script.write_text( + 'CURRENT_CONTEXT_ID = "5ba9ab822c344115939205ca4e8eaec0"\n' + 'OPEN_ISSUES_ID = "623aeb6e55cb4aa39a0541f2ac09aa36"\n', + encoding="utf-8", + ) + monkeypatch.setattr(routes, "get_config", lambda: { + "prefill_messages_script": str(legacy_script), + "webui_prefill_messages_script": ["python3", str(webui_script)], + }) + + def fake_get(path, params=None): + note_id = path.rsplit("/", 1)[-1] + assert note_id != "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + titles = { + "5ba9ab822c344115939205ca4e8eaec0": "Current Context", + "623aeb6e55cb4aa39a0541f2ac09aa36": "Open Issues", + } + return {"id": note_id, "title": titles[note_id], "updated_time": 123, "parent_id": "folder"} + + monkeypatch.setattr(routes, "_joplin_api_get", fake_get) + + notes = routes._joplin_recent_ai_notes(limit=2) + + assert [note["title"] for note in notes] == ["Current Context", "Open Issues"] + + + def test_external_notes_ui_uses_minimal_lucide_icons_for_ai_recent_notes(): from pathlib import Path