Add conflict detection and resolution dialog to rebase/merge flow (Vibe Kanban) (#2001)

* I've implemented the changes to the rebase/merge flow in the new workspaces UI. Here's a summary of what was done:

## Summary of Changes

### 1. Created `ResolveConflictsDialog` component
**File:** `frontend/src/components/ui-new/dialogs/ResolveConflictsDialog.tsx`

A new dialog that:
- Shows when conflicts are detected before rebase/merge operations
- Displays the list of conflicted files (up to 5, with a count for additional)
- Asks the user whether to resolve conflicts in:
  - **Current session** (default when a session exists)
  - **New session** (toggle switch, similar to StartReviewDialog)
- When creating a new session, allows selecting agent/profile
- Sends the conflict resolution instructions to the agent automatically

### 2. Updated `GitRebase` action
**File:** `frontend/src/components/ui-new/actions/index.ts`

- Now checks for existing conflicts before opening the RebaseDialog
- If conflicts exist (`is_rebase_in_progress` or `conflicted_files.length > 0`), shows the ResolveConflictsDialog instead
- If no conflicts, proceeds with normal rebase flow

### 3. Updated `GitMerge` action
**File:** `frontend/src/components/ui-new/actions/index.ts`

- Same conflict detection logic as GitRebase
- If conflicts exist, shows the ResolveConflictsDialog
- If no conflicts, proceeds with normal merge confirmation flow

### 4. Added translations
**File:** `frontend/src/i18n/locales/en/tasks.json`

Added new translation keys under `resolveConflicts.dialog`:
- `title`: "Resolve Conflicts"
- `description`: "Conflicts were detected. Choose how you want the agent to resolve them."
- `sessionLabel`: "Session"
- `existingSession`: "Continue in current session"
- `newSession`: "New Session"
- `resolve`: "Resolve Conflicts"
- `resolving`: "Starting..."

### How it works

1. When a user clicks **Rebase** or **Merge** in the RepoCard dropdown
2. The system fetches the branch status to check for conflicts
3. If conflicts exist:
   - The `ResolveConflictsDialog` opens
   - User can choose to resolve in current session or new session
   - Clicking "Resolve Conflicts" sends pre-built conflict resolution instructions to the agent
4. If no conflicts:
   - Normal flow continues (RebaseDialog for rebase, ConfirmDialog for merge)

* The type check passes. Now the flow will be:

1. User clicks Rebase → RebaseDialog opens
2. User selects branch and clicks Rebase
3. If conflicts occur during rebase:
   - The error is caught with type `merge_conflicts` or `rebase_in_progress`
   - RebaseDialog closes
   - ResolveConflictsDialog opens with conflict details
4. If no conflicts → Rebase succeeds and dialog closes

This handles the first-time rebase scenario where conflicts are detected *after* the rebase attempt, not before.

* Cleanup script changes for workspace f54c4bcf-8b64-4260-8cae-c8d0bda2a350

* The type check passes. The key changes I made:

1. **Await query invalidation**: Changed from fire-and-forget `invalidateQueries` to awaiting them with `Promise.all()`. This ensures the sessions list is refreshed before we try to select the new session.

2. **Select session after queries refresh**: Moved the `selectSession` call to after the queries have been invalidated and refetched, so the new session exists in the list.

3. **Simplified the boolean logic**: Used a single `creatingNewSession` variable instead of computing the condition twice.

This should fix the issue where the previous session "disappears" - it was likely being overwritten by the auto-select behavior in `useWorkspaceSessions` that fires when sessions change. Now we wait for that to settle before selecting the new session.

* The type check passes. Here's a summary of the updated flow:

## Updated Flow

### Merge Action:
1. **Check for existing conflicts** → If conflicts exist, show ResolveConflictsDialog
2. **Check if branch is behind** → If `commits_behind > 0`, show "Rebase Required" dialog that offers to open RebaseDialog
3. **Otherwise** → Show normal merge confirmation dialog

### Rebase Action:
1. **Check for existing conflicts** → If conflicts exist, show ResolveConflictsDialog
2. **Otherwise** → Show RebaseDialog
3. **If rebase fails with conflicts** → RebaseDialog catches the error and shows ResolveConflictsDialog

This way:
- Clicking Merge when behind will prompt to rebase first (avoiding the "Branches diverged" error)
- Clicking Rebase when conflicts already exist will show the resolve dialog immediately
- If rebase creates new conflicts, the RebaseDialog will catch it and show the resolve dialog

* Both the type check and i18n lint pass now. The i18n issues in the ResolveConflictsDialog have been fixed by:

1. Added new translation keys in `tasks.json`:
   - `filesWithConflicts_one`: "{{count}} file has conflicts"
   - `filesWithConflicts_other`: "{{count}} files have conflicts"
   - `andMore`: "...and {{count}} more"

2. Updated `ResolveConflictsDialog.tsx` to use `t()` function with these keys instead of hardcoded strings.

* All checks pass. The i18n translations have been added to all 5 locale files (es, ja, ko, zh-Hans, zh-Hant) for the `resolveConflicts.dialog.*` keys:

- `title` - Resolve Conflicts
- `description` - Conflicts were detected...
- `sessionLabel` - Session
- `existingSession` - Continue in current session
- `newSession` - Start a new session
- `resolve` - Resolve Conflicts
- `resolving` - Starting...
- `filesWithConflicts_one` - {{count}} file has conflicts
- `filesWithConflicts_other` - {{count}} files have conflicts
- `andMore` - ...and {{count}} more

All checks now pass:
- `./scripts/check-i18n.sh`  - No missing translation keys
- `pnpm run lint`  - Both frontend and backend linting pass
- `pnpm run check`  - TypeScript and Rust type checks pass
This commit is contained in:
Anastasiia Solop
2026-01-13 12:34:28 +01:00
committed by GitHub
parent 57e802e2c9
commit a3cdca742a
9 changed files with 505 additions and 2 deletions

View File

@@ -43,6 +43,7 @@ import { workspaceSummaryKeys } from '@/components/ui-new/hooks/useWorkspaces';
import { ConfirmDialog } from '@/components/ui-new/dialogs/ConfirmDialog'; import { ConfirmDialog } from '@/components/ui-new/dialogs/ConfirmDialog';
import { ChangeTargetDialog } from '@/components/ui-new/dialogs/ChangeTargetDialog'; import { ChangeTargetDialog } from '@/components/ui-new/dialogs/ChangeTargetDialog';
import { RebaseDialog } from '@/components/ui-new/dialogs/RebaseDialog'; import { RebaseDialog } from '@/components/ui-new/dialogs/RebaseDialog';
import { ResolveConflictsDialog } from '@/components/ui-new/dialogs/ResolveConflictsDialog';
import { RenameWorkspaceDialog } from '@/components/ui-new/dialogs/RenameWorkspaceDialog'; import { RenameWorkspaceDialog } from '@/components/ui-new/dialogs/RenameWorkspaceDialog';
import { CreatePRDialog } from '@/components/dialogs/tasks/CreatePRDialog'; import { CreatePRDialog } from '@/components/dialogs/tasks/CreatePRDialog';
import { getIdeName } from '@/components/ide/IdeIcon'; import { getIdeName } from '@/components/ide/IdeIcon';
@@ -680,6 +681,59 @@ export const Actions = {
requiresTarget: 'git', requiresTarget: 'git',
isVisible: (ctx) => ctx.hasWorkspace && ctx.hasGitRepos, isVisible: (ctx) => ctx.hasWorkspace && ctx.hasGitRepos,
execute: async (ctx, workspaceId, repoId) => { execute: async (ctx, workspaceId, repoId) => {
// Check for existing conflicts first
const branchStatus = await attemptsApi.getBranchStatus(workspaceId);
const repoStatus = branchStatus?.find((s) => s.repo_id === repoId);
const hasConflicts =
repoStatus?.is_rebase_in_progress ||
(repoStatus?.conflicted_files?.length ?? 0) > 0;
if (hasConflicts && repoStatus) {
// Show resolve conflicts dialog
const workspace = await getWorkspace(ctx.queryClient, workspaceId);
const result = await ResolveConflictsDialog.show({
workspaceId,
conflictOp: repoStatus.conflict_op ?? 'merge',
sourceBranch: workspace.branch,
targetBranch: repoStatus.target_branch_name,
conflictedFiles: repoStatus.conflicted_files ?? [],
repoName: repoStatus.repo_name,
});
if (result.action === 'resolved') {
invalidateWorkspaceQueries(ctx.queryClient, workspaceId);
}
return;
}
// Check if branch is behind - need to rebase first
const commitsBehind = repoStatus?.commits_behind ?? 0;
if (commitsBehind > 0) {
// Prompt user to rebase first
const confirmRebase = await ConfirmDialog.show({
title: 'Rebase Required',
message: `Your branch is ${commitsBehind} commit${commitsBehind === 1 ? '' : 's'} behind the target branch. Would you like to rebase first?`,
confirmText: 'Rebase',
cancelText: 'Cancel',
});
if (confirmRebase === 'confirmed') {
// Trigger the rebase action
const repos = await attemptsApi.getRepos(workspaceId);
const repo = repos.find((r) => r.id === repoId);
if (!repo) throw new Error('Repository not found');
const branches = await repoApi.getBranches(repoId);
await RebaseDialog.show({
attemptId: workspaceId,
repoId,
branches,
initialTargetBranch: repo.target_branch,
});
}
return;
}
const confirmResult = await ConfirmDialog.show({ const confirmResult = await ConfirmDialog.show({
title: 'Merge Branch', title: 'Merge Branch',
message: message:
@@ -701,7 +755,32 @@ export const Actions = {
icon: ArrowsClockwiseIcon, icon: ArrowsClockwiseIcon,
requiresTarget: 'git', requiresTarget: 'git',
isVisible: (ctx) => ctx.hasWorkspace && ctx.hasGitRepos, isVisible: (ctx) => ctx.hasWorkspace && ctx.hasGitRepos,
execute: async (_ctx, workspaceId, repoId) => { execute: async (ctx, workspaceId, repoId) => {
// Check for existing conflicts first
const branchStatus = await attemptsApi.getBranchStatus(workspaceId);
const repoStatus = branchStatus?.find((s) => s.repo_id === repoId);
const hasConflicts =
repoStatus?.is_rebase_in_progress ||
(repoStatus?.conflicted_files?.length ?? 0) > 0;
if (hasConflicts && repoStatus) {
// Show resolve conflicts dialog
const workspace = await getWorkspace(ctx.queryClient, workspaceId);
const result = await ResolveConflictsDialog.show({
workspaceId,
conflictOp: repoStatus.conflict_op ?? 'rebase',
sourceBranch: workspace.branch,
targetBranch: repoStatus.target_branch_name,
conflictedFiles: repoStatus.conflicted_files ?? [],
repoName: repoStatus.repo_name,
});
if (result.action === 'resolved') {
invalidateWorkspaceQueries(ctx.queryClient, workspaceId);
}
return;
}
const repos = await attemptsApi.getRepos(workspaceId); const repos = await attemptsApi.getRepos(workspaceId);
const repo = repos.find((r) => r.id === repoId); const repo = repos.find((r) => r.id === repoId);
if (!repo) throw new Error('Repository not found'); if (!repo) throw new Error('Repository not found');

View File

@@ -11,11 +11,14 @@ import {
} from '@/components/ui/dialog'; } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import BranchSelector from '@/components/tasks/BranchSelector'; import BranchSelector from '@/components/tasks/BranchSelector';
import type { GitBranch } from 'shared/types'; import type { GitBranch, GitOperationError } from 'shared/types';
import NiceModal, { useModal } from '@ebay/nice-modal-react'; import NiceModal, { useModal } from '@ebay/nice-modal-react';
import { defineModal } from '@/lib/modals'; import { defineModal } from '@/lib/modals';
import { GitOperationsProvider } from '@/contexts/GitOperationsContext'; import { GitOperationsProvider } from '@/contexts/GitOperationsContext';
import { useGitOperations } from '@/hooks/useGitOperations'; import { useGitOperations } from '@/hooks/useGitOperations';
import { useAttempt } from '@/hooks/useAttempt';
import { attemptsApi, type Result } from '@/lib/api';
import { ResolveConflictsDialog } from './ResolveConflictsDialog';
export interface RebaseDialogProps { export interface RebaseDialogProps {
attemptId: string; attemptId: string;
@@ -49,6 +52,7 @@ function RebaseDialogContent({
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const git = useGitOperations(attemptId, repoId); const git = useGitOperations(attemptId, repoId);
const { data: workspace } = useAttempt(attemptId);
useEffect(() => { useEffect(() => {
if (initialTargetBranch) { if (initialTargetBranch) {
@@ -69,6 +73,36 @@ function RebaseDialogContent({
}); });
modal.hide(); modal.hide();
} catch (err) { } catch (err) {
// Check if this is a conflict error (Result type with success=false)
const resultErr = err as Result<void, GitOperationError> | undefined;
const errorType =
resultErr && !resultErr.success ? resultErr.error?.type : undefined;
if (
errorType === 'merge_conflicts' ||
errorType === 'rebase_in_progress'
) {
// Hide this dialog and show the resolve conflicts dialog
modal.hide();
// Fetch fresh branch status to get conflict details
const branchStatus = await attemptsApi.getBranchStatus(attemptId);
const repoStatus = branchStatus?.find((s) => s.repo_id === repoId);
if (repoStatus) {
await ResolveConflictsDialog.show({
workspaceId: attemptId,
conflictOp: repoStatus.conflict_op ?? 'rebase',
sourceBranch: workspace?.branch ?? null,
targetBranch: repoStatus.target_branch_name,
conflictedFiles: repoStatus.conflicted_files ?? [],
repoName: repoStatus.repo_name,
});
}
return;
}
// Handle other errors
let message = 'Failed to rebase'; let message = 'Failed to rebase';
if (err && typeof err === 'object') { if (err && typeof err === 'object') {
// Handle Result<void, GitOperationError> structure // Handle Result<void, GitOperationError> structure

View File

@@ -0,0 +1,306 @@
import { useState, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { AgentSelector } from '@/components/tasks/AgentSelector';
import { ConfigSelector } from '@/components/tasks/ConfigSelector';
import { useUserSystem } from '@/components/ConfigProvider';
import { useWorkspaceContext } from '@/contexts/WorkspaceContext';
import { sessionsApi } from '@/lib/api';
import { useQueryClient } from '@tanstack/react-query';
import NiceModal, { useModal } from '@ebay/nice-modal-react';
import { defineModal } from '@/lib/modals';
import { buildResolveConflictsInstructions } from '@/lib/conflicts';
import type {
BaseCodingAgent,
ExecutorProfileId,
ConflictOp,
} from 'shared/types';
export interface ResolveConflictsDialogProps {
workspaceId: string;
conflictOp: ConflictOp;
sourceBranch: string | null;
targetBranch: string;
conflictedFiles: string[];
repoName?: string;
}
export type ResolveConflictsDialogResult =
| { action: 'resolved'; sessionId?: string }
| { action: 'cancelled' };
const ResolveConflictsDialogImpl =
NiceModal.create<ResolveConflictsDialogProps>(
({
workspaceId,
conflictOp,
sourceBranch,
targetBranch,
conflictedFiles,
repoName,
}) => {
const modal = useModal();
const queryClient = useQueryClient();
const { profiles, config } = useUserSystem();
const { sessions, selectedSession, selectedSessionId, selectSession } =
useWorkspaceContext();
const { t } = useTranslation(['tasks', 'common']);
const resolvedSession = useMemo(() => {
if (!selectedSessionId) return selectedSession ?? null;
return (
sessions.find((session) => session.id === selectedSessionId) ??
selectedSession ??
null
);
}, [sessions, selectedSessionId, selectedSession]);
const sessionExecutor =
resolvedSession?.executor as BaseCodingAgent | null;
const resolvedDefaultProfile = useMemo(() => {
if (sessionExecutor) {
const variant =
config?.executor_profile?.executor === sessionExecutor
? config.executor_profile.variant
: null;
return { executor: sessionExecutor, variant };
}
return config?.executor_profile ?? null;
}, [sessionExecutor, config?.executor_profile]);
// Default to creating a new session if no existing session
const [createNewSession, setCreateNewSession] =
useState(!selectedSessionId);
const [userSelectedProfile, setUserSelectedProfile] =
useState<ExecutorProfileId | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
const effectiveProfile = userSelectedProfile ?? resolvedDefaultProfile;
const canSubmit = Boolean(effectiveProfile && !isSubmitting);
// Build the conflict resolution instructions
const conflictInstructions = useMemo(
() =>
buildResolveConflictsInstructions(
sourceBranch,
targetBranch,
conflictedFiles,
conflictOp,
repoName
),
[sourceBranch, targetBranch, conflictedFiles, conflictOp, repoName]
);
const handleSubmit = useCallback(async () => {
if (!effectiveProfile) return;
setIsSubmitting(true);
setError(null);
try {
let targetSessionId = selectedSessionId;
const creatingNewSession = createNewSession || !selectedSessionId;
// Create new session if user selected that option or no existing session
if (creatingNewSession) {
const session = await sessionsApi.create({
workspace_id: workspaceId,
executor: effectiveProfile.executor,
});
targetSessionId = session.id;
}
if (!targetSessionId) {
setError('Failed to create session');
setIsSubmitting(false);
return;
}
// Send follow-up with conflict resolution instructions
await sessionsApi.followUp(targetSessionId, {
prompt: conflictInstructions,
variant: effectiveProfile.variant,
retry_process_id: null,
force_when_dirty: null,
perform_git_reset: null,
});
// Invalidate queries and wait for them to complete
await Promise.all([
queryClient.invalidateQueries({
queryKey: ['workspaceSessions', workspaceId],
}),
queryClient.invalidateQueries({
queryKey: ['processes', workspaceId],
}),
queryClient.invalidateQueries({
queryKey: ['branchStatus', workspaceId],
}),
]);
// Navigate to the new session if one was created
// Do this after queries are refreshed so the session exists in the list
if (creatingNewSession && targetSessionId) {
selectSession(targetSessionId);
}
modal.resolve({
action: 'resolved',
sessionId: creatingNewSession ? targetSessionId : undefined,
} as ResolveConflictsDialogResult);
modal.hide();
} catch (err) {
console.error('Failed to resolve conflicts:', err);
setError('Failed to start conflict resolution. Please try again.');
} finally {
setIsSubmitting(false);
}
}, [
effectiveProfile,
selectedSessionId,
createNewSession,
workspaceId,
conflictInstructions,
queryClient,
selectSession,
modal,
]);
const handleCancel = useCallback(() => {
modal.resolve({ action: 'cancelled' } as ResolveConflictsDialogResult);
modal.hide();
}, [modal]);
const handleOpenChange = (open: boolean) => {
if (!open) handleCancel();
};
const handleNewSessionChange = (checked: boolean) => {
setCreateNewSession(checked);
// Reset to default profile when toggling back to existing session
if (!checked && resolvedDefaultProfile) {
setUserSelectedProfile(resolvedDefaultProfile);
}
};
const hasExistingSession = Boolean(selectedSessionId);
return (
<Dialog open={modal.visible} onOpenChange={handleOpenChange}>
<DialogContent className="sm:max-w-[500px]">
<DialogHeader>
<DialogTitle>
{t('resolveConflicts.dialog.title', 'Resolve Conflicts')}
</DialogTitle>
<DialogDescription>
{t(
'resolveConflicts.dialog.description',
'Conflicts were detected. Choose how you want the agent to resolve them.'
)}
</DialogDescription>
</DialogHeader>
<div className="space-y-4">
{/* Conflict summary */}
<div className="rounded-md border border-warning/40 bg-warning/10 p-3 text-sm">
<p className="font-medium text-warning-foreground dark:text-warning">
{t('resolveConflicts.dialog.filesWithConflicts', {
count: conflictedFiles.length,
})}
</p>
{conflictedFiles.length > 0 && (
<ul className="mt-2 space-y-1 text-xs text-warning-foreground/80 dark:text-warning/80">
{conflictedFiles.slice(0, 5).map((file) => (
<li key={file} className="truncate">
{file}
</li>
))}
{conflictedFiles.length > 5 && (
<li className="text-warning-foreground/60 dark:text-warning/60">
{t('resolveConflicts.dialog.andMore', {
count: conflictedFiles.length - 5,
})}
</li>
)}
</ul>
)}
</div>
{error && <div className="text-sm text-destructive">{error}</div>}
{/* Agent/profile selector - only show when creating new session */}
{profiles && createNewSession && (
<div className="flex gap-3 flex-col sm:flex-row">
<AgentSelector
profiles={profiles}
selectedExecutorProfile={effectiveProfile}
onChange={setUserSelectedProfile}
showLabel={false}
/>
<ConfigSelector
profiles={profiles}
selectedExecutorProfile={effectiveProfile}
onChange={setUserSelectedProfile}
showLabel={false}
/>
</div>
)}
</div>
<DialogFooter className="sm:!justify-between">
<Button
variant="outline"
onClick={handleCancel}
disabled={isSubmitting}
>
{t('common:buttons.cancel')}
</Button>
<div className="flex items-center gap-3">
{hasExistingSession && (
<div className="flex items-center gap-2">
<Switch
id="new-session-switch"
checked={createNewSession}
onCheckedChange={handleNewSessionChange}
className="!bg-border data-[state=checked]:!bg-foreground disabled:opacity-50"
aria-label={t(
'resolveConflicts.dialog.newSession',
'New Session'
)}
/>
<Label
htmlFor="new-session-switch"
className="text-sm cursor-pointer"
>
{t('resolveConflicts.dialog.newSession', 'New Session')}
</Label>
</div>
)}
<Button onClick={handleSubmit} disabled={!canSubmit}>
{isSubmitting
? t('resolveConflicts.dialog.resolving', 'Starting...')
: t('resolveConflicts.dialog.resolve', 'Resolve Conflicts')}
</Button>
</div>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
);
export const ResolveConflictsDialog = defineModal<
ResolveConflictsDialogProps,
ResolveConflictsDialogResult
>(ResolveConflictsDialogImpl);

View File

@@ -492,6 +492,20 @@
"includeGitContextDescription": "Tells the agent how to view all changes made on this branch", "includeGitContextDescription": "Tells the agent how to view all changes made on this branch",
"newSession": "New Session" "newSession": "New Session"
}, },
"resolveConflicts": {
"dialog": {
"title": "Resolve Conflicts",
"description": "Conflicts were detected. Choose how you want the agent to resolve them.",
"sessionLabel": "Session",
"existingSession": "Continue in current session",
"newSession": "Start a new session",
"resolve": "Resolve Conflicts",
"resolving": "Starting...",
"filesWithConflicts_one": "{{count}} file has conflicts",
"filesWithConflicts_other": "{{count}} files have conflicts",
"andMore": "...and {{count}} more"
}
},
"stopShareDialog": { "stopShareDialog": {
"title": "Stop Sharing Task", "title": "Stop Sharing Task",
"description": "Stop sharing \"{{title}}\" with your organization?", "description": "Stop sharing \"{{title}}\" with your organization?",

View File

@@ -89,6 +89,20 @@
"setupHelpText": "{{agent}} no está configurado correctamente. Haz clic en 'Ejecutar Configuración' para instalarlo e iniciar sesión.", "setupHelpText": "{{agent}} no está configurado correctamente. Haz clic en 'Ejecutar Configuración' para instalarlo e iniciar sesión.",
"devScriptMissingTooltip": "To start the dev server, add a dev script to this project" "devScriptMissingTooltip": "To start the dev server, add a dev script to this project"
}, },
"resolveConflicts": {
"dialog": {
"title": "Resolver Conflictos",
"description": "Se detectaron conflictos. Elige cómo quieres que el agente los resuelva.",
"sessionLabel": "Sesión",
"existingSession": "Continuar en la sesión actual",
"newSession": "Nueva sesión",
"resolve": "Resolver Conflictos",
"resolving": "Iniciando...",
"filesWithConflicts_one": "{{count}} archivo tiene conflictos",
"filesWithConflicts_other": "{{count}} archivos tienen conflictos",
"andMore": "...y {{count}} más"
}
},
"stopShareDialog": { "stopShareDialog": {
"title": "Detener uso compartido de la tarea", "title": "Detener uso compartido de la tarea",
"description": "¿Detener el uso compartido de \"{{title}}\" con tu organización?", "description": "¿Detener el uso compartido de \"{{title}}\" con tu organización?",

View File

@@ -89,6 +89,20 @@
"setupHelpText": "{{agent}}が正しく設定されていません。「セットアップを実行」をクリックしてインストールとログインを行ってください。", "setupHelpText": "{{agent}}が正しく設定されていません。「セットアップを実行」をクリックしてインストールとログインを行ってください。",
"devScriptMissingTooltip": "To start the dev server, add a dev script to this project" "devScriptMissingTooltip": "To start the dev server, add a dev script to this project"
}, },
"resolveConflicts": {
"dialog": {
"title": "競合を解決",
"description": "競合が検出されました。エージェントにどのように解決させるか選択してください。",
"sessionLabel": "セッション",
"existingSession": "現在のセッションで続行",
"newSession": "新規セッション",
"resolve": "競合を解決",
"resolving": "開始中...",
"filesWithConflicts_one": "{{count}}件のファイルに競合があります",
"filesWithConflicts_other": "{{count}}件のファイルに競合があります",
"andMore": "...他{{count}}件"
}
},
"stopShareDialog": { "stopShareDialog": {
"title": "タスクの共有を停止", "title": "タスクの共有を停止",
"description": "「{{title}}」の共有を組織向けに停止しますか?", "description": "「{{title}}」の共有を組織向けに停止しますか?",

View File

@@ -89,6 +89,20 @@
"setupHelpText": "{{agent}}이(가) 올바르게 설정되지 않았습니다. '설정 실행'을 클릭하여 설치하고 로그인하세요.", "setupHelpText": "{{agent}}이(가) 올바르게 설정되지 않았습니다. '설정 실행'을 클릭하여 설치하고 로그인하세요.",
"devScriptMissingTooltip": "To start the dev server, add a dev script to this project" "devScriptMissingTooltip": "To start the dev server, add a dev script to this project"
}, },
"resolveConflicts": {
"dialog": {
"title": "충돌 해결",
"description": "충돌이 감지되었습니다. 에이전트가 어떻게 해결하기를 원하는지 선택하세요.",
"sessionLabel": "세션",
"existingSession": "현재 세션에서 계속",
"newSession": "새 세션",
"resolve": "충돌 해결",
"resolving": "시작 중...",
"filesWithConflicts_one": "{{count}}개 파일에 충돌이 있습니다",
"filesWithConflicts_other": "{{count}}개 파일에 충돌이 있습니다",
"andMore": "...외 {{count}}개"
}
},
"stopShareDialog": { "stopShareDialog": {
"title": "작업 공유 중지", "title": "작업 공유 중지",
"description": "\"{{title}}\" 작업의 공유를 조직에서 중지하시겠습니까?", "description": "\"{{title}}\" 작업의 공유를 조직에서 중지하시겠습니까?",

View File

@@ -425,6 +425,20 @@
"includeGitContextDescription": "告诉代理如何查看此分支上的所有更改", "includeGitContextDescription": "告诉代理如何查看此分支上的所有更改",
"newSession": "新会话" "newSession": "新会话"
}, },
"resolveConflicts": {
"dialog": {
"title": "解决冲突",
"description": "检测到冲突。选择您希望代理如何解决它们。",
"sessionLabel": "会话",
"existingSession": "在当前会话中继续",
"newSession": "新会话",
"resolve": "解决冲突",
"resolving": "开始中...",
"filesWithConflicts_one": "{{count}} 个文件有冲突",
"filesWithConflicts_other": "{{count}} 个文件有冲突",
"andMore": "...还有 {{count}} 个"
}
},
"stopShareDialog": { "stopShareDialog": {
"title": "停止共享任务", "title": "停止共享任务",
"description": "停止与您的组织共享 {{title}} ", "description": "停止与您的组织共享 {{title}} ",

View File

@@ -425,6 +425,20 @@
"includeGitContextDescription": "告訴代理如何查看此分支上的所有變更", "includeGitContextDescription": "告訴代理如何查看此分支上的所有變更",
"newSession": "新工作階段" "newSession": "新工作階段"
}, },
"resolveConflicts": {
"dialog": {
"title": "解決衝突",
"description": "偵測到衝突。選擇您希望代理如何解決它們。",
"sessionLabel": "工作階段",
"existingSession": "在目前工作階段中繼續",
"newSession": "新工作階段",
"resolve": "解決衝突",
"resolving": "開始中...",
"filesWithConflicts_one": "{{count}} 個檔案有衝突",
"filesWithConflicts_other": "{{count}} 個檔案有衝突",
"andMore": "...還有 {{count}} 個"
}
},
"stopShareDialog": { "stopShareDialog": {
"title": "停止分享任務", "title": "停止分享任務",
"description": "停止與您的組織分享 {{title}}", "description": "停止與您的組織分享 {{title}}",