feat: isolate Claude and Codex session sources
Persist platform_source across session creation, transcript ingestion, API query paths, and viewer state so Claude and Codex data can coexist without bleeding into each other. - add platform-source normalization helpers and persist platform_source in sdk_sessions via migration 24 with backfill and indexing - thread platformSource through CLI hooks, transcript processing, context generation, pagination, search routes, SSE payloads, and session management - expose source-aware project catalogs, viewer tabs, context preview selectors, and source badges for observations, prompts, and summaries - start the transcript watcher from the worker for transcript-based clients and preserve platform source during Codex ingestion - auto-start the worker from the MCP server for MCP-only clients and tighten stdio-driven cleanup during shutdown - keep createSDKSession backward compatible with existing custom-title callers while allowing explicit platform source forwarding
This commit is contained in:
@@ -136,7 +136,17 @@ export function ContextSettingsModal({
|
||||
}, [settings]);
|
||||
|
||||
// Get context preview based on current form state
|
||||
const { preview, isLoading, error, projects, selectedProject, setSelectedProject } = useContextPreview(formState);
|
||||
const {
|
||||
preview,
|
||||
isLoading,
|
||||
error,
|
||||
projects,
|
||||
sources,
|
||||
selectedSource,
|
||||
setSelectedSource,
|
||||
selectedProject,
|
||||
setSelectedProject
|
||||
} = useContextPreview(formState);
|
||||
|
||||
const updateSetting = useCallback((key: keyof Settings, value: string) => {
|
||||
const newState = { ...formState, [key]: value };
|
||||
@@ -174,10 +184,23 @@ export function ContextSettingsModal({
|
||||
<h2>Settings</h2>
|
||||
<div className="header-controls">
|
||||
<label className="preview-selector">
|
||||
Preview for:
|
||||
Source:
|
||||
<select
|
||||
value={selectedSource || ''}
|
||||
onChange={(e) => setSelectedSource(e.target.value)}
|
||||
disabled={sources.length === 0}
|
||||
>
|
||||
{sources.map(source => (
|
||||
<option key={source} value={source}>{source}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="preview-selector">
|
||||
Project:
|
||||
<select
|
||||
value={selectedProject || ''}
|
||||
onChange={(e) => setSelectedProject(e.target.value)}
|
||||
disabled={projects.length === 0}
|
||||
>
|
||||
{projects.map(project => (
|
||||
<option key={project} value={project}>{project}</option>
|
||||
|
||||
@@ -7,8 +7,11 @@ import { useSpinningFavicon } from '../hooks/useSpinningFavicon';
|
||||
interface HeaderProps {
|
||||
isConnected: boolean;
|
||||
projects: string[];
|
||||
sources: string[];
|
||||
currentFilter: string;
|
||||
currentSource: string;
|
||||
onFilterChange: (filter: string) => void;
|
||||
onSourceChange: (source: string) => void;
|
||||
isProcessing: boolean;
|
||||
queueDepth: number;
|
||||
themePreference: ThemePreference;
|
||||
@@ -16,11 +19,26 @@ interface HeaderProps {
|
||||
onContextPreviewToggle: () => void;
|
||||
}
|
||||
|
||||
function formatSourceLabel(source: string): string {
|
||||
if (source === 'all') return 'All';
|
||||
if (source === 'claude') return 'Claude';
|
||||
if (source === 'codex') return 'Codex';
|
||||
return source.charAt(0).toUpperCase() + source.slice(1);
|
||||
}
|
||||
|
||||
function buildSourceTabs(sources: string[]): string[] {
|
||||
const merged = ['all', 'claude', 'codex', ...sources];
|
||||
return Array.from(new Set(merged.filter(Boolean)));
|
||||
}
|
||||
|
||||
export function Header({
|
||||
isConnected,
|
||||
projects,
|
||||
sources,
|
||||
currentFilter,
|
||||
currentSource,
|
||||
onFilterChange,
|
||||
onSourceChange,
|
||||
isProcessing,
|
||||
queueDepth,
|
||||
themePreference,
|
||||
@@ -28,20 +46,36 @@ export function Header({
|
||||
onContextPreviewToggle
|
||||
}: HeaderProps) {
|
||||
useSpinningFavicon(isProcessing);
|
||||
const availableSources = buildSourceTabs(sources);
|
||||
|
||||
return (
|
||||
<div className="header">
|
||||
<h1>
|
||||
<div style={{ position: 'relative', display: 'inline-block' }}>
|
||||
<img src="claude-mem-logomark.webp" alt="" className={`logomark ${isProcessing ? 'spinning' : ''}`} />
|
||||
{queueDepth > 0 && (
|
||||
<div className="queue-bubble">
|
||||
{queueDepth}
|
||||
</div>
|
||||
)}
|
||||
<div className="header-main">
|
||||
<h1>
|
||||
<div style={{ position: 'relative', display: 'inline-block' }}>
|
||||
<img src="claude-mem-logomark.webp" alt="" className={`logomark ${isProcessing ? 'spinning' : ''}`} />
|
||||
{queueDepth > 0 && (
|
||||
<div className="queue-bubble">
|
||||
{queueDepth}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className="logo-text">claude-mem</span>
|
||||
</h1>
|
||||
<div className="source-tabs" role="tablist" aria-label="Context source tabs">
|
||||
{availableSources.map(source => (
|
||||
<button
|
||||
key={source}
|
||||
type="button"
|
||||
className={`source-tab ${currentSource === source ? 'active' : ''}`}
|
||||
onClick={() => onSourceChange(source)}
|
||||
aria-pressed={currentSource === source}
|
||||
>
|
||||
{formatSourceLabel(source)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<span className="logo-text">claude-mem</span>
|
||||
</h1>
|
||||
</div>
|
||||
<div className="status">
|
||||
<a
|
||||
href="https://docs.claude-mem.ai"
|
||||
|
||||
@@ -52,6 +52,9 @@ export function ObservationCard({ observation }: ObservationCardProps) {
|
||||
<span className={`card-type type-${observation.type}`}>
|
||||
{observation.type}
|
||||
</span>
|
||||
<span className={`card-source source-${observation.platform_source || 'claude'}`}>
|
||||
{observation.platform_source || 'claude'}
|
||||
</span>
|
||||
<span className="card-project">{observation.project}</span>
|
||||
</div>
|
||||
<div className="view-mode-toggles">
|
||||
|
||||
@@ -14,6 +14,9 @@ export function PromptCard({ prompt }: PromptCardProps) {
|
||||
<div className="card-header">
|
||||
<div className="card-header-left">
|
||||
<span className="card-type">Prompt</span>
|
||||
<span className={`card-source source-${prompt.platform_source || 'claude'}`}>
|
||||
{prompt.platform_source || 'claude'}
|
||||
</span>
|
||||
<span className="card-project">{prompt.project}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,6 +21,9 @@ export function SummaryCard({ summary }: SummaryCardProps) {
|
||||
<header className="summary-card-header">
|
||||
<div className="summary-badge-row">
|
||||
<span className="card-type summary-badge">Session Summary</span>
|
||||
<span className={`card-source source-${summary.platform_source || 'claude'}`}>
|
||||
{summary.platform_source || 'claude'}
|
||||
</span>
|
||||
<span className="summary-project-badge">{summary.project}</span>
|
||||
</div>
|
||||
{summary.request && (
|
||||
|
||||
Reference in New Issue
Block a user