backup current stable ejclaw state
This commit is contained in:
@@ -34,6 +34,7 @@ import {
|
||||
refreshAllCodexAccountUsage,
|
||||
} from './codex-usage-collector.js';
|
||||
import { runCodexWarmupCycle } from './codex-warmup.js';
|
||||
import { evaluateAndApplyAutoPairedMode } from './auto-paired-mode.js';
|
||||
import {
|
||||
composeDashboardContent,
|
||||
formatElapsed,
|
||||
@@ -448,12 +449,44 @@ function buildStatusContent(): string {
|
||||
* Ordering: Claude rows → separator → Codex rows.
|
||||
* Exported for testing.
|
||||
*/
|
||||
/**
|
||||
* A row is considered "exhausted" (1% or less remaining in any window) when
|
||||
* either the 5h or 7d utilization is at or above 99%. Rows where a window's
|
||||
* percentage is unknown (h5pct/d7pct < 0) aren't counted toward exhaustion
|
||||
* for that window.
|
||||
*/
|
||||
function isExhaustedRow(row: UsageRow): boolean {
|
||||
const h5Out = row.h5pct >= 99;
|
||||
const d7Out = row.d7pct >= 99;
|
||||
return h5Out || d7Out;
|
||||
}
|
||||
|
||||
function allRowsExhausted(rows: UsageRow[]): boolean {
|
||||
if (rows.length === 0) return false;
|
||||
return rows.every(isExhaustedRow);
|
||||
}
|
||||
|
||||
export function renderUsageTable(
|
||||
claudeBotRows: UsageRow[],
|
||||
codexBotRows: UsageRow[],
|
||||
): string[] {
|
||||
const allRows = [...claudeBotRows, ...codexBotRows];
|
||||
if (allRows.length === 0) return ['_조회 불가_'];
|
||||
// Per-source status: 조회 불가 (no rows) vs 사용량 없음 (all exhausted)
|
||||
const claudeStatus: 'ok' | 'unavailable' | 'exhausted' =
|
||||
claudeBotRows.length === 0
|
||||
? 'unavailable'
|
||||
: allRowsExhausted(claudeBotRows)
|
||||
? 'exhausted'
|
||||
: 'ok';
|
||||
const codexStatus: 'ok' | 'unavailable' | 'exhausted' =
|
||||
codexBotRows.length === 0
|
||||
? 'unavailable'
|
||||
: allRowsExhausted(codexBotRows)
|
||||
? 'exhausted'
|
||||
: 'ok';
|
||||
|
||||
const renderableClaude = claudeStatus === 'ok' ? claudeBotRows : [];
|
||||
const renderableCodex = codexStatus === 'ok' ? codexBotRows : [];
|
||||
const allRenderable = [...renderableClaude, ...renderableCodex];
|
||||
|
||||
const bar = (pct: number) => {
|
||||
const filled = Math.max(0, Math.min(5, Math.round(pct / 20)));
|
||||
@@ -463,12 +496,15 @@ export function renderUsageTable(
|
||||
const visualWidth = (s: string) =>
|
||||
[...s].reduce((w, c) => w + (c.codePointAt(0)! > 0x7f ? 2 : 1), 0);
|
||||
const maxNameWidth =
|
||||
Math.max(8, ...allRows.map((r) => visualWidth(r.name))) + 1;
|
||||
Math.max(8, ...allRenderable.map((r) => visualWidth(r.name))) + 1;
|
||||
const padName = (s: string) =>
|
||||
s + ' '.repeat(Math.max(0, maxNameWidth - visualWidth(s)));
|
||||
const compactReset = (s: string) =>
|
||||
s ? s.replace(/\s+/g, '').replace(/m$/, '') : '';
|
||||
|
||||
const statusLabel = (status: 'unavailable' | 'exhausted'): string =>
|
||||
status === 'unavailable' ? '조회 불가' : '사용량 없음';
|
||||
|
||||
const lines: string[] = [];
|
||||
|
||||
const renderRows = (rows: UsageRow[]) => {
|
||||
@@ -498,14 +534,22 @@ export function renderUsageTable(
|
||||
lines.push('```');
|
||||
lines.push(`${' '.repeat(maxNameWidth)}5h 7d`);
|
||||
|
||||
renderRows(claudeBotRows);
|
||||
|
||||
if (claudeBotRows.length > 0 && codexBotRows.length > 0) {
|
||||
const separatorWidth = maxNameWidth + 20;
|
||||
lines.push('─'.repeat(separatorWidth));
|
||||
// Claude section
|
||||
if (claudeStatus === 'ok') {
|
||||
renderRows(claudeBotRows);
|
||||
} else {
|
||||
lines.push(`${padName('Claude')}${statusLabel(claudeStatus)}`);
|
||||
}
|
||||
|
||||
renderRows(codexBotRows);
|
||||
const separatorWidth = maxNameWidth + 20;
|
||||
lines.push('─'.repeat(separatorWidth));
|
||||
|
||||
// Codex section
|
||||
if (codexStatus === 'ok') {
|
||||
renderRows(codexBotRows);
|
||||
} else {
|
||||
lines.push(`${padName('Codex')}${statusLabel(codexStatus)}`);
|
||||
}
|
||||
|
||||
lines.push('```');
|
||||
|
||||
@@ -751,9 +795,13 @@ export async function startUnifiedDashboard(
|
||||
}
|
||||
|
||||
if (statusMessageId && channel.editMessage) {
|
||||
await channel.editMessage(statusJid, statusMessageId, content);
|
||||
await channel.editMessage(statusJid, statusMessageId, content, {
|
||||
rawMarkdown: true,
|
||||
});
|
||||
} else if (channel.sendAndTrack) {
|
||||
const id = await channel.sendAndTrack(statusJid, content);
|
||||
const id = await channel.sendAndTrack(statusJid, content, {
|
||||
rawMarkdown: true,
|
||||
});
|
||||
if (id) statusMessageId = id;
|
||||
}
|
||||
if (!dashboardUpdateLogged) {
|
||||
@@ -773,7 +821,29 @@ export async function startUnifiedDashboard(
|
||||
await updateStatus();
|
||||
|
||||
if (isRenderer) {
|
||||
setInterval(refreshUsageCache, RENDERER_USAGE_REFRESH_MS);
|
||||
const evaluatePairedMode = async () => {
|
||||
try {
|
||||
await evaluateAndApplyAutoPairedMode({
|
||||
queue: opts.queue,
|
||||
sendNotice: async (jid, text) => {
|
||||
const ch = opts.channels.find(
|
||||
(c) =>
|
||||
c.name.startsWith('discord') &&
|
||||
c.isConnected() &&
|
||||
c.ownsJid(jid),
|
||||
);
|
||||
if (ch) await ch.sendMessage(jid, text);
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
logger.warn({ err }, 'auto-paired-mode evaluation failed');
|
||||
}
|
||||
};
|
||||
setInterval(async () => {
|
||||
await refreshUsageCache();
|
||||
await evaluatePairedMode();
|
||||
}, RENDERER_USAGE_REFRESH_MS);
|
||||
await evaluatePairedMode();
|
||||
}
|
||||
|
||||
// Codex usage collection — runs in unified service regardless of renderer role.
|
||||
|
||||
Reference in New Issue
Block a user