@grifhinz/logics-manager 2.7.0 → 2.8.1
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 +59 -10
- package/VERSION +1 -1
- package/clients/viewer/browser-host.js +1620 -134
- package/clients/viewer/index.html +2 -0
- package/clients/viewer/viewer.css +542 -1
- package/logics_manager/cli.py +22 -20
- package/logics_manager/flow.py +61 -1
- package/logics_manager/sync.py +47 -19
- package/logics_manager/viewer.py +1206 -20
- package/package.json +2 -1
- package/pyproject.toml +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
(() => {
|
|
2
2
|
const stateKey = "logics.localViewer.state";
|
|
3
|
+
const preferenceKey = "logics.localViewer.preferences.v1";
|
|
4
|
+
const preferenceVersion = 1;
|
|
3
5
|
const meta = () => document.getElementById("viewer-meta");
|
|
4
6
|
const documentPanel = () => document.getElementById("viewer-document");
|
|
5
7
|
const documentTitle = () => document.getElementById("viewer-document-title");
|
|
@@ -16,11 +18,13 @@
|
|
|
16
18
|
const projectMenu = () => document.getElementById("viewer-project-menu");
|
|
17
19
|
const repoGithubLink = () => document.getElementById("viewer-repo-github");
|
|
18
20
|
const repoFolderButton = () => document.getElementById("viewer-repo-folder");
|
|
21
|
+
const workspaceButton = () => document.getElementById("viewer-workspace");
|
|
19
22
|
const ciButton = () => document.getElementById("viewer-ci");
|
|
20
23
|
const autoRefreshControl = () => document.getElementById("viewer-auto-refresh");
|
|
21
24
|
const refreshIntervalControl = () => document.getElementById("viewer-refresh-interval");
|
|
22
25
|
const refreshMenuButton = () => document.getElementById("viewer-refresh-menu-button");
|
|
23
26
|
const refreshMenuPanel = () => document.getElementById("viewer-refresh-menu");
|
|
27
|
+
const versionLink = () => document.getElementById("viewer-version-link");
|
|
24
28
|
const activityClearControl = () => document.getElementById("activity-clear");
|
|
25
29
|
const activityStorageLimit = 80;
|
|
26
30
|
const gitHistoryPageSize = 10;
|
|
@@ -54,8 +58,44 @@
|
|
|
54
58
|
let focusApplied = false;
|
|
55
59
|
let latestGitBadgeCounts = { unpushedCommits: 0, uncommittedFiles: 0 };
|
|
56
60
|
let latestCiStatus = { visible: false, badgeState: "unknown", message: "" };
|
|
61
|
+
let latestUpdateInfo = {};
|
|
62
|
+
let latestCdxMissionState = {
|
|
63
|
+
missionId: "full-audit",
|
|
64
|
+
sessionId: "",
|
|
65
|
+
strengthId: "standard",
|
|
66
|
+
missionInputs: {},
|
|
67
|
+
catalog: null,
|
|
68
|
+
statusPayload: null,
|
|
69
|
+
planPayload: null,
|
|
70
|
+
runPayload: null,
|
|
71
|
+
applyPayload: null
|
|
72
|
+
};
|
|
57
73
|
let connectionState = "connected";
|
|
58
74
|
let lastSuccessfulSyncAt = 0;
|
|
75
|
+
let latestViewerStateSignature = "";
|
|
76
|
+
let latestGitStatusSignature = "";
|
|
77
|
+
let latestCdxStatusSignature = "";
|
|
78
|
+
let latestCdxStatusPayload = null;
|
|
79
|
+
let latestCiStatusSignature = "";
|
|
80
|
+
let primaryActionBusyKey = "";
|
|
81
|
+
let cdxMissionBusyKey = "";
|
|
82
|
+
let cdxCloseTarget = null;
|
|
83
|
+
let viewerPreferences = readViewerPreferences();
|
|
84
|
+
let autoRefreshIntervalForcedByLaunch = false;
|
|
85
|
+
const cdxStatusColumns = [
|
|
86
|
+
{ id: "session", label: "SESSION" },
|
|
87
|
+
{ id: "provider", label: "PROV." },
|
|
88
|
+
{ id: "status", label: "STATUS" },
|
|
89
|
+
{ id: "auth", label: "AUTH" },
|
|
90
|
+
{ id: "ok", label: "OK" },
|
|
91
|
+
{ id: "remaining5h", label: "5H" },
|
|
92
|
+
{ id: "remainingWeek", label: "WEEK" },
|
|
93
|
+
{ id: "block", label: "BLOCK", defaultVisible: false },
|
|
94
|
+
{ id: "credits", label: "CR", defaultVisible: false },
|
|
95
|
+
{ id: "reset5h", label: "RESET 5H" },
|
|
96
|
+
{ id: "resetWeek", label: "RESET WEEK" },
|
|
97
|
+
{ id: "updated", label: "UPDATED" }
|
|
98
|
+
];
|
|
59
99
|
|
|
60
100
|
function readStoredState() {
|
|
61
101
|
try {
|
|
@@ -65,6 +105,83 @@
|
|
|
65
105
|
}
|
|
66
106
|
}
|
|
67
107
|
|
|
108
|
+
function readViewerPreferences() {
|
|
109
|
+
try {
|
|
110
|
+
const value = JSON.parse(window.localStorage.getItem(preferenceKey) || "null");
|
|
111
|
+
if (!value || typeof value !== "object" || value.version !== preferenceVersion) {
|
|
112
|
+
return { version: preferenceVersion };
|
|
113
|
+
}
|
|
114
|
+
return { ...value, version: preferenceVersion };
|
|
115
|
+
} catch {
|
|
116
|
+
return { version: preferenceVersion };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function writeViewerPreferences(nextPreferences) {
|
|
121
|
+
viewerPreferences = { ...nextPreferences, version: preferenceVersion };
|
|
122
|
+
try {
|
|
123
|
+
window.localStorage.setItem(preferenceKey, JSON.stringify(viewerPreferences));
|
|
124
|
+
} catch {
|
|
125
|
+
// Keep the in-memory preference for this session when browser storage is unavailable.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function updateViewerPreferences(patch) {
|
|
130
|
+
writeViewerPreferences({ ...viewerPreferences, ...patch });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function preferredAutoRefreshIntervalSeconds() {
|
|
134
|
+
const seconds = Number(viewerPreferences.autoRefreshIntervalSeconds);
|
|
135
|
+
return Number.isFinite(seconds) && seconds > 0 ? normalizeAutoRefreshIntervalSeconds(seconds) : null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function cdxColumnVisibilityPreference() {
|
|
139
|
+
const stored = viewerPreferences.cdxStatusColumns;
|
|
140
|
+
const storedVisibility = stored && typeof stored === "object" ? stored.visibility : null;
|
|
141
|
+
const visibility = {};
|
|
142
|
+
cdxStatusColumns.forEach((column) => {
|
|
143
|
+
visibility[column.id] = column.defaultVisible !== false;
|
|
144
|
+
if (storedVisibility && typeof storedVisibility[column.id] === "boolean") {
|
|
145
|
+
visibility[column.id] = storedVisibility[column.id];
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
return visibility;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function persistCdxColumnVisibility(columnId, visible) {
|
|
152
|
+
const current = cdxColumnVisibilityPreference();
|
|
153
|
+
if (!cdxStatusColumns.some((column) => column.id === columnId)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
updateViewerPreferences({
|
|
157
|
+
cdxStatusColumns: {
|
|
158
|
+
visibility: { ...current, [columnId]: Boolean(visible) }
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function cdxProviderFilterPreference() {
|
|
164
|
+
const stored = viewerPreferences.cdxStatusProviders;
|
|
165
|
+
if (!stored || typeof stored !== "object" || stored.mode !== "subset") {
|
|
166
|
+
return { mode: "all", selected: [] };
|
|
167
|
+
}
|
|
168
|
+
const selected = Array.isArray(stored.selected)
|
|
169
|
+
? stored.selected.map((entry) => String(entry || "").trim()).filter(Boolean)
|
|
170
|
+
: [];
|
|
171
|
+
return selected.length ? { mode: "subset", selected: Array.from(new Set(selected)) } : { mode: "all", selected: [] };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function persistCdxProviderFilter(nextFilter) {
|
|
175
|
+
const selected = Array.isArray(nextFilter?.selected)
|
|
176
|
+
? nextFilter.selected.map((entry) => String(entry || "").trim()).filter(Boolean)
|
|
177
|
+
: [];
|
|
178
|
+
updateViewerPreferences({
|
|
179
|
+
cdxStatusProviders: selected.length
|
|
180
|
+
? { mode: "subset", selected: Array.from(new Set(selected)).sort() }
|
|
181
|
+
: { mode: "all", selected: [] }
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
68
185
|
function sanitizeViewerFilterState(value) {
|
|
69
186
|
const nextState = { ...defaultFilterState };
|
|
70
187
|
if (!value || typeof value !== "object") {
|
|
@@ -78,6 +195,182 @@
|
|
|
78
195
|
return nextState;
|
|
79
196
|
}
|
|
80
197
|
|
|
198
|
+
function stableStringify(value) {
|
|
199
|
+
if (Array.isArray(value)) {
|
|
200
|
+
return `[${value.map((entry) => stableStringify(entry)).join(",")}]`;
|
|
201
|
+
}
|
|
202
|
+
if (value && typeof value === "object") {
|
|
203
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(",")}}`;
|
|
204
|
+
}
|
|
205
|
+
return JSON.stringify(value);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function viewerStateSignature(payload) {
|
|
209
|
+
const items = Array.isArray(payload?.items) ? payload.items : [];
|
|
210
|
+
const projects = Array.isArray(payload?.projects) ? payload.projects : [];
|
|
211
|
+
return stableStringify({
|
|
212
|
+
root: payload?.root || "",
|
|
213
|
+
repository: payload?.repository || {},
|
|
214
|
+
capabilities: normalizeCapabilities(payload),
|
|
215
|
+
projects: projects.map((project) => ({
|
|
216
|
+
id: project?.id || "",
|
|
217
|
+
active: Boolean(project?.active),
|
|
218
|
+
available: project?.available !== false,
|
|
219
|
+
hasLogics: project?.hasLogics !== false,
|
|
220
|
+
root: project?.root || ""
|
|
221
|
+
})),
|
|
222
|
+
items: items.map((item) => ({
|
|
223
|
+
id: item?.id || "",
|
|
224
|
+
relPath: item?.relPath || "",
|
|
225
|
+
stage: item?.stage || "",
|
|
226
|
+
status: item?.indicators?.Status || item?.status || "",
|
|
227
|
+
updatedAt: item?.updatedAt || ""
|
|
228
|
+
}))
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function gitStatusSignature(payload) {
|
|
233
|
+
return stableStringify({
|
|
234
|
+
state: payload?.state || "",
|
|
235
|
+
branch: payload?.branch || "",
|
|
236
|
+
tracking: payload?.tracking || "",
|
|
237
|
+
ahead: Number(payload?.ahead || 0),
|
|
238
|
+
behind: Number(payload?.behind || 0),
|
|
239
|
+
clean: Boolean(payload?.clean),
|
|
240
|
+
counts: payload?.counts || {},
|
|
241
|
+
badgeCounts: payload?.badgeCounts || {},
|
|
242
|
+
latestCommit: payload?.latestCommit || "",
|
|
243
|
+
recentCommitsHasMore: Boolean(payload?.recentCommitsHasMore)
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function runtimeStatusSignature(payload) {
|
|
248
|
+
return stableStringify(payload || {});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function primaryActionControls() {
|
|
252
|
+
return Array.from(document.querySelectorAll([
|
|
253
|
+
"#viewer-insights",
|
|
254
|
+
"#viewer-health",
|
|
255
|
+
"#viewer-workspace",
|
|
256
|
+
"#viewer-git",
|
|
257
|
+
"#viewer-ci",
|
|
258
|
+
"#viewer-cdx",
|
|
259
|
+
"#viewer-repo-folder",
|
|
260
|
+
'[data-action="refresh"]',
|
|
261
|
+
'[data-viewer-action="edit-document"]',
|
|
262
|
+
"[data-viewer-project-id]",
|
|
263
|
+
"[data-viewer-cdx-mode]",
|
|
264
|
+
"[data-viewer-cdx-report]",
|
|
265
|
+
"[data-viewer-cdx-artifact-path]",
|
|
266
|
+
"[data-viewer-cdx-create-request]"
|
|
267
|
+
].join(","))).filter((node) => node instanceof HTMLElement);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function setPrimaryActionBusy(actionKey, label = "") {
|
|
271
|
+
primaryActionBusyKey = actionKey || "";
|
|
272
|
+
document.body?.classList.toggle("viewer-is-busy", Boolean(primaryActionBusyKey));
|
|
273
|
+
document.body?.toggleAttribute("data-viewer-busy", Boolean(primaryActionBusyKey));
|
|
274
|
+
if (primaryActionBusyKey) {
|
|
275
|
+
document.body?.setAttribute("data-viewer-busy-action", primaryActionBusyKey);
|
|
276
|
+
} else {
|
|
277
|
+
document.body?.removeAttribute("data-viewer-busy-action");
|
|
278
|
+
}
|
|
279
|
+
primaryActionControls().forEach((control) => {
|
|
280
|
+
if (!("disabled" in control)) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
control.disabled = Boolean(primaryActionBusyKey);
|
|
284
|
+
control.setAttribute("aria-busy", primaryActionBusyKey ? "true" : "false");
|
|
285
|
+
if (primaryActionBusyKey) {
|
|
286
|
+
control.setAttribute("data-viewer-action-busy", control.getAttribute("data-viewer-action-key") === actionKey ? "active" : "blocked");
|
|
287
|
+
} else {
|
|
288
|
+
control.removeAttribute("data-viewer-action-busy");
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
if (!primaryActionBusyKey) {
|
|
292
|
+
updateCapabilityControls();
|
|
293
|
+
applyLocalViewerChrome();
|
|
294
|
+
}
|
|
295
|
+
if (primaryActionBusyKey && label) {
|
|
296
|
+
setMeta(`${label}...`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function withPrimaryAction(actionKey, label, action) {
|
|
301
|
+
if (primaryActionBusyKey) {
|
|
302
|
+
setMeta("Another viewer action is still running.");
|
|
303
|
+
return Promise.resolve(false);
|
|
304
|
+
}
|
|
305
|
+
setPrimaryActionBusy(actionKey, label);
|
|
306
|
+
return Promise.resolve()
|
|
307
|
+
.then(action)
|
|
308
|
+
.then(() => true)
|
|
309
|
+
.catch((error) => {
|
|
310
|
+
setMeta(error.message || "Viewer action failed.");
|
|
311
|
+
return false;
|
|
312
|
+
})
|
|
313
|
+
.finally(() => {
|
|
314
|
+
setPrimaryActionBusy("", "");
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function cdxMissionActionControls() {
|
|
319
|
+
return Array.from(document.querySelectorAll([
|
|
320
|
+
"[data-viewer-cdx-plan]",
|
|
321
|
+
"[data-viewer-cdx-run]",
|
|
322
|
+
"[data-viewer-cdx-apply-plan]",
|
|
323
|
+
"[data-viewer-cdx-mission]"
|
|
324
|
+
].join(","))).filter((node) => node instanceof HTMLElement);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function setCdxMissionBusy(actionKey, label = "") {
|
|
328
|
+
cdxMissionBusyKey = actionKey || "";
|
|
329
|
+
document.body?.toggleAttribute("data-viewer-cdx-mission-busy", Boolean(cdxMissionBusyKey));
|
|
330
|
+
if (cdxMissionBusyKey) {
|
|
331
|
+
document.body?.setAttribute("data-viewer-cdx-mission-busy-action", cdxMissionBusyKey);
|
|
332
|
+
} else {
|
|
333
|
+
document.body?.removeAttribute("data-viewer-cdx-mission-busy-action");
|
|
334
|
+
}
|
|
335
|
+
cdxMissionActionControls().forEach((control) => {
|
|
336
|
+
if (!("disabled" in control)) {
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
control.disabled = Boolean(cdxMissionBusyKey);
|
|
340
|
+
control.setAttribute("aria-busy", cdxMissionBusyKey ? "true" : "false");
|
|
341
|
+
if (cdxMissionBusyKey) {
|
|
342
|
+
control.setAttribute("data-viewer-action-busy", control.getAttribute("data-viewer-action-key") === actionKey ? "active" : "blocked");
|
|
343
|
+
} else {
|
|
344
|
+
control.removeAttribute("data-viewer-action-busy");
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
if (!cdxMissionBusyKey) {
|
|
348
|
+
updateCapabilityControls();
|
|
349
|
+
applyLocalViewerChrome();
|
|
350
|
+
}
|
|
351
|
+
if (cdxMissionBusyKey && label) {
|
|
352
|
+
setMeta(`${label}...`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function withCdxMissionAction(actionKey, label, action) {
|
|
357
|
+
if (cdxMissionBusyKey) {
|
|
358
|
+
setMeta("Another CDX mission action is still running.");
|
|
359
|
+
return Promise.resolve(false);
|
|
360
|
+
}
|
|
361
|
+
setCdxMissionBusy(actionKey, label);
|
|
362
|
+
return Promise.resolve()
|
|
363
|
+
.then(action)
|
|
364
|
+
.then(() => true)
|
|
365
|
+
.catch((error) => {
|
|
366
|
+
setMeta(error.message || "CDX mission action failed.");
|
|
367
|
+
return false;
|
|
368
|
+
})
|
|
369
|
+
.finally(() => {
|
|
370
|
+
setCdxMissionBusy("", "");
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
81
374
|
function hydrateViewerFilterState() {
|
|
82
375
|
const storedState = readStoredState();
|
|
83
376
|
viewerFilterState = sanitizeViewerFilterState(storedState?.viewerFilterState);
|
|
@@ -257,6 +550,7 @@
|
|
|
257
550
|
autoRefreshIntervalMs = normalizeAutoRefreshIntervalSeconds(value) * 1000;
|
|
258
551
|
if (options.user) {
|
|
259
552
|
autoRefreshIntervalTouched = true;
|
|
553
|
+
updateViewerPreferences({ autoRefreshIntervalSeconds: Math.round(autoRefreshIntervalMs / 1000) });
|
|
260
554
|
}
|
|
261
555
|
updateRefreshIntervalControl();
|
|
262
556
|
scheduleNextAutoRefresh();
|
|
@@ -391,6 +685,7 @@
|
|
|
391
685
|
const capabilities = payload?.capabilities && typeof payload.capabilities === "object" ? payload.capabilities : {};
|
|
392
686
|
return {
|
|
393
687
|
logics: capabilities.logics || { state: "ready", available: true, message: "" },
|
|
688
|
+
workspace: capabilities.workspace || { state: "ready", available: true, message: "" },
|
|
394
689
|
git: capabilities.git || { state: "ready", available: true, message: "" },
|
|
395
690
|
ci: capabilities.ci || { state: "ready", available: true, message: "" },
|
|
396
691
|
cdx: capabilities.cdx || { state: "ready", available: true, message: "" },
|
|
@@ -429,6 +724,16 @@
|
|
|
429
724
|
}
|
|
430
725
|
|
|
431
726
|
function updateCapabilityControls() {
|
|
727
|
+
const workspace = workspaceButton();
|
|
728
|
+
if (workspace instanceof HTMLElement) {
|
|
729
|
+
workspace.hidden = !isCapabilityAvailable("workspace");
|
|
730
|
+
if (isCapabilityAvailable("workspace")) {
|
|
731
|
+
setButtonAvailable(workspace, "Show file explorer");
|
|
732
|
+
} else {
|
|
733
|
+
setButtonUnavailable(workspace, capabilityMessage("workspace", "Explorer is not available for this project."));
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
432
737
|
const gitButton = document.getElementById("viewer-git");
|
|
433
738
|
if (gitButton instanceof HTMLElement) {
|
|
434
739
|
gitButton.hidden = !isCapabilityAvailable("git");
|
|
@@ -476,6 +781,18 @@
|
|
|
476
781
|
}
|
|
477
782
|
}
|
|
478
783
|
|
|
784
|
+
function updateVersionLink(updateInfo = latestUpdateInfo) {
|
|
785
|
+
latestUpdateInfo = updateInfo && typeof updateInfo === "object" ? updateInfo : {};
|
|
786
|
+
const link = versionLink();
|
|
787
|
+
if (!(link instanceof HTMLAnchorElement)) {
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
const currentVersion = String(latestUpdateInfo.currentVersion || "").trim();
|
|
791
|
+
link.textContent = currentVersion ? `v${currentVersion.replace(/^v/i, "")}` : "v0.0.0";
|
|
792
|
+
link.href = latestRepository.githubUrl || "https://github.com/AlexAgo83/logics-manager";
|
|
793
|
+
link.title = "Open Logics Manager on GitHub";
|
|
794
|
+
}
|
|
795
|
+
|
|
479
796
|
async function openRepositoryFolder() {
|
|
480
797
|
if (!latestRepository.root) {
|
|
481
798
|
setMeta("Repository folder is unavailable.");
|
|
@@ -614,6 +931,7 @@
|
|
|
614
931
|
}
|
|
615
932
|
const data = await response.json();
|
|
616
933
|
if (response.ok && data.ok) {
|
|
934
|
+
latestCiStatusSignature = runtimeStatusSignature(data.payload);
|
|
617
935
|
updateMainCiBadge(data.payload);
|
|
618
936
|
}
|
|
619
937
|
} catch {
|
|
@@ -644,13 +962,22 @@
|
|
|
644
962
|
return cdxProviders(status).reduce((total, provider) => total + Math.max(0, Number(provider.active || 0)), 0);
|
|
645
963
|
}
|
|
646
964
|
|
|
647
|
-
function
|
|
965
|
+
function activeCdxRunCountFromPayload(payload) {
|
|
966
|
+
if (!payload || payload.state !== "ok" || !Array.isArray(payload.runs)) {
|
|
967
|
+
return 0;
|
|
968
|
+
}
|
|
969
|
+
return payload.runs.filter((run) => ["running", "starting", "pending"].includes(String(cdxField(run, ["status", "state"], "")).toLowerCase())).length;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function updateMainCdxBadge(payload, runsPayload = null) {
|
|
648
973
|
const button = document.getElementById("viewer-cdx");
|
|
649
974
|
if (!(button instanceof HTMLElement)) {
|
|
650
975
|
return;
|
|
651
976
|
}
|
|
652
977
|
button.querySelector("[data-viewer-cdx-badge]")?.remove();
|
|
653
|
-
const
|
|
978
|
+
const activeSessions = activeCdxAssistantCountFromPayload(payload);
|
|
979
|
+
const activeRuns = activeCdxRunCountFromPayload(runsPayload);
|
|
980
|
+
const activeCount = activeSessions + activeRuns;
|
|
654
981
|
if (activeCount <= 0) {
|
|
655
982
|
button.title = isCapabilityAvailable("cdx")
|
|
656
983
|
? "Show CDX status"
|
|
@@ -658,9 +985,17 @@
|
|
|
658
985
|
return;
|
|
659
986
|
}
|
|
660
987
|
const label = activeCount > 9 ? "9+" : String(activeCount);
|
|
661
|
-
const
|
|
988
|
+
const titleParts = [];
|
|
989
|
+
if (activeSessions > 0) {
|
|
990
|
+
titleParts.push(activeSessions === 1 ? "1 active session" : `${activeSessions} active sessions`);
|
|
991
|
+
}
|
|
992
|
+
if (activeRuns > 0) {
|
|
993
|
+
titleParts.push(activeRuns === 1 ? "1 running run" : `${activeRuns} running runs`);
|
|
994
|
+
}
|
|
995
|
+
const title = titleParts.join(" · ");
|
|
662
996
|
button.title = `Show CDX status · ${title}`;
|
|
663
|
-
|
|
997
|
+
const tone = activeRuns > 0 ? " viewer-cdx-button-badge--runs" : "";
|
|
998
|
+
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
999
|
}
|
|
665
1000
|
|
|
666
1001
|
async function refreshCdxBadgeCounters() {
|
|
@@ -669,14 +1004,23 @@
|
|
|
669
1004
|
return;
|
|
670
1005
|
}
|
|
671
1006
|
try {
|
|
672
|
-
const
|
|
673
|
-
|
|
1007
|
+
const [statusResponse, runsResponse] = await Promise.all([
|
|
1008
|
+
fetch("/api/cdx-status"),
|
|
1009
|
+
fetch("/api/cdx-runs").catch(() => null)
|
|
1010
|
+
]);
|
|
1011
|
+
if (statusResponse.status === 404) {
|
|
674
1012
|
updateMainCdxBadge(null);
|
|
675
1013
|
return;
|
|
676
1014
|
}
|
|
677
|
-
const data = await
|
|
678
|
-
|
|
679
|
-
|
|
1015
|
+
const data = await statusResponse.json();
|
|
1016
|
+
let runsPayload = null;
|
|
1017
|
+
if (runsResponse && runsResponse.ok) {
|
|
1018
|
+
const runsData = await runsResponse.json();
|
|
1019
|
+
runsPayload = runsData?.ok ? runsData.payload : null;
|
|
1020
|
+
}
|
|
1021
|
+
if (statusResponse.ok && data.ok) {
|
|
1022
|
+
latestCdxStatusSignature = runtimeStatusSignature({ status: data.payload, runs: runsPayload });
|
|
1023
|
+
updateMainCdxBadge(data.payload, runsPayload);
|
|
680
1024
|
}
|
|
681
1025
|
} catch {
|
|
682
1026
|
updateMainCdxBadge(null);
|
|
@@ -700,6 +1044,7 @@
|
|
|
700
1044
|
const response = await fetch("/api/git-status");
|
|
701
1045
|
const data = await response.json();
|
|
702
1046
|
if (response.ok && data.ok && data.payload?.state === "ok") {
|
|
1047
|
+
latestGitStatusSignature = gitStatusSignature(data.payload);
|
|
703
1048
|
setGitBadgeCountsFromPayload(data.payload);
|
|
704
1049
|
}
|
|
705
1050
|
} catch {
|
|
@@ -825,6 +1170,7 @@
|
|
|
825
1170
|
}
|
|
826
1171
|
|
|
827
1172
|
function setDocument(titleText, html) {
|
|
1173
|
+
cdxCloseTarget = null;
|
|
828
1174
|
const panel = documentPanel();
|
|
829
1175
|
const title = documentTitle();
|
|
830
1176
|
const content = documentContent();
|
|
@@ -843,6 +1189,35 @@
|
|
|
843
1189
|
renderMermaidDiagrams();
|
|
844
1190
|
}
|
|
845
1191
|
|
|
1192
|
+
function currentDocumentSnapshot(fallbackTitle = "Document") {
|
|
1193
|
+
const title = documentTitle();
|
|
1194
|
+
const content = documentContent();
|
|
1195
|
+
return {
|
|
1196
|
+
title: title?.textContent || fallbackTitle,
|
|
1197
|
+
html: content?.innerHTML || ""
|
|
1198
|
+
};
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
async function closeDocumentPanel() {
|
|
1202
|
+
const target = cdxCloseTarget;
|
|
1203
|
+
cdxCloseTarget = null;
|
|
1204
|
+
if (target?.type === "cdx-report") {
|
|
1205
|
+
setDocument(target.title || "CDX run report", target.html || "");
|
|
1206
|
+
cdxCloseTarget = { type: "cdx-runs" };
|
|
1207
|
+
setMeta("Returned to CDX run report.");
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
if (target?.type === "cdx-runs") {
|
|
1211
|
+
await showCdxRuns({ silent: true });
|
|
1212
|
+
setMeta("Returned to CDX runs.");
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
const panel = documentPanel();
|
|
1216
|
+
if (panel) {
|
|
1217
|
+
panel.hidden = true;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
846
1221
|
function showMermaidFallback(message) {
|
|
847
1222
|
document.querySelectorAll(".markdown-preview__mermaid-fallback").forEach((node) => {
|
|
848
1223
|
if (!(node instanceof HTMLElement)) {
|
|
@@ -949,9 +1324,24 @@
|
|
|
949
1324
|
|
|
950
1325
|
function postToApp(payload, options = {}) {
|
|
951
1326
|
markConnectionHealthy({ silent: Boolean(options.silent) });
|
|
1327
|
+
const nextSignature = viewerStateSignature(payload);
|
|
1328
|
+
if (!options.force && latestViewerStateSignature && nextSignature === latestViewerStateSignature) {
|
|
1329
|
+
if (!options.silent) {
|
|
1330
|
+
setMeta(`Checked just now · no viewer changes (${new Date().toLocaleTimeString()})`);
|
|
1331
|
+
}
|
|
1332
|
+
scheduleNextAutoRefresh();
|
|
1333
|
+
return false;
|
|
1334
|
+
}
|
|
1335
|
+
latestViewerStateSignature = nextSignature;
|
|
952
1336
|
latestItems = updateStoredActivity(Array.isArray(payload.items) ? payload.items : []);
|
|
953
1337
|
if (!autoRefreshIntervalTouched) {
|
|
954
|
-
|
|
1338
|
+
const launchSeconds = Number(payload.autoRefreshIntervalSeconds);
|
|
1339
|
+
const preferredSeconds = preferredAutoRefreshIntervalSeconds();
|
|
1340
|
+
autoRefreshIntervalForcedByLaunch = Boolean(payload.autoRefreshIntervalForced);
|
|
1341
|
+
const nextSeconds = autoRefreshIntervalForcedByLaunch || preferredSeconds === null
|
|
1342
|
+
? launchSeconds
|
|
1343
|
+
: preferredSeconds;
|
|
1344
|
+
autoRefreshIntervalMs = normalizeAutoRefreshIntervalSeconds(nextSeconds) * 1000;
|
|
955
1345
|
updateRefreshIntervalControl();
|
|
956
1346
|
}
|
|
957
1347
|
updateRepositoryIdentity(payload);
|
|
@@ -965,12 +1355,14 @@
|
|
|
965
1355
|
setMeta(`${rootName} · ${payload.items.length} docs · refreshed ${new Date().toLocaleTimeString()}`);
|
|
966
1356
|
}
|
|
967
1357
|
scheduleNextAutoRefresh();
|
|
1358
|
+
updateVersionLink(payload.updateInfo);
|
|
968
1359
|
renderUpdateNotice(payload.updateInfo);
|
|
969
1360
|
refreshCiBadgeCounters();
|
|
970
1361
|
refreshCdxBadgeCounters();
|
|
971
1362
|
updateFilterSummary();
|
|
972
1363
|
applyLocalViewerChrome();
|
|
973
1364
|
bindRefreshMenuControls();
|
|
1365
|
+
return true;
|
|
974
1366
|
}
|
|
975
1367
|
|
|
976
1368
|
function renderUpdateNotice(updateInfo) {
|
|
@@ -1007,11 +1399,11 @@
|
|
|
1007
1399
|
if (!response.ok || !data.ok) {
|
|
1008
1400
|
throw new Error(data.error || "Unable to load viewer data.");
|
|
1009
1401
|
}
|
|
1010
|
-
postToApp(data.payload, { silent: Boolean(options.silent) });
|
|
1402
|
+
const changed = postToApp(data.payload, { silent: Boolean(options.silent), force: Boolean(options.force) });
|
|
1011
1403
|
if (method !== "POST") {
|
|
1012
1404
|
await refreshGitBadgeCounters();
|
|
1013
1405
|
}
|
|
1014
|
-
return
|
|
1406
|
+
return changed;
|
|
1015
1407
|
} catch (error) {
|
|
1016
1408
|
markConnectionDisconnected(error);
|
|
1017
1409
|
throw error;
|
|
@@ -1026,6 +1418,12 @@
|
|
|
1026
1418
|
return Boolean(panel && !panel.hidden && title && title.textContent === "Git status");
|
|
1027
1419
|
}
|
|
1028
1420
|
|
|
1421
|
+
function isWorkspaceOpen() {
|
|
1422
|
+
const panel = documentPanel();
|
|
1423
|
+
const title = documentTitle();
|
|
1424
|
+
return Boolean(panel && !panel.hidden && title && title.textContent === "Explorer");
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1029
1427
|
function isCdxStatusOpen() {
|
|
1030
1428
|
const panel = documentPanel();
|
|
1031
1429
|
const title = documentTitle();
|
|
@@ -1038,6 +1436,12 @@
|
|
|
1038
1436
|
return Boolean(panel && !panel.hidden && title && title.textContent === "CDX runs");
|
|
1039
1437
|
}
|
|
1040
1438
|
|
|
1439
|
+
function isCdxMissionsOpen() {
|
|
1440
|
+
const panel = documentPanel();
|
|
1441
|
+
const title = documentTitle();
|
|
1442
|
+
return Boolean(panel && !panel.hidden && title && title.textContent === "CDX missions");
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1041
1445
|
function isCiStatusOpen() {
|
|
1042
1446
|
const panel = documentPanel();
|
|
1043
1447
|
const title = documentTitle();
|
|
@@ -1045,18 +1449,27 @@
|
|
|
1045
1449
|
}
|
|
1046
1450
|
|
|
1047
1451
|
async function refreshViewer(method = "POST", options = {}) {
|
|
1048
|
-
await loadItems(method, options);
|
|
1049
|
-
if (
|
|
1050
|
-
|
|
1452
|
+
const changed = await loadItems(method, options);
|
|
1453
|
+
if (isWorkspaceOpen()) {
|
|
1454
|
+
if (changed || options.force) {
|
|
1455
|
+
await showWorkspace({ silent: Boolean(options.silent) });
|
|
1456
|
+
}
|
|
1457
|
+
} else if (isGitStatusOpen()) {
|
|
1458
|
+
await showGitStatus({ preserve: true, silent: Boolean(options.silent), skipUnchanged: !changed && !options.force, force: Boolean(options.force) });
|
|
1051
1459
|
} else if (isCiStatusOpen()) {
|
|
1052
|
-
await showCiStatus({ silent: Boolean(options.silent) });
|
|
1460
|
+
await showCiStatus({ silent: Boolean(options.silent), skipUnchanged: !changed && !options.force, force: Boolean(options.force) });
|
|
1053
1461
|
} else if (isCdxStatusOpen()) {
|
|
1054
|
-
await showCdxStatus({ silent: Boolean(options.silent) });
|
|
1462
|
+
await showCdxStatus({ silent: Boolean(options.silent), skipUnchanged: !changed && !options.force, force: Boolean(options.force) });
|
|
1055
1463
|
} else if (isCdxRunsOpen()) {
|
|
1056
|
-
|
|
1464
|
+
if (changed || options.force) {
|
|
1465
|
+
await showCdxRuns({ silent: Boolean(options.silent) });
|
|
1466
|
+
}
|
|
1057
1467
|
} else if (method === "POST") {
|
|
1058
1468
|
await refreshGitBadgeCounters();
|
|
1059
1469
|
}
|
|
1470
|
+
if (!changed && !options.silent && !options.force) {
|
|
1471
|
+
setMeta(`Checked just now · no viewer changes (${new Date().toLocaleTimeString()})`);
|
|
1472
|
+
}
|
|
1060
1473
|
}
|
|
1061
1474
|
|
|
1062
1475
|
function autoRefreshItems() {
|
|
@@ -1347,6 +1760,31 @@
|
|
|
1347
1760
|
`).join("");
|
|
1348
1761
|
}
|
|
1349
1762
|
|
|
1763
|
+
function renderGitSummaryCard(label, value) {
|
|
1764
|
+
return `
|
|
1765
|
+
<div class="viewer-insights__card">
|
|
1766
|
+
<div class="viewer-insights__label">${escapeHtml(label)}</div>
|
|
1767
|
+
<div class="viewer-insights__value">${escapeHtml(value)}</div>
|
|
1768
|
+
</div>
|
|
1769
|
+
`;
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
function renderGitSummarySegments(label, segments) {
|
|
1773
|
+
return `
|
|
1774
|
+
<div class="viewer-insights__card viewer-git__summary-card">
|
|
1775
|
+
<div class="viewer-insights__label">${escapeHtml(label)}</div>
|
|
1776
|
+
<div class="viewer-git__summary-segments">
|
|
1777
|
+
${segments.map(([segmentLabel, value]) => `
|
|
1778
|
+
<span class="viewer-git__summary-segment">
|
|
1779
|
+
<span>${escapeHtml(segmentLabel)}</span>
|
|
1780
|
+
<strong>${escapeHtml(value)}</strong>
|
|
1781
|
+
</span>
|
|
1782
|
+
`).join("")}
|
|
1783
|
+
</div>
|
|
1784
|
+
</div>
|
|
1785
|
+
`;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1350
1788
|
function renderInsightBars(entries, total) {
|
|
1351
1789
|
const denominator = Math.max(1, Number(total) || 0);
|
|
1352
1790
|
if (!entries.length) {
|
|
@@ -1733,6 +2171,145 @@
|
|
|
1733
2171
|
setMeta("Health loaded.");
|
|
1734
2172
|
}
|
|
1735
2173
|
|
|
2174
|
+
function workspaceParentPath(path) {
|
|
2175
|
+
const parts = String(path || "").split("/").filter(Boolean);
|
|
2176
|
+
parts.pop();
|
|
2177
|
+
return parts.join("/");
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
function renderWorkspaceTree(treePayload, selectedPath = "") {
|
|
2181
|
+
if (!treePayload || treePayload.state !== "ok") {
|
|
2182
|
+
return `<div class="viewer-workspace__empty">${escapeHtml(treePayload?.message || "Workspace tree is unavailable.")}</div>`;
|
|
2183
|
+
}
|
|
2184
|
+
const currentPath = String(treePayload.path || "");
|
|
2185
|
+
const parentPath = workspaceParentPath(currentPath);
|
|
2186
|
+
const upButton = currentPath
|
|
2187
|
+
? `<button class="viewer-workspace__item viewer-workspace__item--up" type="button" data-viewer-workspace-tree="${escapeHtml(parentPath)}">..</button>`
|
|
2188
|
+
: "";
|
|
2189
|
+
const rows = (Array.isArray(treePayload.entries) ? treePayload.entries : []).map((entry) => {
|
|
2190
|
+
const path = String(entry.path || "");
|
|
2191
|
+
const kind = String(entry.kind || "file");
|
|
2192
|
+
const ignored = Boolean(entry.ignored);
|
|
2193
|
+
const selected = path === selectedPath;
|
|
2194
|
+
const actionAttr = kind === "directory" && !ignored
|
|
2195
|
+
? `data-viewer-workspace-tree="${escapeHtml(path)}"`
|
|
2196
|
+
: `data-viewer-workspace-preview="${escapeHtml(path)}"`;
|
|
2197
|
+
const icon = kind === "directory" ? (ignored ? "x" : ">") : "-";
|
|
2198
|
+
return `
|
|
2199
|
+
<button class="viewer-workspace__item${selected ? " is-selected" : ""}${ignored ? " is-muted" : ""}" type="button" ${actionAttr} title="${escapeHtml(path)}">
|
|
2200
|
+
<span class="viewer-workspace__item-icon" aria-hidden="true">${escapeHtml(icon)}</span>
|
|
2201
|
+
<span class="viewer-workspace__item-name">${escapeHtml(entry.name || path || "/")}</span>
|
|
2202
|
+
</button>
|
|
2203
|
+
`;
|
|
2204
|
+
}).join("");
|
|
2205
|
+
return `
|
|
2206
|
+
<div class="viewer-workspace__tree-header">
|
|
2207
|
+
<span>${escapeHtml(currentPath || "/")}</span>
|
|
2208
|
+
</div>
|
|
2209
|
+
<div class="viewer-workspace__tree-list">
|
|
2210
|
+
${upButton}
|
|
2211
|
+
${rows || '<div class="viewer-workspace__empty">Directory is empty.</div>'}
|
|
2212
|
+
</div>
|
|
2213
|
+
${treePayload.truncated ? '<div class="viewer-workspace__empty">Directory listing truncated.</div>' : ""}
|
|
2214
|
+
`;
|
|
2215
|
+
}
|
|
2216
|
+
|
|
2217
|
+
function renderWorkspacePreview(previewPayload) {
|
|
2218
|
+
if (!previewPayload) {
|
|
2219
|
+
return '<div class="viewer-workspace__empty">Select a file or directory.</div>';
|
|
2220
|
+
}
|
|
2221
|
+
const path = previewPayload.path || "/";
|
|
2222
|
+
const name = previewPayload.name || path || "/";
|
|
2223
|
+
const state = previewPayload.state || "unknown";
|
|
2224
|
+
if (state === "ok") {
|
|
2225
|
+
return `
|
|
2226
|
+
<div class="viewer-workspace__preview-header">
|
|
2227
|
+
<div><strong>${escapeHtml(name)}</strong><span>${escapeHtml(path)}</span></div>
|
|
2228
|
+
<em>${escapeHtml(previewPayload.truncated ? "truncated" : `${previewPayload.size || 0} bytes`)}</em>
|
|
2229
|
+
</div>
|
|
2230
|
+
${previewPayload.truncated ? '<div class="viewer-cdx__state viewer-cdx__state--warn">Preview truncated.</div>' : ""}
|
|
2231
|
+
<pre class="viewer-workspace__code">${escapeHtml(previewPayload.content || "")}</pre>
|
|
2232
|
+
`;
|
|
2233
|
+
}
|
|
2234
|
+
if (state === "image") {
|
|
2235
|
+
return `
|
|
2236
|
+
<div class="viewer-workspace__preview-header">
|
|
2237
|
+
<div><strong>${escapeHtml(name)}</strong><span>${escapeHtml(path)}</span></div>
|
|
2238
|
+
<em>${escapeHtml(previewPayload.contentType || "image")}</em>
|
|
2239
|
+
</div>
|
|
2240
|
+
<img class="viewer-workspace__image" src="/api/workspace-file?path=${encodeURIComponent(path)}" alt="${escapeHtml(name)}">
|
|
2241
|
+
`;
|
|
2242
|
+
}
|
|
2243
|
+
return `
|
|
2244
|
+
<div class="viewer-workspace__preview-header">
|
|
2245
|
+
<div><strong>${escapeHtml(name)}</strong><span>${escapeHtml(path)}</span></div>
|
|
2246
|
+
<em>${escapeHtml(state)}</em>
|
|
2247
|
+
</div>
|
|
2248
|
+
<div class="viewer-workspace__empty">${escapeHtml(previewPayload.message || "No preview is available.")}</div>
|
|
2249
|
+
`;
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
function renderWorkspace(treePayload, previewPayload) {
|
|
2253
|
+
const selectedPath = previewPayload?.path || "";
|
|
2254
|
+
return `
|
|
2255
|
+
<div class="viewer-workspace">
|
|
2256
|
+
<aside class="viewer-workspace__tree" aria-label="Workspace files">
|
|
2257
|
+
${renderWorkspaceTree(treePayload, selectedPath)}
|
|
2258
|
+
</aside>
|
|
2259
|
+
<section class="viewer-workspace__preview" aria-label="Workspace preview">
|
|
2260
|
+
${renderWorkspacePreview(previewPayload)}
|
|
2261
|
+
</section>
|
|
2262
|
+
</div>
|
|
2263
|
+
`;
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
async function fetchWorkspaceTree(path = "") {
|
|
2267
|
+
const response = await fetch(`/api/workspace-tree?path=${encodeURIComponent(path)}`);
|
|
2268
|
+
const data = await response.json();
|
|
2269
|
+
if (!response.ok || !data.ok) {
|
|
2270
|
+
throw new Error(data.error || "Unable to load workspace tree.");
|
|
2271
|
+
}
|
|
2272
|
+
return data.payload;
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
async function fetchWorkspacePreview(path = "") {
|
|
2276
|
+
const response = await fetch(`/api/workspace-preview?path=${encodeURIComponent(path)}`);
|
|
2277
|
+
const data = await response.json();
|
|
2278
|
+
if (!response.ok || !data.ok) {
|
|
2279
|
+
throw new Error(data.error || "Unable to load workspace preview.");
|
|
2280
|
+
}
|
|
2281
|
+
return data.payload;
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
async function showWorkspace(options = {}) {
|
|
2285
|
+
if (!isCapabilityAvailable("workspace")) {
|
|
2286
|
+
const message = capabilityMessage("workspace", "Explorer is not available for this project.");
|
|
2287
|
+
setDocument("Explorer", renderWorkspace({ state: "unavailable", message }, { state: "unavailable", message }));
|
|
2288
|
+
setMeta(message);
|
|
2289
|
+
return;
|
|
2290
|
+
}
|
|
2291
|
+
if (!options.silent) {
|
|
2292
|
+
setMeta("Loading workspace...");
|
|
2293
|
+
}
|
|
2294
|
+
const tree = await fetchWorkspaceTree("");
|
|
2295
|
+
const preview = await fetchWorkspacePreview("");
|
|
2296
|
+
setDocument("Explorer", renderWorkspace(tree, preview));
|
|
2297
|
+
setMeta(options.silent ? "Explorer refreshed." : "Explorer loaded.");
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
async function openWorkspaceTree(path) {
|
|
2301
|
+
const [tree, preview] = await Promise.all([fetchWorkspaceTree(path), fetchWorkspacePreview(path)]);
|
|
2302
|
+
setDocument("Explorer", renderWorkspace(tree, preview));
|
|
2303
|
+
setMeta(path ? `Explorer folder ${path}` : "Explorer root.");
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
async function openWorkspacePreview(path) {
|
|
2307
|
+
const treePath = workspaceParentPath(path);
|
|
2308
|
+
const [tree, preview] = await Promise.all([fetchWorkspaceTree(treePath), fetchWorkspacePreview(path)]);
|
|
2309
|
+
setDocument("Explorer", renderWorkspace(tree, preview));
|
|
2310
|
+
setMeta(`Previewing ${path || "workspace root"}.`);
|
|
2311
|
+
}
|
|
2312
|
+
|
|
1736
2313
|
function objectEntries(value) {
|
|
1737
2314
|
return value && typeof value === "object" && !Array.isArray(value) ? Object.entries(value) : [];
|
|
1738
2315
|
}
|
|
@@ -1770,15 +2347,41 @@
|
|
|
1770
2347
|
return asArray(status?.rows);
|
|
1771
2348
|
}
|
|
1772
2349
|
|
|
2350
|
+
function numericValues(values) {
|
|
2351
|
+
return values.map((value) => Number(value)).filter((value) => Number.isFinite(value));
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
function formatPercentRange(values) {
|
|
2355
|
+
const numbers = numericValues(values).map((value) => Math.max(0, Math.min(100, Math.round(value))));
|
|
2356
|
+
if (!numbers.length) {
|
|
2357
|
+
return "not reported";
|
|
2358
|
+
}
|
|
2359
|
+
const min = Math.min(...numbers);
|
|
2360
|
+
const max = Math.max(...numbers);
|
|
2361
|
+
return min === max ? `${min}%` : `${min}-${max}%`;
|
|
2362
|
+
}
|
|
2363
|
+
|
|
1773
2364
|
function cdxProviders(status) {
|
|
1774
|
-
const
|
|
1775
|
-
if (
|
|
1776
|
-
return
|
|
2365
|
+
const rows = cdxRows(status);
|
|
2366
|
+
if (!rows.length) {
|
|
2367
|
+
return pickFirstArray(status, ["providers", "providerStatus", "provider_status"]);
|
|
1777
2368
|
}
|
|
1778
2369
|
const grouped = new Map();
|
|
1779
|
-
|
|
2370
|
+
rows.forEach((row) => {
|
|
1780
2371
|
const provider = String(row.provider || "unknown");
|
|
1781
|
-
const current = grouped.get(provider) || {
|
|
2372
|
+
const current = grouped.get(provider) || {
|
|
2373
|
+
name: provider,
|
|
2374
|
+
enabled: 0,
|
|
2375
|
+
active: 0,
|
|
2376
|
+
authenticated: 0,
|
|
2377
|
+
sessions: 0,
|
|
2378
|
+
remaining_5h: "not reported",
|
|
2379
|
+
remaining_week: "not reported",
|
|
2380
|
+
credits: "",
|
|
2381
|
+
_remaining5hValues: [],
|
|
2382
|
+
_remainingWeekValues: [],
|
|
2383
|
+
_creditsValues: []
|
|
2384
|
+
};
|
|
1782
2385
|
current.sessions += 1;
|
|
1783
2386
|
if (row.enabled) {
|
|
1784
2387
|
current.enabled += 1;
|
|
@@ -1789,15 +2392,31 @@
|
|
|
1789
2392
|
if (String(row.auth_status || "").toLowerCase() === "authenticated") {
|
|
1790
2393
|
current.authenticated += 1;
|
|
1791
2394
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
2395
|
+
const fiveHour = Number(row.remaining_5h_pct ?? row.remaining5hPct);
|
|
2396
|
+
if (Number.isFinite(fiveHour)) {
|
|
2397
|
+
current._remaining5hValues.push(fiveHour);
|
|
2398
|
+
}
|
|
2399
|
+
const week = Number(row.remaining_week_pct ?? row.remainingWeekPct);
|
|
2400
|
+
if (Number.isFinite(week)) {
|
|
2401
|
+
current._remainingWeekValues.push(week);
|
|
2402
|
+
}
|
|
2403
|
+
if (row.credits !== undefined && row.credits !== null && row.credits !== "") {
|
|
2404
|
+
current._creditsValues.push(row.credits);
|
|
1796
2405
|
}
|
|
1797
2406
|
current.state = current.active > 0 ? "active" : current.enabled > 0 ? "enabled" : "disabled";
|
|
1798
2407
|
grouped.set(provider, current);
|
|
1799
2408
|
});
|
|
1800
|
-
return Array.from(grouped.values())
|
|
2409
|
+
return Array.from(grouped.values()).map((provider) => {
|
|
2410
|
+
const creditsNumbers = numericValues(provider._creditsValues);
|
|
2411
|
+
const creditsTotal = creditsNumbers.length ? creditsNumbers.reduce((total, value) => total + value, 0) : null;
|
|
2412
|
+
const { _remaining5hValues, _remainingWeekValues, _creditsValues, ...publicProvider } = provider;
|
|
2413
|
+
return {
|
|
2414
|
+
...publicProvider,
|
|
2415
|
+
remaining_5h: formatPercentRange(_remaining5hValues),
|
|
2416
|
+
remaining_week: formatPercentRange(_remainingWeekValues),
|
|
2417
|
+
credits: creditsTotal === null ? "" : creditsTotal.toFixed(2)
|
|
2418
|
+
};
|
|
2419
|
+
});
|
|
1801
2420
|
}
|
|
1802
2421
|
|
|
1803
2422
|
function cdxSessions(status) {
|
|
@@ -1837,8 +2456,25 @@
|
|
|
1837
2456
|
return rows || `<li class="viewer-cdx__empty">${escapeHtml(emptyText)}</li>`;
|
|
1838
2457
|
}
|
|
1839
2458
|
|
|
2459
|
+
function renderCdxArtifactRows(value, emptyText) {
|
|
2460
|
+
const rows = objectEntries(value).slice(0, 12).map(([key, entry]) => {
|
|
2461
|
+
const path = typeof entry === "string" ? entry : "";
|
|
2462
|
+
return `
|
|
2463
|
+
<li class="viewer-cdx__row">
|
|
2464
|
+
<span>${escapeHtml(cdxLabel(key))}</span>
|
|
2465
|
+
<strong>${path
|
|
2466
|
+
? `<button class="viewer-cdx__path-link" type="button" data-viewer-cdx-artifact-path="${escapeHtml(path)}">${escapeHtml(path)}</button>`
|
|
2467
|
+
: escapeHtml(typeof entry === "object" ? JSON.stringify(entry) : entry)}
|
|
2468
|
+
</strong>
|
|
2469
|
+
</li>
|
|
2470
|
+
`;
|
|
2471
|
+
}).join("");
|
|
2472
|
+
return rows || `<li class="viewer-cdx__empty">${escapeHtml(emptyText)}</li>`;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
1840
2475
|
function cdxLabel(value) {
|
|
1841
2476
|
return String(value || "")
|
|
2477
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
|
1842
2478
|
.replace(/[_-]+/g, " ")
|
|
1843
2479
|
.replace(/\b\w/g, (letter) => letter.toUpperCase());
|
|
1844
2480
|
}
|
|
@@ -1848,7 +2484,7 @@
|
|
|
1848
2484
|
if (["ready", "ok", "active", "enabled", "authenticated"].some((entry) => state.includes(entry))) {
|
|
1849
2485
|
return "ok";
|
|
1850
2486
|
}
|
|
1851
|
-
if (["starting", "pending", "warning", "low", "limited"].some((entry) => state.includes(entry))) {
|
|
2487
|
+
if (["starting", "pending", "running", "warning", "low", "limited", "stale"].some((entry) => state.includes(entry))) {
|
|
1852
2488
|
return "warn";
|
|
1853
2489
|
}
|
|
1854
2490
|
if (["error", "failed", "disabled", "unavailable", "unauthenticated"].some((entry) => state.includes(entry))) {
|
|
@@ -1981,6 +2617,10 @@
|
|
|
1981
2617
|
return `<span class="viewer-cdx__badge viewer-cdx__badge--${cdxStateClass(label)}">${escapeHtml(cdxLabel(label))}</span>`;
|
|
1982
2618
|
}
|
|
1983
2619
|
|
|
2620
|
+
function cdxRunStatusDetail(run) {
|
|
2621
|
+
return "";
|
|
2622
|
+
}
|
|
2623
|
+
|
|
1984
2624
|
function cdxDetailEntries(item, excludedKeys) {
|
|
1985
2625
|
return objectEntries(item)
|
|
1986
2626
|
.filter(([key, value]) => !excludedKeys.includes(key) && value !== undefined && value !== null && value !== "")
|
|
@@ -2023,31 +2663,108 @@
|
|
|
2023
2663
|
return explicit === true ? "YES" : "-";
|
|
2024
2664
|
}
|
|
2025
2665
|
|
|
2666
|
+
function cdxProviderName(item) {
|
|
2667
|
+
return String(cdxField(item, ["provider", "name"], "unknown") || "unknown");
|
|
2668
|
+
}
|
|
2669
|
+
|
|
2670
|
+
function cdxKnownProviders(status, providers, sessions) {
|
|
2671
|
+
const names = new Set();
|
|
2672
|
+
providers.forEach((provider) => {
|
|
2673
|
+
const name = cdxProviderName(provider);
|
|
2674
|
+
if (name) {
|
|
2675
|
+
names.add(name);
|
|
2676
|
+
}
|
|
2677
|
+
});
|
|
2678
|
+
sessions.forEach((session) => {
|
|
2679
|
+
const name = cdxProviderName(session);
|
|
2680
|
+
if (name) {
|
|
2681
|
+
names.add(name);
|
|
2682
|
+
}
|
|
2683
|
+
});
|
|
2684
|
+
pickFirstArray(status, ["providers", "providerStatus", "provider_status"]).forEach((provider) => {
|
|
2685
|
+
const name = cdxProviderName(provider);
|
|
2686
|
+
if (name) {
|
|
2687
|
+
names.add(name);
|
|
2688
|
+
}
|
|
2689
|
+
});
|
|
2690
|
+
return Array.from(names).sort((left, right) => left.localeCompare(right));
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
function filterCdxEntriesByProvider(entries, providerFilter) {
|
|
2694
|
+
if (providerFilter.mode !== "subset" || !providerFilter.selected.length) {
|
|
2695
|
+
return entries;
|
|
2696
|
+
}
|
|
2697
|
+
const selected = new Set(providerFilter.selected);
|
|
2698
|
+
return entries.filter((entry) => selected.has(cdxProviderName(entry)));
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
function renderCdxStatusControls(knownProviders, visibleColumns, providerFilter) {
|
|
2702
|
+
const columnRows = cdxStatusColumns.map((column) => `
|
|
2703
|
+
<label class="viewer-cdx__menu-check">
|
|
2704
|
+
<input type="checkbox" data-viewer-cdx-column="${escapeHtml(column.id)}"${visibleColumns[column.id] ? " checked" : ""}>
|
|
2705
|
+
<span>${escapeHtml(column.label)}</span>
|
|
2706
|
+
</label>
|
|
2707
|
+
`).join("");
|
|
2708
|
+
const selected = new Set(providerFilter.mode === "subset" ? providerFilter.selected : knownProviders);
|
|
2709
|
+
const providerRows = knownProviders.map((provider) => `
|
|
2710
|
+
<label class="viewer-cdx__menu-check">
|
|
2711
|
+
<input type="checkbox" data-viewer-cdx-provider="${escapeHtml(provider)}"${selected.has(provider) ? " checked" : ""}>
|
|
2712
|
+
<span>${escapeHtml(provider)}</span>
|
|
2713
|
+
</label>
|
|
2714
|
+
`).join("");
|
|
2715
|
+
const providerSummary = providerFilter.mode === "subset" && providerFilter.selected.length
|
|
2716
|
+
? `${providerFilter.selected.length}/${knownProviders.length || providerFilter.selected.length}`
|
|
2717
|
+
: "All";
|
|
2718
|
+
return `
|
|
2719
|
+
<div class="viewer-cdx__controls" aria-label="CDX status table controls">
|
|
2720
|
+
<details class="viewer-cdx__menu">
|
|
2721
|
+
<summary class="viewer-cdx__icon-button" title="Configure status columns" aria-label="Configure status columns">
|
|
2722
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 8.5a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7Z" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.9l.1.1-2 3.4-.2-.1a1.7 1.7 0 0 0-2 .1 1.7 1.7 0 0 0-.8 1.7v.2H9.2v-.2a1.7 1.7 0 0 0-.8-1.7 1.7 1.7 0 0 0-2-.1l-.2.1-2-3.4.1-.1a1.7 1.7 0 0 0 .3-1.9 1.7 1.7 0 0 0-1.5-1.1H3v-3.8h.1A1.7 1.7 0 0 0 4.6 9a1.7 1.7 0 0 0-.3-1.9l-.1-.1 2-3.4.2.1a1.7 1.7 0 0 0 2-.1 1.7 1.7 0 0 0 .8-1.7v-.2h5.6v.2a1.7 1.7 0 0 0 .8 1.7 1.7 1.7 0 0 0 2 .1l.2-.1 2 3.4-.1.1a1.7 1.7 0 0 0-.3 1.9 1.7 1.7 0 0 0 1.5 1.1h.1v3.8h-.1a1.7 1.7 0 0 0-1.5 1.1Z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/></svg>
|
|
2723
|
+
</summary>
|
|
2724
|
+
<div class="viewer-cdx__menu-panel" role="menu" aria-label="CDX status columns">${columnRows}</div>
|
|
2725
|
+
</details>
|
|
2726
|
+
<details class="viewer-cdx__menu">
|
|
2727
|
+
<summary class="viewer-cdx__icon-button" title="Filter status providers" aria-label="Filter status providers">
|
|
2728
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 6h16l-6.5 7.2V19l-3 1.5v-7.3z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" stroke-linecap="round"/></svg>
|
|
2729
|
+
<span class="viewer-cdx__icon-count">${escapeHtml(providerSummary)}</span>
|
|
2730
|
+
</summary>
|
|
2731
|
+
<div class="viewer-cdx__menu-panel" role="menu" aria-label="CDX provider filter">
|
|
2732
|
+
<button class="viewer-cdx__menu-action" type="button" data-viewer-cdx-provider-all>All providers</button>
|
|
2733
|
+
${providerRows || '<div class="viewer-cdx__empty">No providers reported.</div>'}
|
|
2734
|
+
</div>
|
|
2735
|
+
</details>
|
|
2736
|
+
</div>
|
|
2737
|
+
`;
|
|
2738
|
+
}
|
|
2739
|
+
|
|
2026
2740
|
function renderCdxSessionTable(sessions, emptyText) {
|
|
2027
2741
|
if (!sessions.length) {
|
|
2028
2742
|
return `<div class="viewer-cdx__empty">${escapeHtml(emptyText)}</div>`;
|
|
2029
2743
|
}
|
|
2744
|
+
const visibleColumns = cdxColumnVisibilityPreference();
|
|
2745
|
+
const cellRenderers = {
|
|
2746
|
+
session: (item) => {
|
|
2747
|
+
const name = cdxField(item, ["session_name", "name", "id", "value"]);
|
|
2748
|
+
return `<td class="viewer-cdx__session-name">${escapeHtml(`${name}${item.active ? "*" : ""}`)}</td>`;
|
|
2749
|
+
},
|
|
2750
|
+
provider: (item) => `<td>${escapeHtml(cdxField(item, ["provider"], "-"))}</td>`,
|
|
2751
|
+
status: (item) => `<td>${renderCdxBadge(cdxField(item, ["status", "state"]))}</td>`,
|
|
2752
|
+
auth: (item) => `<td>${escapeHtml(String(cdxField(item, ["auth_status", "authStatus"], "-")).replace("authenticated", "logged"))}</td>`,
|
|
2753
|
+
ok: (item) => `<td>${renderCdxRemainingPill(item) || escapeHtml(cdxPct(cdxField(item, ["available_pct", "availablePct"], NaN)))}</td>`,
|
|
2754
|
+
remaining5h: (item) => `<td>${escapeHtml(cdxPct(cdxField(item, ["remaining_5h_pct", "remaining5hPct"], NaN)))}</td>`,
|
|
2755
|
+
remainingWeek: (item) => `<td>${escapeHtml(cdxPct(cdxField(item, ["remaining_week_pct", "remainingWeekPct"], NaN)))}</td>`,
|
|
2756
|
+
block: (item) => `<td>${escapeHtml(cdxSessionBlock(item))}</td>`,
|
|
2757
|
+
credits: (item) => `<td>${escapeHtml(formatCdxCredits(cdxField(item, ["credits", "cr"], "-")))}</td>`,
|
|
2758
|
+
reset5h: (item) => `<td>${escapeHtml(formatCdxResetAt(cdxField(item, ["reset_5h_at", "reset5hAt", "reset_at", "resetAt"], "")))}</td>`,
|
|
2759
|
+
resetWeek: (item) => `<td>${escapeHtml(formatCdxResetAt(cdxField(item, ["reset_week_at", "resetWeekAt", "reset_at", "resetAt"], "")))}</td>`,
|
|
2760
|
+
updated: (item) => `<td>${escapeHtml(formatCdxResetAt(cdxField(item, ["updated_at", "updatedAt"], "")))}</td>`
|
|
2761
|
+
};
|
|
2762
|
+
const activeColumns = cdxStatusColumns.filter((column) => visibleColumns[column.id]);
|
|
2030
2763
|
const rows = sessions.slice(0, 24).map((entry) => {
|
|
2031
2764
|
const item = entry && typeof entry === "object" ? entry : { value: entry };
|
|
2032
|
-
const name = cdxField(item, ["session_name", "name", "id", "value"]);
|
|
2033
|
-
const sessionName = `${name}${item.active ? "*" : ""}`;
|
|
2034
|
-
const status = cdxField(item, ["status", "state"]);
|
|
2035
|
-
const auth = String(cdxField(item, ["auth_status", "authStatus"], "-")).replace("authenticated", "logged");
|
|
2036
|
-
const block = cdxSessionBlock(item);
|
|
2037
2765
|
return `
|
|
2038
2766
|
<tr>
|
|
2039
|
-
|
|
2040
|
-
<td>${escapeHtml(cdxField(item, ["provider"], "-"))}</td>
|
|
2041
|
-
<td>${renderCdxBadge(status)}</td>
|
|
2042
|
-
<td>${escapeHtml(auth)}</td>
|
|
2043
|
-
<td>${renderCdxRemainingPill(item) || escapeHtml(cdxPct(cdxField(item, ["available_pct", "availablePct"], NaN)))}</td>
|
|
2044
|
-
<td>${escapeHtml(cdxPct(cdxField(item, ["remaining_5h_pct", "remaining5hPct"], NaN)))}</td>
|
|
2045
|
-
<td>${escapeHtml(cdxPct(cdxField(item, ["remaining_week_pct", "remainingWeekPct"], NaN)))}</td>
|
|
2046
|
-
<td>${escapeHtml(block)}</td>
|
|
2047
|
-
<td>${escapeHtml(formatCdxCredits(cdxField(item, ["credits", "cr"], "-")))}</td>
|
|
2048
|
-
<td>${escapeHtml(formatCdxResetAt(cdxField(item, ["reset_5h_at", "reset5hAt", "reset_at", "resetAt"], "")))}</td>
|
|
2049
|
-
<td>${escapeHtml(formatCdxResetAt(cdxField(item, ["reset_week_at", "resetWeekAt", "reset_at", "resetAt"], "")))}</td>
|
|
2050
|
-
<td>${escapeHtml(formatCdxResetAt(cdxField(item, ["updated_at", "updatedAt"], "")))}</td>
|
|
2767
|
+
${activeColumns.map((column) => cellRenderers[column.id](item)).join("")}
|
|
2051
2768
|
</tr>
|
|
2052
2769
|
`;
|
|
2053
2770
|
}).join("");
|
|
@@ -2056,18 +2773,7 @@
|
|
|
2056
2773
|
<table class="viewer-cdx__table">
|
|
2057
2774
|
<thead>
|
|
2058
2775
|
<tr>
|
|
2059
|
-
|
|
2060
|
-
<th>PROV.</th>
|
|
2061
|
-
<th>STATUS</th>
|
|
2062
|
-
<th>AUTH</th>
|
|
2063
|
-
<th>OK</th>
|
|
2064
|
-
<th>5H</th>
|
|
2065
|
-
<th>WEEK</th>
|
|
2066
|
-
<th>BLOCK</th>
|
|
2067
|
-
<th>CR</th>
|
|
2068
|
-
<th>RESET 5H</th>
|
|
2069
|
-
<th>RESET WEEK</th>
|
|
2070
|
-
<th>UPDATED</th>
|
|
2776
|
+
${activeColumns.map((column) => `<th>${escapeHtml(column.label)}</th>`).join("")}
|
|
2071
2777
|
</tr>
|
|
2072
2778
|
</thead>
|
|
2073
2779
|
<tbody>${rows}</tbody>
|
|
@@ -2106,28 +2812,250 @@
|
|
|
2106
2812
|
return rows || `<li class="viewer-cdx__empty">${escapeHtml(emptyText)}</li>`;
|
|
2107
2813
|
}
|
|
2108
2814
|
|
|
2109
|
-
function
|
|
2110
|
-
return
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2815
|
+
function cdxMissionCatalog(payload = {}) {
|
|
2816
|
+
return payload.catalog || {
|
|
2817
|
+
missions: [
|
|
2818
|
+
{ 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" }] },
|
|
2819
|
+
{ 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" }] },
|
|
2820
|
+
{ 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 },
|
|
2821
|
+
{ 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 }] },
|
|
2822
|
+
{ 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" }] }
|
|
2823
|
+
],
|
|
2824
|
+
strengths: [
|
|
2825
|
+
{ id: "standard", label: "Standard" },
|
|
2826
|
+
{ id: "deep", label: "Deep" },
|
|
2827
|
+
{ id: "max", label: "Max" }
|
|
2828
|
+
],
|
|
2829
|
+
defaultMissionId: "full-audit",
|
|
2830
|
+
defaultStrengthId: "standard"
|
|
2831
|
+
};
|
|
2116
2832
|
}
|
|
2117
2833
|
|
|
2118
|
-
function
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2834
|
+
function selectedCdxMissionRequest() {
|
|
2835
|
+
const catalog = latestCdxMissionState.catalog || cdxMissionCatalog();
|
|
2836
|
+
const missions = Array.isArray(catalog.missions) ? catalog.missions : [];
|
|
2837
|
+
const missionId = latestCdxMissionState.missionId || "full-audit";
|
|
2838
|
+
const mission = missions.find((entry) => entry.id === missionId) || {};
|
|
2839
|
+
const allowFileWrites = mission.supportsFileWrites === false
|
|
2840
|
+
? "false"
|
|
2841
|
+
: (latestCdxMissionState.missionInputs.allowFileWrites === "false" ? "false" : "true");
|
|
2842
|
+
return {
|
|
2843
|
+
missionId,
|
|
2844
|
+
sessionId: latestCdxMissionState.sessionId || "",
|
|
2845
|
+
strengthId: latestCdxMissionState.strengthId || "standard",
|
|
2846
|
+
...latestCdxMissionState.missionInputs,
|
|
2847
|
+
allowFileWrites,
|
|
2848
|
+
commitAtEnd: latestCdxMissionState.missionInputs.commitAtEnd === "true" ? "true" : "false"
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
function renderCdxMissionInputs(mission) {
|
|
2853
|
+
const fields = Array.isArray(mission?.inputFields) ? mission.inputFields : [];
|
|
2854
|
+
if (!fields.length) {
|
|
2855
|
+
return "";
|
|
2856
|
+
}
|
|
2857
|
+
const rows = fields.map((field) => {
|
|
2858
|
+
const id = field.id || "";
|
|
2859
|
+
const value = latestCdxMissionState.missionInputs[id] || "";
|
|
2860
|
+
if (field.type === "checkbox") {
|
|
2861
|
+
return `
|
|
2862
|
+
<label class="viewer-cdx__field viewer-cdx__field--check">
|
|
2863
|
+
<input data-viewer-cdx-input="${escapeHtml(id)}" type="checkbox"${value === "true" ? " checked" : ""}>
|
|
2864
|
+
<span>${escapeHtml(field.label || cdxLabel(id))}</span>
|
|
2865
|
+
</label>
|
|
2866
|
+
`;
|
|
2867
|
+
}
|
|
2868
|
+
if (field.type === "textarea") {
|
|
2869
|
+
return `
|
|
2870
|
+
<label class="viewer-cdx__field">
|
|
2871
|
+
<span>${escapeHtml(field.label || cdxLabel(id))}</span>
|
|
2872
|
+
<textarea data-viewer-cdx-input="${escapeHtml(id)}" placeholder="${escapeHtml(field.placeholder || "")}" rows="5">${escapeHtml(value)}</textarea>
|
|
2873
|
+
</label>
|
|
2874
|
+
`;
|
|
2875
|
+
}
|
|
2876
|
+
return `
|
|
2877
|
+
<label class="viewer-cdx__field">
|
|
2878
|
+
<span>${escapeHtml(field.label || cdxLabel(id))}</span>
|
|
2879
|
+
<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)}"` : ""}>
|
|
2880
|
+
</label>
|
|
2881
|
+
`;
|
|
2882
|
+
}).join("");
|
|
2883
|
+
return `<div class="viewer-cdx__inputs">${rows}</div>`;
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
function renderCdxMissionSetup(statusPayload, planPayload, runPayload, applyPayload) {
|
|
2887
|
+
const catalog = cdxMissionCatalog(planPayload || {});
|
|
2888
|
+
latestCdxMissionState.catalog = catalog;
|
|
2889
|
+
const missions = Array.isArray(catalog.missions) ? catalog.missions : [];
|
|
2890
|
+
const strengths = Array.isArray(catalog.strengths) ? catalog.strengths : [];
|
|
2891
|
+
const status = statusPayload?.status || {};
|
|
2892
|
+
const sessions = cdxSessions(status);
|
|
2893
|
+
const selectedSession = latestCdxMissionState.sessionId || cdxField(sessions[0] || {}, ["id", "name", "session_name", "value"], "");
|
|
2894
|
+
const missionId = latestCdxMissionState.missionId || catalog.defaultMissionId || "full-audit";
|
|
2895
|
+
const selectedMission = missions.find((mission) => mission.id === missionId) || {};
|
|
2896
|
+
const strengthId = latestCdxMissionState.strengthId || catalog.defaultStrengthId || "standard";
|
|
2897
|
+
const supportsFileWrites = selectedMission.supportsFileWrites !== false;
|
|
2898
|
+
const allowFileWrites = supportsFileWrites && latestCdxMissionState.missionInputs.allowFileWrites !== "false";
|
|
2899
|
+
const fileWriteLabel = ["full-audit", "release-review"].includes(selectedMission.id)
|
|
2900
|
+
? "Write mission corpus/report"
|
|
2901
|
+
: "Allow CDX to modify files";
|
|
2902
|
+
const fileWriteControl = supportsFileWrites
|
|
2903
|
+
? `
|
|
2904
|
+
<label class="viewer-cdx__field viewer-cdx__field--check">
|
|
2905
|
+
<input data-viewer-cdx-input="allowFileWrites" type="checkbox"${allowFileWrites ? " checked" : ""}>
|
|
2906
|
+
<span>${escapeHtml(fileWriteLabel)}</span>
|
|
2907
|
+
</label>
|
|
2908
|
+
<label class="viewer-cdx__field viewer-cdx__field--check">
|
|
2909
|
+
<input data-viewer-cdx-input="commitAtEnd" type="checkbox"${latestCdxMissionState.missionInputs.commitAtEnd === "true" ? " checked" : ""}>
|
|
2910
|
+
<span>Commit changes at end</span>
|
|
2911
|
+
</label>
|
|
2912
|
+
`
|
|
2913
|
+
: `
|
|
2914
|
+
<div class="viewer-cdx__meta">Corpus updates are applied after CDX returns allowed actions.</div>
|
|
2915
|
+
`;
|
|
2916
|
+
latestCdxMissionState.sessionId = selectedSession;
|
|
2917
|
+
const missionCards = missions.map((mission) => `
|
|
2918
|
+
<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"}">
|
|
2919
|
+
<strong>${escapeHtml(mission.title || mission.id)}</strong>
|
|
2920
|
+
<span>${escapeHtml(mission.description || "")}</span>
|
|
2921
|
+
<em>${escapeHtml(cdxLabel(mission.scope || ""))}</em>
|
|
2922
|
+
</button>
|
|
2923
|
+
`).join("");
|
|
2924
|
+
const sessionOptions = sessions.map((session) => {
|
|
2925
|
+
const item = session && typeof session === "object" ? session : { value: session };
|
|
2926
|
+
const id = cdxField(item, ["id", "name", "session_name", "value"], "");
|
|
2927
|
+
const label = [id, cdxField(item, ["provider"], ""), renderTextRemaining(item)].filter(Boolean).join(" · ");
|
|
2928
|
+
return `<option value="${escapeHtml(id)}"${id === selectedSession ? " selected" : ""}>${escapeHtml(label || id)}</option>`;
|
|
2929
|
+
}).join("");
|
|
2930
|
+
const strengthButtons = strengths.map((strength) => `
|
|
2931
|
+
<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>
|
|
2932
|
+
`).join("");
|
|
2933
|
+
const plan = planPayload?.plan;
|
|
2934
|
+
const warnings = Array.isArray(plan?.warnings) ? plan.warnings : [];
|
|
2935
|
+
const command = Array.isArray(plan?.command) ? plan.command.join(" ") : "";
|
|
2936
|
+
const warningRows = warnings.map((warning) => `<li>${escapeHtml(warning)}</li>`).join("");
|
|
2937
|
+
const canRun = planPayload?.state === "ok" && plan?.canRun;
|
|
2938
|
+
const usage = runPayload?.run?.usage || {};
|
|
2939
|
+
const run = runPayload?.run;
|
|
2940
|
+
const usageText = usage.available
|
|
2941
|
+
? `${usage.totalTokens ?? "-"} total · ${usage.inputTokens ?? "-"} in · ${usage.outputTokens ?? "-"} out`
|
|
2942
|
+
: (usage.message || "Token usage not reported yet.");
|
|
2943
|
+
const parsedActions = Array.isArray(run?.parsed?.actions) ? run.parsed.actions : [];
|
|
2944
|
+
const applyResults = Array.isArray(applyPayload?.results) ? applyPayload.results : [];
|
|
2945
|
+
const actionRows = parsedActions.map((action) => `
|
|
2946
|
+
<li class="viewer-cdx__row"><span>${escapeHtml(cdxLabel(action.type || "action"))}</span><strong>${escapeHtml(action.target || "-")}</strong></li>
|
|
2947
|
+
`).join("");
|
|
2948
|
+
const applyRows = applyResults.map((result) => `
|
|
2949
|
+
<li class="viewer-cdx__row"><span>${escapeHtml(cdxLabel(result.type || "action"))}</span><strong>${escapeHtml(result.returnCode === 0 ? "applied" : "failed")}</strong></li>
|
|
2950
|
+
`).join("");
|
|
2951
|
+
return `
|
|
2952
|
+
<div class="viewer-cdx__workspace viewer-cdx__workspace--missions">
|
|
2953
|
+
<div class="viewer-cdx__stack">
|
|
2954
|
+
<section class="viewer-cdx__section">
|
|
2955
|
+
<h2 class="viewer-cdx__heading">Mission</h2>
|
|
2956
|
+
<div class="viewer-cdx__missions">${missionCards}</div>
|
|
2957
|
+
</section>
|
|
2958
|
+
<section class="viewer-cdx__section">
|
|
2959
|
+
<h2 class="viewer-cdx__heading">Execution</h2>
|
|
2960
|
+
<label class="viewer-cdx__field">
|
|
2961
|
+
<span>Session</span>
|
|
2962
|
+
<select data-viewer-cdx-session>${sessionOptions || '<option value="">No session reported</option>'}</select>
|
|
2963
|
+
</label>
|
|
2964
|
+
<div class="viewer-cdx__strengths">${strengthButtons}</div>
|
|
2965
|
+
${fileWriteControl}
|
|
2966
|
+
${renderCdxMissionInputs(selectedMission)}
|
|
2967
|
+
<div class="viewer-cdx__actions">
|
|
2968
|
+
<button class="btn" type="button" data-viewer-cdx-plan>Preview</button>
|
|
2969
|
+
<button class="btn" type="button" data-viewer-cdx-run${canRun ? "" : " disabled"}>Launch run</button>
|
|
2970
|
+
</div>
|
|
2971
|
+
</section>
|
|
2972
|
+
</div>
|
|
2973
|
+
<div class="viewer-cdx__stack">
|
|
2974
|
+
<section class="viewer-cdx__section">
|
|
2975
|
+
<h2 class="viewer-cdx__heading">Plan preview</h2>
|
|
2976
|
+
${planPayload && planPayload.state !== "ok" ? `<div class="viewer-cdx__state">${escapeHtml(planPayload.message || "Unable to build mission plan.")}</div>` : ""}
|
|
2977
|
+
${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>'}
|
|
2978
|
+
${plan?.releaseTag ? `<div class="viewer-cdx__meta">Base tag: ${escapeHtml(plan.releaseTag)}</div>` : ""}
|
|
2979
|
+
${plan?.commitAtEnd ? '<div class="viewer-cdx__meta">Commit at end: enabled when mission changes files.</div>' : ""}
|
|
2980
|
+
${plan?.requiresConfirmation ? '<div class="viewer-cdx__meta">Plan-first mission: Logics changes need explicit apply after CDX returns allowed actions.</div>' : ""}
|
|
2981
|
+
${warningRows ? `<ul class="viewer-cdx__warnings">${warningRows}</ul>` : ""}
|
|
2982
|
+
</section>
|
|
2983
|
+
<section class="viewer-cdx__section">
|
|
2984
|
+
<h2 class="viewer-cdx__heading">Run output</h2>
|
|
2985
|
+
${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>'}
|
|
2986
|
+
${run ? `<ul class="viewer-cdx__list">
|
|
2987
|
+
<li class="viewer-cdx__row"><span>Run</span><strong>${escapeHtml(run.runId || "-")}</strong></li>
|
|
2988
|
+
<li class="viewer-cdx__row"><span>Usage</span><strong>${escapeHtml(usageText)}</strong></li>
|
|
2989
|
+
<li class="viewer-cdx__row"><span>Return code</span><strong>${escapeHtml(run.returnCode ?? "-")}</strong></li>
|
|
2990
|
+
</ul>` : ""}
|
|
2991
|
+
${run?.stdout ? `<pre class="viewer-cdx__code">${escapeHtml(run.stdout)}</pre>` : ""}
|
|
2992
|
+
${run?.stderr ? `<pre class="viewer-cdx__code viewer-cdx__code--error">${escapeHtml(run.stderr)}</pre>` : ""}
|
|
2993
|
+
</section>
|
|
2994
|
+
${plan?.missionId === "corpus-ready" || latestCdxMissionState.missionId === "corpus-ready" ? `
|
|
2995
|
+
<section class="viewer-cdx__section">
|
|
2996
|
+
<h2 class="viewer-cdx__heading">Corpus apply</h2>
|
|
2997
|
+
<ul class="viewer-cdx__list">${actionRows || '<li class="viewer-cdx__empty">CDX has not returned allowed corpus actions yet.</li>'}</ul>
|
|
2998
|
+
<div class="viewer-cdx__actions">
|
|
2999
|
+
<button class="btn" type="button" data-viewer-cdx-apply-plan${parsedActions.length ? "" : " disabled"}>Apply allowed actions</button>
|
|
3000
|
+
</div>
|
|
3001
|
+
${applyPayload ? `<div class="viewer-cdx__state viewer-cdx__state--${escapeHtml(cdxStateClass(applyPayload.state))}">${escapeHtml(applyPayload.message || cdxLabel(applyPayload.state))}</div>` : ""}
|
|
3002
|
+
${applyRows ? `<ul class="viewer-cdx__list">${applyRows}</ul>` : ""}
|
|
3003
|
+
</section>
|
|
3004
|
+
` : ""}
|
|
3005
|
+
</div>
|
|
3006
|
+
</div>
|
|
3007
|
+
`;
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
function renderTextRemaining(item) {
|
|
3011
|
+
const percent = cdxRemainingPct(item);
|
|
3012
|
+
return percent === null ? "" : `${percent}% remaining`;
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
function renderCdxMissions(statusPayload, planPayload = null, runPayload = null, applyPayload = null) {
|
|
3016
|
+
if (!statusPayload || statusPayload.state !== "ok") {
|
|
3017
|
+
return `
|
|
3018
|
+
<div class="viewer-cdx">
|
|
3019
|
+
${renderCdxModeSwitcher("missions")}
|
|
3020
|
+
<div class="viewer-cdx__state">${escapeHtml(statusPayload?.message || "CDX missions are unavailable.")}</div>
|
|
3021
|
+
</div>
|
|
3022
|
+
`;
|
|
3023
|
+
}
|
|
3024
|
+
return `
|
|
3025
|
+
<div class="viewer-cdx">
|
|
3026
|
+
${renderCdxModeSwitcher("missions")}
|
|
3027
|
+
${renderCdxMissionSetup(statusPayload, planPayload, runPayload, applyPayload)}
|
|
3028
|
+
</div>
|
|
3029
|
+
`;
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
function renderCdxModeSwitcher(active) {
|
|
3033
|
+
return `
|
|
3034
|
+
<div class="viewer-cdx__modes" role="tablist" aria-label="CDX views">
|
|
3035
|
+
<button class="viewer-cdx__mode${active === "status" ? " is-active" : ""}" type="button" data-viewer-cdx-mode="status" aria-selected="${active === "status" ? "true" : "false"}">Status</button>
|
|
3036
|
+
<button class="viewer-cdx__mode${active === "missions" ? " is-active" : ""}" type="button" data-viewer-cdx-mode="missions" aria-selected="${active === "missions" ? "true" : "false"}">Missions</button>
|
|
3037
|
+
<button class="viewer-cdx__mode${active === "runs" ? " is-active" : ""}" type="button" data-viewer-cdx-mode="runs" aria-selected="${active === "runs" ? "true" : "false"}">Runs</button>
|
|
3038
|
+
</div>
|
|
3039
|
+
`;
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
function renderCdxStatus(payload) {
|
|
3043
|
+
if (!payload || payload.state !== "ok") {
|
|
3044
|
+
return `
|
|
3045
|
+
<div class="viewer-cdx">
|
|
3046
|
+
${renderCdxModeSwitcher("status")}
|
|
3047
|
+
<div class="viewer-cdx__state">${escapeHtml(payload?.message || "CDX status is unavailable.")}</div>
|
|
3048
|
+
</div>
|
|
3049
|
+
`;
|
|
3050
|
+
}
|
|
3051
|
+
const status = payload.status || {};
|
|
3052
|
+
const allProviders = cdxProviders(status);
|
|
3053
|
+
const allSessions = cdxSessions(status);
|
|
3054
|
+
const providerFilter = cdxProviderFilterPreference();
|
|
3055
|
+
const knownProviders = cdxKnownProviders(status, allProviders, allSessions);
|
|
3056
|
+
const providers = filterCdxEntriesByProvider(allProviders, providerFilter);
|
|
3057
|
+
const sessions = filterCdxEntriesByProvider(allSessions, providerFilter);
|
|
3058
|
+
const readiness = cdxReadiness(status);
|
|
2131
3059
|
const commands = pickFirstArray(status, ["nextCommands", "next_commands", "safeCommands", "safe_commands", "commands"])
|
|
2132
3060
|
.map((entry) => typeof entry === "string" ? entry : (entry.command || entry.value || entry.name || ""))
|
|
2133
3061
|
.filter(Boolean);
|
|
@@ -2157,16 +3085,13 @@
|
|
|
2157
3085
|
<div class="viewer-cdx">
|
|
2158
3086
|
${renderCdxModeSwitcher("status")}
|
|
2159
3087
|
<div class="viewer-cdx__summary">${cards}</div>
|
|
3088
|
+
${renderCdxStatusControls(knownProviders, cdxColumnVisibilityPreference(), providerFilter)}
|
|
2160
3089
|
<div class="viewer-cdx__workspace">
|
|
2161
3090
|
<div class="viewer-cdx__stack">
|
|
2162
3091
|
<section class="viewer-cdx__section">
|
|
2163
3092
|
<h2 class="viewer-cdx__heading">Sessions</h2>
|
|
2164
3093
|
${renderCdxSessionTable(sessions, "No sessions reported.")}
|
|
2165
3094
|
</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
3095
|
</div>
|
|
2171
3096
|
<div class="viewer-cdx__stack">
|
|
2172
3097
|
<section class="viewer-cdx__section">
|
|
@@ -2177,12 +3102,22 @@
|
|
|
2177
3102
|
<h2 class="viewer-cdx__heading">Safe next commands</h2>
|
|
2178
3103
|
<ul class="viewer-cdx__commands">${commandRows || '<li class="viewer-cdx__empty">No suggested commands reported.</li>'}</ul>
|
|
2179
3104
|
</section>
|
|
3105
|
+
<section class="viewer-cdx__section">
|
|
3106
|
+
<h2 class="viewer-cdx__heading">Providers</h2>
|
|
3107
|
+
<ul class="viewer-cdx__list">${renderCdxEntityRows(providers, "No provider status reported.", { subtitleKeys: ["model"] })}</ul>
|
|
3108
|
+
</section>
|
|
2180
3109
|
</div>
|
|
2181
3110
|
</div>
|
|
2182
3111
|
</div>
|
|
2183
3112
|
`;
|
|
2184
3113
|
}
|
|
2185
3114
|
|
|
3115
|
+
function rerenderCdxStatusFromPreferences() {
|
|
3116
|
+
if (isCdxStatusOpen() && latestCdxStatusPayload) {
|
|
3117
|
+
setDocument("CDX status", renderCdxStatus(latestCdxStatusPayload));
|
|
3118
|
+
}
|
|
3119
|
+
}
|
|
3120
|
+
|
|
2186
3121
|
function renderCdxRuns(payload) {
|
|
2187
3122
|
if (!payload || payload.state !== "ok") {
|
|
2188
3123
|
return `
|
|
@@ -2193,21 +3128,33 @@
|
|
|
2193
3128
|
`;
|
|
2194
3129
|
}
|
|
2195
3130
|
const runs = Array.isArray(payload.runs) ? payload.runs : [];
|
|
2196
|
-
const
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
3131
|
+
const staleCount = runs.filter((run) => String(cdxField(run, ["status", "state"], "")).toLowerCase() === "stale").length;
|
|
3132
|
+
const runningCount = runs.filter((run) => ["running", "starting", "pending"].includes(String(cdxField(run, ["status", "state"], "")).toLowerCase())).length;
|
|
3133
|
+
const runsSummary = staleCount
|
|
3134
|
+
? `${runs.length} reported · ${staleCount} incomplete${runningCount ? ` · ${runningCount} running` : ""}`
|
|
3135
|
+
: runningCount
|
|
3136
|
+
? `${runs.length} reported · ${runningCount} running`
|
|
3137
|
+
: `${runs.length} reported`;
|
|
3138
|
+
const rows = runs.map((run) => {
|
|
3139
|
+
const runId = cdxField(run, ["run_id", "runId", "id"], "");
|
|
3140
|
+
const status = cdxField(run, ["status", "state"], "unknown");
|
|
3141
|
+
const detail = cdxRunStatusDetail(run);
|
|
3142
|
+
return `
|
|
3143
|
+
<tr>
|
|
3144
|
+
<td><code>${escapeHtml(runId || "-")}</code>${detail ? `<div class="viewer-cdx__meta">${escapeHtml(detail)}</div>` : ""}</td>
|
|
3145
|
+
<td>${renderCdxBadge(status)}</td>
|
|
3146
|
+
<td>${escapeHtml(cdxField(run, ["kind"], "assistant"))}</td>
|
|
3147
|
+
<td>${escapeHtml(cdxField(run, ["session", "session_id", "sessionId"], "-"))}</td>
|
|
3148
|
+
<td>${escapeHtml(cdxField(run, ["cwd", "workspace", "repo"], "-"))}</td>
|
|
3149
|
+
<td>${runId ? `<button class="viewer-cdx__mode" type="button" data-viewer-cdx-report="${escapeHtml(runId)}">Report</button>` : ""}</td>
|
|
3150
|
+
</tr>
|
|
3151
|
+
`;
|
|
3152
|
+
}).join("");
|
|
2206
3153
|
return `
|
|
2207
3154
|
<div class="viewer-cdx">
|
|
2208
3155
|
${renderCdxModeSwitcher("runs")}
|
|
2209
3156
|
<section class="viewer-cdx__section">
|
|
2210
|
-
<div class="viewer-ci__heading"><h2>Assistant runs</h2><span>${escapeHtml(
|
|
3157
|
+
<div class="viewer-ci__heading"><h2>Assistant runs</h2><span>${escapeHtml(runsSummary)}</span></div>
|
|
2211
3158
|
<div class="viewer-cdx__table-wrap">
|
|
2212
3159
|
<table class="viewer-cdx__table">
|
|
2213
3160
|
<thead><tr><th>RUN</th><th>STATUS</th><th>KIND</th><th>SESSION</th><th>CWD</th><th>REPORT</th></tr></thead>
|
|
@@ -2219,6 +3166,188 @@
|
|
|
2219
3166
|
`;
|
|
2220
3167
|
}
|
|
2221
3168
|
|
|
3169
|
+
function cdxReportMissionOutput(report, run, taskReport) {
|
|
3170
|
+
const parsed = report?.parsed && typeof report.parsed === "object" ? report.parsed : {};
|
|
3171
|
+
const candidates = [
|
|
3172
|
+
report?.missionOutput,
|
|
3173
|
+
report?.mission_output,
|
|
3174
|
+
parsed.missionOutput,
|
|
3175
|
+
parsed.mission_output,
|
|
3176
|
+
run?.missionOutput,
|
|
3177
|
+
run?.mission_output,
|
|
3178
|
+
taskReport?.missionOutput,
|
|
3179
|
+
taskReport?.mission_output
|
|
3180
|
+
];
|
|
3181
|
+
return candidates.find((candidate) => candidate && typeof candidate === "object" && !Array.isArray(candidate)) || null;
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
function cdxCount(value) {
|
|
3185
|
+
if (Array.isArray(value)) {
|
|
3186
|
+
return value.length;
|
|
3187
|
+
}
|
|
3188
|
+
if (value && typeof value === "object") {
|
|
3189
|
+
return objectEntries(value).length;
|
|
3190
|
+
}
|
|
3191
|
+
return value ? 1 : 0;
|
|
3192
|
+
}
|
|
3193
|
+
|
|
3194
|
+
function cdxReportCanCreateRequest(taskReport, missionOutput) {
|
|
3195
|
+
if (taskReport?.kind === "code-review") {
|
|
3196
|
+
return true;
|
|
3197
|
+
}
|
|
3198
|
+
if (cdxCount(taskReport?.findings)) {
|
|
3199
|
+
return true;
|
|
3200
|
+
}
|
|
3201
|
+
return ["findings", "recommendations", "requestFiles", "actionableFixes", "releasePlan"].some((key) => cdxCount(missionOutput?.[key]));
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
function renderCdxReportCards(cards) {
|
|
3205
|
+
return `
|
|
3206
|
+
<div class="viewer-cdx__summary">
|
|
3207
|
+
${cards.map(([label, value]) => `
|
|
3208
|
+
<div class="viewer-cdx__card">
|
|
3209
|
+
<div class="viewer-cdx__label">${escapeHtml(label)}</div>
|
|
3210
|
+
<div class="viewer-cdx__value">${escapeHtml(value)}</div>
|
|
3211
|
+
</div>
|
|
3212
|
+
`).join("")}
|
|
3213
|
+
</div>
|
|
3214
|
+
`;
|
|
3215
|
+
}
|
|
3216
|
+
|
|
3217
|
+
function renderCdxDetailValue(value) {
|
|
3218
|
+
if (Array.isArray(value)) {
|
|
3219
|
+
return `
|
|
3220
|
+
<ol class="viewer-cdx__detail-list">
|
|
3221
|
+
${value.map((item) => `
|
|
3222
|
+
<li>${typeof item === "object" && item !== null
|
|
3223
|
+
? `<pre class="viewer-cdx__detail-code">${escapeHtml(JSON.stringify(item, null, 2))}</pre>`
|
|
3224
|
+
: escapeHtml(String(item))}
|
|
3225
|
+
</li>
|
|
3226
|
+
`).join("")}
|
|
3227
|
+
</ol>
|
|
3228
|
+
`;
|
|
3229
|
+
}
|
|
3230
|
+
if (value && typeof value === "object") {
|
|
3231
|
+
return `<pre class="viewer-cdx__detail-code">${escapeHtml(JSON.stringify(value, null, 2))}</pre>`;
|
|
3232
|
+
}
|
|
3233
|
+
return `<strong>${escapeHtml(String(value))}</strong>`;
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
function renderCdxDetailRow(label, value) {
|
|
3237
|
+
return `
|
|
3238
|
+
<li class="viewer-cdx__row viewer-cdx__row--block">
|
|
3239
|
+
<span>${escapeHtml(label)}</span>
|
|
3240
|
+
<div class="viewer-cdx__detail-value">${renderCdxDetailValue(value)}</div>
|
|
3241
|
+
</li>
|
|
3242
|
+
`;
|
|
3243
|
+
}
|
|
3244
|
+
|
|
3245
|
+
function parseCdxLogJson(content) {
|
|
3246
|
+
const raw = String(content || "").trim();
|
|
3247
|
+
if (!raw) {
|
|
3248
|
+
return null;
|
|
3249
|
+
}
|
|
3250
|
+
try {
|
|
3251
|
+
return { kind: "json", value: JSON.parse(raw) };
|
|
3252
|
+
} catch {
|
|
3253
|
+
// Fall through to JSONL detection.
|
|
3254
|
+
}
|
|
3255
|
+
const lines = raw.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
3256
|
+
if (lines.length < 2) {
|
|
3257
|
+
return null;
|
|
3258
|
+
}
|
|
3259
|
+
const values = [];
|
|
3260
|
+
for (const line of lines) {
|
|
3261
|
+
try {
|
|
3262
|
+
values.push(JSON.parse(line));
|
|
3263
|
+
} catch {
|
|
3264
|
+
return null;
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
return { kind: "jsonl", value: values };
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3270
|
+
function renderCdxStructuredLog(parsed) {
|
|
3271
|
+
if (!parsed) {
|
|
3272
|
+
return "";
|
|
3273
|
+
}
|
|
3274
|
+
const label = parsed.kind === "jsonl" ? `${parsed.value.length} JSONL event(s)` : "JSON document";
|
|
3275
|
+
return `
|
|
3276
|
+
<details class="viewer-cdx__log-structured" open>
|
|
3277
|
+
<summary>Structured preview · ${escapeHtml(label)}</summary>
|
|
3278
|
+
<div class="viewer-cdx__detail-value">${renderCdxDetailValue(parsed.value)}</div>
|
|
3279
|
+
</details>
|
|
3280
|
+
`;
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
function renderCdxLogPreview(payload) {
|
|
3284
|
+
const path = payload?.path || "";
|
|
3285
|
+
const content = payload?.content || "";
|
|
3286
|
+
const truncated = Boolean(payload?.truncated);
|
|
3287
|
+
const parsed = parseCdxLogJson(content);
|
|
3288
|
+
return `
|
|
3289
|
+
<div class="viewer-cdx">
|
|
3290
|
+
<section class="viewer-cdx__section">
|
|
3291
|
+
<div class="viewer-ci__heading"><h2>Log preview</h2><span>${truncated ? "latest output" : "complete file"}</span></div>
|
|
3292
|
+
<div class="viewer-cdx__log-preview">
|
|
3293
|
+
<div class="viewer-cdx__meta">${escapeHtml(path)}</div>
|
|
3294
|
+
${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>' : ""}
|
|
3295
|
+
${renderCdxStructuredLog(parsed)}
|
|
3296
|
+
<details class="viewer-cdx__log-raw"${parsed ? "" : " open"}>
|
|
3297
|
+
<summary>Raw log</summary>
|
|
3298
|
+
<pre class="viewer-cdx__log-content">${escapeHtml(content || "Log is empty.")}</pre>
|
|
3299
|
+
</details>
|
|
3300
|
+
</div>
|
|
3301
|
+
</section>
|
|
3302
|
+
</div>
|
|
3303
|
+
`;
|
|
3304
|
+
}
|
|
3305
|
+
|
|
3306
|
+
function renderCdxMissionOutput(output) {
|
|
3307
|
+
if (!output) {
|
|
3308
|
+
return "";
|
|
3309
|
+
}
|
|
3310
|
+
const rows = [
|
|
3311
|
+
["Summary", output.summary],
|
|
3312
|
+
["Version", output.version],
|
|
3313
|
+
["Validation", output.validationMode],
|
|
3314
|
+
["Blocked", typeof output.blocked === "boolean" ? (output.blocked ? "Yes" : "No") : ""],
|
|
3315
|
+
["Actions", cdxCount(output.actions)],
|
|
3316
|
+
["Findings", cdxCount(output.findings)],
|
|
3317
|
+
["Recommendations", cdxCount(output.recommendations)],
|
|
3318
|
+
["Changed files", cdxCount(output.changedFiles)],
|
|
3319
|
+
["Corpus files", cdxCount(output.corpusFiles)],
|
|
3320
|
+
["Generated files", cdxCount(output.generatedFiles)],
|
|
3321
|
+
["Validation evidence", cdxCount(output.validationEvidence)]
|
|
3322
|
+
].filter(([_label, value]) => value !== undefined && value !== null && value !== "" && value !== 0);
|
|
3323
|
+
const detailKeys = [
|
|
3324
|
+
"actions",
|
|
3325
|
+
"findings",
|
|
3326
|
+
"recommendations",
|
|
3327
|
+
"directFixes",
|
|
3328
|
+
"requestFiles",
|
|
3329
|
+
"actionableFixes",
|
|
3330
|
+
"changedFiles",
|
|
3331
|
+
"corpusFiles",
|
|
3332
|
+
"generatedFiles",
|
|
3333
|
+
"validationEvidence",
|
|
3334
|
+
"releasePlan"
|
|
3335
|
+
];
|
|
3336
|
+
const details = detailKeys
|
|
3337
|
+
.filter((key) => cdxCount(output[key]))
|
|
3338
|
+
.map((key) => renderCdxDetailRow(cdxLabel(key), output[key]))
|
|
3339
|
+
.join("");
|
|
3340
|
+
return `
|
|
3341
|
+
<section class="viewer-cdx__section">
|
|
3342
|
+
<div class="viewer-ci__heading"><h2>Mission output</h2><span>${escapeHtml(rows.length)} signals</span></div>
|
|
3343
|
+
<ul class="viewer-cdx__list">
|
|
3344
|
+
${rows.map(([label, value]) => renderCdxDetailRow(label, value)).join("") || '<li class="viewer-cdx__empty">No structured mission output was reported.</li>'}
|
|
3345
|
+
</ul>
|
|
3346
|
+
${details ? `<ul class="viewer-cdx__list">${details}</ul>` : ""}
|
|
3347
|
+
</section>
|
|
3348
|
+
`;
|
|
3349
|
+
}
|
|
3350
|
+
|
|
2222
3351
|
function renderCdxReport(payload) {
|
|
2223
3352
|
if (!payload || payload.state !== "ok" || !payload.report) {
|
|
2224
3353
|
return `
|
|
@@ -2231,24 +3360,49 @@
|
|
|
2231
3360
|
const report = payload.report || {};
|
|
2232
3361
|
const run = report.run || {};
|
|
2233
3362
|
const taskReport = report.task_report || {};
|
|
3363
|
+
const runError = report.error || run.error || {};
|
|
3364
|
+
const artifacts = report.artifacts || run.artifacts || {};
|
|
2234
3365
|
const findings = Array.isArray(taskReport.findings) ? taskReport.findings : [];
|
|
3366
|
+
const missionOutput = cdxReportMissionOutput(report, run, taskReport);
|
|
2235
3367
|
const findingRows = findings.map((finding, index) => {
|
|
2236
3368
|
const location = [finding.path || finding.file || "", finding.line || ""].filter(Boolean).join(":") || "-";
|
|
2237
3369
|
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
3370
|
}).join("");
|
|
2239
|
-
const canCreate = taskReport
|
|
3371
|
+
const canCreate = cdxReportCanCreateRequest(taskReport, missionOutput);
|
|
2240
3372
|
return `
|
|
2241
3373
|
<div class="viewer-cdx">
|
|
2242
3374
|
${renderCdxModeSwitcher("runs")}
|
|
2243
3375
|
<section class="viewer-cdx__section">
|
|
2244
|
-
<div class="viewer-ci__heading
|
|
3376
|
+
<div class="viewer-ci__heading viewer-ci__heading--actions">
|
|
3377
|
+
<div><h2>Run report</h2><span>${escapeHtml(run.status || "unknown")}</span></div>
|
|
3378
|
+
<button class="viewer-cdx__mode" type="button" data-viewer-cdx-back-runs>Back to runs</button>
|
|
3379
|
+
</div>
|
|
3380
|
+
${renderCdxReportCards([
|
|
3381
|
+
["Status", run.status || "unknown"],
|
|
3382
|
+
["Kind", taskReport.kind || run.kind || "assistant"],
|
|
3383
|
+
["Findings", String(findings.length)],
|
|
3384
|
+
["Artifacts", String(objectEntries(artifacts).length)]
|
|
3385
|
+
])}
|
|
2245
3386
|
<ul class="viewer-cdx__list">
|
|
2246
3387
|
<li class="viewer-cdx__row"><span>Run</span><strong>${escapeHtml(run.run_id || taskReport.run_id || "-")}</strong></li>
|
|
2247
3388
|
<li class="viewer-cdx__row"><span>Kind</span><strong>${escapeHtml(taskReport.kind || run.kind || "assistant")}</strong></li>
|
|
2248
|
-
|
|
3389
|
+
${renderCdxDetailRow("Summary", taskReport.summary || "No summary reported.")}
|
|
2249
3390
|
</ul>
|
|
2250
3391
|
${canCreate ? `<button class="btn" type="button" data-viewer-cdx-create-request="${escapeHtml(run.run_id || taskReport.run_id || "")}">Create Logics request</button>` : ""}
|
|
2251
3392
|
</section>
|
|
3393
|
+
${renderCdxMissionOutput(missionOutput)}
|
|
3394
|
+
${objectEntries(runError).length ? `
|
|
3395
|
+
<section class="viewer-cdx__section">
|
|
3396
|
+
<div class="viewer-ci__heading"><h2>Run signal</h2><span>${escapeHtml(runError.code || "reported")}</span></div>
|
|
3397
|
+
<ul class="viewer-cdx__list">${renderCdxObjectRows(runError, "No run signal reported.")}</ul>
|
|
3398
|
+
</section>
|
|
3399
|
+
` : ""}
|
|
3400
|
+
${objectEntries(artifacts).length ? `
|
|
3401
|
+
<section class="viewer-cdx__section">
|
|
3402
|
+
<div class="viewer-ci__heading"><h2>Artifacts</h2><span>${escapeHtml(objectEntries(artifacts).length)} paths</span></div>
|
|
3403
|
+
<ul class="viewer-cdx__list">${renderCdxArtifactRows(artifacts, "No artifact paths reported.")}</ul>
|
|
3404
|
+
</section>
|
|
3405
|
+
` : ""}
|
|
2252
3406
|
<section class="viewer-cdx__section">
|
|
2253
3407
|
<div class="viewer-ci__heading"><h2>Findings</h2><span>${escapeHtml(findings.length)} reported</span></div>
|
|
2254
3408
|
<ul class="viewer-cdx__list">${findingRows || '<li class="viewer-cdx__empty">No structured findings reported.</li>'}</ul>
|
|
@@ -2285,11 +3439,131 @@
|
|
|
2285
3439
|
if (!response.ok || !data.ok) {
|
|
2286
3440
|
throw new Error(data.error || "Unable to load CDX status.");
|
|
2287
3441
|
}
|
|
3442
|
+
const nextCdxSignature = runtimeStatusSignature(data.payload);
|
|
3443
|
+
if (options.skipUnchanged && !options.force && latestCdxStatusSignature && nextCdxSignature === latestCdxStatusSignature) {
|
|
3444
|
+
updateMainCdxBadge(data.payload);
|
|
3445
|
+
if (!options.silent) {
|
|
3446
|
+
setMeta(`Checked CDX status just now · no changes (${new Date().toLocaleTimeString()})`);
|
|
3447
|
+
}
|
|
3448
|
+
return;
|
|
3449
|
+
}
|
|
3450
|
+
latestCdxStatusSignature = nextCdxSignature;
|
|
3451
|
+
latestCdxStatusPayload = data.payload;
|
|
2288
3452
|
updateMainCdxBadge(data.payload);
|
|
2289
3453
|
setDocument("CDX status", renderCdxStatus(data.payload));
|
|
2290
3454
|
setMeta(options.silent ? "CDX status refreshed." : "CDX status loaded.");
|
|
2291
3455
|
}
|
|
2292
3456
|
|
|
3457
|
+
async function showCdxMissions(options = {}) {
|
|
3458
|
+
if (!isCapabilityAvailable("cdx")) {
|
|
3459
|
+
const message = capabilityMessage("cdx", "CDX is not available for this project.");
|
|
3460
|
+
setDocument("CDX missions", renderCdxMissions({ state: capability("cdx").state, message }));
|
|
3461
|
+
setMeta(message);
|
|
3462
|
+
return;
|
|
3463
|
+
}
|
|
3464
|
+
if (!options.silent) {
|
|
3465
|
+
setMeta("Loading CDX missions...");
|
|
3466
|
+
}
|
|
3467
|
+
const response = await fetch("/api/cdx-status");
|
|
3468
|
+
let data = {};
|
|
3469
|
+
try {
|
|
3470
|
+
data = await response.json();
|
|
3471
|
+
} catch {
|
|
3472
|
+
data = {};
|
|
3473
|
+
}
|
|
3474
|
+
if (!response.ok || !data.ok) {
|
|
3475
|
+
throw new Error(data.error || "Unable to load CDX mission status.");
|
|
3476
|
+
}
|
|
3477
|
+
latestCdxMissionState.statusPayload = data.payload;
|
|
3478
|
+
const sessions = cdxSessions(data.payload?.status || {});
|
|
3479
|
+
if (!latestCdxMissionState.sessionId && sessions.length) {
|
|
3480
|
+
latestCdxMissionState.sessionId = cdxField(sessions[0], ["id", "name", "session_name", "value"], "");
|
|
3481
|
+
}
|
|
3482
|
+
updateMainCdxBadge(data.payload);
|
|
3483
|
+
setDocument("CDX missions", renderCdxMissions(data.payload, latestCdxMissionState.planPayload, latestCdxMissionState.runPayload, latestCdxMissionState.applyPayload));
|
|
3484
|
+
setMeta(options.silent ? "CDX missions refreshed." : "CDX missions loaded.");
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
async function previewCdxMission() {
|
|
3488
|
+
setMeta("Preparing CDX mission preview...");
|
|
3489
|
+
const response = await fetch("/api/cdx-mission-plan", {
|
|
3490
|
+
method: "POST",
|
|
3491
|
+
headers: { "Content-Type": "application/json" },
|
|
3492
|
+
body: JSON.stringify(selectedCdxMissionRequest())
|
|
3493
|
+
});
|
|
3494
|
+
const data = await response.json();
|
|
3495
|
+
if (!response.ok || !data.ok) {
|
|
3496
|
+
throw new Error(data.error || "Unable to preview CDX mission.");
|
|
3497
|
+
}
|
|
3498
|
+
latestCdxMissionState.planPayload = data.payload;
|
|
3499
|
+
latestCdxMissionState.runPayload = null;
|
|
3500
|
+
latestCdxMissionState.applyPayload = null;
|
|
3501
|
+
if (data.payload?.plan?.sessionId) {
|
|
3502
|
+
latestCdxMissionState.sessionId = data.payload.plan.sessionId;
|
|
3503
|
+
}
|
|
3504
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload || data.payload?.status, data.payload, null, null));
|
|
3505
|
+
setMeta(data.payload?.state === "ok" ? "CDX mission preview ready." : (data.payload?.message || "CDX mission preview failed."));
|
|
3506
|
+
}
|
|
3507
|
+
|
|
3508
|
+
async function launchCdxMission() {
|
|
3509
|
+
setMeta("Launching CDX mission...");
|
|
3510
|
+
const request = selectedCdxMissionRequest();
|
|
3511
|
+
const plan = latestCdxMissionState.planPayload?.plan || null;
|
|
3512
|
+
const pendingPayload = {
|
|
3513
|
+
state: "running",
|
|
3514
|
+
message: "CDX mission is running. You can keep using the viewer; this panel will update when it completes.",
|
|
3515
|
+
plan,
|
|
3516
|
+
run: {
|
|
3517
|
+
runId: "pending",
|
|
3518
|
+
returnCode: "pending",
|
|
3519
|
+
pending: true,
|
|
3520
|
+
usage: { available: false, message: "Still running." },
|
|
3521
|
+
stdout: "",
|
|
3522
|
+
stderr: ""
|
|
3523
|
+
}
|
|
3524
|
+
};
|
|
3525
|
+
latestCdxMissionState.runPayload = pendingPayload;
|
|
3526
|
+
latestCdxMissionState.applyPayload = null;
|
|
3527
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload, latestCdxMissionState.planPayload, pendingPayload, null));
|
|
3528
|
+
const response = await fetch("/api/cdx-mission-run", {
|
|
3529
|
+
method: "POST",
|
|
3530
|
+
headers: { "Content-Type": "application/json" },
|
|
3531
|
+
body: JSON.stringify(request)
|
|
3532
|
+
});
|
|
3533
|
+
const data = await response.json();
|
|
3534
|
+
if (!response.ok || !data.ok) {
|
|
3535
|
+
throw new Error(data.error || "Unable to launch CDX mission.");
|
|
3536
|
+
}
|
|
3537
|
+
latestCdxMissionState.planPayload = { state: data.payload?.state === "ok" ? "ok" : data.payload?.state, message: data.payload?.message || "", plan: data.payload?.plan };
|
|
3538
|
+
latestCdxMissionState.runPayload = data.payload;
|
|
3539
|
+
latestCdxMissionState.applyPayload = null;
|
|
3540
|
+
if (isCdxMissionsOpen()) {
|
|
3541
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload, latestCdxMissionState.planPayload, data.payload, null));
|
|
3542
|
+
}
|
|
3543
|
+
setMeta(data.payload?.state === "ok" ? "CDX mission launched." : (data.payload?.message || "CDX mission failed."));
|
|
3544
|
+
}
|
|
3545
|
+
|
|
3546
|
+
async function applyCdxMissionPlan() {
|
|
3547
|
+
const actions = latestCdxMissionState.runPayload?.run?.parsed?.actions;
|
|
3548
|
+
if (!Array.isArray(actions) || !actions.length) {
|
|
3549
|
+
setMeta("No corpus actions to apply.");
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
setMeta("Applying allowed corpus actions...");
|
|
3553
|
+
const response = await fetch("/api/cdx-mission-apply-plan", {
|
|
3554
|
+
method: "POST",
|
|
3555
|
+
headers: { "Content-Type": "application/json" },
|
|
3556
|
+
body: JSON.stringify({ actions })
|
|
3557
|
+
});
|
|
3558
|
+
const data = await response.json();
|
|
3559
|
+
if (!response.ok || !data.ok) {
|
|
3560
|
+
throw new Error(data.error || "Unable to apply corpus plan.");
|
|
3561
|
+
}
|
|
3562
|
+
latestCdxMissionState.applyPayload = data.payload;
|
|
3563
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload, latestCdxMissionState.planPayload, latestCdxMissionState.runPayload, data.payload));
|
|
3564
|
+
setMeta(data.payload?.state === "ok" ? "Corpus actions applied." : (data.payload?.message || "Corpus apply failed."));
|
|
3565
|
+
}
|
|
3566
|
+
|
|
2293
3567
|
async function showCdxRuns(options = {}) {
|
|
2294
3568
|
if (!isCapabilityAvailable("cdx")) {
|
|
2295
3569
|
const message = capabilityMessage("cdx", "CDX is not available for this project.");
|
|
@@ -2325,9 +3599,30 @@
|
|
|
2325
3599
|
throw new Error(data.error || "Unable to load CDX report.");
|
|
2326
3600
|
}
|
|
2327
3601
|
setDocument("CDX run report", renderCdxReport(data.payload));
|
|
3602
|
+
cdxCloseTarget = { type: "cdx-runs" };
|
|
2328
3603
|
setMeta("CDX report loaded.");
|
|
2329
3604
|
}
|
|
2330
3605
|
|
|
3606
|
+
async function openCdxArtifact(path) {
|
|
3607
|
+
if (!path) {
|
|
3608
|
+
return;
|
|
3609
|
+
}
|
|
3610
|
+
setMeta("Loading CDX log...");
|
|
3611
|
+
const response = await fetch("/api/file-preview", {
|
|
3612
|
+
method: "POST",
|
|
3613
|
+
headers: { "Content-Type": "application/json" },
|
|
3614
|
+
body: JSON.stringify({ path })
|
|
3615
|
+
});
|
|
3616
|
+
const data = await response.json();
|
|
3617
|
+
if (!response.ok || !data.ok) {
|
|
3618
|
+
throw new Error(data.error || "Unable to load CDX artifact.");
|
|
3619
|
+
}
|
|
3620
|
+
const reportSnapshot = currentDocumentSnapshot("CDX run report");
|
|
3621
|
+
setDocument(data.payload?.name ? `CDX log · ${data.payload.name}` : "CDX log", renderCdxLogPreview(data.payload));
|
|
3622
|
+
cdxCloseTarget = { type: "cdx-report", title: reportSnapshot.title, html: reportSnapshot.html };
|
|
3623
|
+
setMeta(`Loaded ${data.payload?.path || path}.`);
|
|
3624
|
+
}
|
|
3625
|
+
|
|
2331
3626
|
async function createRequestFromCdxReport(runId) {
|
|
2332
3627
|
if (!runId) {
|
|
2333
3628
|
return;
|
|
@@ -2370,11 +3665,26 @@
|
|
|
2370
3665
|
const run = payload.run && typeof payload.run === "object" ? payload.run : null;
|
|
2371
3666
|
const jobs = Array.isArray(payload.jobs) ? payload.jobs : [];
|
|
2372
3667
|
const state = payload.badgeState || run?.badgeState || payload.state || "unknown";
|
|
3668
|
+
const matchLabel = run?.matchSource === "head-active"
|
|
3669
|
+
? "Current HEAD running"
|
|
3670
|
+
: run?.matchSource === "head-failing"
|
|
3671
|
+
? "Current HEAD failing"
|
|
3672
|
+
: run?.matchSource === "head-cancelled"
|
|
3673
|
+
? "Current HEAD cancelled"
|
|
3674
|
+
: run?.matchSource === "head-unknown"
|
|
3675
|
+
? "Current HEAD unknown"
|
|
3676
|
+
: run?.matchSource === "head"
|
|
3677
|
+
? "Current HEAD"
|
|
3678
|
+
: run?.matchSource === "branch-active"
|
|
3679
|
+
? "Branch running"
|
|
3680
|
+
: run?.matchSource === "branch-failing"
|
|
3681
|
+
? "Branch failing"
|
|
3682
|
+
: "Latest branch run";
|
|
2373
3683
|
const cards = renderMetricCards([
|
|
2374
3684
|
["State", ciBadgeLabel(state)],
|
|
2375
3685
|
["Branch", run?.branch || payload.branch || "Unknown"],
|
|
2376
3686
|
["Commit", (run?.headSha || payload.headSha || "").slice(0, 7) || "Unknown"],
|
|
2377
|
-
["Match",
|
|
3687
|
+
["Match", matchLabel]
|
|
2378
3688
|
]);
|
|
2379
3689
|
const runUrl = run?.htmlUrl ? `<a class="viewer-ci__link" href="${escapeHtml(run.htmlUrl)}" target="_blank" rel="noreferrer">Open in GitHub</a>` : "";
|
|
2380
3690
|
const runRows = run ? [
|
|
@@ -2444,6 +3754,15 @@
|
|
|
2444
3754
|
if (!response.ok || !data.ok) {
|
|
2445
3755
|
throw new Error(data.error || "Unable to load CI status.");
|
|
2446
3756
|
}
|
|
3757
|
+
const nextCiSignature = runtimeStatusSignature(data.payload);
|
|
3758
|
+
if (options.skipUnchanged && !options.force && latestCiStatusSignature && nextCiSignature === latestCiStatusSignature) {
|
|
3759
|
+
updateMainCiBadge(data.payload);
|
|
3760
|
+
if (!options.silent) {
|
|
3761
|
+
setMeta(`Checked CI status just now · no changes (${new Date().toLocaleTimeString()})`);
|
|
3762
|
+
}
|
|
3763
|
+
return;
|
|
3764
|
+
}
|
|
3765
|
+
latestCiStatusSignature = nextCiSignature;
|
|
2447
3766
|
updateMainCiBadge(data.payload);
|
|
2448
3767
|
setDocument("CI status", renderCiStatus(data.payload));
|
|
2449
3768
|
setMeta(options.silent ? "CI status refreshed." : "CI status loaded.");
|
|
@@ -2463,17 +3782,20 @@
|
|
|
2463
3782
|
const deletedCount = Number(counts.deleted || 0);
|
|
2464
3783
|
const renamedCount = Number(counts.renamed || 0);
|
|
2465
3784
|
const untrackedCount = Number(counts.untracked || 0);
|
|
2466
|
-
const
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
3785
|
+
const cards = [
|
|
3786
|
+
renderGitSummaryCard("Branch", payload.branch || "HEAD"),
|
|
3787
|
+
renderGitSummaryCard("Tracking", payload.tracking || "None"),
|
|
3788
|
+
renderGitSummarySegments("Ahead / Behind", [
|
|
3789
|
+
["Ahead", payload.ahead || 0],
|
|
3790
|
+
["Behind", payload.behind || 0]
|
|
3791
|
+
]),
|
|
3792
|
+
renderGitSummaryCard("State", payload.clean ? "Clean" : "Dirty"),
|
|
3793
|
+
renderGitSummarySegments("Files", [
|
|
3794
|
+
["Staged", stagedCount],
|
|
3795
|
+
["Worktree", modifiedCount + deletedCount + renamedCount],
|
|
3796
|
+
["Untracked", untrackedCount]
|
|
3797
|
+
])
|
|
3798
|
+
].join("");
|
|
2477
3799
|
const groupDefs = [
|
|
2478
3800
|
["staged", "Staged", "staged"],
|
|
2479
3801
|
["modified", "Modified", "worktree"],
|
|
@@ -2486,7 +3808,7 @@
|
|
|
2486
3808
|
["staged", "Staged", stagedCount],
|
|
2487
3809
|
["worktree", "Worktree", modifiedCount + deletedCount + renamedCount],
|
|
2488
3810
|
["untracked", "Untracked", untrackedCount],
|
|
2489
|
-
["history", "History",
|
|
3811
|
+
["history", "History", formatGitHistoryCount(payload)],
|
|
2490
3812
|
["remote", "Remote", payload.tracking ? 1 : 0]
|
|
2491
3813
|
];
|
|
2492
3814
|
const domains = domainDefs.map(([key, label, count], index) => `
|
|
@@ -2528,6 +3850,7 @@
|
|
|
2528
3850
|
const untrackedSections = renderFileSections(["untracked"]);
|
|
2529
3851
|
const clean = payload.clean ? '<p class="viewer-git__state">Working tree clean.</p>' : "";
|
|
2530
3852
|
const recentCommits = Array.isArray(payload.recentCommits) ? payload.recentCommits : [];
|
|
3853
|
+
const historyCount = formatGitHistoryCount(payload);
|
|
2531
3854
|
const renderGitHistoryReveal = (hiddenCount) => {
|
|
2532
3855
|
if (hiddenCount <= 0) {
|
|
2533
3856
|
return "";
|
|
@@ -2565,7 +3888,7 @@
|
|
|
2565
3888
|
return `
|
|
2566
3889
|
<div class="viewer-git">
|
|
2567
3890
|
<div class="viewer-git__summary">${cards}</div>
|
|
2568
|
-
<div class="viewer-git__workspace">
|
|
3891
|
+
<div class="viewer-git__workspace has-diff-detail">
|
|
2569
3892
|
<nav class="viewer-git__domains" aria-label="Git domains">${domains}</nav>
|
|
2570
3893
|
<div class="viewer-git__content" aria-label="Git domain content">
|
|
2571
3894
|
<section class="viewer-git__panel" data-viewer-git-panel="changes">
|
|
@@ -2586,7 +3909,7 @@
|
|
|
2586
3909
|
${untrackedSections || '<p class="viewer-git__state">No untracked files.</p>'}
|
|
2587
3910
|
</section>
|
|
2588
3911
|
<section class="viewer-git__panel" data-viewer-git-panel="history" hidden>
|
|
2589
|
-
<header class="viewer-git__panel-header"><span>History</span><strong>${escapeHtml(
|
|
3912
|
+
<header class="viewer-git__panel-header"><span>History</span><strong>${escapeHtml(historyCount)} commits</strong></header>
|
|
2590
3913
|
${history}
|
|
2591
3914
|
</section>
|
|
2592
3915
|
<section class="viewer-git__panel" data-viewer-git-panel="remote" hidden>
|
|
@@ -2594,7 +3917,7 @@
|
|
|
2594
3917
|
${remote}
|
|
2595
3918
|
</section>
|
|
2596
3919
|
</div>
|
|
2597
|
-
<section class="viewer-git__detail" aria-label="Git diff">
|
|
3920
|
+
<section class="viewer-git__detail" aria-label="Git diff" data-viewer-git-detail>
|
|
2598
3921
|
<div class="viewer-git__detail-title">Diff preview</div>
|
|
2599
3922
|
<div class="viewer-git__diff" data-viewer-git-diff>Select a changed file to preview its diff.</div>
|
|
2600
3923
|
</section>
|
|
@@ -2603,6 +3926,11 @@
|
|
|
2603
3926
|
`;
|
|
2604
3927
|
}
|
|
2605
3928
|
|
|
3929
|
+
function formatGitHistoryCount(payload) {
|
|
3930
|
+
const count = Array.isArray(payload?.recentCommits) ? payload.recentCommits.length : (payload?.latestCommit ? 1 : 0);
|
|
3931
|
+
return `${count}${payload?.recentCommitsHasMore ? "+" : ""}`;
|
|
3932
|
+
}
|
|
3933
|
+
|
|
2606
3934
|
function setActiveGitFile(button) {
|
|
2607
3935
|
document.querySelectorAll("[data-viewer-git-file]").forEach((node) => {
|
|
2608
3936
|
if (node instanceof HTMLElement) {
|
|
@@ -2636,12 +3964,16 @@
|
|
|
2636
3964
|
|
|
2637
3965
|
async function loadGitDiff(path, cached, button = null) {
|
|
2638
3966
|
const diffPanel = document.querySelector("[data-viewer-git-diff]");
|
|
3967
|
+
const detailTitle = document.querySelector("[data-viewer-git-detail] .viewer-git__detail-title");
|
|
2639
3968
|
if (!(diffPanel instanceof HTMLElement) || !path) {
|
|
2640
3969
|
return;
|
|
2641
3970
|
}
|
|
2642
3971
|
if (button instanceof HTMLElement) {
|
|
2643
3972
|
setActiveGitFile(button);
|
|
2644
3973
|
}
|
|
3974
|
+
if (detailTitle instanceof HTMLElement) {
|
|
3975
|
+
detailTitle.textContent = "Diff preview";
|
|
3976
|
+
}
|
|
2645
3977
|
diffPanel.textContent = "Loading diff...";
|
|
2646
3978
|
const params = new URLSearchParams({ path });
|
|
2647
3979
|
if (cached) {
|
|
@@ -2654,12 +3986,38 @@
|
|
|
2654
3986
|
diffPanel.textContent = payload.message || data.error || "Unable to load diff.";
|
|
2655
3987
|
return;
|
|
2656
3988
|
}
|
|
2657
|
-
const content = payload.diff ||
|
|
3989
|
+
const content = payload.diff || "";
|
|
3990
|
+
if (!content.trim()) {
|
|
3991
|
+
await loadGitFilePreview(path, diffPanel, detailTitle);
|
|
3992
|
+
return;
|
|
3993
|
+
}
|
|
2658
3994
|
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
3995
|
}
|
|
2660
3996
|
|
|
3997
|
+
async function loadGitFilePreview(path, diffPanel, detailTitle = null) {
|
|
3998
|
+
if (detailTitle instanceof HTMLElement) {
|
|
3999
|
+
detailTitle.textContent = "File preview";
|
|
4000
|
+
}
|
|
4001
|
+
diffPanel.textContent = "Loading file preview...";
|
|
4002
|
+
const response = await fetch(`/api/git-file-preview?${new URLSearchParams({ path }).toString()}`);
|
|
4003
|
+
const data = await response.json();
|
|
4004
|
+
const payload = data.payload || {};
|
|
4005
|
+
if (!response.ok || !data.ok) {
|
|
4006
|
+
diffPanel.textContent = data.error || "Unable to load file preview.";
|
|
4007
|
+
return;
|
|
4008
|
+
}
|
|
4009
|
+
if (payload.state !== "ok") {
|
|
4010
|
+
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>`;
|
|
4011
|
+
return;
|
|
4012
|
+
}
|
|
4013
|
+
const content = payload.content || "";
|
|
4014
|
+
diffPanel.innerHTML = `<div class="viewer-git__diff-meta">${escapeHtml(payload.path || path)} · file preview${payload.truncated ? " · truncated" : ""}</div><pre><code>${renderGitDiffPreview(content)}</code></pre>`;
|
|
4015
|
+
}
|
|
4016
|
+
|
|
2661
4017
|
function applyGitDomain(domain) {
|
|
2662
4018
|
const selected = domain || "changes";
|
|
4019
|
+
const diffDomains = new Set(["changes", "staged", "worktree", "untracked"]);
|
|
4020
|
+
const showDiffDetail = diffDomains.has(selected);
|
|
2663
4021
|
document.querySelectorAll(".viewer-git__domain[data-viewer-git-domain]").forEach((node) => {
|
|
2664
4022
|
if (node instanceof HTMLElement) {
|
|
2665
4023
|
const active = node.getAttribute("data-viewer-git-domain") === selected;
|
|
@@ -2672,6 +4030,16 @@
|
|
|
2672
4030
|
node.hidden = node.getAttribute("data-viewer-git-panel") !== selected;
|
|
2673
4031
|
}
|
|
2674
4032
|
});
|
|
4033
|
+
document.querySelectorAll(".viewer-git__workspace").forEach((node) => {
|
|
4034
|
+
if (node instanceof HTMLElement) {
|
|
4035
|
+
node.classList.toggle("has-diff-detail", showDiffDetail);
|
|
4036
|
+
}
|
|
4037
|
+
});
|
|
4038
|
+
document.querySelectorAll("[data-viewer-git-detail]").forEach((node) => {
|
|
4039
|
+
if (node instanceof HTMLElement) {
|
|
4040
|
+
node.hidden = !showDiffDetail;
|
|
4041
|
+
}
|
|
4042
|
+
});
|
|
2675
4043
|
}
|
|
2676
4044
|
|
|
2677
4045
|
function currentGitViewState() {
|
|
@@ -2721,6 +4089,16 @@
|
|
|
2721
4089
|
if (!response.ok || !data.ok) {
|
|
2722
4090
|
throw new Error(data.error || "Unable to load Git status.");
|
|
2723
4091
|
}
|
|
4092
|
+
const nextGitSignature = gitStatusSignature(data.payload);
|
|
4093
|
+
if (options.skipUnchanged && !options.force && latestGitStatusSignature && nextGitSignature === latestGitStatusSignature) {
|
|
4094
|
+
setGitBadgeCountsFromPayload(data.payload, { updateMain: false });
|
|
4095
|
+
updateMainGitBadges();
|
|
4096
|
+
if (!options.silent) {
|
|
4097
|
+
setMeta(`Checked Git status just now · no changes (${new Date().toLocaleTimeString()})`);
|
|
4098
|
+
}
|
|
4099
|
+
return;
|
|
4100
|
+
}
|
|
4101
|
+
latestGitStatusSignature = nextGitSignature;
|
|
2724
4102
|
setGitBadgeCountsFromPayload(data.payload, { updateMain: false });
|
|
2725
4103
|
updateMainGitBadges();
|
|
2726
4104
|
setDocument("Git status", renderGitStatus(data.payload));
|
|
@@ -2744,7 +4122,7 @@
|
|
|
2744
4122
|
return;
|
|
2745
4123
|
}
|
|
2746
4124
|
if (message.type === "refresh") {
|
|
2747
|
-
refreshViewer("POST").catch((error) => setMeta(error.message));
|
|
4125
|
+
refreshViewer("POST", { force: Boolean(message.force) }).catch((error) => setMeta(error.message));
|
|
2748
4126
|
return;
|
|
2749
4127
|
}
|
|
2750
4128
|
if (message.type === "bootstrap-logics") {
|
|
@@ -2782,7 +4160,13 @@
|
|
|
2782
4160
|
[document.getElementById("viewer-insights")].forEach((button) => {
|
|
2783
4161
|
button?.addEventListener("click", () => {
|
|
2784
4162
|
setRefreshMenuOpen(false);
|
|
2785
|
-
|
|
4163
|
+
withPrimaryAction("insights", "Loading insights", showCorpusInsights);
|
|
4164
|
+
});
|
|
4165
|
+
});
|
|
4166
|
+
[workspaceButton()].forEach((button) => {
|
|
4167
|
+
button?.addEventListener("click", () => {
|
|
4168
|
+
setRefreshMenuOpen(false);
|
|
4169
|
+
withPrimaryAction("workspace", "Loading Explorer", showWorkspace);
|
|
2786
4170
|
});
|
|
2787
4171
|
});
|
|
2788
4172
|
const autoControl = autoRefreshControl();
|
|
@@ -2825,30 +4209,30 @@
|
|
|
2825
4209
|
if (!(element instanceof HTMLElement)) {
|
|
2826
4210
|
return;
|
|
2827
4211
|
}
|
|
2828
|
-
element.addEventListener("click", () => {
|
|
4212
|
+
element.addEventListener("click", (event) => {
|
|
2829
4213
|
setRefreshMenuOpen(false);
|
|
2830
|
-
|
|
4214
|
+
withPrimaryAction("refresh", "Refreshing", () => refreshViewer("POST", { force: Boolean(event.shiftKey) }));
|
|
2831
4215
|
});
|
|
2832
4216
|
});
|
|
2833
4217
|
document.getElementById("viewer-health")?.addEventListener("click", () => {
|
|
2834
4218
|
setRefreshMenuOpen(false);
|
|
2835
|
-
|
|
4219
|
+
withPrimaryAction("health", "Checking health", showHealth);
|
|
2836
4220
|
});
|
|
2837
4221
|
document.getElementById("viewer-git")?.addEventListener("click", () => {
|
|
2838
|
-
|
|
4222
|
+
withPrimaryAction("git", "Checking Git status", showGitStatus);
|
|
2839
4223
|
});
|
|
2840
4224
|
ciButton()?.addEventListener("click", () => {
|
|
2841
|
-
|
|
4225
|
+
withPrimaryAction("ci", "Checking CI status", showCiStatus);
|
|
2842
4226
|
});
|
|
2843
4227
|
document.getElementById("viewer-cdx")?.addEventListener("click", () => {
|
|
2844
|
-
|
|
4228
|
+
withPrimaryAction("cdx", "Checking CDX status", showCdxStatus);
|
|
2845
4229
|
});
|
|
2846
4230
|
repoPill()?.addEventListener("click", () => {
|
|
2847
4231
|
const menu = projectMenu();
|
|
2848
4232
|
setProjectMenuOpen(Boolean(menu?.hidden));
|
|
2849
4233
|
});
|
|
2850
4234
|
repoFolderButton()?.addEventListener("click", () => {
|
|
2851
|
-
|
|
4235
|
+
withPrimaryAction("open-repo-folder", "Opening repository folder", openRepositoryFolder);
|
|
2852
4236
|
});
|
|
2853
4237
|
activityClearControl()?.addEventListener("click", () => {
|
|
2854
4238
|
clearActivityHistory();
|
|
@@ -2873,9 +4257,50 @@
|
|
|
2873
4257
|
const editButton = editDocumentButton();
|
|
2874
4258
|
if (editButton instanceof HTMLElement) {
|
|
2875
4259
|
editButton.addEventListener("click", () => {
|
|
2876
|
-
|
|
4260
|
+
withPrimaryAction("edit-document", "Opening document", () => editDocument(selectedItem()));
|
|
2877
4261
|
});
|
|
2878
4262
|
}
|
|
4263
|
+
document.addEventListener("change", (event) => {
|
|
4264
|
+
const sessionTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-session]") : null;
|
|
4265
|
+
const cdxInputTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-input]") : null;
|
|
4266
|
+
const cdxColumnTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-column]") : null;
|
|
4267
|
+
const cdxProviderTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-provider]") : null;
|
|
4268
|
+
if (sessionTarget instanceof HTMLSelectElement) {
|
|
4269
|
+
latestCdxMissionState.sessionId = sessionTarget.value || "";
|
|
4270
|
+
latestCdxMissionState.planPayload = null;
|
|
4271
|
+
latestCdxMissionState.runPayload = null;
|
|
4272
|
+
latestCdxMissionState.applyPayload = null;
|
|
4273
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload));
|
|
4274
|
+
}
|
|
4275
|
+
if (cdxInputTarget instanceof HTMLInputElement || cdxInputTarget instanceof HTMLTextAreaElement) {
|
|
4276
|
+
const key = cdxInputTarget.getAttribute("data-viewer-cdx-input") || "";
|
|
4277
|
+
if (key) {
|
|
4278
|
+
latestCdxMissionState.missionInputs[key] = cdxInputTarget instanceof HTMLInputElement && cdxInputTarget.type === "checkbox" ? (cdxInputTarget.checked ? "true" : "false") : (cdxInputTarget.value || "");
|
|
4279
|
+
latestCdxMissionState.planPayload = null;
|
|
4280
|
+
latestCdxMissionState.runPayload = null;
|
|
4281
|
+
latestCdxMissionState.applyPayload = null;
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
if (cdxColumnTarget instanceof HTMLInputElement) {
|
|
4285
|
+
persistCdxColumnVisibility(cdxColumnTarget.getAttribute("data-viewer-cdx-column") || "", cdxColumnTarget.checked);
|
|
4286
|
+
rerenderCdxStatusFromPreferences();
|
|
4287
|
+
}
|
|
4288
|
+
if (cdxProviderTarget instanceof HTMLInputElement) {
|
|
4289
|
+
const provider = cdxProviderTarget.getAttribute("data-viewer-cdx-provider") || "";
|
|
4290
|
+
const status = latestCdxStatusPayload?.status || {};
|
|
4291
|
+
const allProviders = cdxKnownProviders(status, cdxProviders(status), cdxSessions(status));
|
|
4292
|
+
const current = cdxProviderFilterPreference();
|
|
4293
|
+
const selected = new Set(current.mode === "subset" ? current.selected : allProviders);
|
|
4294
|
+
if (cdxProviderTarget.checked) {
|
|
4295
|
+
selected.add(provider);
|
|
4296
|
+
} else {
|
|
4297
|
+
selected.delete(provider);
|
|
4298
|
+
}
|
|
4299
|
+
const nextSelected = Array.from(selected).filter((entry) => allProviders.includes(entry));
|
|
4300
|
+
persistCdxProviderFilter(nextSelected.length === allProviders.length ? { mode: "all", selected: [] } : { mode: "subset", selected: nextSelected });
|
|
4301
|
+
rerenderCdxStatusFromPreferences();
|
|
4302
|
+
}
|
|
4303
|
+
});
|
|
2879
4304
|
document.addEventListener("click", (event) => {
|
|
2880
4305
|
window.setTimeout(() => applyLocalViewerChrome(), 0);
|
|
2881
4306
|
const target = event.target instanceof Element ? event.target.closest("[data-viewer-doc-path]") : null;
|
|
@@ -2885,28 +4310,92 @@
|
|
|
2885
4310
|
const gitHistoryRevealTarget = event.target instanceof Element ? event.target.closest("[data-viewer-git-history-reveal]") : null;
|
|
2886
4311
|
const gitDomainTarget = event.target instanceof Element ? event.target.closest(".viewer-git__domain[data-viewer-git-domain]") : null;
|
|
2887
4312
|
const gitFileTarget = event.target instanceof Element ? event.target.closest("[data-viewer-git-file]") : null;
|
|
4313
|
+
const workspaceTreeTarget = event.target instanceof Element ? event.target.closest("[data-viewer-workspace-tree]") : null;
|
|
4314
|
+
const workspacePreviewTarget = event.target instanceof Element ? event.target.closest("[data-viewer-workspace-preview]") : null;
|
|
2888
4315
|
const projectSwitcherTarget = event.target instanceof Element ? event.target.closest("#viewer-repo-pill") : null;
|
|
2889
4316
|
const projectTarget = event.target instanceof Element ? event.target.closest("[data-viewer-project-id]") : null;
|
|
2890
4317
|
const cdxModeTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-mode]") : null;
|
|
4318
|
+
const cdxBackRunsTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-back-runs]") : null;
|
|
2891
4319
|
const cdxReportTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-report]") : null;
|
|
4320
|
+
const cdxArtifactTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-artifact-path]") : null;
|
|
4321
|
+
const cdxProviderAllTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-provider-all]") : null;
|
|
2892
4322
|
const cdxCreateRequestTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-create-request]") : null;
|
|
4323
|
+
const cdxMissionTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-mission]") : null;
|
|
4324
|
+
const cdxStrengthTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-strength]") : null;
|
|
4325
|
+
const cdxPlanTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-plan]") : null;
|
|
4326
|
+
const cdxRunTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-run]") : null;
|
|
4327
|
+
const cdxApplyPlanTarget = event.target instanceof Element ? event.target.closest("[data-viewer-cdx-apply-plan]") : null;
|
|
4328
|
+
if (cdxMissionTarget instanceof HTMLElement) {
|
|
4329
|
+
latestCdxMissionState.missionId = cdxMissionTarget.getAttribute("data-viewer-cdx-mission") || "full-audit";
|
|
4330
|
+
latestCdxMissionState.planPayload = null;
|
|
4331
|
+
latestCdxMissionState.runPayload = null;
|
|
4332
|
+
latestCdxMissionState.applyPayload = null;
|
|
4333
|
+
latestCdxMissionState.missionInputs = {};
|
|
4334
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload));
|
|
4335
|
+
return;
|
|
4336
|
+
}
|
|
4337
|
+
if (cdxStrengthTarget instanceof HTMLElement) {
|
|
4338
|
+
latestCdxMissionState.strengthId = cdxStrengthTarget.getAttribute("data-viewer-cdx-strength") || "standard";
|
|
4339
|
+
latestCdxMissionState.planPayload = null;
|
|
4340
|
+
latestCdxMissionState.runPayload = null;
|
|
4341
|
+
latestCdxMissionState.applyPayload = null;
|
|
4342
|
+
setDocument("CDX missions", renderCdxMissions(latestCdxMissionState.statusPayload));
|
|
4343
|
+
return;
|
|
4344
|
+
}
|
|
4345
|
+
if (cdxPlanTarget instanceof HTMLElement) {
|
|
4346
|
+
withCdxMissionAction("cdx-plan", "Building CDX mission plan", previewCdxMission);
|
|
4347
|
+
return;
|
|
4348
|
+
}
|
|
4349
|
+
if (cdxRunTarget instanceof HTMLElement) {
|
|
4350
|
+
withCdxMissionAction("cdx-run", "Launching CDX mission", launchCdxMission);
|
|
4351
|
+
return;
|
|
4352
|
+
}
|
|
4353
|
+
if (cdxApplyPlanTarget instanceof HTMLElement) {
|
|
4354
|
+
withCdxMissionAction("cdx-apply-plan", "Applying CDX mission plan", applyCdxMissionPlan);
|
|
4355
|
+
return;
|
|
4356
|
+
}
|
|
4357
|
+
if (cdxProviderAllTarget instanceof HTMLElement) {
|
|
4358
|
+
persistCdxProviderFilter({ mode: "all", selected: [] });
|
|
4359
|
+
rerenderCdxStatusFromPreferences();
|
|
4360
|
+
return;
|
|
4361
|
+
}
|
|
4362
|
+
if (cdxBackRunsTarget instanceof HTMLElement) {
|
|
4363
|
+
withPrimaryAction("cdx-runs", "Loading CDX runs", showCdxRuns);
|
|
4364
|
+
return;
|
|
4365
|
+
}
|
|
2893
4366
|
if (cdxReportTarget instanceof HTMLElement) {
|
|
2894
|
-
showCdxReport(cdxReportTarget.getAttribute("data-viewer-cdx-report") || "")
|
|
4367
|
+
withPrimaryAction("cdx-report", "Loading CDX report", () => showCdxReport(cdxReportTarget.getAttribute("data-viewer-cdx-report") || ""));
|
|
4368
|
+
return;
|
|
4369
|
+
}
|
|
4370
|
+
if (cdxArtifactTarget instanceof HTMLElement) {
|
|
4371
|
+
withPrimaryAction("cdx-artifact", "Opening CDX artifact", () => openCdxArtifact(cdxArtifactTarget.getAttribute("data-viewer-cdx-artifact-path") || ""));
|
|
2895
4372
|
return;
|
|
2896
4373
|
}
|
|
2897
4374
|
if (cdxCreateRequestTarget instanceof HTMLElement) {
|
|
2898
|
-
createRequestFromCdxReport(cdxCreateRequestTarget.getAttribute("data-viewer-cdx-create-request") || "")
|
|
4375
|
+
withPrimaryAction("cdx-create-request", "Creating Logics request", () => createRequestFromCdxReport(cdxCreateRequestTarget.getAttribute("data-viewer-cdx-create-request") || ""));
|
|
2899
4376
|
return;
|
|
2900
4377
|
}
|
|
2901
4378
|
if (cdxModeTarget instanceof HTMLElement) {
|
|
2902
4379
|
const mode = cdxModeTarget.getAttribute("data-viewer-cdx-mode") || "status";
|
|
2903
4380
|
if (mode === "runs") {
|
|
2904
|
-
|
|
4381
|
+
withPrimaryAction("cdx-runs", "Loading CDX runs", showCdxRuns);
|
|
4382
|
+
} else if (mode === "missions") {
|
|
4383
|
+
withPrimaryAction("cdx-missions", "Loading CDX missions", showCdxMissions);
|
|
2905
4384
|
} else {
|
|
2906
|
-
|
|
4385
|
+
withPrimaryAction("cdx", "Checking CDX status", showCdxStatus);
|
|
2907
4386
|
}
|
|
2908
4387
|
return;
|
|
2909
4388
|
}
|
|
4389
|
+
if (workspaceTreeTarget instanceof HTMLElement) {
|
|
4390
|
+
event.preventDefault();
|
|
4391
|
+
withPrimaryAction("workspace-tree", "Loading Explorer folder", () => openWorkspaceTree(workspaceTreeTarget.getAttribute("data-viewer-workspace-tree") || ""));
|
|
4392
|
+
return;
|
|
4393
|
+
}
|
|
4394
|
+
if (workspacePreviewTarget instanceof HTMLElement) {
|
|
4395
|
+
event.preventDefault();
|
|
4396
|
+
withPrimaryAction("workspace-preview", "Loading Explorer preview", () => openWorkspacePreview(workspacePreviewTarget.getAttribute("data-viewer-workspace-preview") || ""));
|
|
4397
|
+
return;
|
|
4398
|
+
}
|
|
2910
4399
|
if (projectSwitcherTarget instanceof HTMLElement) {
|
|
2911
4400
|
const menu = projectMenu();
|
|
2912
4401
|
setProjectMenuOpen(Boolean(menu?.hidden));
|
|
@@ -2914,7 +4403,7 @@
|
|
|
2914
4403
|
}
|
|
2915
4404
|
if (projectTarget instanceof HTMLElement) {
|
|
2916
4405
|
event.preventDefault();
|
|
2917
|
-
switchViewerProject(projectTarget.getAttribute("data-viewer-project-id") || "")
|
|
4406
|
+
withPrimaryAction("switch-project", "Switching project", () => switchViewerProject(projectTarget.getAttribute("data-viewer-project-id") || ""));
|
|
2918
4407
|
return;
|
|
2919
4408
|
}
|
|
2920
4409
|
if (gitHistoryRevealTarget instanceof HTMLElement) {
|
|
@@ -2966,7 +4455,7 @@
|
|
|
2966
4455
|
return;
|
|
2967
4456
|
}
|
|
2968
4457
|
if (healthTarget instanceof HTMLElement) {
|
|
2969
|
-
|
|
4458
|
+
withPrimaryAction("health", "Checking health", showHealth);
|
|
2970
4459
|
return;
|
|
2971
4460
|
}
|
|
2972
4461
|
if (filterTarget instanceof HTMLElement) {
|
|
@@ -2976,14 +4465,11 @@
|
|
|
2976
4465
|
}
|
|
2977
4466
|
const path = target instanceof HTMLElement ? target.getAttribute("data-viewer-doc-path") : "";
|
|
2978
4467
|
if (path) {
|
|
2979
|
-
|
|
4468
|
+
withPrimaryAction("read-document", "Loading document", () => showDocumentByPath(path));
|
|
2980
4469
|
}
|
|
2981
4470
|
});
|
|
2982
4471
|
document.getElementById("viewer-document-close")?.addEventListener("click", () => {
|
|
2983
|
-
|
|
2984
|
-
if (panel) {
|
|
2985
|
-
panel.hidden = true;
|
|
2986
|
-
}
|
|
4472
|
+
withPrimaryAction("close-document", "Closing preview", closeDocumentPanel);
|
|
2987
4473
|
});
|
|
2988
4474
|
startAutoRefresh();
|
|
2989
4475
|
});
|