abtop 0.4.0

AI agent monitor for your terminal
use crate::app::App;
use crate::model::{AgentSession, ChildProcess, FileAccess, FileOp, OrphanPort, RateLimitInfo, SessionStatus, SubAgent, ToolCall};
use std::time::{SystemTime, UNIX_EPOCH};

fn now_ms() -> u64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis() as u64
}

fn now_secs() -> u64 {
    now_ms() / 1000
}

pub fn populate_demo(app: &mut App) {
    let now = now_ms();

    // --- Sessions ---
    app.sessions = vec![
        AgentSession {
            agent_cli: "claude",
            pid: 7336,
            session_id: "a1b2c3d4-5678-9abc-def0-111111111111".into(),
            cwd: "/Users/demo/webshop".into(),
            project_name: "webshop".into(),
            started_at: now - 2 * 3600 * 1000, // 2h ago
            status: SessionStatus::Executing,
            model: "claude-opus-4-6".into(),
            effort: String::new(),
            context_percent: 72.0,
            total_input_tokens: 48_200,
            total_output_tokens: 12_800,
            total_cache_read: 1_420_000,
            total_cache_create: 185_000,
            turn_count: 34,
            current_tasks: vec!["Edit src/checkout/payment.rs".into()],
            mem_mb: 342,
            version: "2.1.87".into(),
            git_branch: "main".into(),
            git_added: 2,
            git_modified: 8,
            token_history: vec![
                18000, 22000, 45000, 38000, 52000, 41000, 35000, 28000,
                61000, 55000, 48000, 39000, 44000, 50000, 32000, 27000,
                58000, 46000, 42000, 36000, 53000, 47000, 41000, 38000,
                62000, 55000, 49000, 43000, 51000, 44000, 38000, 33000,
                56000, 48000,
            ],
            context_history: vec![
                20000, 35000, 52000, 68000, 85000, 102000, 118000, 135000,
                148000, 162000, 175000, 185000, 192000,
                // compaction event: 192k -> 65k (66% drop)
                65000,
                78000, 92000, 108000, 125000, 138000, 145000, 155000,
                168000, 178000, 185000, 190000,
                // second compaction: 190k -> 58k
                58000,
                72000, 88000, 105000, 120000, 135000, 142000, 148000,
            ],
            compaction_count: 2,
            context_window: 200_000,
            subagents: vec![
                SubAgent {
                    name: "Explore test coverage".into(),

                    status: "done".into(),
                    tokens: 12_400,
                },
                SubAgent {
                    name: "Run integration tests".into(),

                    status: "working".into(),
                    tokens: 8_200,
                },
            ],
            mem_file_count: 4,
            mem_line_count: 12,
            children: vec![
                ChildProcess {
                    pid: 7401,
                    command: "cargo build --release".into(),
                    mem_kb: 342_000,
                    port: None,
                },
                ChildProcess {
                    pid: 7455,
                    command: "cargo test".into(),
                    mem_kb: 28_000,
                    port: None,
                },
            ],

            first_assistant_text: String::new(),
            initial_prompt: "Implement Stripe payment integration for checkout flow".into(),
            tool_calls: vec![
                ToolCall { name: "Read".into(), arg: "src/checkout/mod.rs".into(), duration_ms: 85 },
                ToolCall { name: "Read".into(), arg: "Cargo.toml".into(), duration_ms: 42 },
                ToolCall { name: "Bash".into(), arg: "cargo add stripe-rust".into(), duration_ms: 3200 },
                ToolCall { name: "Edit".into(), arg: "src/checkout/payment.rs".into(), duration_ms: 120 },
                ToolCall { name: "Edit".into(), arg: "src/checkout/mod.rs".into(), duration_ms: 95 },
                ToolCall { name: "Write".into(), arg: "src/checkout/stripe.rs".into(), duration_ms: 180 },
                ToolCall { name: "Bash".into(), arg: "cargo test".into(), duration_ms: 8400 },
                ToolCall { name: "Edit".into(), arg: "src/checkout/stripe.rs".into(), duration_ms: 110 },
                ToolCall { name: "Bash".into(), arg: "cargo test checkout".into(), duration_ms: 4200 },
                ToolCall { name: "Read".into(), arg: "src/config.rs".into(), duration_ms: 55 },
                ToolCall { name: "Edit".into(), arg: "src/config.rs".into(), duration_ms: 90 },
                ToolCall { name: "Grep".into(), arg: "STRIPE_SECRET".into(), duration_ms: 320 },
                ToolCall { name: "Agent".into(), arg: "security review".into(), duration_ms: 12400 },
                ToolCall { name: "Bash".into(), arg: "cargo clippy".into(), duration_ms: 5100 },
                ToolCall { name: "Edit".into(), arg: "src/checkout/payment.rs".into(), duration_ms: 145 },
                ToolCall { name: "Bash".into(), arg: "cargo test".into(), duration_ms: 7800 },
                // Currently running: timeline bar grows in real time.
                ToolCall { name: "WebSearch".into(), arg: "stripe webhook best practice".into(), duration_ms: 0 },
            ],
            pending_since_ms: now - 6_000, // 6s ago => bar animates
            thinking_since_ms: 0,
            file_accesses: vec![
                FileAccess { path: "src/checkout/payment.rs".into(), operation: FileOp::Read, turn_index: 2 },
                FileAccess { path: "src/checkout/mod.rs".into(), operation: FileOp::Read, turn_index: 3 },
                FileAccess { path: "src/checkout/payment.rs".into(), operation: FileOp::Edit, turn_index: 5 },
                FileAccess { path: "src/config/stripe.rs".into(), operation: FileOp::Write, turn_index: 7 },
                FileAccess { path: "src/checkout/payment.rs".into(), operation: FileOp::Edit, turn_index: 10 },
                FileAccess { path: "tests/checkout_test.rs".into(), operation: FileOp::Write, turn_index: 12 },
                FileAccess { path: "src/models/order.rs".into(), operation: FileOp::Read, turn_index: 15 },
                FileAccess { path: "src/checkout/payment.rs".into(), operation: FileOp::Edit, turn_index: 18 },
                FileAccess { path: "Cargo.toml".into(), operation: FileOp::Read, turn_index: 20 },
                FileAccess { path: "tests/checkout_test.rs".into(), operation: FileOp::Edit, turn_index: 22 },
            ],
        },
        AgentSession {
            agent_cli: "claude",
            pid: 8840,
            session_id: "b2c3d4e5-6789-abcd-ef01-222222222222".into(),
            cwd: "/Users/demo/ml-pipeline".into(),
            project_name: "ml-pipeline".into(),
            started_at: now - 47 * 60 * 1000, // 47m ago
            status: SessionStatus::Waiting,
            model: "claude-sonnet-4-6".into(),
            effort: String::new(),
            context_percent: 91.0,
            total_input_tokens: 82_000,
            total_output_tokens: 38_000,
            total_cache_read: 2_100_000,
            total_cache_create: 280_000,
            turn_count: 71,
            current_tasks: vec!["waiting for user input".into()],
            mem_mb: 128,
            version: "2.1.87".into(),
            git_branch: "feat/batch-inference".into(),
            git_added: 1,
            git_modified: 4,
            token_history: vec![
                32000, 28000, 41000, 55000, 62000, 48000, 35000, 29000,
                44000, 58000, 51000, 39000, 46000, 53000, 42000, 37000,
                60000, 52000, 45000, 38000, 56000, 49000, 43000, 36000,
                63000, 57000, 50000, 44000, 54000, 47000,
            ],
            context_history: vec![
                15000, 28000, 45000, 62000, 80000, 95000, 112000, 128000,
                142000, 158000, 172000, 182000, 190000, 195000,
                // compaction: 195k -> 70k
                70000,
                85000, 98000, 115000, 130000, 145000, 158000, 170000, 182000,
            ],
            compaction_count: 1,
            context_window: 200_000,
            subagents: vec![],
            mem_file_count: 2,
            mem_line_count: 8,
            children: vec![],

            first_assistant_text: String::new(),
            initial_prompt: "Add batch inference endpoint with GPU scheduling".into(),
            tool_calls: vec![],
            pending_since_ms: 0,
            thinking_since_ms: 0,
            file_accesses: vec![],
        },
        AgentSession {
            agent_cli: "claude",
            pid: 9102,
            session_id: "c3d4e5f6-789a-bcde-f012-333333333333".into(),
            cwd: "/Users/demo/api-server".into(),
            project_name: "api-server".into(),
            started_at: now - 15 * 60 * 1000, // 15m ago
            status: SessionStatus::Executing,
            model: "claude-haiku-4-5".into(),
            effort: String::new(),
            context_percent: 42.0,
            total_input_tokens: 5_200,
            total_output_tokens: 2_800,
            total_cache_read: 320_000,
            total_cache_create: 45_000,
            turn_count: 12,
            current_tasks: vec!["Bash npm run dev".into()],
            mem_mb: 86,
            version: "2.1.87".into(),
            git_branch: "main".into(),
            git_added: 0,
            git_modified: 2,
            token_history: vec![
                8000, 12000, 15000, 22000, 18000, 25000, 20000, 16000,
                28000, 24000, 19000, 14000,
            ],
            context_history: vec![],
            compaction_count: 0,
            context_window: 200_000,
            subagents: vec![],
            mem_file_count: 1,
            mem_line_count: 3,
            children: vec![
                ChildProcess {
                    pid: 9150,
                    command: "node server.js".into(),
                    mem_kb: 98_000,
                    port: Some(3000),
                },
                ChildProcess {
                    pid: 9178,
                    command: "node worker.js".into(),
                    mem_kb: 52_000,
                    port: Some(3001),
                },
                ChildProcess {
                    pid: 9203,
                    command: "postgres -D /usr/local/var/postgres".into(),
                    mem_kb: 24_000,
                    port: Some(5432),
                },
            ],

            first_assistant_text: String::new(),
            initial_prompt: "Fix CORS headers and add rate limiting middleware".into(),
            tool_calls: vec![
                ToolCall { name: "Read".into(), arg: "src/server.ts".into(), duration_ms: 60 },
                ToolCall { name: "Edit".into(), arg: "src/middleware/cors.ts".into(), duration_ms: 150 },
                ToolCall { name: "Write".into(), arg: "src/middleware/rate-limit.ts".into(), duration_ms: 200 },
                ToolCall { name: "Bash".into(), arg: "npm test".into(), duration_ms: 2800 },
                ToolCall { name: "Edit".into(), arg: "src/server.ts".into(), duration_ms: 110 },
                ToolCall { name: "Bash".into(), arg: "npm run dev".into(), duration_ms: 1500 },
            ],
            pending_since_ms: 0,
            // Model is generating its next reply - virtual "Thinking" row
            // animates. 1s offset keeps the bar visibly growing against the
            // session's 2.8s max tool duration before it caps at 100%.
            thinking_since_ms: now - 1_000,
            file_accesses: vec![],
        },
        AgentSession {
            agent_cli: "codex",
            pid: 8901,
            session_id: "d4e5f6a7-89ab-cdef-0123-444444444444".into(),
            cwd: "/Users/demo/data-viz".into(),
            project_name: "data-viz".into(),
            started_at: now - 5 * 60 * 1000, // 5m ago
            status: SessionStatus::Thinking,
            model: "gpt-5.4".into(),
            effort: "medium".into(),
            context_percent: 18.0,
            total_input_tokens: 3_100,
            total_output_tokens: 1_400,
            total_cache_read: 85_000,
            total_cache_create: 12_000,
            turn_count: 6,
            current_tasks: vec!["Write src/charts/heatmap.py".into()],
            mem_mb: 64,
            version: "0.116.0".into(),
            git_branch: "feat/heatmap".into(),
            git_added: 3,
            git_modified: 1,
            token_history: vec![
                5000, 8000, 12000, 18000, 15000, 22000,
            ],
            context_history: vec![],
            compaction_count: 0,
            context_window: 200_000,
            subagents: vec![],
            mem_file_count: 0,
            mem_line_count: 0,
            children: vec![
                ChildProcess {
                    pid: 8950,
                    command: "python -m http.server 8080".into(),
                    mem_kb: 32_000,
                    port: Some(8080),
                },
            ],

            first_assistant_text: String::new(),
            initial_prompt: "Create interactive heatmap component with D3.js".into(),
            tool_calls: vec![],
            pending_since_ms: 0,
            thinking_since_ms: 0,
            file_accesses: vec![],
        },
    ];

    // --- Summaries (pre-populated, no LLM calls) ---
    app.summaries.insert(
        "a1b2c3d4-5678-9abc-def0-111111111111".into(),
        "Stripe payment integration".into(),
    );
    app.summaries.insert(
        "b2c3d4e5-6789-abcd-ef01-222222222222".into(),
        "Batch inference endpoint".into(),
    );
    app.summaries.insert(
        "c3d4e5f6-789a-bcde-f012-333333333333".into(),
        "CORS fix + rate limiting".into(),
    );
    app.summaries.insert(
        "d4e5f6a7-89ab-cdef-0123-444444444444".into(),
        "D3 heatmap component".into(),
    );

    // --- Rate limits ---
    app.rate_limits = vec![
        RateLimitInfo {
            source: "claude".into(),
            five_hour_pct: Some(35.0),
            five_hour_resets_at: Some(now_secs() + 3 * 3600),
            seven_day_pct: Some(12.0),
            seven_day_resets_at: Some(now_secs() + 5 * 24 * 3600),
            updated_at: Some(now_secs() - 10),
        },
        RateLimitInfo {
            source: "codex".into(),
            five_hour_pct: Some(9.0),
            five_hour_resets_at: Some(now_secs() + 4 * 3600),
            seven_day_pct: Some(14.0),
            seven_day_resets_at: Some(now_secs() + 6 * 24 * 3600),
            updated_at: Some(now_secs() - 5),
        },
    ];

    // --- Token rates (synthetic sparkline) ---
    let rates = [
        0.0, 0.0, 120.0, 340.0, 580.0, 420.0, 0.0, 0.0, 890.0, 1200.0,
        950.0, 680.0, 0.0, 0.0, 450.0, 780.0, 1100.0, 1350.0, 920.0, 610.0,
        0.0, 340.0, 670.0, 890.0, 1050.0, 780.0, 520.0, 0.0, 0.0, 1400.0,
        1180.0, 850.0, 620.0, 410.0, 0.0, 560.0, 820.0, 1060.0, 1280.0, 940.0,
        700.0, 480.0, 0.0, 0.0, 380.0, 720.0, 1150.0, 1320.0, 980.0, 650.0,
    ];
    app.token_rates.clear();
    for &r in &rates {
        app.token_rates.push_back(r);
    }

    // --- Orphan ports ---
    app.orphan_ports = vec![
        OrphanPort {
            port: 4000,
            pid: 6543,
            command: "node dist/server.js".into(),
            project_name: "old-project".into(),
        },
    ];

    // --- Host metrics + agent aggregate (demo values) ---
    app.host_metrics = Some(crate::host_info::HostMetrics {
        cpu_pct: 23.0,
        mem_pct: 41.0,
        load1: 1.8,
    });
    app.agent_aggregate = crate::host_info::AgentAggregate::from_sessions(&app.sessions);
}