backup current stable ejclaw state

This commit is contained in:
Codex
2026-05-27 10:53:05 +09:00
parent 646bc34372
commit 1509108e04
57 changed files with 7127 additions and 154 deletions

View File

@@ -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.