eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1// Sidecar boot: one process hosts the instance's default agents, each a normal
2// E2EE member with its own identity on the /data disk (see runtime.mjs for the
3// per-bot chat loop):
4//
5// Claude the plain assistant — replies in DMs, on @mention in groups
6// (LCHAT_SESSION / LCHAT_INVITE; name via LCHAT_AGENT_NAME)
7// Eleven the app guide — same chat loop with a shipped-in persona, plus a
8// welcome DM to each new member (LCHAT_ELEVEN_SESSION / _INVITE)
9//
10// Each identity is optional: it runs if its session exists or its invite is set,
11// so an instance can host any subset. (A third identity, the write-only
12// Announcer, is retired: the server announces its own changelog now — a push
13// to admins + the Admin app's update stream.)
14import fs from "node:fs";
15import http from "node:http";
16import { BASE, AGENT_NAME, requireBase, loadSession } from "./config.mjs";
17import { provision } from "./provision.mjs";
18import { runBot } from "./runtime.mjs";
19import { CHIP_TOOLS } from "./actions.mjs";
20import { ELEVEN_NAME, ELEVEN_PERSONA, ELEVEN_DOCS, loadElevenSession } from "./eleven.mjs";
21
22// Synchronous logging: async stdout to a pipe is buffered and lost if the
23// container is killed (e.g. a failed health check), so write key lines with
24// fs.writeSync so they always reach the logs.
25const log = (m) => {
26 try {
27 fs.writeSync(1, `${m}\n`);
28 } catch {}
29};
30process.on("uncaughtException", (e) => {
31 log(`uncaughtException: ${e?.stack || e}`);
32 process.exit(1);
33});
34process.on("unhandledRejection", (e) => {
35 log(`unhandledRejection: ${e?.stack || e}`);
36 process.exit(1);
37});
38log(`boot: sidecar starting, base=${BASE}`);
39
40requireBase();
41
42// Optional health endpoint for container platforms that health-check over HTTP
43// (e.g. Miren). Only bind it when PORT is explicitly set: the sidecar is a pure
44// outbound WebSocket client, so under systemd nothing health-checks or routes to
45// it — it needs no inbound listener, and binding a fixed port would collide when
46// several sidecars share a host.
47if (process.env.PORT) {
48 const PORT = Number(process.env.PORT);
49 http
50 .createServer((_req, res) => {
51 res.writeHead(200, { "content-type": "text/plain" });
52 res.end("ok\n");
53 })
54 .listen(PORT, "0.0.0.0", () => log(`health listening on :${PORT}`));
55}
56
57// --- Claude: the plain assistant (the original sidecar bot) ----------------
58let claudeSession = loadSession();
59if (!claudeSession && process.env.LCHAT_INVITE) {
60 log("no session; provisioning from LCHAT_INVITE…");
61 try {
62 claudeSession = await provision(process.env.LCHAT_INVITE);
63 log(`provisioned "${claudeSession.name}"`);
64 } catch (e) {
65 log(`provision failed: ${e?.message || e}`);
66 process.exit(1);
67 }
68}
69if (claudeSession) runBot({ name: AGENT_NAME, session: claudeSession });
70
71// --- Eleven: the app guide + welcomer ---------------------------------------
72let elevenSession = null;
73try {
74 elevenSession = await loadElevenSession();
75} catch (e) {
76 log(`eleven provision failed: ${e?.message || e}`);
77 process.exit(1);
78}
79if (elevenSession) {
80 // Eleven runs a deterministic command floor (welcome + slash commands) that
81 // works with no API key, and "upgrades" to conversational AI when one is set —
82 // its badge (BOT vs AI) follows, self-declared on connect. See runtime.mjs.
83 runBot({
84 name: ELEVEN_NAME,
85 session: elevenSession,
86 builtinPersona: ELEVEN_PERSONA,
87 builtinDocs: ELEVEN_DOCS,
88 tools: CHIP_TOOLS,
89 commands: true,
90 });
91 // The welcomer is STOOD DOWN: the client-side welcome conversation
92 // (web/static/apps/welcome.js) greets every joiner now — one voice, not two
93 // Elevens saying hello. startWelcomer() in eleven.mjs stays for standalone
94 // runs; re-wire it here only if the client welcome ever retires.
95}
96
97if (!claudeSession && !elevenSession) {
98 log("no chat identities — set LCHAT_INVITE and/or LCHAT_ELEVEN_INVITE (or provide sessions)");
99 process.exit(1);
100}
101