{sanitizeInboxText(item.summary) || t.inbox.noSummary}
+Offline
+네트워크가 복구되면 대시보드를 다시 불러옵니다.
+ +diff --git a/.env.example b/.env.example index 2727de8..23e11f6 100644 --- a/.env.example +++ b/.env.example @@ -36,6 +36,22 @@ STATUS_CHANNEL_ID= # Discord channel ID for live status updat # STATUS_SHOW_ROOMS=true # Show room status in dashboard # STATUS_SHOW_ROOM_DETAILS=false # Show detailed room info +# --- Codex warm-up (optional, disabled by default) --- +# Sends a tiny real Codex prompt only when both 5h and 7d usage are 0%, so +# reset countdown starts once after a fresh quota window. Keep disabled if +# OpenAI changes policy or starts blocking automated warm-up prompts. +# Normally only set this one line; the other knobs have conservative defaults. +# CODEX_WARMUP_ENABLED=false +# CODEX_WARMUP_PROMPT=Reply exactly OK. Do not run tools. +# CODEX_WARMUP_MODEL= # Defaults to CODEX_MODEL +# CODEX_WARMUP_INTERVAL_MS=300000 # Check every 5min +# CODEX_WARMUP_MIN_INTERVAL_MS=18300000 # Per-account minimum gap: 5h5m +# CODEX_WARMUP_STAGGER_MS=1800000 # Global gap between accounts: 30min +# CODEX_WARMUP_MAX_USAGE_PCT=0 # Only warm accounts at/below this 5h usage +# CODEX_WARMUP_MAX_D7_USAGE_PCT=0 # Only warm accounts at/below this 7d usage +# CODEX_WARMUP_MAX_CONSECUTIVE_FAILURES=2 +# CODEX_WARMUP_FAILURE_COOLDOWN_MS=21600000 # Back off 6h after repeated failure + # --- Fallback provider (when Claude/Codex both unavailable) --- # FALLBACK_ENABLED=true # Enable fallback provider # FALLBACK_PROVIDER_NAME=kimi # Provider name for logging diff --git a/apps/dashboard/index.html b/apps/dashboard/index.html new file mode 100644 index 0000000..3fdc00b --- /dev/null +++ b/apps/dashboard/index.html @@ -0,0 +1,22 @@ + + +
+ + + + + + + + + +네트워크가 복구되면 대시보드를 다시 불러옵니다.
+ +
+ {s.value}
+ ,
+ );
+ return;
+ }
+ if (s.kind === 'bold') {
+ usedRef.used += s.value.length;
+ out.push({s.value});
+ return;
+ }
+ if (s.kind === 'italic') {
+ usedRef.used += s.value.length;
+ out.push({s.value});
+ return;
+ }
+ if (s.kind === 'strike') {
+ usedRef.used += s.value.length;
+ out.push(
+ {s.value}
+ ,
+ );
+ return;
+ }
+ if (s.kind === 'marker') {
+ usedRef.used += s.value.length;
+ out.push(
+
+ {s.value}
+ ,
+ );
+ return;
+ }
+ });
+ return out;
+}
+
+function ParsedBody({
+ text,
+ truncate,
+}: {
+ text: string | null | undefined;
+ truncate?: number;
+}): ReactNode {
+ const cleaned = useMemo(() => {
+ if (!text) return '';
+ let out = text
+ .replace(SECRET_ASSIGNMENT_RE, (_m, key) => `${key}=***`)
+ .replace(SECRET_VALUE_RE, '***');
+ if (truncate && out.length > truncate) {
+ out = out.slice(0, truncate) + '…';
+ }
+ return out;
+ }, [text, truncate]);
+
+ if (!cleaned.trim()) {
+ return —;
+ }
+
+ return (
+ {error}
: null} + {!draft ? ( ++ {busy ? '불러오는 중…' : '모델 정보 없음'} +
+ ) : ( + <> + {(['owner', 'reviewer', 'arbiter'] as const).map((role) => ( +{error}
: null} + +{busy ? '불러오는 중…' : '없음'}
+ ) : data.claude.length === 0 ? ( +계정 없음
+ ) : ( +{busy ? '불러오는 중…' : '없음'}
+ ) : data.codex.length === 0 ? ( +계정 없음
+ ) : ( +
+ Codex 계정 추가는 codex login CLI로 진행한 뒤
+ ~/.codex-accounts/N/ 디렉터리에 auth.json 파일이 생성되면 됩니다.
+
{sanitizeInboxText(item.summary) || t.inbox.noSummary}
+{restart.error}
+ ) : null} +{latestOutput.outputText}
+ ) : ( +{t.rooms.noOutput}
+ )} ++ {task?.title || task?.id || t.rooms.noTask} +
+{t.rooms.noOutput}
+ ) : ( +{output.outputText}
+{t.rooms.noMessages}
+ ) : ( +{message.content}
+| {t.rooms.room} | +{t.rooms.status} | +{t.rooms.queue} | +{t.rooms.elapsed} | +{t.rooms.activity} | +{t.rooms.message} | +
|---|---|---|---|---|---|
|
+ {entry.name}
+
+
+ {t.rooms.details}+ + {entry.folder} · {entry.jid} · {entry.serviceId} ·{' '} + {entry.agentType} + + |
+ + + {statusLabel(entry.status, t)} + + | ++ {queueLabel(entry.pendingTasks, entry.pendingMessages, t)} + | +{formatDuration(entry.elapsedMs, t)} | +
+ |
+
+ |
+
+ {entry.folder} · {entry.jid} · {entry.serviceId} ·{' '} + {entry.agentType} +
+{turn.progressText}
+ ) : turn.activeRunId ? (
+ {turn.activeRunId}
+ ) : null}
+ {t.rooms.noActivity}
+ ) : null} + + {!expanded && activityLoading && !activity ? ( +{t.rooms.loadingActivity}
+ ) : null} + + {expanded ? ( +{t.rooms.noActivity}
+ ) : ( +
+ {liveProgressDisplay}
+
+ ) : (
+ {t.rooms.loadingActivity}
+ )} ++ {safePreview(task.promptPreview, t.tasks.emptyPrompt)} +
+ + {task.id} · {task.contextMode} · {task.promptLength}{' '} + {t.units.chars} + +