Gateway turns reach the agent enriched with channel-bound skill payloads, sender metadata and other model context, so the first user message the titler saw could be thousands of characters of skill text ahead of the actual question, and new sessions were named after the skill instead of the request. The gateway now captures the inbound event text before that enrichment and passes it as a turn-local title_user_message through TurnContext, the turn runner, AIAgent.run_conversation and the conversation prologue. Only the titler reads it: the main model, persisted history and relay metadata still receive the full enriched message. An explicit empty or whitespace override suppresses titling for that turn, None keeps the existing model-facing fallback (attachment-only turns, paste previews), and the override cannot enable model titling for cron or subagent runs. The value is a per-call argument, so a cached agent never reuses a previous turn's override. Rebased onto current main: the gateway call keeps main's pinned channel prompt for internal events, and the integration fixture's context-prompt stub accepts main's internal= keyword. Ritvik Gautam made this change with Doryani AI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
102 lines
4.8 KiB
Python
102 lines
4.8 KiB
Python
"""Per-turn context shared between ``GatewayRunner._run_agent_inner`` and ``TurnRunner``.
|
|
|
|
Each ex-closure local is a field, written once by ``_run_agent_inner`` while wiring the turn.
|
|
``message`` (ex-``nonlocal``) is the only rebindable field; other mutable state uses
|
|
single-element lists so mutation stays visible to the outer body.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import Any, Callable, List, Optional
|
|
|
|
|
|
@dataclass
|
|
class TurnContext:
|
|
# read-only turn identity / wiring
|
|
source: Any = None
|
|
reply_expected: Optional[bool] = None
|
|
# Scheduled heartbeats are proactive work, not replies to the source message that
|
|
# registered the watch. Their routine delivery surfaces stay quiet.
|
|
scheduled_heartbeat: bool = False
|
|
_run_still_current: Callable[[], bool] = None # type: ignore[assignment]
|
|
_live_status_adapter: Any = None
|
|
_live_status_mode: str = "off"
|
|
_thinking_enabled: bool = False
|
|
progress_mode: str = "off"
|
|
progress_grouping: str = "grouped"
|
|
tool_progress_enabled: bool = False
|
|
progress_queue: Any = None
|
|
log_queue: Any = None
|
|
# mutable single-element containers (shared with the outer body)
|
|
last_progress_msg: list = field(default_factory=lambda: [None])
|
|
last_tool: list = field(default_factory=lambda: [None])
|
|
last_was_terminal_block: list = field(default_factory=lambda: [False])
|
|
repeat_count: list = field(default_factory=lambda: [0])
|
|
long_tool_hint_fired: list = field(default_factory=lambda: [False])
|
|
agent_holder: list = field(default_factory=lambda: [None])
|
|
_LONG_TOOL_THRESHOLD_S: float = 30.0
|
|
_cleanup_progress: bool = False
|
|
_cleanup_msg_ids: List[str] = field(default_factory=list)
|
|
_progress_metadata: Optional[dict] = None
|
|
_progress_reply_to: Optional[Any] = None
|
|
message: Optional[str] = None # the only rebindable field
|
|
# turn parameters / config snapshots (read-only in run_sync)
|
|
history: Any = None
|
|
context_prompt: Optional[str] = None
|
|
channel_prompt: Optional[str] = None
|
|
session_id: Optional[str] = None
|
|
session_key: Optional[str] = None
|
|
run_generation: Optional[int] = None
|
|
process_task_id: str = ""
|
|
process_baseline: frozenset[str] = field(default_factory=frozenset)
|
|
_interrupt_depth: int = 0
|
|
event_message_id: Optional[str] = None
|
|
# Raw inbound platform id (not the event_message_id reply anchor); stamped on the user turn.
|
|
inbound_message_id: Optional[str] = None
|
|
moa_config: Optional[dict] = None
|
|
title_user_message: Optional[str] = None
|
|
persist_user_message: Optional[Any] = None
|
|
persist_user_timestamp: Optional[float] = None
|
|
# display_kind of the persisted user row for a self-injected turn; DB-only, never sent.
|
|
# "internal_notification" for async-delegation/background notifications (#82888).
|
|
persist_user_display_kind: Optional[str] = None
|
|
persist_user_display_metadata: Optional[dict] = None
|
|
user_config: Any = None
|
|
mute_notification_reply: bool = False
|
|
enabled_toolsets: Any = None
|
|
disabled_toolsets: Any = None
|
|
log_mode_enabled: bool = False
|
|
interim_assistant_messages_enabled: bool = False
|
|
needs_progress_queue: bool = False
|
|
AIAgent: Any = None
|
|
resolve_display_setting: Any = None
|
|
result_holder: list = field(default_factory=lambda: [None])
|
|
tools_holder: list = field(default_factory=lambda: [None])
|
|
stream_consumer_holder: list = field(default_factory=lambda: [None])
|
|
streaming_tts_consumer_holder: list = field(default_factory=lambda: [None])
|
|
# voice-ack wiring
|
|
_voice_ack_fired: list = field(default_factory=lambda: [False])
|
|
_voice_ack_guild: list = field(default_factory=lambda: [None])
|
|
_voice_ack_loop: Any = None
|
|
# hook / status bridge wiring
|
|
_loop_for_step: Any = None
|
|
_hooks_ref: Any = None
|
|
_status_adapter: Any = None
|
|
_status_chat_id: Any = None
|
|
_status_thread_metadata: Optional[dict] = None
|
|
# bound TurnRunner callbacks read via ctx
|
|
progress_callback: Optional[Callable] = None
|
|
voice_ack_callback: Optional[Callable] = None
|
|
_step_callback_sync: Optional[Callable] = None
|
|
_event_callback_sync: Optional[Callable] = None
|
|
_status_callback_sync: Optional[Callable] = None
|
|
# Slack-native task cards (opt-in); ID-bearing callbacks correlate start/complete by call ID
|
|
# --- Slack-native task-card progress (opt-in; #29483) ------------------ True when the Slack adapter's
|
|
# ``native_task_cards_enabled()`` opt-in is set for this turn's platform. The ID-bearing lifecycle
|
|
# callbacks are published by TurnRunner (like voice_ack_callback above) so tool starts and completions
|
|
# correlate by real tool-call ID instead of tool name.
|
|
_native_slack_task_cards: bool = False
|
|
native_tool_start_callback: Optional[Callable] = None
|
|
native_tool_complete_callback: Optional[Callable] = None
|