import * as gitHttp from './gitApiHttp'; import { opencodeClient } from './opencode/client'; import { renderMagicPrompt } from './magicPrompts'; import { useSessionUIStore } from '@/sync/session-ui-store'; import { useContextStore } from '@/stores/contextStore'; import { useConfigStore } from '@/stores/useConfigStore'; import { getRegisteredRuntimeAPIs } from '@/contexts/runtimeAPIRegistry'; export type { GitStatus, GitDiffResponse, GetGitDiffOptions, GitBranchDetails, GitBranch, GitCommitResult, GitPushResult, GitPullResult, GitIdentityProfile, GitIdentityAuthType, GitIdentitySummary, GitLogEntry, GitLogResponse, GitWorktreeInfo, CreateGitWorktreePayload, GitWorktreeCreateResult, RemoveGitWorktreePayload, GitWorktreeValidationError, GitWorktreeValidationResult, GitDeleteBranchPayload, GitDeleteRemoteBranchPayload, GitRemoveRemotePayload, DiscoveredGitCredential, GitRemote, GitMergeResult, GitRebaseResult, MergeConflictDetails, CommitFileDiffResponse, } from './api/types'; const getRuntimeGit = () => { return getRegisteredRuntimeAPIs()?.git ?? null; }; const requestChatForceScrollBottom = (sessionId: string) => { if (typeof window === 'undefined') return; window.dispatchEvent(new CustomEvent('openchamber:chat-force-scroll-bottom', { detail: { sessionId }, })); }; const extractJsonObject = (value: string): Record | null => { const text = value.trim(); const fenced = text.match(/```(?:json)?\s*([\s\S]*?)```/i); const candidate = (fenced?.[1] ?? text).trim(); const starts = [candidate.indexOf('{')].filter((index) => index >= 0); for (const start of starts) { for (let end = candidate.length; end > start; end -= 1) { if (candidate[end - 1] !== '}') continue; try { const parsed = JSON.parse(candidate.slice(start, end)) as unknown; if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { return parsed as Record; } } catch { // Keep scanning; models sometimes wrap JSON with prose or fences. } } } return null; }; const extractAssistantText = (response: unknown): string => { const data = (response as { data?: { parts?: Array } } | null)?.data; const parts = Array.isArray(data?.parts) ? data.parts : []; return parts .map((part) => { const item = part as { type?: unknown; text?: unknown; content?: unknown; value?: unknown }; if (item.type !== 'text') return ''; if (typeof item.text === 'string') return item.text; if (typeof item.content === 'string') return item.content; if (typeof item.value === 'string') return item.value; return ''; }) .filter((text) => text.trim().length > 0) .join('\n') .trim(); }; export async function checkIsGitRepository(directory: string): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.checkIsGitRepository(directory); return gitHttp.checkIsGitRepository(directory); } export async function getGitStatus(directory: string, options?: { mode?: 'light' }): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.getGitStatus(directory, options); return gitHttp.getGitStatus(directory, options); } export async function getGitDiff(directory: string, options: import('./api/types').GetGitDiffOptions): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.getGitDiff(directory, options); return gitHttp.getGitDiff(directory, options); } export async function getGitFileDiff( directory: string, options: import('./api/types').GetGitFileDiffOptions ): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.getGitFileDiff(directory, options); return gitHttp.getGitFileDiff(directory, options); } export async function revertGitFile( directory: string, filePath: string, options?: { scope?: 'all' | 'working' } ): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.revertGitFile(directory, filePath, options); return gitHttp.revertGitFile(directory, filePath, options); } export async function stageGitFile(directory: string, filePath: string): Promise { const runtime = getRuntimeGit(); if (runtime?.stageGitFile) return runtime.stageGitFile(directory, filePath); return gitHttp.stageGitFile(directory, filePath); } export async function stageGitFiles(directory: string, filePaths: string[]): Promise { const runtime = getRuntimeGit(); if (runtime?.stageGitFiles) return runtime.stageGitFiles(directory, filePaths); return gitHttp.stageGitFiles(directory, filePaths); } export async function unstageGitFile(directory: string, filePath: string): Promise { const runtime = getRuntimeGit(); if (runtime?.unstageGitFile) return runtime.unstageGitFile(directory, filePath); return gitHttp.unstageGitFile(directory, filePath); } export async function unstageGitFiles(directory: string, filePaths: string[]): Promise { const runtime = getRuntimeGit(); if (runtime?.unstageGitFiles) return runtime.unstageGitFiles(directory, filePaths); return gitHttp.unstageGitFiles(directory, filePaths); } export async function isLinkedWorktree(directory: string): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.isLinkedWorktree(directory); return gitHttp.isLinkedWorktree(directory); } export async function getGitBranches(directory: string): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.getGitBranches(directory); return gitHttp.getGitBranches(directory); } export async function deleteGitBranch(directory: string, payload: import('./api/types').GitDeleteBranchPayload): Promise<{ success: boolean }> { const runtime = getRuntimeGit(); if (runtime) return runtime.deleteGitBranch(directory, payload); return gitHttp.deleteGitBranch(directory, payload); } export async function deleteRemoteBranch(directory: string, payload: import('./api/types').GitDeleteRemoteBranchPayload): Promise<{ success: boolean }> { const runtime = getRuntimeGit(); if (runtime) return runtime.deleteRemoteBranch(directory, payload); return gitHttp.deleteRemoteBranch(directory, payload); } export async function generateCommitMessage( directory: string, files: string[], options?: { zenModel?: string; providerId?: string; modelId?: string } ): Promise<{ message: import('./api/types').GeneratedCommitMessage }> { const startedAt = Date.now(); void options; const generationSession = resolveSessionGenerationContext(); if (!generationSession) { throw new Error('Select existing session for generation'); } console.info('[git-generation][browser] request', { transport: 'session', kind: 'commit', directory, selectedFiles: files.length, sessionId: generationSession.sessionId, providerId: generationSession.providerID, modelId: generationSession.modelID, agent: generationSession.agent, }); const visiblePrompt = await renderMagicPrompt('git.commit.generate.visible'); const hiddenPrompt = await renderMagicPrompt('git.commit.generate.instructions', { selected_files: files.map((file) => `- ${file}`).join('\n'), }); try { const structured = await runStructuredGenerationInActiveSession({ directory, visiblePrompt, hiddenPrompt, generationSession, kind: 'commit', }); const subject = typeof structured.subject === 'string' ? structured.subject.trim() : ''; const highlights = Array.isArray(structured.highlights) ? structured.highlights.filter((item) => typeof item === 'string').map((item) => item.trim()).filter(Boolean).slice(0, 3) : []; if (!subject) { throw new Error('Structured output missing subject'); } const result = { message: { subject, highlights } }; console.info('[git-generation][browser] success', { transport: 'session', kind: 'commit', elapsedMs: Date.now() - startedAt, subjectLength: result.message.subject.length, highlightsCount: result.message.highlights.length, }); return result; } catch (error) { console.error('[git-generation][browser] failed', { transport: 'session', kind: 'commit', elapsedMs: Date.now() - startedAt, message: error instanceof Error ? error.message : String(error), error, }); throw error; } } export async function generatePullRequestDescription( directory: string, payload: { base: string; head: string; context?: string; zenModel?: string; providerId?: string; modelId?: string } ): Promise { const startedAt = Date.now(); const generationSession = resolveSessionGenerationContext(); if (!generationSession) { throw new Error('Select existing session for generation'); } const commitLog = await getGitLog(directory, { from: payload.base, to: payload.head, maxCount: 50, }); const commits = (Array.isArray(commitLog?.all) ? commitLog.all : []) .filter((entry) => typeof entry?.hash === 'string' && entry.hash.length > 0) .map((entry) => ({ hash: entry.hash, subject: typeof entry.message === 'string' ? entry.message.trim() : '', })); if (commits.length === 0) { throw new Error(`No commits found in range ${payload.base}...${payload.head}`); } const filesSet = new Set(); await Promise.all(commits.map(async (commit) => { try { const response = await getCommitFiles(directory, commit.hash); const files = Array.isArray(response?.files) ? response.files : []; for (const file of files) { if (typeof file?.path === 'string' && file.path.trim().length > 0) { filesSet.add(file.path.trim()); } } } catch (error) { console.warn('[git-generation][browser] failed to collect commit files', { hash: commit.hash, error: error instanceof Error ? error.message : String(error), }); } })); const changedFiles = Array.from(filesSet).sort().slice(0, 300); console.info('[git-generation][browser] request', { transport: 'session', kind: 'pr', directory, sessionId: generationSession.sessionId, providerId: generationSession.providerID, modelId: generationSession.modelID, agent: generationSession.agent, base: payload.base, head: payload.head, commits: commits.length, changedFiles: changedFiles.length, }); const visiblePrompt = await renderMagicPrompt('git.pr.generate.visible'); const hiddenPrompt = await renderMagicPrompt('git.pr.generate.instructions', { base_branch: payload.base, head_branch: payload.head, commits: commits.map((commit) => `- ${commit.hash.slice(0, 7)} ${commit.subject || '(no subject)'}`).join('\n'), changed_files: changedFiles.length > 0 ? changedFiles.map((file) => `- ${file}`).join('\n') : '- none detected', additional_context_block: payload.context?.trim() ? `\nAdditional context:\n${payload.context.trim()}` : '', }); try { const structured = await runStructuredGenerationInActiveSession({ directory, visiblePrompt, hiddenPrompt, generationSession, kind: 'pr', }); const result = { title: typeof structured.title === 'string' ? structured.title.trim() : '', body: typeof structured.body === 'string' ? structured.body.trim() : '', }; console.info('[git-generation][browser] success', { transport: 'session', kind: 'pr', elapsedMs: Date.now() - startedAt, titleLength: result.title.length, bodyLength: result.body.length, }); return result; } catch (error) { console.error('[git-generation][browser] failed', { transport: 'session', kind: 'pr', elapsedMs: Date.now() - startedAt, message: error instanceof Error ? error.message : String(error), error, }); throw error; } } type SessionGenerationContext = { sessionId: string; providerID: string; modelID: string; agent?: string; variant?: string; }; const resolveSessionGenerationContext = (): SessionGenerationContext | null => { const sessionId = useSessionUIStore.getState().currentSessionId; if (!sessionId) { return null; } const context = useContextStore.getState(); const config = useConfigStore.getState(); const agent = context.getSessionAgentSelection(sessionId) || config.currentAgentName || undefined; const sessionModel = context.getSessionModelSelection(sessionId); const agentModel = agent ? context.getAgentModelForSession(sessionId, agent) : null; const selectedModel = agentModel || sessionModel || (config.currentProviderId && config.currentModelId ? { providerId: config.currentProviderId, modelId: config.currentModelId } : null); if (!selectedModel?.providerId || !selectedModel?.modelId) { return null; } const agentVariant = agent ? context.getAgentModelVariantForSession(sessionId, agent, selectedModel.providerId, selectedModel.modelId) : undefined; const variant = agentVariant || config.currentVariant || undefined; return { sessionId, providerID: selectedModel.providerId, modelID: selectedModel.modelId, agent, variant, }; }; const runStructuredGenerationInActiveSession = async ({ directory, visiblePrompt, hiddenPrompt, generationSession, kind, }: { directory: string; visiblePrompt: string; hiddenPrompt?: string; generationSession: SessionGenerationContext; kind: 'commit' | 'pr'; }): Promise> => { const requestStartedAt = Date.now(); console.info('[git-generation][browser] runStructuredGenerationInActiveSession start', { kind, directory, sessionId: generationSession.sessionId, providerID: generationSession.providerID, modelID: generationSession.modelID, agent: generationSession.agent, variant: generationSession.variant, }); const trimmedDirectory = typeof directory === 'string' ? directory.trim() : ''; const visiblePromptText = typeof visiblePrompt === 'string' ? visiblePrompt.trim() : ''; const hiddenPromptText = typeof hiddenPrompt === 'string' ? hiddenPrompt.trim() : ''; const promptParts: Array<{ type: 'text'; text: string; synthetic?: boolean }> = []; if (visiblePromptText) { promptParts.push({ type: 'text', text: hiddenPromptText ? `${visiblePromptText}\n\n` : visiblePromptText, synthetic: false, }); } if (hiddenPromptText) { promptParts.push({ type: 'text', text: hiddenPromptText, synthetic: true }); } if (promptParts.length === 0) { throw new Error('Generation prompts are empty'); } requestChatForceScrollBottom(generationSession.sessionId); const response = await opencodeClient.withDirectory(directory, async () => { return opencodeClient.getApiClient().session.prompt({ sessionID: generationSession.sessionId, ...(trimmedDirectory.length > 0 ? { directory: trimmedDirectory } : {}), model: { providerID: generationSession.providerID, modelID: generationSession.modelID, }, ...(generationSession.agent ? { agent: generationSession.agent } : {}), ...(generationSession.variant ? { variant: generationSession.variant } : {}), parts: promptParts, }); }); const responseError = response?.error as { message?: string } | undefined; if (!response?.data) { throw new Error(responseError?.message || `Failed to generate ${kind} output`); } const info = response.data.info as { finish?: string; error?: unknown }; const assistantText = extractAssistantText(response); const parsedOutput = extractJsonObject(assistantText); if (!parsedOutput) { console.error('[git-generation][browser] invalid JSON output', { kind, sessionId: generationSession.sessionId, elapsedMs: Date.now() - requestStartedAt, finish: info?.finish, assistantText, messageInfo: response.data.info, messageParts: response.data.parts, }); throw new Error('No JSON output returned by session'); } return parsedOutput; }; export async function listGitWorktrees(directory: string): Promise { const runtime = getRuntimeGit(); if (runtime?.worktree?.list) { return runtime.worktree.list(directory); } if (runtime) return runtime.listGitWorktrees(directory); return gitHttp.listGitWorktrees(directory); } export async function validateGitWorktree( directory: string, payload: import('./api/types').CreateGitWorktreePayload ): Promise { const runtime = getRuntimeGit(); if (runtime?.worktree?.validate) { return runtime.worktree.validate(directory, payload); } if (runtime?.validateGitWorktree) { return runtime.validateGitWorktree(directory, payload); } return gitHttp.validateGitWorktree(directory, payload); } export async function getGitWorktreeBootstrapStatus( directory: string, ): Promise { const runtime = getRuntimeGit(); if (runtime?.worktree?.bootstrapStatus) { return runtime.worktree.bootstrapStatus(directory); } if (runtime?.getGitWorktreeBootstrapStatus) { return runtime.getGitWorktreeBootstrapStatus(directory); } return gitHttp.getGitWorktreeBootstrapStatus(directory); } export async function previewGitWorktree( directory: string, payload: import('./api/types').CreateGitWorktreePayload ): Promise { const runtime = getRuntimeGit(); if (runtime?.worktree?.preview) { return runtime.worktree.preview(directory, payload); } if (runtime?.previewGitWorktree) { return runtime.previewGitWorktree(directory, payload); } return gitHttp.previewGitWorktree(directory, payload); } export async function createGitWorktree( directory: string, payload: import('./api/types').CreateGitWorktreePayload ): Promise { const runtime = getRuntimeGit(); if (runtime?.worktree?.create) { return runtime.worktree.create(directory, payload); } if (runtime?.createGitWorktree) { return runtime.createGitWorktree(directory, payload); } return gitHttp.createGitWorktree(directory, payload); } export async function deleteGitWorktree( directory: string, payload: import('./api/types').RemoveGitWorktreePayload ): Promise<{ success: boolean }> { const runtime = getRuntimeGit(); if (runtime?.worktree?.remove) { return runtime.worktree.remove(directory, payload); } if (runtime?.deleteGitWorktree) { return runtime.deleteGitWorktree(directory, payload); } return gitHttp.deleteGitWorktree(directory, payload); } export const git = { worktree: { list: listGitWorktrees, validate: validateGitWorktree, create: createGitWorktree, remove: deleteGitWorktree, }, }; export async function createGitCommit( directory: string, message: string, options: import('./api/types').CreateGitCommitOptions = {} ): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.createGitCommit(directory, message, options); return gitHttp.createGitCommit(directory, message, options); } export async function gitPush( directory: string, options: { remote?: string; branch?: string; options?: string[] | Record } = {} ): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.gitPush(directory, options); return gitHttp.gitPush(directory, options); } export async function gitPull( directory: string, options: import('./api/types').GitPullOptions = {} ): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.gitPull(directory, options); return gitHttp.gitPull(directory, options); } export async function gitFetch( directory: string, options: { remote?: string; branch?: string } = {} ): Promise<{ success: boolean }> { const runtime = getRuntimeGit(); if (runtime) return runtime.gitFetch(directory, options); return gitHttp.gitFetch(directory, options); } export async function listGitStashes(directory: string): Promise<{ stashes: import('./api/types').GitStashEntry[] }> { const runtime = getRuntimeGit(); if (runtime) return runtime.listGitStashes(directory); return gitHttp.listGitStashes(directory); } export async function countGitStashFiles(directory: string, refs: string[]): Promise<{ counts: Record }> { const runtime = getRuntimeGit(); if (runtime) return runtime.countGitStashFiles(directory, refs); return gitHttp.countGitStashFiles(directory, refs); } export async function stashGitChanges(directory: string, options: { message?: string } = {}): Promise<{ success: boolean; created: boolean; message: string; output: string }> { const runtime = getRuntimeGit(); if (runtime) return runtime.stashGitChanges(directory, options); return gitHttp.stashGitChanges(directory, options); } export async function applyGitStash(directory: string, options: { ref: string }): Promise<{ success: boolean; ref: string }> { const runtime = getRuntimeGit(); if (runtime) return runtime.applyGitStash(directory, options); return gitHttp.applyGitStash(directory, options); } export async function popGitStash(directory: string, options: { ref: string }): Promise<{ success: boolean; ref: string }> { const runtime = getRuntimeGit(); if (runtime) return runtime.popGitStash(directory, options); return gitHttp.popGitStash(directory, options); } export async function dropGitStash(directory: string, options: { ref: string }): Promise<{ success: boolean; ref: string }> { const runtime = getRuntimeGit(); if (runtime) return runtime.dropGitStash(directory, options); return gitHttp.dropGitStash(directory, options); } export async function checkoutBranch(directory: string, branch: string): Promise<{ success: boolean; branch: string }> { const runtime = getRuntimeGit(); if (runtime) return runtime.checkoutBranch(directory, branch); return gitHttp.checkoutBranch(directory, branch); } export async function createBranch( directory: string, name: string, startPoint?: string ): Promise<{ success: boolean; branch: string }> { const runtime = getRuntimeGit(); if (runtime) return runtime.createBranch(directory, name, startPoint); return gitHttp.createBranch(directory, name, startPoint); } export async function renameBranch( directory: string, oldName: string, newName: string ): Promise<{ success: boolean; branch: string }> { const runtime = getRuntimeGit(); if (runtime) return runtime.renameBranch(directory, oldName, newName); return gitHttp.renameBranch(directory, oldName, newName); } export async function getGitLog( directory: string, options: import('./api/types').GitLogOptions = {} ): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.getGitLog(directory, options); return gitHttp.getGitLog(directory, options); } export async function getCommitFiles( directory: string, hash: string ): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.getCommitFiles(directory, hash); return gitHttp.getCommitFiles(directory, hash); } export async function getCommitFileDiff( directory: string, hash: string, filePath: string, isBinary: boolean ): Promise { const runtime = getRuntimeGit(); if (runtime?.getCommitFileDiff) return runtime.getCommitFileDiff(directory, hash, filePath, isBinary); return gitHttp.getCommitFileDiff(directory, hash, filePath, isBinary); } export async function getGitIdentities(): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.getGitIdentities(); return gitHttp.getGitIdentities(); } export async function createGitIdentity(profile: import('./api/types').GitIdentityProfile): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.createGitIdentity(profile); return gitHttp.createGitIdentity(profile); } export async function updateGitIdentity(id: string, updates: import('./api/types').GitIdentityProfile): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.updateGitIdentity(id, updates); return gitHttp.updateGitIdentity(id, updates); } export async function deleteGitIdentity(id: string): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.deleteGitIdentity(id); return gitHttp.deleteGitIdentity(id); } export async function getCurrentGitIdentity(directory: string): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.getCurrentGitIdentity(directory); return gitHttp.getCurrentGitIdentity(directory); } export async function hasLocalIdentity(directory: string): Promise { const runtime = getRuntimeGit(); if (runtime?.hasLocalIdentity) return runtime.hasLocalIdentity(directory); return gitHttp.hasLocalIdentity(directory); } export async function setGitIdentity( directory: string, profileId: string ): Promise<{ success: boolean; profile: import('./api/types').GitIdentityProfile }> { const runtime = getRuntimeGit(); if (runtime) return runtime.setGitIdentity(directory, profileId); return gitHttp.setGitIdentity(directory, profileId); } export async function discoverGitCredentials(): Promise { const runtime = getRuntimeGit(); if (runtime?.discoverGitCredentials) return runtime.discoverGitCredentials(); return gitHttp.discoverGitCredentials(); } export async function getGlobalGitIdentity(): Promise { const runtime = getRuntimeGit(); if (runtime?.getGlobalGitIdentity) return runtime.getGlobalGitIdentity(); return gitHttp.getGlobalGitIdentity(); } export async function getRemoteUrl(directory: string, remote?: string): Promise { const runtime = getRuntimeGit(); if (runtime?.getRemoteUrl) return runtime.getRemoteUrl(directory, remote); return gitHttp.getRemoteUrl(directory, remote); } export async function getRemotes(directory: string): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.getRemotes(directory); return gitHttp.getRemotes(directory); } export async function removeRemote( directory: string, payload: import('./api/types').GitRemoveRemotePayload ): Promise<{ success: boolean }> { const runtime = getRuntimeGit(); if (runtime) return runtime.removeRemote(directory, payload); return gitHttp.removeRemote(directory, payload); } export async function rebase( directory: string, options: { onto: string } ): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.rebase(directory, options); return gitHttp.rebase(directory, options); } export async function abortRebase(directory: string): Promise<{ success: boolean }> { const runtime = getRuntimeGit(); if (runtime) return runtime.abortRebase(directory); return gitHttp.abortRebase(directory); } export async function merge( directory: string, options: { branch: string } ): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.merge(directory, options); return gitHttp.merge(directory, options); } export async function checkoutCommit( directory: string, hash: string ): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.checkoutCommit(directory, hash); return gitHttp.checkoutCommit(directory, hash); } export async function cherryPick( directory: string, hash: string ): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.cherryPick(directory, hash); return gitHttp.cherryPick(directory, hash); } export async function revertCommit( directory: string, hash: string ): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.revertCommit(directory, hash); return gitHttp.revertCommit(directory, hash); } export async function resetToCommit( directory: string, hash: string, mode: 'soft' | 'mixed' | 'hard', force?: boolean ): Promise { const runtime = getRuntimeGit(); if (runtime) return runtime.resetToCommit(directory, hash, mode, force); return gitHttp.resetToCommit(directory, hash, mode, force); } export async function abortMerge(directory: string): Promise<{ success: boolean }> { const runtime = getRuntimeGit(); if (runtime) return runtime.abortMerge(directory); return gitHttp.abortMerge(directory); } export async function continueRebase(directory: string): Promise<{ success: boolean; conflict: boolean; conflictFiles?: string[] }> { const runtime = getRuntimeGit(); if (runtime) return runtime.continueRebase(directory); return gitHttp.continueRebase(directory); } export async function continueMerge(directory: string): Promise<{ success: boolean; conflict: boolean; conflictFiles?: string[] }> { const runtime = getRuntimeGit(); if (runtime) return runtime.continueMerge(directory); return gitHttp.continueMerge(directory); } export async function stash( directory: string, options?: { message?: string; includeUntracked?: boolean } ): Promise<{ success: boolean }> { const runtime = getRuntimeGit(); if (runtime) return runtime.stash(directory, options); return gitHttp.stash(directory, options); } export async function stashPop(directory: string): Promise<{ success: boolean }> { const runtime = getRuntimeGit(); if (runtime) return runtime.stashPop(directory); return gitHttp.stashPop(directory); } export async function getConflictDetails(directory: string): Promise { const runtime = getRuntimeGit(); if (runtime?.getConflictDetails) return runtime.getConflictDetails(directory); return gitHttp.getConflictDetails(directory); } export async function validateWorktreeDirectory( directory: string, worktreeRoot: string ): Promise<{ valid: boolean; insideWorktreeRoot: boolean; resolvedWorktreeRoot: string | null; resolvedCwd: string | null; }> { const runtime = getRuntimeGit(); if (runtime?.validateWorktreeDirectory) { return runtime.validateWorktreeDirectory(directory, worktreeRoot); } return gitHttp.validateWorktreeDirectory(directory, worktreeRoot); } export async function canonicalizeWorktreeState( directory: string ): Promise<{ worktreeRoot: string | null; cwd: string | null; branch: string | null; headState: 'branch' | 'detached' | 'unborn'; worktreeStatus: 'ready' | 'missing' | 'invalid' | 'not-a-repo'; legacy: boolean; degraded: boolean; attentionReason?: 'merge' | 'rebase' | 'cherry-pick' | 'revert' | 'bisect' | null; }> { const runtime = getRuntimeGit(); if (runtime?.canonicalizeWorktreeState) { return runtime.canonicalizeWorktreeState(directory); } return gitHttp.canonicalizeWorktreeState(directory); }