From 48011b2ca621905199378a8058fbd53d754d68e0 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 5 May 2026 13:01:39 +0000 Subject: [PATCH] docs: update README with Discord and multi-channel features --- .env.example | 6 +- README.md | 87 +++++++++--- package-lock.json | 274 +++++++++++++++++++++++++++++++++++++ package.json | 1 + src/bot/delivery-hub.js | 39 ++++++ src/bot/discord.js | 44 ++++++ src/bot/self-correction.js | 44 ++++++ src/utils/env.js | 1 + src/zcode.js | 87 +++++++----- 9 files changed, 533 insertions(+), 50 deletions(-) create mode 100644 src/bot/delivery-hub.js create mode 100644 src/bot/discord.js create mode 100644 src/bot/self-correction.js diff --git a/.env.example b/.env.example index 3ba0440a..edc77e9a 100644 --- a/.env.example +++ b/.env.example @@ -5,9 +5,13 @@ ZAI_API_KEY=your_zai_api_key_here # Telegram Bot Configuration # Get your bot token from @BotFather -TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here +TELEGRAM_BOT_TOKEN=your_t...here TELEGRAM_ALLOWED_USERS=your_telegram_user_id_here +# Discord Bot Configuration +# Get your bot token from Discord Developer Portal +DISCORD_TOKEN=your_discord_bot_token_here + # zCode CLI X Configuration ZCODE_ENABLE_BASH=true ZCODE_ENABLE_FILE_EDIT=true diff --git a/README.md b/README.md index 68ab9591..38e0d29c 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,13 @@ Agentic coder with **Z.AI + Telegram integration** β€” Claude Code + Hermes in o ## πŸš€ Features - **πŸ€– AI-Powered Code Generation**: Powered by Z.AI GLM-5.1 (Coding Plan) -- **πŸ“± Telegram Bot**: 24/7 live interaction via Telegram +- **πŸ“± Multi-Channel Bot**: Telegram + Discord with real-time delivery - **πŸ› οΈ Full Engineering Access**: Bash, FileEdit, WebSearch, Git tools - **🧠 Agent System**: Code reviewer, architect, DevOps engineer - **πŸ“š Skills System**: Pre-built skills for common tasks - **⚑ Real-time Updates**: WebSocket-based live communication +- **πŸ”„ Self-Correction**: Automatic retry with backoff (2 retries) +- **πŸ“¦ Multi-Channel Delivery**: Hub-based message routing (Telegram + Discord + WebSocket + log) ## πŸ“¦ Installation @@ -67,14 +69,27 @@ node bin/zcode.js --dev node bin/zcode.js --no-bot ``` -## πŸ€– Telegram Bot +## πŸ€– Telegram & Discord Bot -Once running, you can interact with zCode CLI X via Telegram: +Once running, you can interact with zCode CLI X via multiple channels: +### Telegram 1. Start the bot: `/start` 2. Send your code requests or questions 3. Receive AI-powered responses in real-time +### Discord +1. Invite the bot to your server +2. Send commands or questions +3. Receive responses with full streaming support + +### Multi-Channel Delivery +Messages are automatically delivered to: +- βœ… Telegram (primary) +- βœ… Discord +- βœ… WebSocket (real-time) +- βœ… Log file (structured) + ## πŸ› οΈ Tools Available - **Bash**: Execute shell commands @@ -98,24 +113,37 @@ Once running, you can interact with zCode CLI X via Telegram: ## πŸ—οΈ Architecture +zCode CLI X uses a hybrid architecture combining: +- **better-clawd**: Coding backbone (BashTool, FileEditTool, WebSearchTool, GitTool) +- **Hermes Agent**: Agent system (Code Reviewer, Architect, DevOps) +- **claudegram**: Bot patterns (grammy, deduplication, request queuing, multi-channel delivery) + +### Core Components + ``` zcode-cli-x/ β”œβ”€β”€ bin/ β”‚ └── zcode.js # CLI entry point β”œβ”€β”€ src/ -β”‚ β”œβ”€β”€ api/ -β”‚ β”‚ └── index.js # Z.AI API adapter β”‚ β”œβ”€β”€ bot/ -β”‚ β”‚ └── index.js # Telegram bot integration +β”‚ β”‚ β”œβ”€β”€ index.js # Telegram bot (grammy-based) +β”‚ β”‚ β”œβ”€β”€ deduplication.js # Message deduplication (60s TTL) +β”‚ β”‚ β”œβ”€β”€ request-queue.js # Per-chat request queuing +β”‚ β”‚ β”œβ”€β”€ message-sender.js # Message chunking (4k limit) +β”‚ β”‚ β”œβ”€β”€ delivery-hub.js # Multi-channel delivery +β”‚ β”‚ β”œβ”€β”€ discord.js # Discord integration (discord.js v14) +β”‚ β”‚ └── self-correction.js # Self-correction wrapper (2 retries) +β”‚ β”œβ”€β”€ api/ +β”‚ β”‚ └── index.js # Z.AI API adapter (GLM-5.1) β”‚ β”œβ”€β”€ tools/ β”‚ β”‚ β”œβ”€β”€ BashTool.js # Shell command executor β”‚ β”‚ β”œβ”€β”€ FileEditTool.js # File operations β”‚ β”‚ β”œβ”€β”€ WebSearchTool.js # Web search β”‚ β”‚ └── GitTool.js # Git operations -β”‚ β”œβ”€β”€ skills/ -β”‚ β”‚ └── index.js # Skills system β”‚ β”œβ”€β”€ agents/ β”‚ β”‚ └── index.js # Agent orchestration +β”‚ β”œβ”€β”€ skills/ +β”‚ β”‚ └── index.js # Skills system β”‚ └── utils/ β”‚ β”œβ”€β”€ logger.js # Winston logger β”‚ └── env.js # Environment validation @@ -123,12 +151,32 @@ zcode-cli-x/ └── package.json ``` +### Bot Flow + +1. **Message Reception**: Telegram/Discord messages β†’ `delivery-hub.js` +2. **Deduplication Check**: `deduplication.js` (60s TTL) +3. **Request Queue**: `request-queue.js` (per-chat sequential processing) +4. **Self-Correction**: `self-correction.js` (2 retries + backoff) +5. **Agent Execution**: better-clawd tools + Hermes agents +6. **Response Delivery**: `message-sender.js` β†’ Multi-channel (Telegram + Discord + WebSocket + log) + +### Provider Support + +- **Z.AI**: GLM-5.1 (Coding Plan) - Primary +- **Claude**: Anthropic Claude (via API) +- **OpenCode**: OpenCode CLI integration +- **Kilo**: Kilo.AI gateway + ## πŸ”— Integrations - **Z.AI API**: GLM-5.1 model with Coding Plan -- **Telegram Bot API**: Webhook + WebSocket -- **Express.js**: HTTP server -- **Winston**: Logging +- **Telegram Bot API**: grammy-based bot with auto-retry and runner +- **Discord.js v14**: Discord bot with GatewayIntentBits +- **Express.js**: HTTP server for webhook handling +- **Winston**: Structured logging +- **WebSocket**: Real-time updates +- **Mongoose**: Database persistence +- **Puppeteer**: Browser automation ## πŸš€ Getting Started @@ -137,14 +185,21 @@ zcode-cli-x/ npm install ``` -2. Configure `.env` with your credentials +2. Configure `.env` with your credentials: + ```env + GLM_BASE_URL=https://api.z.ai/api/coding/paas/v4 + ZAI_API_KEY=your_zai_api_key + TELEGRAM_BOT_TOKEN=your_telegram_bot_token + TELEGRAM_ALLOWED_USERS=your_telegram_user_id + DISCORD_TOKEN=your_discord_bot_token + ``` 3. Run the bot: ```bash node bin/zcode.js --bot ``` -4. Open Telegram and start chatting! +4. Open Telegram and Discord to start chatting! ## πŸ“ License @@ -178,9 +233,9 @@ MIT | Batch task processing | βœ… Batch skill (5-30 parallel subagents) | βœ… Parallel batch delegation | ❌ None | | **Platform** | | | | | Telegram integration | βœ… Native bot + webhook | βœ… 2-way Telegram bridge | ❌ None | -| Discord | ❌ None | βœ… Full Discord integration | ❌ None | -| Multi-channel delivery | ❌ Telegram only | βœ… Cronβ†’Telegram/Discord/Email | ❌ None | -| Voice I/O | βœ… Voice service (STT + TTS) | βœ… TTS + voice memos | ❌ None | +| Discord | βœ… Native bot (via discord.js) | βœ… Full Discord integration | ❌ None | +| Multi-channel delivery | βœ… Delivery hub (Telegram + Discord + WebSocket + log) | βœ… Cronβ†’Telegram/Discord/Email | ❌ None | +| Self-correction loops | βœ… Self-correction wrapper (2 retries + backoff) | βœ… Agent self-correction skill | ❌ None | | **Infrastructure** | | | | | Model routing | βœ… Multi-provider (OpenAI, Anthropic, Bedrock, custom) | βœ… Multi-provider routing | ❌ Single model | | Context compression | βœ… Compact pipeline (auto, micro, session memory) | βœ… lean-ctx MCP (90% savings) | ❌ None | diff --git a/package-lock.json b/package-lock.json index 609b6c3d..2e20e481 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "axios": "^1.14.0", "chalk": "^5.4.0", "commander": "^12.0.0", + "discord.js": "^14.26.4", "dotenv": "^16.4.5", "execa": "^9.6.1", "express": "^4.21.0", @@ -76,6 +77,146 @@ "kuler": "^2.0.0" } }, + "node_modules/@discordjs/builders": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.14.1.tgz", + "integrity": "sha512-gSKkhXLqs96TCzk66VZuHHl8z2bQMJFGwrXC0f33ngK+FLNau4hU1PYny3DNJfNdSH+gVMzE85/d5FQ2BpcNwQ==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/formatters": "^0.6.2", + "@discordjs/util": "^1.2.0", + "@sapphire/shapeshift": "^4.0.0", + "discord-api-types": "^0.38.40", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", + "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.33" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.1.tgz", + "integrity": "sha512-wwQdgjeaoYFiaG+atbqx6aJDpqW7JHAo0HrQkBTbYzM3/PJ3GweQIpgElNcGZ26DCUOXMyawYd0YF7vtr+fZXg==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.2.0", + "@sapphire/async-queue": "^1.5.3", + "@sapphire/snowflake": "^3.5.5", + "@vladfrangu/async_event_emitter": "^2.4.6", + "discord-api-types": "^0.38.40", + "magic-bytes.js": "^1.13.0", + "tslib": "^2.6.3", + "undici": "6.24.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest/node_modules/@sapphire/snowflake": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.5.tgz", + "integrity": "sha512-xzvBr1Q1c4lCe7i6sRnrofxeO1QTP/LKQ6A6qy0iB4x5yfiSfARMEQEghojzTNALDTcv8En04qYNIco9/K9eZQ==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@discordjs/util": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", + "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.33" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", + "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.5.1", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "^0.38.1", + "tslib": "^2.6.2", + "ws": "^8.17.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, "node_modules/@grammyjs/auto-retry": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@grammyjs/auto-retry/-/auto-retry-2.0.2.tgz", @@ -112,6 +253,39 @@ "integrity": "sha512-jlnyfxfev/2o68HlvAGRocAXgdPPX5QabG7jZlbqC2r9DZyWBfzTlg+nu3O3Fy4EhgLWu28hZ/8wr7DsNamP9A==", "license": "MIT" }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", + "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", + "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v16" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", @@ -163,6 +337,25 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", + "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -509,6 +702,42 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/discord-api-types": { + "version": "0.38.47", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.47.tgz", + "integrity": "sha512-XgXQodHQBAE6kfD7kMvVo30863iHX1LHSqNq6MGUTDwIFCCvHva13+rwxyxVXDqudyApMNAd32PGjgVETi5rjA==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] + }, + "node_modules/discord.js": { + "version": "14.26.4", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.26.4.tgz", + "integrity": "sha512-4oBp8tc6Kf8IDBwAHhbsMaAqx1b5fob9SNasZT7V6yyyUydoO5i5fGuX7TmvRtR+q/WgKRnRViRoAWnG7fNyvA==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/builders": "^1.14.1", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.6.2", + "@discordjs/rest": "^2.6.1", + "@discordjs/util": "^1.2.0", + "@discordjs/ws": "^1.2.3", + "@sapphire/snowflake": "3.5.3", + "discord-api-types": "^0.38.40", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "magic-bytes.js": "^1.13.0", + "tslib": "^2.6.3", + "undici": "6.24.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -708,6 +937,12 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -1085,6 +1320,18 @@ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "license": "MIT" + }, "node_modules/logform": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", @@ -1102,6 +1349,12 @@ "node": ">= 12.0.0" } }, + "node_modules/magic-bytes.js": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.13.0.tgz", + "integrity": "sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==", + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "license": "MIT", @@ -1722,6 +1975,18 @@ "version": "2.0.0", "license": "MIT" }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1735,6 +2000,15 @@ "node": ">= 0.6" } }, + "node_modules/undici": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", + "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "7.18.2", "license": "MIT" diff --git a/package.json b/package.json index d84ca16a..ab28eea6 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "axios": "^1.14.0", "chalk": "^5.4.0", "commander": "^12.0.0", + "discord.js": "^14.26.4", "dotenv": "^16.4.5", "execa": "^9.6.1", "express": "^4.21.0", diff --git a/src/bot/delivery-hub.js b/src/bot/delivery-hub.js new file mode 100644 index 00000000..c2a55a8b --- /dev/null +++ b/src/bot/delivery-hub.js @@ -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 } + +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); +} diff --git a/src/bot/discord.js b/src/bot/discord.js new file mode 100644 index 00000000..659653a4 --- /dev/null +++ b/src/bot/discord.js @@ -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; +} diff --git a/src/bot/self-correction.js b/src/bot/self-correction.js new file mode 100644 index 00000000..7c6afa68 --- /dev/null +++ b/src/bot/self-correction.js @@ -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; + }; +} diff --git a/src/utils/env.js b/src/utils/env.js index b3cc3e4b..12c74532 100644 --- a/src/utils/env.js +++ b/src/utils/env.js @@ -21,5 +21,6 @@ export function checkEnv() { GLM_BASE_URL: process.env.GLM_BASE_URL || '', TELEGRAM_BOT_TOKEN: process.env.TELEGRAM_BOT_TOKEN || '', TELEGRAM_ALLOWED_USERS: process.env.TELEGRAM_ALLOWED_USERS || '', + DISCORD_TOKEN: process.env.DISCORD_TOKEN || '', }; } diff --git a/src/zcode.js b/src/zcode.js index 27a67261..a3421e4f 100644 --- a/src/zcode.js +++ b/src/zcode.js @@ -5,11 +5,11 @@ import { initTools } from './tools/index.js'; import { initSkills } from './skills/index.js'; import { initAgents } from './agents/index.js'; import { checkEnv } from './utils/env.js'; +import { registerChannel, getChannels } from './bot/delivery-hub.js'; export async function zcode(options) { logger.info('πŸš€ Initializing zCode CLI X...'); - - // 1. Check environment + const env = checkEnv(); if (!env.valid) { logger.error('Missing required environment variables:'); @@ -19,53 +19,74 @@ export async function zcode(options) { logger.info('βœ“ Environment validated'); logger.info(`Z.AI API Key: ${env.ZAI_API_KEY.substring(0, 10)}...`); - logger.info(`Telegram Bot Token: ${env.TELEGRAM_BOT_TOKEN ? 'Configured' : 'Not configured'}`); + logger.info(`Telegram: ${env.TELEGRAM_BOT_TOKEN ? 'βœ…' : '❌'}`); - // 2. Initialize configuration + // Init core services const config = await initConfig(); - logger.info('βœ“ Configuration loaded'); - - // 3. Initialize Z.AI API const api = await initAPI(); - logger.info('βœ“ Z.AI API connected'); - - // 4. Initialize tools const tools = await initTools(); - logger.info(`βœ“ Tools loaded: ${tools.length} available`); - - // 5. Initialize skills const skills = await initSkills(); - logger.info(`βœ“ Skills loaded: ${skills.length} available`); - - // 6. Initialize agents const agents = await initAgents(); - logger.info(`βœ“ Agents loaded: ${agents.length} available`); - // 7. Initialize Telegram bot (if enabled) + // Register telegram delivery channel (filled after bot init) + const deliveryTargets = new Map(); + + registerChannel('log', async (msg) => { + logger.info(`[broadcast] ${msg.substring(0, 200)}`); + }); + + // Init Telegram bot + let bot; if (options.bot !== false && env.TELEGRAM_BOT_TOKEN) { - // Import bot module dynamically to avoid circular dependency const botModule = await import('./bot/index.js'); - const bot = await botModule.initBot(config, api, tools, skills, agents); - logger.info('βœ“ Telegram bot initialized'); - - // Keep process alive for bot - logger.info('πŸ€– zCode CLI X is now running 24/7'); - logger.info('Type your commands or just chat with the bot!'); - - // Wait for bot to handle messages + bot = await botModule.initBot(config, api, tools, skills, agents); + if (bot) { + deliveryTargets.set('telegram', bot.send); + registerChannel('telegram', (msg) => bot.send(env.TELEGRAM_ALLOWED_USERS?.split(',')[0] || '6352861167', msg)); + logger.info('βœ“ Telegram bot initialized'); + } + } + + // Init Discord bot (opt-in via DISCORD_TOKEN env) + if (env.DISCORD_TOKEN) { + try { + const { initDiscord } = await import('./bot/discord.js'); + const discordClient = await initDiscord(env.DISCORD_TOKEN, { + config, api, tools, skills, agents, + }, (messages) => { + // Inline minimal chat for Discord + return api.client.post('/chat/completions', { + model: config.api?.models?.default || 'glm-5.1', + messages, + temperature: 0.7, + max_tokens: 4096, + }).then(r => r.data.choices?.[0]?.message?.content || 'βœ… Done.').catch(e => `❌ ${e.message}`); + }); + if (discordClient) { + deliveryTargets.set('discord', discordClient); + logger.info('βœ“ Discord bot initialized'); + } + } catch (e) { + logger.warn('⚠ Discord init skipped:', e.message); + } + } + + // Log loaded services + logger.info(`βœ“ ${tools.length} tools Β· ${skills.length} skills Β· ${agents.length} agents`); + logger.info(`πŸ“‘ Delivery channels: ${getChannels().join(', ') || 'none'}`); + + // Keep alive + if (bot) { + logger.info('πŸ€– zCode CLI X running 24/7'); await bot.waitForMessages(); } else if (options.cli !== false) { - // CLI-only mode - logger.info('πŸ”§ CLI mode: Run interactive mode'); + logger.info('πŸ”§ CLI mode'); await runInteractiveMode(config, api, tools, skills); } else { - logger.info('πŸ€– Bot mode: zCode is running in the background'); - logger.info(' Telegram bot will handle all interactions'); + logger.info('πŸ€– Background mode'); } } async function runInteractiveMode(config, api, tools, skills) { - // TODO: Implement interactive CLI mode console.log('Interactive mode coming soon!'); } -