orchestrix-yuri 4.7.11 → 4.8.0

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.
@@ -309,7 +309,7 @@ function runClaude(args, cwd, timeout) {
309
309
  }, (err, stdout, stderr) => {
310
310
  if (err && err.killed) {
311
311
  log.warn('Claude CLI timed out');
312
- return resolve({ reply: '⏱ Taking longer than expected. Try a specific command like `*change "desc"` or `*status`.', raw: '' });
312
+ return resolve({ reply: '⏱ This is taking longer than usual. The operation may still be running.\n\nTry `*status` to check progress, or send your message again.', raw: '' });
313
313
  }
314
314
  // Claude CLI may return non-zero exit code even with valid JSON output
315
315
  // (e.g., stderr "Warning: no stdin data received" causes exit code 1).
@@ -317,7 +317,7 @@ function runClaude(args, cwd, timeout) {
317
317
  if (err && !stdout.trim()) {
318
318
  log.error(`Claude CLI error: ${err.message.slice(0, 200)}`);
319
319
  if (stderr) log.info(`stderr: ${stderr.slice(0, 200)}`);
320
- return resolve({ reply: `❌ Claude CLI error: ${err.message.slice(0, 200)}`, raw: stderr });
320
+ return resolve({ reply: '❌ Something went wrong. Try again, or use `*status` to check state.', raw: stderr });
321
321
  }
322
322
 
323
323
  try {
@@ -478,7 +478,7 @@ function composePrompt(userMessage) {
478
478
  if (l2) sections.push(l2);
479
479
 
480
480
  log.engine('Memory context changed, injecting refresh into prompt');
481
- return `[CONTEXT UPDATE Your memory has been updated]\n${sections.join('\n\n')}\n[END CONTEXT UPDATE]\n\n${userMessage}`;
481
+ 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
482
  }
483
483
 
484
484
  /**
@@ -67,7 +67,8 @@ class Dispatcher {
67
67
  async classify(text) {
68
68
  // Re-entry guard
69
69
  if (this._busy) {
70
- return { action: 'conversation', description: text, reasoning: 'dispatcher busy' };
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
- return { action: 'conversation', description: text, reasoning: 'dispatcher unavailable' };
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: Look for bare action words in the last few lines
169
- const tail = lines.slice(-5).join(' ').toLowerCase();
170
- for (const action of valid) {
171
- if (action !== 'conversation' && tail.includes(`"${action}"`)) {
172
- return { action, description: originalText, reasoning: 'keyword match' };
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
- // Parse failed — default to "change" (bias toward action, not conversation)
177
- log.warn('Dispatcher: failed to parse response, defaulting to change');
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
  /**
@@ -194,7 +194,6 @@ class PhaseOrchestrator {
194
194
 
195
195
  const waiting = this._waitingForInput ? ' (WAITING FOR YOUR INPUT)' : '';
196
196
  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
197
  `--- Agent output (last 40 lines) ---\n${pane}\n--- End agent output ---`;
199
198
  }
200
199
 
@@ -205,9 +204,9 @@ class PhaseOrchestrator {
205
204
  for (let w = 0; w < 4; w++) {
206
205
  const tail = tmx.capturePane(this._session, w, 5);
207
206
  const lastLine = tail.split('\n').filter((l) => l.trim()).pop() || '(empty)';
208
- summaries.push(` Window ${w} (${windows[w]}): ${lastLine.trim().slice(0, 80)}`);
207
+ summaries.push(` ${windows[w]}: ${lastLine.trim().slice(0, 80)}`);
209
208
  }
210
- return `[LIVE AGENT CONTEXT] Phase: develop, Session: ${this._session}\n${summaries.join('\n')}`;
209
+ return `[LIVE AGENT CONTEXT] Phase: develop\n${summaries.join('\n')}`;
211
210
  }
212
211
 
213
212
  return null;
@@ -539,8 +538,19 @@ class PhaseOrchestrator {
539
538
  this._phase = null;
540
539
  this._step = 0;
541
540
 
541
+ // Build output summary
542
+ const outputs = [];
543
+ for (const agent of PLAN_AGENTS) {
544
+ if (agent.output) {
545
+ const outputPath = path.join(this._projectRoot, agent.output);
546
+ const exists = fs.existsSync(outputPath);
547
+ outputs.push(` ${exists ? '✅' : '⚠️'} ${agent.name} → ${agent.output}`);
548
+ }
549
+ }
550
+ const outputSummary = outputs.length > 0 ? `\n\n${outputs.join('\n')}` : '';
551
+
542
552
  log.engine('Plan phase complete');
543
- this.onComplete('plan', '🎉 Planning phase complete! All 6 agents finished.\n\nRun *develop to start automated development.');
553
+ this.onComplete('plan', `🎉 Planning complete!${outputSummary}\n\nNext: review the docs above, then run \`*develop\` to start building.`);
544
554
  }
545
555
 
546
556
  /**
@@ -819,7 +829,7 @@ class PhaseOrchestrator {
819
829
  // Current activity
820
830
  if (p.currentAgent) {
821
831
  const storyInfo = p.currentStory ? ` → ${p.currentStory}` : '';
822
- lines.push(`Current: ${p.currentAgent} (window ${p.currentWindow})${storyInfo}`);
832
+ lines.push(`Current: ${p.currentAgent}${storyInfo}`);
823
833
  } else {
824
834
  lines.push(`Current: waiting for next handoff`);
825
835
  }
@@ -875,7 +885,7 @@ class PhaseOrchestrator {
875
885
  }
876
886
 
877
887
  log.engine('Dev phase complete');
878
- this.onComplete('develop', '🎉 Development complete! All stories finished.\n\nRun *test to start smoke testing.');
888
+ this.onComplete('develop', '🎉 Development complete! All stories implemented.\n\nNext: run `*test` to validate each epic with smoke tests.');
879
889
  }
880
890
 
881
891
  // ── Test Phase ──────────────────────────────────────────────────────────────
@@ -1432,7 +1442,7 @@ class PhaseOrchestrator {
1432
1442
 
1433
1443
  this._changeContext = null;
1434
1444
  log.engine('Iterate complete — dev automation started');
1435
- this.onComplete('iterate', `🔄 New iteration launched!\n\nSM is drafting stories in dev session: ${devSession}\nAgents will chain automatically via handoff-detector.`);
1445
+ this.onComplete('iterate', `🔄 New iteration launched!\n\nSM is drafting new stories. Agents will chain automatically (SM → Architect → Dev → QA).`);
1436
1446
 
1437
1447
  // Transition to dev monitoring so SM → Architect → Dev → QA cycle is tracked.
1438
1448
  // Without this, the entire dev cycle runs unmonitored after iterate completes.
@@ -1711,7 +1721,25 @@ class PhaseOrchestrator {
1711
1721
  }
1712
1722
  this._phase = null;
1713
1723
  log.error(`Phase ${phase} error: ${message}`);
1714
- this.onError(phase, message);
1724
+
1725
+ // Sanitize internal details from user-facing message
1726
+ const cleanMsg = message
1727
+ .replace(/orchestrix-[\w-]+/g, 'dev session')
1728
+ .replace(/op-[\w-]+/g, 'planning session')
1729
+ .replace(/yuri-[\w-]+/g, 'dispatcher')
1730
+ .replace(/\/Users\/\S+/g, '')
1731
+ .replace(/tmux session/gi, 'agent session');
1732
+
1733
+ const hints = {
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.';
1742
+ this.onError(phase, `❌ ${cleanMsg}\n\n${recovery}`);
1715
1743
  }
1716
1744
 
1717
1745
  _updatePlanMemory(status) {
@@ -65,7 +65,7 @@ class Router {
65
65
  config: config.engine,
66
66
  onProgress: (msg) => this._sendProactive(msg),
67
67
  onComplete: (phase, summary) => this._sendProactive(summary),
68
- onError: (phase, err) => this._sendProactive(`❌ Phase ${phase} error: ${err}`),
68
+ onError: (phase, err) => this._sendProactive(err),
69
69
  onQuestionAsked: (text) => this._sendProactiveWithId(text),
70
70
  });
71
71
 
@@ -1123,7 +1123,7 @@ Reply with ONLY one word: small, medium, or large. Nothing else.`;
1123
1123
  const last = lines.slice(-3).map((l) => l.trim().slice(0, 100)).join('\n ');
1124
1124
  summaries.push(` Window ${w} (${windows[w]}):\n ${last || '(idle)'}`);
1125
1125
  }
1126
- return `[LIVE DEV SESSION] ${devSession} (orchestrator not actively tracking)\n${summaries.join('\n')}`;
1126
+ return `[LIVE DEV SESSION] (agents running independently)\n${summaries.join('\n')}`;
1127
1127
  } catch { return null; }
1128
1128
  }
1129
1129
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orchestrix-yuri",
3
- "version": "4.7.11",
3
+ "version": "4.8.0",
4
4
  "description": "Yuri — Meta-Orchestrator for Orchestrix. Drive your entire project lifecycle with natural language.",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {