docs: update README with Discord and multi-channel features

This commit is contained in:
admin
2026-05-05 13:01:39 +00:00
Unverified
parent cbe816a421
commit 48011b2ca6
9 changed files with 533 additions and 50 deletions

39
src/bot/delivery-hub.js Normal file
View File

@@ -0,0 +1,39 @@
// Delivery hub — send to multiple channels from one call
import { logger } from '../utils/logger.js';
const channels = new Map(); // name -> { send: (msg) => Promise<void> }
export function registerChannel(name, sendFn) {
channels.set(name, sendFn);
logger.info(`📡 Channel registered: ${name}`);
}
export function unregisterChannel(name) {
channels.delete(name);
logger.info(`📡 Channel unregistered: ${name}`);
}
export function getChannels() {
return Array.from(channels.keys());
}
export async function broadcast(message, opts = {}, except = []) {
const results = [];
for (const [name, sendFn] of channels) {
if (except.includes(name)) continue;
try {
await sendFn(message);
results.push({ channel: name, ok: true });
} catch (e) {
logger.error(`Broadcast to ${name} failed:`, e.message);
results.push({ channel: name, ok: false, error: e.message });
}
}
return results;
}
export async function sendTo(channel, message) {
const sendFn = channels.get(channel);
if (!sendFn) throw new Error(`Channel not found: ${channel}`);
await sendFn(message);
}

44
src/bot/discord.js Normal file
View File

@@ -0,0 +1,44 @@
// Discord bot — minimal, fast. Reuses svc registry from bot/index.js
import { Client, GatewayIntentBits, Partials } from 'discord.js';
import { logger } from '../utils/logger.js';
import { registerChannel } from './delivery-hub.js';
const INTENTS = GatewayIntentBits.GuildMessages | GatewayIntentBits.MessageContent |
GatewayIntentBits.DirectMessages;
const PARTIALS = [Partials.Channel];
export async function initDiscord(token, svc, chatWithAI) {
if (!token) { logger.warn('⚠ Discord token not set'); return null; }
const client = new Client({ intents: INTENTS, partials: PARTIALS,
makeCache: (manager) => ['UserManager', 'ChannelManager'].includes(manager.constructor.name) ? manager : false,
});
client.once('ready', () => {
logger.info('✅ Discord bot connected');
registerChannel('discord', async (msg) => {
// broadcast to first available guild channel
const guild = client.guilds.cache.first();
if (!guild) return;
const ch = guild.systemChannel || guild.channels.cache.find(c => c.type === 0);
if (ch) await ch.send(msg.slice(0, 1900));
});
});
client.on('messageCreate', async (msg) => {
if (msg.author.bot) return;
// Ignore commands directed at other bots
if (msg.mentions.has(client.user) || msg.channel.type === 1) {
const text = msg.content.replace(/<@!?\d+>/g, '').trim() || 'hello';
await msg.channel.sendTyping();
const result = await chatWithAI([
{ role: 'system', content: `You are zCode CLI X on Discord. Be concise.` },
{ role: 'user', content: text },
]);
await msg.reply(typeof result === 'string' ? result.slice(0, 1900) : result);
}
});
await client.login(token);
return client;
}

View File

@@ -0,0 +1,44 @@
// Self-correction loop — retry on failure with backoff & simplified approach
import { logger } from '../utils/logger.js';
const MAX_RETRIES = 2;
const RETRY_DELAY_MS = 500;
function shouldRetry(content) {
if (!content) return true;
const lowered = content.toLowerCase();
if (lowered.includes('❌') && lowered.includes('error')) return true;
if (lowered.includes('rate limit') || lowered.includes('timeout')) return true;
if (lowered.includes('internal server error') || lowered.includes('5xx')) return true;
return false;
}
export function withSelfCorrection(fn) {
return async (...args) => {
let lastError;
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
try {
const result = await fn(...args);
if (typeof result === 'string' && shouldRetry(result) && attempt < MAX_RETRIES) {
logger.warn(`Self-correct: retry ${attempt + 1}/${MAX_RETRIES} — error in response`);
await new Promise(r => setTimeout(r, RETRY_DELAY_MS * (attempt + 1)));
// Simplify the prompt on retry
const lastMsg = args[1]?.[args[1].length - 1];
if (lastMsg) lastMsg.content = `[SIMPLIFIED RETRY ${attempt + 1}] ${lastMsg.content.slice(0, 500)}`;
continue;
}
return result;
} catch (err) {
lastError = err;
if (attempt < MAX_RETRIES) {
logger.warn(`Self-correct: retry ${attempt + 1}/${MAX_RETRIES}${err.message}`);
await new Promise(r => setTimeout(r, RETRY_DELAY_MS * (attempt + 1)));
continue;
}
}
}
const msg = lastError ? `❌ Failed after ${MAX_RETRIES + 1} attempts: ${lastError.message}` : '❌ Failed after retries';
logger.error(msg);
return msg;
};
}