orchestrix-yuri 4.7.12 → 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 {
|
|
@@ -478,7 +479,7 @@ function composePrompt(userMessage) {
|
|
|
478
479
|
if (l2) sections.push(l2);
|
|
479
480
|
|
|
480
481
|
log.engine('Memory context changed, injecting refresh into prompt');
|
|
481
|
-
return
|
|
482
|
+
return `<system-context-update silent="true">\nDo not mention or reference this context update in your response. This is an automatic memory refresh.\n${sections.join('\n\n')}\n</system-context-update>\n\n${userMessage}`;
|
|
482
483
|
}
|
|
483
484
|
|
|
484
485
|
/**
|
|
@@ -67,7 +67,8 @@ class Dispatcher {
|
|
|
67
67
|
async classify(text) {
|
|
68
68
|
// Re-entry guard
|
|
69
69
|
if (this._busy) {
|
|
70
|
-
|
|
70
|
+
log.warn('Dispatcher: busy, defaulting to change');
|
|
71
|
+
return { action: 'change', description: text, reasoning: 'dispatcher busy' };
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
// Auto-recover if session died
|
|
@@ -75,7 +76,8 @@ class Dispatcher {
|
|
|
75
76
|
this._ready = false;
|
|
76
77
|
await this.start();
|
|
77
78
|
if (!this._ready) {
|
|
78
|
-
|
|
79
|
+
log.warn('Dispatcher: unavailable after recovery attempt, defaulting to change');
|
|
80
|
+
return { action: 'change', description: text, reasoning: 'dispatcher unavailable' };
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
|
|
@@ -165,17 +167,32 @@ class Dispatcher {
|
|
|
165
167
|
}
|
|
166
168
|
}
|
|
167
169
|
|
|
168
|
-
// Strategy 3:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
// Strategy 3: Claude re-classify (replaces keyword matching)
|
|
171
|
+
// Only reached when tmux output was garbled or unparseable — rare but important.
|
|
172
|
+
log.warn('Dispatcher: JSON parse failed, attempting Claude re-classify');
|
|
173
|
+
try {
|
|
174
|
+
const engine = require('./claude-sdk');
|
|
175
|
+
const binary = engine.findClaudeBinary();
|
|
176
|
+
const safeText = originalText.slice(0, 500).replace(/'/g, "'\\''");
|
|
177
|
+
const prompt = `Classify this user message into ONE action. Valid actions: ${valid.join(', ')}. Reply with ONLY a JSON object like {"action":"change","description":"summary","reasoning":"why"}. User message: "${safeText}"`;
|
|
178
|
+
const result = execSync(
|
|
179
|
+
`${binary} -p '${prompt.replace(/'/g, "'\\''")}' --output-format json 2>/dev/null`,
|
|
180
|
+
{ encoding: 'utf8', timeout: 15000 }
|
|
181
|
+
).trim();
|
|
182
|
+
const json = JSON.parse(result);
|
|
183
|
+
const reply = json.result || '';
|
|
184
|
+
const reParsed = JSON.parse(reply);
|
|
185
|
+
if (reParsed.action && valid.includes(reParsed.action)) {
|
|
186
|
+
log.engine(`Dispatcher: re-classified as "${reParsed.action}"`);
|
|
187
|
+
return { action: reParsed.action, description: reParsed.description || originalText, reasoning: 'reclassified' };
|
|
173
188
|
}
|
|
189
|
+
} catch (err) {
|
|
190
|
+
log.warn(`Dispatcher: re-classify failed: ${err.message}`);
|
|
174
191
|
}
|
|
175
192
|
|
|
176
|
-
//
|
|
177
|
-
log.warn('Dispatcher:
|
|
178
|
-
return { action: 'change', description: originalText, reasoning: 'parse failed' };
|
|
193
|
+
// Absolute last resort — default to "change" (bias toward action)
|
|
194
|
+
log.warn('Dispatcher: all strategies failed, defaulting to change');
|
|
195
|
+
return { action: 'change', description: originalText, reasoning: 'all parse strategies failed' };
|
|
179
196
|
}
|
|
180
197
|
|
|
181
198
|
/**
|
|
@@ -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');
|
|
@@ -194,7 +195,6 @@ class PhaseOrchestrator {
|
|
|
194
195
|
|
|
195
196
|
const waiting = this._waitingForInput ? ' (WAITING FOR YOUR INPUT)' : '';
|
|
196
197
|
return `[LIVE AGENT CONTEXT] Phase: plan, Agent: ${agent.name} (${this._step + 1}/${PLAN_AGENTS.length})${waiting}\n` +
|
|
197
|
-
`tmux session: ${this._session}, window: ${agent.window}\n` +
|
|
198
198
|
`--- Agent output (last 40 lines) ---\n${pane}\n--- End agent output ---`;
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -205,9 +205,9 @@ class PhaseOrchestrator {
|
|
|
205
205
|
for (let w = 0; w < 4; w++) {
|
|
206
206
|
const tail = tmx.capturePane(this._session, w, 5);
|
|
207
207
|
const lastLine = tail.split('\n').filter((l) => l.trim()).pop() || '(empty)';
|
|
208
|
-
summaries.push(`
|
|
208
|
+
summaries.push(` ${windows[w]}: ${lastLine.trim().slice(0, 80)}`);
|
|
209
209
|
}
|
|
210
|
-
return `[LIVE AGENT CONTEXT] Phase: develop
|
|
210
|
+
return `[LIVE AGENT CONTEXT] Phase: develop\n${summaries.join('\n')}`;
|
|
211
211
|
}
|
|
212
212
|
|
|
213
213
|
return null;
|
|
@@ -539,8 +539,19 @@ class PhaseOrchestrator {
|
|
|
539
539
|
this._phase = null;
|
|
540
540
|
this._step = 0;
|
|
541
541
|
|
|
542
|
+
// Build output summary
|
|
543
|
+
const outputs = [];
|
|
544
|
+
for (const agent of PLAN_AGENTS) {
|
|
545
|
+
if (agent.output) {
|
|
546
|
+
const outputPath = path.join(this._projectRoot, agent.output);
|
|
547
|
+
const exists = fs.existsSync(outputPath);
|
|
548
|
+
outputs.push(` ${exists ? '✅' : '⚠️'} ${agent.name} → ${agent.output}`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const outputSummary = outputs.length > 0 ? `\n\n${outputs.join('\n')}` : '';
|
|
552
|
+
|
|
542
553
|
log.engine('Plan phase complete');
|
|
543
|
-
this.onComplete('plan', '
|
|
554
|
+
this.onComplete('plan', msg('plan_complete', { summary: outputSummary }));
|
|
544
555
|
}
|
|
545
556
|
|
|
546
557
|
/**
|
|
@@ -642,7 +653,7 @@ class PhaseOrchestrator {
|
|
|
642
653
|
|
|
643
654
|
const reportMin = Math.round(this._reportInterval / 60000);
|
|
644
655
|
log.engine(`Dev phase started: session=${this._session}, report every ${reportMin}min`);
|
|
645
|
-
return
|
|
656
|
+
return msg('dev_started', { minutes: reportMin });
|
|
646
657
|
}
|
|
647
658
|
|
|
648
659
|
_pollDevSession() {
|
|
@@ -819,7 +830,7 @@ class PhaseOrchestrator {
|
|
|
819
830
|
// Current activity
|
|
820
831
|
if (p.currentAgent) {
|
|
821
832
|
const storyInfo = p.currentStory ? ` → ${p.currentStory}` : '';
|
|
822
|
-
lines.push(`Current: ${p.currentAgent}
|
|
833
|
+
lines.push(`Current: ${p.currentAgent}${storyInfo}`);
|
|
823
834
|
} else {
|
|
824
835
|
lines.push(`Current: waiting for next handoff`);
|
|
825
836
|
}
|
|
@@ -875,7 +886,7 @@ class PhaseOrchestrator {
|
|
|
875
886
|
}
|
|
876
887
|
|
|
877
888
|
log.engine('Dev phase complete');
|
|
878
|
-
this.onComplete('develop', '
|
|
889
|
+
this.onComplete('develop', msg('dev_complete'));
|
|
879
890
|
}
|
|
880
891
|
|
|
881
892
|
// ── Test Phase ──────────────────────────────────────────────────────────────
|
|
@@ -1312,9 +1323,9 @@ class PhaseOrchestrator {
|
|
|
1312
1323
|
}
|
|
1313
1324
|
|
|
1314
1325
|
if (failed === 0) {
|
|
1315
|
-
lines.push('
|
|
1326
|
+
lines.push(msg('test_all_passed'));
|
|
1316
1327
|
} else {
|
|
1317
|
-
lines.push(
|
|
1328
|
+
lines.push(msg('test_some_failed', { count: failed }));
|
|
1318
1329
|
}
|
|
1319
1330
|
|
|
1320
1331
|
this._phase = null;
|
|
@@ -1432,7 +1443,7 @@ class PhaseOrchestrator {
|
|
|
1432
1443
|
|
|
1433
1444
|
this._changeContext = null;
|
|
1434
1445
|
log.engine('Iterate complete — dev automation started');
|
|
1435
|
-
this.onComplete('iterate',
|
|
1446
|
+
this.onComplete('iterate', msg('iterate_launched'));
|
|
1436
1447
|
|
|
1437
1448
|
// Transition to dev monitoring so SM → Architect → Dev → QA cycle is tracked.
|
|
1438
1449
|
// Without this, the entire dev cycle runs unmonitored after iterate completes.
|
|
@@ -1447,7 +1458,7 @@ class PhaseOrchestrator {
|
|
|
1447
1458
|
const pollInterval = this.config.dev_poll_interval || 300000;
|
|
1448
1459
|
this._timer = setInterval(() => this._pollDevSession(), pollInterval);
|
|
1449
1460
|
log.engine(`Iterate → dev monitoring: session=${devSession}, poll every ${Math.round(pollInterval / 60000)}min`);
|
|
1450
|
-
this.onProgress(
|
|
1461
|
+
this.onProgress(msg('monitoring_dev'));
|
|
1451
1462
|
} else {
|
|
1452
1463
|
this._phase = null;
|
|
1453
1464
|
}
|
|
@@ -1501,7 +1512,7 @@ class PhaseOrchestrator {
|
|
|
1501
1512
|
this._timer = setInterval(() => this._pollChange(), pollInterval);
|
|
1502
1513
|
|
|
1503
1514
|
log.engine(`Quick fix started: "${bugDesc.slice(0, 60)}..."`);
|
|
1504
|
-
return
|
|
1515
|
+
return msg('quickfix_started', { desc: bugDesc.slice(0, 100) });
|
|
1505
1516
|
}
|
|
1506
1517
|
|
|
1507
1518
|
// ── Change Management ───────────────────────────────────────────────────────
|
|
@@ -1563,7 +1574,7 @@ class PhaseOrchestrator {
|
|
|
1563
1574
|
this._changeContext = { scope: 'small', description };
|
|
1564
1575
|
this._timer = setInterval(() => this._pollChange(), pollInterval);
|
|
1565
1576
|
|
|
1566
|
-
return
|
|
1577
|
+
return msg('change_small', { desc: description.slice(0, 100) });
|
|
1567
1578
|
}
|
|
1568
1579
|
|
|
1569
1580
|
/**
|
|
@@ -1593,7 +1604,7 @@ class PhaseOrchestrator {
|
|
|
1593
1604
|
};
|
|
1594
1605
|
this._timer = setInterval(() => this._pollChange(), pollInterval);
|
|
1595
1606
|
|
|
1596
|
-
return
|
|
1607
|
+
return msg('change_medium', { scope: scope === 'large' ? 'Large' : 'Medium', desc: description.slice(0, 100) });
|
|
1597
1608
|
}
|
|
1598
1609
|
|
|
1599
1610
|
/**
|
|
@@ -1695,7 +1706,7 @@ class PhaseOrchestrator {
|
|
|
1695
1706
|
const pollInterval = this.config.dev_poll_interval || 300000;
|
|
1696
1707
|
this._timer = setInterval(() => this._pollDevSession(), pollInterval);
|
|
1697
1708
|
log.engine(`Change → dev monitoring: session=${this._session}, poll every ${Math.round(pollInterval / 60000)}min`);
|
|
1698
|
-
this.onProgress(
|
|
1709
|
+
this.onProgress(msg('monitoring_dev'));
|
|
1699
1710
|
} else {
|
|
1700
1711
|
this._phase = null;
|
|
1701
1712
|
this._changeContext = null;
|
|
@@ -1711,7 +1722,17 @@ class PhaseOrchestrator {
|
|
|
1711
1722
|
}
|
|
1712
1723
|
this._phase = null;
|
|
1713
1724
|
log.error(`Phase ${phase} error: ${message}`);
|
|
1714
|
-
|
|
1725
|
+
|
|
1726
|
+
// Sanitize internal details from user-facing message
|
|
1727
|
+
const cleanMsg = message
|
|
1728
|
+
.replace(/orchestrix-[\w-]+/g, 'dev session')
|
|
1729
|
+
.replace(/op-[\w-]+/g, 'planning session')
|
|
1730
|
+
.replace(/yuri-[\w-]+/g, 'dispatcher')
|
|
1731
|
+
.replace(/\/Users\/\S+/g, '')
|
|
1732
|
+
.replace(/tmux session/gi, 'agent session');
|
|
1733
|
+
|
|
1734
|
+
const recovery = msg('error_recovery', { _sub: phase });
|
|
1735
|
+
this.onError(phase, `❌ ${cleanMsg}\n\n${recovery}`);
|
|
1715
1736
|
}
|
|
1716
1737
|
|
|
1717
1738
|
_updatePlanMemory(status) {
|
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');
|
|
@@ -65,7 +66,7 @@ class Router {
|
|
|
65
66
|
config: config.engine,
|
|
66
67
|
onProgress: (msg) => this._sendProactive(msg),
|
|
67
68
|
onComplete: (phase, summary) => this._sendProactive(summary),
|
|
68
|
-
onError: (phase, err) => this._sendProactive(
|
|
69
|
+
onError: (phase, err) => this._sendProactive(err),
|
|
69
70
|
onQuestionAsked: (text) => this._sendProactiveWithId(text),
|
|
70
71
|
});
|
|
71
72
|
|
|
@@ -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);
|
|
@@ -1123,7 +1124,7 @@ Reply with ONLY one word: small, medium, or large. Nothing else.`;
|
|
|
1123
1124
|
const last = lines.slice(-3).map((l) => l.trim().slice(0, 100)).join('\n ');
|
|
1124
1125
|
summaries.push(` Window ${w} (${windows[w]}):\n ${last || '(idle)'}`);
|
|
1125
1126
|
}
|
|
1126
|
-
return `[LIVE DEV SESSION]
|
|
1127
|
+
return `[LIVE DEV SESSION] (agents running independently)\n${summaries.join('\n')}`;
|
|
1127
1128
|
} catch { return null; }
|
|
1128
1129
|
}
|
|
1129
1130
|
|