orchestrix-yuri 2.2.0 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/gateway/channels/telegram.js +21 -6
- package/lib/gateway/config.js +3 -2
- package/lib/gateway/engine/claude-tmux.js +12 -11
- package/lib/gateway/index.js +16 -13
- package/lib/gateway/log.js +42 -0
- package/lib/gateway/router.js +6 -5
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { Bot } = require('grammy');
|
|
4
|
+
const { log } = require('../log');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Telegram channel adapter using grammy (polling mode).
|
|
@@ -19,7 +20,7 @@ class TelegramAdapter {
|
|
|
19
20
|
|
|
20
21
|
async start() {
|
|
21
22
|
if (!this.token) {
|
|
22
|
-
throw new Error('Telegram bot token is required.
|
|
23
|
+
throw new Error('Telegram bot token is required. Run: orchestrix-yuri start --token YOUR_TOKEN');
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
this.bot = new Bot(this.token);
|
|
@@ -47,7 +48,7 @@ class TelegramAdapter {
|
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
} catch (err) {
|
|
50
|
-
|
|
51
|
+
log.error(`Message handling failed: ${err.message}`);
|
|
51
52
|
await ctx.reply('❌ Internal error. Check Yuri Gateway logs.').catch(() => {});
|
|
52
53
|
}
|
|
53
54
|
});
|
|
@@ -72,14 +73,28 @@ class TelegramAdapter {
|
|
|
72
73
|
|
|
73
74
|
// Error handler
|
|
74
75
|
this.bot.catch((err) => {
|
|
75
|
-
|
|
76
|
+
const msg = err.message || String(err);
|
|
77
|
+
if (msg.includes('409') || msg.includes('Conflict')) {
|
|
78
|
+
log.error('Polling conflict — another instance is using this token.');
|
|
79
|
+
log.info('This instance will exit. Stop the other process and restart.');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
log.warn(`Bot error: ${msg}`);
|
|
76
83
|
});
|
|
77
84
|
|
|
85
|
+
// Clear any stale webhook/polling before starting
|
|
86
|
+
log.telegram('Connecting...');
|
|
87
|
+
try {
|
|
88
|
+
await this.bot.api.deleteWebhook({ drop_pending_updates: true });
|
|
89
|
+
} catch {
|
|
90
|
+
// ignore — not critical
|
|
91
|
+
}
|
|
92
|
+
|
|
78
93
|
// Start polling
|
|
79
|
-
console.log('[telegram] Starting bot polling...');
|
|
80
94
|
await this.bot.start({
|
|
95
|
+
drop_pending_updates: true,
|
|
81
96
|
onStart: (botInfo) => {
|
|
82
|
-
|
|
97
|
+
log.telegram(`Bot @${botInfo.username} is live (polling mode)`);
|
|
83
98
|
},
|
|
84
99
|
});
|
|
85
100
|
}
|
|
@@ -87,7 +102,7 @@ class TelegramAdapter {
|
|
|
87
102
|
async stop() {
|
|
88
103
|
if (this.bot) {
|
|
89
104
|
await this.bot.stop();
|
|
90
|
-
|
|
105
|
+
log.telegram('Bot stopped');
|
|
91
106
|
}
|
|
92
107
|
}
|
|
93
108
|
}
|
package/lib/gateway/config.js
CHANGED
|
@@ -80,8 +80,9 @@ function applyCliOverrides(config, opts) {
|
|
|
80
80
|
// Persist token to channels.yaml so user only needs to pass it once
|
|
81
81
|
if (shouldSave) {
|
|
82
82
|
saveConfig(config);
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
const { log } = require('./log');
|
|
84
|
+
log.ok('Token saved to ~/.yuri/config/channels.yaml');
|
|
85
|
+
log.info('Next time just run: orchestrix-yuri start');
|
|
85
86
|
console.log('');
|
|
86
87
|
}
|
|
87
88
|
|
|
@@ -8,6 +8,7 @@ const crypto = require('crypto');
|
|
|
8
8
|
const yaml = require('js-yaml');
|
|
9
9
|
|
|
10
10
|
const YURI_GLOBAL = path.join(os.homedir(), '.yuri');
|
|
11
|
+
const { log } = require('../log');
|
|
11
12
|
|
|
12
13
|
// ── Shared Utilities (formerly in claude-cli.js) ───────────────────────────────
|
|
13
14
|
|
|
@@ -108,7 +109,7 @@ let _claudeBinary = null;
|
|
|
108
109
|
function getClaudeBinary() {
|
|
109
110
|
if (!_claudeBinary) {
|
|
110
111
|
_claudeBinary = findClaudeBinary();
|
|
111
|
-
|
|
112
|
+
log.tmux(`Using binary: ${_claudeBinary}`);
|
|
112
113
|
}
|
|
113
114
|
return _claudeBinary;
|
|
114
115
|
}
|
|
@@ -308,7 +309,7 @@ function ensureClaudeMd(projectRoot) {
|
|
|
308
309
|
// Append channel mode instructions
|
|
309
310
|
const separator = content.trim() ? '\n\n' : '';
|
|
310
311
|
fs.writeFileSync(claudeMdPath, content + separator + CHANNEL_MODE_INSTRUCTIONS + '\n');
|
|
311
|
-
|
|
312
|
+
log.tmux(`Channel Mode Instructions written to ${claudeMdPath}`);
|
|
312
313
|
}
|
|
313
314
|
|
|
314
315
|
/**
|
|
@@ -316,15 +317,15 @@ function ensureClaudeMd(projectRoot) {
|
|
|
316
317
|
* Returns true if compact completed successfully.
|
|
317
318
|
*/
|
|
318
319
|
async function proactiveCompact(name) {
|
|
319
|
-
|
|
320
|
+
log.tmux('Proactive /compact triggered');
|
|
320
321
|
injectMessage(name, '/compact focus on the most recent user conversation and any pending operations');
|
|
321
322
|
|
|
322
323
|
const ok = await waitForIdle(name, 120000); // compact can take up to 2min
|
|
323
324
|
if (ok) {
|
|
324
325
|
_messageCount = 0;
|
|
325
|
-
|
|
326
|
+
log.tmux('Proactive /compact completed');
|
|
326
327
|
} else {
|
|
327
|
-
|
|
328
|
+
log.warn('Proactive /compact timed out');
|
|
328
329
|
}
|
|
329
330
|
return ok;
|
|
330
331
|
}
|
|
@@ -378,7 +379,7 @@ async function createSession(engineConfig) {
|
|
|
378
379
|
}
|
|
379
380
|
|
|
380
381
|
_sessionReady = true;
|
|
381
|
-
|
|
382
|
+
log.tmux(`Session "${sessionName}" ready (cwd: ${projectRoot})`);
|
|
382
383
|
}
|
|
383
384
|
|
|
384
385
|
/**
|
|
@@ -450,7 +451,7 @@ async function captureResponse(name, marker, engineConfig) {
|
|
|
450
451
|
const poll = () => {
|
|
451
452
|
// Timeout: return whatever we have
|
|
452
453
|
if (Date.now() > deadline) {
|
|
453
|
-
|
|
454
|
+
log.warn('Response capture timed out');
|
|
454
455
|
const raw = capturePaneRaw(name, 500);
|
|
455
456
|
return resolve(extractResponse(raw, marker));
|
|
456
457
|
}
|
|
@@ -503,7 +504,7 @@ async function captureResponse(name, marker, engineConfig) {
|
|
|
503
504
|
}
|
|
504
505
|
|
|
505
506
|
if (stableCount >= stableThreshold && sawProcessing) {
|
|
506
|
-
|
|
507
|
+
log.tmux('Response detected via content stability');
|
|
507
508
|
return resolve(extractResponse(raw, marker));
|
|
508
509
|
}
|
|
509
510
|
|
|
@@ -575,7 +576,7 @@ async function ensureSession(engineConfig) {
|
|
|
575
576
|
await createSession(engineConfig);
|
|
576
577
|
return;
|
|
577
578
|
} catch (err) {
|
|
578
|
-
|
|
579
|
+
log.warn(`Session init attempt ${attempt}/${maxRetries} failed: ${err.message}`);
|
|
579
580
|
if (attempt === maxRetries) throw err;
|
|
580
581
|
// Brief pause before retry
|
|
581
582
|
await new Promise((r) => setTimeout(r, 3000));
|
|
@@ -627,7 +628,7 @@ async function callClaude(opts) {
|
|
|
627
628
|
_messageCount++;
|
|
628
629
|
resolve(result);
|
|
629
630
|
} catch (err) {
|
|
630
|
-
|
|
631
|
+
log.error(`callClaude error: ${err.message}`);
|
|
631
632
|
|
|
632
633
|
// Mark session as not ready so it gets recreated next time
|
|
633
634
|
_sessionReady = false;
|
|
@@ -656,7 +657,7 @@ function composePrompt(userMessage, _chatHistory) {
|
|
|
656
657
|
function destroySession() {
|
|
657
658
|
if (_sessionName && hasSession(_sessionName)) {
|
|
658
659
|
tmuxSafe(`kill-session -t ${_sessionName}`);
|
|
659
|
-
|
|
660
|
+
log.tmux(`Session "${_sessionName}" destroyed.`);
|
|
660
661
|
}
|
|
661
662
|
_sessionName = null;
|
|
662
663
|
_sessionReady = false;
|
package/lib/gateway/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { loadConfig, applyCliOverrides } = require('./config');
|
|
4
4
|
const { Router } = require('./router');
|
|
5
5
|
const { TelegramAdapter } = require('./channels/telegram');
|
|
6
|
+
const { log, c } = require('./log');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Start the Yuri Gateway.
|
|
@@ -12,9 +13,7 @@ const { TelegramAdapter } = require('./channels/telegram');
|
|
|
12
13
|
* @param {string} [opts.port] - Override server port
|
|
13
14
|
*/
|
|
14
15
|
async function startGateway(opts = {}) {
|
|
15
|
-
|
|
16
|
-
console.log(' 🚀 Yuri Gateway starting...');
|
|
17
|
-
console.log('');
|
|
16
|
+
log.banner('🚀 Yuri Gateway starting...');
|
|
18
17
|
|
|
19
18
|
// Load and merge config
|
|
20
19
|
let config = loadConfig();
|
|
@@ -38,37 +37,41 @@ async function startGateway(opts = {}) {
|
|
|
38
37
|
try {
|
|
39
38
|
await telegram.start();
|
|
40
39
|
} catch (err) {
|
|
41
|
-
|
|
40
|
+
if (err.message.includes('409') || err.message.includes('Conflict')) {
|
|
41
|
+
log.error('Another bot instance is already running with this token.');
|
|
42
|
+
log.info('Stop the other instance first, or wait a moment and retry.');
|
|
43
|
+
} else {
|
|
44
|
+
log.error(`Telegram failed to start: ${err.message}`);
|
|
45
|
+
}
|
|
42
46
|
process.exit(1);
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
// Start Feishu adapter if enabled (placeholder)
|
|
47
51
|
if (config.channels.feishu.enabled) {
|
|
48
|
-
|
|
52
|
+
log.warn('Feishu adapter not yet implemented. Skipping.');
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
// Check if any channel is active
|
|
52
56
|
if (adapters.length === 0) {
|
|
53
57
|
console.error('');
|
|
54
|
-
|
|
58
|
+
log.error('No channels enabled. Set up your Telegram bot token:');
|
|
55
59
|
console.error('');
|
|
56
|
-
console.error(
|
|
60
|
+
console.error(` ${c.cyan}orchestrix-yuri start --token YOUR_BOT_TOKEN${c.reset}`);
|
|
57
61
|
console.error('');
|
|
58
|
-
|
|
62
|
+
log.info('The token is saved automatically. After that, just run:');
|
|
59
63
|
console.error('');
|
|
60
|
-
console.error(
|
|
64
|
+
console.error(` ${c.cyan}orchestrix-yuri start${c.reset}`);
|
|
61
65
|
console.error('');
|
|
62
66
|
process.exit(1);
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
console.log(' Yuri Gateway is running. Press Ctrl+C to stop.');
|
|
67
|
-
console.log('');
|
|
69
|
+
log.banner('Yuri Gateway is running. Press Ctrl+C to stop.');
|
|
68
70
|
|
|
69
71
|
// Graceful shutdown
|
|
70
72
|
const shutdown = async () => {
|
|
71
|
-
console.log('
|
|
73
|
+
console.log('');
|
|
74
|
+
log.info('Shutting down Yuri Gateway...');
|
|
72
75
|
await router.shutdown();
|
|
73
76
|
for (const adapter of adapters) {
|
|
74
77
|
await adapter.stop().catch(() => {});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// ── ANSI Color Helpers ─────────────────────────────────────────────────────────
|
|
4
|
+
// No dependencies — raw escape codes for terminal coloring.
|
|
5
|
+
|
|
6
|
+
const c = {
|
|
7
|
+
reset: '\x1b[0m',
|
|
8
|
+
bold: '\x1b[1m',
|
|
9
|
+
dim: '\x1b[2m',
|
|
10
|
+
red: '\x1b[31m',
|
|
11
|
+
green: '\x1b[32m',
|
|
12
|
+
yellow: '\x1b[33m',
|
|
13
|
+
blue: '\x1b[34m',
|
|
14
|
+
magenta: '\x1b[35m',
|
|
15
|
+
cyan: '\x1b[36m',
|
|
16
|
+
white: '\x1b[37m',
|
|
17
|
+
gray: '\x1b[90m',
|
|
18
|
+
bgRed: '\x1b[41m',
|
|
19
|
+
bgGreen: '\x1b[42m',
|
|
20
|
+
bgBlue: '\x1b[44m',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const tag = (color, label) => `${color}${c.bold}[${label}]${c.reset}`;
|
|
24
|
+
|
|
25
|
+
const log = {
|
|
26
|
+
// Tagged module loggers
|
|
27
|
+
gateway: (msg) => console.log(`${tag(c.magenta, 'gateway')} ${msg}`),
|
|
28
|
+
router: (msg) => console.log(`${tag(c.blue, 'router')} ${msg}`),
|
|
29
|
+
tmux: (msg) => console.log(`${tag(c.cyan, 'tmux')} ${msg}`),
|
|
30
|
+
telegram:(msg) => console.log(`${tag(c.green, 'telegram')} ${msg}`),
|
|
31
|
+
|
|
32
|
+
// Levels
|
|
33
|
+
ok: (msg) => console.log(` ${c.green}✅ ${msg}${c.reset}`),
|
|
34
|
+
warn: (msg) => console.warn(` ${c.yellow}⚠️ ${msg}${c.reset}`),
|
|
35
|
+
error: (msg) => console.error(` ${c.red}❌ ${msg}${c.reset}`),
|
|
36
|
+
info: (msg) => console.log(` ${c.dim}${msg}${c.reset}`),
|
|
37
|
+
|
|
38
|
+
// Banner
|
|
39
|
+
banner: (msg) => console.log(`\n ${c.bold}${c.magenta}${msg}${c.reset}\n`),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
module.exports = { log, c };
|
package/lib/gateway/router.js
CHANGED
|
@@ -8,6 +8,7 @@ const yaml = require('js-yaml');
|
|
|
8
8
|
const { ChatHistory } = require('./history');
|
|
9
9
|
const { OwnerBinding } = require('./binding');
|
|
10
10
|
const engine = require('./engine/claude-tmux');
|
|
11
|
+
const { log } = require('./log');
|
|
11
12
|
|
|
12
13
|
const YURI_GLOBAL = path.join(os.homedir(), '.yuri');
|
|
13
14
|
|
|
@@ -52,7 +53,7 @@ class Router {
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
if (authResult.firstBind) {
|
|
55
|
-
|
|
56
|
+
log.router(`First bind: ${msg.channelType} chat ${msg.chatId} (${msg.userName})`);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
// Handle /start command
|
|
@@ -94,7 +95,7 @@ class Router {
|
|
|
94
95
|
const prompt = engine.composePrompt(msg.text, chatHistory);
|
|
95
96
|
|
|
96
97
|
// ═══ WORK: Call Claude engine ═══
|
|
97
|
-
|
|
98
|
+
log.router(`Processing: "${msg.text.slice(0, 80)}..." → cwd: ${projectRoot || '~'}`);
|
|
98
99
|
const result = await engine.callClaude({
|
|
99
100
|
prompt,
|
|
100
101
|
cwd: projectRoot,
|
|
@@ -114,7 +115,7 @@ class Router {
|
|
|
114
115
|
// ═══ ENGINE: Update Focus (code-enforced) ═══
|
|
115
116
|
this._updateGlobalFocus(msg, projectRoot);
|
|
116
117
|
|
|
117
|
-
|
|
118
|
+
log.router(`Reply: "${result.reply.slice(0, 80)}..."`);
|
|
118
119
|
return { text: result.reply };
|
|
119
120
|
}
|
|
120
121
|
|
|
@@ -132,7 +133,7 @@ class Router {
|
|
|
132
133
|
const ONE_HOUR = 3600_000;
|
|
133
134
|
|
|
134
135
|
if (gap > ONE_HOUR) {
|
|
135
|
-
|
|
136
|
+
log.router(`Catch-up: ${Math.round(gap / 60000)}min since last active. Refreshing portfolio.`);
|
|
136
137
|
this._refreshPortfolioPulse();
|
|
137
138
|
}
|
|
138
139
|
}
|
|
@@ -215,7 +216,7 @@ class Router {
|
|
|
215
216
|
focus.updated_at = new Date().toISOString();
|
|
216
217
|
fs.writeFileSync(focusPath, yaml.dump(focus, { lineWidth: -1 }));
|
|
217
218
|
} catch (err) {
|
|
218
|
-
|
|
219
|
+
log.warn(`Failed to update focus: ${err.message}`);
|
|
219
220
|
}
|
|
220
221
|
}
|
|
221
222
|
|