orchestrix-yuri 4.8.0 → 4.8.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.
|
@@ -9,6 +9,7 @@ const yaml = require('js-yaml');
|
|
|
9
9
|
const YURI_GLOBAL = path.join(os.homedir(), '.yuri');
|
|
10
10
|
const SESSION_FILE = path.join(YURI_GLOBAL, 'gateway-session.json');
|
|
11
11
|
const { log } = require('../log');
|
|
12
|
+
const { msg } = require('./messages');
|
|
12
13
|
|
|
13
14
|
// ── Shared Utilities ───────────────────────────────────────────────────────────
|
|
14
15
|
|
|
@@ -309,7 +310,7 @@ function runClaude(args, cwd, timeout) {
|
|
|
309
310
|
}, (err, stdout, stderr) => {
|
|
310
311
|
if (err && err.killed) {
|
|
311
312
|
log.warn('Claude CLI timed out');
|
|
312
|
-
return resolve({ reply: '
|
|
313
|
+
return resolve({ reply: msg('timeout'), raw: '' });
|
|
313
314
|
}
|
|
314
315
|
// Claude CLI may return non-zero exit code even with valid JSON output
|
|
315
316
|
// (e.g., stderr "Warning: no stdin data received" causes exit code 1).
|
|
@@ -317,7 +318,7 @@ function runClaude(args, cwd, timeout) {
|
|
|
317
318
|
if (err && !stdout.trim()) {
|
|
318
319
|
log.error(`Claude CLI error: ${err.message.slice(0, 200)}`);
|
|
319
320
|
if (stderr) log.info(`stderr: ${stderr.slice(0, 200)}`);
|
|
320
|
-
return resolve({ reply: '
|
|
321
|
+
return resolve({ reply: msg('cli_error'), raw: stderr });
|
|
321
322
|
}
|
|
322
323
|
|
|
323
324
|
try {
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
// ── Language Detection ──────────────────────────────────────────────────────
|
|
8
|
+
|
|
9
|
+
let _cachedLang = null;
|
|
10
|
+
let _langCheckedAt = 0;
|
|
11
|
+
const LANG_CACHE_TTL = 300000; // 5 min
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detect user language from boss/preferences.yaml.
|
|
15
|
+
* Falls back to 'en'. Caches for 5 min.
|
|
16
|
+
*/
|
|
17
|
+
function detectLang() {
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
if (_cachedLang && now - _langCheckedAt < LANG_CACHE_TTL) return _cachedLang;
|
|
20
|
+
|
|
21
|
+
_langCheckedAt = now;
|
|
22
|
+
try {
|
|
23
|
+
const prefPath = path.join(os.homedir(), '.yuri', 'boss', 'preferences.yaml');
|
|
24
|
+
if (fs.existsSync(prefPath)) {
|
|
25
|
+
const content = fs.readFileSync(prefPath, 'utf8');
|
|
26
|
+
// Quick extraction without yaml dependency (preferences.yaml is simple)
|
|
27
|
+
const match = content.match(/language:\s*["']?(\w+)/);
|
|
28
|
+
if (match && match[1]) {
|
|
29
|
+
const lang = match[1].toLowerCase();
|
|
30
|
+
_cachedLang = lang.startsWith('zh') ? 'zh' : 'en';
|
|
31
|
+
return _cachedLang;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch { /* fallback */ }
|
|
35
|
+
|
|
36
|
+
_cachedLang = 'en';
|
|
37
|
+
return _cachedLang;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Override language (useful for testing). */
|
|
41
|
+
function setLang(lang) {
|
|
42
|
+
_cachedLang = lang;
|
|
43
|
+
_langCheckedAt = Date.now();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ── Message Templates ───────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
const MESSAGES = {
|
|
49
|
+
// ── Phase Completion ──
|
|
50
|
+
plan_complete: {
|
|
51
|
+
en: '🎉 Planning complete!{summary}\n\nNext: review the docs above, then run `*develop` to start building.',
|
|
52
|
+
zh: '🎉 规划完成!{summary}\n\n下一步:检查上面的文档,然后运行 `*develop` 开始开发。',
|
|
53
|
+
},
|
|
54
|
+
dev_complete: {
|
|
55
|
+
en: '🎉 Development complete! All stories implemented.\n\nNext: run `*test` to validate each epic with smoke tests.',
|
|
56
|
+
zh: '🎉 开发完成!所有 story 已实现。\n\n下一步:运行 `*test` 对每个 epic 进行冒烟测试。',
|
|
57
|
+
},
|
|
58
|
+
test_all_passed: {
|
|
59
|
+
en: '\n🚀 All tests passed! Run `*deploy` when ready.',
|
|
60
|
+
zh: '\n🚀 全部测试通过!准备好后运行 `*deploy` 部署。',
|
|
61
|
+
},
|
|
62
|
+
test_some_failed: {
|
|
63
|
+
en: '\n⚠️ {count} epic(s) failed. Review and fix manually, or run `*test` again.',
|
|
64
|
+
zh: '\n⚠️ {count} 个 epic 未通过。手动修复后重新运行 `*test`。',
|
|
65
|
+
},
|
|
66
|
+
iterate_launched: {
|
|
67
|
+
en: '🔄 New iteration launched!\n\nSM is drafting new stories. Agents will chain automatically (SM → Architect → Dev → QA).',
|
|
68
|
+
zh: '🔄 新迭代已启动!\n\nSM 正在拆分新 story,Agent 将自动接力(SM → Architect → Dev → QA)。',
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// ── Phase Start ──
|
|
72
|
+
dev_started: {
|
|
73
|
+
en: '🚀 Development started! 4 agents (Architect, SM, Dev, QA) are running.\n\nAgents chain automatically via handoff-detector. I\'ll send a progress report every {minutes} minutes.',
|
|
74
|
+
zh: '🚀 开发已启动!4 个 Agent(Architect、SM、Dev、QA)正在工作。\n\nAgent 通过 handoff-detector 自动接力。我每 {minutes} 分钟发送一次进度报告。',
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// ── Change / Direct Agent ──
|
|
78
|
+
change_small: {
|
|
79
|
+
en: '🔧 Small change started → Dev *solo\n\n"{desc}"\n\nI\'ll notify you when it\'s done.',
|
|
80
|
+
zh: '🔧 小改动已启动 → Dev *solo\n\n"{desc}"\n\n完成后我会通知你。',
|
|
81
|
+
},
|
|
82
|
+
change_medium: {
|
|
83
|
+
en: '🔧 {scope} change started → PO *route-change\n\n"{desc}"\n\nPO will assess and route to the right agent. I\'ll keep you updated.',
|
|
84
|
+
zh: '🔧 {scope}级改动已启动 → PO *route-change\n\n"{desc}"\n\nPO 评估后分配给合适的 Agent,我会持续更新。',
|
|
85
|
+
},
|
|
86
|
+
direct_agent: {
|
|
87
|
+
en: '🎯 → **{agent}**\n\n"{desc}"\n\nI\'ll notify you when done.',
|
|
88
|
+
zh: '🎯 → **{agent}**\n\n"{desc}"\n\n完成后通知你。',
|
|
89
|
+
},
|
|
90
|
+
quickfix_started: {
|
|
91
|
+
en: '🐛 Quick fix started → Dev *quick-fix\n\n"{desc}"\n\nI\'ll notify you when it\'s done.',
|
|
92
|
+
zh: '🐛 快速修复已启动 → Dev *quick-fix\n\n"{desc}"\n\n完成后通知你。',
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// ── Progress ──
|
|
96
|
+
agent_handoff: {
|
|
97
|
+
en: '🔄 {from} → **{to}**{story}',
|
|
98
|
+
zh: '🔄 {from} → **{to}**{story}',
|
|
99
|
+
},
|
|
100
|
+
monitoring_dev: {
|
|
101
|
+
en: '🔄 Now monitoring dev cycle (SM → Architect → Dev → QA). I\'ll report agent handoffs and progress.',
|
|
102
|
+
zh: '🔄 正在监控开发流程(SM → Architect → Dev → QA),我会汇报 Agent 交接和进度。',
|
|
103
|
+
},
|
|
104
|
+
change_complete: {
|
|
105
|
+
en: '✅ Change complete.\n\n{summary}\n\nWhat would you like to do next?',
|
|
106
|
+
zh: '✅ 改动完成。\n\n{summary}\n\n接下来要做什么?',
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// ── Errors ──
|
|
110
|
+
error_recovery: {
|
|
111
|
+
en: {
|
|
112
|
+
plan: 'Run `*plan` to restart, or `*status` to check saved progress.',
|
|
113
|
+
develop: 'Run `*develop` to restart, or `*status` to see completed stories.',
|
|
114
|
+
test: 'Run `*test` to restart testing.',
|
|
115
|
+
change: 'Send your change request again.',
|
|
116
|
+
iterate: 'Run `*iterate` again.',
|
|
117
|
+
default: 'Use `*status` to check current state.',
|
|
118
|
+
},
|
|
119
|
+
zh: {
|
|
120
|
+
plan: '运行 `*plan` 重新开始,或 `*status` 查看已保存的进度。',
|
|
121
|
+
develop: '运行 `*develop` 重新开始,或 `*status` 查看已完成的 story。',
|
|
122
|
+
test: '运行 `*test` 重新测试。',
|
|
123
|
+
change: '重新发送你的改动请求。',
|
|
124
|
+
iterate: '重新运行 `*iterate`。',
|
|
125
|
+
default: '使用 `*status` 查看当前状态。',
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
timeout: {
|
|
129
|
+
en: '⏱ This is taking longer than usual. The operation may still be running.\n\nTry `*status` to check progress, or send your message again.',
|
|
130
|
+
zh: '⏱ 处理时间较长,操作可能仍在进行中。\n\n试试 `*status` 查看进度,或重新发送消息。',
|
|
131
|
+
},
|
|
132
|
+
cli_error: {
|
|
133
|
+
en: '❌ Something went wrong. Try again, or use `*status` to check state.',
|
|
134
|
+
zh: '❌ 出了点问题。请重试,或使用 `*status` 查看状态。',
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
// ── Status ──
|
|
138
|
+
no_phase: {
|
|
139
|
+
en: 'No active phase. Available commands: *plan, *develop, *test, *deploy, *projects, *switch',
|
|
140
|
+
zh: '当前无活跃阶段。可用命令:*plan、*develop、*test、*deploy、*projects、*switch',
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// ── Public API ──────────────────────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get a localized message by key with parameter substitution.
|
|
148
|
+
* @param {string} key - Message key (e.g., 'plan_complete')
|
|
149
|
+
* @param {object} params - Substitution params (e.g., { summary: '...' })
|
|
150
|
+
* @returns {string}
|
|
151
|
+
*/
|
|
152
|
+
function msg(key, params = {}) {
|
|
153
|
+
const lang = detectLang();
|
|
154
|
+
const template = MESSAGES[key];
|
|
155
|
+
if (!template) return key;
|
|
156
|
+
|
|
157
|
+
let text = template[lang] || template.en || key;
|
|
158
|
+
|
|
159
|
+
// Handle nested objects (like error_recovery)
|
|
160
|
+
if (typeof text === 'object') {
|
|
161
|
+
const subKey = params._sub || 'default';
|
|
162
|
+
text = text[subKey] || text.default || JSON.stringify(text);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Substitute {param} placeholders
|
|
166
|
+
return text.replace(/\{(\w+)\}/g, (_, k) => (params[k] != null ? params[k] : `{${k}}`));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = { msg, detectLang, setLang, MESSAGES };
|
|
@@ -7,6 +7,7 @@ const os = require('os');
|
|
|
7
7
|
const yaml = require('js-yaml');
|
|
8
8
|
const tmx = require('./tmux-utils');
|
|
9
9
|
const { log } = require('../log');
|
|
10
|
+
const { msg } = require('./messages');
|
|
10
11
|
|
|
11
12
|
const YURI_GLOBAL = path.join(os.homedir(), '.yuri');
|
|
12
13
|
const SKILL_DIR = path.join(os.homedir(), '.claude', 'skills', 'yuri');
|
|
@@ -550,7 +551,7 @@ class PhaseOrchestrator {
|
|
|
550
551
|
const outputSummary = outputs.length > 0 ? `\n\n${outputs.join('\n')}` : '';
|
|
551
552
|
|
|
552
553
|
log.engine('Plan phase complete');
|
|
553
|
-
this.onComplete('plan',
|
|
554
|
+
this.onComplete('plan', msg('plan_complete', { summary: outputSummary }));
|
|
554
555
|
}
|
|
555
556
|
|
|
556
557
|
/**
|
|
@@ -652,7 +653,7 @@ class PhaseOrchestrator {
|
|
|
652
653
|
|
|
653
654
|
const reportMin = Math.round(this._reportInterval / 60000);
|
|
654
655
|
log.engine(`Dev phase started: session=${this._session}, report every ${reportMin}min`);
|
|
655
|
-
return
|
|
656
|
+
return msg('dev_started', { minutes: reportMin });
|
|
656
657
|
}
|
|
657
658
|
|
|
658
659
|
_pollDevSession() {
|
|
@@ -885,7 +886,7 @@ class PhaseOrchestrator {
|
|
|
885
886
|
}
|
|
886
887
|
|
|
887
888
|
log.engine('Dev phase complete');
|
|
888
|
-
this.onComplete('develop', '
|
|
889
|
+
this.onComplete('develop', msg('dev_complete'));
|
|
889
890
|
}
|
|
890
891
|
|
|
891
892
|
// ── Test Phase ──────────────────────────────────────────────────────────────
|
|
@@ -1322,9 +1323,9 @@ class PhaseOrchestrator {
|
|
|
1322
1323
|
}
|
|
1323
1324
|
|
|
1324
1325
|
if (failed === 0) {
|
|
1325
|
-
lines.push('
|
|
1326
|
+
lines.push(msg('test_all_passed'));
|
|
1326
1327
|
} else {
|
|
1327
|
-
lines.push(
|
|
1328
|
+
lines.push(msg('test_some_failed', { count: failed }));
|
|
1328
1329
|
}
|
|
1329
1330
|
|
|
1330
1331
|
this._phase = null;
|
|
@@ -1442,7 +1443,7 @@ class PhaseOrchestrator {
|
|
|
1442
1443
|
|
|
1443
1444
|
this._changeContext = null;
|
|
1444
1445
|
log.engine('Iterate complete — dev automation started');
|
|
1445
|
-
this.onComplete('iterate',
|
|
1446
|
+
this.onComplete('iterate', msg('iterate_launched'));
|
|
1446
1447
|
|
|
1447
1448
|
// Transition to dev monitoring so SM → Architect → Dev → QA cycle is tracked.
|
|
1448
1449
|
// Without this, the entire dev cycle runs unmonitored after iterate completes.
|
|
@@ -1457,7 +1458,7 @@ class PhaseOrchestrator {
|
|
|
1457
1458
|
const pollInterval = this.config.dev_poll_interval || 300000;
|
|
1458
1459
|
this._timer = setInterval(() => this._pollDevSession(), pollInterval);
|
|
1459
1460
|
log.engine(`Iterate → dev monitoring: session=${devSession}, poll every ${Math.round(pollInterval / 60000)}min`);
|
|
1460
|
-
this.onProgress(
|
|
1461
|
+
this.onProgress(msg('monitoring_dev'));
|
|
1461
1462
|
} else {
|
|
1462
1463
|
this._phase = null;
|
|
1463
1464
|
}
|
|
@@ -1511,7 +1512,7 @@ class PhaseOrchestrator {
|
|
|
1511
1512
|
this._timer = setInterval(() => this._pollChange(), pollInterval);
|
|
1512
1513
|
|
|
1513
1514
|
log.engine(`Quick fix started: "${bugDesc.slice(0, 60)}..."`);
|
|
1514
|
-
return
|
|
1515
|
+
return msg('quickfix_started', { desc: bugDesc.slice(0, 100) });
|
|
1515
1516
|
}
|
|
1516
1517
|
|
|
1517
1518
|
// ── Change Management ───────────────────────────────────────────────────────
|
|
@@ -1573,7 +1574,7 @@ class PhaseOrchestrator {
|
|
|
1573
1574
|
this._changeContext = { scope: 'small', description };
|
|
1574
1575
|
this._timer = setInterval(() => this._pollChange(), pollInterval);
|
|
1575
1576
|
|
|
1576
|
-
return
|
|
1577
|
+
return msg('change_small', { desc: description.slice(0, 100) });
|
|
1577
1578
|
}
|
|
1578
1579
|
|
|
1579
1580
|
/**
|
|
@@ -1603,7 +1604,7 @@ class PhaseOrchestrator {
|
|
|
1603
1604
|
};
|
|
1604
1605
|
this._timer = setInterval(() => this._pollChange(), pollInterval);
|
|
1605
1606
|
|
|
1606
|
-
return
|
|
1607
|
+
return msg('change_medium', { scope: scope === 'large' ? 'Large' : 'Medium', desc: description.slice(0, 100) });
|
|
1607
1608
|
}
|
|
1608
1609
|
|
|
1609
1610
|
/**
|
|
@@ -1705,7 +1706,7 @@ class PhaseOrchestrator {
|
|
|
1705
1706
|
const pollInterval = this.config.dev_poll_interval || 300000;
|
|
1706
1707
|
this._timer = setInterval(() => this._pollDevSession(), pollInterval);
|
|
1707
1708
|
log.engine(`Change → dev monitoring: session=${this._session}, poll every ${Math.round(pollInterval / 60000)}min`);
|
|
1708
|
-
this.onProgress(
|
|
1709
|
+
this.onProgress(msg('monitoring_dev'));
|
|
1709
1710
|
} else {
|
|
1710
1711
|
this._phase = null;
|
|
1711
1712
|
this._changeContext = null;
|
|
@@ -1730,15 +1731,7 @@ class PhaseOrchestrator {
|
|
|
1730
1731
|
.replace(/\/Users\/\S+/g, '')
|
|
1731
1732
|
.replace(/tmux session/gi, 'agent session');
|
|
1732
1733
|
|
|
1733
|
-
const
|
|
1734
|
-
plan: 'Run `*plan` to restart, or `*status` to check saved progress.',
|
|
1735
|
-
develop: 'Run `*develop` to restart, or `*status` to see completed stories.',
|
|
1736
|
-
test: 'Run `*test` to restart testing.',
|
|
1737
|
-
change: 'Send your change request again.',
|
|
1738
|
-
iterate: 'Run `*iterate` again.',
|
|
1739
|
-
};
|
|
1740
|
-
|
|
1741
|
-
const recovery = hints[phase] || 'Use `*status` to check current state.';
|
|
1734
|
+
const recovery = msg('error_recovery', { _sub: phase });
|
|
1742
1735
|
this.onError(phase, `❌ ${cleanMsg}\n\n${recovery}`);
|
|
1743
1736
|
}
|
|
1744
1737
|
|
package/lib/gateway/router.js
CHANGED
|
@@ -11,6 +11,7 @@ const engine = require('./engine/claude-sdk');
|
|
|
11
11
|
const { runReflect } = require('./engine/reflect');
|
|
12
12
|
const { PhaseOrchestrator } = require('./engine/phase-orchestrator');
|
|
13
13
|
const { Dispatcher } = require('./engine/dispatcher');
|
|
14
|
+
const { msg } = require('./engine/messages');
|
|
14
15
|
const { log } = require('./log');
|
|
15
16
|
|
|
16
17
|
const YURI_GLOBAL = path.join(os.homedir(), '.yuri');
|
|
@@ -526,7 +527,7 @@ class Router {
|
|
|
526
527
|
}, pollInterval);
|
|
527
528
|
|
|
528
529
|
this.history.append(msg.chatId, 'user', msg.text);
|
|
529
|
-
const reply =
|
|
530
|
+
const reply = msg('direct_agent', { agent: matched.slug, desc: cleanDesc.slice(0, 120) });
|
|
530
531
|
this.history.append(msg.chatId, 'assistant', reply);
|
|
531
532
|
this._updateGlobalFocus(msg, projectRoot);
|
|
532
533
|
|
|
@@ -648,7 +649,7 @@ Reply with ONLY one word: small, medium, or large. Nothing else.`;
|
|
|
648
649
|
}
|
|
649
650
|
|
|
650
651
|
if (parts.length === 0) {
|
|
651
|
-
parts.push('
|
|
652
|
+
parts.push(msg('no_phase'));
|
|
652
653
|
}
|
|
653
654
|
|
|
654
655
|
this.history.append(msg.chatId, 'user', msg.text);
|