mirror of
https://github.com/jlengrand/Maestro.git
synced 2026-03-10 08:31:19 +00:00
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:
24
CLAUDE.md
24
CLAUDE.md
@@ -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
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
@@ -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 */}
|
||||
|
||||
Reference in New Issue
Block a user