Newer
Older
navi-1 / navi / tools / synapse_instructions.py
"""Reaction instructions tool — read / update the per-user Synapse reaction rules.

The instructions are the standing contract between the user and the reaction
system: which events to react to, which profile to route them to, what to do.
Navi may refine this document through this tool (self-improvement); every edit
is recorded with edited_by='navi' in the version history.
"""

from navi.synapse.settings_store import SynapseSettingsStore
from navi.tools._internal.base import Tool, ToolContext, ToolResult, current_user_id


class SynapseInstructionsTool(Tool):
    name = "synapse_instructions"
    description = (
        "Read or update the user's Synapse reaction instructions — the standing rules "
        "describing which platform events Navi reacts to and how. Read them before "
        "acting on Synapse events; update them only when a change is clearly wanted "
        "and explained (the user reviews the edit history)."
    )
    parameters = {
        "type": "object",
        "properties": {
            "op": {
                "type": "string",
                "enum": ["read", "update"],
                "description": "read: return the current document. update: replace it with `content`.",
            },
            "content": {
                "type": "string",
                "description": "update only: the FULL new text of the instructions document.",
            },
            "reason": {
                "type": "string",
                "description": "update only: one sentence explaining why you are changing the rules.",
            },
        },
        "required": ["op"],
    }

    def __init__(self, pool_provider=None) -> None:
        self._pool_provider = pool_provider

    async def execute(self, params: dict, ctx: ToolContext | None = None) -> ToolResult:
        user_id = ctx.user_id if ctx is not None else current_user_id.get(None)
        if user_id is None:
            return ToolResult(
                success=False, output="",
                error="No user context — reaction instructions are per-user.",
            )

        if not callable(self._pool_provider):
            return ToolResult(
                success=False, output="",
                error="Reaction settings storage is not available (no pool).",
            )
        store = SynapseSettingsStore(self._pool_provider())
        settings_row = await store.get(user_id)

        op = params.get("op", "read")
        if op == "read":
            body = settings_row.instructions.strip()
            if not body:
                return ToolResult(
                    success=True,
                    output="The reaction instructions document is empty — no reactions are configured yet.",
                )
            return ToolResult(success=True, output=body)

        if op != "update":
            return ToolResult(success=False, output="", error=f"Unknown op '{op}'.")

        content = (params.get("content") or "").strip()
        if not content:
            return ToolResult(
                success=False, output="",
                error="update requires non-empty `content` — use the full new text of the document.",
            )
        reason = (params.get("reason") or "").strip()
        await store.save_instructions(
            user_id, content, edited_by="navi", reason=reason or None,
        )
        return ToolResult(
            success=True,
            output=(
                "Instructions updated and recorded in the edit history"
                + (f" ({reason})" if reason else "")
                + ". The change applies to future Synapse reactions."
            ),
        )