Update UI for session/conflict controls and remove workspace dir

This commit is contained in:
2026-02-24 10:50:43 -05:00
parent 9f032d9b14
commit 6863c1da0b
5 changed files with 149 additions and 1 deletions

View File

@@ -2,6 +2,7 @@ const state = {
config: null,
manifests: [],
sessions: [],
sessionMetadata: [],
runs: [],
selectedSessionId: "",
selectedManifestPath: "",
@@ -29,6 +30,11 @@ const dom = {
runValidationNodes: document.querySelector("#run-validation-nodes"),
killRun: document.querySelector("#kill-run"),
runStatus: document.querySelector("#run-status"),
sessionForm: document.querySelector("#session-form"),
sessionProjectPath: document.querySelector("#session-project-path"),
sessionCreate: document.querySelector("#session-create"),
sessionClose: document.querySelector("#session-close"),
sessionCloseMerge: document.querySelector("#session-close-merge"),
nodeInspector: document.querySelector("#node-inspector"),
eventsLimit: document.querySelector("#events-limit"),
eventsRefresh: document.querySelector("#events-refresh"),
@@ -78,6 +84,7 @@ const dom = {
cfgTopologyDepth: document.querySelector("#cfg-topology-depth"),
cfgTopologyRetries: document.querySelector("#cfg-topology-retries"),
cfgRelationshipChildren: document.querySelector("#cfg-relationship-children"),
cfgMergeConflictAttempts: document.querySelector("#cfg-merge-conflict-attempts"),
cfgPortBase: document.querySelector("#cfg-port-base"),
cfgPortBlockSize: document.querySelector("#cfg-port-block-size"),
cfgPortBlockCount: document.querySelector("#cfg-port-block-count"),
@@ -137,6 +144,8 @@ const LABEL_HELP_BY_CONTROL = Object.freeze({
"run-flags": "Optional JSON object passed in as initial run flags.",
"run-runtime-context": "Optional JSON object of template values injected into persona prompts (for example repo or ticket).",
"run-validation-nodes": "Optional comma-separated node IDs to simulate validation outcomes for.",
"session-project-path": "Absolute project path used when creating an explicit managed session.",
"session-close-merge": "When enabled, close will merge the session base branch back into the project branch.",
"events-limit": "Set how many recent runtime events are loaded per refresh.",
"cfg-webhook-url": "Webhook endpoint that receives runtime event notifications.",
"cfg-webhook-severity": "Minimum severity level that triggers webhook notifications.",
@@ -152,6 +161,7 @@ const LABEL_HELP_BY_CONTROL = Object.freeze({
"cfg-topology-depth": "Maximum orchestration graph depth permitted by topology rules.",
"cfg-topology-retries": "Maximum retry expansions allowed by topology orchestration.",
"cfg-relationship-children": "Maximum children each persona relationship can spawn.",
"cfg-merge-conflict-attempts": "Maximum merge-conflict resolution attempts before emitting unresolved conflict events.",
"cfg-port-base": "Starting port number for provisioning port allocations.",
"cfg-port-block-size": "Number of ports reserved per allocated block.",
"cfg-port-block-count": "Number of port blocks available for allocation.",
@@ -1036,6 +1046,7 @@ async function loadConfig() {
dom.cfgTopologyDepth.value = String(limits.topologyMaxDepth);
dom.cfgTopologyRetries.value = String(limits.topologyMaxRetries);
dom.cfgRelationshipChildren.value = String(limits.relationshipMaxChildren);
dom.cfgMergeConflictAttempts.value = String(limits.mergeConflictMaxAttempts);
dom.cfgPortBase.value = String(limits.portBase);
dom.cfgPortBlockSize.value = String(limits.portBlockSize);
dom.cfgPortBlockCount.value = String(limits.portBlockCount);
@@ -1067,11 +1078,28 @@ function statusChipClass(status) {
return `status-chip status-${status || "unknown"}`;
}
function getSessionLifecycleStatus(sessionId) {
const metadata = state.sessionMetadata.find((entry) => entry?.sessionId === sessionId);
if (!metadata) {
return undefined;
}
const status = metadata.sessionStatus;
if (status === "active" || status === "suspended" || status === "closed" || status === "closed_with_conflicts") {
return status;
}
return undefined;
}
function renderRunsAndSessionsTable() {
const rows = [];
for (const session of state.sessions) {
const sessionStatus = session.status || "unknown";
const lifecycleStatus = getSessionLifecycleStatus(session.sessionId);
const sessionStatus =
lifecycleStatus === "closed" || lifecycleStatus === "closed_with_conflicts"
? lifecycleStatus
: session.status || lifecycleStatus || "unknown";
rows.push(`
<tr data-session-id="${escapeHtml(session.sessionId)}">
<td>${escapeHtml(session.sessionId)}</td>
@@ -1099,6 +1127,7 @@ function renderRunsAndSessionsTable() {
async function loadSessions() {
const payload = await apiRequest("/api/sessions");
state.sessions = payload.sessions || [];
state.sessionMetadata = payload.sessionMetadata || [];
state.runs = payload.runs || [];
if (!state.selectedSessionId && state.sessions.length > 0) {
@@ -1511,6 +1540,17 @@ async function startRun(event) {
simulateValidationNodeIds: fromCsv(dom.runValidationNodes.value),
};
const selectedSessionMetadata = state.sessionMetadata.find(
(entry) => entry?.sessionId === state.selectedSessionId,
);
if (
selectedSessionMetadata &&
(selectedSessionMetadata.sessionStatus === "active" ||
selectedSessionMetadata.sessionStatus === "suspended")
) {
payload.sessionId = selectedSessionMetadata.sessionId;
}
if (manifestSelection === RUN_MANIFEST_EDITOR_VALUE) {
const manifestFromEditor = parseJsonSafe(dom.manifestEditor.value, null);
if (!manifestFromEditor) {
@@ -1566,6 +1606,64 @@ async function cancelActiveRun() {
}
}
async function createSessionFromUi() {
const projectPath = dom.sessionProjectPath.value.trim();
if (!projectPath) {
showRunStatus("Project path is required to create a session.", true);
return;
}
try {
const payload = await apiRequest("/api/sessions", {
method: "POST",
body: JSON.stringify({
projectPath,
}),
});
const created = payload.session;
if (created?.sessionId) {
state.selectedSessionId = created.sessionId;
showRunStatus(`Session ${created.sessionId} created.`);
} else {
showRunStatus("Session created.");
}
await loadSessions();
if (state.selectedSessionId) {
dom.sessionSelect.value = state.selectedSessionId;
await refreshGraph();
await refreshEvents();
}
} catch (error) {
showRunStatus(error instanceof Error ? error.message : String(error), true);
}
}
async function closeSelectedSessionFromUi() {
const sessionId = state.selectedSessionId || dom.sessionSelect.value;
if (!sessionId) {
showRunStatus("Select a session before closing.", true);
return;
}
try {
const payload = await apiRequest(`/api/sessions/${encodeURIComponent(sessionId)}/close`, {
method: "POST",
body: JSON.stringify({
mergeToProject: dom.sessionCloseMerge.checked,
}),
});
const nextStatus = payload?.session?.sessionStatus || "closed";
showRunStatus(`Session ${sessionId} closed with status ${nextStatus}.`);
await loadSessions();
await refreshGraph();
await refreshEvents();
} catch (error) {
showRunStatus(error instanceof Error ? error.message : String(error), true);
}
}
async function saveNotifications(event) {
event.preventDefault();
const payload = {
@@ -1611,6 +1709,7 @@ async function saveLimits(event) {
topologyMaxDepth: Number(dom.cfgTopologyDepth.value),
topologyMaxRetries: Number(dom.cfgTopologyRetries.value),
relationshipMaxChildren: Number(dom.cfgRelationshipChildren.value),
mergeConflictMaxAttempts: Number(dom.cfgMergeConflictAttempts.value),
portBase: Number(dom.cfgPortBase.value),
portBlockSize: Number(dom.cfgPortBlockSize.value),
portBlockCount: Number(dom.cfgPortBlockCount.value),
@@ -1737,6 +1836,12 @@ function bindUiEvents() {
dom.killRun.addEventListener("click", () => {
void cancelActiveRun();
});
dom.sessionCreate.addEventListener("click", () => {
void createSessionFromUi();
});
dom.sessionClose.addEventListener("click", () => {
void closeSelectedSessionFromUi();
});
dom.notificationsForm.addEventListener("submit", (event) => {
void saveNotifications(event);