ai-maestro 1.0.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.
- package/CHANGELOG.md +11 -0
- package/LICENSE +15 -0
- package/README.md +727 -0
- package/bin/maestro.mjs +246 -0
- package/package.json +29 -0
- package/src/checkpoint.mjs +102 -0
- package/src/codex-diagnostics.mjs +682 -0
- package/src/codex-home-inspect.mjs +252 -0
- package/src/codex-home.mjs +258 -0
- package/src/commands.mjs +809 -0
- package/src/config.mjs +11 -0
- package/src/debug.mjs +164 -0
- package/src/encoding-guard.mjs +125 -0
- package/src/engines/ai-router-engine.mjs +37 -0
- package/src/engines/codex-engine.mjs +21 -0
- package/src/engines/engine.mjs +21 -0
- package/src/engines/registry.mjs +29 -0
- package/src/files.mjs +65 -0
- package/src/format.mjs +132 -0
- package/src/lock.mjs +439 -0
- package/src/memory-log.mjs +18 -0
- package/src/memory.mjs +1 -0
- package/src/orchestration/attempt-chain-runtime.mjs +627 -0
- package/src/orchestration/budget-manager.mjs +121 -0
- package/src/orchestration/capability-registry.mjs +108 -0
- package/src/orchestration/consolidator.mjs +772 -0
- package/src/orchestration/delegation-executor.mjs +262 -0
- package/src/orchestration/delegation-manager.mjs +116 -0
- package/src/orchestration/deployment-intent.mjs +36 -0
- package/src/orchestration/engine-history.mjs +110 -0
- package/src/orchestration/engine-policy.mjs +73 -0
- package/src/orchestration/engine-selector.mjs +187 -0
- package/src/orchestration/execution-context.mjs +45 -0
- package/src/orchestration/failure-classifier.mjs +136 -0
- package/src/orchestration/failure-evidence.mjs +237 -0
- package/src/orchestration/fallback-chain-lock.mjs +217 -0
- package/src/orchestration/fallback-chain-trail.mjs +218 -0
- package/src/orchestration/fallback-executor.mjs +146 -0
- package/src/orchestration/fallback-graph.mjs +106 -0
- package/src/orchestration/fallback-recommendation.mjs +73 -0
- package/src/orchestration/file-conflict-detector.mjs +126 -0
- package/src/orchestration/host-validation.mjs +241 -0
- package/src/orchestration/orchestration-loop.mjs +1971 -0
- package/src/orchestration/orchestration-runtime.mjs +1019 -0
- package/src/orchestration/orchestration-scheduler.mjs +135 -0
- package/src/orchestration/orchestration-trail.mjs +192 -0
- package/src/orchestration/preflight.mjs +212 -0
- package/src/orchestration/provider-health.mjs +566 -0
- package/src/orchestration/provider-router.mjs +352 -0
- package/src/orchestration/rc-check-adapters.mjs +817 -0
- package/src/orchestration/rc-check.mjs +544 -0
- package/src/orchestration/rc-policy.mjs +200 -0
- package/src/orchestration/runtime-gate.mjs +146 -0
- package/src/orchestration/sector-managers.mjs +215 -0
- package/src/orchestration/self-hosting-canary.mjs +231 -0
- package/src/orchestration/self-hosting-readiness.mjs +244 -0
- package/src/orchestration/spec-planner.mjs +877 -0
- package/src/orchestration/subtask-planner.mjs +176 -0
- package/src/orchestration/task-classifier.mjs +241 -0
- package/src/orchestration/verifier.mjs +1608 -0
- package/src/orchestration-commands.mjs +279 -0
- package/src/planner.mjs +38 -0
- package/src/project.mjs +1 -0
- package/src/run-diagnostics.mjs +641 -0
- package/src/runner.mjs +243 -0
- package/src/safety.mjs +116 -0
- package/src/schema.mjs +371 -0
- package/src/session-commands.mjs +521 -0
- package/src/shell.mjs +93 -0
- package/src/smart-planner.mjs +249 -0
- package/src/task-commands.mjs +1182 -0
- package/src/tasks.mjs +134 -0
- package/src/ui/commands.mjs +76 -0
- package/src/ui/events.mjs +45 -0
- package/src/ui/public/app.js +600 -0
- package/src/ui/public/index.html +88 -0
- package/src/ui/public/style.css +460 -0
- package/src/ui/server.mjs +112 -0
- package/src/ui/state.mjs +504 -0
- package/src/usage.mjs +178 -0
- package/src/workspace-diff.mjs +228 -0
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
const FILTERS = [
|
|
2
|
+
{ id: 'all', label: 'Todos' },
|
|
3
|
+
{ id: 'maestro', label: 'Maestro' },
|
|
4
|
+
{ id: 'codex', label: 'Codex' },
|
|
5
|
+
{ id: 'memory', label: 'Memory' },
|
|
6
|
+
{ id: 'safety', label: 'Safety' },
|
|
7
|
+
{ id: 'engine', label: 'Engine' },
|
|
8
|
+
{ id: 'ui', label: 'UI' },
|
|
9
|
+
{ id: 'issues', label: 'Erros/Bloqueios' }
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const COMMANDS = [
|
|
13
|
+
{ id: 'doctor', label: 'Doctor' },
|
|
14
|
+
{ id: 'codex-home', label: 'Codex Home' },
|
|
15
|
+
{ id: 'codex-test', label: 'Codex Test' },
|
|
16
|
+
{ id: 'task-list', label: 'Task List' },
|
|
17
|
+
{ id: 'task-next', label: 'Task Next' },
|
|
18
|
+
{ id: 'run-one', label: 'Run One' },
|
|
19
|
+
{ id: 'run-all-limit-3', label: 'Run 3' },
|
|
20
|
+
{ id: 'checkpoint', label: 'Checkpoint' },
|
|
21
|
+
{ id: 'handoff', label: 'Handoff' },
|
|
22
|
+
{ id: 'stats', label: 'Stats' },
|
|
23
|
+
{ id: 'release-check', label: 'Release Check' },
|
|
24
|
+
{ id: 'schema-check', label: 'Schema Check' }
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
const STATUS_META = {
|
|
28
|
+
idle: { key: 'idle', label: 'parado', icon: '⏸', tone: 'idle' },
|
|
29
|
+
running: { key: 'running', label: 'executando', icon: '▶', tone: 'running' },
|
|
30
|
+
thinking: { key: 'thinking', label: 'pensando', icon: '☁', tone: 'thinking' },
|
|
31
|
+
completed: { key: 'completed', label: 'finalizado', icon: '✓', tone: 'success' },
|
|
32
|
+
done: { key: 'completed', label: 'finalizado', icon: '✓', tone: 'success' },
|
|
33
|
+
completed_with_warnings: { key: 'completed_with_warnings', label: 'finalizado com alertas', icon: '⚠', tone: 'warning' },
|
|
34
|
+
blocked: { key: 'blocked', label: 'bloqueado', icon: '⛔', tone: 'blocked' },
|
|
35
|
+
needs_human: { key: 'needs_human', label: 'precisa de decisão humana', icon: '❔', tone: 'blocked' },
|
|
36
|
+
failed: { key: 'failed', label: 'erro', icon: '⚠', tone: 'error' },
|
|
37
|
+
error: { key: 'failed', label: 'erro', icon: '⚠', tone: 'error' },
|
|
38
|
+
success: { key: 'completed', label: 'finalizado', icon: '✓', tone: 'success' },
|
|
39
|
+
stopped: { key: 'idle', label: 'parado', icon: '⏸', tone: 'idle' },
|
|
40
|
+
warning: { key: 'warning', label: 'atenção', icon: '!', tone: 'warning' }
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const el = {
|
|
44
|
+
projectName: document.querySelector('#projectName'),
|
|
45
|
+
connectionStatus: document.querySelector('#connectionStatus'),
|
|
46
|
+
commandStatus: document.querySelector('#commandStatus'),
|
|
47
|
+
cards: document.querySelector('#cards'),
|
|
48
|
+
taskCounts: document.querySelector('#taskCounts'),
|
|
49
|
+
taskFilters: document.querySelector('#taskFilters'),
|
|
50
|
+
taskList: document.querySelector('#taskList'),
|
|
51
|
+
operationStatus: document.querySelector('#operationStatus'),
|
|
52
|
+
commandButtons: document.querySelector('#commandButtons'),
|
|
53
|
+
commandSummary: document.querySelector('#commandSummary'),
|
|
54
|
+
commandLog: document.querySelector('#commandLog'),
|
|
55
|
+
toggleCommandLogButton: document.querySelector('#toggleCommandLogButton'),
|
|
56
|
+
copyCommandLogButton: document.querySelector('#copyCommandLogButton'),
|
|
57
|
+
orchestraMeta: document.querySelector('#orchestraMeta'),
|
|
58
|
+
orchestraEngines: document.querySelector('#orchestraEngines'),
|
|
59
|
+
orchestraBudget: document.querySelector('#orchestraBudget'),
|
|
60
|
+
orchestraFallback: document.querySelector('#orchestraFallback'),
|
|
61
|
+
timelineCounts: document.querySelector('#timelineCounts'),
|
|
62
|
+
timelineFilters: document.querySelector('#timelineFilters'),
|
|
63
|
+
timelineList: document.querySelector('#timelineList'),
|
|
64
|
+
copyHandoffButton: document.querySelector('#copyHandoffButton'),
|
|
65
|
+
clearVisualButton: document.querySelector('#clearVisualButton'),
|
|
66
|
+
toast: document.querySelector('#toast')
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const app = {
|
|
70
|
+
state: null,
|
|
71
|
+
filter: 'all',
|
|
72
|
+
commandBusy: false,
|
|
73
|
+
connection: 'connecting',
|
|
74
|
+
commandLogExpanded: false,
|
|
75
|
+
commandResult: null,
|
|
76
|
+
commandRawText: '',
|
|
77
|
+
expandedRawKeys: new Set(),
|
|
78
|
+
toastTimer: null
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
function text(value) {
|
|
82
|
+
return value == null ? '' : String(value);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function normalizeStatus(raw) {
|
|
86
|
+
return app.state?.statusMap?.[String(raw || 'idle')] || STATUS_META[String(raw || 'idle')] || STATUS_META.idle;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function normalizeGroup(raw) {
|
|
90
|
+
return app.state?.groupMap?.[String(raw || 'maestro')] || { key: String(raw || 'maestro'), label: String(raw || 'Maestro'), icon: '▣' };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function formatRelativeTime(value) {
|
|
94
|
+
if (!value) return 'agora';
|
|
95
|
+
const time = new Date(value).getTime();
|
|
96
|
+
if (Number.isNaN(time)) return 'agora';
|
|
97
|
+
const delta = Math.max(0, Date.now() - time);
|
|
98
|
+
const minutes = Math.floor(delta / 60000);
|
|
99
|
+
if (minutes < 1) return 'agora';
|
|
100
|
+
if (minutes < 60) return `${minutes}m`;
|
|
101
|
+
const hours = Math.floor(minutes / 60);
|
|
102
|
+
if (hours < 24) return `${hours}h`;
|
|
103
|
+
const days = Math.floor(hours / 24);
|
|
104
|
+
return `${days}d`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function isIssue(entry) {
|
|
108
|
+
return entry.issue || entry.status === 'blocked' || entry.status === 'failed';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function matchesFilter(entry) {
|
|
112
|
+
if (app.filter === 'all') return true;
|
|
113
|
+
if (app.filter === 'issues') return isIssue(entry);
|
|
114
|
+
return entry.group === app.filter;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function setToast(message, sticky = false) {
|
|
118
|
+
el.toast.textContent = message;
|
|
119
|
+
el.toast.classList.add('toast--visible');
|
|
120
|
+
if (app.toastTimer) clearTimeout(app.toastTimer);
|
|
121
|
+
if (!sticky) {
|
|
122
|
+
app.toastTimer = setTimeout(() => {
|
|
123
|
+
el.toast.classList.remove('toast--visible');
|
|
124
|
+
el.toast.textContent = '';
|
|
125
|
+
}, 2200);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function setConnection(status) {
|
|
130
|
+
app.connection = status;
|
|
131
|
+
const meta = {
|
|
132
|
+
connecting: { label: 'conectando', tone: 'warning' },
|
|
133
|
+
connected: { label: 'conectado', tone: 'success' },
|
|
134
|
+
reconnecting: { label: 'reconectando', tone: 'warning' },
|
|
135
|
+
disconnected: { label: 'desconectado', tone: 'error' }
|
|
136
|
+
}[status] || { label: status, tone: 'warning' };
|
|
137
|
+
el.connectionStatus.textContent = meta.label;
|
|
138
|
+
el.connectionStatus.className = `chip chip--${meta.tone}`;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function setCommandBusy(busy) {
|
|
142
|
+
app.commandBusy = busy;
|
|
143
|
+
const meta = busy ? { label: 'executando', tone: 'running' } : { label: 'parado', tone: 'idle' };
|
|
144
|
+
el.commandStatus.textContent = meta.label;
|
|
145
|
+
el.commandStatus.className = `chip chip--${meta.tone}`;
|
|
146
|
+
render();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function setCommandLogExpanded(expanded) {
|
|
150
|
+
app.commandLogExpanded = expanded;
|
|
151
|
+
renderCommandPanel();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function logRawKey(key) {
|
|
155
|
+
return app.expandedRawKeys.has(key);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function toggleRawKey(key) {
|
|
159
|
+
if (app.expandedRawKeys.has(key)) {
|
|
160
|
+
app.expandedRawKeys.delete(key);
|
|
161
|
+
} else {
|
|
162
|
+
app.expandedRawKeys.add(key);
|
|
163
|
+
}
|
|
164
|
+
render();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function escapeClipboardText(textValue) {
|
|
168
|
+
return textValue || '';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function copyText(value, successMessage) {
|
|
172
|
+
const clipboardValue = escapeClipboardText(value);
|
|
173
|
+
try {
|
|
174
|
+
await navigator.clipboard.writeText(clipboardValue);
|
|
175
|
+
setToast(successMessage);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
const fallback = document.createElement('textarea');
|
|
178
|
+
fallback.value = clipboardValue;
|
|
179
|
+
fallback.setAttribute('readonly', 'true');
|
|
180
|
+
fallback.style.position = 'fixed';
|
|
181
|
+
fallback.style.opacity = '0';
|
|
182
|
+
document.body.appendChild(fallback);
|
|
183
|
+
fallback.select();
|
|
184
|
+
document.execCommand('copy');
|
|
185
|
+
document.body.removeChild(fallback);
|
|
186
|
+
setToast(successMessage);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function clearVisualState() {
|
|
191
|
+
app.expandedRawKeys.clear();
|
|
192
|
+
app.commandLogExpanded = false;
|
|
193
|
+
app.commandRawText = '';
|
|
194
|
+
el.commandSummary.textContent = 'Nenhum comando executado.';
|
|
195
|
+
el.commandLog.textContent = '';
|
|
196
|
+
el.commandLog.classList.add('terminal--hidden');
|
|
197
|
+
render();
|
|
198
|
+
setToast('Visual limpo');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function createChip(meta, extraClass = '') {
|
|
202
|
+
const chip = document.createElement('span');
|
|
203
|
+
chip.className = `chip chip--${meta.tone || 'neutral'}${extraClass ? ' ' + extraClass : ''}`;
|
|
204
|
+
chip.textContent = `${meta.icon} ${meta.label}`;
|
|
205
|
+
return chip;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function createButton(label, handler, options = {}) {
|
|
209
|
+
const button = document.createElement('button');
|
|
210
|
+
button.type = 'button';
|
|
211
|
+
button.textContent = label;
|
|
212
|
+
if (options.disabled) button.disabled = true;
|
|
213
|
+
if (options.className) button.className = options.className;
|
|
214
|
+
if (options.localAction) button.dataset.localAction = 'true';
|
|
215
|
+
if (options.serverAction) button.dataset.serverAction = 'true';
|
|
216
|
+
button.addEventListener('click', handler);
|
|
217
|
+
return button;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function renderFilterBar(container) {
|
|
221
|
+
container.innerHTML = '';
|
|
222
|
+
for (const filter of FILTERS) {
|
|
223
|
+
const button = createButton(filter.label, () => {
|
|
224
|
+
app.filter = filter.id;
|
|
225
|
+
render();
|
|
226
|
+
}, { className: app.filter === filter.id ? 'filter filter--active' : 'filter', localAction: true });
|
|
227
|
+
container.appendChild(button);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function renderCards() {
|
|
232
|
+
el.cards.innerHTML = '';
|
|
233
|
+
const cards = app.state?.cards || [];
|
|
234
|
+
for (const card of cards) {
|
|
235
|
+
const meta = normalizeStatus(card.status);
|
|
236
|
+
const cardEl = document.createElement('article');
|
|
237
|
+
cardEl.className = 'card';
|
|
238
|
+
|
|
239
|
+
const header = document.createElement('div');
|
|
240
|
+
header.className = 'card-head';
|
|
241
|
+
const title = document.createElement('h3');
|
|
242
|
+
title.textContent = card.title;
|
|
243
|
+
header.appendChild(title);
|
|
244
|
+
header.appendChild(createChip(meta));
|
|
245
|
+
|
|
246
|
+
const detail = document.createElement('p');
|
|
247
|
+
detail.className = 'card-detail';
|
|
248
|
+
detail.textContent = text(card.detail);
|
|
249
|
+
|
|
250
|
+
const eventLine = document.createElement('div');
|
|
251
|
+
eventLine.className = 'card-event';
|
|
252
|
+
const lastEvent = card.lastEvent;
|
|
253
|
+
if (lastEvent) {
|
|
254
|
+
eventLine.appendChild(document.createTextNode(`${lastEvent.icon} ${lastEvent.summary} · ${formatRelativeTime(lastEvent.timestamp)}`));
|
|
255
|
+
} else {
|
|
256
|
+
eventLine.textContent = card.lastEventLabel || 'Sem evento recente';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const actions = document.createElement('div');
|
|
260
|
+
actions.className = 'inline-actions';
|
|
261
|
+
const rawKey = `card:${card.id}`;
|
|
262
|
+
const rawVisible = logRawKey(rawKey);
|
|
263
|
+
actions.appendChild(createButton(rawVisible ? 'Ocultar log bruto' : 'Ver log bruto', () => toggleRawKey(rawKey), { localAction: true }));
|
|
264
|
+
actions.appendChild(createButton('Copiar log', () => copyText(lastEvent ? lastEvent.rawText : text(card.lastEventLabel), 'Log copiado'), { localAction: true }));
|
|
265
|
+
|
|
266
|
+
const raw = document.createElement('pre');
|
|
267
|
+
raw.className = rawVisible ? 'terminal terminal--card' : 'terminal terminal--card terminal--hidden';
|
|
268
|
+
raw.textContent = lastEvent ? lastEvent.rawText : text(card.lastEventLabel);
|
|
269
|
+
|
|
270
|
+
cardEl.append(header, detail, eventLine, actions, raw);
|
|
271
|
+
el.cards.appendChild(cardEl);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function renderCommandButtons() {
|
|
276
|
+
el.commandButtons.innerHTML = '';
|
|
277
|
+
for (const command of COMMANDS) {
|
|
278
|
+
el.commandButtons.appendChild(createButton(
|
|
279
|
+
command.label,
|
|
280
|
+
() => runCommand(command.id),
|
|
281
|
+
{ disabled: app.commandBusy, serverAction: true, className: 'action-button action-button--primary' }
|
|
282
|
+
));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function renderTasks() {
|
|
287
|
+
const tasks = app.state?.tasks || [];
|
|
288
|
+
el.taskCounts.textContent = tasks.length + ' tarefas · ' + Object.entries(app.state?.taskCounts || {}).map(([key, value]) => `${key}: ${value}`).join(' ');
|
|
289
|
+
el.taskList.innerHTML = '';
|
|
290
|
+
el.taskFilters.innerHTML = '';
|
|
291
|
+
const taskFilterLabel = document.createElement('span');
|
|
292
|
+
taskFilterLabel.className = 'meta';
|
|
293
|
+
taskFilterLabel.textContent = `Filtro ativo: ${FILTERS.find(filter => filter.id === app.filter)?.label || 'Todos'}`;
|
|
294
|
+
el.taskFilters.appendChild(taskFilterLabel);
|
|
295
|
+
|
|
296
|
+
for (const task of tasks) {
|
|
297
|
+
const item = document.createElement('article');
|
|
298
|
+
item.className = 'task-row';
|
|
299
|
+
|
|
300
|
+
const head = document.createElement('div');
|
|
301
|
+
head.className = 'task-row__head';
|
|
302
|
+
const statusMeta = task.statusView || normalizeStatus(task.status);
|
|
303
|
+
head.appendChild(createChip(statusMeta));
|
|
304
|
+
|
|
305
|
+
const title = document.createElement('div');
|
|
306
|
+
title.className = 'task-row__title';
|
|
307
|
+
title.textContent = `#${task.id} ${task.title || task.description || ''}`;
|
|
308
|
+
|
|
309
|
+
const meta = document.createElement('div');
|
|
310
|
+
meta.className = 'task-row__meta';
|
|
311
|
+
meta.textContent = `${task.priority || 'medium'} · ${task.engine || 'codex9'} · ${task.lastUpdatedAgo || 'agora'}`;
|
|
312
|
+
|
|
313
|
+
const actions = document.createElement('div');
|
|
314
|
+
actions.className = 'inline-actions';
|
|
315
|
+
for (const action of task.actions || []) {
|
|
316
|
+
actions.appendChild(createButton(
|
|
317
|
+
action.label,
|
|
318
|
+
() => runCommand(action.command, { taskId: action.taskId }),
|
|
319
|
+
{ disabled: app.commandBusy, serverAction: true, className: 'action-button action-button--' + (action.tone || 'primary') }
|
|
320
|
+
));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
item.append(head, title, meta, actions);
|
|
324
|
+
el.taskList.appendChild(item);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function kvRow(container, label, value) {
|
|
329
|
+
const row = document.createElement('div');
|
|
330
|
+
row.className = 'orchestra-kv-row';
|
|
331
|
+
const labelEl = document.createElement('span');
|
|
332
|
+
labelEl.className = 'orchestra-kv-label';
|
|
333
|
+
labelEl.textContent = label;
|
|
334
|
+
const valueEl = document.createElement('span');
|
|
335
|
+
valueEl.className = 'orchestra-kv-value';
|
|
336
|
+
valueEl.textContent = String(value);
|
|
337
|
+
row.append(labelEl, valueEl);
|
|
338
|
+
container.appendChild(row);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function renderOrchestra() {
|
|
342
|
+
const orchestra = app.state?.orchestra;
|
|
343
|
+
el.orchestraEngines.innerHTML = '';
|
|
344
|
+
el.orchestraBudget.innerHTML = '';
|
|
345
|
+
el.orchestraFallback.innerHTML = '';
|
|
346
|
+
if (!orchestra) {
|
|
347
|
+
el.orchestraMeta.textContent = 'Sem dados de orquestra (.maestro/engines.json ausente)';
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
el.orchestraMeta.textContent = `${orchestra.enabledEngineCount} de ${orchestra.engines.length} engines habilitadas`;
|
|
351
|
+
|
|
352
|
+
for (const engine of orchestra.engines) {
|
|
353
|
+
const card = document.createElement('div');
|
|
354
|
+
card.className = 'orchestra-engine';
|
|
355
|
+
const head = document.createElement('div');
|
|
356
|
+
head.className = 'orchestra-engine__head';
|
|
357
|
+
const name = document.createElement('span');
|
|
358
|
+
name.className = 'orchestra-engine__name';
|
|
359
|
+
name.textContent = engine.id;
|
|
360
|
+
head.appendChild(name);
|
|
361
|
+
head.appendChild(createChip({
|
|
362
|
+
icon: engine.enabled ? '✓' : '⏸',
|
|
363
|
+
label: engine.enabled ? 'enabled' : 'disabled',
|
|
364
|
+
tone: engine.enabled ? 'success' : 'idle'
|
|
365
|
+
}));
|
|
366
|
+
head.appendChild(createChip({
|
|
367
|
+
icon: engine.validated ? '✓' : '?',
|
|
368
|
+
label: engine.validated ? 'validated' : 'unvalidated',
|
|
369
|
+
tone: engine.validated ? 'success' : 'warning'
|
|
370
|
+
}));
|
|
371
|
+
const detail = document.createElement('p');
|
|
372
|
+
detail.className = 'orchestra-engine__detail';
|
|
373
|
+
detail.textContent = `${engine.provider} · custo: ${engine.costClass} · papéis: ${engine.roles.length ? engine.roles.join(', ') : 'nenhum'}`;
|
|
374
|
+
card.append(head, detail);
|
|
375
|
+
el.orchestraEngines.appendChild(card);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (orchestra.budget) {
|
|
379
|
+
kvRow(el.orchestraBudget, 'preferFree', orchestra.budget.preferFree);
|
|
380
|
+
kvRow(el.orchestraBudget, 'allowPaid', orchestra.budget.allowPaid);
|
|
381
|
+
kvRow(el.orchestraBudget, 'allowClaudeReview', orchestra.budget.allowClaudeReview);
|
|
382
|
+
kvRow(el.orchestraBudget, 'allowDirectOpenAI', orchestra.budget.allowDirectOpenAI);
|
|
383
|
+
kvRow(el.orchestraBudget, 'estimatedTokenBudget', orchestra.budget.estimatedTokenBudget);
|
|
384
|
+
kvRow(el.orchestraBudget, 'maxRunsPerTask', orchestra.budget.maxRunsPerTask);
|
|
385
|
+
} else {
|
|
386
|
+
kvRow(el.orchestraBudget, 'status', 'sem .maestro/budget.json');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
kvRow(el.orchestraFallback, 'regras de fallback', orchestra.fallbackRuleCount);
|
|
390
|
+
kvRow(el.orchestraFallback, 'profundidade máxima', orchestra.delegationLimits.maxDepth);
|
|
391
|
+
kvRow(el.orchestraFallback, 'subagentes por gerente', orchestra.delegationLimits.maxSubagentsPerManager);
|
|
392
|
+
kvRow(el.orchestraFallback, 'engines tentadas por task', orchestra.delegationLimits.maxEnginesTriedPerTask);
|
|
393
|
+
kvRow(el.orchestraFallback, 'gerentes de setor', orchestra.sectorManagers.length);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function renderTimeline() {
|
|
397
|
+
const timeline = (app.state?.timeline || []).filter(matchesFilter);
|
|
398
|
+
el.timelineCounts.textContent = `${timeline.length} de ${(app.state?.timeline || []).length} eventos`;
|
|
399
|
+
el.timelineList.innerHTML = '';
|
|
400
|
+
renderFilterBar(el.timelineFilters);
|
|
401
|
+
|
|
402
|
+
for (const entry of timeline) {
|
|
403
|
+
const item = document.createElement('article');
|
|
404
|
+
item.className = 'timeline-row';
|
|
405
|
+
|
|
406
|
+
const head = document.createElement('div');
|
|
407
|
+
head.className = 'timeline-row__head';
|
|
408
|
+
const groupMeta = normalizeGroup(entry.group);
|
|
409
|
+
const groupTone = {
|
|
410
|
+
maestro: 'running',
|
|
411
|
+
codex: 'thinking',
|
|
412
|
+
memory: 'success',
|
|
413
|
+
safety: 'blocked',
|
|
414
|
+
engine: 'warning',
|
|
415
|
+
ui: 'idle',
|
|
416
|
+
issue: 'error'
|
|
417
|
+
}[groupMeta.key] || 'idle';
|
|
418
|
+
head.appendChild(createChip({ ...groupMeta, tone: isIssue(entry) ? 'error' : groupTone }));
|
|
419
|
+
head.appendChild(createChip(normalizeStatus(entry.status)));
|
|
420
|
+
const time = document.createElement('span');
|
|
421
|
+
time.className = 'meta';
|
|
422
|
+
time.textContent = formatRelativeTime(entry.timestamp);
|
|
423
|
+
head.appendChild(time);
|
|
424
|
+
|
|
425
|
+
const summary = document.createElement('p');
|
|
426
|
+
summary.className = 'timeline-row__summary';
|
|
427
|
+
summary.textContent = `${entry.icon} ${entry.summary}`;
|
|
428
|
+
|
|
429
|
+
const detail = document.createElement('div');
|
|
430
|
+
detail.className = 'timeline-row__detail';
|
|
431
|
+
detail.textContent = `${entry.agent} · ${entry.kind} · ${entry.source}`;
|
|
432
|
+
|
|
433
|
+
const actions = document.createElement('div');
|
|
434
|
+
actions.className = 'inline-actions';
|
|
435
|
+
const rawKey = `timeline:${entry.source}:${entry.timestamp}:${entry.commandId || entry.taskId || entry.kind}`;
|
|
436
|
+
const rawVisible = logRawKey(rawKey);
|
|
437
|
+
actions.appendChild(createButton(rawVisible ? 'Ocultar log bruto' : 'Ver log bruto', () => toggleRawKey(rawKey), { localAction: true }));
|
|
438
|
+
actions.appendChild(createButton('Copiar log', () => copyText(entry.rawText, 'Log copiado'), { localAction: true }));
|
|
439
|
+
|
|
440
|
+
const raw = document.createElement('pre');
|
|
441
|
+
raw.className = rawVisible ? 'terminal terminal--timeline' : 'terminal terminal--timeline terminal--hidden';
|
|
442
|
+
raw.textContent = entry.rawText;
|
|
443
|
+
|
|
444
|
+
item.append(head, summary, detail, actions, raw);
|
|
445
|
+
el.timelineList.appendChild(item);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function renderCommandPanel() {
|
|
450
|
+
const result = app.commandResult;
|
|
451
|
+
const summary = result
|
|
452
|
+
? `${result.commandId}${result.taskId ? ' #' + result.taskId : ''} · ${normalizeStatus(result.status).label} · exit ${result.exitCode} · ${result.durationMs}ms`
|
|
453
|
+
: 'Nenhum comando executado.';
|
|
454
|
+
el.commandSummary.textContent = summary;
|
|
455
|
+
el.commandLog.textContent = result ? (result.stdout || result.stderr || result.summary || '') : '';
|
|
456
|
+
el.commandLog.className = app.commandLogExpanded ? 'terminal' : 'terminal terminal--hidden';
|
|
457
|
+
el.toggleCommandLogButton.textContent = app.commandLogExpanded ? 'Ocultar log bruto' : 'Ver log bruto';
|
|
458
|
+
el.copyCommandLogButton.disabled = !result;
|
|
459
|
+
el.operationStatus.textContent = result ? `${formatRelativeTime(result.timestamp)}` : '';
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function renderTopStatus() {
|
|
463
|
+
const projectName = app.state?.projectRoot ? app.state.projectRoot.split(/[\\/]/).pop() : 'Maestro Console';
|
|
464
|
+
el.projectName.textContent = projectName || 'Maestro Console';
|
|
465
|
+
if (!app.state) {
|
|
466
|
+
el.commandStatus.textContent = 'parado';
|
|
467
|
+
el.commandStatus.className = 'chip chip--idle';
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function render() {
|
|
472
|
+
if (!app.state) return;
|
|
473
|
+
renderTopStatus();
|
|
474
|
+
renderCards();
|
|
475
|
+
renderCommandButtons();
|
|
476
|
+
renderTasks();
|
|
477
|
+
renderOrchestra();
|
|
478
|
+
renderTimeline();
|
|
479
|
+
renderCommandPanel();
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
async function fetchState() {
|
|
483
|
+
const response = await fetch('/api/state');
|
|
484
|
+
if (!response.ok) throw new Error('Falha ao carregar estado');
|
|
485
|
+
return response.json();
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async function refreshState() {
|
|
489
|
+
app.state = await fetchState();
|
|
490
|
+
render();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
async function runCommand(command, payload = {}) {
|
|
494
|
+
if (app.commandBusy) return;
|
|
495
|
+
setCommandBusy(true);
|
|
496
|
+
app.commandLogExpanded = false;
|
|
497
|
+
try {
|
|
498
|
+
const response = await fetch('/api/command', {
|
|
499
|
+
method: 'POST',
|
|
500
|
+
headers: { 'Content-Type': 'application/json' },
|
|
501
|
+
body: JSON.stringify({ command, ...payload })
|
|
502
|
+
});
|
|
503
|
+
const result = await response.json();
|
|
504
|
+
if (!response.ok) {
|
|
505
|
+
throw new Error(result.error || 'Command failed');
|
|
506
|
+
}
|
|
507
|
+
app.commandResult = result;
|
|
508
|
+
app.commandRawText = result.stdout || result.stderr || result.summary || '';
|
|
509
|
+
el.commandLog.textContent = app.commandRawText;
|
|
510
|
+
el.commandLog.className = app.commandLogExpanded ? 'terminal' : 'terminal terminal--hidden';
|
|
511
|
+
setToast(`${command} executado`);
|
|
512
|
+
await refreshState();
|
|
513
|
+
} catch (error) {
|
|
514
|
+
app.commandResult = {
|
|
515
|
+
commandId: command,
|
|
516
|
+
status: 'failed',
|
|
517
|
+
exitCode: 1,
|
|
518
|
+
timestamp: new Date().toISOString(),
|
|
519
|
+
summary: error.message,
|
|
520
|
+
stdout: '',
|
|
521
|
+
stderr: error.message,
|
|
522
|
+
durationMs: 0
|
|
523
|
+
};
|
|
524
|
+
app.commandRawText = error.message;
|
|
525
|
+
el.commandLog.textContent = error.message;
|
|
526
|
+
el.commandLog.className = app.commandLogExpanded ? 'terminal' : 'terminal terminal--hidden';
|
|
527
|
+
setToast(error.message, true);
|
|
528
|
+
} finally {
|
|
529
|
+
setCommandBusy(false);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function copyHandoff() {
|
|
534
|
+
try {
|
|
535
|
+
const response = await fetch('/api/handoff');
|
|
536
|
+
const result = await response.json();
|
|
537
|
+
if (!response.ok) throw new Error(result.error || 'Falha ao ler handoff');
|
|
538
|
+
await copyText(result.content || '', 'Handoff copiado');
|
|
539
|
+
} catch (error) {
|
|
540
|
+
setToast(error.message, true);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function clearVisual() {
|
|
545
|
+
app.expandedRawKeys.clear();
|
|
546
|
+
app.commandLogExpanded = false;
|
|
547
|
+
app.commandRawText = '';
|
|
548
|
+
app.commandResult = null;
|
|
549
|
+
render();
|
|
550
|
+
setToast('Visual limpo');
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function wireControls() {
|
|
554
|
+
el.toggleCommandLogButton.addEventListener('click', () => {
|
|
555
|
+
app.commandLogExpanded = !app.commandLogExpanded;
|
|
556
|
+
renderCommandPanel();
|
|
557
|
+
});
|
|
558
|
+
el.copyCommandLogButton.addEventListener('click', () => {
|
|
559
|
+
copyText(app.commandRawText || el.commandSummary.textContent || '', 'Log copiado');
|
|
560
|
+
});
|
|
561
|
+
el.copyHandoffButton.addEventListener('click', copyHandoff);
|
|
562
|
+
el.clearVisualButton.addEventListener('click', clearVisual);
|
|
563
|
+
setConnection('connecting');
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function bindSocket() {
|
|
567
|
+
const source = new EventSource('/api/events');
|
|
568
|
+
source.addEventListener('state', event => {
|
|
569
|
+
app.state = JSON.parse(event.data);
|
|
570
|
+
render();
|
|
571
|
+
setConnection('connected');
|
|
572
|
+
});
|
|
573
|
+
source.addEventListener('heartbeat', () => {
|
|
574
|
+
if (app.connection !== 'connected') setConnection('connected');
|
|
575
|
+
});
|
|
576
|
+
source.onerror = () => {
|
|
577
|
+
if (source.readyState === EventSource.CLOSED) {
|
|
578
|
+
setConnection('disconnected');
|
|
579
|
+
} else {
|
|
580
|
+
setConnection('reconnecting');
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
source.onopen = () => {
|
|
584
|
+
setConnection('connected');
|
|
585
|
+
if (!app.state) {
|
|
586
|
+
refreshState().catch(error => setToast(error.message, true));
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async function bootstrap() {
|
|
592
|
+
wireControls();
|
|
593
|
+
await refreshState().catch(error => {
|
|
594
|
+
setConnection('disconnected');
|
|
595
|
+
setToast(error.message, true);
|
|
596
|
+
});
|
|
597
|
+
bindSocket();
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
bootstrap();
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="pt-BR">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Maestro Console</title>
|
|
7
|
+
<link rel="stylesheet" href="/style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<header class="topbar">
|
|
11
|
+
<div class="brand">
|
|
12
|
+
<div class="eyebrow">AI-MAESTRO</div>
|
|
13
|
+
<h1 id="projectName">Maestro Console</h1>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="topbar-actions">
|
|
16
|
+
<span id="connectionStatus" class="chip chip--neutral">conectando</span>
|
|
17
|
+
<span id="commandStatus" class="chip chip--neutral">parado</span>
|
|
18
|
+
<button id="copyHandoffButton" type="button">Copiar Handoff</button>
|
|
19
|
+
<button id="clearVisualButton" type="button">Limpar visual</button>
|
|
20
|
+
</div>
|
|
21
|
+
</header>
|
|
22
|
+
|
|
23
|
+
<main class="shell">
|
|
24
|
+
<section id="cards" class="cards"></section>
|
|
25
|
+
|
|
26
|
+
<section class="workspace">
|
|
27
|
+
<section class="panel tasks-panel">
|
|
28
|
+
<div class="panel-head">
|
|
29
|
+
<h2>Tarefas</h2>
|
|
30
|
+
<span id="taskCounts" class="meta"></span>
|
|
31
|
+
</div>
|
|
32
|
+
<div id="taskFilters" class="filters"></div>
|
|
33
|
+
<div id="taskList" class="task-list"></div>
|
|
34
|
+
</section>
|
|
35
|
+
|
|
36
|
+
<section class="panel operations-panel">
|
|
37
|
+
<div class="panel-head">
|
|
38
|
+
<h2>Operações</h2>
|
|
39
|
+
<span id="operationStatus" class="meta"></span>
|
|
40
|
+
</div>
|
|
41
|
+
<div id="commandButtons" class="command-buttons"></div>
|
|
42
|
+
<div class="microterminal">
|
|
43
|
+
<div class="microterminal-head">
|
|
44
|
+
<span>Saída</span>
|
|
45
|
+
<div class="microterminal-actions">
|
|
46
|
+
<button id="toggleCommandLogButton" type="button">Ver log bruto</button>
|
|
47
|
+
<button id="copyCommandLogButton" type="button">Copiar log</button>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
<div id="commandSummary" class="terminal-summary">Nenhum comando executado.</div>
|
|
51
|
+
<pre id="commandLog" class="terminal terminal--hidden"></pre>
|
|
52
|
+
</div>
|
|
53
|
+
</section>
|
|
54
|
+
</section>
|
|
55
|
+
|
|
56
|
+
<section class="panel orchestra-panel">
|
|
57
|
+
<div class="panel-head">
|
|
58
|
+
<h2>Orquestra</h2>
|
|
59
|
+
<span id="orchestraMeta" class="meta"></span>
|
|
60
|
+
</div>
|
|
61
|
+
<div id="orchestraEngines" class="orchestra-engines"></div>
|
|
62
|
+
<div class="orchestra-grid">
|
|
63
|
+
<div class="orchestra-block">
|
|
64
|
+
<h3>Budget</h3>
|
|
65
|
+
<div id="orchestraBudget" class="orchestra-kv"></div>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="orchestra-block">
|
|
68
|
+
<h3>Fallback & Delegação</h3>
|
|
69
|
+
<div id="orchestraFallback" class="orchestra-kv"></div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
<p class="orchestra-note">Somente leitura nesta versão. Edite <code>.maestro/engines.json</code>, <code>.maestro/budget.json</code> e <code>.maestro/fallback-graph.json</code> diretamente para alterar a política.</p>
|
|
73
|
+
</section>
|
|
74
|
+
|
|
75
|
+
<section class="panel timeline-panel">
|
|
76
|
+
<div class="panel-head">
|
|
77
|
+
<h2>Timeline</h2>
|
|
78
|
+
<span id="timelineCounts" class="meta"></span>
|
|
79
|
+
</div>
|
|
80
|
+
<div id="timelineFilters" class="filters"></div>
|
|
81
|
+
<div id="timelineList" class="timeline-list"></div>
|
|
82
|
+
</section>
|
|
83
|
+
</main>
|
|
84
|
+
|
|
85
|
+
<div id="toast" class="toast" aria-live="polite"></div>
|
|
86
|
+
<script src="/app.js" type="module"></script>
|
|
87
|
+
</body>
|
|
88
|
+
</html>
|