logancyang_obsidian-copilot/docs/MESSAGE_ARCHITECTURE.md
Logan Yang 05beb941eb
Context revamp (#1971)
* Implement context engine for a layered context

* Wire in new context library to LLM chain

- Updated the context engine to maintain a cumulative library of all context items (L2) without deduplication, enhancing cache stability.
- Introduced smart referencing in the user message (L3) to reference existing items in L2 by ID and include full content for new items.
- Improved the LayerToMessagesConverter to format and handle layered context envelopes for better debugging and clarity.
- Fixed critical bugs related to context deduplication that previously broke cache stability.
- Enhanced tests to validate the new context handling and ensure proper functionality across various scenarios.

* Enhance VaultQAChainRunner with tag extraction and inline citation detection

- Integrated tag extraction from user queries to improve context-aware retrieval.
- Implemented `hasInlineCitations` function to detect inline citation markers in responses.
- Updated message construction logic to utilize envelope-based context, enhancing the quality of AI interactions.
- Added comprehensive tests for the new inline citation detection functionality, ensuring robustness and accuracy.
- Improved logging for better debugging and traceability of the message processing flow.

Refactor VaultQAChainRunner to enhance context handling and citation instructions

- Integrated LayerToMessagesConverter for improved message construction, ensuring smart referencing and context preservation.
- Updated logging to reflect the use of the new converter in envelope-based context construction.
- Revised comments for clarity on context preparation and citation instructions.
- Minor adjustment in citation utility to improve regex handling for citation updates.

* Complete migration to envelope-based context in CopilotPlus and LLM chain runners

- Removed all legacy fallback paths, ensuring that context envelopes are now mandatory for operations in CopilotPlusChainRunner and LLMChainRunner.
- Enhanced image extraction to only pull from the active note, preventing unintended leaks from attached context notes.
- Fixed critical issues with context-in-context (CiC) formatting and added early envelope guards in VaultQAChainRunner to prevent silent failures.
- Updated message construction logic to utilize LayerToMessagesConverter for improved smart referencing and context preservation.
- Comprehensive tests added to validate the new envelope-based context handling and ensure robustness across all chain runners.

* Integrate Plus chain with context envelope

* Enhance AutonomousAgentChainRunner with envelope-based context integration

- Introduced `LayerToMessagesConverter` for improved message construction, ensuring system and user messages are derived from the context envelope.
- Implemented envelope validation to ensure context is available before processing.
- Updated message preparation logic to maintain consistency with the envelope-first architecture, preserving tool execution and multimodal support.
- Added comprehensive logging for envelope-based context construction to aid debugging.
- Documented changes in the context engineering documentation to reflect the new integration and design points.

* Support loading saved chat context to L2 context library

* Fix ChatPersistenceManager tests for context handling
2025-10-27 23:28:36 -07:00

755 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Message Architecture & Context Design
This document describes the new message management and context processing architecture that replaced the legacy SharedState system. The new design follows clean architecture principles with a single source of truth, computed views, and complete project isolation.
**Note**: For detailed information about the layered context system (L1-L5 layers, L2 auto-promotion, cache optimization), see [CONTEXT_ENGINEERING.md](./CONTEXT_ENGINEERING.md).
## Architecture Principles
### Single Source of Truth
- Each message is stored exactly once in `MessageRepository`
- All UI and LLM views are computed from this single storage
- No complex dual-array synchronization or ID matching
### Clean Architecture Flow
```
User Input → ChatUIState → ChatManager → getCurrentMessageRepo() → MessageRepository + ContextManager
↓ ↓
ChatPersistenceManager Project-specific storage
UI Components ← ChatUIState ← Computed Views ← MessageRepository
LLM Processing ← Chain Memory ← getLLMMessages() ← MessageRepository
```
### Context Always Fresh
- Context is reprocessed when messages are edited
- No stale context issues from cached processing
- Ensures accurate context for LLM interactions
### Project Isolation
- Each project maintains its own isolated chat history
- Automatic detection and switching when project changes
- Zero configuration required - works automatically
- Non-project chats use a default repository
## Core Components
### 1. MessageRepository (`src/core/MessageRepository.ts`)
**Purpose**: Single source of truth for all messages
**Key Concepts**:
- Stores `StoredMessage` objects with both `displayText` and `processedText`
- `displayText`: What the user typed or AI responded (for UI display)
- `processedText`: For user messages, includes context. For AI messages, same as display
**Core Methods**:
```typescript
// Add new message
addMessage(
displayText: string,
processedText: string,
sender: string,
context?: MessageContext,
content?: MessageContent[]
): string
// Get computed views
getDisplayMessages(): ChatMessage[] // For UI rendering
getLLMMessages(): ChatMessage[] // For AI processing
// Edit operations
editMessage(id: string, newDisplayText: string): boolean
updateProcessedText(
id: string,
processedText: string,
contextEnvelope?: PromptContextEnvelope
): boolean
// Bulk operations
truncateAfterMessageId(messageId: string): void
loadMessages(messages: ChatMessage[]): void
```
> **Storing envelopes**: When `ChatManager` calls `addMessage` with a full `NewChatMessage`, it includes the `contextEnvelope` property so the repository keeps both the legacy `processedText` and the canonical layered representation. The string-based overload remains for low-level utilities and test fixtures.
### 2. ChatManager (`src/core/ChatManager.ts`)
**Purpose**: Central business logic coordinator
**Responsibilities**:
- Orchestrates MessageRepository, ContextManager, and LLM operations
- Handles all message CRUD operations with proper error handling
- Synchronizes with chain memory for conversation history
- Manages context processing lifecycle
- **Project Isolation**: Maintains separate MessageRepository per project
- **Persistence**: Integrates with ChatPersistenceManager for saving/loading
**Envelope Lifecycle**:
1. User sends a message → `ContextManager.processMessageContext()` returns both `processedContent` **and** a `PromptContextEnvelope`.
2. `ChatManager` stores the envelope with `MessageRepository.updateProcessedText(...)`.
3. When any chain runner executes, it reads `userMessage.contextEnvelope` and feeds it to `LayerToMessagesConverter.convert()` to materialize the L1-L5 prompt structure.
4. Regeneration or edits call `reprocessMessageContext`, ensuring a fresh envelope replaces the stale one.
**Key Operations**:
```typescript
// Send new message with context processing
async sendMessage(displayText: string, context: MessageContext, chainType: ChainType, includeActiveNote?: boolean): Promise<string>
// Edit message and reprocess context
async editMessage(messageId: string, newText: string, chainType: ChainType, includeActiveNote?: boolean): Promise<boolean>
// Regenerate AI response
async regenerateMessage(messageId: string, onUpdateMessage: Function, onAddMessage: Function): Promise<boolean>
// Memory synchronization
private async updateChainMemory(): Promise<void>
// Project management
private getCurrentMessageRepo(): MessageRepository // Auto-detects current project
async handleProjectSwitch(): Promise<void> // Forces project detection
// Persistence
async saveChat(modelKey: string): Promise<{ success: boolean; path?: string; error?: string }>
```
**Project Isolation Implementation**:
```typescript
// Internal structure
private projectMessageRepos: Map<string, MessageRepository>
// Automatic project detection
getCurrentMessageRepo() {
const currentProjectId = ProjectManager.getCurrentProjectId() || defaultProjectKey;
if (!this.projectMessageRepos.has(currentProjectId)) {
// Create new repository for this project
const repo = new MessageRepository();
this.projectMessageRepos.set(currentProjectId, repo);
}
return this.projectMessageRepos.get(currentProjectId)!;
}
```
### 3. ChatUIState (`src/state/ChatUIState.ts`)
**Purpose**: Clean UI-only state manager
**Design Philosophy**:
- Delegates ALL business logic to ChatManager
- Provides React integration with subscription mechanism
- Replaces legacy SharedState with minimal, focused approach
**React Integration**:
```typescript
// Subscribe to state changes
subscribe(listener: () => void): () => void
// Delegate operations to ChatManager
async sendMessage(displayText: string, context: MessageContext, chainType: ChainType, includeActiveNote?: boolean): Promise<string>
getMessages(): ChatMessage[] // Computed view for UI
// Project and persistence operations
async handleProjectSwitch(): Promise<void> // Handle UI updates for project switch
async saveChat(modelKey: string): Promise<{ success: boolean; path?: string; error?: string }>
// Legacy compatibility (for backward compatibility)
get chatHistory(): ChatMessage[]
addMessage(message: ChatMessage): void
clearChatHistory(): void
// Notify React components of changes
private notifyListeners(): void
```
### 4. ContextManager (`src/core/ContextManager.ts`)
**Purpose**: Handles context processing and reprocessing with layered context architecture
**Key Features**:
- **Layered Context System**: Builds structured L1-L5 context layers (see CONTEXT_ENGINEERING.md)
- **L2 Auto-Promotion**: Automatically promotes previous turn's context to L2 for cache stability
- **Deduplication**: Ensures context appears only once (L3 takes priority over L2)
- **Context Processing**: Handles notes, URLs, selected text, tags, and folders
- **Reprocessing**: Regenerates fresh context when messages are edited
- **Envelope Building**: Creates `PromptContextEnvelope` with structured layers and hashes
- **Chain-Aware Processing**: Applies chain-specific rules (e.g., Copilot Plus URL processing, active-note handling for vision models)
**Core Methods**:
```typescript
// Process context for new message (includes L2 building from history)
async processMessageContext(
message: ChatMessage,
fileParserManager: FileParserManager,
vault: Vault,
chainType: ChainType,
includeActiveNote: boolean,
activeNote: TFile | null,
messageRepo: MessageRepository,
systemPrompt?: string
): Promise<ContextProcessingResult>
// Reprocess context for edited message
async reprocessMessageContext(messageId: string, ...): Promise<void>
```
### 5. ChatPersistenceManager (`src/core/ChatPersistenceManager.ts`)
**Purpose**: Handles saving and loading chat history to/from markdown files
**Key Features**:
- Project-aware file naming (prefixes with project ID)
- Filters chat history files based on current project
- Parses and formats chat content for storage
- Integrated with ChatManager for seamless persistence
**Core Methods**:
```typescript
// Save chat to markdown file
async saveChat(messages: ChatMessage[], modelKey: string, projectId?: string): Promise<{ success: boolean; path?: string; error?: string }>
// Get available chat history files
async getChatHistoryFiles(): Promise<TFile[]>
// File naming convention
// Project chats: `[projectId]-[timestamp]-[modelKey]-chat.md`
// Non-project chats: `[timestamp]-[modelKey]-chat.md`
```
## Architecture Diagrams
### Complete System Architecture
```
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ User Interface Layer │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ Chat.tsx │ ◄────── uses ──────────► │ CopilotView.tsx │ │
│ │ │ │ │ │
│ └────────┬────────┘ └──────────────────┘ │
│ │ │
│ │ subscribes to & calls │
│ ▼ │
└───────────┬─────────────────────────────────────────────────────────────────────────┘
┌───────────┴─────────────────────────────────────────────────────────────────────────┐
│ State Layer │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ │ │
│ ┌────────▼────────┐ │
│ │ ChatUIState │ - React state management │
│ │ │ - Subscription mechanism for UI updates │
│ │ │ - Delegates all business logic to ChatManager │
│ └────────┬────────┘ │
│ │ │
└───────────┴─────────────────────────────────────────────────────────────────────────┘
│ delegates to
┌───────────▼─────────────────────────────────────────────────────────────────────────┐
│ Business Logic Layer │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ orchestrates ┌─────────────────────────────┐ │
│ │ ChatManager │ ◄──────────────────────────► │ ContextManager (singleton) │ │
│ │ │ │ │ │
│ │ - Message CRUD │ │ - Process message context │ │
│ │ - Project │ │ - Handle note attachments │ │
│ │ isolation │ │ - Reprocess on edit │ │
│ │ - Memory sync │ └─────────────────────────────┘ │
│ └────────┬────────┘ │
│ │ │
│ │ manages ┌─────────────────────────────┐ │
│ │ │ ChatPersistenceManager │ │
│ ├──────────────────────────────────────►│ │ │
│ │ │ - Save/load chat history │ │
│ │ │ - Project-aware file naming │ │
│ │ └─────────────────────────────┘ │
│ │ │
│ │ coordinates ┌─────────────────────────────┐ │
│ ├──────────────────────────────────────►│ ChainManager │ │
│ │ │ │ │
│ │ │ - Memory management │ │
│ │ │ - LLM chain operations │ │
│ │ └──────────┬──────────────────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────────────┐ │
│ │ │ MemoryManager │ │
│ │ │ │ │
│ │ │ - Chain memory storage │ │
│ │ │ - Conversation history │ │
│ │ └─────────────────────────────┘ │
└───────────┴─────────────────────────────────────────────────────────────────────────┘
┌───────────▼─────────────────────────────────────────────────────────────────────────┐
│ Data Storage Layer │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────┐ │
│ │ MessageRepository │ │
│ │ │ │
│ │ ┌─────────────────┐ Computed Views ┌────────────────────────────┐ │ │
│ │ │ StoredMessage[] │ ──────────────────────► │ getDisplayMessages() │ │ │
│ │ │ │ │ (for UI rendering) │ │ │
│ │ │ - id │ └────────────────────────────┘ │ │
│ │ │ - displayText │ │ │
│ │ │ - processedText │ ──────────────────────► ┌────────────────────────────┐ │ │
│ │ │ - sender │ │ getLLMMessages() │ │ │
│ │ │ - timestamp │ │ (for AI processing) │ │ │
│ │ │ - context │ └────────────────────────────┘ │ │
│ │ └─────────────────┘ │ │
│ │ │ │
│ │ Single source of truth - no dual storage! │ │
│ └─────────────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────────────────┘
```
## Project Isolation Architecture
### Multi-Repository Design
```
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ ChatManager │
├─────────────────────────────────────────────────────────────────────────────────────┤
│ │
│ projectMessageRepos: Map<string, MessageRepository> │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ "defaultProject" │ │ "project-1" │ │ "project-2" │ │
│ │ │ │ │ │ │ │
│ │ MessageRepo │ │ MessageRepo │ │ MessageRepo │ │
│ │ - Non-project │ │ - Project 1 │ │ - Project 2 │ │
│ │ messages │ │ messages only │ │ messages only │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ ▲ ▲ ▲ │
│ │ │ │ │
│ └─────────────────────────┴─────────────────────────┘ │
│ │ │
│ getCurrentMessageRepo() │
│ (auto-detects active project) │
│ │
└──────────────────────────────────────────────────────────────────────────────────────┘
```
### Project Switch Flow
```
Project Switch Detected (via Obsidian workspace)
ProjectManager.getCurrentProjectId() returns new ID
ChatManager.getCurrentMessageRepo()
Check if repository exists for project
↓ (if not)
Create new MessageRepository
Store in projectMessageRepos Map
Return project-specific repository
```
## Message Lifecycle
### Example: User Message with Context Note
When a user types "Summarize this note" and attaches "meeting-notes.md":
1. **Input**: User text + attached file → Chat component
2. **Storage**: MessageRepository stores displayText: "Summarize this note"
3. **Processing**: ContextManager reads the note and creates processedText with proper XML structure
4. **Memory Sync**: Chain memory receives the processed version for LLM
5. **UI Update**: Shows message with context badge, displays only "Summarize this note"
6. **LLM Processing**: AI receives full context and generates response
### Context XML Format
All context is wrapped in semantic XML tags for clear structure:
> Layered Prompting: During Phase 3 the raw XML is still captured in `processedText` for backward compatibility, but the canonical representation sent to the LLM comes from the envelope layers:
>
> - **L1_SYSTEM / L2_PREVIOUS**: Stable prefixes rendered from accumulated context
> - **L3_TURN**: Turn-specific smart references that either link back to L2 or embed full content
> - **L4_STRIP**: Chat history managed by memory
> - **L5_USER**: The users raw message (plus composer directives when present)
#### Note Context
```xml
<note_context>
<title>meeting-notes</title>
<path>docs/meeting-notes.md</path>
<ctime>2024-01-15T10:00:00.000Z</ctime>
<mtime>2024-01-15T14:30:00.000Z</mtime>
<content>
[actual note content here]
</content>
</note_context>
```
#### URL Context
```xml
<url_content>
<url>https://example.com/article</url>
<content>
[fetched content from URL]
</content>
</url_content>
```
#### Selected Text Context
```xml
<selected_text>
<title>Source Note Title</title>
<path>path/to/source.md</path>
<start_line>45</start_line>
<end_line>52</end_line>
<content>
[selected text content]
</content>
</selected_text>
```
#### Error Cases
```xml
<note_context_error>
<title>filename</title>
<path>path/to/file.ext</path>
<error>[Error: Could not process file]</error>
</note_context_error>
```
This separation ensures:
- Clean UI (shows what user typed)
- Rich context for AI (includes note content)
- Reprocessable context on message edits
For detailed examples, see:
- `src/core/MessageLifecycle.test.ts` - Complete lifecycle demonstration with context notes
- `src/core/MessageLifecycle.xmltags.test.ts` - XML tag formatting tests and examples
### 1. Sending a New Message
```
User Input
↓ (via Chat component)
ChatUIState.sendMessage()
ChatManager.sendMessage()
MessageRepository.addMessage() // Store with basic content
ContextManager.processMessageContext() // Add context
MessageRepository.updateProcessedText() // Update with processed text + context envelope
ChatManager.updateChainMemory() // Sync to LLM
ChatUIState.notifyListeners() // Update UI
```
### 2. Editing a Message
```
User Edit
ChatUIState.editMessage()
ChatManager.editMessage()
MessageRepository.editMessage() // Update display text
ContextManager.reprocessMessageContext() // Fresh context
ChatManager.updateChainMemory() // Sync to LLM
ChatUIState.notifyListeners() // Update UI
```
### 3. Message Display
```
React Component Render
ChatUIState.getMessages()
ChatManager.getDisplayMessages()
ChatManager.getCurrentMessageRepo() // Project-aware
MessageRepository.getDisplayMessages() // Computed view
Filter visible messages → Map to ChatMessage format
```
### 4. Saving Chat History
```
User Save Action
Chat.tsx → ChatUIState.saveChat(modelKey)
ChatManager.saveChat(modelKey)
Get current project ID and messages
ChatPersistenceManager.saveChat(messages, modelKey, projectId)
Create markdown file with project prefix
Return success with file path
```
### 5. Project Switch
```
Project Change in Obsidian
ChatUIState.handleProjectSwitch()
ChatManager.handleProjectSwitch()
Force getCurrentMessageRepo() to re-detect project
Switch to different MessageRepository
Update chain memory with new project's messages
Notify UI listeners for refresh
```
## Data Structures
### StoredMessage (Internal)
```typescript
interface StoredMessage {
id: string;
displayText: string; // What user typed/AI responded
processedText: string; // With context for user, same as display for AI
sender: string;
timestamp: FormattedDateTime;
context?: MessageContext;
isVisible: boolean;
isErrorMessage?: boolean;
sources?: { title: string; score: number }[];
content?: any[];
}
```
### ChatMessage (External Interface)
```typescript
interface ChatMessage {
id?: string;
message: string; // Display text
originalMessage?: string; // Processed text
sender: string;
timestamp: FormattedDateTime | null;
isVisible: boolean;
context?: MessageContext;
isErrorMessage?: boolean;
sources?: { title: string; score: number }[];
content?: any[];
}
```
### MessageContext
```typescript
interface MessageContext {
notes: TFile[];
urls: string[];
selectedTextContexts: SelectedTextContext[];
}
```
## Chat History Loading
### Pending Message Mechanism
The new architecture uses a "pending message" pattern for loading chat history:
```
main.ts.loadChatHistory()
Parse messages from file
CopilotView.setPendingMessages()
Chat component receives pendingMessages prop
useEffect detects pendingMessages
ChatUIState.loadMessages()
onPendingMessagesProcessed() callback clears pending
```
### Project-Aware Loading
When loading chat history:
1. ChatPersistenceManager filters files based on current project
2. Only shows chat files prefixed with current project ID
3. Non-project chats visible when no project is active
## Testing Strategy
### Unit Tests
- **MessageRepository**: 23 comprehensive tests including bug prevention
- **ChatManager**: 25+ tests covering all critical functionality
- **Component Tests**: MessageContext duplicate key prevention
### Bug Prevention Tests
1. **Context Badge Bug**: Ensures context displays correctly
2. **Memory Synchronization**: Prevents chat memory count mismatches
3. **Edit Message Bug**: Verifies proper context reprocessing
4. **Duplicate Notes**: Prevents React key conflicts in context display
## Migration from SharedState
### Before (Legacy)
```typescript
// Multiple sources of truth
const sharedState = {
currentChatMessages: ChatMessage[],
chatHistory: ChatMessage[],
// Complex sync logic between arrays
}
```
### After (Clean Architecture)
```typescript
// Single source of truth
const messageRepository = new MessageRepository();
const chatManager = new ChatManager(messageRepository, ...);
const chatUIState = new ChatUIState(chatManager);
// Computed views
const displayMessages = chatUIState.getMessages(); // For UI
const llmMessages = chatManager.getLLMMessages(); // For AI
```
## Performance Considerations
### Memory Efficiency
- Single storage eliminates duplicate message objects
- Computed views are generated on-demand
- Context processing only when needed
### React Optimization
- Subscription-based updates minimize re-renders
- Unique keys prevent React reconciliation issues
- State changes are batched through ChatUIState
## Key Architectural Features
### Project Isolation Benefits
1. **Complete Separation**: Each project has entirely separate chat history
2. **Automatic Management**: No user configuration needed
3. **Seamless Switching**: Instant context switch when changing projects
4. **Memory Efficient**: Only active project's messages in memory
5. **Fresh Start**: Each project starts with empty chat history
### Persistence Integration
1. **Project-Aware Naming**: Files prefixed with project ID
2. **Filtered File Lists**: Only shows relevant chat files
3. **Consistent Format**: Same markdown format across all projects
4. **Error Handling**: Graceful fallbacks for save/load failures
## Troubleshooting
### Common Issues
1. **Context not updating**: Check if `updateChainMemory()` is called after edits
2. **UI not refreshing**: Ensure `notifyListeners()` is called after state changes
3. **Memory count mismatch**: Verify `truncateAfterMessageId()` updates chain memory
4. **Duplicate context badges**: Check React keys in MessageContext component
5. **Wrong project messages**: Check `getCurrentProjectId()` returns expected value
6. **Missing chat history**: Verify project ID in filename matches current project
### Debug Methods
```typescript
// Check message repository state
messageRepo.getDebugInfo();
// Check chat manager state
chatManager.getDebugInfo();
// Check LLM vs display message counts
console.log({
display: chatUIState.getMessages().length,
llm: chatManager.getLLMMessages().length,
});
// Check current project and repository
const debugInfo = chatManager.getDebugInfo();
console.log({
currentProject: debugInfo.currentProjectId,
totalProjects: debugInfo.projectCount,
messagesByProject: debugInfo.messageCountByProject,
});
```
## Related Files
### Documentation
- `docs/CONTEXT_ENGINEERING.md` - Layered context system design and L2 auto-promotion details
- `docs/MESSAGE_ARCHITECTURE.md` - This document (message management architecture)
### Core Implementation
- `src/core/MessageRepository.ts` - Message storage
- `src/core/ChatManager.ts` - Business logic with project isolation
- `src/state/ChatUIState.ts` - UI state management
- `src/core/ContextManager.ts` - Context processing
- `src/core/ChatPersistenceManager.ts` - Chat history persistence
### React Integration
- `src/components/Chat.tsx` - Main chat component
- `src/hooks/useChatManager.ts` - React hook for ChatUIState
- `src/components/chat-components/ChatSingleMessage.tsx` - Message display
### Testing
- `src/core/MessageRepository.test.ts` - Repository tests
- `src/core/ChatManager.test.ts` - Manager tests
- `src/core/MessageLifecycle.test.ts` - Complete lifecycle examples with context notes
- `src/core/MessageLifecycle.xmltags.test.ts` - XML tag formatting tests and examples
- `src/components/chat-components/MessageContext.test.tsx` - Context display tests