// src/gadget-drone.ts // Copyright (C) 2026 Rob Colbert // Licensed under the Apache License, Version 2.0 import env from "./config/env.ts"; import assert from "node:assert"; import { io, ManagerOptions, SocketOptions, Socket } from "socket.io-client"; import AgentService, { IAgentWorkOrder } from "./services/agent.ts"; import AiService from "./services/ai.ts"; import PlatformService, { DroneStatus, PlatformRegistration, } from "./services/platform.ts"; import { GadgetProcess } from "./lib/process.ts"; class GadgetDrone extends GadgetProcess { private registration: PlatformRegistration | undefined; private socket: Socket | undefined; private isShuttingDown: boolean = false; get name(): string { return "GadgetDrone"; } get slug(): string { return "gadget-drone"; } constructor() { super(); } async start(): Promise { /* * Initialize the system */ this.hookProcessSignals(); await this.startServices(); /* * Register this Drone with the Gadget Code web services platform. */ const email = "rob@digitaltelepresence.com"; const password = "ionfrali"; this.registration = await PlatformService.register(email, password); this.log.info("registered with platform", { registration: this.registration, }); /* * Connect to the Gadget Code web services platform and configure the real- * time messaging system on Socket.IO. */ await this.connectSocket(); /* * Mark this Drone as available and ready to accept work orders. */ await PlatformService.setStatus(DroneStatus.Available); this.log.info(`Gadget Drone v${env.pkg.version} started`); } async stop(): Promise { this.log.info(`Gadget Drone v${env.pkg.version} shutting down`); if (this.socket) { this.socket.disconnect(); delete this.socket; } await PlatformService.unregister(); await this.stopServices(); return 0; } async startServices(): Promise { this.log.info("starting services"); await AgentService.start(); await AiService.start(); await PlatformService.start(); this.log.info("services started"); } async stopServices(): Promise { this.log.info("stopping services"); await AgentService.stop(); await AiService.stop(); await PlatformService.stop(); this.log.info("services stopped"); } async connectSocket(): Promise { return new Promise((resolve, reject) => { assert(this.registration, "must be registered with Gadget Code platform"); const options: Partial = { auth: { token: this.registration._id }, reconnectionAttempts: 10, timeout: 5000, transports: ["websocket"], }; /* * Allow self-signed certs in non-production environments */ if (env.NODE_ENV !== "production") { options.rejectUnauthorized = false; } this.log.debug("connecting to Gadget Code platform..."); this.socket = io("https://code-dev.g4dge7.com:5174/", options); this.socket.on("connect_error", (err) => { this.log.error("socket connect error", { err }); reject(err); }); this.socket.on("connect", () => { this.log.info("connected to Gadget Code platform."); resolve(); }); }); } hookProcessSignals(): void { process.title = this.name; process.on("unhandledRejection", async (error: Error, p) => { this.log.error("Unhandled rejection", { error, promise: p, stack: error.stack, }); const exitCode = await this.stop(); process.exit(exitCode); }); process.on("warning", (error) => { if (error.name === "DeprecationWarning") return; this.log.alert("warning", { error }); }); process.on("SIGINT", async () => { this.log.info("SIGINT received"); if (this.isShuttingDown) return; this.log.info("requesting shutdown"); const exitCode = await this.stop(); process.exit(exitCode); }); } } (async () => { try { const drone = new GadgetDrone(); await drone.start(); } catch (error) { console.error("failed to start gadget-drone", error); process.exit(-1); } })();