| 201 | return context_prompt, {}, {} |
| 202 | |
| 203 | async def submit_message( |
| 204 | self, |
| 205 | prompt: str | list[ContentBlock], |
| 206 | *, |
| 207 | on_message: Callable[[Message | StreamEvent], None] | None = None, |
| 208 | ) -> AsyncGenerator[Message | StreamEvent, None]: |
| 209 | # ``MessageContent = str | list[ContentBlock]`` already supports |
| 210 | # both shapes; the list form lets callers attach image/document |
| 211 | # content blocks alongside the text prompt (e.g. from @image.png |
| 212 | # @-mentions in the REPL). |
| 213 | user_msg = UserMessage(content=prompt) |
| 214 | self._mutable_messages.append(user_msg) |
| 215 | |
| 216 | system_prompt, user_context, system_context = ( |
| 217 | await self._build_system_prompt_parts() |
| 218 | ) |
| 219 | |
| 220 | # Prepend user context (CLAUDE.md + date) as <system-reminder> |
| 221 | messages_for_query = prepend_user_context( |
| 222 | list(self._mutable_messages), user_context, |
| 223 | ) |
| 224 | |
| 225 | # TS query loop runs 5-layer compression pipeline every iteration |
| 226 | # (Phase 0: toolResultBudget → snip → microcompact → collapse → autocompact). |
| 227 | # Enable it by passing a PipelineConfig. |
| 228 | # |
| 229 | # Build read_file_state from the tool context's read_file_fingerprints |
| 230 | # so post-compact attachments can re-inject recently read files. |
| 231 | # The attachment builder only reads timestamp from each entry and |
| 232 | # re-reads content from disk, so we just need the timestamp. |
| 233 | read_file_state: dict[str, Any] = {} |
| 234 | try: |
| 235 | for path, fp in self._config.tool_context.read_file_fingerprints.items(): |
| 236 | # fp is (mtime, size) or (mtime, size, partial) |
| 237 | read_file_state[str(path)] = {"timestamp": fp[0]} |
| 238 | except Exception: |
| 239 | pass |
| 240 | |
| 241 | pipeline_config = PipelineConfig( |
| 242 | provider=self._config.provider, |
| 243 | model=getattr(self._config.provider, 'model', '') or '', |
| 244 | read_file_state=read_file_state or None, |
| 245 | # Ch5/B.5 — thread the session-scoped tracking instance so |
| 246 | # the autocompact circuit-breaker can count consecutive |
| 247 | # failures across user prompts. ``auto_compact_if_needed`` |
| 248 | # mutates ``tracking.consecutive_failures`` in place. |
| 249 | autocompact_tracking=self._auto_compact_tracking, |
| 250 | ) |
| 251 | |
| 252 | params = QueryParams( |
| 253 | messages=messages_for_query, |
| 254 | system_prompt=system_prompt, |
| 255 | tools=self._config.tools, |
| 256 | tool_registry=self._config.tool_registry, |
| 257 | tool_use_context=self._config.tool_context, |
| 258 | provider=self._config.provider, |
| 259 | abort_controller=self._abort_controller, |
| 260 | query_source=self._config.query_source, |