aicodeman 0.4.5 → 0.4.7

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.
Files changed (129) hide show
  1. package/dist/orchestrator-loop.d.ts +124 -0
  2. package/dist/orchestrator-loop.d.ts.map +1 -0
  3. package/dist/orchestrator-loop.js +853 -0
  4. package/dist/orchestrator-loop.js.map +1 -0
  5. package/dist/orchestrator-planner.d.ts +58 -0
  6. package/dist/orchestrator-planner.d.ts.map +1 -0
  7. package/dist/orchestrator-planner.js +342 -0
  8. package/dist/orchestrator-planner.js.map +1 -0
  9. package/dist/orchestrator-verifier.d.ts +52 -0
  10. package/dist/orchestrator-verifier.d.ts.map +1 -0
  11. package/dist/orchestrator-verifier.js +234 -0
  12. package/dist/orchestrator-verifier.js.map +1 -0
  13. package/dist/prompts/index.d.ts +1 -0
  14. package/dist/prompts/index.d.ts.map +1 -1
  15. package/dist/prompts/index.js +1 -0
  16. package/dist/prompts/index.js.map +1 -1
  17. package/dist/prompts/orchestrator.d.ts +65 -0
  18. package/dist/prompts/orchestrator.d.ts.map +1 -0
  19. package/dist/prompts/orchestrator.js +112 -0
  20. package/dist/prompts/orchestrator.js.map +1 -0
  21. package/dist/state-store.d.ts +6 -0
  22. package/dist/state-store.d.ts.map +1 -1
  23. package/dist/state-store.js +21 -0
  24. package/dist/state-store.js.map +1 -1
  25. package/dist/types/app-state.d.ts +2 -0
  26. package/dist/types/app-state.d.ts.map +1 -1
  27. package/dist/types/app-state.js.map +1 -1
  28. package/dist/types/index.d.ts +2 -0
  29. package/dist/types/index.d.ts.map +1 -1
  30. package/dist/types/index.js +2 -0
  31. package/dist/types/index.js.map +1 -1
  32. package/dist/types/orchestrator.d.ts +211 -0
  33. package/dist/types/orchestrator.d.ts.map +1 -0
  34. package/dist/types/orchestrator.js +58 -0
  35. package/dist/types/orchestrator.js.map +1 -0
  36. package/dist/web/ports/index.d.ts +1 -0
  37. package/dist/web/ports/index.d.ts.map +1 -1
  38. package/dist/web/ports/orchestrator-port.d.ts +10 -0
  39. package/dist/web/ports/orchestrator-port.d.ts.map +1 -0
  40. package/dist/web/ports/orchestrator-port.js +6 -0
  41. package/dist/web/ports/orchestrator-port.js.map +1 -0
  42. package/dist/web/public/api-client.3adebdc2.js.gz +0 -0
  43. package/dist/web/public/app.cbf6e9e8.js +26 -0
  44. package/dist/web/public/app.cbf6e9e8.js.br +0 -0
  45. package/dist/web/public/app.cbf6e9e8.js.gz +0 -0
  46. package/dist/web/public/{constants.cd61abbc.js → constants.64161167.js} +14 -0
  47. package/dist/web/public/constants.64161167.js.br +0 -0
  48. package/dist/web/public/constants.64161167.js.gz +0 -0
  49. package/dist/web/public/index.html +23 -9
  50. package/dist/web/public/index.html.br +0 -0
  51. package/dist/web/public/index.html.gz +0 -0
  52. package/dist/web/public/input-cjk.92544c51.js.gz +0 -0
  53. package/dist/web/public/keyboard-accessory.9fb81db6.js.gz +0 -0
  54. package/dist/web/public/{mobile-handlers.65e5638d.js → mobile-handlers.1e2a8ef8.js} +86 -22
  55. package/dist/web/public/mobile-handlers.1e2a8ef8.js.br +0 -0
  56. package/dist/web/public/mobile-handlers.1e2a8ef8.js.gz +0 -0
  57. package/dist/web/public/mobile.fdd28a54.css +1 -0
  58. package/dist/web/public/mobile.fdd28a54.css.br +0 -0
  59. package/dist/web/public/mobile.fdd28a54.css.gz +0 -0
  60. package/dist/web/public/notification-manager.2d5ea8ec.js.gz +0 -0
  61. package/dist/web/public/orchestrator-panel.js +475 -0
  62. package/dist/web/public/orchestrator-panel.js.br +0 -0
  63. package/dist/web/public/orchestrator-panel.js.gz +0 -0
  64. package/dist/web/public/{panels-ui.d7f6be08.js → panels-ui.3dd2e29b.js} +25 -25
  65. package/dist/web/public/panels-ui.3dd2e29b.js.br +0 -0
  66. package/dist/web/public/panels-ui.3dd2e29b.js.gz +0 -0
  67. package/dist/web/public/ralph-panel.7b014f16.js.gz +0 -0
  68. package/dist/web/public/ralph-wizard.f31ab90e.js.gz +0 -0
  69. package/dist/web/public/respawn-ui.372c6ea7.js.gz +0 -0
  70. package/dist/web/public/session-ui.0a07c3b7.js.gz +0 -0
  71. package/dist/web/public/settings-ui.94c57184.js.gz +0 -0
  72. package/dist/web/public/styles.8e110d27.css +1 -0
  73. package/dist/web/public/styles.8e110d27.css.br +0 -0
  74. package/dist/web/public/styles.8e110d27.css.gz +0 -0
  75. package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
  76. package/dist/web/public/sw.js.gz +0 -0
  77. package/dist/web/public/terminal-ui.9b40798a.js +3 -0
  78. package/dist/web/public/terminal-ui.9b40798a.js.br +0 -0
  79. package/dist/web/public/terminal-ui.9b40798a.js.gz +0 -0
  80. package/dist/web/public/upload.html.gz +0 -0
  81. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  82. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  83. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  84. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  85. package/dist/web/public/vendor/xterm.css.gz +0 -0
  86. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  87. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  88. package/dist/web/routes/index.d.ts +1 -0
  89. package/dist/web/routes/index.d.ts.map +1 -1
  90. package/dist/web/routes/index.js +1 -0
  91. package/dist/web/routes/index.js.map +1 -1
  92. package/dist/web/routes/orchestrator-routes.d.ts +21 -0
  93. package/dist/web/routes/orchestrator-routes.d.ts.map +1 -0
  94. package/dist/web/routes/orchestrator-routes.js +230 -0
  95. package/dist/web/routes/orchestrator-routes.js.map +1 -0
  96. package/dist/web/routes/session-routes.d.ts.map +1 -1
  97. package/dist/web/routes/session-routes.js +62 -9
  98. package/dist/web/routes/session-routes.js.map +1 -1
  99. package/dist/web/schemas.d.ts +25 -0
  100. package/dist/web/schemas.d.ts.map +1 -1
  101. package/dist/web/schemas.js +22 -0
  102. package/dist/web/schemas.js.map +1 -1
  103. package/dist/web/server.d.ts +2 -0
  104. package/dist/web/server.d.ts.map +1 -1
  105. package/dist/web/server.js +23 -1
  106. package/dist/web/server.js.map +1 -1
  107. package/dist/web/sse-events.d.ts +37 -1
  108. package/dist/web/sse-events.d.ts.map +1 -1
  109. package/dist/web/sse-events.js +39 -1
  110. package/dist/web/sse-events.js.map +1 -1
  111. package/package.json +1 -1
  112. package/dist/web/public/app.9dab4ee2.js +0 -26
  113. package/dist/web/public/app.9dab4ee2.js.br +0 -0
  114. package/dist/web/public/app.9dab4ee2.js.gz +0 -0
  115. package/dist/web/public/constants.cd61abbc.js.br +0 -0
  116. package/dist/web/public/constants.cd61abbc.js.gz +0 -0
  117. package/dist/web/public/mobile-handlers.65e5638d.js.br +0 -0
  118. package/dist/web/public/mobile-handlers.65e5638d.js.gz +0 -0
  119. package/dist/web/public/mobile.b09497a4.css +0 -1
  120. package/dist/web/public/mobile.b09497a4.css.br +0 -0
  121. package/dist/web/public/mobile.b09497a4.css.gz +0 -0
  122. package/dist/web/public/panels-ui.d7f6be08.js.br +0 -0
  123. package/dist/web/public/panels-ui.d7f6be08.js.gz +0 -0
  124. package/dist/web/public/styles.b8ec2f5a.css +0 -1
  125. package/dist/web/public/styles.b8ec2f5a.css.br +0 -0
  126. package/dist/web/public/styles.b8ec2f5a.css.gz +0 -0
  127. package/dist/web/public/terminal-ui.e4565c7b.js +0 -3
  128. package/dist/web/public/terminal-ui.e4565c7b.js.br +0 -0
  129. package/dist/web/public/terminal-ui.e4565c7b.js.gz +0 -0
@@ -0,0 +1,475 @@
1
+ /**
2
+ * @fileoverview Orchestrator loop panel — plan-based autonomous execution UI.
3
+ * Shows orchestrator state, plan phases, task progress, and verification results.
4
+ * Provides controls for start, approve, reject, pause, resume, stop, skip, retry.
5
+ *
6
+ * @mixin Extends CodemanApp.prototype via Object.assign
7
+ * @dependency app.js (CodemanApp class, this.orchestratorState)
8
+ * @dependency constants.js (SSE_EVENTS, escapeHtml)
9
+ * @loadorder 9.5 of 16 — loaded after ralph-panel.js, before settings-ui.js
10
+ */
11
+
12
+ // ═══════════════════════════════════════════════════════════════
13
+ // State color/label mappings
14
+ // ═══════════════════════════════════════════════════════════════
15
+
16
+ const ORCH_STATE_COLORS = {
17
+ idle: '#6b7280',
18
+ planning: '#f59e0b',
19
+ approval: '#8b5cf6',
20
+ executing: '#3b82f6',
21
+ verifying: '#06b6d4',
22
+ replanning: '#f97316',
23
+ completed: '#22c55e',
24
+ failed: '#ef4444',
25
+ paused: '#9ca3af',
26
+ };
27
+
28
+ const ORCH_PHASE_STATUS_ICONS = {
29
+ pending: '\u25cb', // ○
30
+ executing: '\u25d4', // ◔
31
+ passed: '\u2713', // ✓
32
+ failed: '\u2717', // ✗
33
+ skipped: '\u2192', // →
34
+ };
35
+
36
+ Object.assign(CodemanApp.prototype, {
37
+
38
+ // ═══════════════════════════════════════════════════════════════
39
+ // SSE Event Handlers
40
+ // ═══════════════════════════════════════════════════════════════
41
+
42
+ _onOrchestratorStateChanged(data) {
43
+ if (!this.orchestratorState) this.orchestratorState = {};
44
+ this.orchestratorState.state = data.state;
45
+ if (data.state === 'planning') {
46
+ this.orchestratorState.planProgress = [];
47
+ this.showOrchestratorPanel();
48
+ }
49
+ this.renderOrchestratorPanel();
50
+ },
51
+
52
+ _onOrchestratorPlanProgress(data) {
53
+ if (!this.orchestratorState) this.orchestratorState = {};
54
+ if (!this.orchestratorState.planProgress) this.orchestratorState.planProgress = [];
55
+ this.orchestratorState.planProgress.push({ phase: data.phase, detail: data.detail, time: Date.now() });
56
+ this.renderOrchestratorPanel();
57
+ },
58
+
59
+ _onOrchestratorPlanReady(data) {
60
+ if (!this.orchestratorState) this.orchestratorState = {};
61
+ this.orchestratorState.plan = data.plan;
62
+ this.orchestratorState.state = 'approval';
63
+ this.showOrchestratorPanel();
64
+ this.renderOrchestratorPanel();
65
+ },
66
+
67
+ _onOrchestratorPhaseStarted(data) {
68
+ if (!this.orchestratorState) return;
69
+ this._updateOrchestratorPhase(data.phase);
70
+ this.renderOrchestratorPanel();
71
+ },
72
+
73
+ _onOrchestratorPhaseCompleted(data) {
74
+ if (!this.orchestratorState) return;
75
+ this._updateOrchestratorPhase(data.phase);
76
+ this.renderOrchestratorPanel();
77
+ },
78
+
79
+ _onOrchestratorPhaseFailed(data) {
80
+ if (!this.orchestratorState) return;
81
+ this._updateOrchestratorPhase(data.phase);
82
+ this.renderOrchestratorPanel();
83
+ },
84
+
85
+ _onOrchestratorVerification(data) {
86
+ if (!this.orchestratorState) return;
87
+ // Store verification result on the phase
88
+ if (this.orchestratorState.plan) {
89
+ const phase = this.orchestratorState.plan.phases.find(p => p.id === data.phaseId);
90
+ if (phase) phase._lastVerification = data.result;
91
+ }
92
+ this.renderOrchestratorPanel();
93
+ },
94
+
95
+ _onOrchestratorTaskAssigned(data) {
96
+ if (!this.orchestratorState) return;
97
+ this._updateOrchestratorTask(data.task);
98
+ this.renderOrchestratorPanel();
99
+ },
100
+
101
+ _onOrchestratorTaskCompleted(data) {
102
+ if (!this.orchestratorState) return;
103
+ this._updateOrchestratorTask(data.task);
104
+ this.renderOrchestratorPanel();
105
+ },
106
+
107
+ _onOrchestratorTaskFailed(data) {
108
+ if (!this.orchestratorState) return;
109
+ this._updateOrchestratorTask(data.task);
110
+ this.renderOrchestratorPanel();
111
+ },
112
+
113
+ _onOrchestratorCompleted(data) {
114
+ if (!this.orchestratorState) this.orchestratorState = {};
115
+ this.orchestratorState.state = 'completed';
116
+ this.orchestratorState.stats = data.stats;
117
+ this.renderOrchestratorPanel();
118
+ },
119
+
120
+ _onOrchestratorError(data) {
121
+ if (!this.orchestratorState) this.orchestratorState = {};
122
+ this.orchestratorState.state = 'failed';
123
+ this.orchestratorState.lastError = data.error;
124
+ this.renderOrchestratorPanel();
125
+ },
126
+
127
+ // ═══════════════════════════════════════════════════════════════
128
+ // Internal helpers
129
+ // ═══════════════════════════════════════════════════════════════
130
+
131
+ _updateOrchestratorPhase(updatedPhase) {
132
+ if (!this.orchestratorState?.plan) return;
133
+ const idx = this.orchestratorState.plan.phases.findIndex(p => p.id === updatedPhase.id);
134
+ if (idx >= 0) this.orchestratorState.plan.phases[idx] = updatedPhase;
135
+ },
136
+
137
+ _updateOrchestratorTask(updatedTask) {
138
+ if (!this.orchestratorState?.plan) return;
139
+ for (const phase of this.orchestratorState.plan.phases) {
140
+ const idx = phase.tasks.findIndex(t => t.id === updatedTask.id);
141
+ if (idx >= 0) {
142
+ phase.tasks[idx] = updatedTask;
143
+ return;
144
+ }
145
+ }
146
+ },
147
+
148
+ // ═══════════════════════════════════════════════════════════════
149
+ // Panel visibility
150
+ // ═══════════════════════════════════════════════════════════════
151
+
152
+ showOrchestratorPanel() {
153
+ this.orchestratorPanelVisible = true;
154
+ const panel = document.getElementById('orchestratorPanel');
155
+ if (panel) panel.style.display = '';
156
+ this.renderOrchestratorPanel();
157
+ },
158
+
159
+ closeOrchestratorPanel() {
160
+ this.orchestratorPanelVisible = false;
161
+ const panel = document.getElementById('orchestratorPanel');
162
+ if (panel) panel.style.display = 'none';
163
+ },
164
+
165
+ toggleOrchestratorPanel() {
166
+ if (this.orchestratorPanelVisible) {
167
+ this.closeOrchestratorPanel();
168
+ } else {
169
+ this.showOrchestratorPanel();
170
+ }
171
+ },
172
+
173
+ // ═══════════════════════════════════════════════════════════════
174
+ // API calls
175
+ // ═══════════════════════════════════════════════════════════════
176
+
177
+ async orchestratorStart(goal, config) {
178
+ try {
179
+ const res = await fetch('/api/orchestrator/start', {
180
+ method: 'POST',
181
+ headers: { 'Content-Type': 'application/json' },
182
+ body: JSON.stringify({ goal, config }),
183
+ });
184
+ const data = await res.json();
185
+ if (data.ok) {
186
+ this.orchestratorState = { state: 'planning', plan: null };
187
+ this.showOrchestratorPanel();
188
+ this.renderOrchestratorPanel();
189
+ }
190
+ return data;
191
+ } catch (err) {
192
+ console.error('[Orchestrator] Start failed:', err);
193
+ }
194
+ },
195
+
196
+ async orchestratorApprove() {
197
+ try {
198
+ await fetch('/api/orchestrator/approve', { method: 'POST' });
199
+ } catch (err) {
200
+ console.error('[Orchestrator] Approve failed:', err);
201
+ }
202
+ },
203
+
204
+ async orchestratorReject(feedback) {
205
+ try {
206
+ await fetch('/api/orchestrator/reject', {
207
+ method: 'POST',
208
+ headers: { 'Content-Type': 'application/json' },
209
+ body: JSON.stringify({ feedback }),
210
+ });
211
+ } catch (err) {
212
+ console.error('[Orchestrator] Reject failed:', err);
213
+ }
214
+ },
215
+
216
+ async orchestratorPause() {
217
+ try {
218
+ await fetch('/api/orchestrator/pause', { method: 'POST' });
219
+ } catch (err) {
220
+ console.error('[Orchestrator] Pause failed:', err);
221
+ }
222
+ },
223
+
224
+ async orchestratorResume() {
225
+ try {
226
+ await fetch('/api/orchestrator/resume', { method: 'POST' });
227
+ } catch (err) {
228
+ console.error('[Orchestrator] Resume failed:', err);
229
+ }
230
+ },
231
+
232
+ async orchestratorStop() {
233
+ try {
234
+ await fetch('/api/orchestrator/stop', { method: 'POST' });
235
+ this.orchestratorState = { state: 'idle' };
236
+ this.renderOrchestratorPanel();
237
+ } catch (err) {
238
+ console.error('[Orchestrator] Stop failed:', err);
239
+ }
240
+ },
241
+
242
+ async orchestratorSkipPhase(phaseId) {
243
+ try {
244
+ await fetch(`/api/orchestrator/phase/${phaseId}/skip`, { method: 'POST' });
245
+ } catch (err) {
246
+ console.error('[Orchestrator] Skip failed:', err);
247
+ }
248
+ },
249
+
250
+ async orchestratorRetryPhase(phaseId) {
251
+ try {
252
+ await fetch(`/api/orchestrator/phase/${phaseId}/retry`, { method: 'POST' });
253
+ } catch (err) {
254
+ console.error('[Orchestrator] Retry failed:', err);
255
+ }
256
+ },
257
+
258
+ async refreshOrchestratorStatus() {
259
+ try {
260
+ const res = await fetch('/api/orchestrator/status');
261
+ const data = await res.json();
262
+ if (data.ok) {
263
+ this.orchestratorState = data;
264
+ this.renderOrchestratorPanel();
265
+ }
266
+ } catch (err) {
267
+ console.error('[Orchestrator] Status fetch failed:', err);
268
+ }
269
+ },
270
+
271
+ // ═══════════════════════════════════════════════════════════════
272
+ // Rendering
273
+ // ═══════════════════════════════════════════════════════════════
274
+
275
+ renderOrchestratorPanel() {
276
+ const panel = document.getElementById('orchestratorPanel');
277
+ if (!panel) return;
278
+
279
+ const state = this.orchestratorState?.state || 'idle';
280
+ const plan = this.orchestratorState?.plan;
281
+
282
+ // Update badge
283
+ const badge = document.getElementById('orchestratorStateBadge');
284
+ if (badge) {
285
+ badge.textContent = state;
286
+ badge.style.background = ORCH_STATE_COLORS[state] || '#6b7280';
287
+ }
288
+
289
+ // Update action buttons
290
+ const actions = document.getElementById('orchestratorActions');
291
+ if (actions) {
292
+ actions.innerHTML = this._renderOrchestratorActions(state);
293
+ }
294
+
295
+ // Update body
296
+ const body = document.getElementById('orchestratorBody');
297
+ if (body) {
298
+ body.innerHTML = this._renderOrchestratorBody(state, plan);
299
+ }
300
+ },
301
+
302
+ _renderOrchestratorActions(state) {
303
+ const btn = (label, onclick, cls = '') =>
304
+ `<button class="orch-btn ${cls}" onclick="${onclick}">${label}</button>`;
305
+
306
+ switch (state) {
307
+ case 'idle':
308
+ case 'completed':
309
+ case 'failed':
310
+ return btn('New Goal', 'app.promptOrchestratorGoal()', 'orch-btn-primary');
311
+ case 'planning':
312
+ return btn('Cancel', 'app.orchestratorStop()', 'orch-btn-danger');
313
+ case 'approval':
314
+ return [
315
+ btn('Approve', 'app.orchestratorApprove()', 'orch-btn-primary'),
316
+ btn('Reject', 'app.promptOrchestratorReject()', 'orch-btn-warn'),
317
+ btn('Cancel', 'app.orchestratorStop()', 'orch-btn-danger'),
318
+ ].join('');
319
+ case 'executing':
320
+ case 'verifying':
321
+ case 'replanning':
322
+ return [
323
+ btn('Pause', 'app.orchestratorPause()'),
324
+ btn('Stop', 'app.orchestratorStop()', 'orch-btn-danger'),
325
+ ].join('');
326
+ case 'paused':
327
+ return [
328
+ btn('Resume', 'app.orchestratorResume()', 'orch-btn-primary'),
329
+ btn('Stop', 'app.orchestratorStop()', 'orch-btn-danger'),
330
+ ].join('');
331
+ default:
332
+ return '';
333
+ }
334
+ },
335
+
336
+ _renderOrchestratorBody(state, plan) {
337
+ if (state === 'idle' && !plan) {
338
+ return '<div class="orch-empty">No orchestration active. Click "New Goal" to start.</div>';
339
+ }
340
+
341
+ if (state === 'planning') {
342
+ const progress = this.orchestratorState?.planProgress || [];
343
+ let progressHtml = '';
344
+ if (progress.length > 0) {
345
+ const items = progress.map(p =>
346
+ `<div class="orch-progress-item"><span class="orch-progress-phase">${escapeHtml(p.phase)}</span> ${escapeHtml(p.detail)}</div>`
347
+ ).join('');
348
+ progressHtml = `<div class="orch-progress-log">${items}</div>`;
349
+ }
350
+ return `<div class="orch-planning"><div class="orch-spinner"></div>Generating plan...${progressHtml}</div>`;
351
+ }
352
+
353
+ if (!plan) return '';
354
+
355
+ const parts = [];
356
+
357
+ // Goal
358
+ parts.push(`<div class="orch-goal"><strong>Goal:</strong> ${escapeHtml(plan.goal.slice(0, 200))}</div>`);
359
+
360
+ // Progress summary
361
+ const completed = plan.phases.filter(p => p.status === 'passed' || p.status === 'skipped').length;
362
+ const total = plan.phases.length;
363
+ const pct = total > 0 ? Math.round((completed / total) * 100) : 0;
364
+ parts.push(`<div class="orch-progress-bar"><div class="orch-progress-fill" style="width:${pct}%"></div><span>${completed}/${total} phases</span></div>`);
365
+
366
+ // Phase list
367
+ parts.push('<div class="orch-phases">');
368
+ for (const phase of plan.phases) {
369
+ parts.push(this._renderOrchestratorPhase(phase, state));
370
+ }
371
+ parts.push('</div>');
372
+
373
+ // Stats (if completed/failed)
374
+ if (state === 'completed' || state === 'failed') {
375
+ const stats = this.orchestratorState?.stats;
376
+ if (stats) {
377
+ parts.push(this._renderOrchestratorStats(stats));
378
+ }
379
+ if (this.orchestratorState?.lastError) {
380
+ parts.push(`<div class="orch-error">Error: ${escapeHtml(this.orchestratorState.lastError)}</div>`);
381
+ }
382
+ }
383
+
384
+ return parts.join('');
385
+ },
386
+
387
+ _renderOrchestratorPhase(phase, orchState) {
388
+ const icon = ORCH_PHASE_STATUS_ICONS[phase.status] || '\u25cb';
389
+ const isActive = phase.status === 'executing';
390
+ const cls = `orch-phase ${isActive ? 'orch-phase-active' : ''} orch-phase-${phase.status}`;
391
+
392
+ let actions = '';
393
+ if (orchState === 'executing' || orchState === 'failed') {
394
+ if (phase.status === 'pending') {
395
+ actions += `<button class="orch-phase-btn" onclick="app.orchestratorSkipPhase('${phase.id}')" title="Skip">skip</button>`;
396
+ }
397
+ if (phase.status === 'failed') {
398
+ actions += `<button class="orch-phase-btn" onclick="app.orchestratorRetryPhase('${phase.id}')" title="Retry">retry</button>`;
399
+ }
400
+ }
401
+
402
+ // Task summary
403
+ const tasksDone = phase.tasks.filter(t => t.status === 'completed').length;
404
+ const tasksFailed = phase.tasks.filter(t => t.status === 'failed').length;
405
+ const tasksTotal = phase.tasks.length;
406
+ const taskSummary = `${tasksDone}/${tasksTotal}${tasksFailed > 0 ? ` (${tasksFailed} failed)` : ''}`;
407
+
408
+ // Duration
409
+ let duration = '';
410
+ if (phase.durationMs) {
411
+ const secs = Math.round(phase.durationMs / 1000);
412
+ duration = secs < 60 ? `${secs}s` : `${Math.floor(secs / 60)}m ${secs % 60}s`;
413
+ }
414
+
415
+ let html = `<div class="${cls}">`;
416
+ html += `<div class="orch-phase-header">`;
417
+ html += `<span class="orch-phase-icon">${icon}</span>`;
418
+ html += `<span class="orch-phase-name">${escapeHtml(phase.name)}</span>`;
419
+ html += `<span class="orch-phase-tasks">${taskSummary}</span>`;
420
+ if (duration) html += `<span class="orch-phase-duration">${duration}</span>`;
421
+ if (actions) html += `<span class="orch-phase-actions">${actions}</span>`;
422
+ html += `</div>`;
423
+
424
+ // Expanded task list for active phase
425
+ if (isActive || phase.status === 'failed') {
426
+ html += '<div class="orch-phase-tasks-list">';
427
+ for (const task of phase.tasks) {
428
+ const taskIcon = ORCH_PHASE_STATUS_ICONS[task.status] || '\u25cb';
429
+ const taskCls = `orch-task orch-task-${task.status}`;
430
+ html += `<div class="${taskCls}"><span class="orch-task-icon">${taskIcon}</span>`;
431
+ html += `<span class="orch-task-prompt">${escapeHtml(task.prompt.slice(0, 100))}</span>`;
432
+ if (task.error) html += `<span class="orch-task-error">${escapeHtml(task.error.slice(0, 80))}</span>`;
433
+ html += '</div>';
434
+ }
435
+ html += '</div>';
436
+ }
437
+
438
+ // Verification result
439
+ if (phase._lastVerification) {
440
+ const v = phase._lastVerification;
441
+ const vCls = v.passed ? 'orch-verify-pass' : 'orch-verify-fail';
442
+ html += `<div class="${vCls}">${v.passed ? 'Verified' : 'Failed'}: ${escapeHtml(v.summary || '')}</div>`;
443
+ }
444
+
445
+ html += '</div>';
446
+ return html;
447
+ },
448
+
449
+ _renderOrchestratorStats(stats) {
450
+ return `<div class="orch-stats">
451
+ <span>Phases: ${stats.phasesCompleted} done, ${stats.phasesFailed} failed</span>
452
+ <span>Tasks: ${stats.totalTasksCompleted} done, ${stats.totalTasksFailed} failed</span>
453
+ ${stats.replanCount > 0 ? `<span>Replans: ${stats.replanCount}</span>` : ''}
454
+ ${stats.totalDurationMs ? `<span>Duration: ${Math.round(stats.totalDurationMs / 60000)}m</span>` : ''}
455
+ </div>`;
456
+ },
457
+
458
+ // ═══════════════════════════════════════════════════════════════
459
+ // User prompts
460
+ // ═══════════════════════════════════════════════════════════════
461
+
462
+ promptOrchestratorGoal() {
463
+ const goal = prompt('Enter your goal for the orchestrator:');
464
+ if (goal && goal.trim()) {
465
+ this.orchestratorStart(goal.trim());
466
+ }
467
+ },
468
+
469
+ promptOrchestratorReject() {
470
+ const feedback = prompt('Feedback on the plan (what should change?):');
471
+ if (feedback && feedback.trim()) {
472
+ this.orchestratorReject(feedback.trim());
473
+ }
474
+ },
475
+ });