forked from farhoodlabs/paperclip
[codex] Add runtime lifecycle recovery and live issue visibility (#4419)
This commit is contained in:
@@ -110,6 +110,8 @@ const runStatusIcons: Record<string, { icon: typeof CheckCircle2; color: string
|
||||
cancelled: { icon: Slash, color: "text-neutral-500 dark:text-neutral-400" },
|
||||
};
|
||||
|
||||
const RUN_LOG_PAGE_BYTES = 256_000;
|
||||
|
||||
const REDACTED_ENV_VALUE = "***REDACTED***";
|
||||
const SECRET_ENV_KEY_RE =
|
||||
/(api[-_]?key|access[-_]?token|auth(?:_?token)?|authorization|bearer|secret|passwd|password|credential|jwt|private[-_]?key|cookie|connectionstring)/i;
|
||||
@@ -3473,6 +3475,8 @@ function LogViewer({ run, adapterType }: { run: HeartbeatRun; adapterType: strin
|
||||
const [logLoading, setLogLoading] = useState(!!run.logRef);
|
||||
const [logError, setLogError] = useState<string | null>(null);
|
||||
const [logOffset, setLogOffset] = useState(0);
|
||||
const [hasMoreLog, setHasMoreLog] = useState(false);
|
||||
const [loadingMoreLog, setLoadingMoreLog] = useState(false);
|
||||
const [isFollowing, setIsFollowing] = useState(false);
|
||||
const [isStreamingConnected, setIsStreamingConnected] = useState(false);
|
||||
const [transcriptMode, setTranscriptMode] = useState<TranscriptMode>("nice");
|
||||
@@ -3627,6 +3631,8 @@ function LogViewer({ run, adapterType }: { run: HeartbeatRun; adapterType: strin
|
||||
pendingLogLineRef.current = "";
|
||||
setLogLines([]);
|
||||
setLogOffset(0);
|
||||
setHasMoreLog(false);
|
||||
setLoadingMoreLog(false);
|
||||
setLogError(null);
|
||||
|
||||
if (!run.logRef && !isLive) {
|
||||
@@ -3637,25 +3643,14 @@ function LogViewer({ run, adapterType }: { run: HeartbeatRun; adapterType: strin
|
||||
}
|
||||
|
||||
setLogLoading(true);
|
||||
const firstLimit =
|
||||
typeof run.logBytes === "number" && run.logBytes > 0
|
||||
? Math.min(Math.max(run.logBytes + 1024, 256_000), 2_000_000)
|
||||
: 256_000;
|
||||
|
||||
const load = async () => {
|
||||
try {
|
||||
let offset = 0;
|
||||
let first = true;
|
||||
while (!cancelled) {
|
||||
const result = await heartbeatsApi.log(run.id, offset, first ? firstLimit : 256_000);
|
||||
if (cancelled) break;
|
||||
appendLogContent(result.content, result.nextOffset === undefined);
|
||||
const next = result.nextOffset ?? offset + result.content.length;
|
||||
setLogOffset(next);
|
||||
offset = next;
|
||||
first = false;
|
||||
if (result.nextOffset === undefined || isLive) break;
|
||||
}
|
||||
const result = await heartbeatsApi.log(run.id, 0, RUN_LOG_PAGE_BYTES);
|
||||
if (cancelled) return;
|
||||
appendLogContent(result.content, result.nextOffset === undefined);
|
||||
const next = result.nextOffset ?? result.content.length;
|
||||
setLogOffset(next);
|
||||
setHasMoreLog(!isLive && result.nextOffset !== undefined);
|
||||
} catch (err) {
|
||||
if (!cancelled) {
|
||||
if (isLive && isRunLogUnavailable(err)) {
|
||||
@@ -3675,6 +3670,23 @@ function LogViewer({ run, adapterType }: { run: HeartbeatRun; adapterType: strin
|
||||
};
|
||||
}, [run.id, run.logRef, run.logBytes, isLive]);
|
||||
|
||||
async function loadMorePersistedLog() {
|
||||
if (loadingMoreLog || !hasMoreLog) return;
|
||||
setLoadingMoreLog(true);
|
||||
setLogError(null);
|
||||
try {
|
||||
const result = await heartbeatsApi.log(run.id, logOffset, RUN_LOG_PAGE_BYTES);
|
||||
appendLogContent(result.content, result.nextOffset === undefined);
|
||||
const next = result.nextOffset ?? logOffset + result.content.length;
|
||||
setLogOffset(next);
|
||||
setHasMoreLog(result.nextOffset !== undefined);
|
||||
} catch (err) {
|
||||
setLogError(err instanceof Error ? err.message : "Failed to load more run log");
|
||||
} finally {
|
||||
setLoadingMoreLog(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Poll for live updates
|
||||
useEffect(() => {
|
||||
if (!isLive || isStreamingConnected) return;
|
||||
@@ -3941,6 +3953,25 @@ function LogViewer({ run, adapterType }: { run: HeartbeatRun; adapterType: strin
|
||||
streaming={isLive}
|
||||
emptyMessage={run.logRef ? "Waiting for transcript..." : "No persisted transcript for this run."}
|
||||
/>
|
||||
{hasMoreLog && (
|
||||
<div className="mt-3 flex flex-wrap items-center gap-2 border-t border-border/60 pt-3">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="xs"
|
||||
onClick={loadMorePersistedLog}
|
||||
disabled={loadingMoreLog}
|
||||
>
|
||||
{loadingMoreLog ? "Loading..." : "Load more log"}
|
||||
</Button>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
Showing the first {Math.round(logOffset / 1024).toLocaleString("en-US")} KB
|
||||
{typeof run.logBytes === "number" && run.logBytes > 0
|
||||
? ` of ${Math.round(run.logBytes / 1024).toLocaleString("en-US")} KB`
|
||||
: ""}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{logError && (
|
||||
<div className="mt-3 rounded-xl border border-red-500/20 bg-red-500/[0.06] px-3 py-2 text-xs text-red-700 dark:text-red-300">
|
||||
{logError}
|
||||
|
||||
Reference in New Issue
Block a user