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
137 changes: 131 additions & 6 deletions apps/decodex/src/orchestrator/operator_dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -5983,6 +5983,58 @@ <h2 id="recent-title">Run History</h2>
return codexAccountTokenLabel(refreshStatus).replace(/^token /, "");
}

function codexAccountRefreshStatusNeedsAttention(refreshStatus) {
const status = String(refreshStatus || "").toLowerCase();
return Boolean(
status &&
!["not_needed", "refreshed", "succeeded", "none"].includes(status),
);
}

function codexAccountRefreshFailed(account) {
return String(account?.refresh_status || "").toLowerCase().includes("failed");
}

function codexAccountNote(account) {
return String(account?.note || "").trim();
}

function codexAccountNoteLooksRoutine(note) {
return String(note || "").trim().toLowerCase() === "usage probe ok";
}

function codexAccountNoteLooksError(note) {
return /\b(failed|error|unauthorized|forbidden|invalid|missing|unusable)\b/i.test(
String(note || ""),
);
}

function replaceLiteral(value, needle, replacement) {
const text = String(value || "");
const target = String(needle || "");
return target ? text.split(target).join(replacement) : text;
}

function codexAccountPrivacyLabel(account) {
return codexAccountShowsEmail(account)
? codexAccountEmail(account)
: codexAccountRandomName(account);
}

function codexAccountPrivacyText(account, value) {
let text = String(value || "");
if (!text || !accountEmailsHidden) {
return text;
}

const replacement = codexAccountPrivacyLabel(account);
text = replaceLiteral(text, codexAccountEmail(account), replacement);
return text.replace(
/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi,
replacement,
);
}

function codexAccountNumber(value) {
if (value == null) {
return null;
Expand Down Expand Up @@ -6149,8 +6201,7 @@ <h2 id="recent-title">Run History</h2>

const status = String(account.status || "").toLowerCase();
const refresh = String(account.refresh_status || "").toLowerCase();
const refreshNeedsAttention =
refresh && !["not_needed", "refreshed", "succeeded", "none"].includes(refresh);
const refreshNeedsAttention = codexAccountRefreshStatusNeedsAttention(refresh);

if (
codexAccountUsageLimited(account) ||
Expand Down Expand Up @@ -6262,17 +6313,21 @@ <h2 id="recent-title">Run History</h2>
return [];
}
const refreshStatus = String(account.refresh_status || "").toLowerCase();
const note = String(account.note || "").trim();
const noteLooksRoutine = note.toLowerCase() === "usage probe ok";
const note = codexAccountNote(account);
const noteLooksRoutine = codexAccountNoteLooksRoutine(note);
const noteLooksError = codexAccountNoteLooksError(note);

const facts = [
refreshStatus && !["not_needed", "none", "succeeded", "refreshed"].includes(refreshStatus)
codexAccountRefreshStatusNeedsAttention(refreshStatus) &&
!codexAccountRefreshFailed(account)
? ["token", codexAccountTokenValue(account.refresh_status)]
: null,
account.cooldown_until_unix_epoch
? ["cooldown", codexAccountUnixTimestamp(account.cooldown_until_unix_epoch)]
: null,
note && !noteLooksRoutine ? ["note", note] : null,
note && !noteLooksRoutine && !noteLooksError
? ["note", codexAccountPrivacyText(account, note)]
: null,
];

return facts.filter(Boolean);
Expand Down Expand Up @@ -7413,6 +7468,10 @@ <h2 id="recent-title">Run History</h2>
});
}

for (const accountNotice of codexAccountNotices(snapshot)) {
notices.push(accountNotice);
}

for (const controlEvent of dashboardControlEvents) {
notices.push({
tone: controlEvent.accepted ? "warning" : "danger",
Expand All @@ -7425,6 +7484,72 @@ <h2 id="recent-title">Run History</h2>
return notices;
}

function codexAccountHasNotice(account) {
if (!account) {
return false;
}

const status = String(account.status || "").toLowerCase();
const note = codexAccountNote(account);
return Boolean(
codexAccountRefreshFailed(account) ||
codexAccountNoteLooksError(note) ||
status.includes("failed") ||
status.includes("unusable"),
);
}

function codexAccountNoticeTitle(account) {
if (codexAccountRefreshFailed(account)) {
return "Codex account token";
}
if (codexAccountNoteLooksError(codexAccountNote(account))) {
return "Codex account usage";
}

return "Codex account";
}

function codexAccountNoticeCopy(account) {
const note = codexAccountNote(account);
const parts = [];
const noteIncludesRefreshFailure = /refresh failed|token refresh failed/i.test(note);
if (note && !codexAccountNoteLooksRoutine(note)) {
parts.push(codexAccountPrivacyText(account, note));
}
if (codexAccountRefreshFailed(account) && !noteIncludesRefreshFailure) {
parts.unshift(codexAccountTokenLabel(account.refresh_status));
}
if (!parts.length) {
parts.push(codexAccountStatusLabel(account));
}

return `${codexAccountPrivacyLabel(account)}: ${parts.join("; ")}`;
}

function codexAccountNotices(snapshot) {
const notices = [];
const seen = new Set();
for (const account of codexAccountPoolAccounts(snapshot)) {
if (!codexAccountHasNotice(account)) {
continue;
}
const notice = {
tone: "danger",
title: codexAccountNoticeTitle(account),
copy: codexAccountNoticeCopy(account),
};
const key = `${notice.title}:${notice.copy}`;
if (seen.has(key)) {
continue;
}
seen.add(key);
notices.push(notice);
}

return notices;
}

function warningNotice(warning) {
if (warning === "tracker_rate_limited") {
return {
Expand Down
21 changes: 21 additions & 0 deletions apps/decodex/src/orchestrator/tests/operator/status/dashboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -545,9 +545,13 @@ fn operator_dashboard_account_privacy_controls_use_compact_identities() {
assert!(response.contains("function codexAccountRandomNameOffset(account)"));
assert!(response.contains("function renderCodexAccountRandomNameButton(account)"));
assert!(response.contains("function codexAccountShowsEmail(account)"));
assert!(response.contains("function codexAccountPrivacyLabel(account)"));
assert!(response.contains("function codexAccountPrivacyText(account, value)"));
assert!(response.contains("function codexAccountVisibleName(account)"));
assert!(response.contains("function codexAccountDisplayTitle(account)"));
assert!(response.contains("function codexAccountControlStatusLabel(snapshot)"));
assert!(response.contains("text = replaceLiteral(text, codexAccountEmail(account), replacement);"));
assert!(response.contains("/[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}/gi"));
assert!(response.contains("return codexAccountShowsEmail(account) ? email : codexAccountRandomName(account);"));
assert!(response.contains("? compactAccountEmail(email)"));
assert!(response.contains("const account = codexAccountPoolAccounts(snapshot).find("));
Expand Down Expand Up @@ -594,6 +598,23 @@ fn operator_dashboard_account_privacy_controls_use_compact_identities() {
assert!(!response.contains("${pluralize(accounts.length, \"account\")} · ${activeCount} active"));
}

#[test]
fn operator_dashboard_account_errors_route_to_notice_dock_with_privacy() {
let response = dashboard_response();

assert!(response.contains("function codexAccountNotices(snapshot)"));
assert!(response.contains("for (const accountNotice of codexAccountNotices(snapshot))"));
assert!(response.contains("notices.push(accountNotice);"));
assert!(response.contains("function codexAccountHasNotice(account)"));
assert!(response.contains("function codexAccountNoticeCopy(account)"));
assert!(response.contains("return `${codexAccountPrivacyLabel(account)}: ${parts.join(\"; \")}`;"));
assert!(response.contains("codexAccountRefreshFailed(account) && !noteIncludesRefreshFailure"));
assert!(response.contains("codexAccountRefreshStatusNeedsAttention(refreshStatus) &&"));
assert!(response.contains("!codexAccountRefreshFailed(account)"));
assert!(response.contains("note && !noteLooksRoutine && !noteLooksError"));
assert!(response.contains("codexAccountPrivacyText(account, note)"));
}

#[test]
fn operator_dashboard_uses_expanded_section_titles() {
let response = dashboard_response();
Expand Down