mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
Merge pull request #868 from RooVetGit/cte/current-checkpoint-indicator
Current checkpoint indicator
This commit is contained in:
commit
d65e9efc89
8 changed files with 66 additions and 26 deletions
|
|
@ -3313,6 +3313,10 @@ export class Cline {
|
|||
const commit = await service.saveCheckpoint(`Task: ${this.taskId}, Time: ${Date.now()}`)
|
||||
|
||||
if (commit?.commit) {
|
||||
await this.providerRef
|
||||
.deref()
|
||||
?.postMessageToWebview({ type: "currentCheckpointUpdated", text: service.currentCheckpoint })
|
||||
|
||||
await this.say("checkpoint_saved", commit.commit)
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -3349,6 +3353,10 @@ export class Cline {
|
|||
const service = await this.getCheckpointService()
|
||||
await service.restoreCheckpoint(commitHash)
|
||||
|
||||
await this.providerRef
|
||||
.deref()
|
||||
?.postMessageToWebview({ type: "currentCheckpointUpdated", text: service.currentCheckpoint })
|
||||
|
||||
if (mode === "restore") {
|
||||
await this.overwriteApiConversationHistory(
|
||||
this.apiConversationHistory.filter((m) => !m.ts || m.ts < ts),
|
||||
|
|
|
|||
|
|
@ -9,12 +9,6 @@ if (process.env.NODE_ENV !== "test") {
|
|||
debug.enable("simple-git")
|
||||
}
|
||||
|
||||
export interface Checkpoint {
|
||||
hash: string
|
||||
message: string
|
||||
timestamp?: Date
|
||||
}
|
||||
|
||||
export type CheckpointServiceOptions = {
|
||||
taskId: string
|
||||
git?: SimpleGit
|
||||
|
|
@ -60,6 +54,16 @@ export type CheckpointServiceOptions = {
|
|||
*/
|
||||
|
||||
export class CheckpointService {
|
||||
private _currentCheckpoint?: string
|
||||
|
||||
public get currentCheckpoint() {
|
||||
return this._currentCheckpoint
|
||||
}
|
||||
|
||||
private set currentCheckpoint(value: string | undefined) {
|
||||
this._currentCheckpoint = value
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly taskId: string,
|
||||
private readonly git: SimpleGit,
|
||||
|
|
@ -217,6 +221,8 @@ export class CheckpointService {
|
|||
await this.popStash()
|
||||
}
|
||||
|
||||
this.currentCheckpoint = commit.commit
|
||||
|
||||
return commit
|
||||
} catch (err) {
|
||||
this.log(`[saveCheckpoint] Failed to save checkpoint: ${err instanceof Error ? err.message : String(err)}`)
|
||||
|
|
@ -237,6 +243,7 @@ export class CheckpointService {
|
|||
await this.ensureBranch(this.mainBranch)
|
||||
await this.git.clean([CleanOptions.FORCE, CleanOptions.RECURSIVE])
|
||||
await this.git.raw(["restore", "--source", commitHash, "--worktree", "--", "."])
|
||||
this.currentCheckpoint = commitHash
|
||||
}
|
||||
|
||||
public static async create({ taskId, git, baseDir, log = console.log }: CheckpointServiceOptions) {
|
||||
|
|
@ -291,7 +298,7 @@ export class CheckpointService {
|
|||
// the checkpoint (i.e. the `git restore` command doesn't work
|
||||
// for empty commits).
|
||||
await fs.writeFile(path.join(baseDir, ".gitkeep"), "")
|
||||
await git.add(".")
|
||||
await git.add(".gitkeep")
|
||||
const commit = await git.commit("Initial commit")
|
||||
|
||||
if (!commit.commit) {
|
||||
|
|
|
|||
|
|
@ -291,6 +291,7 @@ describe("CheckpointService", () => {
|
|||
const baseDir = path.join(os.tmpdir(), `checkpoint-service-test2-${Date.now()}`)
|
||||
await fs.mkdir(baseDir)
|
||||
const newTestFile = path.join(baseDir, "test.txt")
|
||||
await fs.writeFile(newTestFile, "Hello, world!")
|
||||
|
||||
const newGit = simpleGit(baseDir)
|
||||
const initSpy = jest.spyOn(newGit, "init")
|
||||
|
|
@ -300,7 +301,6 @@ describe("CheckpointService", () => {
|
|||
expect(initSpy).toHaveBeenCalled()
|
||||
|
||||
// Save a checkpoint: Hello, world!
|
||||
await fs.writeFile(newTestFile, "Hello, world!")
|
||||
const commit1 = await newService.saveCheckpoint("Hello, world!")
|
||||
expect(commit1?.commit).toBeTruthy()
|
||||
expect(await fs.readFile(newTestFile, "utf-8")).toBe("Hello, world!")
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ export interface ExtensionMessage {
|
|||
| "autoApprovalEnabled"
|
||||
| "updateCustomMode"
|
||||
| "deleteCustomMode"
|
||||
| "currentCheckpointUpdated"
|
||||
text?: string
|
||||
action?:
|
||||
| "chatButtonClicked"
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ export const ChatRowContent = ({
|
|||
isLast,
|
||||
isStreaming,
|
||||
}: ChatRowContentProps) => {
|
||||
const { mcpServers, alwaysAllowMcp } = useExtensionState()
|
||||
const { mcpServers, alwaysAllowMcp, currentCheckpoint } = useExtensionState()
|
||||
const [reasoningCollapsed, setReasoningCollapsed] = useState(false)
|
||||
|
||||
// Auto-collapse reasoning when new messages arrive
|
||||
|
|
@ -757,7 +757,13 @@ export const ChatRowContent = ({
|
|||
</>
|
||||
)
|
||||
case "checkpoint_saved":
|
||||
return <CheckpointSaved ts={message.ts!} commitHash={message.text!} />
|
||||
return (
|
||||
<CheckpointSaved
|
||||
ts={message.ts!}
|
||||
commitHash={message.text!}
|
||||
currentCheckpointHash={currentCheckpoint}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -8,13 +8,16 @@ import { Button, Popover, PopoverContent, PopoverTrigger } from "@/components/ui
|
|||
type CheckpointMenuProps = {
|
||||
ts: number
|
||||
commitHash: string
|
||||
currentCheckpointHash?: string
|
||||
}
|
||||
|
||||
export const CheckpointMenu = ({ ts, commitHash }: CheckpointMenuProps) => {
|
||||
export const CheckpointMenu = ({ ts, commitHash, currentCheckpointHash }: CheckpointMenuProps) => {
|
||||
const [portalContainer, setPortalContainer] = useState<HTMLElement>()
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [isConfirming, setIsConfirming] = useState(false)
|
||||
|
||||
const isCurrent = currentCheckpointHash === commitHash
|
||||
|
||||
const onCheckpointDiff = useCallback(() => {
|
||||
vscode.postMessage({ type: "checkpointDiff", payload: { ts, commitHash, mode: "checkpoint" } })
|
||||
}, [ts, commitHash])
|
||||
|
|
@ -56,14 +59,16 @@ export const CheckpointMenu = ({ ts, commitHash }: CheckpointMenuProps) => {
|
|||
</PopoverTrigger>
|
||||
<PopoverContent align="end" container={portalContainer}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-1 group hover:text-foreground">
|
||||
<Button variant="secondary" onClick={onPreview}>
|
||||
Restore Files
|
||||
</Button>
|
||||
<div className="text-muted transition-colors group-hover:text-foreground">
|
||||
Restores your project's files back to a snapshot taken at this point.
|
||||
{!isCurrent && (
|
||||
<div className="flex flex-col gap-1 group hover:text-foreground">
|
||||
<Button variant="secondary" onClick={onPreview}>
|
||||
Restore Files
|
||||
</Button>
|
||||
<div className="text-muted transition-colors group-hover:text-foreground">
|
||||
Restores your project's files back to a snapshot taken at this point.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-1 group hover:text-foreground">
|
||||
<div className="flex flex-col gap-1 group hover:text-foreground">
|
||||
{!isConfirming ? (
|
||||
|
|
|
|||
|
|
@ -3,14 +3,20 @@ import { CheckpointMenu } from "./CheckpointMenu"
|
|||
type CheckpointSavedProps = {
|
||||
ts: number
|
||||
commitHash: string
|
||||
currentCheckpointHash?: string
|
||||
}
|
||||
|
||||
export const CheckpointSaved = (props: CheckpointSavedProps) => (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="codicon codicon-git-commit text-blue-400" />
|
||||
<span className="font-bold">Checkpoint</span>
|
||||
export const CheckpointSaved = (props: CheckpointSavedProps) => {
|
||||
const isCurrent = props.currentCheckpointHash === props.commitHash
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex gap-2">
|
||||
<span className="codicon codicon-git-commit text-blue-400" />
|
||||
<span className="font-bold">Checkpoint</span>
|
||||
{isCurrent && <span className="text-muted text-sm">Current</span>}
|
||||
</div>
|
||||
<CheckpointMenu {...props} />
|
||||
</div>
|
||||
<CheckpointMenu {...props} />
|
||||
</div>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ export interface ExtensionStateContextType extends ExtensionState {
|
|||
openRouterModels: Record<string, ModelInfo>
|
||||
openAiModels: string[]
|
||||
mcpServers: McpServer[]
|
||||
currentCheckpoint?: string
|
||||
filePaths: string[]
|
||||
openedTabs: Array<{ label: string; isActive: boolean; path?: string }>
|
||||
setApiConfiguration: (config: ApiConfiguration) => void
|
||||
|
|
@ -126,6 +127,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
|
||||
const [openAiModels, setOpenAiModels] = useState<string[]>([])
|
||||
const [mcpServers, setMcpServers] = useState<McpServer[]>([])
|
||||
const [currentCheckpoint, setCurrentCheckpoint] = useState<string>()
|
||||
|
||||
const setListApiConfigMeta = useCallback(
|
||||
(value: ApiConfigMeta[]) => setState((prevState) => ({ ...prevState, listApiConfigMeta: value })),
|
||||
|
|
@ -241,6 +243,10 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
setMcpServers(message.mcpServers ?? [])
|
||||
break
|
||||
}
|
||||
case "currentCheckpointUpdated": {
|
||||
setCurrentCheckpoint(message.text)
|
||||
break
|
||||
}
|
||||
case "listApiConfig": {
|
||||
setListApiConfigMeta(message.listApiConfig ?? [])
|
||||
break
|
||||
|
|
@ -265,6 +271,7 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode
|
|||
openRouterModels,
|
||||
openAiModels,
|
||||
mcpServers,
|
||||
currentCheckpoint,
|
||||
filePaths,
|
||||
openedTabs,
|
||||
soundVolume: state.soundVolume,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue