/**
 * src/autonomy/boot.ts
 *
 * Wires autonomy on gateway boot. In device mode, this is the moment Muassis
 * stops being a chatbot and starts being an employee:
 *   1. Resolve the device tenant from <dataDir>/owner.id.
 *   2. Seed default cron jobs / policies / starter goal (idempotent).
 *   3. Auto-start the WhatsApp Baileys session for that tenant so inbound
 *      messages from real customers route through the agent without anyone
 *      clicking a "connect" button in the dashboard.
 *
 * Failures here log + continue. The gateway must come up even if no tenant
 * exists yet (`muassis init` has not been run). In control-plane mode this
 * function is a no-op — multi-tenant SaaS uses per-request seeding.
 */
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { config } from '../config.js';
import { getDataDir } from '../utils/data-dir.js';
import { supabase } from '../utils/supabase.js';
import { createChildLogger } from '../utils/logger.js';
import { seedAutonomyDefaults } from './seeder.js';
const log = createChildLogger('autonomy:boot');
async function resolveDeviceTenant() {
    const ownerFile = join(getDataDir(), 'owner.id');
    if (!existsSync(ownerFile)) {
        log.info('no owner.id present — skipping autonomy boot (run `muassis init` first)');
        return null;
    }
    const ownerId = readFileSync(ownerFile, 'utf-8').trim();
    if (ownerId.length !== 36) {
        log.warn({ ownerId }, 'owner.id present but not a UUID');
        return null;
    }
    const { data, error } = await supabase
        .from('tenants')
        .select('id, whatsapp_phone, owner_phone, created_at')
        .eq('owner_id', ownerId)
        .order('created_at', { ascending: false })
        .limit(1);
    if (error) {
        log.warn({ error: error.message }, 'tenant lookup failed during autonomy boot');
        return null;
    }
    const row = data?.[0];
    if (!row) {
        log.info('owner.id present but no matching tenant row yet');
        return null;
    }
    return row;
}
export async function bootAutonomy() {
    if (config.MUASSIS_MODE !== 'device') {
        log.info('control-plane mode — autonomy seeding is per-tenant on demand');
        return;
    }
    const tenant = await resolveDeviceTenant();
    if (!tenant)
        return;
    // 0. Reap orphaned in-flight subagents. Subagents run in-process; when the
    // gateway restarts, any that were mid-run die but their DB row stays
    // 'running'/'queued' forever → the chat shows them stuck spinning and the
    // task counters never drain. At boot NOTHING is running yet, so every such
    // row is definitionally orphaned. Mark them failed so the UI progresses.
    try {
        const { error } = await supabase
            .from('subagent_runs')
            .update({ status: 'failed', error: 'orphaned by gateway restart (auto-reaped on boot)', finished_at: new Date().toISOString() })
            .eq('tenant_id', tenant.id)
            .in('status', ['running', 'queued', 'pending']);
        if (error)
            log.warn({ error: error.message }, 'orphan-subagent reap failed');
        else
            log.info({ tenantId: tenant.id }, 'reaped orphaned in-flight subagents on boot');
    }
    catch (err) {
        log.warn({ error: err.message }, 'orphan-subagent reap threw (non-fatal)');
    }
    // 1. Seed defaults (idempotent — flag stored in identity_facts)
    try {
        const ownerPhone = tenant.owner_phone ?? tenant.whatsapp_phone ?? null;
        const result = await seedAutonomyDefaults({ tenantId: tenant.id, ownerPhone });
        log.info({ tenantId: tenant.id, ...result }, 'autonomy seeding complete');
    }
    catch (err) {
        log.warn({ error: err.message }, 'autonomy seeding failed (non-fatal)');
    }
    // 1a. Catch-up scan — surface a "while you were away" digest if cron jobs
    // missed fires while the gateway was off. Non-fatal; runs after seeding so
    // a fresh-install gateway with no execution history skips cleanly.
    try {
        const { runCatchupScan } = await import('../cron/catchup.js');
        const catchup = await runCatchupScan(tenant.id);
        if (catchup.missed.length > 0) {
            log.info({ tenantId: tenant.id, ...catchup }, 'catch-up digest issued');
        }
    }
    catch (err) {
        log.warn({ error: err.message }, 'catch-up scan failed (non-fatal)');
    }
    // 1b. Seed all bundled items (skills, cron jobs, policies, tools) into the
    // knowledge graph so they're queryable as first-class entities. Idempotent.
    // The dashboard graph view picks these up so the owner sees the full
    // out-of-box inventory without documentation hunting.
    try {
        const { seedAllToKnowledgeGraph } = await import('./kg-seeder.js');
        const kgResult = await seedAllToKnowledgeGraph(tenant.id);
        log.info({ tenantId: tenant.id, ...kgResult }, 'KG seed of out-of-box items complete');
    }
    catch (err) {
        log.warn({ error: err.message }, 'KG seeding failed (non-fatal — graph view may show empty)');
    }
    // 1c. Run-on-launch catch-up — fire any daily autonomy cycle that went stale
    // while the desktop was asleep (reflection, initiative, research, …). Crons
    // can't fire on a sleeping Mac, so this is what guarantees "today's thinking"
    // actually happens. Fire-and-forget: runs sequentially in the background so a
    // slow Opus cycle never blocks boot. Self-guarded by waking-window + staleness.
    try {
        const { runOnLaunchCatchup } = await import('./run-on-launch.js');
        void runOnLaunchCatchup(tenant.id)
            .then((r) => log.info({ tenantId: tenant.id, fired: r.fired.length, skipped: r.skipped.length, ksaHour: r.ksaHour }, 'launch catch-up dispatched'))
            .catch((err) => log.warn({ error: err.message }, 'launch catch-up failed (non-fatal)'));
    }
    catch (err) {
        log.warn({ error: err.message }, 'launch catch-up import failed (non-fatal)');
    }
    // 2. Auto-start WhatsApp session (best-effort, never blocks boot)
    try {
        const { startWhatsAppSession } = await import('../channels/whatsapp/baileys-adapter.js');
        void startWhatsAppSession(tenant.id);
        log.info({ tenantId: tenant.id }, 'whatsapp session auto-start kicked off');
    }
    catch (err) {
        log.warn({ error: err.message }, 'whatsapp auto-start failed (non-fatal)');
    }
    // 2b. Pre-warm browser session if MUASSIS_BROWSER_PREWARM=true. Off by
    //     default (saves ~200MB RAM); on for installs that use browser tools
    //     heavily and want zero-latency first call.
    try {
        const { prewarmBrowser } = await import('../integrations/browser-session.js');
        void prewarmBrowser(tenant.id);
    }
    catch { /* ignore — module optional */ }
    // 3. Auto-start Telegram if a bot token is configured for this tenant.
    // Token resolution order:
    //   a) identity_fact `channel.telegram_bot_token` (per-tenant, paste-in-chat)
    //   b) MUASSIS_TELEGRAM_BOT_TOKEN env var (single-tenant device default)
    // Customers paste a BotFather token into chat → tryQuickConfig saves it as
    // an identity fact → on next boot it auto-starts. Zero-setup ethos preserved.
    try {
        const { getFact } = await import('./identity.js');
        const stored = await getFact(tenant.id, 'channel.telegram_bot_token');
        const token = stored?.fact_value || process.env.MUASSIS_TELEGRAM_BOT_TOKEN || '';
        if (token) {
            const { startTelegramSession } = await import('../channels/telegram/adapter.js');
            void startTelegramSession(tenant.id, token);
            log.info({ tenantId: tenant.id }, 'telegram session auto-start kicked off');
        }
    }
    catch (err) {
        log.warn({ error: err.message }, 'telegram auto-start failed (non-fatal)');
    }
}
//# sourceMappingURL=boot.js.map