feat: add conversation management, persistence, and mobile app spec

Major enhancements to voice assistant with conversation persistence
and mobile app planning:

- Add conversation management with multiple active conversations
- Implement conversation persistence (save/load from disk)
- Add conversation selector UI for switching between conversations
- Enhance agent prompt with per-conversation tmux sessions
- Add mobile app technical specification (React Native + Expo)
- Improve UI with active processes view and agent stream
- Add ACP (Agent Control Protocol) server implementation

Mobile spec details:
- React Native with Expo SDK 54+
- NativeWind v5 for styling
- Share Zod schemas/types from server
- Android in-call experience (InCallManager)
- Direct WebSocket connection via Tailscale
- Same audio quality as web prototype

🤖 Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
Mohamed Boudra
2025-10-21 14:50:07 +02:00
co-authored by Claude Happy
parent 37a363da6d
commit 4f2c2fc14a
34 changed files with 6963 additions and 420 deletions
@@ -0,0 +1,102 @@
import { Terminal, Bot } from "lucide-react";
import type { AgentStatus } from "../../server/acp/types.js";
export interface ActiveProcessesProps {
terminals: Array<{ id: string; name: string }>;
agents: Array<{ id: string; status: AgentStatus; type: "claude" }>;
activeProcessId: string | null;
activeProcessType: "terminal" | "agent" | null;
onSelectProcess: (id: string, type: "terminal" | "agent") => void;
onBackToOrchestrator: () => void;
}
function getAgentStatusColor(status: AgentStatus): string {
switch (status) {
case "initializing":
return "#f59e0b"; // orange
case "ready":
return "#3b82f6"; // blue
case "processing":
return "#fbbf24"; // yellow
case "completed":
return "#22c55e"; // green
case "failed":
return "#ef4444"; // red
case "killed":
return "#9ca3af"; // gray
default:
return "#9ca3af";
}
}
function getAgentStatusLabel(status: AgentStatus): string {
return status;
}
export function ActiveProcesses({
terminals,
agents,
activeProcessId,
activeProcessType,
onSelectProcess,
onBackToOrchestrator,
}: ActiveProcessesProps) {
const hasActiveProcess = activeProcessId !== null && activeProcessType !== null;
return (
<div className="active-processes-bar">
{hasActiveProcess && (
<button
className="back-to-orchestrator-button"
onClick={onBackToOrchestrator}
type="button"
>
Back to Chat
</button>
)}
<div className="processes-pills">
{terminals.map((terminal) => (
<button
key={`terminal-${terminal.id}`}
className={`process-pill terminal ${
activeProcessType === "terminal" && activeProcessId === terminal.id
? "active"
: ""
}`}
onClick={() => onSelectProcess(terminal.id, "terminal")}
type="button"
>
<Terminal size={14} />
<span className="process-name">{terminal.name}</span>
<span className="process-status-indicator running" />
</button>
))}
{agents.map((agent) => (
<button
key={`agent-${agent.id}`}
className={`process-pill agent ${
activeProcessType === "agent" && activeProcessId === agent.id
? "active"
: ""
}`}
onClick={() => onSelectProcess(agent.id, "agent")}
type="button"
data-status={agent.status}
>
<Bot size={14} />
<span className="process-name">
{agent.id.substring(0, 8)}
</span>
<span
className={`process-status-indicator ${agent.status}`}
style={{ backgroundColor: getAgentStatusColor(agent.status) }}
title={getAgentStatusLabel(agent.status)}
/>
</button>
))}
</div>
</div>
);
}