Repository navigation
feat(lastcode): detect and recover stale Codex and Claude runs #282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
b2c7842
feat(lastcode): recover stale Codex and Claude runs
lastobelus 55a3bf7
fix(lastcode): preserve actionable recovery across lifecycle failures
lastobelus a041e56
fix(lastcode): guard recovery notices against superseded runs
lastobelus 793c0b5
fix(lastcode): require a durable provider turn before recovery
lastobelus a7b6815
fix(lastcode): retain successor recovery registrations
lastobelus d693c1a
fix(lastcode): create repair threads with valid user provenance
lastobelus a74445e
fix(lastcode): clear recovery when queued successors start
lastobelus ed1439f
fix(lastcode): synchronize repair navigation and encode recovery details
lastobelus 5dad786
fix(mcp): label thread recovery tools
lastobelus 0424a0a
fix(ci): align recovery summaries and migration expectations
lastobelus fa0acaa
fix(ci): correct migration ledger tests and repair wait error
lastobelus e1a0aad
fix(recovery): preserve linked repair conversations on retry
lastobelus b44daca
fix(recovery): reject active probes from released provider sessions
lastobelus fe3872a
fix(recovery): keep exhausted incidents from delayed suspicion
lastobelus 94a327b
fix(test): retain literal MCP capability type
lastobelus 8ebc699
fix(recovery): validate repair links and serialize recovery launches
lastobelus fed854b
docs(agents): finish findings from the final local review
lastobelus bbd64dd
fix(server): safely accept repairs and settle released recovery work
lastobelus e3829a6
fix(server): drain native completion events before recovery settlement
lastobelus acd71d7
fix(server): close released recovery readers and preserve restarts
lastobelus 1ef30ab
fix(server): drain background work after every recovered terminal
lastobelus fd74fe6
fix(server): retain terminal evidence across unrelated completions
lastobelus e25218b
fix(server): release terminal evidence after durable finalization
lastobelus f3bfbd4
fix(mcp): align recovery with current caller scopes
lastobelus 89bb2d4
fix(clients): queue follow-ups while thread recovery is active
lastobelus 0eb8b8b
fix(server): retry recovered background settlement after write failures
lastobelus File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
apps/mobile/src/features/threads/ThreadRecoveryNotice.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| import { useNavigation } from "@react-navigation/native"; | ||
| import { presentThreadRecovery } from "@t3tools/client-runtime/state/thread-recovery"; | ||
| import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime"; | ||
| import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell"; | ||
| import type { EnvironmentId } from "@t3tools/contracts"; | ||
| import { useEffect, useLayoutEffect, useRef, useState } from "react"; | ||
| import { View } from "react-native"; | ||
| import { AppText as Text } from "../../components/AppText"; | ||
| import { ControlPill } from "../../components/ControlPill"; | ||
| import { useThreadShell } from "../../state/entities"; | ||
| import { threadEnvironment } from "../../state/threads"; | ||
| import { useAtomCommand } from "../../state/use-atom-command"; | ||
|
|
||
| export function ThreadRecoveryNotice({ | ||
| thread, | ||
| environmentId, | ||
| }: { | ||
| thread: EnvironmentThreadShell; | ||
| environmentId: EnvironmentId; | ||
| }) { | ||
| const navigation = useNavigation(); | ||
| const recover = useAtomCommand(threadEnvironment.recoverThread, { reportFailure: false }); | ||
| const repair = useAtomCommand(threadEnvironment.repairThread, { reportFailure: false }); | ||
| const [pendingKey, setPendingKey] = useState<string | null>(null); | ||
| const [error, setError] = useState<{ key: string; message: string } | null>(null); | ||
| const [dismissedKey, setDismissedKey] = useState<string | null>(null); | ||
| const recovery = thread.recovery; | ||
| const repairThread = useThreadShell( | ||
| recovery?.repairThreadId ? { environmentId, threadId: recovery.repairThreadId } : null, | ||
| ); | ||
| const key = `${environmentId}:${thread.id}:${recovery?.runId}:${recovery?.attemptId}`; | ||
| const mounted = useRef(true); | ||
| useEffect(() => { | ||
| mounted.current = true; | ||
| return () => { | ||
| mounted.current = false; | ||
| }; | ||
| }, []); | ||
| const currentKey = useRef(key); | ||
| useLayoutEffect(() => { | ||
| currentKey.current = key; | ||
| }, [key]); | ||
| const presentation = presentThreadRecovery( | ||
| recovery, | ||
| repairThread !== null && repairThread.deletedAt === null, | ||
| ); | ||
| if (!recovery || !presentation || (dismissedKey === key && recovery.status === "recovered")) | ||
| return null; | ||
| const pending = pendingKey === key; | ||
| const act = async () => { | ||
| if (pending || presentation.busy) return; | ||
| setPendingKey(key); | ||
| setError(null); | ||
| const input = { threadId: thread.id, runId: recovery.runId, attemptId: recovery.attemptId }; | ||
| try { | ||
| if (presentation.action === "launch-repair" || presentation.action === "view-repair") { | ||
| const result = await repair({ environmentId, input }); | ||
| if (result._tag === "Failure") throw squashAtomCommandFailure(result); | ||
| if (mounted.current && currentKey.current === key && navigation.isFocused()) | ||
| navigation.navigate("Thread", { environmentId, threadId: result.value.threadId }); | ||
| } else { | ||
| const result = await recover({ environmentId, input }); | ||
| if (result._tag === "Failure") throw squashAtomCommandFailure(result); | ||
| } | ||
| } catch (cause) { | ||
| setError({ | ||
| key, | ||
| message: cause instanceof Error ? cause.message : "Could not recover this run.", | ||
| }); | ||
| } finally { | ||
| setPendingKey((value) => (value === key ? null : value)); | ||
| } | ||
| }; | ||
| return ( | ||
| <View className="mx-3 mb-2 gap-2 rounded-xl border border-warning-foreground/25 bg-background p-3"> | ||
| <Text | ||
| accessibilityLiveRegion="polite" | ||
| accessibilityRole="header" | ||
| className="text-sm font-t3-bold text-foreground" | ||
| > | ||
| {presentation.title} | ||
| </Text> | ||
| <Text className="text-xs text-muted-foreground">{presentation.description}</Text> | ||
| <View className="flex-row justify-end"> | ||
| {presentation.label ? ( | ||
| <ControlPill | ||
| label={ | ||
| pending | ||
| ? presentation.action === "launch-repair" || presentation.action === "view-repair" | ||
| ? "Opening…" | ||
| : recovery.status === "suspect" | ||
| ? "Checking…" | ||
| : "Recovering…" | ||
| : presentation.label | ||
| } | ||
| disabled={pending || presentation.busy} | ||
| onPress={() => void act()} | ||
| variant="pill" | ||
| /> | ||
| ) : ( | ||
| <ControlPill label="Dismiss" onPress={() => setDismissedKey(key)} variant="pill" /> | ||
| )} | ||
| </View> | ||
| {error?.key === key ? ( | ||
| <Text accessibilityRole="alert" className="text-sm text-destructive"> | ||
| {error.message} | ||
| </Text> | ||
| ) : null} | ||
| </View> | ||
| ); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.