issue-flow 0.12.0 → 0.14.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/README.md +79 -274
- package/dist/agent-OX3KPHE4.js +30 -0
- package/dist/{analyze-OEET7KGE.js → analyze-WIND2SEB.js} +31 -16
- package/dist/analyze-WIND2SEB.js.map +1 -0
- package/dist/{apply-ZIA3C52S.js → apply-JNOI2LR4.js} +18 -7
- package/dist/{apply-ZIA3C52S.js.map → apply-JNOI2LR4.js.map} +1 -1
- package/dist/bench-B4VZON7E.js +910 -0
- package/dist/bench-B4VZON7E.js.map +1 -0
- package/dist/{chunk-FADTCQGM.js → chunk-2IQ6M4ZE.js} +94 -32
- package/dist/chunk-2IQ6M4ZE.js.map +1 -0
- package/dist/chunk-3BWZO4YI.js +141 -0
- package/dist/chunk-3BWZO4YI.js.map +1 -0
- package/dist/chunk-3WFZETIM.js +163 -0
- package/dist/chunk-3WFZETIM.js.map +1 -0
- package/dist/chunk-67YIU2VB.js +362 -0
- package/dist/chunk-67YIU2VB.js.map +1 -0
- package/dist/{chunk-QCPGHBSZ.js → chunk-6BENHR2I.js} +63 -21
- package/dist/chunk-6BENHR2I.js.map +1 -0
- package/dist/{chunk-BYZDPR7N.js → chunk-6QYMSNVA.js} +194 -235
- package/dist/chunk-6QYMSNVA.js.map +1 -0
- package/dist/{chunk-Y5ZDJ4PJ.js → chunk-76MMPJ53.js} +20 -10
- package/dist/chunk-76MMPJ53.js.map +1 -0
- package/dist/chunk-7Z4Y3VKM.js +26 -0
- package/dist/chunk-7Z4Y3VKM.js.map +1 -0
- package/dist/{chunk-3QHK4YQA.js → chunk-B72RQZBV.js} +346 -119
- package/dist/chunk-B72RQZBV.js.map +1 -0
- package/dist/chunk-BEZ5BA5U.js +625 -0
- package/dist/chunk-BEZ5BA5U.js.map +1 -0
- package/dist/chunk-BRCNQXYQ.js +74 -0
- package/dist/chunk-BRCNQXYQ.js.map +1 -0
- package/dist/chunk-CGKLE6WR.js +1203 -0
- package/dist/chunk-CGKLE6WR.js.map +1 -0
- package/dist/chunk-CRGIQFUQ.js +83 -0
- package/dist/chunk-CRGIQFUQ.js.map +1 -0
- package/dist/{chunk-NS6R37II.js → chunk-F6TOYSYY.js} +141 -12
- package/dist/chunk-F6TOYSYY.js.map +1 -0
- package/dist/chunk-G5EK2XRN.js +25 -0
- package/dist/chunk-G5EK2XRN.js.map +1 -0
- package/dist/chunk-HAKDLR6N.js +134 -0
- package/dist/chunk-HAKDLR6N.js.map +1 -0
- package/dist/chunk-J3SOTUVI.js +298 -0
- package/dist/chunk-J3SOTUVI.js.map +1 -0
- package/dist/chunk-JN6D3MY6.js +30 -0
- package/dist/chunk-JN6D3MY6.js.map +1 -0
- package/dist/{chunk-LAFNLLQP.js → chunk-KMRFRXIT.js} +5 -5
- package/dist/chunk-KT7JKOXB.js +72 -0
- package/dist/chunk-KT7JKOXB.js.map +1 -0
- package/dist/chunk-L6HSKTQE.js +77 -0
- package/dist/chunk-L6HSKTQE.js.map +1 -0
- package/dist/chunk-LBGHI4S2.js +1264 -0
- package/dist/chunk-LBGHI4S2.js.map +1 -0
- package/dist/chunk-LEW4QM74.js +180 -0
- package/dist/chunk-LEW4QM74.js.map +1 -0
- package/dist/{chunk-FJP3UQGD.js → chunk-LFEQIZLB.js} +115 -20
- package/dist/chunk-LFEQIZLB.js.map +1 -0
- package/dist/chunk-OOVG2KBY.js +77 -0
- package/dist/chunk-OOVG2KBY.js.map +1 -0
- package/dist/{chunk-56KS3TFQ.js → chunk-OR43364P.js} +115 -160
- package/dist/chunk-OR43364P.js.map +1 -0
- package/dist/chunk-P3LRUNC6.js +16 -0
- package/dist/chunk-P3LRUNC6.js.map +1 -0
- package/dist/{chunk-TSOLWK7Y.js → chunk-PBTA3QQX.js} +8 -7
- package/dist/{chunk-TSOLWK7Y.js.map → chunk-PBTA3QQX.js.map} +1 -1
- package/dist/{chunk-MJULIKQU.js → chunk-PHW2YV6N.js} +23 -47
- package/dist/chunk-PHW2YV6N.js.map +1 -0
- package/dist/{chunk-73ATPIYX.js → chunk-PZTWU277.js} +36 -71
- package/dist/chunk-PZTWU277.js.map +1 -0
- package/dist/{chunk-M5UCXTIT.js → chunk-QD4VOILZ.js} +22 -18
- package/dist/chunk-QD4VOILZ.js.map +1 -0
- package/dist/chunk-QJ5UZAH5.js +2718 -0
- package/dist/chunk-QJ5UZAH5.js.map +1 -0
- package/dist/chunk-QK7WUO7A.js +283 -0
- package/dist/chunk-QK7WUO7A.js.map +1 -0
- package/dist/{chunk-A4SLHOW7.js → chunk-QZVKRDJY.js} +27 -19
- package/dist/chunk-QZVKRDJY.js.map +1 -0
- package/dist/chunk-REO3PXXO.js +71 -0
- package/dist/chunk-REO3PXXO.js.map +1 -0
- package/dist/{chunk-EZATO3DW.js → chunk-RVO7L4FX.js} +594 -17
- package/dist/chunk-RVO7L4FX.js.map +1 -0
- package/dist/{chunk-XQUSIEKA.js → chunk-SZMHCEYV.js} +63 -14
- package/dist/chunk-SZMHCEYV.js.map +1 -0
- package/dist/chunk-TYRNTSPJ.js +243 -0
- package/dist/chunk-TYRNTSPJ.js.map +1 -0
- package/dist/chunk-UJ3L2MS7.js +238 -0
- package/dist/chunk-UJ3L2MS7.js.map +1 -0
- package/dist/chunk-V7MO5EDS.js +120 -0
- package/dist/chunk-V7MO5EDS.js.map +1 -0
- package/dist/chunk-WB2X37MO.js +415 -0
- package/dist/chunk-WB2X37MO.js.map +1 -0
- package/dist/{chunk-NTMMF5UD.js → chunk-X44FKNKP.js} +525 -252
- package/dist/chunk-X44FKNKP.js.map +1 -0
- package/dist/chunk-XEYH6ZZI.js +289 -0
- package/dist/chunk-XEYH6ZZI.js.map +1 -0
- package/dist/cli.js +267 -44
- package/dist/cli.js.map +1 -1
- package/dist/{config-PRJGWW7J.js → config-HMFMCY2M.js} +31 -5
- package/dist/contract-JH2WBIO6.js +8 -0
- package/dist/conventions-NJF2OPVQ.js +146 -0
- package/dist/conventions-NJF2OPVQ.js.map +1 -0
- package/dist/diagnostics-THH42ZMP.js +23 -0
- package/dist/execute-KFNNZZHI.js +35 -0
- package/dist/{generate-EGB4KV2B.js → generate-FW5C6NP7.js} +37 -20
- package/dist/{generate-EGB4KV2B.js.map → generate-FW5C6NP7.js.map} +1 -1
- package/dist/git-W72QQ6GT.js +33 -0
- package/dist/init-HTN4V67Q.js +24 -0
- package/dist/{operations-BYT626KQ.js → operations-BCFOBH42.js} +27 -15
- package/dist/{operations-BYT626KQ.js.map → operations-BCFOBH42.js.map} +1 -1
- package/dist/permissions-4AKNQZHK.js +18 -0
- package/dist/plan-SIZLGA4V.js +34 -0
- package/dist/{policy-6S4GFC76.js → policy-DT6Q6ZVM.js} +20 -6
- package/dist/policy-DT6Q6ZVM.js.map +1 -0
- package/dist/pr-OMWCGUOW.js +39 -0
- package/dist/pr-review-N24CZPKR.js +32 -0
- package/dist/pr-review-N24CZPKR.js.map +1 -0
- package/dist/prd-5OFTZAHJ.js +33 -0
- package/dist/prd-5OFTZAHJ.js.map +1 -0
- package/dist/ps-RFORTCUD.js +93 -0
- package/dist/ps-RFORTCUD.js.map +1 -0
- package/dist/recorder-KCCPMKQG.js +32 -0
- package/dist/recorder-KCCPMKQG.js.map +1 -0
- package/dist/registry-GLPXOMFG.js +15 -0
- package/dist/registry-GLPXOMFG.js.map +1 -0
- package/dist/{resume-PPCNQVVV.js → resume-M5RLKP4I.js} +55 -34
- package/dist/{resume-PPCNQVVV.js.map → resume-M5RLKP4I.js.map} +1 -1
- package/dist/review-YM4JAABY.js +35 -0
- package/dist/review-YM4JAABY.js.map +1 -0
- package/dist/routing-NNT5WKGX.js +94 -0
- package/dist/routing-NNT5WKGX.js.map +1 -0
- package/dist/run-PZUKHPQK.js +54 -0
- package/dist/run-PZUKHPQK.js.map +1 -0
- package/dist/session-publisher-T7EI3CIP.js +11 -0
- package/dist/session-publisher-T7EI3CIP.js.map +1 -0
- package/dist/{state-manager-2EDVDBAR.js → state-manager-GQH32C44.js} +4 -2
- package/dist/state-manager-GQH32C44.js.map +1 -0
- package/dist/usage-V2CHE5PY.js +177 -0
- package/dist/usage-V2CHE5PY.js.map +1 -0
- package/dist/{web-EF5WIIGG.js → web-EA3KGNFN.js} +45 -30
- package/dist/web-EA3KGNFN.js.map +1 -0
- package/package.json +6 -2
- package/prompts/execute.md +7 -8
- package/prompts/plan.md +4 -5
- package/prompts/pr.md +2 -2
- package/web/AGENTS.md +152 -13
- package/web/public/app.css +380 -109
- package/web/public/app.js +525 -25
- package/web/public/index.html +68 -1
- package/dist/analyze-OEET7KGE.js.map +0 -1
- package/dist/chunk-3QHK4YQA.js.map +0 -1
- package/dist/chunk-42PJFMAR.js +0 -605
- package/dist/chunk-42PJFMAR.js.map +0 -1
- package/dist/chunk-4JSOYE4G.js +0 -368
- package/dist/chunk-4JSOYE4G.js.map +0 -1
- package/dist/chunk-56KS3TFQ.js.map +0 -1
- package/dist/chunk-73ATPIYX.js.map +0 -1
- package/dist/chunk-A4SLHOW7.js.map +0 -1
- package/dist/chunk-BAUPQXPE.js +0 -173
- package/dist/chunk-BAUPQXPE.js.map +0 -1
- package/dist/chunk-BYZDPR7N.js.map +0 -1
- package/dist/chunk-CD5ULRVC.js +0 -615
- package/dist/chunk-CD5ULRVC.js.map +0 -1
- package/dist/chunk-DIDUVL6O.js +0 -1365
- package/dist/chunk-DIDUVL6O.js.map +0 -1
- package/dist/chunk-EZATO3DW.js.map +0 -1
- package/dist/chunk-FADTCQGM.js.map +0 -1
- package/dist/chunk-FJP3UQGD.js.map +0 -1
- package/dist/chunk-LWRSMTTZ.js +0 -18
- package/dist/chunk-LWRSMTTZ.js.map +0 -1
- package/dist/chunk-M5UCXTIT.js.map +0 -1
- package/dist/chunk-M66SJHQQ.js +0 -206
- package/dist/chunk-M66SJHQQ.js.map +0 -1
- package/dist/chunk-MJULIKQU.js.map +0 -1
- package/dist/chunk-NS6R37II.js.map +0 -1
- package/dist/chunk-NTMMF5UD.js.map +0 -1
- package/dist/chunk-QCPGHBSZ.js.map +0 -1
- package/dist/chunk-XQUSIEKA.js.map +0 -1
- package/dist/chunk-Y5ZDJ4PJ.js.map +0 -1
- package/dist/execute-XBNVL753.js +0 -18
- package/dist/init-AJHCXJBP.js +0 -10
- package/dist/plan-QOZ3RIO6.js +0 -21
- package/dist/policy-6S4GFC76.js.map +0 -1
- package/dist/pr-EBBHD2ZX.js +0 -26
- package/dist/pr-review-VVMW5PO4.js +0 -20
- package/dist/prd-JQSZF7PR.js +0 -21
- package/dist/review-3F6MARWH.js +0 -19
- package/dist/run-RXE5GDQO.js +0 -36
- package/dist/web-EF5WIIGG.js.map +0 -1
- /package/dist/{config-PRJGWW7J.js.map → agent-OX3KPHE4.js.map} +0 -0
- /package/dist/{chunk-LAFNLLQP.js.map → chunk-KMRFRXIT.js.map} +0 -0
- /package/dist/{execute-XBNVL753.js.map → config-HMFMCY2M.js.map} +0 -0
- /package/dist/{init-AJHCXJBP.js.map → contract-JH2WBIO6.js.map} +0 -0
- /package/dist/{plan-QOZ3RIO6.js.map → diagnostics-THH42ZMP.js.map} +0 -0
- /package/dist/{pr-EBBHD2ZX.js.map → execute-KFNNZZHI.js.map} +0 -0
- /package/dist/{pr-review-VVMW5PO4.js.map → git-W72QQ6GT.js.map} +0 -0
- /package/dist/{prd-JQSZF7PR.js.map → init-HTN4V67Q.js.map} +0 -0
- /package/dist/{review-3F6MARWH.js.map → permissions-4AKNQZHK.js.map} +0 -0
- /package/dist/{run-RXE5GDQO.js.map → plan-SIZLGA4V.js.map} +0 -0
- /package/dist/{state-manager-2EDVDBAR.js.map → pr-OMWCGUOW.js.map} +0 -0
package/web/public/app.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Interface do monitoramento web do issue-flow.
|
|
2
2
|
// JS puro, sem framework e sem recursos externos — funciona offline.
|
|
3
|
-
// Consome GET api/sessions, GET api/status (ETag/304) e GET api/health;
|
|
3
|
+
// Consome GET api/sessions, GET api/status (ETag/304), GET api/events e GET api/health;
|
|
4
4
|
// todo texto dinâmico entra via textContent (nunca innerHTML com dados
|
|
5
5
|
// do snapshot).
|
|
6
6
|
(() => {
|
|
@@ -9,6 +9,12 @@
|
|
|
9
9
|
const REFRESH_OPTIONS = [3, 5, 10, 30];
|
|
10
10
|
const PAUSED = 0;
|
|
11
11
|
const STORAGE_KEY = 'issue-flow:refresh-seconds';
|
|
12
|
+
const THEME_STORAGE_KEY = 'issue-flow:theme';
|
|
13
|
+
// Esta chave também é lida pelo <script> inline do <head>
|
|
14
|
+
// do index.html, que aplica o tema antes do primeiro paint. A duplicação
|
|
15
|
+
// é deliberada: aquele script roda antes deste arquivo existir na página e
|
|
16
|
+
// não pode depender de nenhum símbolo daqui. Mudou o formato do valor,
|
|
17
|
+
// mude nos dois lugares.
|
|
12
18
|
const MAX_BACKOFF_MS = 60000;
|
|
13
19
|
const ALERT_PREVIEW = 3;
|
|
14
20
|
const DESCRIPTION_PREVIEW = 140;
|
|
@@ -71,6 +77,8 @@
|
|
|
71
77
|
estimate: document.getElementById('estimate'),
|
|
72
78
|
refreshSelect: document.getElementById('refresh-select'),
|
|
73
79
|
refreshSelectDashboard: document.getElementById('refresh-select-dashboard'),
|
|
80
|
+
themeSelect: document.getElementById('theme-select'),
|
|
81
|
+
themeSelectDashboard: document.getElementById('theme-select-dashboard'),
|
|
74
82
|
alerts: document.getElementById('alerts'),
|
|
75
83
|
alertsBody: document.getElementById('alerts-body'),
|
|
76
84
|
issueSummary: document.getElementById('issue-summary'),
|
|
@@ -79,6 +87,8 @@
|
|
|
79
87
|
progressPercent: document.getElementById('progress-percent'),
|
|
80
88
|
progressCounters: document.getElementById('progress-counters'),
|
|
81
89
|
now: document.getElementById('now'),
|
|
90
|
+
resilience: document.getElementById('resilience'),
|
|
91
|
+
configuration: document.getElementById('configuration'),
|
|
82
92
|
phases: document.getElementById('phases'),
|
|
83
93
|
nextSteps: document.getElementById('next-steps'),
|
|
84
94
|
stories: document.getElementById('stories'),
|
|
@@ -86,6 +96,8 @@
|
|
|
86
96
|
pullRequests: document.getElementById('pull-requests'),
|
|
87
97
|
logFilter: document.getElementById('log-filter'),
|
|
88
98
|
logs: document.getElementById('logs'),
|
|
99
|
+
historyFilter: document.getElementById('history-filter'),
|
|
100
|
+
history: document.getElementById('history'),
|
|
89
101
|
sessionMeta: document.getElementById('session-meta'),
|
|
90
102
|
tabs: Array.prototype.slice.call(document.querySelectorAll('[role="tab"]')),
|
|
91
103
|
kanban: document.getElementById('kanban'),
|
|
@@ -105,9 +117,16 @@
|
|
|
105
117
|
polling: false,
|
|
106
118
|
logFilter: 'all',
|
|
107
119
|
activeTab: 'tab-execution',
|
|
120
|
+
// 'system' | 'light' | 'dark'. Valor inicial em initTheme(), lido da
|
|
121
|
+
// preferência guardada em localStorage por readStoredTheme().
|
|
122
|
+
theme: 'system',
|
|
123
|
+
historyFilter: 'all',
|
|
124
|
+
events: [],
|
|
125
|
+
diagnostics: [],
|
|
108
126
|
// Só o id: o card que abriu o drawer é destruído no próximo render, então
|
|
109
127
|
// guardar o nó levaria a uma referência morta.
|
|
110
|
-
|
|
128
|
+
selectedDetail: null,
|
|
129
|
+
configWritable: false,
|
|
111
130
|
// Multi-sessão (#35): lista de /api/sessions e seleção explícita do usuário.
|
|
112
131
|
// selectedSessionId null = modo automático (1 sessão → detalhe; 2+ → dashboard).
|
|
113
132
|
sessions: [],
|
|
@@ -116,6 +135,7 @@
|
|
|
116
135
|
detailSessionId: null,
|
|
117
136
|
viewMode: 'detail', // 'detail' | 'dashboard'
|
|
118
137
|
statusUrl: 'api/status',
|
|
138
|
+
eventsUrl: null,
|
|
119
139
|
// Se selectSession/clearSessionSelection chega durante um poll, reexecuta.
|
|
120
140
|
pollAgain: false,
|
|
121
141
|
};
|
|
@@ -186,8 +206,9 @@
|
|
|
186
206
|
}
|
|
187
207
|
|
|
188
208
|
// Custos abaixo de um centavo perderiam todo o significado com 2 casas.
|
|
189
|
-
function formatCost(value) {
|
|
190
|
-
|
|
209
|
+
function formatCost(value, approximate) {
|
|
210
|
+
const prefix = approximate === false ? '$' : '~$';
|
|
211
|
+
return prefix + (Math.abs(value) < 0.01 ? value.toFixed(4) : value.toFixed(2));
|
|
191
212
|
}
|
|
192
213
|
|
|
193
214
|
// Ex.: '12.4k in / 3.1k out · 88.0k cache · ~$0.42'. Segmentos sem dado são
|
|
@@ -301,6 +322,88 @@
|
|
|
301
322
|
}
|
|
302
323
|
}
|
|
303
324
|
|
|
325
|
+
// ---- Tema ------------------------------------------------------------------
|
|
326
|
+
// Três estados: 'system' segue o SO (o @media decide), 'light'/'dark' forçam.
|
|
327
|
+
// Quem pinta é o CSS; aqui só se define (ou remove) o data-theme da raiz.
|
|
328
|
+
|
|
329
|
+
// Par ler/gravar tolerante a exceção, na mesma forma de readStoredRefresh()
|
|
330
|
+
// e storeRefresh(): armazenamento bloqueado (janela privada, cookies de
|
|
331
|
+
// terceiros desligados) não pode derrubar o painel.
|
|
332
|
+
function readStoredTheme() {
|
|
333
|
+
let raw = null;
|
|
334
|
+
try {
|
|
335
|
+
raw = window.localStorage.getItem(THEME_STORAGE_KEY);
|
|
336
|
+
} catch (err) {
|
|
337
|
+
// localStorage indisponível (ex.: bloqueado) — segue no modo sistema.
|
|
338
|
+
}
|
|
339
|
+
return raw === 'light' || raw === 'dark' ? raw : 'system';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function storeTheme(value) {
|
|
343
|
+
try {
|
|
344
|
+
window.localStorage.setItem(THEME_STORAGE_KEY, value);
|
|
345
|
+
} catch (err) {
|
|
346
|
+
// Persistência é conveniência; falha é ignorada. Sem gravar, a escolha
|
|
347
|
+
// continua valendo nesta aba — só não sobrevive ao reload.
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function applyTheme(theme) {
|
|
352
|
+
// 'system' **remove** o atributo em vez de gravar 'system': é a ausência
|
|
353
|
+
// do data-theme que devolve a decisão ao @media (prefers-color-scheme).
|
|
354
|
+
if (theme === 'light' || theme === 'dark') {
|
|
355
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
356
|
+
} else {
|
|
357
|
+
document.documentElement.removeAttribute('data-theme');
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Os dois headers têm o seu seletor; mudar num precisa refletir no outro.
|
|
362
|
+
// As três opções são estáticas no HTML — aqui só se sincroniza o .value.
|
|
363
|
+
function syncThemeSelects() {
|
|
364
|
+
if (els.themeSelect) els.themeSelect.value = state.theme;
|
|
365
|
+
if (els.themeSelectDashboard) els.themeSelectDashboard.value = state.theme;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Preferência do SO. Só se observa no modo 'system': é ali que a troca de
|
|
369
|
+
// claro para escuro no SO precisa chegar ao painel com a página aberta.
|
|
370
|
+
const systemThemeQuery =
|
|
371
|
+
typeof window.matchMedia === 'function'
|
|
372
|
+
? window.matchMedia('(prefers-color-scheme: dark)')
|
|
373
|
+
: null;
|
|
374
|
+
|
|
375
|
+
function onSystemThemeChange() {
|
|
376
|
+
// Sincroniza o lado JS do tema com a preferência nova do SO: a raiz (que
|
|
377
|
+
// no modo sistema segue sem data-theme, para o @media decidir) e o .value
|
|
378
|
+
// dos dois seletores. O repaint das cores é do próprio @media, que o
|
|
379
|
+
// navegador reavalia sozinho — por isso não há nada a pintar aqui.
|
|
380
|
+
applyTheme(state.theme);
|
|
381
|
+
syncThemeSelects();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Anexa no modo 'system' e desanexa nos modos forçados: com tema forçado a
|
|
385
|
+
// preferência do SO não vale, e continuar ouvindo seria ruído.
|
|
386
|
+
function watchSystemTheme(enabled) {
|
|
387
|
+
if (!systemThemeQuery) return;
|
|
388
|
+
if (enabled) systemThemeQuery.addEventListener('change', onSystemThemeChange);
|
|
389
|
+
else systemThemeQuery.removeEventListener('change', onSystemThemeChange);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function setTheme(theme) {
|
|
393
|
+
state.theme = theme === 'light' || theme === 'dark' ? theme : 'system';
|
|
394
|
+
applyTheme(state.theme);
|
|
395
|
+
syncThemeSelects();
|
|
396
|
+
watchSystemTheme(state.theme === 'system');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function initTheme() {
|
|
400
|
+
// O <script> inline do <head> já leu a mesma chave e aplicou o tema antes
|
|
401
|
+
// do primeiro paint; esta é a segunda metade da duplicação deliberada
|
|
402
|
+
// documentada em THEME_STORAGE_KEY — traz a preferência para o state e,
|
|
403
|
+
// com ela, para os dois seletores.
|
|
404
|
+
setTheme(readStoredTheme());
|
|
405
|
+
}
|
|
406
|
+
|
|
304
407
|
// ---- Polling: intervalo configurável, aba oculta e backoff ----------------
|
|
305
408
|
|
|
306
409
|
function readStoredRefresh() {
|
|
@@ -372,9 +475,6 @@
|
|
|
372
475
|
if (!stillThere) {
|
|
373
476
|
state.selectedSessionId = null;
|
|
374
477
|
state.etag = null;
|
|
375
|
-
} else if (sessions.length === 1) {
|
|
376
|
-
// Seleção explícita deixa de fazer sentido com uma única sessão.
|
|
377
|
-
state.selectedSessionId = null;
|
|
378
478
|
}
|
|
379
479
|
}
|
|
380
480
|
|
|
@@ -398,11 +498,11 @@
|
|
|
398
498
|
state.viewMode = mode;
|
|
399
499
|
els.viewDashboard.hidden = mode !== 'dashboard';
|
|
400
500
|
els.viewDetail.hidden = mode !== 'detail';
|
|
401
|
-
// Back
|
|
501
|
+
// Back quando o usuário escolheu um card e existe uma lista para voltar.
|
|
402
502
|
const showBack =
|
|
403
|
-
mode === 'detail' && state.selectedSessionId !== null && state.sessions.length >=
|
|
503
|
+
mode === 'detail' && state.selectedSessionId !== null && state.sessions.length >= 1;
|
|
404
504
|
els.backToDashboard.hidden = !showBack;
|
|
405
|
-
if (mode === 'dashboard' && state.
|
|
505
|
+
if (mode === 'dashboard' && state.selectedDetail !== null) {
|
|
406
506
|
closeDrawer();
|
|
407
507
|
}
|
|
408
508
|
}
|
|
@@ -414,10 +514,18 @@
|
|
|
414
514
|
return session.statusUrl.replace(/^\//, '');
|
|
415
515
|
}
|
|
416
516
|
|
|
517
|
+
function eventsUrlFor(session) {
|
|
518
|
+
if (!session || !session.eventsUrl) return null;
|
|
519
|
+
return session.eventsUrl.replace(/^\//, '');
|
|
520
|
+
}
|
|
521
|
+
|
|
417
522
|
function clearDetailState() {
|
|
418
523
|
state.etag = null;
|
|
419
524
|
state.snapshot = null;
|
|
420
525
|
state.detailSessionId = null;
|
|
526
|
+
state.events = [];
|
|
527
|
+
state.diagnostics = [];
|
|
528
|
+
state.eventsUrl = null;
|
|
421
529
|
}
|
|
422
530
|
|
|
423
531
|
function requestPoll() {
|
|
@@ -491,6 +599,7 @@
|
|
|
491
599
|
state.statusUrl = nextUrl;
|
|
492
600
|
state.etag = null;
|
|
493
601
|
}
|
|
602
|
+
state.eventsUrl = eventsUrlFor(resolved.session);
|
|
494
603
|
|
|
495
604
|
// Evita flash da sessão anterior enquanto o status novo carrega.
|
|
496
605
|
if (sessionChanged || !state.snapshot) {
|
|
@@ -516,6 +625,21 @@
|
|
|
516
625
|
state.snapshot = await res.json();
|
|
517
626
|
render();
|
|
518
627
|
}
|
|
628
|
+
if (state.eventsUrl) {
|
|
629
|
+
const eventsRes = await fetch(state.eventsUrl, { cache: 'no-store' });
|
|
630
|
+
if (!eventsRes.ok) throw new Error('HTTP ' + eventsRes.status);
|
|
631
|
+
const entries = await eventsRes.json();
|
|
632
|
+
state.events = Array.isArray(entries) ? entries : [];
|
|
633
|
+
const diagnosticUrl =
|
|
634
|
+
'api/diagnostics?session=' + encodeURIComponent(state.detailSessionId || '');
|
|
635
|
+
const diagnosticsRes = await fetch(diagnosticUrl, { cache: 'no-store' });
|
|
636
|
+
if (diagnosticsRes.ok) {
|
|
637
|
+
const diagnostics = await diagnosticsRes.json();
|
|
638
|
+
state.diagnostics = Array.isArray(diagnostics) ? diagnostics : [];
|
|
639
|
+
}
|
|
640
|
+
renderHistory();
|
|
641
|
+
renderDrawer(state.snapshot);
|
|
642
|
+
}
|
|
519
643
|
setViewMode('detail');
|
|
520
644
|
state.failures = 0;
|
|
521
645
|
els.banner.hidden = true;
|
|
@@ -624,7 +748,13 @@
|
|
|
624
748
|
meta.appendChild(el('span', null, 'correção ' + session.correctionCycle));
|
|
625
749
|
}
|
|
626
750
|
if (session.updatedAt) {
|
|
627
|
-
meta.appendChild(
|
|
751
|
+
meta.appendChild(
|
|
752
|
+
el('span', null, 'atividade ' + formatAgo(session.lastActivityAt || session.updatedAt)),
|
|
753
|
+
);
|
|
754
|
+
}
|
|
755
|
+
if (session.provider) meta.appendChild(el('span', null, 'provider ' + session.provider));
|
|
756
|
+
if (typeof session.attempt === 'number' && session.attempt > 0) {
|
|
757
|
+
meta.appendChild(el('span', null, 'tentativa ' + session.attempt));
|
|
628
758
|
}
|
|
629
759
|
card.appendChild(meta);
|
|
630
760
|
|
|
@@ -668,6 +798,8 @@
|
|
|
668
798
|
renderRepository(snapshot);
|
|
669
799
|
renderProgress(snapshot);
|
|
670
800
|
renderNow(snapshot);
|
|
801
|
+
renderResilience(snapshot);
|
|
802
|
+
renderConfiguration(snapshot);
|
|
671
803
|
renderPhases(snapshot);
|
|
672
804
|
renderNextSteps(snapshot);
|
|
673
805
|
renderStories(snapshot);
|
|
@@ -676,6 +808,7 @@
|
|
|
676
808
|
renderDrawer(snapshot);
|
|
677
809
|
renderGit(snapshot);
|
|
678
810
|
renderLogs(snapshot);
|
|
811
|
+
renderHistory();
|
|
679
812
|
renderMeta(snapshot);
|
|
680
813
|
renderTimers();
|
|
681
814
|
renderTitle(snapshot);
|
|
@@ -702,7 +835,15 @@
|
|
|
702
835
|
|
|
703
836
|
const branch = snapshot.git.branch;
|
|
704
837
|
const base = snapshot.git.baseBranch;
|
|
705
|
-
|
|
838
|
+
const branchMode =
|
|
839
|
+
snapshot.git.branchCreated === false
|
|
840
|
+
? 'branch atual · não criada pelo Issue Flow'
|
|
841
|
+
: snapshot.git.branchCreated === true
|
|
842
|
+
? 'criada pelo Issue Flow'
|
|
843
|
+
: 'origem da branch não informada';
|
|
844
|
+
els.branchLine.textContent = branch
|
|
845
|
+
? (base ? branch + ' ← ' + base : branch) + ' · ' + branchMode
|
|
846
|
+
: '';
|
|
706
847
|
|
|
707
848
|
els.statusBadge.textContent = STATUS_LABELS[snapshot.status] || snapshot.status;
|
|
708
849
|
els.statusBadge.className = 'badge status-' + snapshot.status;
|
|
@@ -857,6 +998,125 @@
|
|
|
857
998
|
els.now.appendChild(grid);
|
|
858
999
|
}
|
|
859
1000
|
|
|
1001
|
+
function renderResilience(snapshot) {
|
|
1002
|
+
clear(els.resilience);
|
|
1003
|
+
const resilience = snapshot.resilience || {};
|
|
1004
|
+
const grid = el('dl', 'now-grid');
|
|
1005
|
+
nowRow(grid, 'Tentativa', resilience.attempt > 0 ? String(resilience.attempt) : '—');
|
|
1006
|
+
nowRow(grid, 'Provider', resilience.provider || '—');
|
|
1007
|
+
nowRow(grid, 'Modelo', resilience.model || '—');
|
|
1008
|
+
nowRow(grid, 'Última falha', resilience.lastFailureKind || '—');
|
|
1009
|
+
nowRow(
|
|
1010
|
+
grid,
|
|
1011
|
+
'Cooldown',
|
|
1012
|
+
resilience.cooldownUntil ? formatClock(resilience.cooldownUntil) : '—',
|
|
1013
|
+
);
|
|
1014
|
+
const activity = resilience.lastActivityAt
|
|
1015
|
+
? formatClock(resilience.lastActivityAt) + ' (' + formatAgo(resilience.lastActivityAt) + ')'
|
|
1016
|
+
: '—';
|
|
1017
|
+
nowRow(grid, 'Última atividade', activity);
|
|
1018
|
+
els.resilience.appendChild(grid);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function configSourceLabel(source) {
|
|
1022
|
+
const labels = {
|
|
1023
|
+
default: 'default do Issue Flow',
|
|
1024
|
+
global: 'configuração global',
|
|
1025
|
+
project: 'configuração do projeto',
|
|
1026
|
+
env: 'variável de ambiente',
|
|
1027
|
+
cli: 'override da execução',
|
|
1028
|
+
fallback: 'fallback',
|
|
1029
|
+
};
|
|
1030
|
+
return labels[source] || source || 'não informado';
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function renderConfiguration(snapshot) {
|
|
1034
|
+
clear(els.configuration);
|
|
1035
|
+
const config = snapshot.configuration;
|
|
1036
|
+
if (!config) {
|
|
1037
|
+
els.configuration.appendChild(el('p', 'empty', 'Configuração não capturada nesta execução.'));
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
const summary = el('dl', 'now-grid');
|
|
1042
|
+
nowRow(
|
|
1043
|
+
summary,
|
|
1044
|
+
'Harness padrão',
|
|
1045
|
+
(config.defaultProvider.value || '—') + ' · ' + configSourceLabel(config.defaultProvider.source),
|
|
1046
|
+
);
|
|
1047
|
+
nowRow(
|
|
1048
|
+
summary,
|
|
1049
|
+
'Modelo padrão',
|
|
1050
|
+
(config.defaultModel.value || 'default do provider') +
|
|
1051
|
+
' · ' +
|
|
1052
|
+
configSourceLabel(config.defaultModel.source),
|
|
1053
|
+
);
|
|
1054
|
+
nowRow(summary, 'Fallbacks', list(config.fallbacks).join(' → ') || 'nenhum configurado');
|
|
1055
|
+
nowRow(summary, 'Precedência', list(config.precedence).join(' → '));
|
|
1056
|
+
els.configuration.appendChild(summary);
|
|
1057
|
+
|
|
1058
|
+
const phases = el('div', 'config-phase-grid');
|
|
1059
|
+
for (const phase of list(config.phases)) {
|
|
1060
|
+
const row = el('button', 'config-phase-row');
|
|
1061
|
+
row.type = 'button';
|
|
1062
|
+
row.appendChild(el('span', 'mono', phase.phase));
|
|
1063
|
+
row.appendChild(el('span', null, phase.provider.value || '—'));
|
|
1064
|
+
row.appendChild(el('span', null, phase.model.value || 'default do provider'));
|
|
1065
|
+
row.appendChild(
|
|
1066
|
+
el(
|
|
1067
|
+
'span',
|
|
1068
|
+
'muted',
|
|
1069
|
+
configSourceLabel(phase.provider.source) + ' / ' + configSourceLabel(phase.model.source),
|
|
1070
|
+
),
|
|
1071
|
+
);
|
|
1072
|
+
row.addEventListener('click', () => openDrawer('phase', phase.phase));
|
|
1073
|
+
phases.appendChild(row);
|
|
1074
|
+
}
|
|
1075
|
+
els.configuration.appendChild(phases);
|
|
1076
|
+
|
|
1077
|
+
const form = el('form', 'config-form');
|
|
1078
|
+
const provider = el('select');
|
|
1079
|
+
provider.setAttribute('aria-label', 'Harness padrão para execuções futuras');
|
|
1080
|
+
for (const id of ['claude', 'codex', 'cursor', 'antigravity']) {
|
|
1081
|
+
const option = el('option', null, id);
|
|
1082
|
+
option.value = id;
|
|
1083
|
+
provider.appendChild(option);
|
|
1084
|
+
}
|
|
1085
|
+
provider.value = config.defaultProvider.value || 'claude';
|
|
1086
|
+
const model = el('input');
|
|
1087
|
+
model.type = 'text';
|
|
1088
|
+
model.placeholder = 'modelo (vazio = default)';
|
|
1089
|
+
model.value = config.defaultModel.value || '';
|
|
1090
|
+
const save = el('button', 'config-save', 'Salvar preferência global');
|
|
1091
|
+
save.type = 'submit';
|
|
1092
|
+
const feedback = el('span', 'muted');
|
|
1093
|
+
form.appendChild(provider);
|
|
1094
|
+
form.appendChild(model);
|
|
1095
|
+
form.appendChild(save);
|
|
1096
|
+
form.appendChild(feedback);
|
|
1097
|
+
form.addEventListener('submit', async (event) => {
|
|
1098
|
+
event.preventDefault();
|
|
1099
|
+
save.disabled = true;
|
|
1100
|
+
feedback.textContent = 'salvando…';
|
|
1101
|
+
try {
|
|
1102
|
+
const response = await fetch('api/config/agent', {
|
|
1103
|
+
method: 'POST',
|
|
1104
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1105
|
+
body: JSON.stringify({ provider: provider.value, model: model.value }),
|
|
1106
|
+
});
|
|
1107
|
+
const result = await response.json();
|
|
1108
|
+
if (!response.ok) throw new Error(result.error || 'falha ao salvar');
|
|
1109
|
+
feedback.textContent = 'salvo para execuções futuras';
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
feedback.textContent = error.message || 'falha ao salvar';
|
|
1112
|
+
} finally {
|
|
1113
|
+
save.disabled = false;
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
form.hidden = !state.configWritable;
|
|
1117
|
+
els.configuration.appendChild(form);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
860
1120
|
function renderPhases(snapshot) {
|
|
861
1121
|
clear(els.phases);
|
|
862
1122
|
if (snapshot.phases.length === 0) {
|
|
@@ -865,6 +1125,7 @@
|
|
|
865
1125
|
}
|
|
866
1126
|
for (const phase of snapshot.phases) {
|
|
867
1127
|
const item = el('li');
|
|
1128
|
+
item.className = 'detail-row';
|
|
868
1129
|
item.appendChild(el('span', 'item-icon icon-' + phase.status, PHASE_ICONS[phase.status] || '○'));
|
|
869
1130
|
const main = el('div', 'item-main');
|
|
870
1131
|
main.appendChild(el('div', 'item-title', phase.name));
|
|
@@ -876,6 +1137,12 @@
|
|
|
876
1137
|
formatUsage(phase),
|
|
877
1138
|
]);
|
|
878
1139
|
if (side) item.appendChild(el('span', 'item-side', side));
|
|
1140
|
+
item.tabIndex = 0;
|
|
1141
|
+
item.setAttribute('role', 'button');
|
|
1142
|
+
item.addEventListener('click', () => openDrawer('phase', phase.name));
|
|
1143
|
+
item.addEventListener('keydown', (event) => {
|
|
1144
|
+
if (event.key === 'Enter' || event.key === ' ') openDrawer('phase', phase.name);
|
|
1145
|
+
});
|
|
879
1146
|
els.phases.appendChild(item);
|
|
880
1147
|
}
|
|
881
1148
|
}
|
|
@@ -906,6 +1173,7 @@
|
|
|
906
1173
|
// antes da issue 38 pode chegar sem ele.
|
|
907
1174
|
const storyStage = story.stage !== null && story.stage !== undefined ? story.stage : 'pending';
|
|
908
1175
|
const item = el('li', storyStage === 'executing' ? 'story-executing' : null);
|
|
1176
|
+
item.classList.add('detail-row');
|
|
909
1177
|
const status = story.passes ? 'completed' : 'pending';
|
|
910
1178
|
item.appendChild(el('span', 'item-icon icon-' + status, story.passes ? '✓' : '○'));
|
|
911
1179
|
const main = el('div', 'item-main');
|
|
@@ -940,6 +1208,12 @@
|
|
|
940
1208
|
formatUsage(story),
|
|
941
1209
|
]);
|
|
942
1210
|
if (side) item.appendChild(el('span', 'item-side', side));
|
|
1211
|
+
item.tabIndex = 0;
|
|
1212
|
+
item.setAttribute('role', 'button');
|
|
1213
|
+
item.addEventListener('click', () => openDrawer('story', story.id));
|
|
1214
|
+
item.addEventListener('keydown', (event) => {
|
|
1215
|
+
if (event.key === 'Enter' || event.key === ' ') openDrawer('story', story.id);
|
|
1216
|
+
});
|
|
943
1217
|
els.stories.appendChild(item);
|
|
944
1218
|
}
|
|
945
1219
|
}
|
|
@@ -966,7 +1240,7 @@
|
|
|
966
1240
|
card.appendChild(
|
|
967
1241
|
el('span', 'badge story-status-' + story.status, STORY_STATUS_LABELS[story.status]),
|
|
968
1242
|
);
|
|
969
|
-
card.addEventListener('click', () => openDrawer(story.id));
|
|
1243
|
+
card.addEventListener('click', () => openDrawer('story', story.id));
|
|
970
1244
|
return card;
|
|
971
1245
|
}
|
|
972
1246
|
|
|
@@ -997,18 +1271,145 @@
|
|
|
997
1271
|
if (event.key === 'Escape') closeDrawer();
|
|
998
1272
|
}
|
|
999
1273
|
|
|
1000
|
-
|
|
1274
|
+
function executionsFor(snapshot, kind, id) {
|
|
1275
|
+
return list(snapshot.executions).filter((execution) => {
|
|
1276
|
+
if (kind === 'phase') return execution.purpose === id;
|
|
1277
|
+
return list(execution.storyIds).includes(id);
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
function executionCost(execution) {
|
|
1282
|
+
const cost = execution.cost || {};
|
|
1283
|
+
if (cost.status === 'reported' || cost.status === 'estimated') {
|
|
1284
|
+
return formatCost(cost.amount, cost.status === 'estimated');
|
|
1285
|
+
}
|
|
1286
|
+
return cost.reason ? 'não informado (' + cost.reason + ')' : 'não informado';
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
function renderExecutionHistory(snapshot, executions) {
|
|
1290
|
+
drawerSection('Tentativas, revisões e correções', (body) => {
|
|
1291
|
+
if (executions.length === 0) {
|
|
1292
|
+
body.appendChild(el('p', 'empty', 'Nenhuma invocação associada.'));
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const timeline = el('ol', 'execution-timeline');
|
|
1296
|
+
for (const execution of executions.slice().reverse()) {
|
|
1297
|
+
const item = el('li', 'execution-entry');
|
|
1298
|
+
const title = el('div', 'execution-entry-head');
|
|
1299
|
+
title.appendChild(
|
|
1300
|
+
el(
|
|
1301
|
+
'span',
|
|
1302
|
+
'badge status-' + (execution.status === 'completed' ? 'completed' : execution.status),
|
|
1303
|
+
execution.status,
|
|
1304
|
+
),
|
|
1305
|
+
);
|
|
1306
|
+
title.appendChild(
|
|
1307
|
+
el(
|
|
1308
|
+
'strong',
|
|
1309
|
+
null,
|
|
1310
|
+
execution.purpose + ' · tentativa ' + execution.attempt + ' · ' + execution.trigger,
|
|
1311
|
+
),
|
|
1312
|
+
);
|
|
1313
|
+
item.appendChild(title);
|
|
1314
|
+
const grid = el('dl', 'now-grid execution-grid');
|
|
1315
|
+
nowRow(grid, 'Harness', execution.agent?.harness || '—');
|
|
1316
|
+
nowRow(grid, 'Modelo', execution.agent?.model?.resolved || execution.agent?.model?.requested || '—');
|
|
1317
|
+
nowRow(grid, 'Início', formatClock(execution.startedAt));
|
|
1318
|
+
nowRow(grid, 'Fim', execution.finishedAt ? formatClock(execution.finishedAt) : 'em andamento');
|
|
1319
|
+
nowRow(grid, 'Duração', metric(execution.durationMs) !== null ? formatDuration(execution.durationMs / 1000) : '—');
|
|
1320
|
+
nowRow(grid, 'Tokens', formatUsage(execution.usage || {}) || '—');
|
|
1321
|
+
nowRow(grid, 'Custo', executionCost(execution));
|
|
1322
|
+
if (execution.correctionCycle) nowRow(grid, 'Correção', 'ciclo ' + execution.correctionCycle);
|
|
1323
|
+
if (execution.verdict?.status) nowRow(grid, 'Veredito', execution.verdict.status);
|
|
1324
|
+
item.appendChild(grid);
|
|
1325
|
+
if (execution.failure?.message) item.appendChild(el('p', 'item-error', execution.failure.message));
|
|
1326
|
+
timeline.appendChild(item);
|
|
1327
|
+
}
|
|
1328
|
+
body.appendChild(timeline);
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
function renderProcessLogs(snapshot, executions, phase) {
|
|
1333
|
+
const ids = new Set(executions.map((execution) => execution.id));
|
|
1334
|
+
const logs = list(snapshot.processLogs).filter(
|
|
1335
|
+
(entry) => ids.has(entry.executionId) || (ids.size === 0 && entry.phase === phase),
|
|
1336
|
+
);
|
|
1337
|
+
drawerSection('Saída do processo', (body) => {
|
|
1338
|
+
const details = el('details', 'process-output');
|
|
1339
|
+
const summary = el('summary', null, logs.length + ' linha(s) sanitizada(s)');
|
|
1340
|
+
details.appendChild(summary);
|
|
1341
|
+
if (logs.length === 0) {
|
|
1342
|
+
details.appendChild(el('p', 'empty', 'Nenhuma saída capturada.'));
|
|
1343
|
+
} else {
|
|
1344
|
+
const output = el('pre', 'process-output-body');
|
|
1345
|
+
output.textContent = logs
|
|
1346
|
+
.slice(-200)
|
|
1347
|
+
.map((entry) => formatClock(entry.at) + ' ' + entry.message)
|
|
1348
|
+
.join('\n');
|
|
1349
|
+
details.appendChild(output);
|
|
1350
|
+
}
|
|
1351
|
+
body.appendChild(details);
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
function renderGlobalDiagnostics(kind, id, executions) {
|
|
1356
|
+
const ids = new Set(executions.map((execution) => execution.id));
|
|
1357
|
+
const entries = state.diagnostics.filter((entry) => {
|
|
1358
|
+
if (ids.has(entry.executionId)) return true;
|
|
1359
|
+
if (kind === 'phase') return entry.phase === id;
|
|
1360
|
+
return typeof entry.story === 'string' && entry.story.split(',').includes(id);
|
|
1361
|
+
});
|
|
1362
|
+
if (entries.length === 0) return;
|
|
1363
|
+
drawerSection('Diagnóstico global persistente', (body) => {
|
|
1364
|
+
const details = el('details', 'process-output');
|
|
1365
|
+
details.appendChild(el('summary', null, entries.length + ' registro(s) em ~/.issue-flow/logs'));
|
|
1366
|
+
const output = el('pre', 'process-output-body');
|
|
1367
|
+
output.textContent = entries
|
|
1368
|
+
.slice(0, 200)
|
|
1369
|
+
.map((entry) => formatClock(entry.timestamp) + ' ' + entry.level + ' ' + entry.message)
|
|
1370
|
+
.join('\n');
|
|
1371
|
+
details.appendChild(output);
|
|
1372
|
+
body.appendChild(details);
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// Reidrata a partir de kind/id a cada render: o drawer sobrevive ao poll.
|
|
1001
1377
|
function renderDrawer(snapshot) {
|
|
1002
|
-
if (state.
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
|
|
1378
|
+
if (state.selectedDetail === null) return;
|
|
1379
|
+
const kind = state.selectedDetail.kind;
|
|
1380
|
+
const id = state.selectedDetail.id;
|
|
1381
|
+
const story = kind === 'story' ? getStoryById(snapshot, id) : null;
|
|
1382
|
+
const phase = kind === 'phase' ? list(snapshot.phases).find((entry) => entry.name === id) : null;
|
|
1383
|
+
if ((kind === 'story' && story === null) || (kind === 'phase' && !phase)) {
|
|
1006
1384
|
closeDrawer();
|
|
1007
1385
|
return;
|
|
1008
1386
|
}
|
|
1009
|
-
els.drawerTitle.textContent = story.id + ' · ' + story.title;
|
|
1010
1387
|
|
|
1011
1388
|
clear(els.drawerBody);
|
|
1389
|
+
if (kind === 'phase') {
|
|
1390
|
+
els.drawerTitle.textContent = 'Fase · ' + phase.name;
|
|
1391
|
+
els.drawerBody.appendChild(el('span', 'badge status-' + phase.status, phase.status));
|
|
1392
|
+
const grid = el('dl', 'now-grid drawer-summary-grid');
|
|
1393
|
+
nowRow(grid, 'Início', phase.startedAt ? formatClock(phase.startedAt) : '—');
|
|
1394
|
+
nowRow(grid, 'Fim', phase.endedAt ? formatClock(phase.endedAt) : '—');
|
|
1395
|
+
nowRow(grid, 'Duração', metric(phase.durationSeconds) !== null ? formatDuration(phase.durationSeconds) : '—');
|
|
1396
|
+
nowRow(grid, 'Uso total', formatUsage(phase) || '—');
|
|
1397
|
+
const configured = list(snapshot.configuration?.phases).find((entry) => entry.phase === phase.name);
|
|
1398
|
+
if (configured) {
|
|
1399
|
+
nowRow(grid, 'Harness efetivo', configured.provider.value || '—');
|
|
1400
|
+
nowRow(grid, 'Origem', configSourceLabel(configured.provider.source));
|
|
1401
|
+
nowRow(grid, 'Modelo efetivo', configured.model.value || 'default do provider');
|
|
1402
|
+
}
|
|
1403
|
+
els.drawerBody.appendChild(grid);
|
|
1404
|
+
if (phase.error) els.drawerBody.appendChild(el('p', 'item-error', phase.error));
|
|
1405
|
+
const executions = executionsFor(snapshot, kind, id);
|
|
1406
|
+
renderExecutionHistory(snapshot, executions);
|
|
1407
|
+
renderProcessLogs(snapshot, executions, phase.name);
|
|
1408
|
+
renderGlobalDiagnostics(kind, id, executions);
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
els.drawerTitle.textContent = story.id + ' · ' + story.title;
|
|
1012
1413
|
els.drawerBody.appendChild(
|
|
1013
1414
|
el('span', 'badge story-status-' + story.status, STORY_STATUS_LABELS[story.status]),
|
|
1014
1415
|
);
|
|
@@ -1058,12 +1459,21 @@
|
|
|
1058
1459
|
for (const entry of history) {
|
|
1059
1460
|
const item = el('li');
|
|
1060
1461
|
if (entry.at) item.appendChild(el('span', 'log-time', formatClock(entry.at)));
|
|
1061
|
-
item.appendChild(
|
|
1462
|
+
item.appendChild(
|
|
1463
|
+
document.createTextNode(
|
|
1464
|
+
(STORY_STAGE_LABELS[entry.stage] || entry.stage || '') +
|
|
1465
|
+
(entry.detail ? ' · ' + entry.detail : ''),
|
|
1466
|
+
),
|
|
1467
|
+
);
|
|
1062
1468
|
entries.appendChild(item);
|
|
1063
1469
|
}
|
|
1064
1470
|
body.appendChild(entries);
|
|
1065
1471
|
});
|
|
1066
1472
|
}
|
|
1473
|
+
const executions = executionsFor(snapshot, kind, id);
|
|
1474
|
+
renderExecutionHistory(snapshot, executions);
|
|
1475
|
+
renderProcessLogs(snapshot, executions, 'execute');
|
|
1476
|
+
renderGlobalDiagnostics(kind, id, executions);
|
|
1067
1477
|
}
|
|
1068
1478
|
|
|
1069
1479
|
function drawerSection(title, fill) {
|
|
@@ -1073,8 +1483,8 @@
|
|
|
1073
1483
|
els.drawerBody.appendChild(section);
|
|
1074
1484
|
}
|
|
1075
1485
|
|
|
1076
|
-
function openDrawer(id) {
|
|
1077
|
-
state.
|
|
1486
|
+
function openDrawer(kind, id) {
|
|
1487
|
+
state.selectedDetail = { kind, id };
|
|
1078
1488
|
els.drawer.hidden = false;
|
|
1079
1489
|
els.drawerOverlay.hidden = false;
|
|
1080
1490
|
document.addEventListener('keydown', onDrawerKeydown);
|
|
@@ -1083,15 +1493,18 @@
|
|
|
1083
1493
|
}
|
|
1084
1494
|
|
|
1085
1495
|
function closeDrawer() {
|
|
1086
|
-
const
|
|
1087
|
-
state.
|
|
1496
|
+
const selected = state.selectedDetail;
|
|
1497
|
+
state.selectedDetail = null;
|
|
1088
1498
|
els.drawer.hidden = true;
|
|
1089
1499
|
els.drawerOverlay.hidden = true;
|
|
1090
1500
|
document.removeEventListener('keydown', onDrawerKeydown);
|
|
1091
1501
|
clear(els.drawerBody);
|
|
1092
1502
|
// O card é recriado a cada render, então o foco volta pelo id, não por uma
|
|
1093
1503
|
// referência guardada na abertura.
|
|
1094
|
-
const card =
|
|
1504
|
+
const card =
|
|
1505
|
+
selected && selected.kind === 'story'
|
|
1506
|
+
? els.kanban.querySelector('[data-story-id="' + selected.id + '"]')
|
|
1507
|
+
: null;
|
|
1095
1508
|
if (card) card.focus();
|
|
1096
1509
|
}
|
|
1097
1510
|
|
|
@@ -1109,6 +1522,11 @@
|
|
|
1109
1522
|
: el('span', 'mono commit-hash', commit.hash);
|
|
1110
1523
|
item.appendChild(hash);
|
|
1111
1524
|
const subject = el('div', 'item-main item-title', commit.subject);
|
|
1525
|
+
const meta = itemSideText([
|
|
1526
|
+
commit.storyId || '',
|
|
1527
|
+
commit.committedAt ? formatClock(commit.committedAt) : '',
|
|
1528
|
+
]);
|
|
1529
|
+
if (meta) subject.appendChild(el('span', 'muted commit-meta', meta));
|
|
1112
1530
|
item.appendChild(subject);
|
|
1113
1531
|
els.commits.appendChild(item);
|
|
1114
1532
|
}
|
|
@@ -1147,6 +1565,70 @@
|
|
|
1147
1565
|
}
|
|
1148
1566
|
}
|
|
1149
1567
|
|
|
1568
|
+
const RESILIENCE_EVENTS = new Set([
|
|
1569
|
+
'retry',
|
|
1570
|
+
'agent:attempt',
|
|
1571
|
+
'agent:activity',
|
|
1572
|
+
'agent:result',
|
|
1573
|
+
'failover',
|
|
1574
|
+
]);
|
|
1575
|
+
|
|
1576
|
+
function historyMessage(event) {
|
|
1577
|
+
switch (event.type) {
|
|
1578
|
+
case 'session:start':
|
|
1579
|
+
return 'Sessão iniciada';
|
|
1580
|
+
case 'session:end':
|
|
1581
|
+
return 'Sessão encerrada: ' + event.status;
|
|
1582
|
+
case 'phase:start':
|
|
1583
|
+
return 'Fase iniciada: ' + event.phase;
|
|
1584
|
+
case 'phase:end':
|
|
1585
|
+
return 'Fase encerrada: ' + event.phase + (event.success ? ' (ok)' : ' (falhou)');
|
|
1586
|
+
case 'iteration:start':
|
|
1587
|
+
return 'Iteração ' + event.iteration + ' iniciada';
|
|
1588
|
+
case 'iteration:end':
|
|
1589
|
+
return 'Iteração ' + event.iteration + ' encerrada';
|
|
1590
|
+
case 'retry':
|
|
1591
|
+
return 'Retry ' + event.attempt + (event.kind ? ': ' + event.kind : '');
|
|
1592
|
+
case 'agent:attempt':
|
|
1593
|
+
return 'Tentativa ' + event.attempt + ' com ' + event.provider;
|
|
1594
|
+
case 'agent:activity':
|
|
1595
|
+
return 'Atividade recebida de ' + event.provider;
|
|
1596
|
+
case 'agent:result':
|
|
1597
|
+
return event.provider + (event.success ? ' concluiu a tentativa' : ' falhou: ' + (event.failureKind || 'desconhecida'));
|
|
1598
|
+
case 'failover':
|
|
1599
|
+
return 'Failover de ' + event.from + ' para ' + event.to + (event.reason ? ': ' + event.reason : '');
|
|
1600
|
+
case 'correction:cycle':
|
|
1601
|
+
return 'Ciclo de correção ' + event.cycle + '/' + event.maxCycles;
|
|
1602
|
+
case 'log':
|
|
1603
|
+
return event.message;
|
|
1604
|
+
default:
|
|
1605
|
+
return event.type;
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
function renderHistory() {
|
|
1610
|
+
clear(els.history);
|
|
1611
|
+
const entries = state.events.filter((entry) => {
|
|
1612
|
+
const type = entry && entry.event && entry.event.type;
|
|
1613
|
+
if (!type) return false;
|
|
1614
|
+
if (state.historyFilter === 'resilience') return RESILIENCE_EVENTS.has(type);
|
|
1615
|
+
if (state.historyFilter === 'pipeline') return !RESILIENCE_EVENTS.has(type);
|
|
1616
|
+
return true;
|
|
1617
|
+
});
|
|
1618
|
+
if (entries.length === 0) {
|
|
1619
|
+
els.history.appendChild(el('p', 'empty', 'Nenhum evento para exibir.'));
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
for (const entry of entries.slice().reverse()) {
|
|
1623
|
+
const event = entry.event;
|
|
1624
|
+
const item = el('li', 'history-entry');
|
|
1625
|
+
item.appendChild(el('span', 'log-time', formatClock(event.at)));
|
|
1626
|
+
item.appendChild(el('span', 'history-type mono', event.type));
|
|
1627
|
+
item.appendChild(el('span', 'history-message', historyMessage(event)));
|
|
1628
|
+
els.history.appendChild(item);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1150
1632
|
function renderMeta(snapshot) {
|
|
1151
1633
|
const parts = [];
|
|
1152
1634
|
if (snapshot.sessionId) parts.push('sessão ' + snapshot.sessionId);
|
|
@@ -1184,6 +1666,11 @@
|
|
|
1184
1666
|
|
|
1185
1667
|
// ---- Inicialização --------------------------------------------------------
|
|
1186
1668
|
|
|
1669
|
+
function onThemeChange(select) {
|
|
1670
|
+
setTheme(select.value);
|
|
1671
|
+
storeTheme(state.theme);
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1187
1674
|
function onRefreshChange(select) {
|
|
1188
1675
|
state.refreshSeconds = Number(select.value);
|
|
1189
1676
|
storeRefresh(state.refreshSeconds);
|
|
@@ -1209,10 +1696,22 @@
|
|
|
1209
1696
|
);
|
|
1210
1697
|
}
|
|
1211
1698
|
|
|
1699
|
+
els.themeSelect.addEventListener('change', () => onThemeChange(els.themeSelect));
|
|
1700
|
+
if (els.themeSelectDashboard) {
|
|
1701
|
+
els.themeSelectDashboard.addEventListener('change', () =>
|
|
1702
|
+
onThemeChange(els.themeSelectDashboard),
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
initTheme();
|
|
1706
|
+
|
|
1212
1707
|
els.logFilter.addEventListener('change', () => {
|
|
1213
1708
|
state.logFilter = els.logFilter.value;
|
|
1214
1709
|
if (state.snapshot) renderLogs(state.snapshot);
|
|
1215
1710
|
});
|
|
1711
|
+
els.historyFilter.addEventListener('change', () => {
|
|
1712
|
+
state.historyFilter = els.historyFilter.value;
|
|
1713
|
+
renderHistory();
|
|
1714
|
+
});
|
|
1216
1715
|
|
|
1217
1716
|
document.addEventListener('visibilitychange', () => {
|
|
1218
1717
|
if (document.hidden) clearTimer();
|
|
@@ -1227,6 +1726,7 @@
|
|
|
1227
1726
|
} else {
|
|
1228
1727
|
try {
|
|
1229
1728
|
const health = await fetch('api/health', { cache: 'no-store' }).then((r) => r.json());
|
|
1729
|
+
state.configWritable = list(health.capabilities).includes('config:agent:write');
|
|
1230
1730
|
const suggested = Number(health.refreshSeconds);
|
|
1231
1731
|
if (Number.isFinite(suggested) && suggested > 0) state.refreshSeconds = suggested;
|
|
1232
1732
|
} catch (err) {
|