"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.worker = worker;
const os_1 = __importDefault(require("os"));
const ws_1 = require("ws");
const list_1 = require("./agents/tools/list");
const login_1 = require("./login");
const services_1 = require("./services");
const PasskeySetup_1 = require("./workers/auth/PasskeySetup");
const WorkerPasskeyAuth_1 = require("./workers/auth/WorkerPasskeyAuth");
const tools_1 = require("./workers/tools");
const Mcp_1 = require("./services/Mcp");
const allTools = __importStar(require("./agents/tools"));
const tools_2 = __importDefault(require("./workers/tools"));
const utils_1 = require("./utils");
const config_1 = require("./config");
const KnowhowClient_1 = require("./services/KnowhowClient");
const workerRegistry_1 = require("./workerRegistry");
const tunnel_1 = require("./tunnel");
const API_URL = KnowhowClient_1.KNOWHOW_API_URL;
async function runWorkerInSandbox(options, config) {
    const { Docker } = (0, services_1.services)();
    console.log("🐳 Starting knowhow worker in Docker sandbox mode...\n");
    const dockerAvailable = await Docker.checkDockerAvailable();
    if (!dockerAvailable) {
        console.error("❌ Docker is not installed or not running.");
        console.error("\nTo use --sandbox mode, you need to:");
        console.error("  1. Install Docker: https://docs.docker.com/get-docker/");
        console.error("  2. Start the Docker daemon");
        console.error("  3. Run this command again with --sandbox\n");
        process.exit(1);
    }
    console.log("✓ Docker is available");
    console.log("🔄 Building Docker image (ensuring latest version)...");
    try {
        await Docker.buildWorkerImage();
    }
    catch (error) {
        console.error("❌ Failed to build Docker image:", error.message);
        console.error("\nPlease check the .knowhow/Dockerfile.worker for errors");
        console.error("  You can edit this file to customize the worker image\n");
        process.exit(1);
    }
    const jwt = await (0, login_1.loadJwt)();
    let containerId;
    console.log("🚀 Starting Docker container...");
    try {
        containerId = await Docker.runWorkerContainer({
            workspaceDir: process.cwd(),
            jwt,
            apiUrl: API_URL,
            config,
            share: options?.share,
            unshare: options?.unshare,
        });
    }
    catch (error) {
        console.error("❌ Failed to start Docker container:", error.message);
        process.exit(1);
    }
    try {
        await Docker.followContainerLogs(containerId);
    }
    finally {
        await Docker.stopContainer(containerId);
    }
}
async function worker(options) {
    const config = await (0, config_1.getConfig)();
    if (options?.passkeyReset) {
        const passkeySetup = new PasskeySetup_1.PasskeySetupService();
        await passkeySetup.reset();
        return;
    }
    if (options?.passkey) {
        let jwt;
        try {
            jwt = await (0, login_1.loadJwt)();
        }
        catch {
            console.error("❌ You must be logged in to set up a passkey.");
            console.error("   Run 'knowhow login' first.");
            process.exit(1);
        }
        const passkeySetup = new PasskeySetup_1.PasskeySetupService();
        await passkeySetup.setup(jwt);
        return;
    }
    const isInsideDocker = process.env.KNOWHOW_DOCKER === "true";
    if (isInsideDocker) {
        console.log("🐳 Already running inside Docker container, skipping sandbox mode");
        if (options) {
            options.sandbox = false;
            options.noSandbox = true;
        }
    }
    let shouldUseSandbox = false;
    let sandboxSource = "";
    if (options?.sandbox) {
        shouldUseSandbox = true;
        sandboxSource = "command line (--sandbox)";
        const updatedConfig = {
            ...config,
            worker: {
                ...config.worker,
                sandbox: true,
            },
        };
        await (0, config_1.updateConfig)(updatedConfig);
        console.log("💾 Sandbox mode preference saved to config");
    }
    else if (options?.noSandbox) {
        shouldUseSandbox = false;
        sandboxSource = "command line (--no-sandbox)";
        const updatedConfig = {
            ...config,
            worker: {
                ...config.worker,
                sandbox: false,
            },
        };
        await (0, config_1.updateConfig)(updatedConfig);
        console.log("💾 No-sandbox mode preference saved to config");
    }
    else {
        shouldUseSandbox = config.worker?.sandbox ?? false;
        sandboxSource =
            config.worker?.sandbox !== undefined
                ? `config (${shouldUseSandbox ? "sandbox" : "no-sandbox"})`
                : "default (no-sandbox)";
    }
    if (shouldUseSandbox) {
        console.log(`🐳 Using sandbox mode (${sandboxSource})`);
        return runWorkerInSandbox(options, config);
    }
    const { Tools, Mcp } = (0, services_1.services)();
    const combinedTools = { ...allTools, ...tools_2.default.tools };
    Tools.defineTools(list_1.includedTools, combinedTools);
    Tools.defineTools(tools_2.default.definitions, tools_2.default.tools);
    await Mcp.addTools(Tools);
    const mcpServer = new Mcp_1.McpServerService(Tools);
    const clientName = "knowhow-worker";
    const clientVersion = "1.1.1";
    let authService = null;
    const passkeyConfig = config.worker?.auth?.passkey;
    if (passkeyConfig?.publicKey && passkeyConfig?.credentialId) {
        authService = new WorkerPasskeyAuth_1.WorkerPasskeyAuthService({
            publicKey: passkeyConfig.publicKey,
            credentialId: passkeyConfig.credentialId,
            algorithm: -7,
        }, config.worker?.auth?.sessionDurationHours ?? 3);
        console.log("🔒 Passkey auth enabled — worker starts locked");
    }
    if (!shouldUseSandbox) {
        console.log(`🖥️  Using host mode (${sandboxSource})`);
    }
    if (!options?.allowedTools && (!config.worker || !config.worker.allowedTools)) {
        console.log("Worker tools configured! Update knowhow.json to adjust which tools are allowed by the worker.");
        config.worker = {
            ...config.worker,
            allowedTools: Tools.getToolNames(),
        };
        await (0, config_1.updateConfig)(config);
        return;
    }
    if (options?.register) {
        await (0, workerRegistry_1.registerWorkerPath)(process.cwd());
        return;
    }
    const resolvedToolNames = options?.allowedTools ?? config.worker.allowedTools;
    let toolsToUse = Tools.getToolsByNames(resolvedToolNames);
    if (authService) {
        const _authService = authService;
        for (const tool of toolsToUse) {
            const toolName = tool.function.name;
            const originalFn = Tools.getFunction(toolName);
            Tools.addFunctions({
                [toolName]: async (...args) => {
                    if (_authService.isLocked()) {
                        return {
                            error: "WORKER_LOCKED",
                            message: "Worker is locked. Call the `unlock` tool with your passkey assertion to unlock it first.",
                        };
                    }
                    return originalFn(...args);
                },
            });
        }
        const { unlock, unlockDefinition } = (0, tools_1.makeUnlockTool)(_authService);
        const { lock, lockDefinition } = (0, tools_1.makeLockTool)(_authService);
        Tools.addFunctions({ unlock, lock });
        toolsToUse = [unlockDefinition, lockDefinition, ...toolsToUse];
        console.log("🔑 Auth tools registered: unlock, lock");
    }
    const { reloadConfig, reloadConfigDefinition } = (0, tools_1.makeReloadConfigTool)(Mcp, Tools, mcpServer, (newTools) => {
        toolsToUse = newTools;
    });
    Tools.addFunctions({ reloadConfig });
    toolsToUse = [...toolsToUse, reloadConfigDefinition];
    console.log("🔄 reloadConfig tool registered");
    mcpServer.createServer(clientName, clientVersion).withTools(toolsToUse);
    let connected = false;
    let tunnelHandler = null;
    let tunnelWs = null;
    let lastJwt = null;
    let unauthorizedJwt = null;
    const tunnelEnabled = options?.allowedTools
        ? true
        : (config.worker?.tunnel?.enabled ?? false);
    const { tunnelLocalHost, portMapping } = (0, tunnel_1.resolveTunnelConfig)(config, isInsideDocker);
    if (tunnelEnabled) {
        const tunnelPorts = config.worker?.tunnel?.allowedPorts || [];
        if (tunnelPorts.length === 0) {
            console.warn("⚠️  Tunnel enabled but no allowedPorts configured. Add tunnel.allowedPorts to knowhow.json");
        }
        else {
            console.log(`🌐 Tunnel enabled for ports: ${tunnelPorts.join(", ")}`);
        }
    }
    else {
        console.log("🚫 Tunnel disabled (enable in knowhow.json: worker.tunnel.enabled = true)");
    }
    async function connectWebSocket() {
        const jwt = await (0, login_1.loadJwt)();
        console.log(`Connecting to ${API_URL}`);
        lastJwt = jwt;
        await mcpServer.reset();
        mcpServer.withTools(toolsToUse);
        const dir = process.cwd();
        const homedir = os_1.default.homedir();
        const hostname = process.env.WORKER_HOSTNAME || os_1.default.hostname();
        const root = process.env.WORKER_ROOT ||
            (dir === homedir ? "~" : dir.replace(homedir, "~"));
        const headers = {
            Authorization: `Bearer ${jwt}`,
            "User-Agent": `${clientName}/${clientVersion}/${hostname}`,
            Root: root,
        };
        if (options?.share) {
            headers.Shared = "true";
            console.log("🔓 Worker shared with organization");
        }
        else if (options?.unshare) {
            headers.Shared = "false";
            console.log("🔒 Worker is now private (unshared)");
        }
        else {
            console.log("🔒 Worker is private (only you can use it)");
        }
        const { domain: tunnelDomain, useHttps: tunnelUseHttps } = (0, tunnel_1.extractTunnelDomain)(API_URL);
        const ws = new ws_1.WebSocket(`${API_URL}/ws/worker`, {
            headers,
        });
        const workerRegisteredHandler = async (data) => {
            try {
                const parsed = JSON.parse(typeof data === "string" ? data : data.toString());
                if (parsed?.type === "workerRegistered" && parsed?.workerId) {
                    const currentConfig = await (0, config_1.getConfig)();
                    const currentWorkerId = currentConfig.worker?.workerId;
                    if (currentWorkerId !== parsed.workerId) {
                        await (0, config_1.updateConfig)({
                            ...currentConfig,
                            worker: {
                                ...currentConfig.worker,
                                workerId: parsed.workerId,
                            },
                        });
                        console.log(`✅ Worker ID recorded: ${parsed.workerId}`);
                    }
                }
                if (parsed?.type === "reloadConfig") {
                    console.log("🔄 Received reloadConfig — reloading MCPs, modules and tools...");
                    try {
                        const freshConfig = await (0, config_1.getConfig)();
                        await Mcp.closeAll();
                        await Mcp.connectToConfigured(Tools);
                        const allowedToolNames = freshConfig.worker?.allowedTools ?? Tools.getToolNames();
                        toolsToUse = Tools.getToolsByNames(allowedToolNames);
                        mcpServer.withTools(toolsToUse);
                        console.log(`✅ Config reloaded: ${toolsToUse.length} tools active`);
                    }
                    catch (err) {
                        console.error("❌ Failed to reload config:", err);
                    }
                }
            }
            catch {
            }
        };
        ws.on("message", workerRegisteredHandler);
        let tunnelConnection = null;
        if (tunnelEnabled) {
            tunnelConnection = (0, tunnel_1.connectTunnelWebSocket)({
                tunnelDomain,
                tunnelUseHttps,
                tunnelLocalHost,
                portMapping,
                config,
                headers,
                authService,
                onOpen: (handler) => {
                    tunnelHandler = handler;
                },
                onClose: (code, _reason) => {
                    if (code === 1008) {
                        unauthorizedJwt = lastJwt;
                        console.error("❌ Tunnel received Unauthorized (1008). The JWT may be expired.");
                        console.error("   Pausing reconnection until JWT changes...");
                    }
                    else {
                        console.log("Tunnel connection will reconnect on next connection cycle...");
                    }
                    if (tunnelHandler) {
                        tunnelHandler.cleanup();
                        tunnelHandler = null;
                    }
                    tunnelWs = null;
                    connected = false;
                },
                onError: (_error) => {
                    connected = false;
                },
            });
            tunnelWs = tunnelConnection;
        }
        ws.on("open", () => {
            console.log("Worker WebSocket connected");
            connected = true;
        });
        ws.on("close", async (code, reason) => {
            console.log(`WebSocket closed. Code: ${code}, Reason: ${reason.toString()}`);
            if (tunnelHandler) {
                tunnelHandler = null;
            }
            connected = false;
            if (code === 1008) {
                unauthorizedJwt = lastJwt;
                console.error("❌ Worker received Unauthorized (1008). The JWT may be expired.");
                console.error("   Run 'knowhow login' to refresh your token, then restart the worker.");
                console.error("   Pausing reconnection until JWT changes...");
            }
            else {
                console.log("Attempting to reconnect...");
            }
        });
        ws.on("error", (error) => {
            console.error("WebSocket error:", error);
        });
        mcpServer.runWsServer(ws);
        return { ws, mcpServer, tunnelWs };
    }
    while (true) {
        let connection = null;
        if (!connected) {
            if (unauthorizedJwt !== null) {
                const currentJwt = await (0, login_1.loadJwt)().catch(() => null);
                if (currentJwt === unauthorizedJwt) {
                    await (0, utils_1.wait)(5000);
                    continue;
                }
                console.log("🔄 JWT has changed, attempting to reconnect...");
                unauthorizedJwt = null;
            }
            console.log("Attempting to connect...");
            connection = await connectWebSocket();
        }
        if (connection && connected) {
            try {
                await connection.ws.ping();
            }
            catch (error) {
                console.error("WebSocket ping failed:", error);
                connected = false;
            }
        }
        await (0, utils_1.wait)(5000);
    }
}
//# sourceMappingURL=worker.js.map