Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 89 additions & 12 deletions apps/decodex/src/orchestrator/operator_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -3542,6 +3542,8 @@ <h2 id="recent-title">Run History</h2>
let dashboardSocket = null;
let dashboardSocketReconnectTimer = null;
let dashboardLiveActiveRuns = [];
let dashboardLiveAccounts = null;
let dashboardLiveAccountControl = null;
let dashboardStreamState = {
supported: typeof window.WebSocket === "function",
connected: false,
Expand Down Expand Up @@ -5615,7 +5617,7 @@ <h2 id="recent-title">Run History</h2>
return facts.map(([label, value]) => renderRunMetaFact(label, value)).join("");
}

function codexAccount(run) {
function codexAccount(run, snapshot = null) {
const selected = run?.account || run?.codex_account || null;
if (selected) {
return selected;
Expand All @@ -5629,7 +5631,39 @@ <h2 id="recent-title">Run History</h2>
return status === "selected";
}) ||
accounts[0] ||
null
selectedDashboardAccount(snapshot)
);
}

function selectedDashboardAccount(snapshot) {
if (!snapshot) {
return null;
}

const accounts = Array.isArray(snapshot.accounts)
? snapshot.accounts.filter(Boolean)
: [];
if (!accounts.length) {
return null;
}

const selector = codexAccountConfiguredSelector(snapshot);
if (selector) {
const fixed = accounts.find(
(account) =>
selector === codexAccountEmail(account) ||
selector === codexAccountFingerprint(account),
);
if (fixed) {
return fixed;
}
}

return (
accounts.find((account) => {
const status = String(account?.status || "").toLowerCase();
return status === "selected";
}) || null
);
}

Expand Down Expand Up @@ -5665,6 +5699,11 @@ <h2 id="recent-title">Run History</h2>
return fingerprint;
}

const email = codexAccountEmail(account);
if (email) {
return email;
}

return account?.plan_type || "";
}

Expand Down Expand Up @@ -6454,8 +6493,9 @@ <h2 id="recent-title">Run History</h2>
`;
}

function renderRunCodexAccountInline(run) {
const account = codexAccount(run);
function renderRunCodexAccountInline(run, snapshot) {
const capturedAccount = codexAccount(run);
const account = capturedAccount || selectedDashboardAccount(snapshot);
if (!account) {
return `
<span class="run-meta-item is-account is-missing" aria-label="account">
Expand All @@ -6472,6 +6512,9 @@ <h2 id="recent-title">Run History</h2>
const displayTitle = codexAccountDisplayTitle(account);
const visibleName = codexAccountVisibleName(account);
const identityClass = codexAccountShowsEmail(account) ? " is-machine" : "";
const pendingTitle = capturedAccount
? displayTitle
: `${displayTitle || visibleName} · run account capture pending`;

return `
<span class="run-meta-item is-account" aria-label="account">
Expand All @@ -6480,14 +6523,14 @@ <h2 id="recent-title">Run History</h2>
<path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M3.35 2.25h9.3c.61 0 1.1.49 1.1 1.1v9.3c0 .61-.49 1.1-1.1 1.1h-9.3c-.61 0-1.1-.49-1.1-1.1v-9.3c0-.61.49-1.1 1.1-1.1ZM8 4.15a1.8 1.8 0 1 1 0 3.6 1.8 1.8 0 0 1 0-3.6Zm0 4.78c2.02 0 3.26.96 3.7 2.78.08.35-.18.67-.54.67H4.84c-.36 0-.62-.32-.54-.67.44-1.82 1.68-2.78 3.7-2.78Z"></path>
</svg>
</span>
<strong class="account-name${identityClass}" title="${escapeHtml(displayTitle)}">${escapeHtml(visibleName)}</strong>
<strong class="account-name${identityClass}" title="${escapeHtml(pendingTitle)}">${escapeHtml(visibleName)}</strong>
</span>
`;
}

function renderRunMetaLine(run) {
function renderRunMetaLine(run, snapshot = null) {
const items = [
renderRunCodexAccountInline(run),
renderRunCodexAccountInline(run, snapshot),
renderRunTelemetryMetaItems(run),
]
.filter(Boolean)
Expand Down Expand Up @@ -8588,7 +8631,7 @@ <h3 class="run-title">${escapeHtml(issueTitle)}</h3>
</div>
<div class="status-line">${statusLineParts.join("")}</div>
${summary ? `<p class="row-summary">${escapeHtml(summary)}</p>` : ""}
${renderRunMetaLine(run)}
${renderRunMetaLine(run, snapshot)}
${renderChildAgentBreakdown(run)}
<details data-detail-key="${escapeHtml(detailKey)}"${detailsOpenAttribute(detailKey)}>
<summary>Debug Details</summary>
Expand Down Expand Up @@ -8725,7 +8768,7 @@ <h4>${escapeHtml(title)}</h4>
${field("Branch", run.branch_name || "none")}
${field("Worktree", run.worktree_path || "none")}
${field("Model", runModelSummary(run))}
${field("Account", codexAccountDebugSummary(codexAccount(run)))}
${field("Account", codexAccountDebugSummary(codexAccount(run, snapshot)))}
${field("Child agent", childAgentDebugSummary(childAgentActivity(run)))}
${field("Context pressure", childAgentContextSummary(childAgentActivity(run)))}
${field("Large outputs", childAgentLargeOutputSummary(childAgentActivity(run)))}
Expand Down Expand Up @@ -8999,6 +9042,9 @@ <h4>${escapeHtml(worktree.branch_name)}</h4>
if (typeof value === "string") {
return value.trim() !== "";
}
if (value && typeof value === "object") {
return Object.keys(value).length > 0;
}

return value !== null && value !== undefined;
}
Expand Down Expand Up @@ -9084,7 +9130,7 @@ <h4>${escapeHtml(worktree.branch_name)}</h4>
return mergedRuns;
}

function mergeDashboardRunActivity(snapshot, activeRuns) {
function mergeDashboardRunActivity(snapshot, activeRuns, activityPayload = {}) {
const activeRunRows = activeRuns
.filter((run) => run && typeof run === "object")
.map((run) => ({ ...run }));
Expand Down Expand Up @@ -9136,21 +9182,41 @@ <h4>${escapeHtml(worktree.branch_name)}</h4>
};
})
: snapshot.projects;
const accounts = Array.isArray(activityPayload.accounts)
? activityPayload.accounts.filter(Boolean).map((account) => ({ ...account }))
: snapshot.accounts;
const accountControl =
activityPayload.accountControl && typeof activityPayload.accountControl === "object"
? { ...(snapshot.account_control || {}), ...activityPayload.accountControl }
: snapshot.account_control;

return {
...snapshot,
account_control: accountControl,
accounts,
active_runs: mergedActiveRuns,
recent_runs: recentRuns,
projects,
};
}

function snapshotWithLiveRunActivity(snapshot) {
if (!snapshot || !dashboardSocketIsOpen() || !dashboardLiveActiveRuns.length) {
if (!snapshot || !dashboardSocketIsOpen()) {
return snapshot;
}

return mergeDashboardRunActivity(snapshot, dashboardLiveActiveRuns);
if (
!dashboardLiveActiveRuns.length &&
!dashboardLiveAccounts &&
!dashboardLiveAccountControl
) {
return snapshot;
}

return mergeDashboardRunActivity(snapshot, dashboardLiveActiveRuns, {
accountControl: dashboardLiveAccountControl,
accounts: dashboardLiveAccounts,
});
}

function applyDashboardRunActivity(payload) {
Expand All @@ -9167,6 +9233,13 @@ <h4>${escapeHtml(worktree.branch_name)}</h4>
dashboardLiveActiveRuns = payload.activeRuns
.filter((run) => run && typeof run === "object")
.map((run) => ({ ...run }));
dashboardLiveAccounts = Array.isArray(payload.accounts) && payload.accounts.length
? payload.accounts.filter(Boolean).map((account) => ({ ...account }))
: null;
dashboardLiveAccountControl =
payload.accountControl && typeof payload.accountControl === "object"
? { ...payload.accountControl }
: null;

if (!lastDashboardRender?.snapshot) {
updateDashboardStreamState({
Expand Down Expand Up @@ -9323,6 +9396,8 @@ <h4>${escapeHtml(worktree.branch_name)}</h4>
};
dashboardSocket.onclose = () => {
dashboardLiveActiveRuns = [];
dashboardLiveAccounts = null;
dashboardLiveAccountControl = null;
updateDashboardStreamState({
connected: false,
error: true,
Expand All @@ -9331,6 +9406,8 @@ <h4>${escapeHtml(worktree.branch_name)}</h4>
};
dashboardSocket.onerror = () => {
dashboardLiveActiveRuns = [];
dashboardLiveAccounts = null;
dashboardLiveAccountControl = null;
updateDashboardStreamState({
connected: false,
error: true,
Expand Down
23 changes: 21 additions & 2 deletions apps/decodex/src/orchestrator/operator_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,8 @@ fn run_operator_run_activity_websocket_broadcasts(

fn build_operator_run_activity_event(state_store: &StateStore) -> Result<DashboardRunActivityEvent> {
let now_unix_epoch = OffsetDateTime::now_utc().unix_timestamp();
let account_control = global_codex_account_control_status();
let mut accounts = Vec::new();
let mut active_runs = Vec::new();

for registration in state_store.list_projects()? {
Expand All @@ -411,19 +413,36 @@ fn build_operator_run_activity_event(state_store: &StateStore) -> Result<Dashboa
},
};
let (runs, _) = state_store.list_project_runs(project.service_id(), 0)?;
let mut project_active_runs = Vec::new();

for run in runs {
let run_status = operator_run_status(&project, run, now_unix_epoch)?;

if operator_run_counts_as_active(&run_status) {
active_runs.push(run_status);
project_active_runs.push(run_status);
}
}

if project_active_runs.is_empty() {
continue;
}

let mut account_warnings = Vec::new();

accounts.extend(codex_account_activity_summaries(&project, &mut account_warnings));
active_runs.extend(project_active_runs);
}

let fingerprint = serde_json::to_vec(&active_runs)?;
let fingerprint_payload = json!({
"accountControl": &account_control,
"accounts": &accounts,
"activeRuns": &active_runs,
});
let fingerprint = serde_json::to_vec(&fingerprint_payload)?;
let payload = json!({
"emittedAtUnixEpoch": now_unix_epoch,
"accountControl": account_control,
"accounts": accounts,
"activeRuns": active_runs,
});

Expand Down
26 changes: 19 additions & 7 deletions apps/decodex/src/orchestrator/tests/operator/status/dashboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,15 +414,18 @@ fn operator_dashboard_active_run_status_copy_stays_concise() {
fn operator_dashboard_renders_account_usage_controls() {
let response = dashboard_response();

assert!(response.contains("function codexAccount(run)"));
assert!(response.contains("function codexAccount(run, snapshot = null)"));
assert!(response.contains("function codexAccounts(run)"));
assert!(response.contains("function selectedDashboardAccount(snapshot)"));
assert!(!response.contains("function runAuthor(run)"));
assert!(!response.contains("function renderRunAuthorInline(run)"));
assert!(response.contains("function codexAccountDisplayName(account)"));
assert!(response.contains("function codexAccountTokenLabel(refreshStatus)"));
assert!(response.contains("function codexAccountWindowLabel(seconds)"));
assert!(response.contains("function codexAccountStatusTone(account)"));
assert!(response.contains("function renderRunCodexAccountInline(run)"));
assert!(response.contains("function renderRunCodexAccountInline(run, snapshot)"));
assert!(response.contains("function renderRunMetaLine(run, snapshot = null)"));
assert!(response.contains("run account capture pending"));
assert!(response.contains("function renderAccountPool(snapshot)"));
assert!(response.contains("function renderAccountModeControl(snapshot)"));
assert!(response.contains("nodes.accountModeMeta.textContent = title;"));
Expand Down Expand Up @@ -806,12 +809,12 @@ fn operator_dashboard_accounts_keeps_identity_rows_compact() {
assert!(!response.contains("<path fill=\"currentColor\" d=\"M3.25 13.15c.48-2.65"));
assert!(!response.contains("fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M3.9 2.2h8.2"));
assert!(!response.contains("<circle cx=\"8\" cy=\"5.1\""));
assert!(response.contains("<strong class=\"account-name${identityClass}\" title=\"${escapeHtml(displayTitle)}\">${escapeHtml(visibleName)}</strong>"));
assert!(response.contains("<strong class=\"account-name${identityClass}\" title=\"${escapeHtml(pendingTitle)}\">${escapeHtml(visibleName)}</strong>"));
assert!(!response.contains("<strong class=\"machine-text\">${escapeHtml(`${value}%`)}</strong>"));
assert!(!response.contains("function codexAccountSecondaryLabel(account)"));
assert!(response.contains("const visibleName = codexAccountVisibleName(account);"));
assert!(response.contains("const displayTitle = codexAccountDisplayTitle(account);"));
assert!(response.contains("title=\"${escapeHtml(displayTitle)}\">${escapeHtml(visibleName)}</strong>"));
assert!(response.contains("title=\"${escapeHtml(pendingTitle)}\">${escapeHtml(visibleName)}</strong>"));
assert!(response.contains("text.startsWith(\"...\") && text.indexOf(\"...\", 3) === -1"));
}

Expand Down Expand Up @@ -875,8 +878,8 @@ fn operator_dashboard_accounts_keeps_window_status_and_credit_copy_compact() {
assert!(!response.contains("<div class=\"account-quota-line\">"));
assert!(response.contains("<div class=\"account-window is-${escapeHtml(prefix)}${toneClass}\""));
assert!(!response.contains("codexAccountStatusBit(account)"));
assert!(response.contains("renderRunCodexAccountInline(run)"));
assert!(response.contains("function renderRunMetaLine(run)"));
assert!(response.contains("renderRunCodexAccountInline(run, snapshot)"));
assert!(response.contains("function renderRunMetaLine(run, snapshot = null)"));
}

#[test]
Expand All @@ -897,7 +900,9 @@ fn operator_dashboard_accounts_keeps_debug_credit_and_reset_copy_compact() {
assert!(!response.contains(
"field(\"Accounts\", codexAccountPoolDebugSummary(codexAccounts(run)))"
));
assert!(response.contains("field(\"Account\", codexAccountDebugSummary(codexAccount(run)))"));
assert!(response.contains(
"field(\"Account\", codexAccountDebugSummary(codexAccount(run, snapshot)))"
));
assert!(response.contains("facts.push([\"Account\", codexAccountHistorySummary(codexAccount(run))])"));
assert!(!response.contains("facts.push([\"Codex pool\""));
assert!(!response.contains("account <strong>"));
Expand Down Expand Up @@ -1495,11 +1500,16 @@ fn operator_dashboard_run_activity_preserves_snapshot_detail_fields() {
assert!(response.contains("function mergeDashboardRunRecord(snapshotRun, activityRun)"));
assert!(response.contains("function mergeDashboardActiveRuns(snapshot, activeRunRows)"));
assert!(response.contains("let dashboardLiveActiveRuns = [];"));
assert!(response.contains("let dashboardLiveAccounts = null;"));
assert!(response.contains("let dashboardLiveAccountControl = null;"));
assert!(response.contains("function snapshotWithLiveRunActivity(snapshot)"));
assert!(response.contains("\"issue_identifier\""));
assert!(response.contains("\"title\""));
assert!(!response.contains("field(\"Author\","));
assert!(!response.contains("\"author\",\n"));
assert!(response.contains("activityPayload.accountControl"));
assert!(response.contains("dashboardLiveAccounts = Array.isArray(payload.accounts)"));
assert!(response.contains("dashboardLiveAccountControl ="));
assert!(response.contains("\"child_agent_activity\""));
assert!(response.contains("\"protocol_activity\""));
assert!(response.contains("!dashboardRunFieldHasValue(activityRun[key])"));
Expand All @@ -1509,6 +1519,8 @@ fn operator_dashboard_run_activity_preserves_snapshot_detail_fields() {
);
assert!(response.contains("dashboardLiveActiveRuns = payload.activeRuns"));
assert!(response.contains("snapshot: snapshotWithLiveRunActivity(payload.snapshot),"));
assert!(response.contains("account_control: accountControl,"));
assert!(response.contains("accounts,"));
assert!(response.contains("active_runs: mergedActiveRuns,"));
assert!(!response.contains("active_runs: activeRunRows,"));
}
Expand Down
8 changes: 8 additions & 0 deletions apps/decodex/src/orchestrator/tests/operator/status/http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1045,8 +1045,16 @@ fn operator_dashboard_run_activity_event_summarizes_active_runs() {
let (payload, _consumed) = websocket_text_payload(&message).expect("event should be a text frame");
let payload: Value = serde_json::from_slice(payload).expect("event data should be json");
let data = &payload["payload"];
let fingerprint: Value =
serde_json::from_slice(&event.fingerprint).expect("fingerprint should be json");

assert_eq!(payload["type"], "runActivity");
assert_eq!(data["accountControl"]["mode"], "balanced");
assert!(data["accounts"].is_array());
assert!(fingerprint.get("emittedAtUnixEpoch").is_none());
assert_eq!(fingerprint["accountControl"]["mode"], "balanced");
assert!(fingerprint["accounts"].is_array());
assert_eq!(fingerprint["activeRuns"][0]["run_id"], "run-1");
assert_eq!(data["activeRuns"][0]["run_id"], "run-1");
assert_eq!(data["activeRuns"][0]["project_id"], "pubfi");
assert_eq!(data["activeRuns"][0]["protocol_activity"]["waiting_reason"], "model");
Expand Down