orchestrix-yuri 4.4.1 → 4.5.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.
|
@@ -119,10 +119,21 @@ class TelegramAdapter {
|
|
|
119
119
|
log.warn(`Bot error: ${msg}`);
|
|
120
120
|
});
|
|
121
121
|
|
|
122
|
+
// Register bot command menu (shows autocomplete when user types /)
|
|
123
|
+
try {
|
|
124
|
+
await this.bot.api.setMyCommands([
|
|
125
|
+
{ command: 'status', description: 'Show project progress card' },
|
|
126
|
+
{ command: 'help', description: 'Show all available commands' },
|
|
127
|
+
{ command: 'projects', description: 'List all registered projects' },
|
|
128
|
+
{ command: 'plan', description: 'Start planning phase (background)' },
|
|
129
|
+
{ command: 'develop', description: 'Start development phase (background)' },
|
|
130
|
+
{ command: 'test', description: 'Start smoke testing (background)' },
|
|
131
|
+
{ command: 'iterate', description: 'Start new iteration' },
|
|
132
|
+
{ command: 'cancel', description: 'Cancel running phase' },
|
|
133
|
+
]);
|
|
134
|
+
} catch { /* non-critical */ }
|
|
135
|
+
|
|
122
136
|
// Force-disconnect any stale polling connection before starting.
|
|
123
|
-
// deleteWebhook only clears webhooks, NOT existing long-polling connections.
|
|
124
|
-
// A direct getUpdates call with timeout=0 "steals" the polling slot,
|
|
125
|
-
// terminating any other instance's connection.
|
|
126
137
|
log.telegram('Connecting...');
|
|
127
138
|
await forceDisconnectPolling(this.token);
|
|
128
139
|
|
|
@@ -59,6 +59,50 @@ function loadL1Context() {
|
|
|
59
59
|
: '';
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Load L2 (project-level) context for the active project.
|
|
64
|
+
* Only loads lightweight files: identity.yaml and project focus.yaml.
|
|
65
|
+
* knowledge/*.md files are intentionally excluded (too large for system prompt —
|
|
66
|
+
* Claude can Read them on demand via tool use).
|
|
67
|
+
*/
|
|
68
|
+
function loadL2Context() {
|
|
69
|
+
const projectRoot = resolveProjectRoot();
|
|
70
|
+
if (!projectRoot) return '';
|
|
71
|
+
|
|
72
|
+
const yuriDir = path.join(projectRoot, '.yuri');
|
|
73
|
+
if (!fs.existsSync(yuriDir)) return '';
|
|
74
|
+
|
|
75
|
+
const files = [
|
|
76
|
+
{ label: 'Project Identity', path: path.join(yuriDir, 'identity.yaml') },
|
|
77
|
+
{ label: 'Project Focus', path: path.join(yuriDir, 'focus.yaml') },
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const sections = [];
|
|
81
|
+
for (const f of files) {
|
|
82
|
+
if (!fs.existsSync(f.path)) continue;
|
|
83
|
+
const content = fs.readFileSync(f.path, 'utf8').trim();
|
|
84
|
+
if (!content || isEmptyTemplate(content)) continue;
|
|
85
|
+
sections.push(`### ${f.label}\n\`\`\`yaml\n${content}\n\`\`\``);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (sections.length === 0) return '';
|
|
89
|
+
|
|
90
|
+
// Also list available knowledge files (so Claude knows they exist)
|
|
91
|
+
const knowledgeDir = path.join(yuriDir, 'knowledge');
|
|
92
|
+
let knowledgeNote = '';
|
|
93
|
+
if (fs.existsSync(knowledgeDir)) {
|
|
94
|
+
try {
|
|
95
|
+
const knowledgeFiles = fs.readdirSync(knowledgeDir).filter((f) => f.endsWith('.md'));
|
|
96
|
+
if (knowledgeFiles.length > 0) {
|
|
97
|
+
knowledgeNote = `\n\n*Project knowledge files available (use Read tool to access):*\n` +
|
|
98
|
+
knowledgeFiles.map((f) => `- \`${path.join(yuriDir, 'knowledge', f)}\``).join('\n');
|
|
99
|
+
}
|
|
100
|
+
} catch { /* ok */ }
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return `## Active Project Context (L2)\n\n${sections.join('\n\n')}${knowledgeNote}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
62
106
|
function resolveProjectRoot() {
|
|
63
107
|
const registryPath = path.join(YURI_GLOBAL, 'portfolio', 'registry.yaml');
|
|
64
108
|
if (!fs.existsSync(registryPath)) return null;
|
|
@@ -170,6 +214,12 @@ function buildSystemPrompt() {
|
|
|
170
214
|
const l1 = loadL1Context();
|
|
171
215
|
if (l1) parts.push(l1);
|
|
172
216
|
|
|
217
|
+
// Layer 2.5: L2 project context (lightweight — identity + focus only)
|
|
218
|
+
// knowledge/*.md is NOT loaded here (too large for system prompt).
|
|
219
|
+
// Claude can Read those files when needed via tool use.
|
|
220
|
+
const l2 = loadL2Context();
|
|
221
|
+
if (l2) parts.push(l2);
|
|
222
|
+
|
|
173
223
|
// Layer 3: Channel mode instructions
|
|
174
224
|
parts.push(CHANNEL_MODE_INSTRUCTIONS);
|
|
175
225
|
|
|
@@ -373,20 +423,26 @@ async function callClaude(opts) {
|
|
|
373
423
|
function composePrompt(userMessage) {
|
|
374
424
|
const crypto = require('crypto');
|
|
375
425
|
const l1 = loadL1Context();
|
|
376
|
-
const
|
|
426
|
+
const l2 = loadL2Context();
|
|
427
|
+
const combined = (l1 || '') + (l2 || '');
|
|
428
|
+
const contextHash = crypto.createHash('md5').update(combined).digest('hex');
|
|
377
429
|
|
|
378
|
-
// First call or
|
|
379
|
-
if (!_lastL1Hash ||
|
|
380
|
-
_lastL1Hash =
|
|
430
|
+
// First call or context unchanged: just the user message
|
|
431
|
+
if (!_lastL1Hash || contextHash === _lastL1Hash) {
|
|
432
|
+
_lastL1Hash = contextHash;
|
|
381
433
|
return userMessage;
|
|
382
434
|
}
|
|
383
435
|
|
|
384
|
-
//
|
|
385
|
-
_lastL1Hash =
|
|
386
|
-
if (!
|
|
436
|
+
// Context changed: prepend refresh
|
|
437
|
+
_lastL1Hash = contextHash;
|
|
438
|
+
if (!combined) return userMessage;
|
|
439
|
+
|
|
440
|
+
const sections = [];
|
|
441
|
+
if (l1) sections.push(l1);
|
|
442
|
+
if (l2) sections.push(l2);
|
|
387
443
|
|
|
388
|
-
log.engine('
|
|
389
|
-
return `[CONTEXT UPDATE — Your
|
|
444
|
+
log.engine('Memory context changed, injecting refresh into prompt');
|
|
445
|
+
return `[CONTEXT UPDATE — Your memory has been updated]\n${sections.join('\n\n')}\n[END CONTEXT UPDATE]\n\n${userMessage}`;
|
|
390
446
|
}
|
|
391
447
|
|
|
392
448
|
/**
|
package/lib/gateway/router.js
CHANGED
|
@@ -205,6 +205,14 @@ class Router {
|
|
|
205
205
|
return { text: '🚀 Yuri is ready. Send me any message to interact with your projects.' };
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// ═══ SLASH → STAR conversion ═══
|
|
209
|
+
// Telegram/Feishu users type /status, /help, etc. via bot menu.
|
|
210
|
+
// Convert /command to *command so the router's pattern matching works.
|
|
211
|
+
// Excludes /start (handled above) and /o, /clear (Claude Code commands).
|
|
212
|
+
if (msg.text.startsWith('/') && !msg.text.startsWith('/start') && !msg.text.startsWith('/o') && !msg.text.startsWith('/clear')) {
|
|
213
|
+
msg.text = '*' + msg.text.slice(1);
|
|
214
|
+
}
|
|
215
|
+
|
|
208
216
|
// ═══ STATUS QUERY — always allowed, even during processing ═══
|
|
209
217
|
if (this._isStatusQuery(msg.text)) {
|
|
210
218
|
return this._handleStatusQuery(msg);
|