From fd380c9bde94ac209a1d4ca574c9127235b52472 Mon Sep 17 00:00:00 2001 From: Yvette Carlisle Date: Tue, 19 May 2026 15:47:04 +0800 Subject: [PATCH] {"schema":"decodex/commit/1","summary":"Show live picked account in dashboard","authority":"manual"} --- .../src/orchestrator/operator_dashboard.html | 101 +++++++++++++++--- .../decodex/src/orchestrator/operator_http.rs | 23 +++- .../tests/operator/status/dashboard.rs | 26 +++-- .../tests/operator/status/http.rs | 8 ++ 4 files changed, 137 insertions(+), 21 deletions(-) diff --git a/apps/decodex/src/orchestrator/operator_dashboard.html b/apps/decodex/src/orchestrator/operator_dashboard.html index d43d13aa..1c9a9067 100644 --- a/apps/decodex/src/orchestrator/operator_dashboard.html +++ b/apps/decodex/src/orchestrator/operator_dashboard.html @@ -3542,6 +3542,8 @@

Run History

let dashboardSocket = null; let dashboardSocketReconnectTimer = null; let dashboardLiveActiveRuns = []; + let dashboardLiveAccounts = null; + let dashboardLiveAccountControl = null; let dashboardStreamState = { supported: typeof window.WebSocket === "function", connected: false, @@ -5615,7 +5617,7 @@

Run History

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; @@ -5629,7 +5631,39 @@

Run History

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 ); } @@ -5665,6 +5699,11 @@

Run History

return fingerprint; } + const email = codexAccountEmail(account); + if (email) { + return email; + } + return account?.plan_type || ""; } @@ -6454,8 +6493,9 @@

Run History

`; } - function renderRunCodexAccountInline(run) { - const account = codexAccount(run); + function renderRunCodexAccountInline(run, snapshot) { + const capturedAccount = codexAccount(run); + const account = capturedAccount || selectedDashboardAccount(snapshot); if (!account) { return ` @@ -6472,6 +6512,9 @@

Run History

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 ` - +
`; } - function renderRunMetaLine(run) { + function renderRunMetaLine(run, snapshot = null) { const items = [ - renderRunCodexAccountInline(run), + renderRunCodexAccountInline(run, snapshot), renderRunTelemetryMetaItems(run), ] .filter(Boolean) @@ -8588,7 +8631,7 @@

${escapeHtml(issueTitle)}

${statusLineParts.join("")}
${summary ? `

${escapeHtml(summary)}

` : ""} - ${renderRunMetaLine(run)} + ${renderRunMetaLine(run, snapshot)} ${renderChildAgentBreakdown(run)}
Debug Details @@ -8725,7 +8768,7 @@

${escapeHtml(title)}

${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)))} @@ -8999,6 +9042,9 @@

${escapeHtml(worktree.branch_name)}

if (typeof value === "string") { return value.trim() !== ""; } + if (value && typeof value === "object") { + return Object.keys(value).length > 0; + } return value !== null && value !== undefined; } @@ -9084,7 +9130,7 @@

${escapeHtml(worktree.branch_name)}

return mergedRuns; } - function mergeDashboardRunActivity(snapshot, activeRuns) { + function mergeDashboardRunActivity(snapshot, activeRuns, activityPayload = {}) { const activeRunRows = activeRuns .filter((run) => run && typeof run === "object") .map((run) => ({ ...run })); @@ -9136,9 +9182,18 @@

${escapeHtml(worktree.branch_name)}

}; }) : 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, @@ -9146,11 +9201,22 @@

${escapeHtml(worktree.branch_name)}

} 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) { @@ -9167,6 +9233,13 @@

${escapeHtml(worktree.branch_name)}

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({ @@ -9323,6 +9396,8 @@

${escapeHtml(worktree.branch_name)}

}; dashboardSocket.onclose = () => { dashboardLiveActiveRuns = []; + dashboardLiveAccounts = null; + dashboardLiveAccountControl = null; updateDashboardStreamState({ connected: false, error: true, @@ -9331,6 +9406,8 @@

${escapeHtml(worktree.branch_name)}

}; dashboardSocket.onerror = () => { dashboardLiveActiveRuns = []; + dashboardLiveAccounts = null; + dashboardLiveAccountControl = null; updateDashboardStreamState({ connected: false, error: true, diff --git a/apps/decodex/src/orchestrator/operator_http.rs b/apps/decodex/src/orchestrator/operator_http.rs index 075c9a3b..1a992232 100644 --- a/apps/decodex/src/orchestrator/operator_http.rs +++ b/apps/decodex/src/orchestrator/operator_http.rs @@ -390,6 +390,8 @@ fn run_operator_run_activity_websocket_broadcasts( fn build_operator_run_activity_event(state_store: &StateStore) -> Result { 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()? { @@ -411,19 +413,36 @@ fn build_operator_run_activity_event(state_store: &StateStore) -> Result${escapeHtml(visibleName)}")); + assert!(response.contains("${escapeHtml(visibleName)}")); assert!(!response.contains("${escapeHtml(`${value}%`)}")); 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)}")); + assert!(response.contains("title=\"${escapeHtml(pendingTitle)}\">${escapeHtml(visibleName)}")); assert!(response.contains("text.startsWith(\"...\") && text.indexOf(\"...\", 3) === -1")); } @@ -875,8 +878,8 @@ fn operator_dashboard_accounts_keeps_window_status_and_credit_copy_compact() { assert!(!response.contains("
")); assert!(response.contains("
")); @@ -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])")); @@ -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,")); } diff --git a/apps/decodex/src/orchestrator/tests/operator/status/http.rs b/apps/decodex/src/orchestrator/tests/operator/status/http.rs index 83e1c959..54072821 100644 --- a/apps/decodex/src/orchestrator/tests/operator/status/http.rs +++ b/apps/decodex/src/orchestrator/tests/operator/status/http.rs @@ -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");