Update UI for session/conflict controls and remove workspace dir
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -90,6 +90,23 @@
|
||||
</form>
|
||||
<div id="run-status" class="subtle"></div>
|
||||
|
||||
<div class="divider"></div>
|
||||
<h3>Session Controls</h3>
|
||||
<form id="session-form" class="stacked-form">
|
||||
<label>
|
||||
Project Path (absolute)
|
||||
<input id="session-project-path" type="text" placeholder="/abs/path/to/project" />
|
||||
</label>
|
||||
<label class="inline-checkbox">
|
||||
<input id="session-close-merge" type="checkbox" />
|
||||
Merge base into project when closing selected session
|
||||
</label>
|
||||
<div class="inline-actions">
|
||||
<button id="session-create" type="button">Create Session</button>
|
||||
<button id="session-close" type="button">Close Selected Session</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="divider"></div>
|
||||
<h3>Node Inspector</h3>
|
||||
<div id="node-inspector" class="inspector empty">Select a graph node.</div>
|
||||
@@ -196,6 +213,7 @@
|
||||
<label>AGENT_TOPOLOGY_MAX_DEPTH<input id="cfg-topology-depth" type="number" min="1" /></label>
|
||||
<label>AGENT_TOPOLOGY_MAX_RETRIES<input id="cfg-topology-retries" type="number" min="0" /></label>
|
||||
<label>AGENT_RELATIONSHIP_MAX_CHILDREN<input id="cfg-relationship-children" type="number" min="1" /></label>
|
||||
<label>AGENT_MERGE_CONFLICT_MAX_ATTEMPTS<input id="cfg-merge-conflict-attempts" type="number" min="1" /></label>
|
||||
<label>AGENT_PORT_BASE<input id="cfg-port-base" type="number" min="1" /></label>
|
||||
<label>AGENT_PORT_BLOCK_SIZE<input id="cfg-port-block-size" type="number" min="1" /></label>
|
||||
<label>AGENT_PORT_BLOCK_COUNT<input id="cfg-port-block-count" type="number" min="1" /></label>
|
||||
|
||||
@@ -142,6 +142,12 @@ label {
|
||||
letter-spacing: 0.015em;
|
||||
}
|
||||
|
||||
label.inline-checkbox {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea,
|
||||
@@ -353,6 +359,22 @@ button.danger {
|
||||
border-color: rgba(255, 201, 74, 0.6);
|
||||
}
|
||||
|
||||
.status-active {
|
||||
color: var(--accent-cool);
|
||||
border-color: rgba(86, 195, 255, 0.6);
|
||||
}
|
||||
|
||||
.status-suspended,
|
||||
.status-closed_with_conflicts {
|
||||
color: var(--warn);
|
||||
border-color: rgba(255, 201, 74, 0.6);
|
||||
}
|
||||
|
||||
.status-closed {
|
||||
color: var(--muted);
|
||||
border-color: rgba(155, 184, 207, 0.45);
|
||||
}
|
||||
|
||||
.status-unknown {
|
||||
color: var(--muted);
|
||||
border-color: rgba(155, 184, 207, 0.45);
|
||||
|
||||
Reference in New Issue
Block a user