refactor: bind cron jobs to origin sessions
This commit is contained in:
@@ -14,7 +14,6 @@ from typing import Any
|
||||
from loguru import logger
|
||||
|
||||
from nanobot.config.paths import get_legacy_sessions_dir
|
||||
from nanobot.session.metadata import SESSION_ROUTING_METADATA_KEY
|
||||
from nanobot.utils.helpers import (
|
||||
ensure_dir,
|
||||
estimate_message_tokens,
|
||||
@@ -37,7 +36,6 @@ _FORK_VOLATILE_METADATA_KEYS = {
|
||||
"pending_user_turn",
|
||||
"runtime_checkpoint",
|
||||
"thread_goal",
|
||||
SESSION_ROUTING_METADATA_KEY,
|
||||
"title",
|
||||
"title_user_edited",
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
"""Shared session metadata keys."""
|
||||
|
||||
SESSION_ROUTING_METADATA_KEY = "_routing_context"
|
||||
@@ -1,104 +0,0 @@
|
||||
"""Persisted session routing context for proactive turns."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Mapping
|
||||
|
||||
from nanobot.bus.events import InboundMessage
|
||||
from nanobot.cron.session_turns import is_cron_turn
|
||||
from nanobot.session.manager import Session
|
||||
from nanobot.session.metadata import SESSION_ROUTING_METADATA_KEY
|
||||
|
||||
_ROUTING_METADATA_KEYS = {
|
||||
"chat_type",
|
||||
"context_chat_id",
|
||||
"conversation_type",
|
||||
"event_id",
|
||||
"message_thread_id",
|
||||
"msg_type",
|
||||
"parent_channel_id",
|
||||
"parent_id",
|
||||
"platform",
|
||||
"root_id",
|
||||
"thread_id",
|
||||
"thread_reply_to_event_id",
|
||||
"thread_root_event_id",
|
||||
}
|
||||
_CHANNEL_ROUTING_METADATA_KEYS = {
|
||||
# Feishu needs a message anchor to reply into an existing topic. Other
|
||||
# channels should avoid stale reply anchors for scheduled cron turns.
|
||||
"feishu": {"message_id"},
|
||||
}
|
||||
_SLACK_ROUTING_KEYS = {"channel_type", "thread_ts"}
|
||||
|
||||
|
||||
def _scalar(value: Any) -> str | int | float | bool | None:
|
||||
if value is None or isinstance(value, (str, int, float, bool)):
|
||||
return value
|
||||
return None
|
||||
|
||||
|
||||
def _routing_metadata(channel: str, metadata: Mapping[str, Any] | None) -> dict[str, Any]:
|
||||
if not isinstance(metadata, Mapping):
|
||||
return {}
|
||||
|
||||
out: dict[str, Any] = {}
|
||||
keys = _ROUTING_METADATA_KEYS | _CHANNEL_ROUTING_METADATA_KEYS.get(channel, set())
|
||||
for key in keys:
|
||||
if key not in metadata:
|
||||
continue
|
||||
value = _scalar(metadata.get(key))
|
||||
if value is not None:
|
||||
out[key] = value
|
||||
|
||||
slack = metadata.get("slack")
|
||||
if isinstance(slack, Mapping):
|
||||
slack_out = {
|
||||
key: value
|
||||
for key in _SLACK_ROUTING_KEYS
|
||||
if (value := _scalar(slack.get(key))) is not None
|
||||
}
|
||||
if slack_out:
|
||||
out["slack"] = slack_out
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def routing_context_for_message(msg: InboundMessage) -> dict[str, Any]:
|
||||
"""Return the stable routing context needed to deliver future session turns."""
|
||||
return {
|
||||
"channel": msg.channel,
|
||||
"chat_id": msg.chat_id,
|
||||
"metadata": _routing_metadata(msg.channel, msg.metadata),
|
||||
}
|
||||
|
||||
|
||||
def persist_routing_context(session: Session, msg: InboundMessage) -> bool:
|
||||
"""Persist the latest non-cron delivery context for a session."""
|
||||
if is_cron_turn(msg.metadata):
|
||||
return False
|
||||
context = routing_context_for_message(msg)
|
||||
if session.metadata.get(SESSION_ROUTING_METADATA_KEY) == context:
|
||||
return False
|
||||
session.metadata[SESSION_ROUTING_METADATA_KEY] = context
|
||||
return True
|
||||
|
||||
|
||||
def read_routing_context(metadata: Mapping[str, Any] | None) -> tuple[str, str, dict[str, Any]] | None:
|
||||
"""Decode a persisted routing context from session metadata."""
|
||||
if not isinstance(metadata, Mapping):
|
||||
return None
|
||||
raw = metadata.get(SESSION_ROUTING_METADATA_KEY)
|
||||
if not isinstance(raw, Mapping):
|
||||
return None
|
||||
|
||||
channel = raw.get("channel")
|
||||
chat_id = raw.get("chat_id")
|
||||
if not isinstance(channel, str) or not channel:
|
||||
return None
|
||||
if not isinstance(chat_id, str) or not chat_id:
|
||||
return None
|
||||
|
||||
route_meta = raw.get("metadata")
|
||||
metadata_out = dict(route_meta) if isinstance(route_meta, Mapping) else {}
|
||||
return channel, chat_id, metadata_out
|
||||
Reference in New Issue
Block a user