@grifhinz/logics-manager 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -3
- package/VERSION +1 -1
- package/clients/viewer/browser-host.js +1181 -83
- package/clients/viewer/index.html +1 -0
- package/clients/viewer/viewer.css +291 -1
- package/logics_manager/cli.py +5 -3
- package/logics_manager/flow.py +61 -1
- package/logics_manager/sync.py +47 -19
- package/logics_manager/viewer.py +973 -16
- package/package.json +1 -1
- package/pyproject.toml +1 -1
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
const refreshIntervalControl = () => document.getElementById("viewer-refresh-interval");
|
|
22
22
|
const refreshMenuButton = () => document.getElementById("viewer-refresh-menu-button");
|
|
23
23
|
const refreshMenuPanel = () => document.getElementById("viewer-refresh-menu");
|
|
24
|
+
const versionLink = () => document.getElementById("viewer-version-link");
|
|
24
25
|
const activityClearControl = () => document.getElementById("activity-clear");
|
|
25
26
|
const activityStorageLimit = 80;
|
|
26
27
|
const gitHistoryPageSize = 10;
|
|
@@ -54,8 +55,27 @@
|
|
|
54
55
|
let focusApplied = false;
|
|
55
56
|
let latestGitBadgeCounts = { unpushedCommits: 0, uncommittedFiles: 0 };
|
|
56
57
|
let latestCiStatus = { visible: false, badgeState: "unknown", message: "" };
|
|
58
|
+
let latestUpdateInfo = {};
|
|
59
|
+
let latestCdxMissionState = {
|
|
60
|
+
missionId: "full-audit",
|
|
61
|
+
sessionId: "",
|
|
62
|
+
strengthId: "standard",
|
|
63
|
+
missionInputs: {},
|
|
64
|
+
catalog: null,
|
|
65
|
+
statusPayload: null,
|
|
66
|
+
planPayload: null,
|
|
67
|
+
runPayload: null,
|
|
68
|
+
applyPayload: null
|
|
69
|
+
};
|
|
57
70
|
let connectionState = "connected";
|
|
58
71
|
let lastSuccessfulSyncAt = 0;
|
|
72
|
+
let latestViewerStateSignature = "";
|
|
73
|
+
let latestGitStatusSignature = "";
|
|
74
|
+
let latestCdxStatusSignature = "";
|
|
75
|
+
let latestCiStatusSignature = "";
|
|
76
|
+
let primaryActionBusyKey = "";
|
|
77
|
+
let cdxMissionBusyKey = "";
|
|
78
|
+
let cdxCloseTarget = null;
|
|
59
79
|
|
|
60
80
|
function readStoredState() {
|
|
61
81
|
try {
|
|
@@ -78,6 +98,181 @@
|
|
|
78
98
|
return nextState;
|
|
79
99
|
}
|
|
80
100
|
|
|
101
|
+
function stableStringify(value) {
|
|
102
|
+
if (Array.isArray(value)) {
|
|
103
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
104
|
+
}
|
|
105
|
+
if (value && typeof value === "object") {
|
|
106
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
|
|
107
|
+
}
|
|
108
|
+
return JSON.stringify(value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function viewerStateSignature(payload) {
|
|
112
|
+
const items = Array.isArray(payload?.items) ? payload.items : [];
|
|
113
|
+
const projects = Array.isArray(payload?.projects) ? payload.projects : [];
|
|
114
|
+
return stableStringify({
|
|
115
|
+
root: payload?.root || "",
|
|
116
|
+
repository: payload?.repository || {},
|
|
117
|
+
capabilities: normalizeCapabilities(payload),
|
|
118
|
+
projects: projects.map((project) => ({
|
|
119
|
+
id: project?.id || "",
|
|
120
|
+
active: Boolean(project?.active),
|
|
121
|
+
available: project?.available !== false,
|
|
122
|
+
hasLogics: project?.hasLogics !== false,
|
|
123
|
+
root: project?.root || ""
|
|
124
|
+
})),
|
|
125
|
+
items: items.map((item) => ({
|
|
126
|
+
id: item?.id || "",
|
|
127
|
+
relPath: item?.relPath || "",
|
|
128
|
+
stage: item?.stage || "",
|
|
129
|
+
status: item?.indicators?.Status || item?.status || "",
|
|
130
|
+
updatedAt: item?.updatedAt || ""
|
|
131
|
+
}))
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function gitStatusSignature(payload) {
|
|
136
|
+
return stableStringify({
|
|
137
|
+
state: payload?.state || "",
|
|
138
|
+
branch: payload?.branch || "",
|
|
139
|
+
tracking: payload?.tracking || "",
|
|
140
|
+
ahead: Number(payload?.ahead || 0),
|
|
141
|
+
behind: Number(payload?.behind || 0),
|
|
142
|
+
clean: Boolean(payload?.clean),
|
|
143
|
+
counts: payload?.counts || {},
|
|
144
|
+
badgeCounts: payload?.badgeCounts || {},
|
|
145
|
+
latestCommit: payload?.latestCommit || "",
|
|
146
|
+
recentCommitsHasMore: Boolean(payload?.recentCommitsHasMore)
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function runtimeStatusSignature(payload) {
|
|
151
|
+
return stableStringify(payload || {});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function primaryActionControls() {
|
|
155
|
+
return Array.from(document.querySelectorAll([
|
|
156
|
+
"#viewer-insights",
|
|
157
|
+
"#viewer-health",
|
|
158
|
+
"#viewer-git",
|
|
159
|
+
"#viewer-ci",
|
|
160
|
+
"#viewer-cdx",
|
|
161
|
+
"#viewer-repo-folder",
|
|
162
|
+
'[data-action="refresh"]',
|
|
163
|
+
'[data-viewer-action="edit-document"]',
|
|
164
|
+
"[data-viewer-project-id]",
|
|
165
|
+
"[data-viewer-cdx-mode]",
|
|
166
|
+
"[data-viewer-cdx-report]",
|
|
167
|
+
"[data-viewer-cdx-artifact-path]",
|
|
168
|
+
"[data-viewer-cdx-create-request]"
|
|
169
|
+
].join(","))).filter((node) => node instanceof HTMLElement);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function setPrimaryActionBusy(actionKey, label = "") {
|
|
173
|
+
primaryActionBusyKey = actionKey || "";
|
|
174
|
+
document.body?.classList.toggle("viewer-is-busy", Boolean(primaryActionBusyKey));
|
|
175
|
+
document.body?.toggleAttribute("data-viewer-busy", Boolean(primaryActionBusyKey));
|
|
176
|
+
if (primaryActionBusyKey) {
|
|
177
|
+
document.body?.setAttribute("data-viewer-busy-action", primaryActionBusyKey);
|
|
178
|
+
} else {
|
|
179
|
+
document.body?.removeAttribute("data-viewer-busy-action");
|
|
180
|
+
}
|
|
181
|
+
primaryActionControls().forEach((control) => {
|
|
182
|
+
if (!("disabled" in control)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
control.disabled = Boolean(primaryActionBusyKey);
|
|
186
|
+
control.setAttribute("aria-busy", primaryActionBusyKey ? "true" : "false");
|
|
187
|
+
if (primaryActionBusyKey) {
|
|
188
|
+
control.setAttribute("data-viewer-action-busy", control.getAttribute("data-viewer-action-key") === actionKey ? "active" : "blocked");
|
|
189
|
+
} else {
|
|
190
|
+
control.removeAttribute("data-viewer-action-busy");
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
if (!primaryActionBusyKey) {
|
|
194
|
+
updateCapabilityControls();
|
|
195
|
+
applyLocalViewerChrome();
|
|
196
|
+
}
|
|
197
|
+
if (primaryActionBusyKey && label) {
|
|
198
|
+
setMeta(`${label}...`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function withPrimaryAction(actionKey, label, action) {
|
|
203
|
+
if (primaryActionBusyKey) {
|
|
204
|
+
setMeta("Another viewer action is still running.");
|
|
205
|
+
return Promise.resolve(false);
|
|
206
|
+
}
|
|
207
|
+
setPrimaryActionBusy(actionKey, label);
|
|
208
|
+
return Promise.resolve()
|
|
209
|
+
.then(action)
|
|
210
|
+
.then(() => true)
|
|
211
|
+
.catch((error) => {
|
|
212
|
+
setMeta(error.message || "Viewer action failed.");
|
|
213
|
+
return false;
|
|
214
|
+
})
|
|
215
|
+
.finally(() => {
|
|
216
|
+
setPrimaryActionBusy("", "");
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function cdxMissionActionControls() {
|
|
221
|
+
return Array.from(document.querySelectorAll([
|
|
222
|
+
"[data-viewer-cdx-plan]",
|
|
223
|
+
"[data-viewer-cdx-run]",
|
|
224
|
+
"[data-viewer-cdx-apply-plan]",
|
|
225
|
+
"[data-viewer-cdx-mission]"
|
|
226
|
+
].join(","))).filter((node) => node instanceof HTMLElement);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function setCdxMissionBusy(actionKey, label = "") {
|
|
230
|
+
cdxMissionBusyKey = actionKey || "";
|
|
231
|
+
document.body?.toggleAttribute("data-viewer-cdx-mission-busy", Boolean(cdxMissionBusyKey));
|
|
232
|
+
if (cdxMissionBusyKey) {
|
|
233
|
+
document.body?.setAttribute("data-viewer-cdx-mission-busy-action", cdxMissionBusyKey);
|
|
234
|
+
} else {
|
|
235
|
+
document.body?.removeAttribute("data-viewer-cdx-mission-busy-action");
|
|
236
|
+
}
|
|
237
|
+
cdxMissionActionControls().forEach((control) => {
|
|
238
|
+
if (!("disabled" in control)) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
control.disabled = Boolean(cdxMissionBusyKey);
|
|
242
|
+
control.setAttribute("aria-busy", cdxMissionBusyKey ? "true" : "false");
|
|
243
|
+
if (cdxMissionBusyKey) {
|
|
244
|
+
control.setAttribute("data-viewer-action-busy", control.getAttribute("data-viewer-action-key") === actionKey ? "active" : "blocked");
|
|
245
|
+
} else {
|
|
246
|
+
control.removeAttribute("data-viewer-action-busy");
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
if (!cdxMissionBusyKey) {
|
|
250
|
+
updateCapabilityControls();
|
|
251
|
+
applyLocalViewerChrome();
|
|
252
|
+
}
|
|
253
|
+
if (cdxMissionBusyKey && label) {
|
|
254
|
+
setMeta(`${label}...`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function withCdxMissionAction(actionKey, label, action) {
|
|
259
|
+
if (cdxMissionBusyKey) {
|
|
260
|
+
setMeta("Another CDX mission action is still running.");
|
|
261
|
+
return Promise.resolve(false);
|
|
262
|
+
}
|
|
263
|
+
setCdxMissionBusy(actionKey, label);
|
|
264
|
+
return Promise.resolve()
|
|
265
|
+
.then(action)
|
|
266
|
+
.then(() => true)
|
|
267
|
+
.catch((error) => {
|
|
268
|
+
setMeta(error.message || "CDX mission action failed.");
|
|
269
|
+
return false;
|
|
270
|
+
})
|
|
271
|
+
.finally(() => {
|
|
272
|
+
setCdxMissionBusy("", "");
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
81
276
|
function hydrateViewerFilterState() {
|
|
82
277
|
const storedState = readStoredState();
|
|
83
278
|
viewerFilterState = sanitizeViewerFilterState(storedState?.viewerFilterState);
|
|
@@ -476,6 +671,18 @@
|
|
|
476
671
|
}
|
|
477
672
|
}
|
|
478
673
|
|
|
674
|
+
function updateVersionLink(updateInfo = latestUpdateInfo) {
|
|
675
|
+
latestUpdateInfo = updateInfo && typeof updateInfo === "object" ? updateInfo : {};
|
|
676
|
+
const link = versionLink();
|
|
677
|
+
if (!(link instanceof HTMLAnchorElement)) {
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const currentVersion = String(latestUpdateInfo.currentVersion || "").trim();
|
|
681
|
+
link.textContent = currentVersion ? `v${currentVersion.replace(/^v/i, "")}` : "v0.0.0";
|
|
682
|
+
link.href = latestRepository.githubUrl || "https://github.com/AlexAgo83/logics-manager";
|
|
683
|
+
link.title = "Open Logics Manager on GitHub";
|
|
684
|
+
}
|
|
685
|
+
|
|
479
686
|
async function openRepositoryFolder() {
|
|
480
687
|
if (!latestRepository.root) {
|
|
481
688
|
setMeta("Repository folder is unavailable.");
|
|
@@ -614,6 +821,7 @@
|
|
|
614
821
|
}
|
|
615
822
|
const data = await response.json();
|
|
616
823
|
if (response.ok && data.ok) {
|
|
824
|
+
latestCiStatusSignature = runtimeStatusSignature(data.payload);
|
|
617
825
|
updateMainCiBadge(data.payload);
|
|
618
826
|
}
|
|
619
827
|
} catch {
|
|
@@ -644,13 +852,22 @@
|
|
|
644
852
|
return cdxProviders(status).reduce((total, provider) => total + Math.max(0, Number(provider.active || 0)), 0);
|
|
645
853
|
}
|
|
646
854
|
|
|
647
|
-
function
|
|
855
|
+
function activeCdxRunCountFromPayload(payload) {
|
|
856
|
+
if (!payload || payload.state !== "ok" || !Array.isArray(payload.runs)) {
|
|
857
|
+
return 0;
|
|
858
|
+
}
|
|
859
|
+
return payload.runs.filter((run) => ["running", "starting", "pending"].includes(String(cdxField(run, ["status", "state"], "")).toLowerCase())).length;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function updateMainCdxBadge(payload, runsPayload = null) {
|
|
648
863
|
const button = document.getElementById("viewer-cdx");
|
|
649
864
|
if (!(button instanceof HTMLElement)) {
|
|
650
865
|
return;
|
|
651
866
|
}
|
|
652
867
|
button.querySelector("[data-viewer-cdx-badge]")?.remove();
|
|
653
|
-
const
|
|
868
|
+
const activeSessions = activeCdxAssistantCountFromPayload(payload);
|
|
869
|
+
const activeRuns = activeCdxRunCountFromPayload(runsPayload);
|
|
870
|
+
const activeCount = activeSessions + activeRuns;
|
|
654
871
|
if (activeCount <= 0) {
|
|
655
872
|
button.title = isCapabilityAvailable("cdx")
|
|
656
873
|
? "Show CDX status"
|
|
@@ -658,9 +875,17 @@
|
|
|
658
875
|
return;
|
|
659
876
|
}
|
|
660
877
|
const label = activeCount > 9 ? "9+" : String(activeCount);
|
|
661
|
-
const
|
|
878
|
+
const titleParts = [];
|
|
879
|
+
if (activeSessions > 0) {
|
|
880
|
+
titleParts.push(activeSessions === 1 ? "1 active session" : `${activeSessions} active sessions`);
|
|
881
|
+
}
|
|
882
|
+
if (activeRuns > 0) {
|
|
883
|
+
titleParts.push(activeRuns === 1 ? "1 running run" : `${activeRuns} running runs`);
|
|
884
|
+
}
|
|
885
|
+
const title = titleParts.join(" · ");
|
|
662
886
|
button.title = `Show CDX status · ${title}`;
|
|
663
|
-
|
|
887
|
+
const tone = activeRuns > 0 ? " viewer-cdx-button-badge--runs" : "";
|
|
888
|
+
button.insertAdjacentHTML("beforeend", `<span class="viewer-cdx-button-badge${tone}" data-viewer-cdx-badge title="${escapeHtml(title)}" aria-label="${escapeHtml(title)}">${escapeHtml(label)}</span>`);
|
|
664
889
|
}
|
|
665
890
|
|
|
666
891
|
async function refreshCdxBadgeCounters() {
|
|
@@ -669,14 +894,23 @@
|
|
|
669
894
|
return;
|
|
670
895
|
}
|
|
671
896
|
try {
|
|
672
|
-
const
|
|
673
|
-
|
|
897
|
+
const [statusResponse, runsResponse] = await Promise.all([
|
|
898
|
+
fetch("/api/cdx-status"),
|
|
899
|
+
fetch("/api/cdx-runs").catch(() => null)
|
|
900
|
+
]);
|
|
901
|
+
if (statusResponse.status === 404) {
|
|
674
902
|
updateMainCdxBadge(null);
|
|
675
903
|
return;
|
|
676
904
|
}
|
|
677
|
-
const data = await
|
|
678
|
-
|
|
679
|
-
|
|
905
|
+
const data = await statusResponse.json();
|
|
906
|
+
let runsPayload = null;
|
|
907
|
+
if (runsResponse && runsResponse.ok) {
|
|
908
|
+
const runsData = await runsResponse.json();
|
|
909
|
+
runsPayload = runsData?.ok ? runsData.payload : null;
|
|
910
|
+
}
|
|
911
|
+
if (statusResponse.ok && data.ok) {
|
|
912
|
+
latestCdxStatusSignature = runtimeStatusSignature({ status: data.payload, runs: runsPayload });
|
|
913
|
+
updateMainCdxBadge(data.payload, runsPayload);
|
|
680
914
|
}
|
|
681
915
|
} catch {
|
|
682
916
|
updateMainCdxBadge(null);
|
|
@@ -700,6 +934,7 @@
|
|
|
700
934
|
const response = await fetch("/api/git-status");
|
|
701
935
|
const data = await response.json();
|
|
702
936
|
if (response.ok && data.ok && data.payload?.state === "ok") {
|
|
937
|
+
latestGitStatusSignature = gitStatusSignature(data.payload);
|
|
703
938
|
setGitBadgeCountsFromPayload(data.payload);
|
|
704
939
|
}
|
|
705
940
|
} catch {
|
|
@@ -825,6 +1060,7 @@
|
|
|
825
1060
|
}
|
|
826
1061
|
|
|
827
1062
|
function setDocument(titleText, html) {
|
|
1063
|
+
cdxCloseTarget = null;
|
|
828
1064
|
const panel = documentPanel();
|
|
829
1065
|
const title = documentTitle();
|
|
830
1066
|
const content = documentContent();
|
|
@@ -843,6 +1079,35 @@
|
|
|
843
1079
|
renderMermaidDiagrams();
|
|
844
1080
|
}
|
|
845
1081
|
|
|
1082
|
+
function currentDocumentSnapshot(fallbackTitle = "Document") {
|
|
1083
|
+
const title = documentTitle();
|
|
1084
|
+
const content = documentContent();
|
|
1085
|
+
return {
|
|
1086
|
+
title: title?.textContent || fallbackTitle,
|
|
1087
|
+
html: content?.innerHTML || ""
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
async function closeDocumentPanel() {
|
|
1092
|
+
const target = cdxCloseTarget;
|
|
1093
|
+
cdxCloseTarget = null;
|
|
1094
|
+
if (target?.type === "cdx-report") {
|
|
1095
|
+
setDocument(target.title || "CDX run report", target.html || "");
|
|
1096
|
+
cdxCloseTarget = { type: "cdx-runs" };
|
|
1097
|
+
setMeta("Returned to CDX run report.");
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
if (target?.type === "cdx-runs") {
|
|
1101
|
+
await showCdxRuns({ silent: true });
|
|
1102
|
+
setMeta("Returned to CDX runs.");
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
const panel = documentPanel();
|
|
1106
|
+
if (panel) {
|
|
1107
|
+
panel.hidden = true;
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
|
|
846
1111
|
function showMermaidFallback(message) {
|
|
847
1112
|
document.querySelectorAll(".markdown-preview__mermaid-fallback").forEach((node) => {
|
|
848
1113
|
if (!(node instanceof HTMLElement)) {
|
|
@@ -949,6 +1214,15 @@
|
|
|
949
1214
|
|
|
950
1215
|
function postToApp(payload, options = {}) {
|
|
951
1216
|
markConnectionHealthy({ silent: Boolean(options.silent) });
|
|
1217
|
+
const nextSignature = viewerStateSignature(payload);
|
|
1218
|
+
if (!options.force && latestViewerStateSignature && nextSignature === latestViewerStateSignature) {
|
|
1219
|
+
if (!options.silent) {
|
|
1220
|
+
setMeta(`Checked just now · no viewer changes (${new Date().toLocaleTimeString()})`);
|
|
1221
|
+
}
|
|
1222
|
+
scheduleNextAutoRefresh();
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
latestViewerStateSignature = nextSignature;
|
|
952
1226
|
latestItems = updateStoredActivity(Array.isArray(payload.items) ? payload.items : []);
|
|
953
1227
|
if (!autoRefreshIntervalTouched) {
|
|
954
1228
|
autoRefreshIntervalMs = normalizeAutoRefreshIntervalSeconds(payload.autoRefreshIntervalSeconds) * 1000;
|
|
@@ -965,12 +1239,14 @@
|
|
|
965
1239
|
setMeta(`${rootName} · ${payload.items.length} docs · refreshed ${new Date().toLocaleTimeString()}`);
|
|
966
1240
|
}
|
|
967
1241
|
scheduleNextAutoRefresh();
|
|
1242
|
+
updateVersionLink(payload.updateInfo);
|
|
968
1243
|
renderUpdateNotice(payload.updateInfo);
|
|
969
1244
|
refreshCiBadgeCounters();
|
|
970
1245
|
refreshCdxBadgeCounters();
|
|
971
1246
|
updateFilterSummary();
|
|
972
1247
|
applyLocalViewerChrome();
|
|
973
1248
|
bindRefreshMenuControls();
|
|
1249
|
+
return true;
|
|
974
1250
|
}
|
|
975
1251
|
|
|
976
1252
|
function renderUpdateNotice(updateInfo) {
|
|
@@ -1007,11 +1283,11 @@
|
|
|
1007
1283
|
if (!response.ok || !data.ok) {
|
|
1008
1284
|
throw new Error(data.error || "Unable to load viewer data.");
|
|
1009
1285
|
}
|
|
1010
|
-
postToApp(data.payload, { silent: Boolean(options.silent) });
|
|
1286
|
+
const changed = postToApp(data.payload, { silent: Boolean(options.silent), force: Boolean(options.force) });
|
|
1011
1287
|
if (method !== "POST") {
|
|
1012
1288
|
await refreshGitBadgeCounters();
|
|
1013
1289
|
}
|
|
1014
|
-
return
|
|
1290
|
+
return changed;
|
|
1015
1291
|
} catch (error) {
|
|
1016
1292
|
markConnectionDisconnected(error);
|
|
1017
1293
|
throw error;
|
|
@@ -1038,6 +1314,12 @@
|
|
|
1038
1314
|
return Boolean(panel && !panel.hidden && title && title.textContent === "CDX runs");
|
|
1039
1315
|
}
|
|
1040
1316
|
|
|
1317
|
+
function isCdxMissionsOpen() {
|
|
1318
|
+
const panel = documentPanel();
|
|
1319
|
+
const title = documentTitle();
|
|
1320
|
+
return Boolean(panel && !panel.hidden && title && title.textContent === "CDX missions");
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1041
1323
|
function isCiStatusOpen() {
|
|
1042
1324
|
const panel = documentPanel();
|
|
1043
1325
|
const title = documentTitle();
|
|
@@ -1045,18 +1327,23 @@
|
|
|
1045
1327
|
}
|
|
1046
1328
|
|
|
1047
1329
|
async function refreshViewer(method = "POST", options = {}) {
|
|
1048
|
-
await loadItems(method, options);
|
|
1330
|
+
const changed = await loadItems(method, options);
|
|
1049
1331
|
if (isGitStatusOpen()) {
|
|
1050
|
-
await showGitStatus({ preserve: true, silent: Boolean(options.silent) });
|
|
1332
|
+
await showGitStatus({ preserve: true, silent: Boolean(options.silent), skipUnchanged: !changed && !options.force, force: Boolean(options.force) });
|
|
1051
1333
|
} else if (isCiStatusOpen()) {
|
|
1052
|
-
await showCiStatus({ silent: Boolean(options.silent) });
|
|
1334
|
+
await showCiStatus({ silent: Boolean(options.silent), skipUnchanged: !changed && !options.force, force: Boolean(options.force) });
|
|
1053
1335
|
} else if (isCdxStatusOpen()) {
|
|
1054
|
-
await showCdxStatus({ silent: Boolean(options.silent) });
|
|
1336
|
+
await showCdxStatus({ silent: Boolean(options.silent), skipUnchanged: !changed && !options.force, force: Boolean(options.force) });
|
|
1055
1337
|
} else if (isCdxRunsOpen()) {
|
|
1056
|
-
|
|
1338
|
+
if (changed || options.force) {
|
|
1339
|
+
await showCdxRuns({ silent: Boolean(options.silent) });
|
|
1340
|
+
}
|
|
1057
1341
|
} else if (method === "POST") {
|
|
1058
1342
|
await refreshGitBadgeCounters();
|
|
1059
1343
|
}
|
|
1344
|
+
if (!changed && !options.silent && !options.force) {
|
|
1345
|
+
setMeta(`Checked just now · no viewer changes (${new Date().toLocaleTimeString()})`);
|
|
1346
|
+
}
|
|
1060
1347
|
}
|
|
1061
1348
|
|
|
1062
1349
|
function autoRefreshItems() {
|
|
@@ -1347,6 +1634,31 @@
|
|
|
1347
1634
|
`).join("");
|
|
1348
1635
|
}
|
|
1349
1636
|
|
|
1637
|
+
function renderGitSummaryCard(label, value) {
|
|
1638
|
+
return `
|
|
1639
|
+
<div class="viewer-insights__card">
|
|
1640
|
+
<div class="viewer-insights__label">${escapeHtml(label)}</div>
|
|
1641
|
+
<div class="viewer-insights__value">${escapeHtml(value)}</div>
|
|
1642
|
+
</div>
|
|
1643
|
+
`;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
function renderGitSummarySegments(label, segments) {
|
|
1647
|
+
return `
|
|
1648
|
+
<div class="viewer-insights__card viewer-git__summary-card">
|
|
1649
|
+
<div class="viewer-insights__label">${escapeHtml(label)}</div>
|
|
1650
|
+
<div class="viewer-git__summary-segments">
|
|
1651
|
+
${segments.map(([segmentLabel, value]) => `
|
|
1652
|
+
<span class="viewer-git__summary-segment">
|
|
1653
|
+
<span>${escapeHtml(segmentLabel)}</span>
|
|
1654
|
+
<strong>${escapeHtml(value)}</strong>
|
|
1655
|
+
</span>
|
|
1656
|
+
`).join("")}
|
|
1657
|
+
</div>
|
|
1658
|
+
</div>
|
|
1659
|
+
`;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1350
1662
|
function renderInsightBars(entries, total) {
|
|
1351
1663
|
const denominator = Math.max(1, Number(total) || 0);
|
|
1352
1664
|
if (!entries.length) {
|
|
@@ -1770,15 +2082,41 @@
|
|
|
1770
2082
|
return asArray(status?.rows);
|
|
1771
2083
|
}
|
|
1772
2084
|
|
|
2085
|
+
function numericValues(values) {
|
|
2086
|
+
return values.map((value) => Number(value)).filter((value) => Number.isFinite(value));
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
function formatPercentRange(values) {
|
|
2090
|
+
const numbers = numericValues(values).map((value) => Math.max(0, Math.min(100, Math.round(value))));
|
|
2091
|
+
if (!numbers.length) {
|
|
2092
|
+
return "not reported";
|
|
2093
|
+
}
|
|
2094
|
+
const min = Math.min(...numbers);
|
|
2095
|
+
const max = Math.max(...numbers);
|
|
2096
|
+
return min === max ? `${min}%` : `${min}-${max}%`;
|
|
2097
|
+
}
|
|
2098
|
+
|
|
1773
2099
|
function cdxProviders(status) {
|
|
1774
|
-
const
|
|
1775
|
-
if (
|
|
1776
|
-
return
|
|
2100
|
+
const rows = cdxRows(status);
|
|
2101
|
+
if (!rows.length) {
|
|
2102
|
+
return pickFirstArray(status, ["providers", "providerStatus", "provider_status"]);
|
|
1777
2103
|
}
|
|
1778
2104
|
const grouped = new Map();
|
|
1779
|
-
|
|
2105
|
+
rows.forEach((row) => {
|
|
1780
2106
|
const provider = String(row.provider || "unknown");
|
|
1781
|
-
const current = grouped.get(provider) || {
|
|
2107
|
+
const current = grouped.get(provider) || {
|
|
2108
|
+
name: provider,
|
|
2109
|
+
enabled: 0,
|
|
2110
|
+
active: 0,
|
|
2111
|
+
authenticated: 0,
|
|
2112
|
+
sessions: 0,
|
|
2113
|
+
remaining_5h: "not reported",
|
|
2114
|
+
remaining_week: "not reported",
|
|
2115
|
+
credits: "",
|
|
2116
|
+
_remaining5hValues: [],
|
|
2117
|
+
_remainingWeekValues: [],
|
|
2118
|
+
_creditsValues: []
|
|
2119
|
+
};
|
|
1782
2120
|
current.sessions += 1;
|
|
1783
2121
|
if (row.enabled) {
|
|
1784
2122
|
current.enabled += 1;
|
|
@@ -1789,15 +2127,31 @@
|
|
|
1789
2127
|
if (String(row.auth_status || "").toLowerCase() === "authenticated") {
|
|
1790
2128
|
current.authenticated += 1;
|
|
1791
2129
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
2130
|
+
const fiveHour = Number(row.remaining_5h_pct ?? row.remaining5hPct);
|
|
2131
|
+
if (Number.isFinite(fiveHour)) {
|
|
2132
|
+
current._remaining5hValues.push(fiveHour);
|
|
2133
|
+
}
|
|
2134
|
+
const week = Number(row.remaining_week_pct ?? row.remainingWeekPct);
|
|
2135
|
+
if (Number.isFinite(week)) {
|
|
2136
|
+
current._remainingWeekValues.push(week);
|
|
2137
|
+
}
|
|
2138
|
+
if (row.credits !== undefined && row.credits !== null && row.credits !== "") {
|
|
2139
|
+
current._creditsValues.push(row.credits);
|
|
1796
2140
|
}
|
|
1797
2141
|
current.state = current.active > 0 ? "active" : current.enabled > 0 ? "enabled" : "disabled";
|
|
1798
2142
|
grouped.set(provider, current);
|
|
1799
2143
|
});
|
|
1800
|
-
return Array.from(grouped.values())
|
|
2144
|
+
return Array.from(grouped.values()).map((provider) => {
|
|
2145
|
+
const creditsNumbers = numericValues(provider._creditsValues);
|
|
2146
|
+
const creditsTotal = creditsNumbers.length ? creditsNumbers.reduce((total, value) => total + value, 0) : null;
|
|
2147
|
+
const { _remaining5hValues, _remainingWeekValues, _creditsValues, ...publicProvider } = provider;
|
|
2148
|
+
return {
|
|
2149
|
+
...publicProvider,
|
|
2150
|
+
remaining_5h: formatPercentRange(_remaining5hValues),
|
|
2151
|
+
remaining_week: formatPercentRange(_remainingWeekValues),
|
|
2152
|
+
credits: creditsTotal === null ? "" : creditsTotal.toFixed(2)
|
|
2153
|
+
};
|
|
2154
|
+
});
|
|
1801
2155
|
}
|
|
1802
2156
|
|
|
1803
2157
|
function cdxSessions(status) {
|
|
@@ -1837,8 +2191,25 @@
|
|
|
1837
2191
|
return rows || `<li class="viewer-cdx__empty">${escapeHtml(emptyText)}</li>`;
|
|
1838
2192
|
}
|
|
1839
2193
|
|
|
2194
|
+
function renderCdxArtifactRows(value, emptyText) {
|
|
2195
|
+
const rows = objectEntries(value).slice(0, 12).map(([key, entry]) => {
|
|
2196
|
+
const path = typeof entry === "string" ? entry : "";
|
|
2197
|
+
return `
|
|
2198
|
+
<li class="viewer-cdx__row">
|
|
2199
|
+
<span>${escapeHtml(cdxLabel(key))}</span>
|
|
2200
|
+
<strong>${path
|
|
2201
|
+
? `<button class="viewer-cdx__path-link" type="button" data-viewer-cdx-artifact-path="${escapeHtml(path)}">${escapeHtml(path)}</button>`
|
|
2202
|
+
: escapeHtml(typeof entry === "object" ? JSON.stringify(entry) : entry)}
|
|
2203
|
+
</strong>
|
|
2204
|
+
</li>
|
|
2205
|
+
`;
|
|
2206
|
+
}).join("");
|
|
2207
|
+
return rows || `<li class="viewer-cdx__empty">${escapeHtml(emptyText)}</li>`;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
1840
2210
|
function cdxLabel(value) {
|
|
1841
2211
|
return String(value || "")
|
|
2212
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
1842
2213
|
.replace(/[_-]+/g, " ")
|
|
1843
2214
|
.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
1844
2215
|
}
|
|
@@ -1848,7 +2219,7 @@
|
|
|
1848
2219
|
if (["ready", "ok", "active", "enabled", "authenticated"].some((entry) => state.includes(entry))) {
|
|
1849
2220
|
return "ok";
|
|
1850
2221
|
}
|
|
1851
|
-
if (["starting", "pending", "warning", "low", "limited"].some((entry) => state.includes(entry))) {
|
|
2222
|
+
if (["starting", "pending", "running", "warning", "low", "limited", "stale"].some((entry) => state.includes(entry))) {
|
|
1852
2223
|
return "warn";
|
|
1853
2224
|
}
|
|
1854
2225
|
if (["error", "failed", "disabled", "unavailable", "unauthenticated"].some((entry) => state.includes(entry))) {
|
|
@@ -1981,6 +2352,10 @@
|
|
|
1981
2352
|
return `<span class="viewer-cdx__badge viewer-cdx__badge--${cdxStateClass(label)}">${escapeHtml(cdxLabel(label))}</span>`;
|
|
1982
2353
|
}
|
|
1983
2354
|
|
|
2355
|
+
function cdxRunStatusDetail(run) {
|
|
2356
|
+
return "";
|
|
2357
|
+
}
|
|
2358
|
+
|
|
1984
2359
|
function cdxDetailEntries(item, excludedKeys) {
|
|
1985
2360
|
return objectEntries(item)
|
|
1986
2361
|
.filter(([key, value]) => !excludedKeys.includes(key) && value !== undefined && value !== null && value !== "")
|
|
@@ -2106,10 +2481,228 @@
|
|
|
2106
2481
|
return rows || `<li class="viewer-cdx__empty">${escapeHtml(emptyText)}</li>`;
|
|
2107
2482
|
}
|
|
2108
2483
|
|
|
2484
|
+
function cdxMissionCatalog(payload = {}) {
|
|
2485
|
+
return payload.catalog || {
|
|
2486
|
+
missions: [
|
|
2487
|
+
{ id: "full-audit", title: "Full audit", description: "Audit the repository and optionally apply safe, validated fixes.", scope: "repository", requiresPlanConfirmation: false, supportsFileWrites: true, inputFields: [{ id: "directFixes", label: "Fix directly", type: "checkbox" }] },
|
|
2488
|
+
{ id: "release-review", title: "Review since latest release", description: "Review changes since the latest release and optionally apply safe fixes.", scope: "latest-release", requiresPlanConfirmation: false, supportsFileWrites: true, inputFields: [{ id: "directFixes", label: "Fix directly", type: "checkbox" }] },
|
|
2489
|
+
{ id: "corpus-ready", title: "Prepare dev-ready corpus", description: "Produce a corpus plan for explicit deterministic application.", scope: "open-logics-workflow", requiresPlanConfirmation: true, supportsFileWrites: false },
|
|
2490
|
+
{ id: "wish-to-request", title: "Wish to request", description: "Create or draft a structured Logics request from a free-form wish.", scope: "request-draft", requiresPlanConfirmation: false, supportsFileWrites: true, inputFields: [{ id: "wishText", label: "Wish or intent", type: "textarea", required: true }] },
|
|
2491
|
+
{ id: "pre-release", title: "Guarded pre-release", description: "Prepare release metadata, changelog, validation, and fixes without tagging or publishing.", scope: "pre-release-report", requiresPlanConfirmation: false, supportsFileWrites: true, inputFields: [{ id: "releaseVersion", label: "Version", type: "text", placeholder: "vX.X.X", required: true }, { id: "runFullValidation", label: "Run full validation and report fixes before pre-release", type: "checkbox" }] }
|
|
2492
|
+
],
|
|
2493
|
+
strengths: [
|
|
2494
|
+
{ id: "standard", label: "Standard" },
|
|
2495
|
+
{ id: "deep", label: "Deep" },
|
|
2496
|
+
{ id: "max", label: "Max" }
|
|
2497
|
+
],
|
|
2498
|
+
defaultMissionId: "full-audit",
|
|
2499
|
+
defaultStrengthId: "standard"
|
|
2500
|
+
};
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
function selectedCdxMissionRequest() {
|
|
2504
|
+
const catalog = latestCdxMissionState.catalog || cdxMissionCatalog();
|
|
2505
|
+
const missions = Array.isArray(catalog.missions) ? catalog.missions : [];
|
|
2506
|
+
const missionId = latestCdxMissionState.missionId || "full-audit";
|
|
2507
|
+
const mission = missions.find((entry) => entry.id === missionId) || {};
|
|
2508
|
+
const allowFileWrites = mission.supportsFileWrites === false
|
|
2509
|
+
? "false"
|
|
2510
|
+
: (latestCdxMissionState.missionInputs.allowFileWrites === "false" ? "false" : "true");
|
|
2511
|
+
return {
|
|
2512
|
+
missionId,
|
|
2513
|
+
sessionId: latestCdxMissionState.sessionId || "",
|
|
2514
|
+
strengthId: latestCdxMissionState.strengthId || "standard",
|
|
2515
|
+
...latestCdxMissionState.missionInputs,
|
|
2516
|
+
allowFileWrites,
|
|
2517
|
+
commitAtEnd: latestCdxMissionState.missionInputs.commitAtEnd === "true" ? "true" : "false"
|
|
2518
|
+
};
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
function renderCdxMissionInputs(mission) {
|
|
2522
|
+
const fields = Array.isArray(mission?.inputFields) ? mission.inputFields : [];
|
|
2523
|
+
if (!fields.length) {
|
|
2524
|
+
return "";
|
|
2525
|
+
}
|
|
2526
|
+
const rows = fields.map((field) => {
|
|
2527
|
+
const id = field.id || "";
|
|
2528
|
+
const value = latestCdxMissionState.missionInputs[id] || "";
|
|
2529
|
+
if (field.type === "checkbox") {
|
|
2530
|
+
return `
|
|
2531
|
+
<label class="viewer-cdx__field viewer-cdx__field--check">
|
|
2532
|
+
<input data-viewer-cdx-input="${escapeHtml(id)}" type="checkbox"${value === "true" ? " checked" : ""}>
|
|
2533
|
+
<span>${escapeHtml(field.label || cdxLabel(id))}</span>
|
|
2534
|
+
</label>
|
|
2535
|
+
`;
|
|
2536
|
+
}
|
|
2537
|
+
if (field.type === "textarea") {
|
|
2538
|
+
return `
|
|
2539
|
+
<label class="viewer-cdx__field">
|
|
2540
|
+
<span>${escapeHtml(field.label || cdxLabel(id))}</span>
|
|
2541
|
+
<textarea data-viewer-cdx-input="${escapeHtml(id)}" placeholder="${escapeHtml(field.placeholder || "")}" rows="5">${escapeHtml(value)}</textarea>
|
|
2542
|
+
</label>
|
|
2543
|
+
`;
|
|
2544
|
+
}
|
|
2545
|
+
return `
|
|
2546
|
+
<label class="viewer-cdx__field">
|
|
2547
|
+
<span>${escapeHtml(field.label || cdxLabel(id))}</span>
|
|
2548
|
+
<input data-viewer-cdx-input="${escapeHtml(id)}" type="${escapeHtml(field.type || "text")}" value="${escapeHtml(value)}" placeholder="${escapeHtml(field.placeholder || "")}"${field.pattern ? ` pattern="${escapeHtml(field.pattern)}"` : ""}>
|
|
2549
|
+
</label>
|
|
2550
|
+
`;
|
|
2551
|
+
}).join("");
|
|
2552
|
+
return `<div class="viewer-cdx__inputs">${rows}</div>`;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
function renderCdxMissionSetup(statusPayload, planPayload, runPayload, applyPayload) {
|
|
2556
|
+
const catalog = cdxMissionCatalog(planPayload || {});
|
|
2557
|
+
latestCdxMissionState.catalog = catalog;
|
|
2558
|
+
const missions = Array.isArray(catalog.missions) ? catalog.missions : [];
|
|
2559
|
+
const strengths = Array.isArray(catalog.strengths) ? catalog.strengths : [];
|
|
2560
|
+
const status = statusPayload?.status || {};
|
|
2561
|
+
const sessions = cdxSessions(status);
|
|
2562
|
+
const selectedSession = latestCdxMissionState.sessionId || cdxField(sessions[0] || {}, ["id", "name", "session_name", "value"], "");
|
|
2563
|
+
const missionId = latestCdxMissionState.missionId || catalog.defaultMissionId || "full-audit";
|
|
2564
|
+
const selectedMission = missions.find((mission) => mission.id === missionId) || {};
|
|
2565
|
+
const strengthId = latestCdxMissionState.strengthId || catalog.defaultStrengthId || "standard";
|
|
2566
|
+
const supportsFileWrites = selectedMission.supportsFileWrites !== false;
|
|
2567
|
+
const allowFileWrites = supportsFileWrites && latestCdxMissionState.missionInputs.allowFileWrites !== "false";
|
|
2568
|
+
const fileWriteLabel = ["full-audit", "release-review"].includes(selectedMission.id)
|
|
2569
|
+
? "Write mission corpus/report"
|
|
2570
|
+
: "Allow CDX to modify files";
|
|
2571
|
+
const fileWriteControl = supportsFileWrites
|
|
2572
|
+
? `
|
|
2573
|
+
<label class="viewer-cdx__field viewer-cdx__field--check">
|
|
2574
|
+
<input data-viewer-cdx-input="allowFileWrites" type="checkbox"${allowFileWrites ? " checked" : ""}>
|
|
2575
|
+
<span>${escapeHtml(fileWriteLabel)}</span>
|
|
2576
|
+
</label>
|
|
2577
|
+
<label class="viewer-cdx__field viewer-cdx__field--check">
|
|
2578
|
+
<input data-viewer-cdx-input="commitAtEnd" type="checkbox"${latestCdxMissionState.missionInputs.commitAtEnd === "true" ? " checked" : ""}>
|
|
2579
|
+
<span>Commit changes at end</span>
|
|
2580
|
+
</label>
|
|
2581
|
+
`
|
|
2582
|
+
: `
|
|
2583
|
+
<div class="viewer-cdx__meta">Corpus updates are applied after CDX returns allowed actions.</div>
|
|
2584
|
+
`;
|
|
2585
|
+
latestCdxMissionState.sessionId = selectedSession;
|
|
2586
|
+
const missionCards = missions.map((mission) => `
|
|
2587
|
+
<button class="viewer-cdx__mission${mission.id === missionId ? " is-active" : ""}" type="button" data-viewer-cdx-mission="${escapeHtml(mission.id)}" aria-pressed="${mission.id === missionId ? "true" : "false"}">
|
|
2588
|
+
<strong>${escapeHtml(mission.title || mission.id)}</strong>
|
|
2589
|
+
<span>${escapeHtml(mission.description || "")}</span>
|
|
2590
|
+
<em>${escapeHtml(cdxLabel(mission.scope || ""))}</em>
|
|
2591
|
+
</button>
|
|
2592
|
+
`).join("");
|
|
2593
|
+
const sessionOptions = sessions.map((session) => {
|
|
2594
|
+
const item = session && typeof session === "object" ? session : { value: session };
|
|
2595
|
+
const id = cdxField(item, ["id", "name", "session_name", "value"], "");
|
|
2596
|
+
const label = [id, cdxField(item, ["provider"], ""), renderTextRemaining(item)].filter(Boolean).join(" · ");
|
|
2597
|
+
return `<option value="${escapeHtml(id)}"${id === selectedSession ? " selected" : ""}>${escapeHtml(label || id)}</option>`;
|
|
2598
|
+
}).join("");
|
|
2599
|
+
const strengthButtons = strengths.map((strength) => `
|
|
2600
|
+
<button class="viewer-cdx__mode${strength.id === strengthId ? " is-active" : ""}" type="button" data-viewer-cdx-strength="${escapeHtml(strength.id)}" aria-pressed="${strength.id === strengthId ? "true" : "false"}">${escapeHtml(strength.label || cdxLabel(strength.id))}</button>
|
|
2601
|
+
`).join("");
|
|
2602
|
+
const plan = planPayload?.plan;
|
|
2603
|
+
const warnings = Array.isArray(plan?.warnings) ? plan.warnings : [];
|
|
2604
|
+
const command = Array.isArray(plan?.command) ? plan.command.join(" ") : "";
|
|
2605
|
+
const warningRows = warnings.map((warning) => `<li>${escapeHtml(warning)}</li>`).join("");
|
|
2606
|
+
const canRun = planPayload?.state === "ok" && plan?.canRun;
|
|
2607
|
+
const usage = runPayload?.run?.usage || {};
|
|
2608
|
+
const run = runPayload?.run;
|
|
2609
|
+
const usageText = usage.available
|
|
2610
|
+
? `${usage.totalTokens ?? "-"} total · ${usage.inputTokens ?? "-"} in · ${usage.outputTokens ?? "-"} out`
|
|
2611
|
+
: (usage.message || "Token usage not reported yet.");
|
|
2612
|
+
const parsedActions = Array.isArray(run?.parsed?.actions) ? run.parsed.actions : [];
|
|
2613
|
+
const applyResults = Array.isArray(applyPayload?.results) ? applyPayload.results : [];
|
|
2614
|
+
const actionRows = parsedActions.map((action) => `
|
|
2615
|
+
<li class="viewer-cdx__row"><span>${escapeHtml(cdxLabel(action.type || "action"))}</span><strong>${escapeHtml(action.target || "-")}</strong></li>
|
|
2616
|
+
`).join("");
|
|
2617
|
+
const applyRows = applyResults.map((result) => `
|
|
2618
|
+
<li class="viewer-cdx__row"><span>${escapeHtml(cdxLabel(result.type || "action"))}</span><strong>${escapeHtml(result.returnCode === 0 ? "applied" : "failed")}</strong></li>
|
|
2619
|
+
`).join("");
|
|
2620
|
+
return `
|
|
2621
|
+
<div class="viewer-cdx__workspace viewer-cdx__workspace--missions">
|
|
2622
|
+
<div class="viewer-cdx__stack">
|
|
2623
|
+
<section class="viewer-cdx__section">
|
|
2624
|
+
<h2 class="viewer-cdx__heading">Mission</h2>
|
|
2625
|
+
<div class="viewer-cdx__missions">${missionCards}</div>
|
|
2626
|
+
</section>
|
|
2627
|
+
<section class="viewer-cdx__section">
|
|
2628
|
+
<h2 class="viewer-cdx__heading">Execution</h2>
|
|
2629
|
+
<label class="viewer-cdx__field">
|
|
2630
|
+
<span>Session</span>
|
|
2631
|
+
<select data-viewer-cdx-session>${sessionOptions || '<option value="">No session reported</option>'}</select>
|
|
2632
|
+
</label>
|
|
2633
|
+
<div class="viewer-cdx__strengths">${strengthButtons}</div>
|
|
2634
|
+
${fileWriteControl}
|
|
2635
|
+
${renderCdxMissionInputs(selectedMission)}
|
|
2636
|
+
<div class="viewer-cdx__actions">
|
|
2637
|
+
<button class="btn" type="button" data-viewer-cdx-plan>Preview</button>
|
|
2638
|
+
<button class="btn" type="button" data-viewer-cdx-run${canRun ? "" : " disabled"}>Launch run</button>
|
|
2639
|
+
</div>
|
|
2640
|
+
</section>
|
|
2641
|
+
</div>
|
|
2642
|
+
<div class="viewer-cdx__stack">
|
|
2643
|
+
<section class="viewer-cdx__section">
|
|
2644
|
+
<h2 class="viewer-cdx__heading">Plan preview</h2>
|
|
2645
|
+
${planPayload && planPayload.state !== "ok" ? `<div class="viewer-cdx__state">${escapeHtml(planPayload.message || "Unable to build mission plan.")}</div>` : ""}
|
|
2646
|
+
${command ? `<pre class="viewer-cdx__code">${escapeHtml(command)}</pre>` : '<div class="viewer-cdx__empty">Preview a mission to inspect the exact command before launch.</div>'}
|
|
2647
|
+
${plan?.releaseTag ? `<div class="viewer-cdx__meta">Base tag: ${escapeHtml(plan.releaseTag)}</div>` : ""}
|
|
2648
|
+
${plan?.commitAtEnd ? '<div class="viewer-cdx__meta">Commit at end: enabled when mission changes files.</div>' : ""}
|
|
2649
|
+
${plan?.requiresConfirmation ? '<div class="viewer-cdx__meta">Plan-first mission: Logics changes need explicit apply after CDX returns allowed actions.</div>' : ""}
|
|
2650
|
+
${warningRows ? `<ul class="viewer-cdx__warnings">${warningRows}</ul>` : ""}
|
|
2651
|
+
</section>
|
|
2652
|
+
<section class="viewer-cdx__section">
|
|
2653
|
+
<h2 class="viewer-cdx__heading">Run output</h2>
|
|
2654
|
+
${runPayload ? `<div class="viewer-cdx__state viewer-cdx__state--${escapeHtml(cdxStateClass(runPayload.state))}">${escapeHtml(runPayload.message || cdxLabel(runPayload.state))}</div>` : '<div class="viewer-cdx__empty">No mission run launched yet.</div>'}
|
|
2655
|
+
${run ? `<ul class="viewer-cdx__list">
|
|
2656
|
+
<li class="viewer-cdx__row"><span>Run</span><strong>${escapeHtml(run.runId || "-")}</strong></li>
|
|
2657
|
+
<li class="viewer-cdx__row"><span>Usage</span><strong>${escapeHtml(usageText)}</strong></li>
|
|
2658
|
+
<li class="viewer-cdx__row"><span>Return code</span><strong>${escapeHtml(run.returnCode ?? "-")}</strong></li>
|
|
2659
|
+
</ul>` : ""}
|
|
2660
|
+
${run?.stdout ? `<pre class="viewer-cdx__code">${escapeHtml(run.stdout)}</pre>` : ""}
|
|
2661
|
+
${run?.stderr ? `<pre class="viewer-cdx__code viewer-cdx__code--error">${escapeHtml(run.stderr)}</pre>` : ""}
|
|
2662
|
+
</section>
|
|
2663
|
+
${plan?.missionId === "corpus-ready" || latestCdxMissionState.missionId === "corpus-ready" ? `
|
|
2664
|
+
<section class="viewer-cdx__section">
|
|
2665
|
+
<h2 class="viewer-cdx__heading">Corpus apply</h2>
|
|
2666
|
+
<ul class="viewer-cdx__list">${actionRows || '<li class="viewer-cdx__empty">CDX has not returned allowed corpus actions yet.</li>'}</ul>
|
|
2667
|
+
<div class="viewer-cdx__actions">
|
|
2668
|
+
<button class="btn" type="button" data-viewer-cdx-apply-plan${parsedActions.length ? "" : " disabled"}>Apply allowed actions</button>
|
|
2669
|
+
</div>
|
|
2670
|
+
${applyPayload ? `<div class="viewer-cdx__state viewer-cdx__state--${escapeHtml(cdxStateClass(applyPayload.state))}">${escapeHtml(applyPayload.message || cdxLabel(applyPayload.state))}</div>` : ""}
|
|
2671
|
+
${applyRows ? `<ul class="viewer-cdx__list">${applyRows}</ul>` : ""}
|
|
2672
|
+
</section>
|
|
2673
|
+
` : ""}
|
|
2674
|
+
</div>
|
|
2675
|
+
</div>
|
|
2676
|
+
`;
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
function renderTextRemaining(item) {
|
|
2680
|
+
const percent = cdxRemainingPct(item);
|
|
2681
|
+
return percent === null ? "" : `${percent}% remaining`;
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
function renderCdxMissions(statusPayload, planPayload = null, runPayload = null, applyPayload = null) {
|
|
2685
|
+
if (!statusPayload || statusPayload.state !== "ok") {
|
|
2686
|
+
return `
|
|
2687
|
+
<div class="viewer-cdx">
|
|
2688
|
+
${renderCdxModeSwitcher("missions")}
|
|
2689
|
+
<div class="viewer-cdx__state">${escapeHtml(statusPayload?.message || "CDX missions are unavailable.")}</div>
|
|
2690
|
+
</div>
|
|
2691
|
+
`;
|
|
2692
|
+
}
|
|
2693
|
+
return `
|
|
2694
|
+
<div class="viewer-cdx">
|
|
2695
|
+
${renderCdxModeSwitcher("missions")}
|
|
2696
|
+
${renderCdxMissionSetup(statusPayload, planPayload, runPayload, applyPayload)}
|
|
2697
|
+
</div>
|
|
2698
|
+
`;
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2109
2701
|
function renderCdxModeSwitcher(active) {
|
|
2110
2702
|
return `
|
|
2111
2703
|
<div class="viewer-cdx__modes" role="tablist" aria-label="CDX views">
|
|
2112
2704
|
<button class="viewer-cdx__mode${active === "status" ? " is-active" : ""}" type="button" data-viewer-cdx-mode="status" aria-selected="${active === "status" ? "true" : "false"}">Status</button>
|
|
2705
|
+
<button class="viewer-cdx__mode${active === "missions" ? " is-active" : ""}" type="button" data-viewer-cdx-mode="missions" aria-selected="${active === "missions" ? "true" : "false"}">Missions</button>
|
|
2113
2706
|
<button class="viewer-cdx__mode${active === "runs" ? " is-active" : ""}" type="button" data-viewer-cdx-mode="runs" aria-selected="${active === "runs" ? "true" : "false"}">Runs</button>
|
|
2114
2707
|
</div>
|
|
2115
2708
|
`;
|
|
@@ -2163,10 +2756,6 @@
|
|
|
2163
2756
|
<h2 class="viewer-cdx__heading">Sessions</h2>
|
|
2164
2757
|
${renderCdxSessionTable(sessions, "No sessions reported.")}
|
|
2165
2758
|
</section>
|
|
2166
|
-
<section class="viewer-cdx__section">
|
|
2167
|
-
<h2 class="viewer-cdx__heading">Providers</h2>
|
|
2168
|
-
<ul class="viewer-cdx__list">${renderCdxEntityRows(providers, "No provider status reported.", { subtitleKeys: ["model"] })}</ul>
|
|
2169
|
-
</section>
|
|
2170
2759
|
</div>
|
|
2171
2760
|
<div class="viewer-cdx__stack">
|
|
2172
2761
|
<section class="viewer-cdx__section">
|
|
@@ -2177,6 +2766,10 @@
|
|
|
2177
2766
|
<h2 class="viewer-cdx__heading">Safe next commands</h2>
|
|
2178
2767
|
<ul class="viewer-cdx__commands">${commandRows || '<li class="viewer-cdx__empty">No suggested commands reported.</li>'}</ul>
|
|
2179
2768
|
</section>
|
|
2769
|
+
<section class="viewer-cdx__section">
|
|
2770
|
+
<h2 class="viewer-cdx__heading">Providers</h2>
|
|
2771
|
+
<ul class="viewer-cdx__list">${renderCdxEntityRows(providers, "No provider status reported.", { subtitleKeys: ["model"] })}</ul>
|
|
2772
|
+
</section>
|
|
2180
2773
|
</div>
|
|
2181
2774
|
</div>
|
|
2182
2775
|
</div>
|
|
@@ -2193,21 +2786,33 @@
|
|
|
2193
2786
|
`;
|
|
2194
2787
|
}
|
|
2195
2788
|
const runs = Array.isArray(payload.runs) ? payload.runs : [];
|
|
2196
|
-
const
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2789
|
+
const staleCount = runs.filter((run) => String(cdxField(run, ["status", "state"], "")).toLowerCase() === "stale").length;
|
|
2790
|
+
const runningCount = runs.filter((run) => ["running", "starting", "pending"].includes(String(cdxField(run, ["status", "state"], "")).toLowerCase())).length;
|
|
2791
|
+
const runsSummary = staleCount
|
|
2792
|
+
? `${runs.length} reported · ${staleCount} incomplete${runningCount ? ` · ${runningCount} running` : ""}`
|
|
2793
|
+
: runningCount
|
|
2794
|
+
? `${runs.length} reported · ${runningCount} running`
|
|
2795
|
+
: `${runs.length} reported`;
|
|
2796
|
+
const rows = runs.map((run) => {
|
|
2797
|
+
const runId = cdxField(run, ["run_id", "runId", "id"], "");
|
|
2798
|
+
const status = cdxField(run, ["status", "state"], "unknown");
|
|
2799
|
+
const detail = cdxRunStatusDetail(run);
|
|
2800
|
+
return `
|
|
2801
|
+
<tr>
|
|
2802
|
+
<td><code>${escapeHtml(runId || "-")}</code>${detail ? `<div class="viewer-cdx__meta">${escapeHtml(detail)}</div>` : ""}</td>
|
|
2803
|
+
<td>${renderCdxBadge(status)}</td>
|
|
2804
|
+
<td>${escapeHtml(cdxField(run, ["kind"], "assistant"))}</td>
|
|
2805
|
+
<td>${escapeHtml(cdxField(run, ["session", "session_id", "sessionId"], "-"))}</td>
|
|
2806
|
+
<td>${escapeHtml(cdxField(run, ["cwd", "workspace", "repo"], "-"))}</td>
|
|
2807
|
+
<td>${runId ? `<button class="viewer-cdx__mode" type="button" data-viewer-cdx-report="${escapeHtml(runId)}">Report</button>` : ""}</td>
|
|
2808
|
+
</tr>
|
|
2809
|
+
`;
|
|
2810
|
+
}).join("");
|
|
2206
2811
|
return `
|
|
2207
2812
|
<div class="viewer-cdx">
|
|
2208
2813
|
${renderCdxModeSwitcher("runs")}
|
|
2209
2814
|
<section class="viewer-cdx__section">
|
|
2210
|
-
<div class="viewer-ci__heading"><h2>Assistant runs</h2><span>${escapeHtml(
|
|
2815
|
+
<div class="viewer-ci__heading"><h2>Assistant runs</h2><span>${escapeHtml(runsSummary)}</span></div>
|
|
2211
2816
|
<div class="viewer-cdx__table-wrap">
|
|
2212
2817
|
<table class="viewer-cdx__table">
|
|
2213
2818
|
<thead><tr><th>RUN</th><th>STATUS</th><th>KIND</th><th>SESSION</th><th>CWD</th><th>REPORT</th></tr></thead>
|
|
@@ -2219,6 +2824,188 @@
|
|
|
2219
2824
|
`;
|
|
2220
2825
|
}
|
|
2221
2826
|
|
|
2827
|
+
function cdxReportMissionOutput(report, run, taskReport) {
|
|
2828
|
+
const parsed = report?.parsed && typeof report.parsed === "object" ? report.parsed : {};
|
|
2829
|
+
const candidates = [
|
|
2830
|
+
report?.missionOutput,
|
|
2831
|
+
report?.mission_output,
|
|
2832
|
+
parsed.missionOutput,
|
|
2833
|
+
parsed.mission_output,
|
|
2834
|
+
run?.missionOutput,
|
|
2835
|
+
run?.mission_output,
|
|
2836
|
+
taskReport?.missionOutput,
|
|
2837
|
+
taskReport?.mission_output
|
|
2838
|
+
];
|
|
2839
|
+
return candidates.find((candidate) => candidate && typeof candidate === "object" && !Array.isArray(candidate)) || null;
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
function cdxCount(value) {
|
|
2843
|
+
if (Array.isArray(value)) {
|
|
2844
|
+
return value.length;
|
|
2845
|
+
}
|
|
2846
|
+
if (value && typeof value === "object") {
|
|
2847
|
+
return objectEntries(value).length;
|
|
2848
|
+
}
|
|
2849
|
+
return value ? 1 : 0;
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
function cdxReportCanCreateRequest(taskReport, missionOutput) {
|
|
2853
|
+
if (taskReport?.kind === "code-review") {
|
|
2854
|
+
return true;
|
|
2855
|
+
}
|
|
2856
|
+
if (cdxCount(taskReport?.findings)) {
|
|
2857
|
+
return true;
|
|
2858
|
+
}
|
|
2859
|
+
return ["findings", "recommendations", "requestFiles", "actionableFixes", "releasePlan"].some((key) => cdxCount(missionOutput?.[key]));
|
|
2860
|
+
}
|
|
2861
|
+
|
|
2862
|
+
function renderCdxReportCards(cards) {
|
|
2863
|
+
return `
|
|
2864
|
+
<div class="viewer-cdx__summary">
|
|
2865
|
+
${cards.map(([label, value]) => `
|
|
2866
|
+
<div class="viewer-cdx__card">
|
|
2867
|
+
<div class="viewer-cdx__label">${escapeHtml(label)}</div>
|
|
2868
|
+
<div class="viewer-cdx__value">${escapeHtml(value)}</div>
|
|
2869
|
+
</div>
|
|
2870
|
+
`).join("")}
|
|
2871
|
+
</div>
|
|
2872
|
+
`;
|
|
2873
|
+
}
|
|
2874
|
+
|
|
2875
|
+
function renderCdxDetailValue(value) {
|
|
2876
|
+
if (Array.isArray(value)) {
|
|
2877
|
+
return `
|
|
2878
|
+
<ol class="viewer-cdx__detail-list">
|
|
2879
|
+
${value.map((item) => `
|
|
2880
|
+
<li>${typeof item === "object" && item !== null
|
|
2881
|
+
? `<pre class="viewer-cdx__detail-code">${escapeHtml(JSON.stringify(item, null, 2))}</pre>`
|
|
2882
|
+
: escapeHtml(String(item))}
|
|
2883
|
+
</li>
|
|
2884
|
+
`).join("")}
|
|
2885
|
+
</ol>
|
|
2886
|
+
`;
|
|
2887
|
+
}
|
|
2888
|
+
if (value && typeof value === "object") {
|
|
2889
|
+
return `<pre class="viewer-cdx__detail-code">${escapeHtml(JSON.stringify(value, null, 2))}</pre>`;
|
|
2890
|
+
}
|
|
2891
|
+
return `<strong>${escapeHtml(String(value))}</strong>`;
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
function renderCdxDetailRow(label, value) {
|
|
2895
|
+
return `
|
|
2896
|
+
<li class="viewer-cdx__row viewer-cdx__row--block">
|
|
2897
|
+
<span>${escapeHtml(label)}</span>
|
|
2898
|
+
<div class="viewer-cdx__detail-value">${renderCdxDetailValue(value)}</div>
|
|
2899
|
+
</li>
|
|
2900
|
+
`;
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
function parseCdxLogJson(content) {
|
|
2904
|
+
const raw = String(content || "").trim();
|
|
2905
|
+
if (!raw) {
|
|
2906
|
+
return null;
|
|
2907
|
+
}
|
|
2908
|
+
try {
|
|
2909
|
+
return { kind: "json", value: JSON.parse(raw) };
|
|
2910
|
+
} catch {
|
|
2911
|
+
// Fall through to JSONL detection.
|
|
2912
|
+
}
|
|
2913
|
+
const lines = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
2914
|
+
if (lines.length < 2) {
|
|
2915
|
+
return null;
|
|
2916
|
+
}
|
|
2917
|
+
const values = [];
|
|
2918
|
+
for (const line of lines) {
|
|
2919
|
+
try {
|
|
2920
|
+
values.push(JSON.parse(line));
|
|
2921
|
+
} catch {
|
|
2922
|
+
return null;
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
return { kind: "jsonl", value: values };
|
|
2926
|
+
}
|
|
2927
|
+
|
|
2928
|
+
function renderCdxStructuredLog(parsed) {
|
|
2929
|
+
if (!parsed) {
|
|
2930
|
+
return "";
|
|
2931
|
+
}
|
|
2932
|
+
const label = parsed.kind === "jsonl" ? `${parsed.value.length} JSONL event(s)` : "JSON document";
|
|
2933
|
+
return `
|
|
2934
|
+
<details class="viewer-cdx__log-structured" open>
|
|
2935
|
+
<summary>Structured preview · ${escapeHtml(label)}</summary>
|
|
2936
|
+
<div class="viewer-cdx__detail-value">${renderCdxDetailValue(parsed.value)}</div>
|
|
2937
|
+
</details>
|
|
2938
|
+
`;
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
function renderCdxLogPreview(payload) {
|
|
2942
|
+
const path = payload?.path || "";
|
|
2943
|
+
const content = payload?.content || "";
|
|
2944
|
+
const truncated = Boolean(payload?.truncated);
|
|
2945
|
+
const parsed = parseCdxLogJson(content);
|
|
2946
|
+
return `
|
|
2947
|
+
<div class="viewer-cdx">
|
|
2948
|
+
<section class="viewer-cdx__section">
|
|
2949
|
+
<div class="viewer-ci__heading"><h2>Log preview</h2><span>${truncated ? "latest output" : "complete file"}</span></div>
|
|
2950
|
+
<div class="viewer-cdx__log-preview">
|
|
2951
|
+
<div class="viewer-cdx__meta">${escapeHtml(path)}</div>
|
|
2952
|
+
${truncated ? '<div class="viewer-cdx__state viewer-cdx__state--warn">Preview truncated to the end of the file. Open the file externally for the full log.</div>' : ""}
|
|
2953
|
+
${renderCdxStructuredLog(parsed)}
|
|
2954
|
+
<details class="viewer-cdx__log-raw"${parsed ? "" : " open"}>
|
|
2955
|
+
<summary>Raw log</summary>
|
|
2956
|
+
<pre class="viewer-cdx__log-content">${escapeHtml(content || "Log is empty.")}</pre>
|
|
2957
|
+
</details>
|
|
2958
|
+
</div>
|
|
2959
|
+
</section>
|
|
2960
|
+
</div>
|
|
2961
|
+
`;
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
function renderCdxMissionOutput(output) {
|
|
2965
|
+
if (!output) {
|
|
2966
|
+
return "";
|
|
2967
|
+
}
|
|
2968
|
+
const rows = [
|
|
2969
|
+
["Summary", output.summary],
|
|
2970
|
+
["Version", output.version],
|
|
2971
|
+
["Validation", output.validationMode],
|
|
2972
|
+
["Blocked", typeof output.blocked === "boolean" ? (output.blocked ? "Yes" : "No") : ""],
|
|
2973
|
+
["Actions", cdxCount(output.actions)],
|
|
2974
|
+
["Findings", cdxCount(output.findings)],
|
|
2975
|
+
["Recommendations", cdxCount(output.recommendations)],
|
|
2976
|
+
["Changed files", cdxCount(output.changedFiles)],
|
|
2977
|
+
["Corpus files", cdxCount(output.corpusFiles)],
|
|
2978
|
+
["Generated files", cdxCount(output.generatedFiles)],
|
|
2979
|
+
["Validation evidence", cdxCount(output.validationEvidence)]
|
|
2980
|
+
].filter(([_label, value]) => value !== undefined && value !== null && value !== "" && value !== 0);
|
|
2981
|
+
const detailKeys = [
|
|
2982
|
+
"actions",
|
|
2983
|
+
"findings",
|
|
2984
|
+
"recommendations",
|
|
2985
|
+
"directFixes",
|
|
2986
|
+
"requestFiles",
|
|
2987
|
+
"actionableFixes",
|
|
2988
|
+
"changedFiles",
|
|
2989
|
+
"corpusFiles",
|
|
2990
|
+
"generatedFiles",
|
|
2991
|
+
"validationEvidence",
|
|
2992
|
+
"releasePlan"
|
|
2993
|
+
];
|
|
2994
|
+
const details = detailKeys
|
|
2995
|
+
.filter((key) => cdxCount(output[key]))
|
|
2996
|
+
.map((key) => renderCdxDetailRow(cdxLabel(key), output[key]))
|
|
2997
|
+
.join("");
|
|
2998
|
+
return `
|
|
2999
|
+
<section class="viewer-cdx__section">
|
|
3000
|
+
<div class="viewer-ci__heading"><h2>Mission output</h2><span>${escapeHtml(rows.length)} signals</span></div>
|
|
3001
|
+
<ul class="viewer-cdx__list">
|
|
3002
|
+
${rows.map(([label, value]) => renderCdxDetailRow(label, value)).join("") || '<li class="viewer-cdx__empty">No structured mission output was reported.</li>'}
|
|
3003
|
+
</ul>
|
|
3004
|
+
${details ? `<ul class="viewer-cdx__list">${details}</ul>` : ""}
|
|
3005
|
+
</section>
|
|
3006
|
+
`;
|
|
3007
|
+
}
|
|
3008
|
+
|
|
2222
3009
|
function renderCdxReport(payload) {
|
|
2223
3010
|
if (!payload || payload.state !== "ok" || !payload.report) {
|
|
2224
3011
|
return `
|
|
@@ -2231,24 +3018,49 @@
|
|
|
2231
3018
|
const report = payload.report || {};
|
|
2232
3019
|
const run = report.run || {};
|
|
2233
3020
|
const taskReport = report.task_report || {};
|
|
3021
|
+
const runError = report.error || run.error || {};
|
|
3022
|
+
const artifacts = report.artifacts || run.artifacts || {};
|
|
2234
3023
|
const findings = Array.isArray(taskReport.findings) ? taskReport.findings : [];
|
|
3024
|
+
const missionOutput = cdxReportMissionOutput(report, run, taskReport);
|
|
2235
3025
|
const findingRows = findings.map((finding, index) => {
|
|
2236
3026
|
const location = [finding.path || finding.file || "", finding.line || ""].filter(Boolean).join(":") || "-";
|
|
2237
3027
|
return `<li class="viewer-cdx__entity"><div class="viewer-cdx__entity-main"><div><strong>${escapeHtml(finding.message || finding.title || `Finding ${index + 1}`)}</strong><div class="viewer-cdx__meta">${escapeHtml(location)}</div></div>${renderCdxBadge(finding.severity || "unknown")}</div></li>`;
|
|
2238
3028
|
}).join("");
|
|
2239
|
-
const canCreate = taskReport
|
|
3029
|
+
const canCreate = cdxReportCanCreateRequest(taskReport, missionOutput);
|
|
2240
3030
|
return `
|
|
2241
3031
|
<div class="viewer-cdx">
|
|
2242
3032
|
${renderCdxModeSwitcher("runs")}
|
|
2243
3033
|
<section class="viewer-cdx__section">
|
|
2244
|
-
<div class="viewer-ci__heading
|
|
3034
|
+
<div class="viewer-ci__heading viewer-ci__heading--actions">
|
|
3035
|
+
<div><h2>Run report</h2><span>${escapeHtml(run.status || "unknown")}</span></div>
|
|
3036
|
+
<button class="viewer-cdx__mode" type="button" data-viewer-cdx-back-runs>Back to runs</button>
|
|
3037
|
+
</div>
|
|
3038
|
+
${renderCdxReportCards([
|
|
3039
|
+
["Status", run.status || "unknown"],
|
|
3040
|
+
["Kind", taskReport.kind || run.kind || "assistant"],
|
|
3041
|
+
["Findings", String(findings.length)],
|
|
3042
|
+
["Artifacts", String(objectEntries(artifacts).length)]
|
|
3043
|
+
])}
|
|
2245
3044
|
<ul class="viewer-cdx__list">
|
|
2246
3045
|
<li class="viewer-cdx__row"><span>Run</span><strong>${escapeHtml(run.run_id || taskReport.run_id || "-")}</strong></li>
|
|
2247
3046
|
<li class="viewer-cdx__row"><span>Kind</span><strong>${escapeHtml(taskReport.kind || run.kind || "assistant")}</strong></li>
|
|
2248
|
-
|
|
3047
|
+
${renderCdxDetailRow("Summary", taskReport.summary || "No summary reported.")}
|
|
2249
3048
|
</ul>
|
|
2250
3049
|
${canCreate ? `<button class="btn" type="button" data-viewer-cdx-create-request="${escapeHtml(run.run_id || taskReport.run_id || "")}">Create Logics request</button>` : ""}
|
|
2251
3050
|
</section>
|
|
3051
|
+
${renderCdxMissionOutput(missionOutput)}
|
|
3052
|
+
${objectEntries(runError).length ? `
|
|
3053
|
+
<section class="viewer-cdx__section">
|
|
3054
|
+
<div class="viewer-ci__heading"><h2>Run signal</h2><span>${escapeHtml(runError.code || "reported")}</span></div>
|
|
3055
|
+
<ul class="viewer-cdx__list">${renderCdxObjectRows(runError, "No run signal reported.")}</ul>
|
|
3056
|
+
</section>
|
|
3057
|
+
` : ""}
|
|
3058
|
+
${objectEntries(artifacts).length ? `
|
|
3059
|
+
<section class="viewer-cdx__section">
|
|
3060
|
+
<div class="viewer-ci__heading"><h2>Artifacts</h2><span>${escapeHtml(objectEntries(artifacts).length)} paths</span></div>
|
|
3061
|
+
<ul class="viewer-cdx__list">${renderCdxArtifactRows(artifacts, "No artifact paths reported.")}</ul>
|
|
3062
|
+
</section>
|
|
3063
|
+
` : ""}
|
|
2252
3064
|
<section class="viewer-cdx__section">
|
|
2253
3065
|
<div class="viewer-ci__heading"><h2>Findings</h2><span>${escapeHtml(findings.length)} reported</span></div>
|
|
2254
3066
|
<ul class="viewer-cdx__list">${findingRows || '<li class="viewer-cdx__empty">No structured findings reported.</li>'}</ul>
|
|
@@ -2285,11 +3097,130 @@
|
|
|
2285
3097
|
if (!response.ok || !data.ok) {
|
|
2286
3098
|
throw new Error(data.error || "Unable to load CDX status.");
|
|
2287
3099
|
}
|
|
3100
|
+
const nextCdxSignature = runtimeStatusSignature(data.payload);
|
|
3101
|
+
if (options.skipUnchanged && !options.force && latestCdxStatusSignature && nextCdxSignature === latestCdxStatusSignature) {
|
|
3102
|
+
updateMainCdxBadge(data.payload);
|
|
3103
|
+
if (!options.silent) {
|
|
3104
|
+
setMeta(`Checked CDX status just now · no changes (${new Date().toLocaleTimeString()})`);
|
|
3105
|
+
}
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
latestCdxStatusSignature = nextCdxSignature;
|
|
2288
3109
|
updateMainCdxBadge(data.payload);
|
|
2289
3110
|
setDocument("CDX status", renderCdxStatus(data.payload));
|
|
2290
3111
|
setMeta(options.silent ? "CDX status refreshed." : "CDX status loaded.");
|
|
2291
3112
|
}
|
|
2292
3113
|
|
|
3114
|
+
async function showCdxMissions(options = {}) {
|
|
3115
|
+
if (!isCapabilityAvailable("cdx")) {
|
|
3116
|
+
const message = capabilityMessage("cdx", "CDX is not available for this project.");
|
|
3117
|
+
setDocument("CDX missions", renderCdxMissions({ state: capability("cdx").state, message }));
|
|
3118
|
+
setMeta(message);
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
if (!options.silent) {
|
|
3122
|
+
setMeta("Loading CDX missions...");
|
|
3123
|
+
}
|
|
3124
|
+
const response = await fetch("/api/cdx-status");
|
|
3125
|
+
let data = {};
|
|
3126
|
+
try {
|
|
3127
|
+
data = await response.json();
|
|
3128
|
+
} catch {
|
|
3129
|
+
data = {};
|
|
3130
|
+
}
|
|
3131
|
+
if (!response.ok || !data.ok) {
|
|
3132
|
+
throw new Error(data.error || "Unable to load CDX mission status.");
|
|
3133
|
+
}
|
|
3134
|
+
latestCdxMissionState.statusPayload = data.payload;
|
|
3135
|
+
const sessions = cdxSessions(data.payload?.status || {});
|
|
3136
|
+
if (!latestCdxMissionState.sessionId && sessions.length) {
|
|
3137
|
+
latestCdxMissionState.sessionId = cdxField(sessions[0], ["id", "name", "session_name", "value"], "");
|
|
3138
|
+
}
|
|
3139
|
+
updateMainCdxBadge(data.payload);
|
|
3140
|
+
setDocument("CDX missions", renderCdxMissions(data.payload, latestCdxMissionState.planPayload, latestCdxMissionState.runPayload, latestCdxMissionState.applyPayload));
|
|
3141
|
+
setMeta(options.silent ? "CDX missions refreshed." : "CDX missions loaded.");
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
async function previewCdxMission() {
|
|
3145
|
+
setMeta("Preparing CDX mission preview...");
|
|
3146
|
+
const response = await fetch("/api/cdx-mission-plan", {
|
|
3147
|
+
method: "POST",
|
|
3148
|
+
headers: { "Content-Type": "application/json" },
|
|
3149
|
+
body: JSON.stringify(selectedCdxMissionRequest())
|
|
3150
|
+
});
|
|
3151
|
+
const data = await response.json();
|
|
3152
|
+
if (!response.ok || !data.ok) {
|
|
3153
|
+
throw new Error(data.error || "Unable to preview CDX mission.");
|
|
3154
|
+
}
|
|
3155
|
+
latestCdxMissionState.planPayload = data.payload;
|
|
3156
|
+
latestCdxMissionState.runPayload = null;
|
|
3157
|
+
latestCdxMissionState.applyPayload = null;
|
|
3158
|
+
if (data.payload?.plan?.sessionId) {
|
|
3159
|
+
latestCdxMissionState.sessionId = data.payload.plan.sessionId;
|
|
3160
|
+
}
|
|
3161
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload || data.payload?.status, data.payload, null, null));
|
|
3162
|
+
setMeta(data.payload?.state === "ok" ? "CDX mission preview ready." : (data.payload?.message || "CDX mission preview failed."));
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
async function launchCdxMission() {
|
|
3166
|
+
setMeta("Launching CDX mission...");
|
|
3167
|
+
const request = selectedCdxMissionRequest();
|
|
3168
|
+
const plan = latestCdxMissionState.planPayload?.plan || null;
|
|
3169
|
+
const pendingPayload = {
|
|
3170
|
+
state: "running",
|
|
3171
|
+
message: "CDX mission is running. You can keep using the viewer; this panel will update when it completes.",
|
|
3172
|
+
plan,
|
|
3173
|
+
run: {
|
|
3174
|
+
runId: "pending",
|
|
3175
|
+
returnCode: "pending",
|
|
3176
|
+
pending: true,
|
|
3177
|
+
usage: { available: false, message: "Still running." },
|
|
3178
|
+
stdout: "",
|
|
3179
|
+
stderr: ""
|
|
3180
|
+
}
|
|
3181
|
+
};
|
|
3182
|
+
latestCdxMissionState.runPayload = pendingPayload;
|
|
3183
|
+
latestCdxMissionState.applyPayload = null;
|
|
3184
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload, latestCdxMissionState.planPayload, pendingPayload, null));
|
|
3185
|
+
const response = await fetch("/api/cdx-mission-run", {
|
|
3186
|
+
method: "POST",
|
|
3187
|
+
headers: { "Content-Type": "application/json" },
|
|
3188
|
+
body: JSON.stringify(request)
|
|
3189
|
+
});
|
|
3190
|
+
const data = await response.json();
|
|
3191
|
+
if (!response.ok || !data.ok) {
|
|
3192
|
+
throw new Error(data.error || "Unable to launch CDX mission.");
|
|
3193
|
+
}
|
|
3194
|
+
latestCdxMissionState.planPayload = { state: data.payload?.state === "ok" ? "ok" : data.payload?.state, message: data.payload?.message || "", plan: data.payload?.plan };
|
|
3195
|
+
latestCdxMissionState.runPayload = data.payload;
|
|
3196
|
+
latestCdxMissionState.applyPayload = null;
|
|
3197
|
+
if (isCdxMissionsOpen()) {
|
|
3198
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload, latestCdxMissionState.planPayload, data.payload, null));
|
|
3199
|
+
}
|
|
3200
|
+
setMeta(data.payload?.state === "ok" ? "CDX mission launched." : (data.payload?.message || "CDX mission failed."));
|
|
3201
|
+
}
|
|
3202
|
+
|
|
3203
|
+
async function applyCdxMissionPlan() {
|
|
3204
|
+
const actions = latestCdxMissionState.runPayload?.run?.parsed?.actions;
|
|
3205
|
+
if (!Array.isArray(actions) || !actions.length) {
|
|
3206
|
+
setMeta("No corpus actions to apply.");
|
|
3207
|
+
return;
|
|
3208
|
+
}
|
|
3209
|
+
setMeta("Applying allowed corpus actions...");
|
|
3210
|
+
const response = await fetch("/api/cdx-mission-apply-plan", {
|
|
3211
|
+
method: "POST",
|
|
3212
|
+
headers: { "Content-Type": "application/json" },
|
|
3213
|
+
body: JSON.stringify({ actions })
|
|
3214
|
+
});
|
|
3215
|
+
const data = await response.json();
|
|
3216
|
+
if (!response.ok || !data.ok) {
|
|
3217
|
+
throw new Error(data.error || "Unable to apply corpus plan.");
|
|
3218
|
+
}
|
|
3219
|
+
latestCdxMissionState.applyPayload = data.payload;
|
|
3220
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload, latestCdxMissionState.planPayload, latestCdxMissionState.runPayload, data.payload));
|
|
3221
|
+
setMeta(data.payload?.state === "ok" ? "Corpus actions applied." : (data.payload?.message || "Corpus apply failed."));
|
|
3222
|
+
}
|
|
3223
|
+
|
|
2293
3224
|
async function showCdxRuns(options = {}) {
|
|
2294
3225
|
if (!isCapabilityAvailable("cdx")) {
|
|
2295
3226
|
const message = capabilityMessage("cdx", "CDX is not available for this project.");
|
|
@@ -2325,9 +3256,30 @@
|
|
|
2325
3256
|
throw new Error(data.error || "Unable to load CDX report.");
|
|
2326
3257
|
}
|
|
2327
3258
|
setDocument("CDX run report", renderCdxReport(data.payload));
|
|
3259
|
+
cdxCloseTarget = { type: "cdx-runs" };
|
|
2328
3260
|
setMeta("CDX report loaded.");
|
|
2329
3261
|
}
|
|
2330
3262
|
|
|
3263
|
+
async function openCdxArtifact(path) {
|
|
3264
|
+
if (!path) {
|
|
3265
|
+
return;
|
|
3266
|
+
}
|
|
3267
|
+
setMeta("Loading CDX log...");
|
|
3268
|
+
const response = await fetch("/api/file-preview", {
|
|
3269
|
+
method: "POST",
|
|
3270
|
+
headers: { "Content-Type": "application/json" },
|
|
3271
|
+
body: JSON.stringify({ path })
|
|
3272
|
+
});
|
|
3273
|
+
const data = await response.json();
|
|
3274
|
+
if (!response.ok || !data.ok) {
|
|
3275
|
+
throw new Error(data.error || "Unable to load CDX artifact.");
|
|
3276
|
+
}
|
|
3277
|
+
const reportSnapshot = currentDocumentSnapshot("CDX run report");
|
|
3278
|
+
setDocument(data.payload?.name ? `CDX log · ${data.payload.name}` : "CDX log", renderCdxLogPreview(data.payload));
|
|
3279
|
+
cdxCloseTarget = { type: "cdx-report", title: reportSnapshot.title, html: reportSnapshot.html };
|
|
3280
|
+
setMeta(`Loaded ${data.payload?.path || path}.`);
|
|
3281
|
+
}
|
|
3282
|
+
|
|
2331
3283
|
async function createRequestFromCdxReport(runId) {
|
|
2332
3284
|
if (!runId) {
|
|
2333
3285
|
return;
|
|
@@ -2370,11 +3322,26 @@
|
|
|
2370
3322
|
const run = payload.run && typeof payload.run === "object" ? payload.run : null;
|
|
2371
3323
|
const jobs = Array.isArray(payload.jobs) ? payload.jobs : [];
|
|
2372
3324
|
const state = payload.badgeState || run?.badgeState || payload.state || "unknown";
|
|
3325
|
+
const matchLabel = run?.matchSource === "head-active"
|
|
3326
|
+
? "Current HEAD running"
|
|
3327
|
+
: run?.matchSource === "head-failing"
|
|
3328
|
+
? "Current HEAD failing"
|
|
3329
|
+
: run?.matchSource === "head-cancelled"
|
|
3330
|
+
? "Current HEAD cancelled"
|
|
3331
|
+
: run?.matchSource === "head-unknown"
|
|
3332
|
+
? "Current HEAD unknown"
|
|
3333
|
+
: run?.matchSource === "head"
|
|
3334
|
+
? "Current HEAD"
|
|
3335
|
+
: run?.matchSource === "branch-active"
|
|
3336
|
+
? "Branch running"
|
|
3337
|
+
: run?.matchSource === "branch-failing"
|
|
3338
|
+
? "Branch failing"
|
|
3339
|
+
: "Latest branch run";
|
|
2373
3340
|
const cards = renderMetricCards([
|
|
2374
3341
|
["State", ciBadgeLabel(state)],
|
|
2375
3342
|
["Branch", run?.branch || payload.branch || "Unknown"],
|
|
2376
3343
|
["Commit", (run?.headSha || payload.headSha || "").slice(0, 7) || "Unknown"],
|
|
2377
|
-
["Match",
|
|
3344
|
+
["Match", matchLabel]
|
|
2378
3345
|
]);
|
|
2379
3346
|
const runUrl = run?.htmlUrl ? `<a class="viewer-ci__link" href="${escapeHtml(run.htmlUrl)}" target="_blank" rel="noreferrer">Open in GitHub</a>` : "";
|
|
2380
3347
|
const runRows = run ? [
|
|
@@ -2444,6 +3411,15 @@
|
|
|
2444
3411
|
if (!response.ok || !data.ok) {
|
|
2445
3412
|
throw new Error(data.error || "Unable to load CI status.");
|
|
2446
3413
|
}
|
|
3414
|
+
const nextCiSignature = runtimeStatusSignature(data.payload);
|
|
3415
|
+
if (options.skipUnchanged && !options.force && latestCiStatusSignature && nextCiSignature === latestCiStatusSignature) {
|
|
3416
|
+
updateMainCiBadge(data.payload);
|
|
3417
|
+
if (!options.silent) {
|
|
3418
|
+
setMeta(`Checked CI status just now · no changes (${new Date().toLocaleTimeString()})`);
|
|
3419
|
+
}
|
|
3420
|
+
return;
|
|
3421
|
+
}
|
|
3422
|
+
latestCiStatusSignature = nextCiSignature;
|
|
2447
3423
|
updateMainCiBadge(data.payload);
|
|
2448
3424
|
setDocument("CI status", renderCiStatus(data.payload));
|
|
2449
3425
|
setMeta(options.silent ? "CI status refreshed." : "CI status loaded.");
|
|
@@ -2463,17 +3439,20 @@
|
|
|
2463
3439
|
const deletedCount = Number(counts.deleted || 0);
|
|
2464
3440
|
const renamedCount = Number(counts.renamed || 0);
|
|
2465
3441
|
const untrackedCount = Number(counts.untracked || 0);
|
|
2466
|
-
const
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
3442
|
+
const cards = [
|
|
3443
|
+
renderGitSummaryCard("Branch", payload.branch || "HEAD"),
|
|
3444
|
+
renderGitSummaryCard("Tracking", payload.tracking || "None"),
|
|
3445
|
+
renderGitSummarySegments("Ahead / Behind", [
|
|
3446
|
+
["Ahead", payload.ahead || 0],
|
|
3447
|
+
["Behind", payload.behind || 0]
|
|
3448
|
+
]),
|
|
3449
|
+
renderGitSummaryCard("State", payload.clean ? "Clean" : "Dirty"),
|
|
3450
|
+
renderGitSummarySegments("Files", [
|
|
3451
|
+
["Staged", stagedCount],
|
|
3452
|
+
["Worktree", modifiedCount + deletedCount + renamedCount],
|
|
3453
|
+
["Untracked", untrackedCount]
|
|
3454
|
+
])
|
|
3455
|
+
].join("");
|
|
2477
3456
|
const groupDefs = [
|
|
2478
3457
|
["staged", "Staged", "staged"],
|
|
2479
3458
|
["modified", "Modified", "worktree"],
|
|
@@ -2486,7 +3465,7 @@
|
|
|
2486
3465
|
["staged", "Staged", stagedCount],
|
|
2487
3466
|
["worktree", "Worktree", modifiedCount + deletedCount + renamedCount],
|
|
2488
3467
|
["untracked", "Untracked", untrackedCount],
|
|
2489
|
-
["history", "History",
|
|
3468
|
+
["history", "History", formatGitHistoryCount(payload)],
|
|
2490
3469
|
["remote", "Remote", payload.tracking ? 1 : 0]
|
|
2491
3470
|
];
|
|
2492
3471
|
const domains = domainDefs.map(([key, label, count], index) => `
|
|
@@ -2528,6 +3507,7 @@
|
|
|
2528
3507
|
const untrackedSections = renderFileSections(["untracked"]);
|
|
2529
3508
|
const clean = payload.clean ? '<p class="viewer-git__state">Working tree clean.</p>' : "";
|
|
2530
3509
|
const recentCommits = Array.isArray(payload.recentCommits) ? payload.recentCommits : [];
|
|
3510
|
+
const historyCount = formatGitHistoryCount(payload);
|
|
2531
3511
|
const renderGitHistoryReveal = (hiddenCount) => {
|
|
2532
3512
|
if (hiddenCount <= 0) {
|
|
2533
3513
|
return "";
|
|
@@ -2565,7 +3545,7 @@
|
|
|
2565
3545
|
return `
|
|
2566
3546
|
<div class="viewer-git">
|
|
2567
3547
|
<div class="viewer-git__summary">${cards}</div>
|
|
2568
|
-
<div class="viewer-git__workspace">
|
|
3548
|
+
<div class="viewer-git__workspace has-diff-detail">
|
|
2569
3549
|
<nav class="viewer-git__domains" aria-label="Git domains">${domains}</nav>
|
|
2570
3550
|
<div class="viewer-git__content" aria-label="Git domain content">
|
|
2571
3551
|
<section class="viewer-git__panel" data-viewer-git-panel="changes">
|
|
@@ -2586,7 +3566,7 @@
|
|
|
2586
3566
|
${untrackedSections || '<p class="viewer-git__state">No untracked files.</p>'}
|
|
2587
3567
|
</section>
|
|
2588
3568
|
<section class="viewer-git__panel" data-viewer-git-panel="history" hidden>
|
|
2589
|
-
<header class="viewer-git__panel-header"><span>History</span><strong>${escapeHtml(
|
|
3569
|
+
<header class="viewer-git__panel-header"><span>History</span><strong>${escapeHtml(historyCount)} commits</strong></header>
|
|
2590
3570
|
${history}
|
|
2591
3571
|
</section>
|
|
2592
3572
|
<section class="viewer-git__panel" data-viewer-git-panel="remote" hidden>
|
|
@@ -2594,7 +3574,7 @@
|
|
|
2594
3574
|
${remote}
|
|
2595
3575
|
</section>
|
|
2596
3576
|
</div>
|
|
2597
|
-
<section class="viewer-git__detail" aria-label="Git diff">
|
|
3577
|
+
<section class="viewer-git__detail" aria-label="Git diff" data-viewer-git-detail>
|
|
2598
3578
|
<div class="viewer-git__detail-title">Diff preview</div>
|
|
2599
3579
|
<div class="viewer-git__diff" data-viewer-git-diff>Select a changed file to preview its diff.</div>
|
|
2600
3580
|
</section>
|
|
@@ -2603,6 +3583,11 @@
|
|
|
2603
3583
|
`;
|
|
2604
3584
|
}
|
|
2605
3585
|
|
|
3586
|
+
function formatGitHistoryCount(payload) {
|
|
3587
|
+
const count = Array.isArray(payload?.recentCommits) ? payload.recentCommits.length : (payload?.latestCommit ? 1 : 0);
|
|
3588
|
+
return `${count}${payload?.recentCommitsHasMore ? "+" : ""}`;
|
|
3589
|
+
}
|
|
3590
|
+
|
|
2606
3591
|
function setActiveGitFile(button) {
|
|
2607
3592
|
document.querySelectorAll("[data-viewer-git-file]").forEach((node) => {
|
|
2608
3593
|
if (node instanceof HTMLElement) {
|
|
@@ -2636,12 +3621,16 @@
|
|
|
2636
3621
|
|
|
2637
3622
|
async function loadGitDiff(path, cached, button = null) {
|
|
2638
3623
|
const diffPanel = document.querySelector("[data-viewer-git-diff]");
|
|
3624
|
+
const detailTitle = document.querySelector("[data-viewer-git-detail] .viewer-git__detail-title");
|
|
2639
3625
|
if (!(diffPanel instanceof HTMLElement) || !path) {
|
|
2640
3626
|
return;
|
|
2641
3627
|
}
|
|
2642
3628
|
if (button instanceof HTMLElement) {
|
|
2643
3629
|
setActiveGitFile(button);
|
|
2644
3630
|
}
|
|
3631
|
+
if (detailTitle instanceof HTMLElement) {
|
|
3632
|
+
detailTitle.textContent = "Diff preview";
|
|
3633
|
+
}
|
|
2645
3634
|
diffPanel.textContent = "Loading diff...";
|
|
2646
3635
|
const params = new URLSearchParams({ path });
|
|
2647
3636
|
if (cached) {
|
|
@@ -2654,12 +3643,38 @@
|
|
|
2654
3643
|
diffPanel.textContent = payload.message || data.error || "Unable to load diff.";
|
|
2655
3644
|
return;
|
|
2656
3645
|
}
|
|
2657
|
-
const content = payload.diff ||
|
|
3646
|
+
const content = payload.diff || "";
|
|
3647
|
+
if (!content.trim()) {
|
|
3648
|
+
await loadGitFilePreview(path, diffPanel, detailTitle);
|
|
3649
|
+
return;
|
|
3650
|
+
}
|
|
2658
3651
|
diffPanel.innerHTML = `<div class="viewer-git__diff-meta">${escapeHtml(payload.path || path)} · ${escapeHtml(payload.mode || "worktree")}${payload.truncated ? " · truncated" : ""}</div><pre><code>${renderGitDiffPreview(content)}</code></pre>`;
|
|
2659
3652
|
}
|
|
2660
3653
|
|
|
3654
|
+
async function loadGitFilePreview(path, diffPanel, detailTitle = null) {
|
|
3655
|
+
if (detailTitle instanceof HTMLElement) {
|
|
3656
|
+
detailTitle.textContent = "File preview";
|
|
3657
|
+
}
|
|
3658
|
+
diffPanel.textContent = "Loading file preview...";
|
|
3659
|
+
const response = await fetch(`/api/git-file-preview?${new URLSearchParams({ path }).toString()}`);
|
|
3660
|
+
const data = await response.json();
|
|
3661
|
+
const payload = data.payload || {};
|
|
3662
|
+
if (!response.ok || !data.ok) {
|
|
3663
|
+
diffPanel.textContent = data.error || "Unable to load file preview.";
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
if (payload.state !== "ok") {
|
|
3667
|
+
diffPanel.innerHTML = `<div class="viewer-git__diff-meta">${escapeHtml(payload.path || path)} · file preview unavailable</div><p class="viewer-git__state">${escapeHtml(payload.message || "File preview is unavailable.")}</p>`;
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3670
|
+
const content = payload.content || "";
|
|
3671
|
+
diffPanel.innerHTML = `<div class="viewer-git__diff-meta">${escapeHtml(payload.path || path)} · file preview${payload.truncated ? " · truncated" : ""}</div><pre><code>${renderGitDiffPreview(content)}</code></pre>`;
|
|
3672
|
+
}
|
|
3673
|
+
|
|
2661
3674
|
function applyGitDomain(domain) {
|
|
2662
3675
|
const selected = domain || "changes";
|
|
3676
|
+
const diffDomains = new Set(["changes", "staged", "worktree", "untracked"]);
|
|
3677
|
+
const showDiffDetail = diffDomains.has(selected);
|
|
2663
3678
|
document.querySelectorAll(".viewer-git__domain[data-viewer-git-domain]").forEach((node) => {
|
|
2664
3679
|
if (node instanceof HTMLElement) {
|
|
2665
3680
|
const active = node.getAttribute("data-viewer-git-domain") === selected;
|
|
@@ -2672,6 +3687,16 @@
|
|
|
2672
3687
|
node.hidden = node.getAttribute("data-viewer-git-panel") !== selected;
|
|
2673
3688
|
}
|
|
2674
3689
|
});
|
|
3690
|
+
document.querySelectorAll(".viewer-git__workspace").forEach((node) => {
|
|
3691
|
+
if (node instanceof HTMLElement) {
|
|
3692
|
+
node.classList.toggle("has-diff-detail", showDiffDetail);
|
|
3693
|
+
}
|
|
3694
|
+
});
|
|
3695
|
+
document.querySelectorAll("[data-viewer-git-detail]").forEach((node) => {
|
|
3696
|
+
if (node instanceof HTMLElement) {
|
|
3697
|
+
node.hidden = !showDiffDetail;
|
|
3698
|
+
}
|
|
3699
|
+
});
|
|
2675
3700
|
}
|
|
2676
3701
|
|
|
2677
3702
|
function currentGitViewState() {
|
|
@@ -2721,6 +3746,16 @@
|
|
|
2721
3746
|
if (!response.ok || !data.ok) {
|
|
2722
3747
|
throw new Error(data.error || "Unable to load Git status.");
|
|
2723
3748
|
}
|
|
3749
|
+
const nextGitSignature = gitStatusSignature(data.payload);
|
|
3750
|
+
if (options.skipUnchanged && !options.force && latestGitStatusSignature && nextGitSignature === latestGitStatusSignature) {
|
|
3751
|
+
setGitBadgeCountsFromPayload(data.payload, { updateMain: false });
|
|
3752
|
+
updateMainGitBadges();
|
|
3753
|
+
if (!options.silent) {
|
|
3754
|
+
setMeta(`Checked Git status just now · no changes (${new Date().toLocaleTimeString()})`);
|
|
3755
|
+
}
|
|
3756
|
+
return;
|
|
3757
|
+
}
|
|
3758
|
+
latestGitStatusSignature = nextGitSignature;
|
|
2724
3759
|
setGitBadgeCountsFromPayload(data.payload, { updateMain: false });
|
|
2725
3760
|
updateMainGitBadges();
|
|
2726
3761
|
setDocument("Git status", renderGitStatus(data.payload));
|
|
@@ -2744,7 +3779,7 @@
|
|
|
2744
3779
|
return;
|
|
2745
3780
|
}
|
|
2746
3781
|
if (message.type === "refresh") {
|
|
2747
|
-
refreshViewer("POST").catch((error) => setMeta(error.message));
|
|
3782
|
+
refreshViewer("POST", { force: Boolean(message.force) }).catch((error) => setMeta(error.message));
|
|
2748
3783
|
return;
|
|
2749
3784
|
}
|
|
2750
3785
|
if (message.type === "bootstrap-logics") {
|
|
@@ -2782,7 +3817,7 @@
|
|
|
2782
3817
|
[document.getElementById("viewer-insights")].forEach((button) => {
|
|
2783
3818
|
button?.addEventListener("click", () => {
|
|
2784
3819
|
setRefreshMenuOpen(false);
|
|
2785
|
-
|
|
3820
|
+
withPrimaryAction("insights", "Loading insights", showCorpusInsights);
|
|
2786
3821
|
});
|
|
2787
3822
|
});
|
|
2788
3823
|
const autoControl = autoRefreshControl();
|
|
@@ -2825,30 +3860,30 @@
|
|
|
2825
3860
|
if (!(element instanceof HTMLElement)) {
|
|
2826
3861
|
return;
|
|
2827
3862
|
}
|
|
2828
|
-
element.addEventListener("click", () => {
|
|
3863
|
+
element.addEventListener("click", (event) => {
|
|
2829
3864
|
setRefreshMenuOpen(false);
|
|
2830
|
-
|
|
3865
|
+
withPrimaryAction("refresh", "Refreshing", () => refreshViewer("POST", { force: Boolean(event.shiftKey) }));
|
|
2831
3866
|
});
|
|
2832
3867
|
});
|
|
2833
3868
|
document.getElementById("viewer-health")?.addEventListener("click", () => {
|
|
2834
3869
|
setRefreshMenuOpen(false);
|
|
2835
|
-
|
|
3870
|
+
withPrimaryAction("health", "Checking health", showHealth);
|
|
2836
3871
|
});
|
|
2837
3872
|
document.getElementById("viewer-git")?.addEventListener("click", () => {
|
|
2838
|
-
|
|
3873
|
+
withPrimaryAction("git", "Checking Git status", showGitStatus);
|
|
2839
3874
|
});
|
|
2840
3875
|
ciButton()?.addEventListener("click", () => {
|
|
2841
|
-
|
|
3876
|
+
withPrimaryAction("ci", "Checking CI status", showCiStatus);
|
|
2842
3877
|
});
|
|
2843
3878
|
document.getElementById("viewer-cdx")?.addEventListener("click", () => {
|
|
2844
|
-
|
|
3879
|
+
withPrimaryAction("cdx", "Checking CDX status", showCdxStatus);
|
|
2845
3880
|
});
|
|
2846
3881
|
repoPill()?.addEventListener("click", () => {
|
|
2847
3882
|
const menu = projectMenu();
|
|
2848
3883
|
setProjectMenuOpen(Boolean(menu?.hidden));
|
|
2849
3884
|
});
|
|
2850
3885
|
repoFolderButton()?.addEventListener("click", () => {
|
|
2851
|
-
|
|
3886
|
+
withPrimaryAction("open-repo-folder", "Opening repository folder", openRepositoryFolder);
|
|
2852
3887
|
});
|
|
2853
3888
|
activityClearControl()?.addEventListener("click", () => {
|
|
2854
3889
|
clearActivityHistory();
|
|
@@ -2873,9 +3908,29 @@
|
|
|
2873
3908
|
const editButton = editDocumentButton();
|
|
2874
3909
|
if (editButton instanceof HTMLElement) {
|
|
2875
3910
|
editButton.addEventListener("click", () => {
|
|
2876
|
-
|
|
3911
|
+
withPrimaryAction("edit-document", "Opening document", () => editDocument(selectedItem()));
|
|
2877
3912
|
});
|
|
2878
3913
|
}
|
|
3914
|
+
document.addEventListener("change", (event) => {
|
|
3915
|
+
const sessionTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-session]") : null;
|
|
3916
|
+
const cdxInputTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-input]") : null;
|
|
3917
|
+
if (sessionTarget instanceof HTMLSelectElement) {
|
|
3918
|
+
latestCdxMissionState.sessionId = sessionTarget.value || "";
|
|
3919
|
+
latestCdxMissionState.planPayload = null;
|
|
3920
|
+
latestCdxMissionState.runPayload = null;
|
|
3921
|
+
latestCdxMissionState.applyPayload = null;
|
|
3922
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload));
|
|
3923
|
+
}
|
|
3924
|
+
if (cdxInputTarget instanceof HTMLInputElement || cdxInputTarget instanceof HTMLTextAreaElement) {
|
|
3925
|
+
const key = cdxInputTarget.getAttribute("data-viewer-cdx-input") || "";
|
|
3926
|
+
if (key) {
|
|
3927
|
+
latestCdxMissionState.missionInputs[key] = cdxInputTarget instanceof HTMLInputElement && cdxInputTarget.type === "checkbox" ? (cdxInputTarget.checked ? "true" : "false") : (cdxInputTarget.value || "");
|
|
3928
|
+
latestCdxMissionState.planPayload = null;
|
|
3929
|
+
latestCdxMissionState.runPayload = null;
|
|
3930
|
+
latestCdxMissionState.applyPayload = null;
|
|
3931
|
+
}
|
|
3932
|
+
}
|
|
3933
|
+
});
|
|
2879
3934
|
document.addEventListener("click", (event) => {
|
|
2880
3935
|
window.setTimeout(() => applyLocalViewerChrome(), 0);
|
|
2881
3936
|
const target = event.target instanceof Element ? event.target.closest("[data-viewer-doc-path]") : null;
|
|
@@ -2888,22 +3943,68 @@
|
|
|
2888
3943
|
const projectSwitcherTarget = event.target instanceof Element ? event.target.closest("#viewer-repo-pill") : null;
|
|
2889
3944
|
const projectTarget = event.target instanceof Element ? event.target.closest("[data-viewer-project-id]") : null;
|
|
2890
3945
|
const cdxModeTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-mode]") : null;
|
|
3946
|
+
const cdxBackRunsTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-back-runs]") : null;
|
|
2891
3947
|
const cdxReportTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-report]") : null;
|
|
3948
|
+
const cdxArtifactTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-artifact-path]") : null;
|
|
2892
3949
|
const cdxCreateRequestTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-create-request]") : null;
|
|
3950
|
+
const cdxMissionTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-mission]") : null;
|
|
3951
|
+
const cdxStrengthTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-strength]") : null;
|
|
3952
|
+
const cdxPlanTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-plan]") : null;
|
|
3953
|
+
const cdxRunTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-run]") : null;
|
|
3954
|
+
const cdxApplyPlanTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-apply-plan]") : null;
|
|
3955
|
+
if (cdxMissionTarget instanceof HTMLElement) {
|
|
3956
|
+
latestCdxMissionState.missionId = cdxMissionTarget.getAttribute("data-viewer-cdx-mission") || "full-audit";
|
|
3957
|
+
latestCdxMissionState.planPayload = null;
|
|
3958
|
+
latestCdxMissionState.runPayload = null;
|
|
3959
|
+
latestCdxMissionState.applyPayload = null;
|
|
3960
|
+
latestCdxMissionState.missionInputs = {};
|
|
3961
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload));
|
|
3962
|
+
return;
|
|
3963
|
+
}
|
|
3964
|
+
if (cdxStrengthTarget instanceof HTMLElement) {
|
|
3965
|
+
latestCdxMissionState.strengthId = cdxStrengthTarget.getAttribute("data-viewer-cdx-strength") || "standard";
|
|
3966
|
+
latestCdxMissionState.planPayload = null;
|
|
3967
|
+
latestCdxMissionState.runPayload = null;
|
|
3968
|
+
latestCdxMissionState.applyPayload = null;
|
|
3969
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload));
|
|
3970
|
+
return;
|
|
3971
|
+
}
|
|
3972
|
+
if (cdxPlanTarget instanceof HTMLElement) {
|
|
3973
|
+
withCdxMissionAction("cdx-plan", "Building CDX mission plan", previewCdxMission);
|
|
3974
|
+
return;
|
|
3975
|
+
}
|
|
3976
|
+
if (cdxRunTarget instanceof HTMLElement) {
|
|
3977
|
+
withCdxMissionAction("cdx-run", "Launching CDX mission", launchCdxMission);
|
|
3978
|
+
return;
|
|
3979
|
+
}
|
|
3980
|
+
if (cdxApplyPlanTarget instanceof HTMLElement) {
|
|
3981
|
+
withCdxMissionAction("cdx-apply-plan", "Applying CDX mission plan", applyCdxMissionPlan);
|
|
3982
|
+
return;
|
|
3983
|
+
}
|
|
3984
|
+
if (cdxBackRunsTarget instanceof HTMLElement) {
|
|
3985
|
+
withPrimaryAction("cdx-runs", "Loading CDX runs", showCdxRuns);
|
|
3986
|
+
return;
|
|
3987
|
+
}
|
|
2893
3988
|
if (cdxReportTarget instanceof HTMLElement) {
|
|
2894
|
-
showCdxReport(cdxReportTarget.getAttribute("data-viewer-cdx-report") || "")
|
|
3989
|
+
withPrimaryAction("cdx-report", "Loading CDX report", () => showCdxReport(cdxReportTarget.getAttribute("data-viewer-cdx-report") || ""));
|
|
3990
|
+
return;
|
|
3991
|
+
}
|
|
3992
|
+
if (cdxArtifactTarget instanceof HTMLElement) {
|
|
3993
|
+
withPrimaryAction("cdx-artifact", "Opening CDX artifact", () => openCdxArtifact(cdxArtifactTarget.getAttribute("data-viewer-cdx-artifact-path") || ""));
|
|
2895
3994
|
return;
|
|
2896
3995
|
}
|
|
2897
3996
|
if (cdxCreateRequestTarget instanceof HTMLElement) {
|
|
2898
|
-
createRequestFromCdxReport(cdxCreateRequestTarget.getAttribute("data-viewer-cdx-create-request") || "")
|
|
3997
|
+
withPrimaryAction("cdx-create-request", "Creating Logics request", () => createRequestFromCdxReport(cdxCreateRequestTarget.getAttribute("data-viewer-cdx-create-request") || ""));
|
|
2899
3998
|
return;
|
|
2900
3999
|
}
|
|
2901
4000
|
if (cdxModeTarget instanceof HTMLElement) {
|
|
2902
4001
|
const mode = cdxModeTarget.getAttribute("data-viewer-cdx-mode") || "status";
|
|
2903
4002
|
if (mode === "runs") {
|
|
2904
|
-
|
|
4003
|
+
withPrimaryAction("cdx-runs", "Loading CDX runs", showCdxRuns);
|
|
4004
|
+
} else if (mode === "missions") {
|
|
4005
|
+
withPrimaryAction("cdx-missions", "Loading CDX missions", showCdxMissions);
|
|
2905
4006
|
} else {
|
|
2906
|
-
|
|
4007
|
+
withPrimaryAction("cdx", "Checking CDX status", showCdxStatus);
|
|
2907
4008
|
}
|
|
2908
4009
|
return;
|
|
2909
4010
|
}
|
|
@@ -2914,7 +4015,7 @@
|
|
|
2914
4015
|
}
|
|
2915
4016
|
if (projectTarget instanceof HTMLElement) {
|
|
2916
4017
|
event.preventDefault();
|
|
2917
|
-
switchViewerProject(projectTarget.getAttribute("data-viewer-project-id") || "")
|
|
4018
|
+
withPrimaryAction("switch-project", "Switching project", () => switchViewerProject(projectTarget.getAttribute("data-viewer-project-id") || ""));
|
|
2918
4019
|
return;
|
|
2919
4020
|
}
|
|
2920
4021
|
if (gitHistoryRevealTarget instanceof HTMLElement) {
|
|
@@ -2966,7 +4067,7 @@
|
|
|
2966
4067
|
return;
|
|
2967
4068
|
}
|
|
2968
4069
|
if (healthTarget instanceof HTMLElement) {
|
|
2969
|
-
|
|
4070
|
+
withPrimaryAction("health", "Checking health", showHealth);
|
|
2970
4071
|
return;
|
|
2971
4072
|
}
|
|
2972
4073
|
if (filterTarget instanceof HTMLElement) {
|
|
@@ -2976,14 +4077,11 @@
|
|
|
2976
4077
|
}
|
|
2977
4078
|
const path = target instanceof HTMLElement ? target.getAttribute("data-viewer-doc-path") : "";
|
|
2978
4079
|
if (path) {
|
|
2979
|
-
|
|
4080
|
+
withPrimaryAction("read-document", "Loading document", () => showDocumentByPath(path));
|
|
2980
4081
|
}
|
|
2981
4082
|
});
|
|
2982
4083
|
document.getElementById("viewer-document-close")?.addEventListener("click", () => {
|
|
2983
|
-
|
|
2984
|
-
if (panel) {
|
|
2985
|
-
panel.hidden = true;
|
|
2986
|
-
}
|
|
4084
|
+
withPrimaryAction("close-document", "Closing preview", closeDocumentPanel);
|
|
2987
4085
|
});
|
|
2988
4086
|
startAutoRefresh();
|
|
2989
4087
|
});
|