orchestrix-yuri 4.7.3 → 4.7.5
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.
|
@@ -107,9 +107,9 @@ class Dispatcher {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
// Timeout — default to
|
|
111
|
-
log.warn('Dispatcher: classify timeout');
|
|
112
|
-
return { action: '
|
|
110
|
+
// Timeout — default to change (bias toward action)
|
|
111
|
+
log.warn('Dispatcher: classify timeout, defaulting to change');
|
|
112
|
+
return { action: 'change', description: text, reasoning: 'classifier timeout' };
|
|
113
113
|
} finally {
|
|
114
114
|
this._busy = false;
|
|
115
115
|
}
|
|
@@ -117,33 +117,65 @@ class Dispatcher {
|
|
|
117
117
|
|
|
118
118
|
/**
|
|
119
119
|
* Parse the dispatcher's tmux pane output to extract the JSON response.
|
|
120
|
-
*
|
|
120
|
+
* Handles: bare JSON lines, markdown code blocks, partial lines with JSON.
|
|
121
121
|
*/
|
|
122
122
|
_parseResponse(output, originalText) {
|
|
123
|
-
const
|
|
123
|
+
const valid = ['bugfix', 'change', 'plan', 'develop', 'test', 'deploy', 'status', 'iterate', 'conversation'];
|
|
124
124
|
|
|
125
|
+
// Strategy 1: Find JSON on its own line (bottom-up)
|
|
126
|
+
const lines = output.split('\n');
|
|
125
127
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
126
128
|
const line = lines[i].trim();
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
if (valid.includes(parsed.action)) {
|
|
129
|
+
if (line.includes('"action"')) {
|
|
130
|
+
// Extract JSON substring (may be embedded in other text)
|
|
131
|
+
const jsonMatch = line.match(/\{[^{}]*"action"\s*:\s*"[^"]+?"[^{}]*\}/);
|
|
132
|
+
if (jsonMatch) {
|
|
133
|
+
try {
|
|
134
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
135
|
+
if (parsed.action && valid.includes(parsed.action)) {
|
|
134
136
|
return {
|
|
135
137
|
action: parsed.action,
|
|
136
138
|
description: parsed.description || originalText,
|
|
137
139
|
reasoning: parsed.reasoning || '',
|
|
138
140
|
};
|
|
139
141
|
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
+
} catch { /* continue */ }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Strategy 2: Search entire output for JSON pattern (handles multiline/code blocks)
|
|
148
|
+
const fullMatch = output.match(/\{\s*"action"\s*:\s*"(\w+)"[^}]*\}/);
|
|
149
|
+
if (fullMatch) {
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(fullMatch[0]);
|
|
152
|
+
if (parsed.action && valid.includes(parsed.action)) {
|
|
153
|
+
return {
|
|
154
|
+
action: parsed.action,
|
|
155
|
+
description: parsed.description || originalText,
|
|
156
|
+
reasoning: parsed.reasoning || '',
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
} catch { /* continue */ }
|
|
160
|
+
|
|
161
|
+
// Even if full JSON parse fails, extract action from regex
|
|
162
|
+
const action = fullMatch[1];
|
|
163
|
+
if (valid.includes(action)) {
|
|
164
|
+
return { action, description: originalText, reasoning: 'partial parse' };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
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' };
|
|
142
173
|
}
|
|
143
174
|
}
|
|
144
175
|
|
|
145
|
-
|
|
146
|
-
|
|
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' };
|
|
147
179
|
}
|
|
148
180
|
|
|
149
181
|
/**
|
|
@@ -108,6 +108,7 @@ class PhaseOrchestrator {
|
|
|
108
108
|
this._session = phase3.tmux.session;
|
|
109
109
|
this._devStartedAt = phase3.started_at ? new Date(phase3.started_at).getTime() : Date.now();
|
|
110
110
|
this._lastReportTime = Date.now(); // don't send report immediately on recover
|
|
111
|
+
this._lastActiveAgent = null;
|
|
111
112
|
|
|
112
113
|
const pollInterval = this.config.dev_poll_interval || 300000;
|
|
113
114
|
this._timer = setInterval(() => this._pollDevSession(), pollInterval);
|
|
@@ -244,12 +245,19 @@ class PhaseOrchestrator {
|
|
|
244
245
|
|
|
245
246
|
if (this._phase === 'iterate') {
|
|
246
247
|
const ctx = this._changeContext || {};
|
|
247
|
-
|
|
248
|
+
const agentLabels = { pm: 'PM generating next-steps', architect: 'Architect resolving changes' };
|
|
249
|
+
const label = agentLabels[ctx.iteratePhase] || ctx.iteratePhase || 'starting';
|
|
250
|
+
return { phase: 'iterate', message: `🔄 Iteration in progress\n📍 ${label} (${this._step + 1}/2)` };
|
|
248
251
|
}
|
|
249
252
|
|
|
250
253
|
if (this._phase === 'change') {
|
|
251
254
|
const ctx = this._changeContext || {};
|
|
252
|
-
|
|
255
|
+
const stepLabels = ['PO routing change', 'Architect resolving', 'SM applying proposal'];
|
|
256
|
+
const stepLabel = stepLabels[this._step] || `Step ${this._step + 1}`;
|
|
257
|
+
return {
|
|
258
|
+
phase: 'change',
|
|
259
|
+
message: `🔧 Change in progress (${ctx.scope || '?'})\n📍 ${stepLabel} (${this._step + 1}/3)`,
|
|
260
|
+
};
|
|
253
261
|
}
|
|
254
262
|
|
|
255
263
|
return { phase: this._phase, message: `Phase ${this._phase} is running.` };
|
|
@@ -630,6 +638,7 @@ class PhaseOrchestrator {
|
|
|
630
638
|
this._timer = setInterval(() => this._pollDevSession(), pollInterval);
|
|
631
639
|
this._devStartedAt = Date.now();
|
|
632
640
|
this._lastReportTime = Date.now();
|
|
641
|
+
this._lastActiveAgent = null;
|
|
633
642
|
|
|
634
643
|
const reportMin = Math.round(this._reportInterval / 60000);
|
|
635
644
|
log.engine(`Dev phase started: session=${this._session}, report every ${reportMin}min`);
|
|
@@ -647,6 +656,18 @@ class PhaseOrchestrator {
|
|
|
647
656
|
// Gather progress data
|
|
648
657
|
const progress = this._gatherDevProgress();
|
|
649
658
|
|
|
659
|
+
// Detect agent handoff and notify user
|
|
660
|
+
if (progress.currentAgent && progress.currentAgent !== this._lastActiveAgent) {
|
|
661
|
+
const prev = this._lastActiveAgent;
|
|
662
|
+
this._lastActiveAgent = progress.currentAgent;
|
|
663
|
+
if (prev) {
|
|
664
|
+
const storyInfo = progress.currentStory ? ` — working on ${progress.currentStory}` : '';
|
|
665
|
+
this.onProgress(`🔄 ${prev} → **${progress.currentAgent}**${storyInfo}`);
|
|
666
|
+
}
|
|
667
|
+
} else if (progress.currentAgent) {
|
|
668
|
+
this._lastActiveAgent = progress.currentAgent;
|
|
669
|
+
}
|
|
670
|
+
|
|
650
671
|
// Check if all stories done
|
|
651
672
|
if (progress.totalStories > 0 && progress.doneStories >= progress.totalStories) {
|
|
652
673
|
this._completeDev();
|
|
@@ -1394,14 +1415,32 @@ class PhaseOrchestrator {
|
|
|
1394
1415
|
|
|
1395
1416
|
_completeIterate(devSession) {
|
|
1396
1417
|
if (this._timer) { clearInterval(this._timer); this._timer = null; }
|
|
1397
|
-
// Kill planning session
|
|
1418
|
+
// Kill planning session (no longer needed)
|
|
1398
1419
|
if (this._session && tmx.hasSession(this._session)) {
|
|
1399
1420
|
tmx.killSession(this._session);
|
|
1400
1421
|
}
|
|
1401
|
-
|
|
1422
|
+
|
|
1402
1423
|
this._changeContext = null;
|
|
1403
1424
|
log.engine('Iterate complete — dev automation started');
|
|
1404
1425
|
this.onComplete('iterate', `🔄 New iteration launched!\n\nSM is drafting stories in dev session: ${devSession}\nAgents will chain automatically via handoff-detector.`);
|
|
1426
|
+
|
|
1427
|
+
// Transition to dev monitoring so SM → Architect → Dev → QA cycle is tracked.
|
|
1428
|
+
// Without this, the entire dev cycle runs unmonitored after iterate completes.
|
|
1429
|
+
if (devSession && tmx.hasSession(devSession)) {
|
|
1430
|
+
this._phase = 'develop';
|
|
1431
|
+
this._session = devSession;
|
|
1432
|
+
this._devStartedAt = Date.now();
|
|
1433
|
+
this._lastReportTime = Date.now();
|
|
1434
|
+
this._lastActiveAgent = null;
|
|
1435
|
+
this._stableCount = 0;
|
|
1436
|
+
this._lastHash = '';
|
|
1437
|
+
const pollInterval = this.config.dev_poll_interval || 300000;
|
|
1438
|
+
this._timer = setInterval(() => this._pollDevSession(), pollInterval);
|
|
1439
|
+
log.engine(`Iterate → dev monitoring: session=${devSession}, poll every ${Math.round(pollInterval / 60000)}min`);
|
|
1440
|
+
this.onProgress('🔄 Now monitoring dev cycle (SM → Architect → Dev → QA). I\'ll report agent handoffs and progress.');
|
|
1441
|
+
} else {
|
|
1442
|
+
this._phase = null;
|
|
1443
|
+
}
|
|
1405
1444
|
}
|
|
1406
1445
|
|
|
1407
1446
|
// ── Quick Fix ───────────────────────────────────────────────────────────────
|
|
@@ -1609,8 +1648,8 @@ class PhaseOrchestrator {
|
|
|
1609
1648
|
execSync('sleep 12');
|
|
1610
1649
|
tmx.sendKeysWithEnter(this._session, 1, '*draft');
|
|
1611
1650
|
|
|
1612
|
-
//
|
|
1613
|
-
|
|
1651
|
+
// Planning window 0 (Architect) is already stable — next 3 stable polls
|
|
1652
|
+
// will trigger step 3 → _completeChange → transition to dev monitoring.
|
|
1614
1653
|
} catch (err) {
|
|
1615
1654
|
this._handleError('change', `Failed to apply in dev session: ${err.message}`);
|
|
1616
1655
|
}
|
|
@@ -1629,10 +1668,28 @@ class PhaseOrchestrator {
|
|
|
1629
1668
|
clearInterval(this._timer);
|
|
1630
1669
|
this._timer = null;
|
|
1631
1670
|
}
|
|
1632
|
-
|
|
1633
|
-
this._changeContext = null;
|
|
1671
|
+
|
|
1634
1672
|
log.engine(`Change management complete: ${summary}`);
|
|
1635
1673
|
this.onComplete('change', `✅ Change management complete.\n\n${summary}`);
|
|
1674
|
+
|
|
1675
|
+
// Transition to dev monitoring if SM was started in a dev session.
|
|
1676
|
+
// Without this, the SM → Architect → Dev → QA cycle runs unmonitored.
|
|
1677
|
+
if (this._session && tmx.hasSession(this._session)) {
|
|
1678
|
+
this._phase = 'develop';
|
|
1679
|
+
this._changeContext = null;
|
|
1680
|
+
this._devStartedAt = Date.now();
|
|
1681
|
+
this._lastReportTime = Date.now();
|
|
1682
|
+
this._lastActiveAgent = null;
|
|
1683
|
+
this._stableCount = 0;
|
|
1684
|
+
this._lastHash = '';
|
|
1685
|
+
const pollInterval = this.config.dev_poll_interval || 300000;
|
|
1686
|
+
this._timer = setInterval(() => this._pollDevSession(), pollInterval);
|
|
1687
|
+
log.engine(`Change → dev monitoring: session=${this._session}, poll every ${Math.round(pollInterval / 60000)}min`);
|
|
1688
|
+
this.onProgress('🔄 Now monitoring dev cycle (SM → Architect → Dev → QA). I\'ll report agent handoffs and progress.');
|
|
1689
|
+
} else {
|
|
1690
|
+
this._phase = null;
|
|
1691
|
+
this._changeContext = null;
|
|
1692
|
+
}
|
|
1636
1693
|
}
|
|
1637
1694
|
|
|
1638
1695
|
// ── Shared ─────────────────────────────────────────────────────────────────
|