MAESTRO: Complete Phase 4 Context Integration for per-session history

- Add documentation for AI context integration in HistoryHelpModal
- Add "Per-Session Storage" section explaining 5,000 entry limit
- Add "AI Context Integration" section explaining history file usage
- Document History API in CLAUDE.md with getFilePath() and listSessions()
- Add cross-session view toggle (Layers button) to HistoryPanel
- Update test mocks for new Layers and FileJson icons
- Build passes and all 10,010 tests pass
This commit is contained in:
Pedram Amini
2025-12-14 19:09:47 -06:00
parent 2a239e8e4b
commit b47c9612fb
4 changed files with 140 additions and 11 deletions

View File

@@ -352,10 +352,32 @@ The `window.maestro` API exposes:
### Automation
- `autorun` - Document and image management for Auto Run
- `playbooks` - Batch run configuration management
- `history` - Per-project execution history with external change detection
- `history` - Per-session execution history (see History API below)
- `cli` - CLI activity detection for playbook runs
- `tempfile` - Temporary file management for batch processing
### History API
Per-session history storage with 5,000 entries per session (up from 1,000 global). Each session's history is stored as a JSON file in `~/Library/Application Support/Maestro/history/{sessionId}.json`.
```typescript
window.maestro.history = {
getAll: (projectPath?, sessionId?) => Promise<HistoryEntry[]>,
add: (entry) => Promise<boolean>,
clear: (projectPath?, sessionId?) => Promise<boolean>,
delete: (entryId, sessionId?) => Promise<boolean>,
update: (entryId, updates, sessionId?) => Promise<boolean>,
// For AI context integration:
getFilePath: (sessionId) => Promise<string | null>,
listSessions: () => Promise<string[]>,
// External change detection:
onExternalChange: (handler) => () => void,
reload: () => Promise<boolean>,
};
```
**AI Context Integration**: Use `getFilePath(sessionId)` to get the path to a session's history file. This file can be passed directly to AI agents as context, giving them visibility into past completed tasks, decisions, and work patterns.
### Utilities
- `fonts` - Font detection
- `notification` - Desktop notifications, text-to-speech

View File

@@ -49,6 +49,12 @@ vi.mock('lucide-react', () => ({
),
Eye: ({ className, style }: { className?: string; style?: React.CSSProperties }) => (
<svg data-testid="eye-icon" className={className} style={style} />
),
Layers: ({ className, style }: { className?: string; style?: React.CSSProperties }) => (
<svg data-testid="layers-icon" className={className} style={style} />
),
FileJson: ({ className, style }: { className?: string; style?: React.CSSProperties }) => (
<svg data-testid="file-json-icon" className={className} style={style} />
)
}));
@@ -227,10 +233,12 @@ describe('HistoryHelpModal', () => {
expect(screen.getByTestId('user-icon')).toBeInTheDocument();
});
it('renders Bot icon in AUTO badge', () => {
it('renders Bot icon in AUTO badge and AI Context section', () => {
render(<HistoryHelpModal {...defaultProps} />);
expect(screen.getByTestId('bot-icon')).toBeInTheDocument();
// There are multiple bot icons: one in AUTO badge, one in AI Context Integration section
const botIcons = screen.getAllByTestId('bot-icon');
expect(botIcons.length).toBeGreaterThanOrEqual(1);
});
it('renders /synopsis code snippet', () => {

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useRef } from 'react';
import { X, History, Play, Clock, DollarSign, BarChart2, CheckCircle, Bot, User, Eye } from 'lucide-react';
import { X, History, Play, Clock, DollarSign, BarChart2, CheckCircle, Bot, User, Eye, Layers, FileJson } from 'lucide-react';
import type { Theme } from '../types';
import { useLayerStack } from '../contexts/LayerStackContext';
import { MODAL_PRIORITIES } from '../constants/modalPriorities';
@@ -277,6 +277,66 @@ export function HistoryHelpModal({ theme, onClose }: HistoryHelpModalProps) {
</p>
</div>
</section>
{/* Per-Session Storage */}
<section>
<div className="flex items-center gap-2 mb-3">
<Layers className="w-5 h-5" style={{ color: theme.colors.accent }} />
<h3 className="font-bold">Per-Session Storage</h3>
</div>
<div
className="text-sm space-y-2 pl-7"
style={{ color: theme.colors.textDim }}
>
<p>
History is stored in per-session files with a limit of{' '}
<strong style={{ color: theme.colors.textMain }}>5,000 entries per session</strong>.
This provides better isolation and scalability compared to a single global file.
</p>
<p>
Use the{' '}
<span
className="inline-flex items-center justify-center w-5 h-5 rounded"
style={{
border: `1px solid ${theme.colors.border}`,
verticalAlign: 'middle'
}}
>
<Layers className="w-3 h-3" />
</span>{' '}
toggle button to switch between viewing only the current session's history
or a cross-session view of all history for the project.
</p>
</div>
</section>
{/* AI Context Integration */}
<section>
<div className="flex items-center gap-2 mb-3">
<Bot className="w-5 h-5" style={{ color: theme.colors.accent }} />
<h3 className="font-bold">AI Context Integration</h3>
</div>
<div
className="text-sm space-y-2 pl-7"
style={{ color: theme.colors.textDim }}
>
<p>
History files can be passed directly to AI agents as context. Each session's
history is stored as a JSON file that the AI can read to understand past work.
</p>
<p>
<strong style={{ color: theme.colors.textMain }}>File location:</strong>{' '}
<code className="px-1.5 py-0.5 rounded text-[11px]" style={{ backgroundColor: theme.colors.bgActivity }}>
~/Library/Application Support/Maestro/history/{'<sessionId>'}.json
</code>
</p>
<p>
<strong style={{ color: theme.colors.textMain }}>Usage with Claude Code:</strong>{' '}
Reference the history file in your prompts to give the AI context about
completed tasks, decisions made, and work patterns in your session.
</p>
</div>
</section>
</div>
{/* Footer */}

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useRef, useCallback, useImperativeHandle, forwardRef, useMemo } from 'react';
import { Bot, User, ExternalLink, Check, X, Clock, HelpCircle, Award } from 'lucide-react';
import { Bot, User, ExternalLink, Check, X, Clock, HelpCircle, Award, Layers } from 'lucide-react';
import type { Session, Theme, HistoryEntry, HistoryEntryType } from '../types';
import { HistoryDetailModal } from './HistoryDetailModal';
import { HistoryHelpModal } from './HistoryHelpModal';
@@ -437,6 +437,7 @@ export const HistoryPanel = React.memo(forwardRef<HistoryPanelHandle, HistoryPan
const [graphReferenceTime, setGraphReferenceTime] = useState<number | undefined>(undefined);
const [helpModalOpen, setHelpModalOpen] = useState(false);
const [graphLookbackHours, setGraphLookbackHours] = useState<number | null>(null); // default to "All time"
const [viewMode, setViewMode] = useState<'session' | 'all'>('session'); // 'session' = current session only, 'all' = cross-session view
const listRef = useRef<HTMLDivElement>(null);
const itemRefs = useRef<Record<number, HTMLDivElement | null>>({});
@@ -454,8 +455,14 @@ export const HistoryPanel = React.memo(forwardRef<HistoryPanelHandle, HistoryPan
}
try {
// Pass sessionId to filter: only show entries from this session or legacy entries without sessionId
const entries = await window.maestro.history.getAll(session.cwd, session.id);
let entries: HistoryEntry[];
if (viewMode === 'all') {
// Cross-session view: get all entries for this project
entries = await window.maestro.history.getAll(session.cwd);
} else {
// Session view: only show entries from this session or legacy entries without sessionId
entries = await window.maestro.history.getAll(session.cwd, session.id);
}
// Ensure entries is an array, limit to MAX_HISTORY_IN_MEMORY
const validEntries = Array.isArray(entries) ? entries : [];
setHistoryEntries(validEntries.slice(0, MAX_HISTORY_IN_MEMORY));
@@ -481,7 +488,7 @@ export const HistoryPanel = React.memo(forwardRef<HistoryPanelHandle, HistoryPan
}
}
// Note: displayCount intentionally NOT in deps - we don't want to reload history when it changes
}, [session.cwd, session.id]);
}, [session.cwd, session.id, viewMode]);
// Expose focus and refreshHistory methods to parent
useImperativeHandle(ref, () => ({
@@ -695,12 +702,12 @@ export const HistoryPanel = React.memo(forwardRef<HistoryPanelHandle, HistoryPan
hasRestoredScroll.current = false;
}, [session.id]);
// Reset selected index, display count, and graph reference time when filters change
// Reset selected index, display count, and graph reference time when filters or view mode change
useEffect(() => {
setSelectedIndex(-1);
setDisplayCount(INITIAL_DISPLAY_COUNT);
setGraphReferenceTime(undefined); // Reset to "now" when filters change
}, [activeFilters, searchFilter]);
}, [activeFilters, searchFilter, viewMode]);
// Scroll selected item into view
useEffect(() => {
@@ -863,6 +870,20 @@ export const HistoryPanel = React.memo(forwardRef<HistoryPanelHandle, HistoryPan
onLookbackChange={handleLookbackChange}
/>
{/* View mode toggle */}
<button
onClick={() => setViewMode(prev => prev === 'session' ? 'all' : 'session')}
className="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded transition-colors hover:bg-white/10"
style={{
color: viewMode === 'all' ? theme.colors.accent : theme.colors.textDim,
border: `1px solid ${viewMode === 'all' ? theme.colors.accent : theme.colors.border}`,
backgroundColor: viewMode === 'all' ? theme.colors.accent + '15' : 'transparent'
}}
title={viewMode === 'session' ? 'Show all sessions (cross-session view)' : 'Show current session only'}
>
<Layers className="w-3.5 h-3.5" />
</button>
{/* Help button */}
<button
onClick={() => setHelpModalOpen(true)}
@@ -926,7 +947,9 @@ export const HistoryPanel = React.memo(forwardRef<HistoryPanelHandle, HistoryPan
) : filteredEntries.length === 0 ? (
<div className="text-center py-8 text-xs opacity-50">
{historyEntries.length === 0
? 'No history yet. Run batch tasks or use /synopsis to add entries.'
? viewMode === 'all'
? 'No history yet for this project. Run batch tasks or use /synopsis to add entries.'
: 'No history yet. Run batch tasks or use /synopsis to add entries.'
: searchFilter
? `No entries match "${searchFilter}"`
: 'No entries match the selected filters.'}
@@ -1018,6 +1041,22 @@ export const HistoryPanel = React.memo(forwardRef<HistoryPanelHandle, HistoryPan
<ExternalLink className="w-2.5 h-2.5 flex-shrink-0" />
</button>
)}
{/* Cross-session indicator - shows when viewing all sessions and entry is from a different session */}
{viewMode === 'all' && entry.sessionId && entry.sessionId !== session.id && (
<span
className="flex items-center gap-1 px-2 py-0.5 rounded-full text-[10px] font-medium"
style={{
backgroundColor: theme.colors.warning + '15',
color: theme.colors.warning,
border: `1px solid ${theme.colors.warning}30`,
}}
title={`From session ${entry.sessionId.substring(0, 8)}...`}
>
<Layers className="w-2.5 h-2.5" />
<span className="font-mono">{entry.sessionId.substring(0, 8)}</span>
</span>
)}
</div>
{/* Timestamp */}