mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
Remove outdated daemon lifecycle and Gmail skill documentation
- Deleted `daemon-lifecycle-management.md` to reflect feature deprecation and simplify technical documentation. - Removed `gmail-skill-integration.md` as it is no longer relevant to the current architecture.
This commit is contained in:
-459
@@ -1,459 +0,0 @@
|
||||
---
|
||||
|
||||
```md
|
||||
# Cross-Platform Tauri Agent Assistant — MVP Specification
|
||||
|
||||
> **Current product docs:** we **ship and support desktop only** (Windows, macOS, Linux). This spec still describes a **broader platform vision** (including mobile) for engineering planning—it is not a promise of availability on Android/iOS or web yet.
|
||||
|
||||
## Overview
|
||||
|
||||
This MVP defines a **Telegram-based Agent Assistant** built with **Tauri (Rust + Web UI)** targeting:
|
||||
|
||||
- Windows
|
||||
- macOS
|
||||
- Android
|
||||
- iOS
|
||||
|
||||
The assistant:
|
||||
- Interacts with users via a **Telegram bot (DM-first)**
|
||||
- Processes Telegram channel data **locally on device**
|
||||
- Uses a **minimal backend** only for:
|
||||
- identity & login
|
||||
- payments & entitlements
|
||||
- push notifications (especially for iOS)
|
||||
- Avoids storing Telegram message content on servers
|
||||
|
||||
---
|
||||
|
||||
## Core Platform Behavior Summary
|
||||
|
||||
| Platform | Listening Model | Trigger to Respond |
|
||||
| -------- | ------------------------------- | -------------------------- |
|
||||
| Windows | Continuous (background) | Bot DM or channel activity |
|
||||
| macOS | Continuous (background) | Bot DM or channel activity |
|
||||
| Android | Continuous (foreground service) | Bot DM or channel activity |
|
||||
| iOS | On-demand only | Bot DM → push → tap → sync |
|
||||
|
||||
---
|
||||
|
||||
## Architectural Pillars
|
||||
|
||||
- **UI-first development**
|
||||
- **Single Rust agent runtime**
|
||||
- **Telegram bot as the user interface**
|
||||
- **Privacy-first local processing**
|
||||
- **Backend as infrastructure, not intelligence**
|
||||
|
||||
---
|
||||
|
||||
# PHASED MVP PLAN
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — Project Skeleton & Tooling
|
||||
|
||||
### Goals
|
||||
|
||||
- Prepare repo structure
|
||||
- Establish documentation and contribution rules
|
||||
- No business logic yet
|
||||
|
||||
### Deliverables
|
||||
|
||||
- Monorepo structure
|
||||
- Tauri project scaffold
|
||||
- Mobile targets enabled (Tauri v2)
|
||||
- CI hooks (optional)
|
||||
|
||||
### Repo Structure
|
||||
|
||||
```
|
||||
|
||||
/apps
|
||||
/desktop
|
||||
/mobile
|
||||
/core
|
||||
/agent-runtime (Rust)
|
||||
/storage
|
||||
/telegram
|
||||
/backend
|
||||
/docs
|
||||
|
||||
```
|
||||
|
||||
### Documentation Commands (Required)
|
||||
|
||||
```bash
|
||||
/docs/architecture.md # high-level system design
|
||||
/docs/decisions/ADR-000.md # initial architecture decision record
|
||||
```
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- App builds and runs (blank UI)
|
||||
- Docs folder initialized
|
||||
- ADR process agreed upon
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — UI-First MVP (No Logic)
|
||||
|
||||
### Goals
|
||||
|
||||
Build the **entire UI flow** before implementing logic.
|
||||
|
||||
### UI Screens
|
||||
|
||||
- Login / Signup
|
||||
- Telegram Connect (bot instructions)
|
||||
- Channel Selection
|
||||
- Sync Status Screen
|
||||
- Settings (background, privacy, storage)
|
||||
- Plan & Billing (stub)
|
||||
- Logs / Activity View (local only)
|
||||
|
||||
### Platforms
|
||||
|
||||
- All platforms (desktop + mobile)
|
||||
|
||||
### Deliverables
|
||||
|
||||
- Responsive UI
|
||||
- Navigation between screens
|
||||
- Mock data only
|
||||
- No Telegram, no backend, no Rust logic
|
||||
|
||||
### Documentation Commands
|
||||
|
||||
```bash
|
||||
/docs/ui/flows.md # user flows
|
||||
/docs/ui/screens.md # screen definitions
|
||||
/docs/ui/states.md # loading / error / empty states
|
||||
```
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- Entire app is navigable
|
||||
- No dead-end screens
|
||||
- UI approved before logic begins
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Local Agent Runtime (Rust Only)
|
||||
|
||||
### Goals
|
||||
|
||||
Implement the **local agent engine** without Telegram or backend.
|
||||
|
||||
### Components
|
||||
|
||||
- Rust agent runtime
|
||||
- Intent router (question / sync / config)
|
||||
- Processing pipeline (stubbed)
|
||||
- Response composer (mock responses)
|
||||
|
||||
### Deliverables
|
||||
|
||||
- Tauri IPC commands:
|
||||
- `agent_init`
|
||||
- `agent_process_query`
|
||||
- `agent_status`
|
||||
|
||||
- In-memory only state
|
||||
|
||||
### Documentation Commands
|
||||
|
||||
```bash
|
||||
/docs/core/agent.md # agent architecture
|
||||
/docs/core/events.md # internal event types
|
||||
/docs/core/state.md # memory state model
|
||||
```
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- UI can send a question
|
||||
- Agent returns a mock response
|
||||
- No persistence yet
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Local Storage & Privacy Layer
|
||||
|
||||
### Goals
|
||||
|
||||
Add **efficient, privacy-first local storage**.
|
||||
|
||||
### Storage Rules
|
||||
|
||||
- No Telegram message bodies by default
|
||||
- Store only:
|
||||
- channel IDs
|
||||
- last processed message IDs
|
||||
- dedupe hashes
|
||||
|
||||
- Encrypted at rest
|
||||
|
||||
### Deliverables
|
||||
|
||||
- Encrypted SQLite
|
||||
- OS keychain integration
|
||||
- Storage abstraction in Rust
|
||||
- “Ephemeral mode” toggle
|
||||
|
||||
### Documentation Commands
|
||||
|
||||
```bash
|
||||
/docs/storage/schema.md
|
||||
/docs/storage/encryption.md
|
||||
/docs/privacy/model.md
|
||||
```
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- App restarts without losing cursors
|
||||
- “Delete local data” wipes all state
|
||||
- No plaintext sensitive data on disk
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Telegram Bot Integration (Agent Assistant)
|
||||
|
||||
### Goals
|
||||
|
||||
Turn the app into a **real Telegram agent assistant**.
|
||||
|
||||
### Telegram Capabilities (MVP)
|
||||
|
||||
- Bot DM interaction
|
||||
- Read user questions
|
||||
- Fetch channel messages (where bot has access)
|
||||
- Reply via DM
|
||||
|
||||
### Platform Behavior
|
||||
|
||||
- Windows/macOS/Android: continuous polling
|
||||
- iOS: no polling (on-demand only)
|
||||
|
||||
### Deliverables
|
||||
|
||||
- Telegram Bot Gateway (Rust)
|
||||
- Update polling / fetching
|
||||
- Message dedupe + cursoring
|
||||
- Agent replies sent via bot DM
|
||||
|
||||
### Documentation Commands
|
||||
|
||||
```bash
|
||||
/docs/telegram/bot.md
|
||||
/docs/telegram/flows.md
|
||||
/docs/telegram/limits.md
|
||||
```
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- User asks a question in Telegram
|
||||
- App processes it
|
||||
- Bot replies correctly
|
||||
- No duplicate replies
|
||||
|
||||
---
|
||||
|
||||
## Phase 5 — Platform Background Execution
|
||||
|
||||
### Goals
|
||||
|
||||
Enable **platform-appropriate background behavior**.
|
||||
|
||||
### Platform Breakdown
|
||||
|
||||
#### Windows
|
||||
|
||||
- Tray app
|
||||
- Autostart
|
||||
- Background polling
|
||||
|
||||
#### macOS
|
||||
|
||||
- Menu bar app
|
||||
- Launch at login
|
||||
|
||||
#### Android
|
||||
|
||||
- Foreground service (persistent notification)
|
||||
- Background polling allowed
|
||||
|
||||
#### iOS
|
||||
|
||||
- ❌ No continuous background
|
||||
- Only foreground execution
|
||||
|
||||
### Documentation Commands
|
||||
|
||||
```bash
|
||||
/docs/platforms/windows.md
|
||||
/docs/platforms/macos.md
|
||||
/docs/platforms/android.md
|
||||
/docs/platforms/ios.md
|
||||
```
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- Desktop apps run without UI open
|
||||
- Android foreground service stable
|
||||
- iOS behaves strictly foreground-only
|
||||
|
||||
---
|
||||
|
||||
## Phase 6 — Minimal Backend Integration
|
||||
|
||||
### Goals
|
||||
|
||||
Introduce backend **without violating privacy goals**.
|
||||
|
||||
### Backend Responsibilities
|
||||
|
||||
- Authentication
|
||||
- Device registration
|
||||
- Entitlements
|
||||
- Push notifications
|
||||
- Payment verification
|
||||
|
||||
### Explicit Non-Responsibilities
|
||||
|
||||
- No Telegram message storage
|
||||
- No agent logic
|
||||
- No summaries
|
||||
|
||||
### Deliverables
|
||||
|
||||
- Auth flow wired into UI
|
||||
- Entitlements fetched on startup
|
||||
- Device registered for push
|
||||
|
||||
### Documentation Commands
|
||||
|
||||
```bash
|
||||
/docs/backend/api.md
|
||||
/docs/backend/data-model.md
|
||||
/docs/backend/security.md
|
||||
```
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- User login works
|
||||
- Entitlements enforced locally
|
||||
- Backend DB contains no message content
|
||||
|
||||
---
|
||||
|
||||
## Phase 7 — iOS Push → Tap → Sync Flow
|
||||
|
||||
### Goals
|
||||
|
||||
Implement the **iOS-specific agent interaction model**.
|
||||
|
||||
### Flow
|
||||
|
||||
1. User sends question to bot
|
||||
2. Backend triggers visible push
|
||||
3. User taps notification
|
||||
4. App opens
|
||||
5. App syncs Telegram
|
||||
6. Agent processes
|
||||
7. Bot replies
|
||||
|
||||
### Deliverables
|
||||
|
||||
- APNs integration
|
||||
- Push payload handling
|
||||
- Sync-on-open logic
|
||||
|
||||
### Documentation Commands
|
||||
|
||||
```bash
|
||||
/docs/ios/push-flow.md
|
||||
/docs/ios/limitations.md
|
||||
```
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- Push reliably opens app
|
||||
- Sync runs automatically
|
||||
- Bot replies successfully
|
||||
|
||||
---
|
||||
|
||||
## Phase 8 — Payments & Plan Gating
|
||||
|
||||
### Goals
|
||||
|
||||
Monetize safely and correctly.
|
||||
|
||||
### Platforms
|
||||
|
||||
- Desktop: Stripe / Paddle
|
||||
- Android: Play Billing
|
||||
- iOS: StoreKit
|
||||
|
||||
### Deliverables
|
||||
|
||||
- Purchase flow per platform
|
||||
- Receipt verification
|
||||
- Feature gating in Rust
|
||||
|
||||
### Documentation Commands
|
||||
|
||||
```bash
|
||||
/docs/billing/plans.md
|
||||
/docs/billing/verification.md
|
||||
/docs/billing/entitlements.md
|
||||
```
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- Paid features unlock correctly
|
||||
- Downgrades enforced
|
||||
- Offline grace period handled
|
||||
|
||||
---
|
||||
|
||||
## Phase 9 — Hardening & Release Prep
|
||||
|
||||
### Goals
|
||||
|
||||
Stability, observability, and trust.
|
||||
|
||||
### Deliverables
|
||||
|
||||
- Error handling
|
||||
- Rate limiting
|
||||
- Abuse prevention
|
||||
- Crash-safe storage
|
||||
- UX polish
|
||||
|
||||
### Documentation Commands
|
||||
|
||||
```bash
|
||||
/docs/release/checklist.md
|
||||
/docs/known-issues.md
|
||||
/docs/security/threat-model.md
|
||||
```
|
||||
|
||||
### Exit Criteria
|
||||
|
||||
- No critical crashes
|
||||
- No duplicate Telegram replies
|
||||
- Clear user-facing error states
|
||||
|
||||
---
|
||||
|
||||
## Final Notes
|
||||
|
||||
- The **Telegram bot is the product interface**
|
||||
- The **Tauri app is the execution engine**
|
||||
- The **backend is infrastructure, not intelligence**
|
||||
- iOS behavior is intentionally constrained for correctness
|
||||
|
||||
---
|
||||
|
||||
END OF MVP
|
||||
@@ -1,350 +0,0 @@
|
||||
# Daemon Lifecycle Management System
|
||||
|
||||
**Status**: ✅ Implemented
|
||||
**Version**: 1.0.0
|
||||
**Date**: February 2026
|
||||
|
||||
## Overview
|
||||
|
||||
The Daemon Lifecycle Management System provides comprehensive frontend integration with the alphahuman Rust daemon, enabling real-time health monitoring, automatic lifecycle management, and user-friendly daemon controls throughout the application.
|
||||
|
||||
## Background
|
||||
|
||||
### Problem Statement
|
||||
|
||||
The alphahuman application runs a sophisticated Rust daemon that manages critical backend services (gateway, channels, heartbeat, scheduler) and emits detailed health information every 5 seconds. However, the frontend previously had:
|
||||
|
||||
- **Poor Visibility**: Daemon controls buried in developer console only
|
||||
- **No Real-Time Monitoring**: Manual status checks with raw JSON output
|
||||
- **No Automatic Management**: No startup detection or error recovery
|
||||
- **Disconnected UX**: Health events from Rust were ignored by frontend
|
||||
- **Manual Recovery**: Users had to manually restart failed services
|
||||
|
||||
### Solution Overview
|
||||
|
||||
This implementation creates a complete bridge between the Rust daemon's health system and the React frontend, providing:
|
||||
|
||||
- Real-time health monitoring with visual indicators
|
||||
- Automatic daemon startup and error recovery
|
||||
- User-friendly health displays in main UI
|
||||
- Enhanced developer tools with live status
|
||||
- Coordinated daemon and socket connection management
|
||||
|
||||
## Architecture
|
||||
|
||||
### System Components
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ React Frontend │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ UI Layer │
|
||||
│ ├── DaemonHealthIndicator (Main UI) │
|
||||
│ ├── DaemonHealthPanel (Detailed View) │
|
||||
│ └── Enhanced TauriCommandsPanel (Dev Tools) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ State Management │
|
||||
│ ├── daemonSlice.ts (Redux) │
|
||||
│ ├── useDaemonHealth.ts (React Hook) │
|
||||
│ └── useDaemonLifecycle.ts (Lifecycle Hook) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Services │
|
||||
│ └── daemonHealthService.ts (Event Processing) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Integration Layer │
|
||||
│ ├── tauriSocket.ts (Event Listening) │
|
||||
│ └── SocketProvider.tsx (Coordinated State) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
Tauri Event Bridge
|
||||
│
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Rust Daemon │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Health System (src-tauri/src/alphahuman/health/mod.rs) │
|
||||
│ ├── Component Health Tracking │
|
||||
│ ├── Health Snapshot Generation │
|
||||
│ └── Event Emission (every 5s) │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Daemon Supervisor (src-tauri/src/alphahuman/daemon/mod.rs) │
|
||||
│ ├── Component Management │
|
||||
│ ├── Automatic Restarts │
|
||||
│ └── Exponential Backoff │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Components │
|
||||
│ ├── Gateway (API Server) │
|
||||
│ ├── Channels (Communication) │
|
||||
│ ├── Heartbeat (Health Monitor) │
|
||||
│ └── Scheduler (Cron Jobs) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Event Flow
|
||||
|
||||
1. **Health Generation**: Rust daemon generates health snapshots every 5 seconds
|
||||
2. **Event Emission**: Health data emitted via `alphahuman:health` Tauri event
|
||||
3. **Frontend Processing**: `daemonHealthService` receives and parses events
|
||||
4. **State Updates**: Redux state updated with component health information
|
||||
5. **UI Reactivity**: React components automatically re-render with new status
|
||||
6. **User Actions**: Manual controls trigger Tauri commands back to Rust
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Redux State Management
|
||||
|
||||
**File**: `src/store/daemonSlice.ts`
|
||||
|
||||
```typescript
|
||||
interface DaemonUserState {
|
||||
status: 'starting' | 'running' | 'error' | 'disconnected';
|
||||
healthSnapshot: HealthSnapshot | null;
|
||||
components: {
|
||||
gateway?: ComponentHealth;
|
||||
channels?: ComponentHealth;
|
||||
heartbeat?: ComponentHealth;
|
||||
scheduler?: ComponentHealth;
|
||||
};
|
||||
lastHealthUpdate: string | null;
|
||||
connectionAttempts: number;
|
||||
autoStartEnabled: boolean;
|
||||
isRecovering: boolean;
|
||||
healthTimeoutId: string | null;
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features**:
|
||||
|
||||
- Per-user daemon state isolation (following existing patterns)
|
||||
- Component-level health tracking with error details
|
||||
- Connection attempt management with exponential backoff
|
||||
- Auto-start preference persistence
|
||||
- Timeout handling for health event detection
|
||||
|
||||
### Health Monitoring Service
|
||||
|
||||
**File**: `src/services/daemonHealthService.ts`
|
||||
|
||||
```typescript
|
||||
export class DaemonHealthService {
|
||||
private healthTimeoutId: NodeJS.Timeout | null = null;
|
||||
private readonly HEALTH_TIMEOUT_MS = 30000; // 30 seconds
|
||||
|
||||
async setupHealthListener(): Promise<UnlistenFn | null>;
|
||||
private parseHealthSnapshot(payload: unknown): HealthSnapshot | null;
|
||||
private updateReduxFromHealth(snapshot: HealthSnapshot): void;
|
||||
startHealthTimeout(): void;
|
||||
}
|
||||
```
|
||||
|
||||
**Responsibilities**:
|
||||
|
||||
- Listen for `alphahuman:health` Tauri events from Rust
|
||||
- Parse and validate health snapshot data
|
||||
- Update Redux state with component health information
|
||||
- Manage 30-second timeout detection for disconnected daemon
|
||||
- Handle cleanup and error recovery
|
||||
|
||||
### UI Components
|
||||
|
||||
#### DaemonHealthIndicator
|
||||
|
||||
**File**: `src/components/daemon/DaemonHealthIndicator.tsx`
|
||||
|
||||
**Purpose**: Compact status indicator for main application UI
|
||||
|
||||
**Visual States**:
|
||||
|
||||
- 🟢 **Green**: All components running healthy (`status: 'running'`)
|
||||
- 🟡 **Yellow**: Daemon starting or recovering (`status: 'starting'`)
|
||||
- 🔴 **Red**: One or more components in error state (`status: 'error'`)
|
||||
- ⚪ **Gray**: Daemon disconnected or not running (`status: 'disconnected'`)
|
||||
|
||||
**Features**:
|
||||
|
||||
- Click to open detailed health panel
|
||||
- Tooltip showing component health summary
|
||||
- Responsive sizing (sm/md/lg variants)
|
||||
- Only visible in Tauri environments
|
||||
|
||||
#### DaemonHealthPanel
|
||||
|
||||
**File**: `src/components/daemon/DaemonHealthPanel.tsx`
|
||||
|
||||
**Purpose**: Detailed health breakdown and manual controls
|
||||
|
||||
**Features**:
|
||||
|
||||
- Component health table with status, last update, restart counts
|
||||
- Manual restart buttons for individual components
|
||||
- Auto-start toggle with persistence
|
||||
- Connection retry controls
|
||||
- Real-time health information display
|
||||
- User-friendly error messages and troubleshooting hints
|
||||
|
||||
### Lifecycle Management
|
||||
|
||||
**File**: `src/hooks/useDaemonLifecycle.ts`
|
||||
|
||||
**Automatic Behaviors**:
|
||||
|
||||
1. **App Startup**:
|
||||
- Detect existing daemon status
|
||||
- Auto-start daemon if enabled and not running
|
||||
- Setup health monitoring listeners
|
||||
|
||||
2. **Error Recovery**:
|
||||
- Exponential backoff retry attempts (1s → 2s → 4s → 8s → 30s max)
|
||||
- Maximum 5 retry attempts before requiring manual intervention
|
||||
- Clear error states on successful recovery
|
||||
|
||||
3. **Background Handling**:
|
||||
- Maintain health monitoring when app backgrounded
|
||||
- Reconnection logic without page refresh
|
||||
- Coordinate with socket connection management
|
||||
|
||||
4. **Cleanup**:
|
||||
- Proper event listener cleanup on unmount
|
||||
- Timeout cancellation to prevent memory leaks
|
||||
- Graceful shutdown coordination
|
||||
|
||||
### Integration Points
|
||||
|
||||
#### Enhanced Service Management
|
||||
|
||||
**File**: `src/components/settings/panels/TauriCommandsPanel.tsx`
|
||||
|
||||
**Improvements to Existing Developer Console**:
|
||||
|
||||
- **Live Status Display**: Real-time daemon status with PID and uptime
|
||||
- **Component Health Grid**: Visual status for all daemon components
|
||||
- **Enhanced Controls**: Smart start/stop buttons with proper loading states
|
||||
- **Auto-Start Toggle**: User preference for automatic daemon startup
|
||||
- **Connection Tracking**: Display retry attempts and recovery status
|
||||
- **Better Error Messages**: User-friendly errors with troubleshooting hints
|
||||
|
||||
**Before vs After**:
|
||||
|
||||
```typescript
|
||||
// Before: Manual status check
|
||||
<PrimaryButton onClick={() => run(alphahumanServiceStatus, 'serviceStatus')}>
|
||||
Status
|
||||
</PrimaryButton>
|
||||
|
||||
// After: Live status display
|
||||
<div className="flex items-center gap-3">
|
||||
<DaemonHealthIndicator size="md" />
|
||||
<div>
|
||||
<div className="text-white font-medium">Daemon Status: {status}</div>
|
||||
<div className="text-xs text-gray-400">PID: {pid} | Uptime: {uptime}</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### Socket Provider Integration
|
||||
|
||||
**File**: `src/providers/SocketProvider.tsx`
|
||||
|
||||
**Coordinated State Management**:
|
||||
|
||||
- Check daemon health before attempting socket connections
|
||||
- Display daemon-related errors in socket connection status
|
||||
- Coordinate daemon startup with socket connection flows
|
||||
- Provide daemon health context to socket consumers
|
||||
|
||||
#### Main UI Integration
|
||||
|
||||
**File**: `src/components/MiniSidebar.tsx`
|
||||
|
||||
**User-Facing Integration**:
|
||||
|
||||
- Daemon health indicator in main navigation (Tauri-only)
|
||||
- Click to open detailed health modal
|
||||
- Non-intrusive but easily accessible
|
||||
- Consistent with existing UI patterns
|
||||
|
||||
## User Experience
|
||||
|
||||
### For End Users
|
||||
|
||||
1. **Visible Status**: Daemon health indicator in main UI shows system status at a glance
|
||||
2. **Automatic Operation**: Daemon starts automatically and recovers from errors without user intervention
|
||||
3. **Clear Feedback**: User-friendly messages explain daemon state and provide actionable guidance
|
||||
4. **Quick Access**: Click health indicator to see detailed component status and manual controls
|
||||
|
||||
### For Developers
|
||||
|
||||
1. **Enhanced Console**: Improved service management in settings with live status updates
|
||||
2. **Component Monitoring**: Real-time visibility into all daemon components (gateway, channels, etc.)
|
||||
3. **Debug Information**: Detailed health snapshots, retry attempts, and error history
|
||||
4. **Manual Override**: Full control over daemon lifecycle with proper state management
|
||||
|
||||
### Error Handling
|
||||
|
||||
1. **Graceful Degradation**: System works properly in non-Tauri environments
|
||||
2. **Timeout Management**: 30-second timeout detection with automatic recovery attempts
|
||||
3. **User Guidance**: Clear error messages with troubleshooting suggestions
|
||||
4. **Recovery Actions**: Manual restart options when automatic recovery fails
|
||||
|
||||
## Technical Benefits
|
||||
|
||||
### Performance
|
||||
|
||||
- **Efficient Updates**: Debounced Redux updates prevent excessive re-renders
|
||||
- **Memory Management**: Proper cleanup of timeouts and event listeners
|
||||
- **Background Optimization**: Minimal resource usage when app backgrounded
|
||||
|
||||
### Reliability
|
||||
|
||||
- **Timeout Handling**: Robust detection of daemon disconnection
|
||||
- **Exponential Backoff**: Smart retry logic prevents resource exhaustion
|
||||
- **State Consistency**: Coordinated daemon and socket connection states
|
||||
- **Error Recovery**: Automatic recovery from transient failures
|
||||
|
||||
### Maintainability
|
||||
|
||||
- **TypeScript**: Full type safety throughout the implementation
|
||||
- **Modular Design**: Clear separation between state, services, and UI components
|
||||
- **Existing Patterns**: Follows established Redux and component patterns
|
||||
- **Testing**: Comprehensive error handling and edge case management
|
||||
|
||||
## Configuration
|
||||
|
||||
### Auto-Start Behavior
|
||||
|
||||
Users can control daemon auto-start behavior through:
|
||||
|
||||
1. **UI Toggle**: Available in both health panel and settings console
|
||||
2. **Persistence**: Preference stored in Redux with persistence
|
||||
3. **Default**: Auto-start enabled by default for better UX
|
||||
|
||||
### Health Monitoring
|
||||
|
||||
- **Event Frequency**: Rust daemon emits health every 5 seconds
|
||||
- **Timeout Duration**: 30 seconds without health events = disconnected
|
||||
- **Retry Logic**: Maximum 5 attempts with exponential backoff
|
||||
- **Component Tracking**: Gateway, channels, heartbeat, scheduler components
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Potential Improvements
|
||||
|
||||
1. **Health History**: Track daemon health over time with charts/graphs
|
||||
2. **Performance Metrics**: CPU/memory usage from daemon components
|
||||
3. **Log Integration**: Show daemon logs directly in health panel
|
||||
4. **Mobile Optimization**: Enhanced mobile-specific daemon management
|
||||
5. **Notification System**: Push notifications for critical daemon events
|
||||
|
||||
### Extensibility
|
||||
|
||||
The architecture supports easy extension for:
|
||||
|
||||
- Additional daemon components
|
||||
- Custom health check logic
|
||||
- Third-party integrations
|
||||
- Advanced monitoring features
|
||||
|
||||
## Conclusion
|
||||
|
||||
The Daemon Lifecycle Management System provides a complete bridge between the sophisticated Rust daemon infrastructure and the React frontend, delivering excellent user experience while maintaining the technical robustness required for a production application.
|
||||
|
||||
The implementation follows established patterns, provides comprehensive error handling, and creates a foundation for future daemon-related features while ensuring the system remains reliable and user-friendly.
|
||||
@@ -1,627 +0,0 @@
|
||||
# Gmail Skill Integration — Design Reference
|
||||
|
||||
**Scope:** Native Gmail skill — OAuth2 connect flow, tunnel-based callback, token storage,
|
||||
initial inbox sync, and `send_email` tool dispatch.
|
||||
|
||||
**Status:** Design reference / implementation guide.
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview
|
||||
|
||||
This document describes the full lifecycle of connecting a Gmail skill to ZeroClaw.
|
||||
It covers four concerns in order:
|
||||
|
||||
1. **OAuth2 connect** — how the user authorises Gmail access
|
||||
2. **Tunnel callback** — how the authorization code reaches the backend through the tunnel
|
||||
3. **Token storage** — how credentials are encrypted and persisted in the auth profile store
|
||||
4. **Email sync + send** — how the initial inbox is fetched and how sending works
|
||||
|
||||
The skill is declared with the following manifest:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "gmail",
|
||||
"name": "Gmail",
|
||||
"version": "1.0.0",
|
||||
"description": "Gmail integration via Google API — comprehensive email management with OAuth2 authentication, send/receive, labels, search, and attachments.",
|
||||
"auto_start": false,
|
||||
"platforms": ["windows", "macos", "linux", "android", "ios"],
|
||||
"setup": {
|
||||
"required": true,
|
||||
"label": "Connect Gmail",
|
||||
"oauth": {
|
||||
"provider": "gmail",
|
||||
"scopes": [
|
||||
"https://www.googleapis.com/auth/gmail.send",
|
||||
"https://www.googleapis.com/auth/gmail.modify",
|
||||
"https://www.googleapis.com/auth/gmail.labels"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The equivalent `SKILL.toml` that ZeroClaw loads from
|
||||
`~/.zeroclaw/workspace/skills/gmail/SKILL.toml` is shown in §2.
|
||||
|
||||
---
|
||||
|
||||
## 2. Skill Manifest (`SKILL.toml`)
|
||||
|
||||
Skills in ZeroClaw are loaded by `src/skills/mod.rs`. The loader reads a `SKILL.toml`
|
||||
(or `SKILL.md`) file from `~/.zeroclaw/workspace/skills/<name>/`. The Rust struct it
|
||||
populates is `Skill` / `SkillManifest` / `SkillTool`.
|
||||
|
||||
`SKILL.toml` for Gmail:
|
||||
|
||||
```toml
|
||||
[skill]
|
||||
name = "gmail"
|
||||
version = "1.0.0"
|
||||
description = "Gmail integration via Google API — send/receive, labels, search, attachments."
|
||||
author = "zeroclaw"
|
||||
tags = ["email", "google", "productivity"]
|
||||
|
||||
# OAuth setup block — interpreted by the skill connect subsystem (see §3).
|
||||
[skill.setup]
|
||||
required = true
|
||||
label = "Connect Gmail"
|
||||
|
||||
[skill.setup.oauth]
|
||||
provider = "gmail"
|
||||
scopes = [
|
||||
"https://www.googleapis.com/auth/gmail.send",
|
||||
"https://www.googleapis.com/auth/gmail.modify",
|
||||
"https://www.googleapis.com/auth/gmail.labels",
|
||||
]
|
||||
|
||||
# Tools exposed to the agent after connection.
|
||||
|
||||
[[tools]]
|
||||
name = "gmail_send_email"
|
||||
description = "Send an email via Gmail. Requires a connected Gmail account."
|
||||
kind = "http"
|
||||
command = "https://gmail.googleapis.com/gmail/v1/users/me/messages/send"
|
||||
|
||||
[[tools]]
|
||||
name = "gmail_list_messages"
|
||||
description = "List messages in the Gmail inbox. Supports query filters."
|
||||
kind = "http"
|
||||
command = "https://gmail.googleapis.com/gmail/v1/users/me/messages"
|
||||
|
||||
[[tools]]
|
||||
name = "gmail_get_message"
|
||||
description = "Fetch the full content of a single Gmail message by ID."
|
||||
kind = "http"
|
||||
command = "https://gmail.googleapis.com/gmail/v1/users/me/messages/{id}"
|
||||
|
||||
[[tools]]
|
||||
name = "gmail_modify_labels"
|
||||
description = "Add or remove labels on a Gmail message."
|
||||
kind = "http"
|
||||
command = "https://gmail.googleapis.com/gmail/v1/users/me/messages/{id}/modify"
|
||||
```
|
||||
|
||||
At runtime, `load_skills_with_config()` (`src/skills/mod.rs:78`) reads these tools into
|
||||
`Vec<SkillTool>` and injects them into the agent system prompt. The `kind = "http"` tools
|
||||
are dispatched via `HttpRequestTool` (`src/tools/http_request.rs`) with the stored
|
||||
Bearer token injected as the `Authorization` header (see §5).
|
||||
|
||||
---
|
||||
|
||||
## 3. OAuth2 Connect Flow
|
||||
|
||||
### 3.1 What the user triggers
|
||||
|
||||
When the user asks to connect Gmail (via the frontend or a CLI command), the backend:
|
||||
|
||||
1. Generates a **PKCE pair** (code verifier + SHA-256 challenge) — following the same
|
||||
pattern as `src/auth/openai_oauth.rs:generate_pkce_state()`.
|
||||
2. Generates a random CSRF `state` token (24 bytes, base64url).
|
||||
3. Constructs the Google OAuth2 authorisation URL:
|
||||
|
||||
```
|
||||
https://accounts.google.com/o/oauth2/v2/auth
|
||||
?response_type=code
|
||||
&client_id=<GOOGLE_CLIENT_ID>
|
||||
&redirect_uri=<TUNNEL_PUBLIC_URL>/auth/callback/gmail
|
||||
&scope=https://www.googleapis.com/auth/gmail.send
|
||||
https://www.googleapis.com/auth/gmail.modify
|
||||
https://www.googleapis.com/auth/gmail.labels
|
||||
&code_challenge=<SHA256_CHALLENGE>
|
||||
&code_challenge_method=S256
|
||||
&state=<RANDOM_STATE>
|
||||
&access_type=offline
|
||||
&prompt=consent
|
||||
```
|
||||
|
||||
`access_type=offline` is required to receive a `refresh_token` from Google.
|
||||
`prompt=consent` ensures the refresh token is issued on every connect, not just the first.
|
||||
|
||||
4. The URL is sent to the frontend. The frontend opens it in a browser or WebView.
|
||||
|
||||
### 3.2 Where the redirect URI points
|
||||
|
||||
The `redirect_uri` is the **tunnel public URL** with the path `/auth/callback/gmail`.
|
||||
|
||||
ZeroClaw's tunnel system (`src/tunnel/mod.rs`) exposes the local gateway port via one of:
|
||||
|
||||
| Provider | How public URL is obtained |
|
||||
| ------------ | ---------------------------------------------------------------- |
|
||||
| `cloudflare` | `cloudflared tunnel` process stdout — `src/tunnel/cloudflare.rs` |
|
||||
| `ngrok` | ngrok local API `GET /api/tunnels` — `src/tunnel/ngrok.rs` |
|
||||
| `tailscale` | `tailscale funnel` + hostname — `src/tunnel/tailscale.rs` |
|
||||
| `custom` | user-supplied URL or stdout pattern — `src/tunnel/custom.rs` |
|
||||
|
||||
The tunnel's `start(local_host, local_port)` method returns the public URL string.
|
||||
This URL is what gets used as the `redirect_uri`.
|
||||
|
||||
Example with Cloudflare Tunnel:
|
||||
|
||||
```
|
||||
https://your-subdomain.trycloudflare.com/auth/callback/gmail
|
||||
```
|
||||
|
||||
Google's OAuth2 server sends the browser to this URL after the user grants consent.
|
||||
|
||||
---
|
||||
|
||||
## 4. Receiving the Authorization Code via the Tunnel
|
||||
|
||||
### 4.1 The callback endpoint
|
||||
|
||||
The gateway (`src/gateway/mod.rs`) runs as an Axum HTTP server. A new route needs to be
|
||||
registered to receive the OAuth2 callback:
|
||||
|
||||
```
|
||||
GET /auth/callback/gmail?code=<AUTH_CODE>&state=<STATE>
|
||||
```
|
||||
|
||||
This route handler must:
|
||||
|
||||
1. **Validate the `state` parameter** against the CSRF token stored in memory when the
|
||||
flow was initiated. Reject mismatches with `400 Bad Request`.
|
||||
2. **Extract the `code`** query parameter.
|
||||
3. **Exchange the code** for tokens by calling Google's token endpoint:
|
||||
|
||||
```
|
||||
POST https://oauth2.googleapis.com/token
|
||||
grant_type=authorization_code
|
||||
code=<AUTH_CODE>
|
||||
client_id=<GOOGLE_CLIENT_ID>
|
||||
client_secret=<GOOGLE_CLIENT_SECRET>
|
||||
redirect_uri=<TUNNEL_PUBLIC_URL>/auth/callback/gmail
|
||||
code_verifier=<PKCE_VERIFIER>
|
||||
```
|
||||
|
||||
The exchange pattern mirrors `src/auth/openai_oauth.rs:exchange_code_for_tokens()`,
|
||||
which posts a form body and parses the JSON response into `TokenSet`.
|
||||
|
||||
Google returns:
|
||||
|
||||
```json
|
||||
{
|
||||
"access_token": "ya29.a0...",
|
||||
"expires_in": 3599,
|
||||
"refresh_token": "1//0g...",
|
||||
"scope": "https://www.googleapis.com/auth/gmail.send ...",
|
||||
"token_type": "Bearer"
|
||||
}
|
||||
```
|
||||
|
||||
4. The handler stores the `TokenSet` (see §4.2) and returns a success response to the
|
||||
browser (an HTML page or redirect to the frontend app confirming the connection).
|
||||
|
||||
### 4.2 The in-flight CSRF / PKCE state
|
||||
|
||||
Before redirecting the user to Google, the pending PKCE state and CSRF token must be
|
||||
stored so the callback handler can look them up. The right storage is the skill's dedicated
|
||||
in-memory or short-lived file-backed state, keyed by the `state` value:
|
||||
|
||||
```
|
||||
pending_oauth_states: HashMap<state_token, (PkceState, initiated_at)>
|
||||
```
|
||||
|
||||
Entries expire after a fixed window (e.g. 10 minutes) to prevent orphaned state
|
||||
accumulation. This follows the same pattern ZeroClaw already uses for pairing nonces
|
||||
in `src/security/pairing.rs`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Token Storage
|
||||
|
||||
After a successful token exchange, the tokens are persisted using the `AuthService`
|
||||
and `AuthProfilesStore` from `src/auth/`.
|
||||
|
||||
### 5.1 Storing the token set
|
||||
|
||||
```rust
|
||||
// Pseudocode — mirrors src/auth/mod.rs AuthService::store_openai_tokens()
|
||||
let token_set = TokenSet {
|
||||
access_token: response.access_token,
|
||||
refresh_token: response.refresh_token,
|
||||
id_token: None,
|
||||
expires_at: Some(Utc::now() + Duration::seconds(response.expires_in)),
|
||||
token_type: Some("Bearer".into()),
|
||||
scope: Some(scopes.join(" ")),
|
||||
};
|
||||
|
||||
auth_service
|
||||
.store_oauth_tokens("gmail", "default", token_set, None, true)
|
||||
.await?;
|
||||
```
|
||||
|
||||
`store_oauth_tokens` calls `AuthProfilesStore::upsert_profile()`, which:
|
||||
|
||||
1. Creates or updates an `AuthProfile` with `kind = AuthProfileKind::OAuth`.
|
||||
2. Serialises the profile data to `~/.zeroclaw/auth-profiles.json` (file-locked via
|
||||
`auth-profiles.lock` with a 10-second timeout — `src/auth/profiles.rs:16–17`).
|
||||
|
||||
### 5.2 How tokens are encrypted at rest
|
||||
|
||||
Before writing to disk, every secret field passes through `SecretStore::encrypt()`
|
||||
(`src/security/secrets.rs`). The encryption scheme is:
|
||||
|
||||
- **Algorithm**: ChaCha20-Poly1305 AEAD (256-bit key)
|
||||
- **Key file**: `~/.zeroclaw/.secret_key` (permissions 0600, created on first use)
|
||||
- **Format on disk**: `enc2:<hex(12-byte-nonce ‖ ciphertext ‖ 16-byte-Poly1305-tag)>`
|
||||
- **Config**: encryption is enabled by default; disable with `secrets.encrypt = false`
|
||||
|
||||
The stored profile entry looks like:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "gmail:default",
|
||||
"provider": "gmail",
|
||||
"profile_name": "default",
|
||||
"kind": "oauth",
|
||||
"token_set": {
|
||||
"access_token": "enc2:a1b2c3...",
|
||||
"refresh_token": "enc2:d4e5f6...",
|
||||
"expires_at": "2026-03-09T12:00:00Z",
|
||||
"token_type": "Bearer",
|
||||
"scope": "https://www.googleapis.com/auth/gmail.send ..."
|
||||
},
|
||||
"created_at": "2026-03-09T11:00:00Z",
|
||||
"updated_at": "2026-03-09T11:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Token refresh
|
||||
|
||||
Google access tokens expire after 3600 seconds. Before any Gmail API call, the skill
|
||||
must check `token_set.is_expiring_within(Duration::from_secs(90))` (the same 90-second
|
||||
skew used for OpenAI tokens in `src/auth/mod.rs:160`). If expiring:
|
||||
|
||||
```
|
||||
POST https://oauth2.googleapis.com/token
|
||||
grant_type=refresh_token
|
||||
refresh_token=<stored_refresh_token>
|
||||
client_id=<GOOGLE_CLIENT_ID>
|
||||
client_secret=<GOOGLE_CLIENT_SECRET>
|
||||
```
|
||||
|
||||
The new `access_token` (and new `refresh_token` if Google rotates it) is written back
|
||||
to the auth profile via `AuthProfilesStore::update_profile()`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Initial Email Sync (First 100 Emails)
|
||||
|
||||
Once connected, the skill performs a one-time initial sync. This is triggered
|
||||
immediately after a successful token exchange and runs as a background task.
|
||||
|
||||
### 6.1 Fetch message IDs
|
||||
|
||||
```
|
||||
GET https://gmail.googleapis.com/gmail/v1/users/me/messages
|
||||
?maxResults=100
|
||||
&labelIds=INBOX
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
|
||||
Returns up to 100 message descriptors: `[{ "id": "...", "threadId": "..." }, ...]`.
|
||||
|
||||
### 6.2 Fetch full message details
|
||||
|
||||
For each message ID, fetch the full message:
|
||||
|
||||
```
|
||||
GET https://gmail.googleapis.com/gmail/v1/users/me/messages/{id}
|
||||
?format=full
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
|
||||
The response contains headers (From, To, Subject, Date), a snippet, and the body parts.
|
||||
|
||||
In practice, to avoid 100 sequential requests, use the Gmail batch API:
|
||||
|
||||
```
|
||||
POST https://www.googleapis.com/batch/gmail/v1
|
||||
Content-Type: multipart/mixed; boundary="batch_boundary"
|
||||
|
||||
--batch_boundary
|
||||
Content-Type: application/http
|
||||
GET /gmail/v1/users/me/messages/{id1}?format=full
|
||||
...
|
||||
```
|
||||
|
||||
### 6.3 Storing emails in ZeroClaw memory
|
||||
|
||||
Each email is stored via `memory_store` (`src/tools/memory_store.rs`) with:
|
||||
|
||||
| Field | Value |
|
||||
| ---------- | ----------------------------------------------------------------- |
|
||||
| `key` | `gmail_msg_<message_id>` |
|
||||
| `content` | Formatted string: `From: ... Subject: ... Date: ... Snippet: ...` |
|
||||
| `category` | `MemoryCategory::Custom("gmail")` |
|
||||
|
||||
This makes every synced email searchable via `memory_recall` with queries like
|
||||
`"gmail from:zeroclaw@example.com"`.
|
||||
|
||||
For larger body content that exceeds a single memory entry, store the full body as a
|
||||
separate entry keyed `gmail_body_<message_id>` and the summary/metadata as
|
||||
`gmail_msg_<message_id>`.
|
||||
|
||||
Example stored entries after sync:
|
||||
|
||||
```
|
||||
key: gmail_msg_18de7f2a1b3c4e5d
|
||||
content: From: sender@example.com
|
||||
To: me@gmail.com
|
||||
Subject: Project update
|
||||
Date: 2026-03-08T14:32:00Z
|
||||
Snippet: "Here is the latest update on the project..."
|
||||
Labels: INBOX, UNREAD
|
||||
category: gmail
|
||||
|
||||
key: gmail_body_18de7f2a1b3c4e5d
|
||||
content: (full decoded email body text)
|
||||
category: gmail
|
||||
```
|
||||
|
||||
### 6.4 Sync state tracking
|
||||
|
||||
Store the highest-known history ID after sync so incremental sync can pick up from
|
||||
where it left off:
|
||||
|
||||
```
|
||||
key: gmail_sync_history_id
|
||||
content: 12345678
|
||||
category: core
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. The `gmail_send_email` Tool
|
||||
|
||||
### 7.1 Tool definition in `SKILL.toml`
|
||||
|
||||
The `[[tools]]` entry from §2:
|
||||
|
||||
```toml
|
||||
[[tools]]
|
||||
name = "gmail_send_email"
|
||||
description = "Send an email via Gmail. Requires a connected Gmail account."
|
||||
kind = "http"
|
||||
command = "https://gmail.googleapis.com/gmail/v1/users/me/messages/send"
|
||||
```
|
||||
|
||||
### 7.2 How the agent calls it
|
||||
|
||||
The LLM emits a tool call:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "gmail_send_email",
|
||||
"arguments": {
|
||||
"to": "recipient@example.com",
|
||||
"subject": "Hello from ZeroClaw",
|
||||
"body": "This is a test email sent by the agent."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.3 Tool dispatch path
|
||||
|
||||
```
|
||||
Agent loop (src/agent/loop_.rs)
|
||||
└── dispatches to registered tool by name
|
||||
└── HttpRequestTool::execute(args) [src/tools/http_request.rs]
|
||||
├── Retrieve gmail access_token from AuthService
|
||||
│ └── auth_service.get_provider_bearer_token("gmail", None)
|
||||
│ └── decrypts enc2: value via SecretStore::decrypt()
|
||||
├── Build RFC 2822 message and base64url-encode it
|
||||
├── POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
|
||||
│ Headers:
|
||||
│ Authorization: Bearer <decrypted_access_token>
|
||||
│ Content-Type: application/json
|
||||
│ Body:
|
||||
│ { "raw": "<base64url_encoded_rfc2822_message>" }
|
||||
└── Return ToolResult { success, output: "Message sent. ID: <id>" }
|
||||
```
|
||||
|
||||
### 7.4 Building the RFC 2822 message
|
||||
|
||||
Gmail's send endpoint requires the email encoded as an RFC 2822 message, then base64url
|
||||
encoded. The structure:
|
||||
|
||||
```
|
||||
From: me@gmail.com
|
||||
To: recipient@example.com
|
||||
Subject: Hello from ZeroClaw
|
||||
Content-Type: text/plain; charset="UTF-8"
|
||||
MIME-Version: 1.0
|
||||
|
||||
This is a test email sent by the agent.
|
||||
```
|
||||
|
||||
Then the entire string is base64url-encoded (no padding) and placed in the `raw` field:
|
||||
|
||||
```json
|
||||
{ "raw": "RnJvbTogbWVAZ21haWwuY29tCi..." }
|
||||
```
|
||||
|
||||
### 7.5 Security enforcement
|
||||
|
||||
Before executing any outbound HTTP call, the `SecurityPolicy` is consulted:
|
||||
|
||||
```rust
|
||||
// src/security/policy.rs
|
||||
security.enforce_tool_operation(ToolOperation::Act, "gmail_send_email")
|
||||
```
|
||||
|
||||
`ToolOperation::Act` is the category for write/side-effect operations. If the agent is
|
||||
running in `ReadOnly` or `Supervised` mode, this call fails with an explicit error before
|
||||
any network request is made.
|
||||
|
||||
The `HttpRequestTool` additionally enforces the `http.allowed_domains` allowlist. For
|
||||
Gmail tools, `googleapis.com` must be present in that list:
|
||||
|
||||
```toml
|
||||
# config.toml
|
||||
[http]
|
||||
enabled = true
|
||||
allowed_domains = ["googleapis.com"]
|
||||
```
|
||||
|
||||
### 7.6 Reply-to and threading
|
||||
|
||||
To send a reply within an existing thread, add the Gmail `threadId` to the request body:
|
||||
|
||||
```json
|
||||
{ "raw": "<base64url_message_with_In-Reply-To_header>", "threadId": "18de7f2a1b3c4e5d" }
|
||||
```
|
||||
|
||||
The RFC 2822 message must include `In-Reply-To: <original_message_id>` and
|
||||
`References: <original_message_id>` headers for proper threading.
|
||||
|
||||
---
|
||||
|
||||
## 8. Full End-to-End Sequence
|
||||
|
||||
```
|
||||
Frontend / User
|
||||
│
|
||||
│ 1. "Connect Gmail"
|
||||
▼
|
||||
ZeroClaw Backend
|
||||
│ 2. Generate PKCE + CSRF state
|
||||
│ 3. Build Google OAuth2 authorize URL with tunnel redirect_uri
|
||||
│ 4. Return URL to frontend
|
||||
│
|
||||
▼
|
||||
Browser (user)
|
||||
│ 5. User visits URL, grants consent in Google
|
||||
│
|
||||
▼
|
||||
Google OAuth2
|
||||
│ 6. Browser redirected to <TUNNEL_PUBLIC_URL>/auth/callback/gmail?code=XXX&state=YYY
|
||||
│
|
||||
▼
|
||||
Tunnel (cloudflare / ngrok / tailscale)
|
||||
│ 7. Proxies HTTPS request to local gateway port
|
||||
│
|
||||
▼
|
||||
Gateway (src/gateway/mod.rs) — GET /auth/callback/gmail
|
||||
│ 8. Validate CSRF state
|
||||
│ 9. Exchange code → TokenSet via POST https://oauth2.googleapis.com/token
|
||||
│ 10. Store TokenSet in AuthProfilesStore (encrypted, "gmail:default")
|
||||
│ 11. Return success page/redirect to frontend
|
||||
│
|
||||
▼
|
||||
Background task — initial sync
|
||||
│ 12. GET /gmail/v1/users/me/messages?maxResults=100&labelIds=INBOX
|
||||
│ 13. Batch-fetch 100 full messages
|
||||
│ 14. memory_store each email under key=gmail_msg_<id>, category=gmail
|
||||
│ 15. memory_store gmail_sync_history_id = <latest_historyId>
|
||||
│
|
||||
▼
|
||||
Agent (subsequent interactions)
|
||||
│ 16. LLM generates tool call: gmail_send_email { to, subject, body }
|
||||
│ 17. Agent loop dispatches to HttpRequestTool
|
||||
│ 18. HttpRequestTool retrieves + decrypts access_token from AuthService
|
||||
│ 19. Refreshes token if expiring within 90 seconds
|
||||
│ 20. POST https://gmail.googleapis.com/gmail/v1/users/me/messages/send
|
||||
│ 21. Returns ToolResult to agent
|
||||
▼
|
||||
Done
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Configuration Reference
|
||||
|
||||
All Gmail skill config lives in `config.toml`:
|
||||
|
||||
```toml
|
||||
# Enable HTTP request tool with Google API access
|
||||
[http]
|
||||
enabled = true
|
||||
allowed_domains = ["googleapis.com", "oauth2.googleapis.com"]
|
||||
timeout_secs = 30
|
||||
max_response_size = 524288 # 512KB — enough for email batch responses
|
||||
|
||||
# Tunnel for OAuth callback (pick one)
|
||||
[tunnel]
|
||||
provider = "cloudflare"
|
||||
[tunnel.cloudflare]
|
||||
token = "your-cloudflare-tunnel-token"
|
||||
|
||||
# Or ngrok:
|
||||
# [tunnel]
|
||||
# provider = "ngrok"
|
||||
# [tunnel.ngrok]
|
||||
# auth_token = "your-ngrok-token"
|
||||
|
||||
# Google OAuth2 app credentials (stored encrypted)
|
||||
# Set via environment or onboard wizard — never commit raw values
|
||||
[integrations.gmail]
|
||||
client_id = "enc2:..."
|
||||
client_secret = "enc2:..."
|
||||
```
|
||||
|
||||
The Google credentials (`client_id`, `client_secret`) are encrypted by `SecretStore`
|
||||
before being written to `config.toml` — the same ChaCha20-Poly1305 scheme used for all
|
||||
secrets (`src/security/secrets.rs`).
|
||||
|
||||
---
|
||||
|
||||
## 10. Key Source Files
|
||||
|
||||
| File | Role |
|
||||
| ---------------------------- | ----------------------------------------------------------------- |
|
||||
| `src/skills/mod.rs` | Skill loading, `SkillTool` struct, `load_skills_with_config()` |
|
||||
| `src/auth/mod.rs` | `AuthService` — store/retrieve/refresh OAuth tokens |
|
||||
| `src/auth/profiles.rs` | `AuthProfile`, `TokenSet`, `AuthProfilesStore` — JSON persistence |
|
||||
| `src/auth/openai_oauth.rs` | PKCE generation, code exchange, refresh — reference pattern |
|
||||
| `src/security/secrets.rs` | `SecretStore` — ChaCha20-Poly1305 encrypt/decrypt |
|
||||
| `src/tunnel/mod.rs` | `Tunnel` trait + factory — public URL for OAuth redirect |
|
||||
| `src/tunnel/cloudflare.rs` | Cloudflare Tunnel implementation |
|
||||
| `src/tunnel/ngrok.rs` | ngrok implementation |
|
||||
| `src/gateway/mod.rs` | Axum HTTP gateway — where `/auth/callback/gmail` is registered |
|
||||
| `src/tools/http_request.rs` | `HttpRequestTool` — dispatches Gmail API calls |
|
||||
| `src/tools/memory_store.rs` | `MemoryStoreTool` — stores synced emails |
|
||||
| `src/tools/memory_recall.rs` | `MemoryRecallTool` — searches synced emails |
|
||||
| `src/security/policy.rs` | `SecurityPolicy` — enforces `ToolOperation::Act` guards |
|
||||
| `docs/config-reference.md` | Full config schema including `[http]`, `[tunnel]` |
|
||||
|
||||
---
|
||||
|
||||
## 11. Security Notes
|
||||
|
||||
- The OAuth2 `redirect_uri` **must be the tunnel URL**. It cannot be `localhost` in a
|
||||
remote/mobile scenario. Google validates the exact URI registered in the Google Cloud
|
||||
Console; register it as `https://<your-tunnel-domain>/auth/callback/gmail`.
|
||||
- The CSRF `state` token must be validated on every callback. Reject mismatched or
|
||||
missing state with `400 Bad Request` before touching any tokens.
|
||||
- The PKCE verifier must be destroyed after a successful or failed exchange — never
|
||||
persist it beyond the in-flight flow.
|
||||
- `client_secret` must never appear in plaintext in `config.toml`, logs, or agent
|
||||
tool output. Encrypt it via the secret store and redact it in observability output.
|
||||
- `googleapis.com` must be explicitly present in `http.allowed_domains` — the
|
||||
`HttpRequestTool` enforces this allowlist before every request
|
||||
(`src/tools/http_request.rs`).
|
||||
- Token refresh runs under the same per-profile Tokio mutex used for OpenAI tokens
|
||||
(`src/auth/mod.rs:287`) to prevent double-refresh races.
|
||||
@@ -1,530 +0,0 @@
|
||||
# AlphaHuman Teams API Reference
|
||||
|
||||
Complete reference for all teams-related API endpoints in the AlphaHuman platform.
|
||||
|
||||
**Base URL**: `https://api.alphahuman.xyz`
|
||||
**Authentication**: Bearer JWT token required for all endpoints
|
||||
**Content-Type**: `application/json`
|
||||
|
||||
---
|
||||
|
||||
## Core Team Management
|
||||
|
||||
### 1. Create Team
|
||||
|
||||
**POST** `/teams`
|
||||
|
||||
Creates a new team with optional encryption.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{ "name": "string (required)", "magicWord": "string (optional)" }
|
||||
```
|
||||
|
||||
**Response Schema:**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"slug": "string",
|
||||
"magicWord": "string | null",
|
||||
"createdBy": "string",
|
||||
"isPersonal": "boolean",
|
||||
"subscription": {
|
||||
"hasActiveSubscription": "boolean",
|
||||
"plan": "FREE|BASIC|PRO",
|
||||
"planExpiry": "date-time | null",
|
||||
"stripeCustomerId": "string | null"
|
||||
},
|
||||
"usage": {
|
||||
"weeklyBudgetUsd": "number",
|
||||
"spentThisWeekUsd": "number",
|
||||
"weekStartDate": "date-time"
|
||||
},
|
||||
"inviteCode": "string | null",
|
||||
"maxMembers": "number",
|
||||
"createdAt": "date-time",
|
||||
"updatedAt": "date-time"
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Team created successfully
|
||||
- `401` - Unauthorized
|
||||
|
||||
---
|
||||
|
||||
### 2. List Teams
|
||||
|
||||
**GET** `/teams`
|
||||
|
||||
Retrieves all teams the authenticated user belongs to.
|
||||
|
||||
**Response Schema:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"team": {
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"slug": "string",
|
||||
"isPersonal": "boolean",
|
||||
"subscription": { "hasActiveSubscription": "boolean", "plan": "FREE|BASIC|PRO" }
|
||||
},
|
||||
"role": "admin|billing_manager|member"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Success
|
||||
- `401` - Unauthorized
|
||||
|
||||
---
|
||||
|
||||
### 3. Get Team Details
|
||||
|
||||
**GET** `/teams/{teamId}`
|
||||
|
||||
Retrieves detailed information for a specific team.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
|
||||
**Response Schema:**
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "string",
|
||||
"name": "string",
|
||||
"slug": "string",
|
||||
"magicWord": "string | null",
|
||||
"createdBy": "string",
|
||||
"isPersonal": "boolean",
|
||||
"subscription": {
|
||||
"hasActiveSubscription": "boolean",
|
||||
"plan": "FREE|BASIC|PRO",
|
||||
"planExpiry": "date-time | null",
|
||||
"stripeCustomerId": "string | null"
|
||||
},
|
||||
"usage": {
|
||||
"weeklyBudgetUsd": "number",
|
||||
"spentThisWeekUsd": "number",
|
||||
"weekStartDate": "date-time"
|
||||
},
|
||||
"inviteCode": "string | null",
|
||||
"maxMembers": "number",
|
||||
"createdAt": "date-time",
|
||||
"updatedAt": "date-time"
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Success
|
||||
- `403` - Not a member of the team
|
||||
- `404` - Team not found
|
||||
|
||||
---
|
||||
|
||||
### 4. Update Team Settings
|
||||
|
||||
**PUT** `/teams/{teamId}`
|
||||
|
||||
Updates team settings. **Admin only**.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{ "name": "string (optional)", "maxMembers": "number (optional)" }
|
||||
```
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Team updated successfully
|
||||
- `403` - Only admins can update team settings
|
||||
|
||||
---
|
||||
|
||||
### 5. Delete Team
|
||||
|
||||
**DELETE** `/teams/{teamId}`
|
||||
|
||||
Deletes a team. **Admin only, non-personal teams only**.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Team deleted successfully
|
||||
- `400` - Cannot delete a personal team
|
||||
- `403` - Only admins can delete a team
|
||||
|
||||
---
|
||||
|
||||
### 6. Switch Active Team
|
||||
|
||||
**POST** `/teams/{teamId}/switch`
|
||||
|
||||
Changes the user's active team.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Successfully switched active team
|
||||
- `403` - Not a member of the specified team
|
||||
|
||||
---
|
||||
|
||||
## Team Member Management
|
||||
|
||||
### 7. List Team Members
|
||||
|
||||
**GET** `/teams/{teamId}/members`
|
||||
|
||||
Lists all members of a team.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
|
||||
**Response Schema:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [{ "user": "string", "role": "admin|billing_manager|member", "joinedAt": "date-time" }]
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Success
|
||||
- `403` - Not a member of this team
|
||||
|
||||
---
|
||||
|
||||
### 8. Remove Team Member
|
||||
|
||||
**DELETE** `/teams/{teamId}/members/{userId}`
|
||||
|
||||
Removes a member from the team. **Admin only**.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
- `userId` (path, required) - User identifier to remove
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Member removed
|
||||
- `403` - Only admins can remove members
|
||||
|
||||
---
|
||||
|
||||
### 9. Change Member Role
|
||||
|
||||
**PUT** `/teams/{teamId}/members/{userId}/role`
|
||||
|
||||
Changes a member's role within the team. **Admin only**.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
- `userId` (path, required) - User identifier
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{ "role": "admin|billing_manager|member" }
|
||||
```
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Role updated
|
||||
- `403` - Only admins can change member roles
|
||||
|
||||
---
|
||||
|
||||
### 10. Leave Team
|
||||
|
||||
**POST** `/teams/{teamId}/leave`
|
||||
|
||||
Allows a user to leave a team.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Successfully left the team
|
||||
- `400` - Cannot leave as the only admin
|
||||
|
||||
---
|
||||
|
||||
## Team Invite Management
|
||||
|
||||
### 11. Create Team Invite
|
||||
|
||||
**POST** `/teams/{teamId}/invites`
|
||||
|
||||
Creates a new invite code for the team.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
|
||||
**Request Body (Optional):**
|
||||
|
||||
```json
|
||||
{ "maxUses": "number (default: 1)", "expiresInDays": "number (default: 7)" }
|
||||
```
|
||||
|
||||
**Response Schema:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": { "code": "string (e.g., T-1A2B3C4D5E6F)", "expiresAt": "date-time", "maxUses": "number" }
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Invite created successfully
|
||||
- `403` - Not a team member
|
||||
|
||||
---
|
||||
|
||||
### 12. List Team Invites
|
||||
|
||||
**GET** `/teams/{teamId}/invites`
|
||||
|
||||
Lists all invites for a team.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Success
|
||||
- `403` - User is not a member of the team
|
||||
|
||||
---
|
||||
|
||||
### 13. Revoke Team Invite
|
||||
|
||||
**DELETE** `/teams/{teamId}/invites/{inviteId}`
|
||||
|
||||
Revokes a team invite. **Admin or invite creator only**.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
- `inviteId` (path, required) - Invite identifier
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Invite successfully revoked
|
||||
- `403` - Only admins or invite creator can revoke
|
||||
- `404` - Invite does not exist
|
||||
|
||||
---
|
||||
|
||||
### 14. Join Team
|
||||
|
||||
**POST** `/teams/join`
|
||||
|
||||
Joins a team using an invite code.
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{ "code": "string (required, e.g., T-1A2B3C4D5E6F)" }
|
||||
```
|
||||
|
||||
**Response Schema:**
|
||||
|
||||
```json
|
||||
{ "success": true, "data": { "team": "string", "membership": "string" } }
|
||||
```
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Joined the team successfully
|
||||
- `400` - Invite expired, max uses reached, or already a team member
|
||||
- `404` - Invite code not found
|
||||
|
||||
---
|
||||
|
||||
## Team Billing Management
|
||||
|
||||
### 15. Purchase Team Subscription
|
||||
|
||||
**POST** `/teams/{teamId}/billing/purchase`
|
||||
|
||||
Purchases a subscription plan for the team. **Admin or billing manager only**.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
|
||||
**Request Body:**
|
||||
|
||||
```json
|
||||
{
|
||||
"plan": "BASIC_MONTHLY|BASIC_YEARLY|PRO_MONTHLY|PRO_YEARLY",
|
||||
"successUrl": "string (optional)",
|
||||
"cancelUrl": "string (optional)"
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Checkout session created successfully
|
||||
- `403` - Only admins or billing managers can purchase plans
|
||||
|
||||
---
|
||||
|
||||
### 16. Get Team Subscription Plan
|
||||
|
||||
**GET** `/teams/{teamId}/billing/plan`
|
||||
|
||||
Retrieves the team's current subscription information.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
|
||||
**Response Schema:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"plan": "FREE|BASIC|PRO",
|
||||
"hasActiveSubscription": "boolean",
|
||||
"planExpiry": "date-time | null"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Success
|
||||
- `401` - Unauthorized
|
||||
|
||||
---
|
||||
|
||||
### 17. Create Billing Portal Session
|
||||
|
||||
**POST** `/teams/{teamId}/billing/portal`
|
||||
|
||||
Creates a Stripe billing portal session for subscription management. **Admin or billing manager only**.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `teamId` (path, required) - Team identifier
|
||||
|
||||
**Request Body (Optional):**
|
||||
|
||||
```json
|
||||
{ "returnUrl": "string" }
|
||||
```
|
||||
|
||||
**Response Schema:**
|
||||
|
||||
```json
|
||||
{ "success": true, "data": { "url": "string" } }
|
||||
```
|
||||
|
||||
**Status Codes:**
|
||||
|
||||
- `200` - Portal session created successfully
|
||||
- `403` - Only admins or billing managers can create portal session
|
||||
|
||||
---
|
||||
|
||||
## Team Roles
|
||||
|
||||
### Role Hierarchy
|
||||
|
||||
1. **admin** - Full team management permissions
|
||||
2. **billing_manager** - Billing and subscription management
|
||||
3. **member** - Basic team member access
|
||||
|
||||
### Permission Matrix
|
||||
|
||||
| Action | Admin | Billing Manager | Member |
|
||||
| -------------------- | ----- | --------------- | ------ |
|
||||
| View team details | ✅ | ✅ | ✅ |
|
||||
| Update team settings | ✅ | ❌ | ❌ |
|
||||
| Delete team | ✅ | ❌ | ❌ |
|
||||
| Add/remove members | ✅ | ❌ | ❌ |
|
||||
| Change member roles | ✅ | ❌ | ❌ |
|
||||
| Create invites | ✅ | ✅ | ✅ |
|
||||
| Manage billing | ✅ | ✅ | ❌ |
|
||||
| Leave team | ✅\* | ✅ | ✅ |
|
||||
|
||||
\*Admin cannot leave if they are the only admin
|
||||
|
||||
---
|
||||
|
||||
## Team Plans & Limits
|
||||
|
||||
### Plan Types
|
||||
|
||||
- **FREE** - Basic team functionality
|
||||
- **BASIC** - Enhanced features and limits
|
||||
- **PRO** - Full feature set and highest limits
|
||||
|
||||
### Usage Tracking
|
||||
|
||||
Teams have usage limits tracked through:
|
||||
|
||||
- `weeklyBudgetUsd` - Weekly spending budget
|
||||
- `spentThisWeekUsd` - Current week spending
|
||||
- `weekStartDate` - When the current week started
|
||||
- `maxMembers` - Maximum team size
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Error Responses
|
||||
|
||||
```json
|
||||
{ "success": false, "error": "Error message description" }
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
All endpoints require a Bearer JWT token:
|
||||
|
||||
```
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
Standard API rate limits apply to all endpoints. Refer to main API documentation for specific limits.
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 350 KiB |
Reference in New Issue
Block a user