diff --git a/apps/mobile/src/components/agents/chat-composer-attachment-send.test.ts b/apps/mobile/src/components/agents/chat-composer-attachment-send.test.ts index 1bde5da9d7..5b731ffc60 100644 --- a/apps/mobile/src/components/agents/chat-composer-attachment-send.test.ts +++ b/apps/mobile/src/components/agents/chat-composer-attachment-send.test.ts @@ -194,6 +194,7 @@ vi.mock('@/lib/agent-attachments/use-agent-attachment-upload', () => ({ retryAttachment: vi.fn(() => undefined), reset: vi.fn(() => undefined), releaseUnclaimedUploads: vi.fn(() => undefined), + commitSent: vi.fn(() => undefined), isUploading: uploadState.isUploading, hasFailedAttachments: uploadState.attachments.some(attachment => attachment.status === 'error'), hasUnclaimedAttachments: uploadState.attachments.some( @@ -341,7 +342,11 @@ describe('ChatComposer attachment-only send', () => { await settle(); expect(onSendMock).toHaveBeenCalledTimes(1); - expect(onSendMock).toHaveBeenCalledWith('', { path: 'path-1', files: ['file.png'] }, undefined); + expect(onSendMock).toHaveBeenCalledWith('', { + attachments: { path: 'path-1', files: ['file.png'] }, + submission: undefined, + onOptimisticSend: expect.any(Function), + }); }); it('does not send an empty draft with no attachments', async () => { diff --git a/apps/mobile/src/components/agents/chat-composer-exit-submit-lock.test.ts b/apps/mobile/src/components/agents/chat-composer-exit-submit-lock.test.ts index 6b12e5eaba..9205e49a72 100644 --- a/apps/mobile/src/components/agents/chat-composer-exit-submit-lock.test.ts +++ b/apps/mobile/src/components/agents/chat-composer-exit-submit-lock.test.ts @@ -45,7 +45,6 @@ function createExitSubmissionHarness() { const dismiss = vi.fn(() => { order.push('dismiss'); }); - const resetAttachments = vi.fn((): void => undefined); return { lock, @@ -53,7 +52,6 @@ function createExitSubmissionHarness() { onExitSession, clearDraft, dismiss, - resetAttachments, submit: async () => { const submitted = await settleVoiceInputBeforeSubmit({ lock: lockAdapter, @@ -72,7 +70,7 @@ function createExitSubmissionHarness() { onRestartSession: vi.fn(), onSendPrompt: vi.fn(), }, - { clearDraft, dismiss, resetAttachments } + { clearDraft, dismiss } ); }, }); @@ -150,7 +148,6 @@ describe('remote session exit submit lock integration', () => { expect(harness.onExitSession).toHaveBeenCalledTimes(1); expect(harness.order).toEqual(['exit', 'accepted', 'clear', 'dismiss']); - expect(harness.resetAttachments).not.toHaveBeenCalled(); expect(harness.lock.isLocked()).toBe(false); }); }); diff --git a/apps/mobile/src/components/agents/chat-composer-submission.test.ts b/apps/mobile/src/components/agents/chat-composer-submission.test.ts index 970d64d229..a63b797e19 100644 --- a/apps/mobile/src/components/agents/chat-composer-submission.test.ts +++ b/apps/mobile/src/components/agents/chat-composer-submission.test.ts @@ -136,7 +136,7 @@ describe('executeChatComposerSubmission', () => { }); describe('prompt submission', () => { - it('clears the draft, resets attachments, and dismisses once when the prompt resolves', async () => { + it('delegates clearing to the composer and leaves cleanup untouched when the prompt resolves', async () => { const handlers = makeHandlers({ onSendPrompt: async () => { await Promise.resolve(); @@ -152,9 +152,11 @@ describe('executeChatComposerSubmission', () => { expect(handlers.onSendPrompt).toHaveBeenCalledTimes(1); expect(handlers.onSendPrompt).toHaveBeenCalledWith('hello world'); - expect(cleanup.clearDraft).toHaveBeenCalledTimes(1); - expect(cleanup.resetAttachments).toHaveBeenCalledTimes(1); - expect(cleanup.dismiss).toHaveBeenCalledTimes(1); + // The optimistic prompt path clears the composer via the SDK's + // onOptimisticSend signal, so the submission helper leaves cleanup to + // the caller and never clobbers a draft typed during the round-trip. + expect(cleanup.clearDraft).not.toHaveBeenCalled(); + expect(cleanup.dismiss).not.toHaveBeenCalled(); }); it('preserves draft and attachments when the prompt send rejects', async () => { diff --git a/apps/mobile/src/components/agents/chat-composer-submission.ts b/apps/mobile/src/components/agents/chat-composer-submission.ts index 02eb3729df..47b3e9aa34 100644 --- a/apps/mobile/src/components/agents/chat-composer-submission.ts +++ b/apps/mobile/src/components/agents/chat-composer-submission.ts @@ -26,7 +26,6 @@ type ChatComposerSubmissionHandlers = { type ChatComposerSubmissionCleanup = { clearDraft: () => void; - resetAttachments: () => void; dismiss: () => void; }; @@ -82,8 +81,10 @@ export async function executeChatComposerSubmission( return; } + // Optimistic prompt send: the caller clears the composer via the SDK's + // onOptimisticSend signal (fired synchronously after the optimistic row is + // inserted). Clearing here would clobber any new text typed during the + // transport round-trip, so this branch only awaits and lets the caller + // restore the draft on failure. await handlers.onSendPrompt(submission.prompt); - cleanup.clearDraft(); - cleanup.resetAttachments(); - cleanup.dismiss(); } diff --git a/apps/mobile/src/components/agents/chat-composer.test.ts b/apps/mobile/src/components/agents/chat-composer.test.ts index 5ade29b97a..7e079c8e9a 100644 --- a/apps/mobile/src/components/agents/chat-composer.test.ts +++ b/apps/mobile/src/components/agents/chat-composer.test.ts @@ -202,6 +202,7 @@ vi.mock('@/lib/agent-attachments/use-agent-attachment-upload', () => ({ moveAttachment: uploadMoveAttachmentMock, reorderAttachments: uploadReorderAttachmentsMock, reset: vi.fn(() => undefined), + commitSent: vi.fn(() => undefined), isUploading: false, hasFailedAttachments: false, uploadPending: vi.fn(async () => ({ @@ -393,7 +394,11 @@ describe('ChatComposer draft restore', () => { return; } expect(onSendMock).toHaveBeenCalledTimes(1); - expect(onSendMock).toHaveBeenCalledWith(sent, undefined, undefined); + expect(onSendMock).toHaveBeenCalledWith(sent, { + attachments: undefined, + submission: undefined, + onOptimisticSend: expect.any(Function), + }); }); // The host mounts the composer before identity (`user.getMe`) and the draft @@ -408,7 +413,11 @@ describe('ChatComposer draft restore', () => { requireInputRowOnSubmit(settled)(); await settle(); - expect(onSendMock).toHaveBeenCalledWith('Restored draft text', undefined, undefined); + expect(onSendMock).toHaveBeenCalledWith('Restored draft text', { + attachments: undefined, + submission: undefined, + onOptimisticSend: expect.any(Function), + }); }); it('keeps text typed before the draft settles instead of restoring over it', async () => { @@ -423,11 +432,11 @@ describe('ChatComposer draft restore', () => { requireInputRowOnSubmit(settled)(); await settle(); - expect(onSendMock).toHaveBeenCalledWith( - 'typed while identity was loading', - undefined, - undefined - ); + expect(onSendMock).toHaveBeenCalledWith('typed while identity was loading', { + attachments: undefined, + submission: undefined, + onOptimisticSend: expect.any(Function), + }); }); }); diff --git a/apps/mobile/src/components/agents/chat-composer.tsx b/apps/mobile/src/components/agents/chat-composer.tsx index bfeacc6ef3..68edfe1bb7 100644 --- a/apps/mobile/src/components/agents/chat-composer.tsx +++ b/apps/mobile/src/components/agents/chat-composer.tsx @@ -127,14 +127,19 @@ type AndroidDismissKeyboardGesture = { /** Imperative handle the host uses to set composer text (Retry / Copy to composer). */ export type ChatComposerControl = { setText: (text: string) => void; + hasContent: () => boolean; + restoreAttachments: (parts: readonly { filename?: string; mime: string; url: string }[]) => void; +}; + +/** Optional send extras the composer threads through to the host's `onSend`. */ +export type ChatComposerSendOptions = { + attachments?: AgentAttachmentWire; + submission?: AgentAttachmentSubmissionPayload; + onOptimisticSend?: () => void; }; type ChatComposerProps = { - onSend: ( - text: string, - attachments?: AgentAttachmentWire, - submission?: AgentAttachmentSubmissionPayload - ) => void | Promise; + onSend: (text: string, options?: ChatComposerSendOptions) => void | Promise; onSendCommand: (command: string, argumentsText: string) => Promise; onCreateSession: () => Promise; onRestartSession: () => Promise; @@ -464,12 +469,26 @@ export function ChatComposer({ const applyComposerTextRef = useRef(applyComposerText); applyComposerTextRef.current = applyComposerText; + // Imperative occupancy check: the host asks whether the composer holds any + // text or attachment so a queued-message cancel can pick restore vs drop. + const hasContentRef = useRef( + () => textRef.current.trim().length > 0 || upload.attachments.length > 0 + ); + hasContentRef.current = () => textRef.current.trim().length > 0 || upload.attachments.length > 0; + + const restoreFilePartsRef = useRef(upload.restoreFileParts); + restoreFilePartsRef.current = upload.restoreFileParts; + useImperativeHandle( controlRef, () => ({ setText: (text: string) => { applyComposerTextRef.current(text); }, + hasContent: () => hasContentRef.current(), + restoreAttachments: parts => { + restoreFilePartsRef.current(parts); + }, }), [] ); @@ -820,14 +839,40 @@ export function ChatComposer({ }, confirmExitSession: showRemoteSessionExitConfirmation, onSendPrompt: async prompt => { - await onSend(prompt, uploaded?.wire, uploaded?.submission); + const optimisticChips = upload.attachments; + try { + await onSend(prompt, { + attachments: uploaded?.wire, + submission: uploaded?.submission, + onOptimisticSend: () => { + // The optimistic row is already in the transcript. Clear the + // draft and chips (non-destructively) and stop the spinner so + // the prompt never renders in both the transcript and the input. + clearDraft(); + upload.clearOptimistic(); + Keyboard.dismiss(); + setIsSending(false); + }, + }); + // The optimistic clear keeps the path so a failed-send restore + // can retry under it. Only a completed send rotates the upload + // path and submission messageUuid for the next message. + upload.commitSent(); + } catch (error) { + // Transport failure: restore only when the composer is still + // empty, so a newer draft is never overwritten. + if (!hasContentRef.current()) { + if (prompt !== '') { + applyComposerText(prompt); + } + upload.restoreChips(optimisticChips); + } + throw error; + } }, }, { clearDraft, - resetAttachments: () => { - upload.reset(); - }, dismiss: () => { Keyboard.dismiss(); }, diff --git a/apps/mobile/src/components/agents/message-bubble.test.ts b/apps/mobile/src/components/agents/message-bubble.test.ts index 661615b184..5afd801434 100644 --- a/apps/mobile/src/components/agents/message-bubble.test.ts +++ b/apps/mobile/src/components/agents/message-bubble.test.ts @@ -27,10 +27,15 @@ vi.mock('react-i18next', async importOriginal => { }; }); +// Hoisted so the iOS size test can flip the platform OS and re-evaluate the +// QUEUED_CONTROL_MIN_HEIGHT module constant via vi.resetModules(). +const { reactNativeMock } = vi.hoisted(() => ({ + reactNativeMock: { Platform: { OS: 'android' } }, +})); vi.mock('react-native', () => ({ Pressable: 'Pressable', View: 'View', - Platform: { OS: 'android' }, + Platform: reactNativeMock.Platform, })); vi.mock('expo-clipboard', () => ({ setStringAsync: vi.fn() })); vi.mock('expo-haptics', () => ({ @@ -181,6 +186,8 @@ async function renderBubbleWithHandlers( deliveryState?: MessageDeliveryState; onRetryMessage?: (m: StoredMessage) => void; onCopyToComposer?: (text: string) => void; + onCancelQueued?: (m: StoredMessage) => void; + onRestoreQueued?: (m: StoredMessage) => void; } ): Promise { const { MessageBubble } = await import('./message-bubble'); @@ -318,6 +325,154 @@ describe('MessageBubble failure footer', () => { }); }); +describe('MessageBubble cancel queued and restore', () => { + it('renders the Cancel button when the message is queued and wired', async () => { + const tree = await renderBubbleWithHandlers(userMessage('m-cancel'), { + deliveryState: { status: 'queued' }, + onCancelQueued: vi.fn<(message: StoredMessage) => void>(), + }); + expect(findText(tree, t => t === 'Cancel message')).toBe(true); + const cancel = findElementByType( + tree, + 'Button', + p => p.accessibilityLabel === 'Cancel queued message' + ); + expect(cancel).not.toBeNull(); + expect(cancel?.props.accessibilityRole).toBe('button'); + }); + + it('omits the Cancel button when no onCancelQueued is wired', async () => { + const tree = await renderBubbleWithHandlers(userMessage('m-cancel-nowired'), { + deliveryState: { status: 'queued' }, + }); + expect( + findElementByType(tree, 'Button', p => p.accessibilityLabel === 'Cancel queued message') + ).toBeNull(); + }); + + it('omits the Cancel button when the message is dequeued', async () => { + const tree = await renderBubbleWithHandlers(userMessage('m-cancel-dequeued'), { + onCancelQueued: vi.fn<(message: StoredMessage) => void>(), + }); + expect( + findElementByType(tree, 'Button', p => p.accessibilityLabel === 'Cancel queued message') + ).toBeNull(); + }); + + it('renders the Restore button when wired and the message is not queued', async () => { + const tree = await renderBubbleWithHandlers(userMessage('m-restore'), { + onRestoreQueued: vi.fn<(message: StoredMessage) => void>(), + }); + expect(findText(tree, t => t === 'Restore')).toBe(true); + const restore = findElementByType( + tree, + 'Button', + p => p.accessibilityLabel === 'Restore canceled message to the composer' + ); + expect(restore).not.toBeNull(); + expect(restore?.props.accessibilityRole).toBe('button'); + }); + + it('sizes the Cancel and Restore controls to the platform minimum touch target', async () => { + const cancelTree = await renderBubbleWithHandlers(userMessage('m-size-cancel'), { + deliveryState: { status: 'queued' }, + onCancelQueued: vi.fn<(message: StoredMessage) => void>(), + }); + const cancel = findElementByType( + cancelTree, + 'Button', + p => p.accessibilityLabel === 'Cancel queued message' + ); + expect(cancel).not.toBeNull(); + // The Platform mock reports android, so the controls must reach 48dp. + expect(cancel?.props.className).toBe('min-h-12'); + expect(cancel?.props.size).toBe('sm'); + + const restoreTree = await renderBubbleWithHandlers(userMessage('m-size-restore'), { + onRestoreQueued: vi.fn<(message: StoredMessage) => void>(), + }); + const restore = findElementByType( + restoreTree, + 'Button', + p => p.accessibilityLabel === 'Restore canceled message to the composer' + ); + expect(restore).not.toBeNull(); + expect(restore?.props.className).toBe('min-h-12'); + expect(restore?.props.size).toBe('sm'); + }); + + it('sizes the controls to the iOS 44pt minimum touch target', async () => { + reactNativeMock.Platform.OS = 'ios'; + vi.resetModules(); + try { + const cancelTree = await renderBubbleWithHandlers(userMessage('m-size-cancel-ios'), { + deliveryState: { status: 'queued' }, + onCancelQueued: vi.fn<(message: StoredMessage) => void>(), + }); + const cancel = findElementByType( + cancelTree, + 'Button', + p => p.accessibilityLabel === 'Cancel queued message' + ); + expect(cancel).not.toBeNull(); + // The Platform mock reports ios, so the controls reach 44pt. + expect(cancel?.props.className).toBe('min-h-11'); + expect(cancel?.props.size).toBe('sm'); + + const restoreTree = await renderBubbleWithHandlers(userMessage('m-size-restore-ios'), { + onRestoreQueued: vi.fn<(message: StoredMessage) => void>(), + }); + const restore = findElementByType( + restoreTree, + 'Button', + p => p.accessibilityLabel === 'Restore canceled message to the composer' + ); + expect(restore).not.toBeNull(); + expect(restore?.props.className).toBe('min-h-11'); + expect(restore?.props.size).toBe('sm'); + } finally { + reactNativeMock.Platform.OS = 'android'; + vi.resetModules(); + } + }); + + it('presses Cancel and Restore to call their handlers with the message', async () => { + const message = userMessage('m-press-cancel'); + const onCancelQueued = vi.fn<(message: StoredMessage) => void>(); + const onRestoreQueued = vi.fn<(message: StoredMessage) => void>(); + + const cancelTree = await renderBubbleWithHandlers(message, { + deliveryState: { status: 'queued' }, + onCancelQueued, + }); + const cancel = findElementByType( + cancelTree, + 'Button', + p => p.accessibilityLabel === 'Cancel queued message' + ); + expect(cancel).not.toBeNull(); + if (!cancel) { + throw new Error('expected Cancel button'); + } + (cancel.props.onPress as () => void)(); + expect(onCancelQueued).toHaveBeenCalledWith(message); + + const restoreMessage = userMessage('m-press-restore'); + const restoreTree = await renderBubbleWithHandlers(restoreMessage, { onRestoreQueued }); + const restore = findElementByType( + restoreTree, + 'Button', + p => p.accessibilityLabel === 'Restore canceled message to the composer' + ); + expect(restore).not.toBeNull(); + if (!restore) { + throw new Error('expected Restore button'); + } + (restore.props.onPress as () => void)(); + expect(onRestoreQueued).toHaveBeenCalledWith(restoreMessage); + }); +}); + describe('MessageBubble copy-to-composer human text', () => { it('passes only the first human text part to Copy, not the synthesized notice', async () => { const message = userMessage('m-copy-human'); diff --git a/apps/mobile/src/components/agents/message-bubble.tsx b/apps/mobile/src/components/agents/message-bubble.tsx index 01bf90a1da..110fb60351 100644 --- a/apps/mobile/src/components/agents/message-bubble.tsx +++ b/apps/mobile/src/components/agents/message-bubble.tsx @@ -1,7 +1,7 @@ import { memo } from 'react'; import { type MessageDeliveryState, type StoredMessage } from '@kilocode/cloud-agent-sdk'; import { Clock } from '@/components/ui/icons'; -import { type AccessibilityActionEvent, Pressable, View } from 'react-native'; +import { type AccessibilityActionEvent, Platform, Pressable, View } from 'react-native'; import { useTranslation } from 'react-i18next'; import { Bubble } from '@/components/ui/bubble'; @@ -21,6 +21,13 @@ import { firstHumanText, isFilePart, isTextPart } from './part-types'; import { useMessageCopy } from './use-message-copy'; import { type OpenChildSession } from './child-session-section'; +/** + * Queued Cancel/Restore controls are compact `size="sm"` buttons (36pt + + * 4pt hitSlop). The platform minimum touch target is 48dp on Android and + * 44pt on iOS, so these two controls raise their min-height accordingly. + */ +const QUEUED_CONTROL_MIN_HEIGHT = Platform.OS === 'android' ? 'min-h-12' : 'min-h-11'; + type MessageBubbleProps = { message: StoredMessage; isLastAssistantMessage?: boolean; @@ -43,6 +50,10 @@ type MessageBubbleProps = { onRetryMessage?: (message: StoredMessage) => void; /** Copies a failed user message's text back into the composer. */ onCopyToComposer?: (text: string) => void; + /** Cancels a queued (not yet accepted) user message. Rendered only while queued. */ + onCancelQueued?: (message: StoredMessage) => void | Promise; + /** Restores a canceled queued message's prompt back into the composer. */ + onRestoreQueued?: (message: StoredMessage) => void; }; function MessageBubbleImpl({ @@ -58,6 +69,8 @@ function MessageBubbleImpl({ holdQueuedSlot, onRetryMessage, onCopyToComposer, + onCancelQueued, + onRestoreQueued, }: Readonly) { const isUser = message.info.role === 'user'; const { copyMessage } = useMessageCopy(); @@ -178,28 +191,58 @@ function MessageBubbleImpl({ ))} - {hasBadgeSlot ? ( + {hasBadgeSlot || onRestoreQueued ? ( - - - - {t('agentChat.messageBubble.queued')} - - + {hasBadgeSlot ? ( + + + + {t('agentChat.messageBubble.queued')} + + + ) : null} + {isQueued && onCancelQueued ? ( + + ) : null} + {!isQueued && onRestoreQueued ? ( + + ) : null} ) : null} diff --git a/apps/mobile/src/components/agents/mobile-session-manager.test.ts b/apps/mobile/src/components/agents/mobile-session-manager.test.ts index aa98bf3635..349752029e 100644 --- a/apps/mobile/src/components/agents/mobile-session-manager.test.ts +++ b/apps/mobile/src/components/agents/mobile-session-manager.test.ts @@ -64,17 +64,20 @@ vi.mock('@/components/agents/file-part-cache', () => ({ const mutate = vi.fn(); const prepareSessionMutate = vi.fn(); const sendMessageMutate = vi.fn(); +const cancelQueuedMessageMutate = vi.fn(); vi.mock('@/lib/trpc', () => ({ trpcClient: { cloudAgentNext: { getAttachmentDownloadUrl: { mutate }, prepareSession: { mutate: prepareSessionMutate }, sendMessage: { mutate: sendMessageMutate }, + cancelQueuedMessage: { mutate: cancelQueuedMessageMutate }, }, organizations: { cloudAgentNext: { prepareSession: { mutate: prepareSessionMutate }, sendMessage: { mutate: sendMessageMutate }, + cancelQueuedMessage: { mutate: cancelQueuedMessageMutate }, }, }, }, @@ -85,6 +88,7 @@ const { buildRemoteAttachmentParts } = const { createMobileAgentSessionManager, fetchSessionWithNotFoundRetry, + isCancelQueuedUpgradeRequired, isCloudPrepareRetryableError, readFetchSessionErrorCode, StreamTicketResponseSchema, @@ -504,3 +508,80 @@ describe('createMobileAgentSessionManager api.send', () => { }); }); }); + +describe('createMobileAgentSessionManager api.cancelQueuedMessage', () => { + beforeEach(() => { + configHolder.current = null; + mockCreateSessionManager.mockClear(); + }); + + function setup(organizationId?: string): SessionManagerConfig { + const options = { + store: {}, + userWebConnection: {}, + ...(organizationId ? { organizationId } : {}), + }; + createMobileAgentSessionManager(options as never); + const config = configHolder.current; + if (config === null) { + throw new Error('createSessionManager did not capture a config'); + } + return config; + } + + it('calls the personal mutation with { sessionId, messageId } when no organizationId is set', async () => { + cancelQueuedMessageMutate.mockReset(); + cancelQueuedMessageMutate.mockResolvedValue({ dropped: true }); + const config = setup(); + const cancel = config.api.cancelQueuedMessage; + if (!cancel) { + throw new Error('expected cancelQueuedMessage api'); + } + const input = { sessionId: 'c-1', messageId: 'm-1' }; + await expect(cancel(input as never)).resolves.toEqual({ dropped: true }); + + expect(cancelQueuedMessageMutate).toHaveBeenCalledTimes(1); + expect(cancelQueuedMessageMutate).toHaveBeenCalledWith( + { sessionId: 'c-1', messageId: 'm-1' }, + { context: { skipBatch: true } } + ); + }); + + it('adds organizationId to the org mutation payload', async () => { + cancelQueuedMessageMutate.mockReset(); + cancelQueuedMessageMutate.mockResolvedValue({ dropped: false }); + const config = setup('org-1'); + const cancel = config.api.cancelQueuedMessage; + if (!cancel) { + throw new Error('expected cancelQueuedMessage api'); + } + const input = { sessionId: 'c-1', messageId: 'm-1' }; + await expect(cancel(input as never)).resolves.toEqual({ dropped: false }); + + expect(cancelQueuedMessageMutate).toHaveBeenCalledTimes(1); + expect(cancelQueuedMessageMutate).toHaveBeenCalledWith( + { sessionId: 'c-1', messageId: 'm-1', organizationId: 'org-1' }, + { context: { skipBatch: true } } + ); + }); +}); + +describe('isCancelQueuedUpgradeRequired', () => { + it('returns true for a CLI_UPGRADE_REQUIRED code', () => { + expect( + isCancelQueuedUpgradeRequired( + Object.assign(new Error('old cli'), { code: 'CLI_UPGRADE_REQUIRED' }) + ) + ).toBe(true); + }); + + it('returns false for another code', () => { + expect( + isCancelQueuedUpgradeRequired(Object.assign(new Error('timeout'), { code: 'TIMEOUT' })) + ).toBe(false); + }); + + it('returns false for an error with no code', () => { + expect(isCancelQueuedUpgradeRequired(new Error('network'))).toBe(false); + }); +}); diff --git a/apps/mobile/src/components/agents/mobile-session-manager.ts b/apps/mobile/src/components/agents/mobile-session-manager.ts index 175226a2ce..2c1aadefb9 100644 --- a/apps/mobile/src/components/agents/mobile-session-manager.ts +++ b/apps/mobile/src/components/agents/mobile-session-manager.ts @@ -88,6 +88,20 @@ export function isCloudPrepareRetryableError(error: unknown): boolean { return CLOUD_PREPARE_TRANSIENT_CODES.has(code); } +const CANCEL_QUEUED_UPGRADE_REQUIRED_CODE = 'CLI_UPGRADE_REQUIRED'; + +export function isCancelQueuedUpgradeRequired(error: unknown): boolean { + return readFetchSessionErrorCode(error) === CANCEL_QUEUED_UPGRADE_REQUIRED_CODE; +} + +export type CancelQueuedRestoreOutcome = 'restore' | 'keep-restore'; + +export function resolveCancelQueuedRestoreOutcome( + composerHasContent: boolean +): CancelQueuedRestoreOutcome { + return composerHasContent ? 'keep-restore' : 'restore'; +} + /* eslint-disable @typescript-eslint/promise-function-async, require-await -- thin tRPC passthrough */ async function defaultFetchSessionQuery( sessionId: KiloSessionId @@ -269,6 +283,21 @@ export function createMobileAgentSessionManager({ await trpcClient.cloudAgentNext.sendMessage.mutate(baseInput, skipBatchOptions); }); }, + /* eslint-disable @typescript-eslint/promise-function-async, require-await -- thin tRPC passthrough */ + cancelQueuedMessage: async input => + withCloudAgentDiagnostics('cancelQueuedMessage', organizationId, async () => { + if (organizationId) { + return trpcClient.organizations.cloudAgentNext.cancelQueuedMessage.mutate( + { sessionId: input.sessionId, messageId: input.messageId, organizationId }, + skipBatchOptions + ); + } + return trpcClient.cloudAgentNext.cancelQueuedMessage.mutate( + { sessionId: input.sessionId, messageId: input.messageId }, + skipBatchOptions + ); + }), + /* eslint-enable @typescript-eslint/promise-function-async, require-await */ interrupt: async payload => { await withCloudAgentDiagnostics('interrupt', organizationId, async () => { if (organizationId) { diff --git a/apps/mobile/src/components/agents/session-detail-content.test.ts b/apps/mobile/src/components/agents/session-detail-content.test.ts index 555b69d9ea..4b29bf230c 100644 --- a/apps/mobile/src/components/agents/session-detail-content.test.ts +++ b/apps/mobile/src/components/agents/session-detail-content.test.ts @@ -1,10 +1,570 @@ -import { describe, expect, it } from 'vitest'; +/* eslint-disable max-lines -- the session test renders the full SessionDetailContent and mocks its RN/expo/SDK surface, so the wiring is long. */ +/* eslint-disable typescript-eslint/no-deprecated -- react-test-renderer is the DOM-free renderer used to mount React/RN trees under vitest (node env, no jsdom); see src/app/(app)/agent-chat/[session-id].mounted.test.tsx. */ +/* eslint-disable require-await, @typescript-eslint/require-await -- mock factories settle without await because they resolve immediately */ +import { createElement, type ReactElement } from 'react'; +import TestRenderer, { act } from 'react-test-renderer'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { type KiloSessionId, type StoredMessage } from '@kilocode/cloud-agent-sdk'; +import type * as ReactI18next from 'react-i18next'; + +import { type SessionTranscriptItem } from '@/components/agents/session-transcript'; import { resolveSendAttachmentKind, shouldRefuseSilentAttachmentDrop, } from '@/components/agents/session-detail-send-attachment'; +// The composer and session-list mocks are the only two sub-components the +// suite drives; everything else is a string host element so the tree renders +// without pulling in the real native/navigation surface. +const hoisted = vi.hoisted(() => ({ + managerRef: { current: null as unknown }, + chatComposer: { + control: { + hasContent: vi.fn(() => false), + setText: vi.fn(), + restoreAttachments: vi.fn(), + }, + lastProps: null as null | { + onSend?: (text: string, options?: Record) => Promise | void; + }, + }, +})); + +// Mock every RN / Expo / SDK side-effect import that `mobile-session-manager.ts` +// and `session-detail-content.tsx` pull in transitively before loading either +// module. +vi.mock('expo-secure-store', () => ({ + getItemAsync: vi.fn(), +})); +vi.mock('sonner-native', () => ({ + toast: { error: vi.fn(), success: vi.fn(), warning: vi.fn() }, +})); +vi.mock('@kilocode/cloud-agent-sdk', () => ({ + createSessionManager: vi.fn(), +})); +vi.mock('@kilocode/cloud-agent-sdk/preparation-attempts', () => ({ + isNoOpCompletedPreparationAttempt: () => false, +})); +vi.mock('@/lib/auth/token-owner', () => ({ + getAuthTokenForRequest: vi.fn(() => 'test-token'), +})); +vi.mock('@/components/agents/mobile-session-transport-payload', () => ({ + normalizeTransportPayload: vi.fn((x: unknown) => x), +})); +vi.mock('@/components/agents/mobile-session-diagnostics', () => ({ + formatSafeCloudAgentFailureDiagnostic: vi.fn(), + withCloudAgentDiagnostics: vi.fn((_op: string, _org: unknown, fn: () => unknown) => fn()), +})); +vi.mock('@/components/agents/mobile-session-page-adapter', () => ({ + fetchMobileSessionSnapshotPage: vi.fn(), +})); +vi.mock('@/lib/config', () => ({ + API_BASE_URL: 'https://api.test', + CLOUD_AGENT_WS_URL: 'wss://ws.test', + WEB_BASE_URL: 'https://web.test', +})); +vi.mock('@/lib/user-web-connection-lifecycle', () => ({ + createNativeUserWebConnectionLifecycleHooks: vi.fn(() => ({})), +})); +vi.mock('@/components/agents/tool-card-image-cache', () => ({ + cacheToolAttachment: vi.fn(), + cacheToolCardImage: vi.fn(), +})); +vi.mock('@/components/agents/file-part-cache', () => ({ + cacheFilePart: vi.fn(), +})); +vi.mock('@/lib/trpc', () => ({ + trpcClient: { + cloudAgentNext: { + getAttachmentDownloadUrl: { mutate: vi.fn() }, + prepareSession: { mutate: vi.fn() }, + sendMessage: { mutate: vi.fn() }, + cancelQueuedMessage: { mutate: vi.fn() }, + }, + organizations: { + cloudAgentNext: { + prepareSession: { mutate: vi.fn() }, + sendMessage: { mutate: vi.fn() }, + cancelQueuedMessage: { mutate: vi.fn() }, + }, + }, + cliSessionsV2: { + getWithRuntimeState: { query: vi.fn() }, + }, + }, +})); + +// ── react-native / native bridges ────────────────────────────────────────── +vi.mock('react-native', () => ({ + KeyboardAvoidingView: 'KeyboardAvoidingView', + Platform: { OS: 'ios' }, + View: 'View', +})); +vi.mock('expo-router', () => ({ + useFocusEffect: vi.fn(), + useIsFocused: () => true, + useRouter: () => ({ replace: vi.fn() }), +})); +vi.mock('expo-keep-awake', () => ({ + useKeepAwake: vi.fn(), +})); +vi.mock('expo-haptics', () => ({ + impactAsync: vi.fn(async () => undefined), + notificationAsync: vi.fn(async () => undefined), + NotificationFeedbackType: { Error: 'error', Success: 'success' }, +})); +vi.mock('react-native-reanimated', () => ({ + default: { View: 'Animated.View' }, + FadeIn: { duration: () => ({}) }, + FadeOut: { duration: () => ({}) }, + LinearTransition: { duration: () => ({}) }, +})); +vi.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: () => ({ bottom: 0, left: 0, right: 0, top: 0 }), +})); +vi.mock('react-i18next', async importOriginal => { + const actual = await importOriginal(); + return { + ...actual, + useTranslation: () => ({ t: (key: string) => key }), + }; +}); + +// ── jotai + session provider ─────────────────────────────────────────────── +// Each atom carries its current value on `.value` so `useAtomValue` can read it +// without a real Jotai store. `useSetAtom` and `useStore` are inert. +vi.mock('jotai', () => ({ + useAtomValue: (atom: { value: unknown }) => atom.value, + useSetAtom: () => vi.fn(), + useStore: () => ({ get: vi.fn(), sub: vi.fn(() => vi.fn()) }), +})); +vi.mock('@/components/agents/session-provider', () => ({ + useSessionManager: () => hoisted.managerRef.current, +})); + +// ── hooks and libs ───────────────────────────────────────────────────────── +vi.mock('@/lib/utils', () => ({ + cn: (...classes: (string | false | null | undefined)[]) => classes.filter(Boolean).join(' '), +})); +vi.mock('@/lib/intl-cache', () => ({ + dateTimeFormat: vi.fn(() => ({ format: () => '' })), + relativeTimeFormat: vi.fn(() => ({ format: () => '' })), +})); +vi.mock('@/lib/analytics/posthog', () => ({ + captureEvent: vi.fn(), + MESSAGE_SENT_EVENT: 'message_sent', + SESSION_VIEWED_EVENT: 'session_viewed', +})); +vi.mock('@/lib/a11y/announce', () => ({ + moveA11yFocus: () => false, +})); +vi.mock('@/lib/persist/drafts', () => ({ + agentComposerDraftKey: (id: string) => `agent-composer:${id}`, +})); +vi.mock('@/lib/persist/use-draft-load', () => ({ + useFencedDraftLoad: () => ({ settled: true, value: null }), +})); +vi.mock('@/lib/hooks/use-current-user-id', () => ({ + useCurrentUserId: () => ({ isLoading: false, userId: undefined }), +})); +vi.mock('@/lib/hooks/use-available-models', () => ({ + useAvailableModels: () => ({ isLoading: false, models: [] }), +})); +vi.mock('@/lib/hooks/use-model-preferences', () => ({ + useModelPreferences: () => ({ setLastSelected: vi.fn() }), +})); +vi.mock('@/lib/hooks/use-persisted-agent-model', () => ({ + usePersistedAgentModel: () => ({ saveModel: vi.fn() }), +})); +vi.mock('@/lib/hooks/use-keep-screen-on-preference', () => ({ + useKeepScreenOnPreference: () => ({ hasLoaded: true, keepScreenOn: false }), +})); +vi.mock('@/lib/hooks/use-reasoning-preference', () => ({ + useReasoningPreference: () => ({ defaultExpanded: false }), +})); +vi.mock('@/lib/hooks/use-session-model-options', () => ({ + createRemoteModelOverride: vi.fn(), + revalidateLegacyGatewayOverride: vi.fn(), + useSessionModelOptions: () => ({ options: [], selectedValue: null, selectedVariant: null }), +})); +vi.mock('@/lib/use-github-repos-refresh', () => ({ + useGitHubReposRefresh: () => ({ openGitHubIntegration: vi.fn() }), +})); +vi.mock('@/lib/session-context-info', () => ({ + resolveSessionContextInfo: () => null, +})); +vi.mock('@/lib/picker-bridge', () => ({ + areModelPickerSelectionScopesEqual: () => true, +})); + +// ── agent hooks ──────────────────────────────────────────────────────────── +vi.mock('@/components/agents/use-continue-session', () => ({ + useContinueSession: () => ({ + clearGuidance: vi.fn(), + continueSession: vi.fn(), + guidance: null, + isContinuing: false, + }), +})); +vi.mock('@/components/agents/use-interaction-handlers', () => ({ + useInteractionHandlers: () => ({ + handleAnswerQuestion: vi.fn(), + handleRejectQuestion: vi.fn(), + handleRespondToPermission: vi.fn(), + isAnswering: false, + isRespondingToPermission: false, + permissionSubmissionError: null, + questionSubmissionError: null, + }), +})); +vi.mock('@/components/agents/use-session-config-sync', () => ({ + useSessionConfigSync: () => ({ + currentMode: 'code', + currentModel: undefined, + currentVariant: '', + setCurrentMode: vi.fn(), + setCurrentModel: vi.fn(), + setCurrentVariant: vi.fn(), + }), +})); +vi.mock('@/components/agents/use-session-detail-rename', () => ({ + useSessionDetailRename: () => ({ + closeModal: vi.fn(), + isModalOpen: false, + isTitleInteractive: false, + modalInitialValue: '', + openModal: vi.fn(), + submit: vi.fn(), + title: 'Test session', + }), +})); +vi.mock('@/components/kilo-chat/hooks/use-cli-session-presence', () => ({ + resolveLoadedCliSessionPresenceId: () => null, + useCliSessionPresence: vi.fn(), +})); + +// ── pure helpers the component still calls ──────────────────────────────── +vi.mock('@/components/agents/agent-interaction-policy', () => ({ + getBlockingInteraction: () => 'none', +})); +vi.mock('@/components/agents/mode-normalize', () => ({ + customModeOptionsFromRuntimeAgents: () => [], + dedupeCustomModeOptions: (options: unknown) => options, + ensureSelectedCustomOption: (options: unknown) => options, + lockedModelOption: () => ({}), + resolvePinnedAgentModel: () => ({}), +})); +vi.mock('@/components/agents/queued-badge-hold', () => ({ + nextHeldQueuedIds: (held: unknown) => held, +})); +vi.mock('@/components/agents/session-keyboard-container-state', () => ({ + getSessionKeyboardContainerKind: () => 'app-aware-padding', +})); +vi.mock('@/components/agents/context-usage-display', () => ({ + getContextSheetMountState: () => ({ mounted: false, visible: false }), +})); +vi.mock('@/components/agents/session-composer-disabled', () => ({ + resolveSessionComposerDisabled: () => false, +})); +vi.mock('@/components/agents/session-list-helpers', () => ({ + selectSessionCostInputs: () => ({ breakdownCostUsd: null, totalMicrodollars: null }), +})); +vi.mock('@/components/agents/mobile-session-manager-helpers', () => ({ + buildRemoteAttachmentParts: vi.fn(), +})); +vi.mock('@/components/agents/session-working-state', () => ({ + shouldShowAgentWorkingIndicator: () => false, + shouldShowFooterWorkingIndicator: () => false, + shouldShowSessionFooterRow: () => false, +})); +vi.mock('@/components/agents/session-keep-awake', () => ({ + shouldKeepSessionAwake: () => false, +})); +vi.mock('@/components/agents/session-focus-refetch', () => ({ + shouldRefetchOnFocus: () => false, +})); +vi.mock('@/components/agents/session-terminal-error', () => ({ + buildTerminalErrorCopyText: () => '', + resolveSessionTerminalError: () => null, +})); +vi.mock('@/components/agents/child-session-card-state', () => ({ + getChildSessionStreaming: () => false, +})); +vi.mock('@/components/agents/child-session-sheet-state', () => ({ + closeChildSessionSheet: vi.fn(), + openChildSessionSheet: vi.fn(), + releaseChildSessionSheet: vi.fn(), +})); +vi.mock('@/components/agents/use-message-copy', () => ({ + performCopy: vi.fn(), +})); +vi.mock('@/components/agents/session-detail-content-helpers', () => ({ + collectEmptyChildSessionIds: () => [], + countInFlightMessages: () => 0, + hydrateEmptyChildSessions: vi.fn(), + resolveRetryPrompt: () => null, + retryMessageAndClear: vi.fn(), + runConnectRepository: vi.fn(), +})); +vi.mock('@/components/agents/create-and-navigate-agent-session', () => ({ + createAndNavigateAgentSession: vi.fn(), +})); +vi.mock('@/components/agents/exit-remote-session-with-feedback', () => ({ + exitRemoteSessionWithFeedback: vi.fn(), +})); +vi.mock('@/components/agents/restart-agent-session', () => ({ + restartAgentSession: vi.fn(), +})); + +// ── sub-components ───────────────────────────────────────────────────────── +// ChatComposer is the only non-string mock: it binds the composer control +// handle and captures its props so the suite can drive `onSend`. +vi.mock('@/components/agents/chat-composer', async () => { + const React = await import('react'); + return { + ChatComposer: (props: { + controlRef?: React.Ref; + onSend?: (text: string, options?: Record) => Promise | void; + }) => { + hoisted.chatComposer.lastProps = props; + React.useImperativeHandle(props.controlRef, () => hoisted.chatComposer.control); + return React.createElement('ChatComposer', null); + }, + }; +}); +vi.mock('@/components/agents/message-bubble', () => ({ + MessageBubble: 'MessageBubble', +})); +vi.mock('@/components/agents/session-message-list', () => ({ + SessionMessageList: 'SessionMessageList', +})); +vi.mock('@/components/agents/message-details-sheet', () => ({ + MessageDetailsSheet: 'MessageDetailsSheet', +})); +vi.mock('@/components/agents/model-selector', () => ({ + ModelPickerSelectionScopeProvider: 'ModelPickerSelectionScopeProvider', +})); +vi.mock('@/components/agents/permission-card', () => ({ + PermissionCard: 'PermissionCard', +})); +vi.mock('@/components/agents/question-card', () => ({ + QuestionCard: 'QuestionCard', +})); +vi.mock('@/components/agents/session-connection-indicator', () => ({ + SessionConnectionIndicator: 'SessionConnectionIndicator', +})); +vi.mock('@/components/agents/session-context-metrics', () => ({ + SessionContextMetrics: 'SessionContextMetrics', +})); +vi.mock('@/components/agents/session-context-sheet', () => ({ + SessionContextSheet: 'SessionContextSheet', +})); +vi.mock('@/components/agents/session-pr-badge', () => ({ + SessionPrBadge: 'SessionPrBadge', +})); +vi.mock('@/components/agents/session-status-indicator', () => ({ + SessionStatusIndicator: 'SessionStatusIndicator', +})); +vi.mock('@/components/agents/preparation-group', () => ({ + PreparationGroup: 'PreparationGroup', +})); +vi.mock('@/components/agents/session-detail-skeleton', () => ({ + SessionSkeletonMessages: 'SessionSkeletonMessages', +})); +vi.mock('@/components/agents/transcript-time-marker', () => ({ + TranscriptTimeMarker: 'TranscriptTimeMarker', +})); +vi.mock('@/components/agents/working-indicator', () => ({ + WorkingIndicator: 'WorkingIndicator', +})); +vi.mock('@/components/agents/child-session-sheet', () => ({ + ChildSessionSheet: 'ChildSessionSheet', +})); +vi.mock('@/components/agents/part-detail-sheet-host', () => ({ + PartDetailSheetHost: 'PartDetailSheetHost', +})); +vi.mock('@/components/agents/part-renderer', () => ({ + PartRenderer: 'PartRenderer', +})); +vi.mock('@/components/empty-state', () => ({ + EmptyState: 'EmptyState', +})); +vi.mock('@/components/kilo-chat/app-aware-keyboard-padding', () => ({ + AppAwareKeyboardPaddingView: 'AppAwareKeyboardPaddingView', +})); +vi.mock('@/components/query-error', () => ({ + QueryError: 'QueryError', +})); +vi.mock('@/components/rename-modal', () => ({ + RenameModal: 'RenameModal', +})); +vi.mock('@/components/screen-header', () => ({ + ScreenHeader: 'ScreenHeader', +})); +vi.mock('@/components/ui/accessible-status', () => ({ + AccessibleStatus: 'AccessibleStatus', +})); +vi.mock('@/components/ui/blur-bar', () => ({ + BlurBar: 'BlurBar', +})); +vi.mock('@/components/ui/button', () => ({ + Button: 'Button', +})); +vi.mock('@/components/ui/text', () => ({ + Text: 'Text', +})); +vi.mock('@/components/ui/icons', () => ({ + MessageSquare: 'MessageSquare', +})); + +const { SessionDetailContent } = await import('@/components/agents/session-detail-content'); + +const SESSION_ID = 'sess-1' as KiloSessionId; + +function makeManager() { + return { + atoms: { + messagesList: { value: [] as StoredMessage[] }, + isLoading: { value: false }, + error: { value: null }, + fetchedSessionData: { + value: { + associatedPr: null, + cloudAgentSessionId: undefined, + gitUrl: undefined, + kiloSessionId: SESSION_ID, + mode: undefined, + organizationId: undefined, + title: 'Test session', + totalCostMicrodollars: null, + }, + }, + sessionConfig: { value: null }, + isStreaming: { value: false }, + statusIndicator: { value: null }, + agentStatus: { value: { type: 'connected' } }, + cloudStatus: { value: null }, + preparationAttempts: { value: [] }, + canSend: { value: true }, + isReadOnly: { value: false }, + supportsAttachments: { value: true }, + activeQuestion: { value: null }, + activePermission: { value: null }, + pendingQuestions: { value: [] }, + pendingPermissions: { value: [] }, + totalCost: { value: null }, + childMessages: { value: () => new Map() }, + childSessionHydrationState: { value: () => ({ status: 'idle' }) }, + childSessionError: { value: () => null }, + pendingMessages: { value: new Map() }, + activeSessionType: { value: null }, + remoteModelState: { value: { catalog: null, ownerConnectionId: null, protocol: 'v1' } }, + observedModel: { value: null }, + remoteModelOverride: { value: null }, + cloudAgentModelOverride: { value: null }, + availableCommands: { value: [] }, + remoteCommandState: { value: null }, + contextUsage: { value: null }, + hasOlderMessages: { value: false }, + isLoadingOlderMessages: { value: false }, + olderMessagesError: { value: null }, + olderMessagesOmittedItemCount: { value: 0 }, + }, + switchSession: vi.fn(), + send: vi.fn(), + cancelQueuedMessage: vi.fn(), + interrupt: vi.fn(), + setCloudAgentModelOverride: vi.fn(), + setRemoteModelOverride: vi.fn(), + updateFetchedAssociatedPr: vi.fn(), + hydrateChildSession: vi.fn(), + loadOlderMessages: vi.fn(), + trimRetainedHistory: vi.fn(), + loadOlderChildMessages: vi.fn(), + clearFailedMessage: vi.fn(), + createRemoteSession: vi.fn(), + exitRemoteSession: vi.fn(), + destroy: vi.fn(), + }; +} + +type TestManager = ReturnType; + +let currentManager: TestManager = makeManager(); + +const TEXT_PART = { type: 'text', text: 'Queued prompt' } as const; +const FILE_PART = { + type: 'file', + filename: 'a.pdf', + mime: 'application/pdf', + url: 'file:///tmp/attachments/sess-1/user-1/msg-1/a.pdf', +} as const; + +function queuedMessage(parts: readonly unknown[] = [TEXT_PART]): StoredMessage { + return { + info: { id: 'msg-queued-1', role: 'user', time: { created: undefined } }, + parts, + } as unknown as StoredMessage; +} + +function mount(): TestRenderer.ReactTestRenderer { + const ref: { current: TestRenderer.ReactTestRenderer | undefined } = { current: undefined }; + act(() => { + ref.current = TestRenderer.create( + createElement(SessionDetailContent, { sessionId: SESSION_ID }) + ); + }); + if (!ref.current) { + throw new Error('SessionDetailContent did not render'); + } + return ref.current; +} + +function findByType( + renderer: TestRenderer.ReactTestRenderer, + type: string +): TestRenderer.ReactTestInstance[] { + return renderer.root.findAll(node => node.type === type); +} + +/** Reads the MessageBubble element the current transcript renders for `messageId`. */ +function readBubble( + renderer: TestRenderer.ReactTestRenderer, + messageId: string +): ReactElement | undefined { + const lists = findByType(renderer, 'SessionMessageList'); + const listProps = lists[0]?.props as + | { + items?: SessionTranscriptItem[]; + renderItem?: (args: { item: SessionTranscriptItem }) => ReactElement; + } + | undefined; + const item = listProps?.items?.find( + candidate => candidate.type === 'message' && candidate.message.info.id === messageId + ); + if (!item || !listProps?.renderItem) { + return undefined; + } + return listProps.renderItem({ item }); +} + +function bubbleProps( + renderer: TestRenderer.ReactTestRenderer, + messageId: string +): Record { + return (readBubble(renderer, messageId)?.props ?? {}) as Record; +} + +beforeEach(() => { + currentManager = makeManager(); + hoisted.managerRef.current = currentManager; + hoisted.chatComposer.lastProps = null; + hoisted.chatComposer.control.hasContent.mockReset().mockReturnValue(false); + hoisted.chatComposer.control.setText.mockReset(); + hoisted.chatComposer.control.restoreAttachments.mockReset(); +}); + describe('resolveSendAttachmentKind', () => { it.each([ { activeSessionType: 'cloud-agent' as const, supports: true, has: true, expected: 'cloud' }, @@ -39,3 +599,138 @@ describe('shouldRefuseSilentAttachmentDrop', () => { } ); }); + +describe('SessionDetailContent cancel/restore', () => { + function seedQueuedMessage(message: StoredMessage = queuedMessage()): void { + currentManager.atoms.messagesList.value = [message]; + currentManager.atoms.pendingMessages.value = new Map([[message.info.id, { status: 'queued' }]]); + } + + it('keeps the Restore action after canceling a queued message while the composer is occupied', async () => { + seedQueuedMessage(); + currentManager.cancelQueuedMessage.mockResolvedValue({ dropped: true }); + hoisted.chatComposer.control.hasContent.mockReturnValue(true); + + const renderer = mount(); + const onCancelQueued = bubbleProps(renderer, 'msg-queued-1').onCancelQueued as + | ((message: StoredMessage) => Promise) + | undefined; + expect(onCancelQueued).toBeInstanceOf(Function); + + await act(async () => { + await onCancelQueued?.(queuedMessage()); + }); + + // Occupied composer → the row must stay with a Restore action, not be + // dropped out of the transcript. + expect(bubbleProps(renderer, 'msg-queued-1').onRestoreQueued).toBeInstanceOf(Function); + + act(() => { + renderer.unmount(); + }); + }); + + it('keeps the queued row and shows a failure status when cancel reports dropped=false', async () => { + seedQueuedMessage(); + currentManager.cancelQueuedMessage.mockResolvedValue({ dropped: false }); + hoisted.chatComposer.control.hasContent.mockReturnValue(false); + + const renderer = mount(); + const onCancelQueued = bubbleProps(renderer, 'msg-queued-1').onCancelQueued as + | ((message: StoredMessage) => Promise) + | undefined; + expect(onCancelQueued).toBeInstanceOf(Function); + + await act(async () => { + await onCancelQueued?.(queuedMessage()); + }); + + // The queue did not drop the message: the row stays queued (still wired + // for Cancel, no Restore) and the prompt is not restored into the composer. + expect(bubbleProps(renderer, 'msg-queued-1').onCancelQueued).toBeInstanceOf(Function); + expect(bubbleProps(renderer, 'msg-queued-1').onRestoreQueued).toBeUndefined(); + expect(hoisted.chatComposer.control.setText).not.toHaveBeenCalled(); + expect(hoisted.chatComposer.control.restoreAttachments).not.toHaveBeenCalled(); + + const statuses = findByType(renderer, 'AccessibleStatus'); + const messages = statuses.map(instance => instance.props.message as string | null); + expect(messages).toContain('agentChat.session.cancelQueuedFailed'); + + act(() => { + renderer.unmount(); + }); + }); + + it('restores the prompt and file parts into an empty composer after cancel', async () => { + const message = queuedMessage([TEXT_PART, FILE_PART]); + seedQueuedMessage(message); + currentManager.cancelQueuedMessage.mockResolvedValue({ dropped: true }); + hoisted.chatComposer.control.hasContent.mockReturnValue(false); + + const renderer = mount(); + const onCancelQueued = bubbleProps(renderer, 'msg-queued-1').onCancelQueued as + | ((message: StoredMessage) => Promise) + | undefined; + expect(onCancelQueued).toBeInstanceOf(Function); + + await act(async () => { + await onCancelQueued?.(message); + }); + + expect(hoisted.chatComposer.control.setText).toHaveBeenCalledWith('Queued prompt'); + expect(hoisted.chatComposer.control.restoreAttachments).toHaveBeenCalledWith([FILE_PART]); + // Empty composer → the row is dropped, so the transcript no longer renders it. + expect(readBubble(renderer, 'msg-queued-1')).toBeUndefined(); + + act(() => { + renderer.unmount(); + }); + }); + + it('shows the upgrade-required copy when cancel fails with CLI_UPGRADE_REQUIRED', async () => { + seedQueuedMessage(); + currentManager.cancelQueuedMessage.mockRejectedValue( + Object.assign(new Error('old cli'), { code: 'CLI_UPGRADE_REQUIRED' }) + ); + + const renderer = mount(); + const onCancelQueued = bubbleProps(renderer, 'msg-queued-1').onCancelQueued as + | ((message: StoredMessage) => Promise) + | undefined; + expect(onCancelQueued).toBeInstanceOf(Function); + + await act(async () => { + await onCancelQueued?.(queuedMessage()); + }); + + const statuses = findByType(renderer, 'AccessibleStatus'); + const messages = statuses.map(instance => instance.props.message as string | null); + expect(messages).toContain('agentChat.session.cancelQueuedUpgradeRequired'); + + act(() => { + renderer.unmount(); + }); + }); +}); + +describe('SessionDetailContent send failure', () => { + it('throws without putting the prompt back itself (the composer owns the restore)', async () => { + currentManager.send.mockResolvedValue(false); + + const renderer = mount(); + const onSend = hoisted.chatComposer.lastProps?.onSend; + expect(onSend).toBeInstanceOf(Function); + + await act(async () => { + await expect(onSend?.('prompt text', {})).rejects.toThrow('Failed to send message'); + }); + + // The session layer must not restore the prompt itself — that would + // duplicate the composer's own empty-composer restore. + expect(hoisted.chatComposer.control.setText).not.toHaveBeenCalled(); + + act(() => { + renderer.unmount(); + }); + }); +}); diff --git a/apps/mobile/src/components/agents/session-detail-content.tsx b/apps/mobile/src/components/agents/session-detail-content.tsx index fbd504dab1..f986a26a42 100644 --- a/apps/mobile/src/components/agents/session-detail-content.tsx +++ b/apps/mobile/src/components/agents/session-detail-content.tsx @@ -5,6 +5,7 @@ import { useAtomValue, useSetAtom, useStore } from 'jotai'; import { MessageSquare } from '@/components/ui/icons'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useKeepAwake } from 'expo-keep-awake'; +import * as Haptics from 'expo-haptics'; import { KeyboardAvoidingView, Platform, type Text as RNText, View } from 'react-native'; import Animated, { FadeIn, FadeOut, LinearTransition } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -12,7 +13,11 @@ import { useTranslation } from 'react-i18next'; import { toast } from 'sonner-native'; import { getBlockingInteraction } from '@/components/agents/agent-interaction-policy'; -import { ChatComposer, type ChatComposerControl } from '@/components/agents/chat-composer'; +import { + ChatComposer, + type ChatComposerControl, + type ChatComposerSendOptions, +} from '@/components/agents/chat-composer'; import { type AgentMode, customModeOptionsFromRuntimeAgents, @@ -42,6 +47,11 @@ import { SessionContextSheet } from '@/components/agents/session-context-sheet'; import { SessionPrBadge } from '@/components/agents/session-pr-badge'; import { selectSessionCostInputs } from '@/components/agents/session-list-helpers'; import { buildRemoteAttachmentParts } from '@/components/agents/mobile-session-manager-helpers'; +import { + isCancelQueuedUpgradeRequired, + resolveCancelQueuedRestoreOutcome, +} from '@/components/agents/mobile-session-manager'; +import { firstHumanText, isFilePart } from './part-types'; import { buildRemoteAttachmentPartsWithRetryableFeedback, resolveSendAttachmentKind, @@ -100,11 +110,10 @@ import { performCopy } from '@/components/agents/use-message-copy'; import { QueryError } from '@/components/query-error'; import { RenameModal } from '@/components/rename-modal'; import { ScreenHeader } from '@/components/screen-header'; +import { AccessibleStatus } from '@/components/ui/accessible-status'; import { BlurBar } from '@/components/ui/blur-bar'; import { Button } from '@/components/ui/button'; import { Text } from '@/components/ui/text'; -import { type AgentAttachmentSubmissionPayload } from '@/lib/agent-attachments/agent-attachment-types'; -import { type AgentAttachmentWire } from '@/lib/agent-attachments/use-agent-attachment-upload'; import { type AnalyticsSurface, captureEvent, @@ -655,9 +664,51 @@ export function SessionDetailContent({ } }, [clearChildSheetReleaseTimeout]); + // Canceled queued rows: `droppedQueuedIds` filters a canceled (empty-composer) + // row out of the transcript before merge; `canceledQueuedMessages` keeps the + // full row with a Restore action when the composer was occupied (the SDK + // deletes the row on cloud.message.canceled, so the local copy re-inserts it). + // `cancelQueuedStatus` surfaces the outcome through `AccessibleStatus`. All + // three reset on switch. + const EMPTY_CANCELED: ReadonlyMap = new Map(); + const [droppedQueuedIds, setDroppedQueuedIds] = useState>(EMPTY_IDS); + const [canceledQueuedMessages, setCanceledQueuedMessages] = + useState>(EMPTY_CANCELED); + const [cancelQueuedStatus, setCancelQueuedStatus] = useState<{ + tone: 'status' | 'error'; + message: string; + } | null>(null); + + const visibleMessages = useMemo(() => { + const base = + droppedQueuedIds.size === 0 + ? messages + : messages.filter(m => !droppedQueuedIds.has(m.info.id)); + if (canceledQueuedMessages.size === 0) { + return base; + } + // Re-insert the canceled-but-kept rows the SDK already removed, in their + // original id-sorted position (the SDK orders messages by id). + const kept = [...canceledQueuedMessages.values()].filter( + m => !base.some(b => b.info.id === m.info.id) + ); + if (kept.length === 0) { + return base; + } + return [...base, ...kept].toSorted((a, b) => { + if (a.info.id < b.info.id) { + return -1; + } + if (a.info.id > b.info.id) { + return 1; + } + return 0; + }); + }, [messages, droppedQueuedIds, canceledQueuedMessages]); + const transcript = useMemo( - () => mergeSessionTranscript(messages, preparationAttempts), - [messages, preparationAttempts] + () => mergeSessionTranscript(visibleMessages, preparationAttempts), + [visibleMessages, preparationAttempts] ); // Render-phase state adjustment: hold queued ids across queue → dequeue @@ -668,6 +719,9 @@ export function SessionDetailContent({ if (prevSessionId !== sessionId) { setPrevSessionId(sessionId); setHeldQueuedIds(EMPTY_IDS); + setDroppedQueuedIds(EMPTY_IDS); + setCanceledQueuedMessages(EMPTY_CANCELED); + setCancelQueuedStatus(null); } else { const next = nextHeldQueuedIds(heldQueuedIds, pendingMessages, isStreaming); if (next !== heldQueuedIds) { @@ -678,11 +732,8 @@ export function SessionDetailContent({ const requiresModel = Boolean(fetchedData?.cloudAgentSessionId); const handleSend = useCallback( - async ( - text: string, - attachments?: AgentAttachmentWire, - submission?: AgentAttachmentSubmissionPayload - ) => { + async (text: string, options?: ChatComposerSendOptions) => { + const { attachments, submission, onOptimisticSend } = options ?? {}; if (requiresModel && !(pinned.model ?? currentModel)) { toast.error(t('agentChat.composer.selectModelBeforeSending')); return; @@ -750,6 +801,7 @@ export function SessionDetailContent({ }, ...(kind === 'cloud' && attachments ? { attachments } : {}), ...(kind === 'remote-capable' && attachmentParts ? { attachmentParts } : {}), + ...(onOptimisticSend ? { onOptimisticSend } : {}), }); if (!sent) { throw new Error('Failed to send message'); @@ -799,6 +851,81 @@ export function SessionDetailContent({ [messages, requiresModel, pinned.model, currentModel, handleSend, manager] ); + const handleCancelQueued = useCallback( + async (message: StoredMessage) => { + let dropped = false; + try { + ({ dropped } = await manager.cancelQueuedMessage(message.info.id)); + } catch (cancelError) { + const upgrade = isCancelQueuedUpgradeRequired(cancelError); + setCancelQueuedStatus({ + tone: 'error', + message: upgrade + ? t('agentChat.session.cancelQueuedUpgradeRequired') + : t('agentChat.session.cancelQueuedFailed'), + }); + void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); + return; + } + if (!dropped) { + // The queue did not drop the message (missing id or already-accepted + // current turn). Keep the row and surface the failure — never restore, + // hide, or drop it, or the user could send again on top of a hidden + // running turn. + setCancelQueuedStatus({ + tone: 'error', + message: t('agentChat.session.cancelQueuedFailed'), + }); + void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error); + return; + } + const composerHasContent = composerControlRef.current?.hasContent() ?? false; + const outcome = resolveCancelQueuedRestoreOutcome(composerHasContent); + const prompt = firstHumanText(message.parts); + if (outcome === 'restore') { + if (prompt !== '') { + composerControlRef.current?.setText(prompt); + } + composerControlRef.current?.restoreAttachments(message.parts.filter(isFilePart)); + setDroppedQueuedIds(prev => new Set(prev).add(message.info.id)); + setCancelQueuedStatus({ + tone: 'status', + message: t('agentChat.session.cancelQueuedRestored'), + }); + } else { + setCanceledQueuedMessages(prev => new Map(prev).set(message.info.id, message)); + setCancelQueuedStatus({ + tone: 'status', + message: t('agentChat.session.cancelQueuedRestoreAvailable'), + }); + } + void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + }, + [manager, t] + ); + + const handleRestoreQueued = useCallback( + (message: StoredMessage) => { + const stored = canceledQueuedMessages.get(message.info.id) ?? message; + const prompt = firstHumanText(stored.parts); + if (prompt !== '') { + composerControlRef.current?.setText(prompt); + } + composerControlRef.current?.restoreAttachments(stored.parts.filter(isFilePart)); + setCanceledQueuedMessages(prev => { + const next = new Map(prev); + next.delete(message.info.id); + return next; + }); + setCancelQueuedStatus({ + tone: 'status', + message: t('agentChat.session.cancelQueuedRestored'), + }); + void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); + }, + [canceledQueuedMessages, t] + ); + const renderItem = useCallback( ({ item }: { item: SessionTranscriptItem }) => { if (item.type === 'preparation') { @@ -829,6 +956,10 @@ export function SessionDetailContent({ holdQueuedSlot={isStreaming && heldQueuedIds.has(item.message.info.id)} onRetryMessage={retryPrompt !== null ? handleRetryMessage : undefined} onCopyToComposer={handleCopyToComposer} + onCancelQueued={deliveryState?.status === 'queued' ? handleCancelQueued : undefined} + onRestoreQueued={ + canceledQueuedMessages.has(item.message.info.id) ? handleRestoreQueued : undefined + } /> ); }, @@ -844,6 +975,9 @@ export function SessionDetailContent({ messages, handleRetryMessage, handleCopyToComposer, + handleCancelQueued, + handleRestoreQueued, + canceledQueuedMessages, ] ); @@ -1271,6 +1405,12 @@ export function SessionDetailContent({ {renderContent()} + + {/* Fixed indicator row — lives outside the FlashList so per-token content-size changes during streaming cannot reposition it. Gated on has-messages so the empty/connecting path (which diff --git a/apps/mobile/src/components/agents/use-new-session-creator.test.ts b/apps/mobile/src/components/agents/use-new-session-creator.test.ts index 70173e7b53..c0e94f2ce2 100644 --- a/apps/mobile/src/components/agents/use-new-session-creator.test.ts +++ b/apps/mobile/src/components/agents/use-new-session-creator.test.ts @@ -147,6 +147,10 @@ const FAKE_ATTACHMENTS: CreatorInput['attachments'] = { reorderAttachments: vi.fn(() => undefined), reset: vi.fn(() => undefined), releaseUnclaimedUploads: vi.fn(() => undefined), + restoreFileParts: vi.fn(() => undefined), + clearOptimistic: vi.fn(() => undefined), + restoreChips: vi.fn(() => undefined), + commitSent: vi.fn(() => undefined), isUploading: false, hasFailedAttachments: false, hasUnclaimedAttachments: false, diff --git a/apps/mobile/src/i18n/locales/af.json b/apps/mobile/src/i18n/locales/af.json index c6bfcb7d35..2c7a7d2b68 100644 --- a/apps/mobile/src/i18n/locales/af.json +++ b/apps/mobile/src/i18n/locales/af.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Die vorige taak is nog besig. Wag asseblief 'n oomblik.", "serviceUnavailable": "Die diens is tans onbeskikbaar. Probeer asseblief weer.", "connectionTrouble": "Verbindingsprobleem. Probeer asseblief oor 'n oomblik weer.", - "modelUnavailable": "Hierdie model is nie beskikbaar vir Cloud Agent nie. Kies 'n ander model en probeer weer." + "modelUnavailable": "Hierdie model is nie beskikbaar vir Cloud Agent nie. Kies 'n ander model en probeer weer.", + "cancelQueuedFailed": "Kon die boodskap nie kanselleer nie.", + "cancelQueuedRestoreAvailable": "Boodskap gekanselleer. Tik Herstel om dit na die boodskapinvoer terug te plaas.", + "cancelQueuedRestored": "Boodskap gekanselleer. Die aansporing is terug in die boodskapinvoer.", + "cancelQueuedUpgradeRequired": "Om boodskappe in die wagry te kanselleer, vereis 'n nuwer Kilo CLI. Dateer Kilo CLI op en maak weer verbinding." }, "remoteSession": { "failedToCreate": "Kon afgeleë sessie nie skep nie", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Boodskap in die ry", "userMessage": "Gebruikerboodskap", "assistantMessage": "Assistentboodskap", - "longPressHint": "Hou lank in vir boodskapbesonderhede" + "longPressHint": "Hou lank in vir boodskapbesonderhede", + "cancelQueued": "Kanselleer boodskap", + "cancelQueuedAccessibility": "Kanselleer boodskap in die wagry", + "restoreQueued": "Herstel", + "restoreQueuedAccessibility": "Herstel gekanselleerde boodskap na die boodskapinvoer" }, "olderMessages": { "arrived": "Vroeër boodskappe gelaai", diff --git a/apps/mobile/src/i18n/locales/am.json b/apps/mobile/src/i18n/locales/am.json index c0e75d8ea8..2ff795f0b3 100644 --- a/apps/mobile/src/i18n/locales/am.json +++ b/apps/mobile/src/i18n/locales/am.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "የቀደመው ተግባር አሁንም እያለቀ ነው። እባክዎ ጠብቅ።", "serviceUnavailable": "አገልግሎቱ አሁን አይገኝም። እባክዎ እንደገና ይሞክሩ።", "connectionTrouble": "የግንኙነት ችግር። እባክዎ በአጭር ጊዜ ውስጥ እንደገና ይሞክሩ።", - "modelUnavailable": "ይህ ሞዴል ለ Cloud Agent አይገኝም። ሌላ ሞዴል ይምረጡ እና እንደገና ይሞክሩ።" + "modelUnavailable": "ይህ ሞዴል ለ Cloud Agent አይገኝም። ሌላ ሞዴል ይምረጡ እና እንደገና ይሞክሩ።", + "cancelQueuedFailed": "መልእክቱን መሰረዝ አልተቻለም።", + "cancelQueuedRestoreAvailable": "መልእክቱ ተሰርዟል። ወደ መልእክት ማስገቢያው ለመመለስ «ይመልሱ»ን ይንኩ።", + "cancelQueuedRestored": "መልእክቱ ተሰርዟል። ጥያቄው ወደ መልእክት ማስገቢያው ተመልሷል።", + "cancelQueuedUpgradeRequired": "በተራ የሚጠባበቁ መልእክቶችን መሰረዝ አዲስ የKilo CLI ስሪት ይፈልጋል። Kilo CLIን ያዘምኑ እና እንደገና ያገናኙ።" }, "remoteSession": { "failedToCreate": "የርቀት ክፍለ ጊዜ መፍጠር አልተሳካም", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "መልእክት ወረፋ ውስጥ ገብቷል", "userMessage": "የተጠቃሚ መልእክት", "assistantMessage": "የረዳት መልእክት", - "longPressHint": "ለመልእክት ዝርዝሮች ረጅም ይጫኑ" + "longPressHint": "ለመልእክት ዝርዝሮች ረጅም ይጫኑ", + "cancelQueued": "መልእክቱን ይሰርዙ", + "cancelQueuedAccessibility": "በተራ የሚጠባበቀውን መልእክት ይሰርዙ", + "restoreQueued": "ይመልሱ", + "restoreQueuedAccessibility": "የተሰረዘውን መልእክት ወደ መልእክት ማስገቢያው ይመልሱ" }, "olderMessages": { "arrived": "የቀደሙ መልእክቶች ተጭነዋል", diff --git a/apps/mobile/src/i18n/locales/ar.json b/apps/mobile/src/i18n/locales/ar.json index 3764e323b4..4ca67b7d25 100644 --- a/apps/mobile/src/i18n/locales/ar.json +++ b/apps/mobile/src/i18n/locales/ar.json @@ -1720,7 +1720,11 @@ "previousTaskFinishing": "لا تزال المهمة السابقة قيد الإنجاز. يرجى الانتظار لحظة.", "serviceUnavailable": "الخدمة غير متاحة الآن. يرجى المحاولة مرة أخرى.", "connectionTrouble": "مشكلة في الاتصال. يرجى إعادة المحاولة بعد لحظة.", - "modelUnavailable": "هذا النموذج غير متاح لـ Cloud Agent. اختر نموذجًا آخر وحاول مرة أخرى." + "modelUnavailable": "هذا النموذج غير متاح لـ Cloud Agent. اختر نموذجًا آخر وحاول مرة أخرى.", + "cancelQueuedFailed": "تعذّر إلغاء الرسالة.", + "cancelQueuedRestoreAvailable": "تم إلغاء الرسالة. اضغط على «استعادة» لإعادتها إلى حقل إدخال الرسالة.", + "cancelQueuedRestored": "تم إلغاء الرسالة. عادت المطالبة إلى حقل إدخال الرسالة.", + "cancelQueuedUpgradeRequired": "يتطلب إلغاء الرسائل في قائمة الانتظار إصدارًا أحدث من Kilo CLI. حدّث Kilo CLI وأعد الاتصال." }, "remoteSession": { "failedToCreate": "تعذّر إنشاء الجلسة البعيدة", @@ -2045,7 +2049,11 @@ "queuedAccessibility": "الرسالة في قائمة الانتظار", "userMessage": "رسالة المستخدم", "assistantMessage": "رسالة المساعد", - "longPressHint": "اضغط مطولًا لعرض تفاصيل الرسالة" + "longPressHint": "اضغط مطولًا لعرض تفاصيل الرسالة", + "cancelQueued": "إلغاء الرسالة", + "cancelQueuedAccessibility": "إلغاء الرسالة في قائمة الانتظار", + "restoreQueued": "استعادة", + "restoreQueuedAccessibility": "استعادة الرسالة الملغاة إلى حقل إدخال الرسالة" }, "olderMessages": { "arrived": "تم تحميل الرسائل السابقة", diff --git a/apps/mobile/src/i18n/locales/az.json b/apps/mobile/src/i18n/locales/az.json index 48a5528509..145fb7633b 100644 --- a/apps/mobile/src/i18n/locales/az.json +++ b/apps/mobile/src/i18n/locales/az.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Əvvəlki tapşırıq hələ də tamamlanır. Bir an gözləyin.", "serviceUnavailable": "Xidmət hazırda əlçatan deyil. Yenidən cəhd edin.", "connectionTrouble": "Əlaqə problemi. Bir dəqiqə sonra yenidən cəhd edin.", - "modelUnavailable": "Bu model Cloud Agent üçün mövcud deyil. Başqa bir model seçin və yenidən cəhd edin." + "modelUnavailable": "Bu model Cloud Agent üçün mövcud deyil. Başqa bir model seçin və yenidən cəhd edin.", + "cancelQueuedFailed": "Mesaj ləğv edilə bilmədi.", + "cancelQueuedRestoreAvailable": "Mesaj ləğv edildi. Onu kompozitora qaytarmaq üçün «Bərpa edin» düyməsinə toxunun.", + "cancelQueuedRestored": "Mesaj ləğv edildi. İstək kompozitora qaytarıldı.", + "cancelQueuedUpgradeRequired": "Növbəyə alınmış mesajları ləğv etmək üçün daha yeni Kilo CLI tələb olunur. Kilo CLI-ni yeniləyin və yenidən qoşulun." }, "remoteSession": { "failedToCreate": "Uzaq sessiya yaradıla bilmədi", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Mesaj növbəyə alınıb", "userMessage": "İstifadəçi mesajı", "assistantMessage": "Köməkçi mesajı", - "longPressHint": "Mesaj detalları üçün uzun basın" + "longPressHint": "Mesaj detalları üçün uzun basın", + "cancelQueued": "Mesajı ləğv edin", + "cancelQueuedAccessibility": "Növbəyə alınmış mesajı ləğv edin", + "restoreQueued": "Bərpa edin", + "restoreQueuedAccessibility": "Ləğv edilmiş mesajı kompozitora bərpa edin" }, "olderMessages": { "arrived": "Əvvəlki mesajlar yükləndi", diff --git a/apps/mobile/src/i18n/locales/be.json b/apps/mobile/src/i18n/locales/be.json index 29a2d3595b..48b573d09f 100644 --- a/apps/mobile/src/i18n/locales/be.json +++ b/apps/mobile/src/i18n/locales/be.json @@ -2172,7 +2172,11 @@ "previousTaskFinishing": "Папярэдняя задача ўсё яшчэ выконваецца. Пачакайце імгненне.", "serviceUnavailable": "Сэрвіс зараз недаступны. Паспрабуйце зноў.", "connectionTrouble": "Праблема з падключэннем. Паспрабуйце зноў праз імгненне.", - "modelUnavailable": "Гэтая мадэль недаступная для Cloud Agent. Выберыце іншую мадэль і паспрабуйце зноў." + "modelUnavailable": "Гэтая мадэль недаступная для Cloud Agent. Выберыце іншую мадэль і паспрабуйце зноў.", + "cancelQueuedFailed": "Не атрымалася адмяніць паведамленне.", + "cancelQueuedRestoreAvailable": "Паведамленне адменена. Націсніце «Аднавіць», каб вярнуць яго ў кампазітар.", + "cancelQueuedRestored": "Паведамленне адменена. Запыт зноў у кампазітары.", + "cancelQueuedUpgradeRequired": "Для адмены паведамленняў у чарзе патрабуецца навейшы Kilo CLI. Абнавіце Kilo CLI і падключыцеся зноў." }, "remoteSession": { "failedToCreate": "Не атрымалася стварыць аддалены сеанс", @@ -2495,7 +2499,11 @@ "queuedAccessibility": "Паведамленне ў чарзе", "userMessage": "Паведамленне карыстальніка", "assistantMessage": "Паведамленне памочніка", - "longPressHint": "Доўгае націсканне для дэталяў паведамлення" + "longPressHint": "Доўгае націсканне для дэталяў паведамлення", + "cancelQueued": "Адмяніць паведамленне", + "cancelQueuedAccessibility": "Адмяніць паведамленне ў чарзе", + "restoreQueued": "Аднавіць", + "restoreQueuedAccessibility": "Аднавіць адмененае паведамленне ў кампазітары" }, "olderMessages": { "arrived": "Загружаныя больш раннія паведамленні", diff --git a/apps/mobile/src/i18n/locales/bg.json b/apps/mobile/src/i18n/locales/bg.json index 84da9d4f06..6d593c1e19 100644 --- a/apps/mobile/src/i18n/locales/bg.json +++ b/apps/mobile/src/i18n/locales/bg.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Предишната задача все още приключва. Моля, изчакайте момент.", "serviceUnavailable": "Услугата в момента е недостъпна. Моля, опитайте отново.", "connectionTrouble": "Проблем с връзката. Моля, опитайте отново след момент.", - "modelUnavailable": "Този модел не е наличен за Cloud Agent. Изберете друг модел и опитайте отново." + "modelUnavailable": "Този модел не е наличен за Cloud Agent. Изберете друг модел и опитайте отново.", + "cancelQueuedFailed": "Съобщението не можа да бъде отменено.", + "cancelQueuedRestoreAvailable": "Съобщението е отменено. Докоснете „Възстановете“, за да го върнете в полето за въвеждане на съобщения.", + "cancelQueuedRestored": "Съобщението е отменено. Подканата е отново в полето за въвеждане на съобщения.", + "cancelQueuedUpgradeRequired": "Отмяната на съобщения в опашката изисква по-нов Kilo CLI. Актуализирайте Kilo CLI и се свържете отново." }, "remoteSession": { "failedToCreate": "Неуспешно създаване на отдалечена сесия", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Съобщението е поставено в опашка", "userMessage": "Съобщение от потребителя", "assistantMessage": "Съобщение от асистента", - "longPressHint": "Задръжте за детайли на съобщението" + "longPressHint": "Задръжте за детайли на съобщението", + "cancelQueued": "Отменете съобщението", + "cancelQueuedAccessibility": "Отменете съобщението в опашката", + "restoreQueued": "Възстановете", + "restoreQueuedAccessibility": "Възстановете отмененото съобщение в полето за въвеждане на съобщения" }, "olderMessages": { "arrived": "По-ранните съобщения са заредени", diff --git a/apps/mobile/src/i18n/locales/bn.json b/apps/mobile/src/i18n/locales/bn.json index 4d76a5b5cd..2a7373f6f5 100644 --- a/apps/mobile/src/i18n/locales/bn.json +++ b/apps/mobile/src/i18n/locales/bn.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "আগের কাজটি এখনও শেষ হচ্ছে। একটু অপেক্ষা করুন।", "serviceUnavailable": "সেবাটি এখন অনুপলব্ধ। আবার চেষ্টা করুন।", "connectionTrouble": "সংযোগ সমস্যা। কিছুক্ষণ পরে আবার চেষ্টা করুন।", - "modelUnavailable": "এই মডেলটি Cloud Agent-এর জন্য উপলব্ধ নয়। অন্য একটি মডেল বেছে নিন এবং আবার চেষ্টা করুন।" + "modelUnavailable": "এই মডেলটি Cloud Agent-এর জন্য উপলব্ধ নয়। অন্য একটি মডেল বেছে নিন এবং আবার চেষ্টা করুন।", + "cancelQueuedFailed": "বার্তাটি বাতিল করা যায়নি।", + "cancelQueuedRestoreAvailable": "বার্তা বাতিল হয়েছে। বার্তা লেখার ঘরে এটি ফেরাতে ‘পুনরুদ্ধার করুন’-এ আলতো চাপুন।", + "cancelQueuedRestored": "বার্তা বাতিল হয়েছে। প্রম্পটটি বার্তা লেখার ঘরে ফিরে এসেছে।", + "cancelQueuedUpgradeRequired": "অপেক্ষমাণ বার্তা বাতিল করতে Kilo CLI-এর নতুন সংস্করণ প্রয়োজন। Kilo CLI আপডেট করুন এবং পুনরায় সংযুক্ত করুন।" }, "remoteSession": { "failedToCreate": "দূরবর্তী সেশন তৈরি করতে ব্যর্থ", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "বার্তা সারিবদ্ধ", "userMessage": "ব্যবহারকারী বার্তা", "assistantMessage": "সহায়ক বার্তা", - "longPressHint": "বার্তার বিবরণের জন্য দীর্ঘ চাপ দিন" + "longPressHint": "বার্তার বিবরণের জন্য দীর্ঘ চাপ দিন", + "cancelQueued": "বার্তা বাতিল করুন", + "cancelQueuedAccessibility": "অপেক্ষমাণ বার্তা বাতিল করুন", + "restoreQueued": "পুনরুদ্ধার করুন", + "restoreQueuedAccessibility": "বাতিল করা বার্তাটি বার্তা লেখার ঘরে পুনরুদ্ধার করুন" }, "olderMessages": { "arrived": "আগের বার্তাগুলি লোড হয়েছে", diff --git a/apps/mobile/src/i18n/locales/bs.json b/apps/mobile/src/i18n/locales/bs.json index 6fb9c7ec89..68a2404d81 100644 --- a/apps/mobile/src/i18n/locales/bs.json +++ b/apps/mobile/src/i18n/locales/bs.json @@ -2157,7 +2157,11 @@ "previousTaskFinishing": "Prethodni zadatak se još uvijek završava. Pričekajte trenutak.", "serviceUnavailable": "Usluga trenutno nije dostupna. Pokušajte ponovo.", "connectionTrouble": "Problem s vezom. Pokušajte ponovo za trenutak.", - "modelUnavailable": "Ovaj model nije dostupan za Cloud Agent. Odaberite drugi model i pokušajte ponovo." + "modelUnavailable": "Ovaj model nije dostupan za Cloud Agent. Odaberite drugi model i pokušajte ponovo.", + "cancelQueuedFailed": "Nije moguće otkazati poruku.", + "cancelQueuedRestoreAvailable": "Poruka je otkazana. Dodirnite Vratite da je vratite u polje za unos poruke.", + "cancelQueuedRestored": "Poruka je otkazana. Upit je ponovo u polju za unos poruke.", + "cancelQueuedUpgradeRequired": "Otkazivanje poruka na čekanju zahtijeva noviji Kilo CLI. Ažurirajte Kilo CLI i ponovo se povežite." }, "remoteSession": { "failedToCreate": "Nije uspjelo stvaranje daljinske sesije", @@ -2479,7 +2483,11 @@ "queuedAccessibility": "Poruka u redu", "userMessage": "Korisnička poruka", "assistantMessage": "Poruka asistenta", - "longPressHint": "Dugo pritisnite za detalje poruke" + "longPressHint": "Dugo pritisnite za detalje poruke", + "cancelQueued": "Otkažite poruku", + "cancelQueuedAccessibility": "Otkažite poruku na čekanju", + "restoreQueued": "Vratite", + "restoreQueuedAccessibility": "Vratite otkazanu poruku u polje za unos poruke" }, "olderMessages": { "arrived": "Ranije poruke su učitane", diff --git a/apps/mobile/src/i18n/locales/ca.json b/apps/mobile/src/i18n/locales/ca.json index c124991446..0bf2937402 100644 --- a/apps/mobile/src/i18n/locales/ca.json +++ b/apps/mobile/src/i18n/locales/ca.json @@ -2157,7 +2157,11 @@ "previousTaskFinishing": "La tasca anterior encara s'està acabant. Espera un moment.", "serviceUnavailable": "El servei no està disponible ara mateix. Torna-ho a provar.", "connectionTrouble": "Problema de connexió. Torna-ho a provar d'aquí a un moment.", - "modelUnavailable": "Aquest model no està disponible per a Cloud Agent. Tria un altre model i torna-ho a provar." + "modelUnavailable": "Aquest model no està disponible per a Cloud Agent. Tria un altre model i torna-ho a provar.", + "cancelQueuedFailed": "No s'ha pogut cancel·lar el missatge.", + "cancelQueuedRestoreAvailable": "Missatge cancel·lat. Toca Restaura per tornar-lo al camp de missatge.", + "cancelQueuedRestored": "Missatge cancel·lat. El missatge torna a ser al camp de missatge.", + "cancelQueuedUpgradeRequired": "Per cancel·lar missatges en cua cal una versió més nova de Kilo CLI. Actualitza Kilo CLI i torna't a connectar." }, "remoteSession": { "failedToCreate": "No s'ha pogut crear la sessió remota", @@ -2479,7 +2483,11 @@ "queuedAccessibility": "Missatge en cua", "userMessage": "Missatge de l'usuari", "assistantMessage": "Missatge de l'assistent", - "longPressHint": "Manteniu premut per veure els detalls del missatge" + "longPressHint": "Manteniu premut per veure els detalls del missatge", + "cancelQueued": "Cancel·la el missatge", + "cancelQueuedAccessibility": "Cancel·la el missatge en cua", + "restoreQueued": "Restaura", + "restoreQueuedAccessibility": "Restaura el missatge cancel·lat al camp de missatge" }, "olderMessages": { "arrived": "S'han carregat els missatges anteriors", diff --git a/apps/mobile/src/i18n/locales/ckb.json b/apps/mobile/src/i18n/locales/ckb.json index 24f1c16248..8cf18bf7da 100644 --- a/apps/mobile/src/i18n/locales/ckb.json +++ b/apps/mobile/src/i18n/locales/ckb.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "ئەرکی پێشوو هێشتا تەواو دەبێت. تکایە کەمێک چاوەڕێ بکە.", "serviceUnavailable": "خزمەتگوزارییەکە ئێستا بەردەست نییە. تکایە دووبارە هەوڵ بدەرەوە.", "connectionTrouble": "کێشەی پەیوەندی. تکایە کەمێک دواتر دووبارە هەوڵ بدەرەوە.", - "modelUnavailable": "ئەم مۆدێلە بۆ Cloud Agent بەردەست نییە. مۆدێلێکی تر هەڵبژێرە و دووبارە هەوڵ بدەرەوە." + "modelUnavailable": "ئەم مۆدێلە بۆ Cloud Agent بەردەست نییە. مۆدێلێکی تر هەڵبژێرە و دووبارە هەوڵ بدەرەوە.", + "cancelQueuedFailed": "نەتوانرا پەیامەکە هەڵبوەشێنرێتەوە.", + "cancelQueuedRestoreAvailable": "پەیامەکە هەڵوەشایەوە. لە «گەڕاندنەوە» بدە بۆ گەڕاندنەوەی بۆ خانەی نووسینی پەیام.", + "cancelQueuedRestored": "پەیامەکە هەڵوەشایەوە. داواکارییەکە گەڕایەوە بۆ خانەی نووسینی پەیام.", + "cancelQueuedUpgradeRequired": "هەڵوەشاندنەوەی پەیامەکانی ناو ڕیز پێویستی بە وەشانێکی نوێتری Kilo CLI هەیە. Kilo CLI نوێ بکەرەوە و دووبارە پەیوەندی بکەوە." }, "remoteSession": { "failedToCreate": "شکستی هێنا لە دروستکردنی سێشنی دوور", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "نامە لە ڕیزدا دانرا", "userMessage": "نامەی بەکارهێنەر", "assistantMessage": "نامەی یاریدەدەر", - "longPressHint": "پەستانی درێژ بۆ وردەکاری نامەکە" + "longPressHint": "پەستانی درێژ بۆ وردەکاری نامەکە", + "cancelQueued": "هەڵوەشاندنەوەی پەیام", + "cancelQueuedAccessibility": "هەڵوەشاندنەوەی پەیامی ناو ڕیز", + "restoreQueued": "گەڕاندنەوە", + "restoreQueuedAccessibility": "گەڕاندنەوەی پەیامی هەڵوەشاوە بۆ خانەی نووسینی پەیام" }, "olderMessages": { "arrived": "نامە پێشووەکان بارکران", diff --git a/apps/mobile/src/i18n/locales/cs.json b/apps/mobile/src/i18n/locales/cs.json index 0c0552a62e..3672a327be 100644 --- a/apps/mobile/src/i18n/locales/cs.json +++ b/apps/mobile/src/i18n/locales/cs.json @@ -2172,7 +2172,11 @@ "previousTaskFinishing": "Předchozí úloha stále běží. Počkejte prosím chvíli.", "serviceUnavailable": "Služba je právě nedostupná. Zkuste to prosím znovu.", "connectionTrouble": "Problém s připojením. Zkuste to prosím za chvíli.", - "modelUnavailable": "Tento model není pro Cloud Agent dostupný. Vyberte jiný model a zkuste to znovu." + "modelUnavailable": "Tento model není pro Cloud Agent dostupný. Vyberte jiný model a zkuste to znovu.", + "cancelQueuedFailed": "Zprávu se nepodařilo zrušit.", + "cancelQueuedRestoreAvailable": "Zpráva zrušena. Klepnutím na Obnovit ji vrátíte do pole pro zprávu.", + "cancelQueuedRestored": "Zpráva zrušena. Dotaz je zpět v poli pro zprávu.", + "cancelQueuedUpgradeRequired": "Rušení zpráv ve frontě vyžaduje novější Kilo CLI. Aktualizujte Kilo CLI a připojte se znovu." }, "remoteSession": { "failedToCreate": "Vzdálenou relaci se nepodařilo vytvořit", @@ -2495,7 +2499,11 @@ "queuedAccessibility": "Zpráva ve frontě", "userMessage": "Uživatelská zpráva", "assistantMessage": "Zpráva asistenta", - "longPressHint": "Dlouhým stisknutím zobrazíte podrobnosti zprávy" + "longPressHint": "Dlouhým stisknutím zobrazíte podrobnosti zprávy", + "cancelQueued": "Zrušit zprávu", + "cancelQueuedAccessibility": "Zrušit zprávu ve frontě", + "restoreQueued": "Obnovit", + "restoreQueuedAccessibility": "Obnovit zrušenou zprávu do pole pro zprávu" }, "olderMessages": { "arrived": "Načteny dřívější zprávy", diff --git a/apps/mobile/src/i18n/locales/cy.json b/apps/mobile/src/i18n/locales/cy.json index c92b283493..f9ac3b8652 100644 --- a/apps/mobile/src/i18n/locales/cy.json +++ b/apps/mobile/src/i18n/locales/cy.json @@ -2202,7 +2202,11 @@ "previousTaskFinishing": "Mae'r dasg flaenorol yn dal i orffen. Arhoswch funud.", "serviceUnavailable": "Nid yw'r gwasanaeth ar gael ar hyn o bryd. Ceisiwch eto.", "connectionTrouble": "Problem cysylltiad. Ceisiwch eto ymhen munud.", - "modelUnavailable": "Nid yw'r model hwn ar gael ar gyfer Cloud Agent. Dewiswch fodel arall a cheisiwch eto." + "modelUnavailable": "Nid yw'r model hwn ar gael ar gyfer Cloud Agent. Dewiswch fodel arall a cheisiwch eto.", + "cancelQueuedFailed": "Ni ellid canslo'r neges.", + "cancelQueuedRestoreAvailable": "Neges wedi'i chanslo. Tapiwch Adfer i'w dychwelyd i'r maes mewnbwn neges.", + "cancelQueuedRestored": "Neges wedi'i chanslo. Mae'r anogiad yn ôl yn y maes mewnbwn neges.", + "cancelQueuedUpgradeRequired": "Mae angen Kilo CLI mwy newydd i ganslo negeseuon yn y ciw. Diweddarwch Kilo CLI ac ailgysylltwch." }, "remoteSession": { "failedToCreate": "Methodd creu sesiwn o bell", @@ -2527,7 +2531,11 @@ "queuedAccessibility": "Neges mewn ciw", "userMessage": "Neges defnyddiwr", "assistantMessage": "Neges cynorthwyydd", - "longPressHint": "Pwyswch yn hir i weld manylion y neges" + "longPressHint": "Pwyswch yn hir i weld manylion y neges", + "cancelQueued": "Canslo neges", + "cancelQueuedAccessibility": "Canslo neges yn y ciw", + "restoreQueued": "Adfer", + "restoreQueuedAccessibility": "Adfer neges wedi'i chanslo i'r maes mewnbwn neges" }, "olderMessages": { "arrived": "Llwythwyd negeseuon cynharach", diff --git a/apps/mobile/src/i18n/locales/da.json b/apps/mobile/src/i18n/locales/da.json index ce87cb8a15..e4010d0b43 100644 --- a/apps/mobile/src/i18n/locales/da.json +++ b/apps/mobile/src/i18n/locales/da.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Den forrige opgave er stadig i gang. Vent et øjeblik.", "serviceUnavailable": "Tjenesten er ikke tilgængelig lige nu. Prøv igen.", "connectionTrouble": "Forbindelsesproblem. Prøv igen om et øjeblik.", - "modelUnavailable": "Denne model er ikke tilgængelig for Cloud Agent. Vælg en anden model og prøv igen." + "modelUnavailable": "Denne model er ikke tilgængelig for Cloud Agent. Vælg en anden model og prøv igen.", + "cancelQueuedFailed": "Kunne ikke annullere beskeden.", + "cancelQueuedRestoreAvailable": "Besked annulleret. Tryk på Gendan for at føre den tilbage til beskedfeltet.", + "cancelQueuedRestored": "Besked annulleret. Prompten er tilbage i beskedfeltet.", + "cancelQueuedUpgradeRequired": "Annullering af beskeder i kø kræver en nyere Kilo CLI. Opdater Kilo CLI, og opret forbindelse igen." }, "remoteSession": { "failedToCreate": "Kunne ikke oprette fjernsession", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Besked i kø", "userMessage": "Brugerbesked", "assistantMessage": "Assistentbesked", - "longPressHint": "Hold nede for beskeddetaljer" + "longPressHint": "Hold nede for beskeddetaljer", + "cancelQueued": "Annuller besked", + "cancelQueuedAccessibility": "Annuller besked i kø", + "restoreQueued": "Gendan", + "restoreQueuedAccessibility": "Gendan annulleret besked i beskedfeltet" }, "olderMessages": { "arrived": "Tidligere beskeder indlæst", diff --git a/apps/mobile/src/i18n/locales/de.json b/apps/mobile/src/i18n/locales/de.json index bb1114b897..5e9a1317f1 100644 --- a/apps/mobile/src/i18n/locales/de.json +++ b/apps/mobile/src/i18n/locales/de.json @@ -1688,7 +1688,11 @@ "previousTaskFinishing": "Die vorherige Aufgabe wird noch abgeschlossen. Bitte warte einen Moment.", "serviceUnavailable": "Der Dienst ist gerade nicht verfügbar. Bitte versuche es erneut.", "connectionTrouble": "Verbindungsproblem. Bitte versuche es in einem Moment erneut.", - "modelUnavailable": "Dieses Modell ist für Cloud Agent nicht verfügbar. Wähle ein anderes Modell und versuche es erneut." + "modelUnavailable": "Dieses Modell ist für Cloud Agent nicht verfügbar. Wähle ein anderes Modell und versuche es erneut.", + "cancelQueuedFailed": "Die Nachricht konnte nicht abgebrochen werden.", + "cancelQueuedRestoreAvailable": "Nachricht abgebrochen. Tippe auf „Wiederherstellen“, um sie zurück in die Nachrichteneingabe zu holen.", + "cancelQueuedRestored": "Nachricht abgebrochen. Der Prompt ist wieder in der Nachrichteneingabe.", + "cancelQueuedUpgradeRequired": "Das Abbrechen von Nachrichten in der Warteschlange erfordert eine neuere Kilo CLI. Aktualisiere die Kilo CLI und verbinde dich erneut." }, "remoteSession": { "failedToCreate": "Remote-Sitzung konnte nicht erstellt werden", @@ -2009,7 +2013,11 @@ "queuedAccessibility": "Nachricht in Warteschlange", "userMessage": "Nutzer-Nachricht", "assistantMessage": "Assistenten-Nachricht", - "longPressHint": "Lange drücken für Nachrichtendetails" + "longPressHint": "Lange drücken für Nachrichtendetails", + "cancelQueued": "Nachricht abbrechen", + "cancelQueuedAccessibility": "Nachricht in der Warteschlange abbrechen", + "restoreQueued": "Wiederherstellen", + "restoreQueuedAccessibility": "Abgebrochene Nachricht in der Nachrichteneingabe wiederherstellen" }, "olderMessages": { "arrived": "Frühere Nachrichten geladen", diff --git a/apps/mobile/src/i18n/locales/el.json b/apps/mobile/src/i18n/locales/el.json index 53f62461c2..ec5955f22f 100644 --- a/apps/mobile/src/i18n/locales/el.json +++ b/apps/mobile/src/i18n/locales/el.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Η προηγούμενη εργασία ολοκληρώνεται ακόμα. Περιμένετε μια στιγμή.", "serviceUnavailable": "Η υπηρεσία δεν είναι διαθέσιμη αυτή τη στιγμή. Δοκιμάστε ξανά.", "connectionTrouble": "Πρόβλημα σύνδεσης. Δοκιμάστε ξανά σε λίγο.", - "modelUnavailable": "Αυτό το μοντέλο δεν είναι διαθέσιμο για το Cloud Agent. Επιλέξτε άλλο μοντέλο και δοκιμάστε ξανά." + "modelUnavailable": "Αυτό το μοντέλο δεν είναι διαθέσιμο για το Cloud Agent. Επιλέξτε άλλο μοντέλο και δοκιμάστε ξανά.", + "cancelQueuedFailed": "Δεν ήταν δυνατή η ακύρωση του μηνύματος.", + "cancelQueuedRestoreAvailable": "Το μήνυμα ακυρώθηκε. Πατήστε Επαναφορά για να το επαναφέρετε στο πεδίο εισαγωγής μηνύματος.", + "cancelQueuedRestored": "Το μήνυμα ακυρώθηκε. Το μήνυμα βρίσκεται ξανά στο πεδίο εισαγωγής μηνύματος.", + "cancelQueuedUpgradeRequired": "Η ακύρωση μηνυμάτων σε αναμονή απαιτεί νεότερη έκδοση του Kilo CLI. Ενημερώστε το Kilo CLI και συνδεθείτε ξανά." }, "remoteSession": { "failedToCreate": "Αποτυχία δημιουργίας απομακρυσμένης συνεδρίας", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Το μήνυμα είναι σε αναμονή", "userMessage": "Μήνυμα χρήστη", "assistantMessage": "Μήνυμα βοηθού", - "longPressHint": "Παρατεταμένο πάτημα για λεπτομέρειες μηνύματος" + "longPressHint": "Παρατεταμένο πάτημα για λεπτομέρειες μηνύματος", + "cancelQueued": "Ακύρωση μηνύματος", + "cancelQueuedAccessibility": "Ακύρωση μηνύματος σε αναμονή", + "restoreQueued": "Επαναφορά", + "restoreQueuedAccessibility": "Επαναφορά ακυρωμένου μηνύματος στο πεδίο εισαγωγής μηνύματος" }, "olderMessages": { "arrived": "Φορτώθηκαν προηγούμενα μηνύματα", diff --git a/apps/mobile/src/i18n/locales/en.json b/apps/mobile/src/i18n/locales/en.json index 1b448f7707..b5707cc2f2 100644 --- a/apps/mobile/src/i18n/locales/en.json +++ b/apps/mobile/src/i18n/locales/en.json @@ -2156,7 +2156,11 @@ "previousTaskFinishing": "The previous task is still finishing. Please wait a moment.", "serviceUnavailable": "The service is unavailable right now. Please try again.", "connectionTrouble": "Connection trouble. Please retry in a moment.", - "modelUnavailable": "This model isn't available for Cloud Agent. Choose another model and try again." + "modelUnavailable": "This model isn't available for Cloud Agent. Choose another model and try again.", + "cancelQueuedRestored": "Message canceled. The prompt is back in the composer.", + "cancelQueuedRestoreAvailable": "Message canceled. Tap Restore to return it to the composer.", + "cancelQueuedUpgradeRequired": "Canceling queued messages requires a newer Kilo CLI. Update Kilo CLI and reconnect.", + "cancelQueuedFailed": "Could not cancel the message." }, "remoteSession": { "failedToCreate": "Failed to create remote session", @@ -2491,6 +2495,10 @@ "copyToComposer": "Copy to composer", "queued": "Queued", "queuedAccessibility": "Message queued", + "cancelQueued": "Cancel message", + "cancelQueuedAccessibility": "Cancel queued message", + "restoreQueued": "Restore", + "restoreQueuedAccessibility": "Restore canceled message to the composer", "userMessage": "User message", "assistantMessage": "Assistant message", "longPressHint": "Long press for message details" diff --git a/apps/mobile/src/i18n/locales/es.json b/apps/mobile/src/i18n/locales/es.json index 78bf134a56..2d7fb24401 100644 --- a/apps/mobile/src/i18n/locales/es.json +++ b/apps/mobile/src/i18n/locales/es.json @@ -1696,7 +1696,11 @@ "previousTaskFinishing": "La tarea anterior aún está finalizando. Espera un momento.", "serviceUnavailable": "El servicio no está disponible en este momento. Inténtalo de nuevo.", "connectionTrouble": "Problema de conexión. Inténtalo de nuevo en un momento.", - "modelUnavailable": "Este modelo no está disponible para Cloud Agent. Elige otro modelo e inténtalo de nuevo." + "modelUnavailable": "Este modelo no está disponible para Cloud Agent. Elige otro modelo e inténtalo de nuevo.", + "cancelQueuedFailed": "No se pudo cancelar el mensaje.", + "cancelQueuedRestoreAvailable": "Mensaje cancelado. Toca Restaurar para devolverlo al campo de mensaje.", + "cancelQueuedRestored": "Mensaje cancelado. El prompt vuelve a estar en el campo de mensaje.", + "cancelQueuedUpgradeRequired": "Cancelar mensajes en cola requiere una versión más reciente de Kilo CLI. Actualiza Kilo CLI y vuelve a conectarte." }, "remoteSession": { "failedToCreate": "No se pudo crear la sesión remota", @@ -2018,7 +2022,11 @@ "queuedAccessibility": "Mensaje en cola", "userMessage": "Mensaje del usuario", "assistantMessage": "Mensaje del asistente", - "longPressHint": "Mantén pulsado para ver los detalles del mensaje" + "longPressHint": "Mantén pulsado para ver los detalles del mensaje", + "cancelQueued": "Cancelar mensaje", + "cancelQueuedAccessibility": "Cancelar mensaje en cola", + "restoreQueued": "Restaurar", + "restoreQueuedAccessibility": "Restaurar el mensaje cancelado en el campo de mensaje" }, "olderMessages": { "arrived": "Mensajes anteriores cargados", diff --git a/apps/mobile/src/i18n/locales/et.json b/apps/mobile/src/i18n/locales/et.json index b8e7bc1e55..f9b5e22bb5 100644 --- a/apps/mobile/src/i18n/locales/et.json +++ b/apps/mobile/src/i18n/locales/et.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Eelmine ülesanne on veel lõpetamisel. Palun oota hetk.", "serviceUnavailable": "Teenus pole praegu saadaval. Palun proovi uuesti.", "connectionTrouble": "Ühenduseprobleem. Palun proovi hetke pärast uuesti.", - "modelUnavailable": "See mudel pole Cloud Agenti jaoks saadaval. Vali teine mudel ja proovi uuesti." + "modelUnavailable": "See mudel pole Cloud Agenti jaoks saadaval. Vali teine mudel ja proovi uuesti.", + "cancelQueuedFailed": "Sõnumi tühistamine ebaõnnestus.", + "cancelQueuedRestoreAvailable": "Sõnum on tühistatud. Selle sõnumi sisestusväljale tagasitoomiseks puudutage nuppu „Taastage“.", + "cancelQueuedRestored": "Sõnum on tühistatud. Päring on tagasi sõnumi sisestusväljal.", + "cancelQueuedUpgradeRequired": "Ootel sõnumite tühistamiseks on vaja uuemat Kilo CLI-d. Uuendage Kilo CLI-d ja ühenduge uuesti." }, "remoteSession": { "failedToCreate": "Kaugseansi loomine ebaõnnestus", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Sõnum järjekorras", "userMessage": "Kasutaja sõnum", "assistantMessage": "Abistaja sõnum", - "longPressHint": "Sõnumi üksikasjade jaoks vajuta pikalt" + "longPressHint": "Sõnumi üksikasjade jaoks vajuta pikalt", + "cancelQueued": "Tühistage sõnum", + "cancelQueuedAccessibility": "Tühistage ootel sõnum", + "restoreQueued": "Taastage", + "restoreQueuedAccessibility": "Taastage tühistatud sõnum sõnumi sisestusväljale" }, "olderMessages": { "arrived": "Varasemad sõnumid laaditud", diff --git a/apps/mobile/src/i18n/locales/eu.json b/apps/mobile/src/i18n/locales/eu.json index 65fa7afded..fb6cf2e308 100644 --- a/apps/mobile/src/i18n/locales/eu.json +++ b/apps/mobile/src/i18n/locales/eu.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Aurreko zeregina oraindik amaitzen ari da. Itxaron pixka bat.", "serviceUnavailable": "Zerbitzua ez dago eskuragarri oraintxe. Saiatu berriro.", "connectionTrouble": "Konexio-arazoa. Saiatu berriro minutu baten buruan.", - "modelUnavailable": "Modelu hau ez dago eskuragarri Cloud Agent-erako. Aukeratu beste modelo bat eta saiatu berriro." + "modelUnavailable": "Modelu hau ez dago eskuragarri Cloud Agent-erako. Aukeratu beste modelo bat eta saiatu berriro.", + "cancelQueuedFailed": "Ezin izan da mezua bertan behera utzi.", + "cancelQueuedRestoreAvailable": "Mezua bertan behera utzi da. Sakatu Berreskuratu konpositorera itzultzeko.", + "cancelQueuedRestored": "Mezua bertan behera utzi da. Proposamena berriro konpositorean dago.", + "cancelQueuedUpgradeRequired": "Ilaran dauden mezuak bertan behera uzteko Kilo CLI bertsio berriago bat behar da. Eguneratu Kilo CLI eta konektatu berriro." }, "remoteSession": { "failedToCreate": "Ezin da urruneko saioa sortu", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Mezua antolatuta", "userMessage": "Erabiltzailearen mezua", "assistantMessage": "Laguntzailearen mezua", - "longPressHint": "Sakatuta eduki mezuen xehetasunak ikusteko" + "longPressHint": "Sakatuta eduki mezuen xehetasunak ikusteko", + "cancelQueued": "Utzi mezua bertan behera", + "cancelQueuedAccessibility": "Utzi ilaran dagoen mezua bertan behera", + "restoreQueued": "Berreskuratu", + "restoreQueuedAccessibility": "Berreskuratu bertan behera utzitako mezua konpositorean" }, "olderMessages": { "arrived": "Aurreko mezuak kargatu dira", diff --git a/apps/mobile/src/i18n/locales/fa.json b/apps/mobile/src/i18n/locales/fa.json index d5215e1b81..348a7aa08b 100644 --- a/apps/mobile/src/i18n/locales/fa.json +++ b/apps/mobile/src/i18n/locales/fa.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "وظیفه قبلی هنوز در حال پایان است. لطفاً لحظهای صبر کنید.", "serviceUnavailable": "سرویس در حال حاضر در دسترس نیست. لطفاً دوباره تلاش کنید.", "connectionTrouble": "مشکل در اتصال. لطفاً لحظهای دیگر دوباره تلاش کنید.", - "modelUnavailable": "این مدل برای Cloud Agent در دسترس نیست. مدل دیگری انتخاب کنید و دوباره تلاش کنید." + "modelUnavailable": "این مدل برای Cloud Agent در دسترس نیست. مدل دیگری انتخاب کنید و دوباره تلاش کنید.", + "cancelQueuedFailed": "لغو پیام ممکن نشد.", + "cancelQueuedRestoreAvailable": "پیام لغو شد. برای بازگرداندن آن به کادر ورودی پیام، روی بازیابی ضربه بزنید.", + "cancelQueuedRestored": "پیام لغو شد. پرسش به کادر ورودی پیام بازگشت.", + "cancelQueuedUpgradeRequired": "لغو پیام‌های در صف به نسخه جدیدتری از Kilo CLI نیاز دارد. Kilo CLI را به‌روزرسانی کنید و دوباره متصل شوید." }, "remoteSession": { "failedToCreate": "ایجاد نشست از راه دور شکست خورد", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "پیام در صف قرار گرفت", "userMessage": "پیام کاربر", "assistantMessage": "پیام دستیار", - "longPressHint": "برای جزئیات پیام، لمس طولانی کنید" + "longPressHint": "برای جزئیات پیام، لمس طولانی کنید", + "cancelQueued": "لغو پیام", + "cancelQueuedAccessibility": "لغو پیام در صف", + "restoreQueued": "بازیابی", + "restoreQueuedAccessibility": "بازیابی پیام لغوشده در کادر ورودی پیام" }, "olderMessages": { "arrived": "پیام‌های قبلی بارگذاری شدند", diff --git a/apps/mobile/src/i18n/locales/fi.json b/apps/mobile/src/i18n/locales/fi.json index 061d2a73df..bb35c40b92 100644 --- a/apps/mobile/src/i18n/locales/fi.json +++ b/apps/mobile/src/i18n/locales/fi.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Edellinen tehtävä on vielä viimeistelyssä. Odota hetki.", "serviceUnavailable": "Palvelu ei ole tällä hetkellä käytettävissä. Yritä uudelleen.", "connectionTrouble": "Yhteysongelma. Yritä uudelleen hetken kuluttua.", - "modelUnavailable": "Tämä malli ei ole käytettävissä Cloud Agentille. Valitse toinen malli ja yritä uudelleen." + "modelUnavailable": "Tämä malli ei ole käytettävissä Cloud Agentille. Valitse toinen malli ja yritä uudelleen.", + "cancelQueuedFailed": "Viestiä ei voitu peruuttaa.", + "cancelQueuedRestoreAvailable": "Viesti peruutettu. Napauta Palauta palauttaaksesi sen viestikenttään.", + "cancelQueuedRestored": "Viesti peruutettu. Kehote on taas viestikentässä.", + "cancelQueuedUpgradeRequired": "Jonossa olevien viestien peruuttaminen edellyttää uudempaa Kilo CLI:tä. Päivitä Kilo CLI ja yhdistä uudelleen." }, "remoteSession": { "failedToCreate": "Etäistunnon luonti epäonnistui", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Viesti jonossa", "userMessage": "Käyttäjän viesti", "assistantMessage": "Avustajan viesti", - "longPressHint": "Pitkä painallus viestin tietoja varten" + "longPressHint": "Pitkä painallus viestin tietoja varten", + "cancelQueued": "Peruuta viesti", + "cancelQueuedAccessibility": "Peruuta jonossa oleva viesti", + "restoreQueued": "Palauta", + "restoreQueuedAccessibility": "Palauta peruutettu viesti viestikenttään" }, "olderMessages": { "arrived": "Aiemmat viestit ladattu", diff --git a/apps/mobile/src/i18n/locales/fil.json b/apps/mobile/src/i18n/locales/fil.json index 6beb985681..3c09ac0e5b 100644 --- a/apps/mobile/src/i18n/locales/fil.json +++ b/apps/mobile/src/i18n/locales/fil.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Tinatapos pa lang ang nakaraang gawain. Pakihintay sandali.", "serviceUnavailable": "Hindi available ang serbisyo ngayon. Pakisubukan muli.", "connectionTrouble": "Problema sa koneksyon. Pakisubukan muli pagkalipas ng ilang sandali.", - "modelUnavailable": "Hindi available ang modelong ito para sa Cloud Agent. Pumili ng ibang modelo at subukang muli." + "modelUnavailable": "Hindi available ang modelong ito para sa Cloud Agent. Pumili ng ibang modelo at subukang muli.", + "cancelQueuedFailed": "Hindi makansela ang mensahe.", + "cancelQueuedRestoreAvailable": "Kinansela ang mensahe. I-tap ang I-restore para ibalik ito sa input ng mensahe.", + "cancelQueuedRestored": "Kinansela ang mensahe. Nasa input ng mensahe na muli ang prompt.", + "cancelQueuedUpgradeRequired": "Ang pagkansela ng mga nakapilang mensahe ay nangangailangan ng mas bagong Kilo CLI. I-update ang Kilo CLI at muling kumonekta." }, "remoteSession": { "failedToCreate": "Nabigong gumawa ng remote session", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Naka-queue ang mensahe", "userMessage": "Mensahe ng user", "assistantMessage": "Mensahe ng assistant", - "longPressHint": "Pindutin nang matagal para sa mga detalye ng mensahe" + "longPressHint": "Pindutin nang matagal para sa mga detalye ng mensahe", + "cancelQueued": "Kanselahin ang mensahe", + "cancelQueuedAccessibility": "Kanselahin ang nakapilang mensahe", + "restoreQueued": "I-restore", + "restoreQueuedAccessibility": "I-restore ang kinanselang mensahe sa input ng mensahe" }, "olderMessages": { "arrived": "Ni-load ang mga naunang mensahe", diff --git a/apps/mobile/src/i18n/locales/fr.json b/apps/mobile/src/i18n/locales/fr.json index c40d8f67af..a7e7b83b77 100644 --- a/apps/mobile/src/i18n/locales/fr.json +++ b/apps/mobile/src/i18n/locales/fr.json @@ -1614,7 +1614,11 @@ "previousTaskFinishing": "La tâche précédente est encore en cours. Veuillez patienter un instant.", "serviceUnavailable": "Le service est indisponible pour le moment. Veuillez réessayer.", "connectionTrouble": "Problème de connexion. Veuillez réessayer dans un instant.", - "modelUnavailable": "Ce modèle n'est pas disponible pour Cloud Agent. Choisissez un autre modèle et réessayez." + "modelUnavailable": "Ce modèle n'est pas disponible pour Cloud Agent. Choisissez un autre modèle et réessayez.", + "cancelQueuedFailed": "Impossible d'annuler le message.", + "cancelQueuedRestoreAvailable": "Message annulé. Touchez Restaurer pour le remettre dans le champ de saisie.", + "cancelQueuedRestored": "Message annulé. Le prompt est de nouveau dans le champ de saisie.", + "cancelQueuedUpgradeRequired": "L'annulation des messages en attente nécessite une version plus récente de Kilo CLI. Mettez à jour Kilo CLI et reconnectez-vous." }, "remoteSession": { "failedToCreate": "Échec de la création de la session distante", @@ -1936,7 +1940,11 @@ "queuedAccessibility": "Message en file d'attente", "userMessage": "Message de l'utilisateur", "assistantMessage": "Message de l'assistant", - "longPressHint": "Appui long pour les détails du message" + "longPressHint": "Appui long pour les détails du message", + "cancelQueued": "Annuler le message", + "cancelQueuedAccessibility": "Annuler le message en attente", + "restoreQueued": "Restaurer", + "restoreQueuedAccessibility": "Restaurer le message annulé dans le champ de saisie" }, "olderMessages": { "arrived": "Messages précédents chargés", diff --git a/apps/mobile/src/i18n/locales/ga.json b/apps/mobile/src/i18n/locales/ga.json index e059092bbd..f774864607 100644 --- a/apps/mobile/src/i18n/locales/ga.json +++ b/apps/mobile/src/i18n/locales/ga.json @@ -2187,7 +2187,11 @@ "previousTaskFinishing": "Tá an tasc roimhe seo fós ag críochnú. Fan nóiméad le do thoil.", "serviceUnavailable": "Níl an tseirbhís ar fáil faoi láthair. Bain triail as arís le do thoil.", "connectionTrouble": "Fadhb cheangail. Bain triail as arís i gceann nóiméid.", - "modelUnavailable": "Níl an tsamhail seo ar fáil do Cloud Agent. Roghnaigh samhail eile agus bain triail as arís." + "modelUnavailable": "Níl an tsamhail seo ar fáil do Cloud Agent. Roghnaigh samhail eile agus bain triail as arís.", + "cancelQueuedFailed": "Níorbh fhéidir an teachtaireacht a chealú.", + "cancelQueuedRestoreAvailable": "Cealaíodh an teachtaireacht. Tapáil Athchóirigh chun í a chur ar ais sa réimse ionchuir teachtaireachta.", + "cancelQueuedRestored": "Cealaíodh an teachtaireacht. Tá an leid ar ais sa réimse ionchuir teachtaireachta.", + "cancelQueuedUpgradeRequired": "Éilíonn cealú teachtaireachtaí sa scuaine Kilo CLI níos nua. Nuashonraigh Kilo CLI agus athcheangail." }, "remoteSession": { "failedToCreate": "Theip ar sheisiún cianda a chruthú", @@ -2511,7 +2515,11 @@ "queuedAccessibility": "Teachtaireacht curtha sa scuaine", "userMessage": "Teachtaireacht úsáideora", "assistantMessage": "Teachtaireacht chúntóra", - "longPressHint": "Brúigh fada le sonraí na teachtaireachta" + "longPressHint": "Brúigh fada le sonraí na teachtaireachta", + "cancelQueued": "Cealaigh teachtaireacht", + "cancelQueuedAccessibility": "Cealaigh teachtaireacht sa scuaine", + "restoreQueued": "Athchóirigh", + "restoreQueuedAccessibility": "Athchóirigh an teachtaireacht chealaithe sa réimse ionchuir teachtaireachta" }, "olderMessages": { "arrived": "Lódáladh teachtaireachtaí níos luaithe", diff --git a/apps/mobile/src/i18n/locales/gl.json b/apps/mobile/src/i18n/locales/gl.json index adcadae60d..3a7237dbc7 100644 --- a/apps/mobile/src/i18n/locales/gl.json +++ b/apps/mobile/src/i18n/locales/gl.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "A tarefa anterior aínda está a rematar. Agarda un momento.", "serviceUnavailable": "O servizo non está dispoñible agora mesmo. Téntao de novo.", "connectionTrouble": "Problema de conexión. Téntao de novo nun momento.", - "modelUnavailable": "Este modelo non está dispoñible para Cloud Agent. Escolle outro modelo e téntao de novo." + "modelUnavailable": "Este modelo non está dispoñible para Cloud Agent. Escolle outro modelo e téntao de novo.", + "cancelQueuedFailed": "Non se puido cancelar a mensaxe.", + "cancelQueuedRestoreAvailable": "Mensaxe cancelada. Toca Restaurar para devolvela ao campo de mensaxe.", + "cancelQueuedRestored": "Mensaxe cancelada. A indicación volveu ao campo de mensaxe.", + "cancelQueuedUpgradeRequired": "Cancelar mensaxes na cola require un Kilo CLI máis novo. Actualiza o Kilo CLI e reconéctate." }, "remoteSession": { "failedToCreate": "Non se puido crear a sesión remota", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Mensaxe en cola", "userMessage": "Mensaxe de usuario", "assistantMessage": "Mensaxe de asistente", - "longPressHint": "Mantén premido para ver os detalles da mensaxe" + "longPressHint": "Mantén premido para ver os detalles da mensaxe", + "cancelQueued": "Cancelar mensaxe", + "cancelQueuedAccessibility": "Cancelar a mensaxe na cola", + "restoreQueued": "Restaurar", + "restoreQueuedAccessibility": "Restaurar a mensaxe cancelada no campo de mensaxe" }, "olderMessages": { "arrived": "Cargáronse as mensaxes anteriores", diff --git a/apps/mobile/src/i18n/locales/gu.json b/apps/mobile/src/i18n/locales/gu.json index 3b98577f9c..25e6c1e25c 100644 --- a/apps/mobile/src/i18n/locales/gu.json +++ b/apps/mobile/src/i18n/locales/gu.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "પાછલું કાર્ય હજી પૂરું થઈ રહ્યું છે. થોડી રાહ જુઓ.", "serviceUnavailable": "સેવા હાલ ઉપલબ્ધ નથી. ફરી પ્રયાસ કરો.", "connectionTrouble": "કનેક્શન સમસ્યા. થોડી વાર પછી ફરી પ્રયાસ કરો.", - "modelUnavailable": "આ મૉડલ Cloud Agent માટે ઉપલબ્ધ નથી. બીજું મૉડલ પસંદ કરીને ફરી પ્રયાસ કરો." + "modelUnavailable": "આ મૉડલ Cloud Agent માટે ઉપલબ્ધ નથી. બીજું મૉડલ પસંદ કરીને ફરી પ્રયાસ કરો.", + "cancelQueuedFailed": "સંદેશ રદ કરી શકાયો નહીં.", + "cancelQueuedRestoreAvailable": "સંદેશ રદ થયો. તેને સંદેશ ઇનપુટમાં પાછો લાવવા માટે પુનઃસ્થાપિત કરો પર ટેપ કરો.", + "cancelQueuedRestored": "સંદેશ રદ થયો. પ્રોમ્પ્ટ સંદેશ ઇનપુટમાં પાછો આવી ગયો છે.", + "cancelQueuedUpgradeRequired": "કતારમાં રહેલા સંદેશો રદ કરવા માટે Kilo CLI નું નવું સંસ્કરણ જરૂરી છે. Kilo CLI અપડેટ કરો અને ફરી કનેક્ટ કરો." }, "remoteSession": { "failedToCreate": "રિમોટ સત્ર બનાવવામાં નિષ્ફળ", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "સંદેશ કતારબદ્ધ", "userMessage": "વપરાશકર્તા સંદેશ", "assistantMessage": "સહાયક સંદેશ", - "longPressHint": "સંદેશ વિગતો માટે લાંબું દબાવો" + "longPressHint": "સંદેશ વિગતો માટે લાંબું દબાવો", + "cancelQueued": "સંદેશ રદ કરો", + "cancelQueuedAccessibility": "કતારમાં રહેલો સંદેશ રદ કરો", + "restoreQueued": "પુનઃસ્થાપિત કરો", + "restoreQueuedAccessibility": "રદ કરેલો સંદેશ સંદેશ ઇનપુટમાં પુનઃસ્થાપિત કરો" }, "olderMessages": { "arrived": "અગાઉના સંદેશાઓ લોડ થયા", diff --git a/apps/mobile/src/i18n/locales/ha.json b/apps/mobile/src/i18n/locales/ha.json index 7d0f2ff68f..95e3c1d3bf 100644 --- a/apps/mobile/src/i18n/locales/ha.json +++ b/apps/mobile/src/i18n/locales/ha.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Aikin da ya gabata har yanzu yana ƙarewa. Da fatan za ka jira ɗan lokaci.", "serviceUnavailable": "Sabis ɗin ba ya aiki a yanzu. Da fatan za ka sake gwadawa.", "connectionTrouble": "Matsalar haɗi. Da fatan za ka sake gwadawa bayan ɗan lokaci.", - "modelUnavailable": "Wannan samfurin ba shi da aiki ga Cloud Agent. Zaɓi wani samfurin kuma sake gwadawa." + "modelUnavailable": "Wannan samfurin ba shi da aiki ga Cloud Agent. Zaɓi wani samfurin kuma sake gwadawa.", + "cancelQueuedFailed": "An kasa soke saƙon.", + "cancelQueuedRestoreAvailable": "An soke saƙon. Danna Maido don mayar da shi zuwa wurin shigar da saƙo.", + "cancelQueuedRestored": "An soke saƙon. Bayanin ya koma wurin shigar da saƙo.", + "cancelQueuedUpgradeRequired": "Soke saƙonnin da ke jira yana buƙatar sabuwar Kilo CLI. Sabunta Kilo CLI kuma sake haɗawa." }, "remoteSession": { "failedToCreate": "An kasa ƙirƙirar zaman na nesa", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "An jera saƙon", "userMessage": "Saƙon mai amfani", "assistantMessage": "Saƙon mataimaki", - "longPressHint": "Danna tsayin lokaci don cikakkun bayanan saƙo" + "longPressHint": "Danna tsayin lokaci don cikakkun bayanan saƙo", + "cancelQueued": "Soke saƙo", + "cancelQueuedAccessibility": "Soke saƙon da ke jira", + "restoreQueued": "Maido", + "restoreQueuedAccessibility": "Maido da saƙon da aka soke zuwa wurin shigar da saƙo" }, "olderMessages": { "arrived": "An ɗora saƙonnin farko", diff --git a/apps/mobile/src/i18n/locales/he.json b/apps/mobile/src/i18n/locales/he.json index eb96e8409f..57f6c7ef74 100644 --- a/apps/mobile/src/i18n/locales/he.json +++ b/apps/mobile/src/i18n/locales/he.json @@ -1696,7 +1696,11 @@ "previousTaskFinishing": "המשימה הקודמת עדיין מסתיימת. אנא המתן רגע.", "serviceUnavailable": "השירות אינו זמין כרגע. אנא נסה שוב.", "connectionTrouble": "בעיית חיבור. אנא נסה שוב בעוד רגע.", - "modelUnavailable": "המודל הזה אינו זמין עבור Cloud Agent. בחר מודל אחר ונסה שוב." + "modelUnavailable": "המודל הזה אינו זמין עבור Cloud Agent. בחר מודל אחר ונסה שוב.", + "cancelQueuedFailed": "לא ניתן היה לבטל את ההודעה.", + "cancelQueuedRestoreAvailable": "ההודעה בוטלה. הקש על שחזר כדי להחזיר אותה לשדה ההודעה.", + "cancelQueuedRestored": "ההודעה בוטלה. ההנחיה חזרה לשדה ההודעה.", + "cancelQueuedUpgradeRequired": "ביטול הודעות בתור דורש Kilo CLI חדש יותר. עדכן את Kilo CLI והתחבר מחדש." }, "remoteSession": { "failedToCreate": "לא ניתן היה ליצור הפעלה מרוחקת", @@ -2018,7 +2022,11 @@ "queuedAccessibility": "ההודעה בתור", "userMessage": "הודעת משתמש", "assistantMessage": "הודעת עוזר", - "longPressHint": "לחיצה ארוכה לפרטי ההודעה" + "longPressHint": "לחיצה ארוכה לפרטי ההודעה", + "cancelQueued": "בטל הודעה", + "cancelQueuedAccessibility": "בטל הודעה בתור", + "restoreQueued": "שחזר", + "restoreQueuedAccessibility": "שחזר הודעה שבוטלה לשדה ההודעה" }, "olderMessages": { "arrived": "הודעות קודמות נטענו", diff --git a/apps/mobile/src/i18n/locales/hi.json b/apps/mobile/src/i18n/locales/hi.json index d09c64d02f..9c7f0b1e51 100644 --- a/apps/mobile/src/i18n/locales/hi.json +++ b/apps/mobile/src/i18n/locales/hi.json @@ -1688,7 +1688,11 @@ "previousTaskFinishing": "पिछला कार्य अभी भी पूरा हो रहा है. कृपया थोड़ा इंतज़ार करें.", "serviceUnavailable": "सेवा अभी उपलब्ध नहीं है. कृपया फिर से प्रयास करें.", "connectionTrouble": "कनेक्शन समस्या. कृपया थोड़ी देर में फिर से प्रयास करें.", - "modelUnavailable": "यह मॉडल Cloud Agent के लिए उपलब्ध नहीं है. दूसरा मॉडल चुनें और फिर से प्रयास करें." + "modelUnavailable": "यह मॉडल Cloud Agent के लिए उपलब्ध नहीं है. दूसरा मॉडल चुनें और फिर से प्रयास करें.", + "cancelQueuedFailed": "संदेश रद्द नहीं किया जा सका।", + "cancelQueuedRestoreAvailable": "संदेश रद्द कर दिया गया। इसे संदेश इनपुट में वापस लाने के लिए पुनर्स्थापित करें पर टैप करें।", + "cancelQueuedRestored": "संदेश रद्द कर दिया गया। प्रॉम्प्ट संदेश इनपुट में वापस आ गया है।", + "cancelQueuedUpgradeRequired": "कतार में लगे संदेशों को रद्द करने के लिए Kilo CLI का नया संस्करण आवश्यक है। Kilo CLI अपडेट करें और फिर से कनेक्ट करें।" }, "sessionConnection": { "connecting": "कनेक्ट हो रहा है…", @@ -1982,7 +1986,11 @@ "queuedAccessibility": "संदेश कतार में", "userMessage": "उपयोगकर्ता संदेश", "assistantMessage": "सहायक संदेश", - "longPressHint": "संदेश विवरण के लिए देर तक दबाएँ" + "longPressHint": "संदेश विवरण के लिए देर तक दबाएँ", + "cancelQueued": "संदेश रद्द करें", + "cancelQueuedAccessibility": "कतार में लगा संदेश रद्द करें", + "restoreQueued": "पुनर्स्थापित करें", + "restoreQueuedAccessibility": "रद्द किए गए संदेश को संदेश इनपुट में पुनर्स्थापित करें" }, "olderMessages": { "arrived": "पहले के संदेश लोड हो गए", diff --git a/apps/mobile/src/i18n/locales/hr.json b/apps/mobile/src/i18n/locales/hr.json index 285d0d1921..a8f0e47d0d 100644 --- a/apps/mobile/src/i18n/locales/hr.json +++ b/apps/mobile/src/i18n/locales/hr.json @@ -2157,7 +2157,11 @@ "previousTaskFinishing": "Prethodni zadatak još uvijek završava. Pričekajte trenutak.", "serviceUnavailable": "Usluga trenutačno nije dostupna. Pokušajte ponovno.", "connectionTrouble": "Problem s vezom. Pokušajte ponovno za trenutak.", - "modelUnavailable": "Ovaj model nije dostupan za Cloud Agent. Odaberite drugi model i pokušajte ponovno." + "modelUnavailable": "Ovaj model nije dostupan za Cloud Agent. Odaberite drugi model i pokušajte ponovno.", + "cancelQueuedFailed": "Nije moguće otkazati poruku.", + "cancelQueuedRestoreAvailable": "Poruka je otkazana. Dodirnite „Vratite” da biste je vratili u polje za unos poruke.", + "cancelQueuedRestored": "Poruka je otkazana. Upit je ponovno u polju za unos poruke.", + "cancelQueuedUpgradeRequired": "Otkazivanje poruka na čekanju zahtijeva noviju verziju Kilo CLI-ja. Ažurirajte Kilo CLI i ponovno se povežite." }, "remoteSession": { "failedToCreate": "Stvaranje udaljene sesije nije uspjelo", @@ -2479,7 +2483,11 @@ "queuedAccessibility": "Poruka u redu čekanja", "userMessage": "Korisnička poruka", "assistantMessage": "Poruka asistenta", - "longPressHint": "Dugo pritisnite za pojedinosti poruke" + "longPressHint": "Dugo pritisnite za pojedinosti poruke", + "cancelQueued": "Otkažite poruku", + "cancelQueuedAccessibility": "Otkažite poruku na čekanju", + "restoreQueued": "Vratite", + "restoreQueuedAccessibility": "Vratite otkazanu poruku u polje za unos poruke" }, "olderMessages": { "arrived": "Ranije poruke učitane", diff --git a/apps/mobile/src/i18n/locales/ht.json b/apps/mobile/src/i18n/locales/ht.json index 19f6bf355e..eed3cfb71f 100644 --- a/apps/mobile/src/i18n/locales/ht.json +++ b/apps/mobile/src/i18n/locales/ht.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Travay anvan an poko fini. Tanpri tann yon ti moman.", "serviceUnavailable": "Sèvis la pa disponib kounye a. Tanpri eseye ankò.", "connectionTrouble": "Pwoblèm koneksyon. Tanpri eseye ankò nan yon moman.", - "modelUnavailable": "Modèl sa a pa disponib pou Cloud Agent. Chwazi yon lòt modèl epi eseye ankò." + "modelUnavailable": "Modèl sa a pa disponib pou Cloud Agent. Chwazi yon lòt modèl epi eseye ankò.", + "cancelQueuedFailed": "Pa t ka anile mesaj la.", + "cancelQueuedRestoreAvailable": "Mesaj la anile. Tape Retabli pou remete li nan chan mesaj la.", + "cancelQueuedRestored": "Mesaj la anile. Pwompt la tounen nan chan mesaj la.", + "cancelQueuedUpgradeRequired": "Anile mesaj annatant yo mande yon vèsyon Kilo CLI ki pi resan. Mete ajou Kilo CLI a epi rekonekte." }, "remoteSession": { "failedToCreate": "Echwe pou kreye sesyon aleka", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Mesaj nan fil", "userMessage": "Mesaj itilizatè", "assistantMessage": "Mesaj asistan", - "longPressHint": "Peze lontan pou detay mesaj" + "longPressHint": "Peze lontan pou detay mesaj", + "cancelQueued": "Anile mesaj la", + "cancelQueuedAccessibility": "Anile mesaj annatant lan", + "restoreQueued": "Retabli", + "restoreQueuedAccessibility": "Retabli mesaj anile a nan chan mesaj la" }, "olderMessages": { "arrived": "Mesaj pi bonè chaje", diff --git a/apps/mobile/src/i18n/locales/hu.json b/apps/mobile/src/i18n/locales/hu.json index 255bdb3b9b..c8f4ebf93d 100644 --- a/apps/mobile/src/i18n/locales/hu.json +++ b/apps/mobile/src/i18n/locales/hu.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Az előző feladat még befejeződik. Kérjük, várjon egy pillanatot.", "serviceUnavailable": "A szolgáltatás jelenleg nem érhető el. Kérjük, próbálja újra.", "connectionTrouble": "Kapcsolódási probléma. Kérjük, próbálja újra egy pillanat múlva.", - "modelUnavailable": "Ez a modell nem érhető el a Cloud Agent számára. Válasszon másik modellt, és próbálja újra." + "modelUnavailable": "Ez a modell nem érhető el a Cloud Agent számára. Válasszon másik modellt, és próbálja újra.", + "cancelQueuedFailed": "Nem sikerült visszavonni az üzenetet.", + "cancelQueuedRestoreAvailable": "Üzenet visszavonva. Koppintson a Visszaállítás gombra, hogy visszakerüljön az üzenetbeviteli mezőbe.", + "cancelQueuedRestored": "Üzenet visszavonva. A prompt visszakerült az üzenetbeviteli mezőbe.", + "cancelQueuedUpgradeRequired": "A várakozó üzenetek visszavonásához újabb Kilo CLI szükséges. Frissítse a Kilo CLI-t, és csatlakozzon újra." }, "remoteSession": { "failedToCreate": "A távoli munkamenet létrehozása nem sikerült", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Üzenet sorban", "userMessage": "Felhasználói üzenet", "assistantMessage": "Asszisztens üzenet", - "longPressHint": "Az üzenet részleteihez tartsd nyomva" + "longPressHint": "Az üzenet részleteihez tartsd nyomva", + "cancelQueued": "Üzenet visszavonása", + "cancelQueuedAccessibility": "Várakozó üzenet visszavonása", + "restoreQueued": "Visszaállítás", + "restoreQueuedAccessibility": "Visszavont üzenet visszaállítása az üzenetbeviteli mezőbe" }, "olderMessages": { "arrived": "Korábbi üzenetek betöltve", diff --git a/apps/mobile/src/i18n/locales/hy.json b/apps/mobile/src/i18n/locales/hy.json index 523b18c07b..848c7aa6f2 100644 --- a/apps/mobile/src/i18n/locales/hy.json +++ b/apps/mobile/src/i18n/locales/hy.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Նախորդ առաջադրանքը դեռ ավարտվում է։ Խնդրում ենք սպասել մի պահ։", "serviceUnavailable": "Ծառայությունն այս պահին հասանելի չէ։ Խնդրում ենք փորձեք կրկին։", "connectionTrouble": "Կապի խնդիր։ Խնդրում ենք փորձեք կրկին մի պահ հետո։", - "modelUnavailable": "Այս մոդելը հասանելի չէ Cloud Agent-ի համար։ Ընտրեք մեկ այլ մոդել և փորձեք կրկին։" + "modelUnavailable": "Այս մոդելը հասանելի չէ Cloud Agent-ի համար։ Ընտրեք մեկ այլ մոդել և փորձեք կրկին։", + "cancelQueuedFailed": "Չհաջողվեց չեղարկել հաղորդագրությունը։", + "cancelQueuedRestoreAvailable": "Հաղորդագրությունը չեղարկված է։ Հպեք «Վերականգնել»՝ այն հաղորդագրության մուտքագրման դաշտ վերադարձնելու համար։", + "cancelQueuedRestored": "Հաղորդագրությունը չեղարկված է։ Հուշումը կրկին հաղորդագրության մուտքագրման դաշտում է։", + "cancelQueuedUpgradeRequired": "Հերթագրված հաղորդագրությունները չեղարկելու համար անհրաժեշտ է Kilo CLI-ի ավելի նոր տարբերակ։ Թարմացրեք Kilo CLI-ն և կրկին միացեք։" }, "remoteSession": { "failedToCreate": "Չհաջողվեց ստեղծել հեռավոր նիստ", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Հաղորդագրությունը հերթում է", "userMessage": "Օգտատիրոջ հաղորդագրություն", "assistantMessage": "Օգնականի հաղորդագրություն", - "longPressHint": "Երկար սեղմեք՝ հաղորդագրության մանրամասները տեսնելու համար" + "longPressHint": "Երկար սեղմեք՝ հաղորդագրության մանրամասները տեսնելու համար", + "cancelQueued": "Չեղարկել հաղորդագրությունը", + "cancelQueuedAccessibility": "Չեղարկել հերթագրված հաղորդագրությունը", + "restoreQueued": "Վերականգնել", + "restoreQueuedAccessibility": "Վերականգնել չեղարկված հաղորդագրությունը հաղորդագրության մուտքագրման դաշտում" }, "olderMessages": { "arrived": "Ավելի վաղ հաղորդագրությունները բեռնված են", diff --git a/apps/mobile/src/i18n/locales/id.json b/apps/mobile/src/i18n/locales/id.json index f9518cc897..f99243080e 100644 --- a/apps/mobile/src/i18n/locales/id.json +++ b/apps/mobile/src/i18n/locales/id.json @@ -1688,7 +1688,11 @@ "previousTaskFinishing": "Tugas sebelumnya masih selesai. Tunggu sebentar.", "serviceUnavailable": "Layanan tidak tersedia saat ini. Coba lagi.", "connectionTrouble": "Gangguan koneksi. Coba lagi sebentar lagi.", - "modelUnavailable": "Model ini tidak tersedia untuk Cloud Agent. Pilih model lain dan coba lagi." + "modelUnavailable": "Model ini tidak tersedia untuk Cloud Agent. Pilih model lain dan coba lagi.", + "cancelQueuedFailed": "Tidak dapat membatalkan pesan.", + "cancelQueuedRestoreAvailable": "Pesan dibatalkan. Ketuk Pulihkan untuk mengembalikannya ke kolom pesan.", + "cancelQueuedRestored": "Pesan dibatalkan. Prompt kembali ke kolom pesan.", + "cancelQueuedUpgradeRequired": "Pembatalan pesan dalam antrean memerlukan Kilo CLI yang lebih baru. Perbarui Kilo CLI dan sambungkan kembali." }, "remoteSession": { "failedToCreate": "Gagal membuat sesi jarak jauh", @@ -2009,7 +2013,11 @@ "queuedAccessibility": "Pesan dalam antrean", "userMessage": "Pesan pengguna", "assistantMessage": "Pesan asisten", - "longPressHint": "Tekan lama untuk detail pesan" + "longPressHint": "Tekan lama untuk detail pesan", + "cancelQueued": "Batalkan pesan", + "cancelQueuedAccessibility": "Batalkan pesan dalam antrean", + "restoreQueued": "Pulihkan", + "restoreQueuedAccessibility": "Pulihkan pesan yang dibatalkan ke kolom pesan" }, "olderMessages": { "arrived": "Pesan sebelumnya dimuat", diff --git a/apps/mobile/src/i18n/locales/ig.json b/apps/mobile/src/i18n/locales/ig.json index 9f6086ba6a..4ba0221280 100644 --- a/apps/mobile/src/i18n/locales/ig.json +++ b/apps/mobile/src/i18n/locales/ig.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Ọrụ gara aga ka na-agwụcha. Biko chere obere oge.", "serviceUnavailable": "Ọrụ ahụ adịghị ugbu a. Biko nwaa ọzọ.", "connectionTrouble": "Nsogbu njikọ. Biko nwaa ọzọ obere oge.", - "modelUnavailable": "Ihe nlereanya a adịghị maka Cloud Agent. Họrọ ihe nlereanya ọzọ ma nwaa ọzọ." + "modelUnavailable": "Ihe nlereanya a adịghị maka Cloud Agent. Họrọ ihe nlereanya ọzọ ma nwaa ọzọ.", + "cancelQueuedFailed": "Enweghị ike ịkagbu ozi ahụ.", + "cancelQueuedRestoreAvailable": "Akagbuola ozi. Pịa Weghachite ka iweghachite ya n'ebe ntinye ozi.", + "cancelQueuedRestored": "Akagbuola ozi. Ihe ị dere alaghachila n'ebe ntinye ozi.", + "cancelQueuedUpgradeRequired": "Ịkagbu ozi na-echere chọrọ Kilo CLI ọhụrụ karịa. Melite Kilo CLI ma jikọọ ọzọ." }, "remoteSession": { "failedToCreate": "Dara ịmepụta nzukọ dị anya", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Edobelere ozi", "userMessage": "Ozi onye ọrụ", "assistantMessage": "Ozi enyemaka", - "longPressHint": "Pịa ogologo maka nkọwa ozi" + "longPressHint": "Pịa ogologo maka nkọwa ozi", + "cancelQueued": "Kagbuo ozi", + "cancelQueuedAccessibility": "Kagbuo ozi na-echere", + "restoreQueued": "Weghachite", + "restoreQueuedAccessibility": "Weghachite ozi akagbuola n'ebe ntinye ozi" }, "olderMessages": { "arrived": "Ebuturu ozi ndị gara aga", diff --git a/apps/mobile/src/i18n/locales/is.json b/apps/mobile/src/i18n/locales/is.json index 4027707d84..10af4293bf 100644 --- a/apps/mobile/src/i18n/locales/is.json +++ b/apps/mobile/src/i18n/locales/is.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Fyrra verkefni er enn að klárast. Bíddu augnablik.", "serviceUnavailable": "Þjónustan er ekki tiltæk núna. Reyndu aftur.", "connectionTrouble": "Tengingarvandræði. Reyndu aftur eftir augnablik.", - "modelUnavailable": "Þetta líkan er ekki tiltækt fyrir Cloud Agent. Veldu annað líkan og reyndu aftur." + "modelUnavailable": "Þetta líkan er ekki tiltækt fyrir Cloud Agent. Veldu annað líkan og reyndu aftur.", + "cancelQueuedFailed": "Ekki tókst að hætta við skilaboðin.", + "cancelQueuedRestoreAvailable": "Hætt var við skilaboðin. Bankaðu á Endurheimta til að færa þau aftur í ritilinn.", + "cancelQueuedRestored": "Hætt var við skilaboðin. Skilaboðin eru aftur í ritlinum.", + "cancelQueuedUpgradeRequired": "Til að hætta við skilaboð í biðröð þarf nýrri Kilo CLI. Uppfærðu Kilo CLI og tengdu aftur." }, "remoteSession": { "failedToCreate": "Ekki tókst að búa til fjarlota lotu", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Skilaboð sett í biðröð", "userMessage": "Skilaboð frá notanda", "assistantMessage": "Skilaboð frá aðstoðarmanni", - "longPressHint": "Haltu lengi inni fyrir skilaboðaupplýsingar" + "longPressHint": "Haltu lengi inni fyrir skilaboðaupplýsingar", + "cancelQueued": "Hætta við skilaboð", + "cancelQueuedAccessibility": "Hætta við skilaboð í biðröð", + "restoreQueued": "Endurheimta", + "restoreQueuedAccessibility": "Endurheimta afturkölluð skilaboð í ritilinn" }, "olderMessages": { "arrived": "Eldri skilaboð hlaðin", diff --git a/apps/mobile/src/i18n/locales/it.json b/apps/mobile/src/i18n/locales/it.json index 99e2db0963..1cef234a61 100644 --- a/apps/mobile/src/i18n/locales/it.json +++ b/apps/mobile/src/i18n/locales/it.json @@ -1680,7 +1680,11 @@ "previousTaskFinishing": "L'attività precedente sta ancora terminando. Attendere un momento.", "serviceUnavailable": "Il servizio non è disponibile al momento. Riprova.", "connectionTrouble": "Problemi di connessione. Riprova tra un momento.", - "modelUnavailable": "Questo modello non è disponibile per Cloud Agent. Scegli un altro modello e riprova." + "modelUnavailable": "Questo modello non è disponibile per Cloud Agent. Scegli un altro modello e riprova.", + "cancelQueuedFailed": "Impossibile annullare il messaggio.", + "cancelQueuedRestoreAvailable": "Messaggio annullato. Tocca Ripristina per riportarlo nel campo di inserimento del messaggio.", + "cancelQueuedRestored": "Messaggio annullato. Il prompt è di nuovo nel campo di inserimento del messaggio.", + "cancelQueuedUpgradeRequired": "Per annullare i messaggi in coda è necessaria una versione più recente di Kilo CLI. Aggiorna Kilo CLI e riconnettiti." }, "remoteSession": { "failedToCreate": "Impossibile creare la sessione remota", @@ -2002,7 +2006,11 @@ "queuedAccessibility": "Messaggio in coda", "userMessage": "Messaggio utente", "assistantMessage": "Messaggio assistente", - "longPressHint": "Tieni premuto per i dettagli del messaggio" + "longPressHint": "Tieni premuto per i dettagli del messaggio", + "cancelQueued": "Annulla messaggio", + "cancelQueuedAccessibility": "Annulla messaggio in coda", + "restoreQueued": "Ripristina", + "restoreQueuedAccessibility": "Ripristina il messaggio annullato nel campo di inserimento del messaggio" }, "olderMessages": { "arrived": "Messaggi precedenti caricati", diff --git a/apps/mobile/src/i18n/locales/ja.json b/apps/mobile/src/i18n/locales/ja.json index dfffc8cea6..7dfb34821d 100644 --- a/apps/mobile/src/i18n/locales/ja.json +++ b/apps/mobile/src/i18n/locales/ja.json @@ -1688,7 +1688,11 @@ "previousTaskFinishing": "前のタスクがまだ終わっていません。少々お待ちください。", "serviceUnavailable": "サービスは現在利用できません。もう一度お試しください。", "connectionTrouble": "接続に問題があります。しばらくしてから再試行してください。", - "modelUnavailable": "このモデルはCloud Agentではご利用いただけません。別のモデルを選んで、もう一度お試しください。" + "modelUnavailable": "このモデルはCloud Agentではご利用いただけません。別のモデルを選んで、もう一度お試しください。", + "cancelQueuedFailed": "メッセージをキャンセルできませんでした。", + "cancelQueuedRestoreAvailable": "メッセージをキャンセルしました。「復元」をタップすると、メッセージ入力欄に戻せます。", + "cancelQueuedRestored": "メッセージをキャンセルしました。プロンプトがメッセージ入力欄に戻りました。", + "cancelQueuedUpgradeRequired": "送信待ちのメッセージをキャンセルするには、新しいKilo CLIが必要です。Kilo CLIを更新して再接続してください。" }, "remoteSession": { "failedToCreate": "リモートセッションを作成できませんでした", @@ -2009,7 +2013,11 @@ "queuedAccessibility": "メッセージがキューに追加されました", "userMessage": "ユーザーメッセージ", "assistantMessage": "アシスタントメッセージ", - "longPressHint": "メッセージの詳細は長押し" + "longPressHint": "メッセージの詳細は長押し", + "cancelQueued": "メッセージをキャンセル", + "cancelQueuedAccessibility": "送信待ちのメッセージをキャンセル", + "restoreQueued": "復元", + "restoreQueuedAccessibility": "キャンセルしたメッセージをメッセージ入力欄に復元" }, "olderMessages": { "arrived": "以前のメッセージを読み込みました", diff --git a/apps/mobile/src/i18n/locales/ka.json b/apps/mobile/src/i18n/locales/ka.json index 3e1d764c57..20552121d3 100644 --- a/apps/mobile/src/i18n/locales/ka.json +++ b/apps/mobile/src/i18n/locales/ka.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "წინა დავალება ჯერ კიდევ სრულდება. გთხოვთ, ცოტა ხანს დაელოდოთ.", "serviceUnavailable": "სერვისი ამ დროისთვის მიუწვდომელია. გთხოვთ, სცადოთ თავიდან.", "connectionTrouble": "კავშირის პრობლემა. გთხოვთ, ცოტა ხანში სცადოთ თავიდან.", - "modelUnavailable": "ეს მოდელი Cloud Agent-ისთვის მიუწვდომელია. აირჩიეთ სხვა მოდელი და სცადეთ თავიდან." + "modelUnavailable": "ეს მოდელი Cloud Agent-ისთვის მიუწვდომელია. აირჩიეთ სხვა მოდელი და სცადეთ თავიდან.", + "cancelQueuedFailed": "შეტყობინების გაუქმება ვერ მოხერხდა.", + "cancelQueuedRestoreAvailable": "შეტყობინება გაუქმდა. შეეხეთ ღილაკს „აღდგენა“, რომ დააბრუნოთ იგი შეტყობინების შეყვანის ველში.", + "cancelQueuedRestored": "შეტყობინება გაუქმდა. მოთხოვნა დაბრუნდა შეტყობინების შეყვანის ველში.", + "cancelQueuedUpgradeRequired": "რიგში მდგომი შეტყობინებების გასაუქმებლად საჭიროა Kilo CLI-ის უფრო ახალი ვერსია. განაახლეთ Kilo CLI და ხელახლა დაუკავშირდით." }, "remoteSession": { "failedToCreate": "დისტანციური სესიის შექმნა ვერ მოხერხდა", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "შეტყობინება რიგშია", "userMessage": "მომხმარებლის შეტყობინება", "assistantMessage": "ასისტენტის შეტყობინება", - "longPressHint": "ხანგრძლივი დაჭერა შეტყობინების დეტალებისთვის" + "longPressHint": "ხანგრძლივი დაჭერა შეტყობინების დეტალებისთვის", + "cancelQueued": "შეტყობინების გაუქმება", + "cancelQueuedAccessibility": "რიგში მდგომი შეტყობინების გაუქმება", + "restoreQueued": "აღდგენა", + "restoreQueuedAccessibility": "გაუქმებული შეტყობინების აღდგენა შეტყობინების შეყვანის ველში" }, "olderMessages": { "arrived": "ადრინდელი შეტყობინებები ჩაიტვირთა", diff --git a/apps/mobile/src/i18n/locales/kk.json b/apps/mobile/src/i18n/locales/kk.json index c2ddfff9dc..3add5e4988 100644 --- a/apps/mobile/src/i18n/locales/kk.json +++ b/apps/mobile/src/i18n/locales/kk.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Алдыңғы тапсырма әлі аяқталуда. Сәл күте тұрыңыз.", "serviceUnavailable": "Қызмет қазір қолжетімсіз. Қайталап көріңіз.", "connectionTrouble": "Байланыс ақауы. Сәлден кейін қайта көріңіз.", - "modelUnavailable": "Бұл модель Cloud Agent үшін қолжетімсіз. Басқа модельді таңдап, қайталап көріңіз." + "modelUnavailable": "Бұл модель Cloud Agent үшін қолжетімсіз. Басқа модельді таңдап, қайталап көріңіз.", + "cancelQueuedFailed": "Хабарламадан бас тарту мүмкін болмады.", + "cancelQueuedRestoreAvailable": "Хабарламадан бас тартылды. Оны хабарлама енгізу өрісіне қайтару үшін «Қалпына келтіру» түймесін түртіңіз.", + "cancelQueuedRestored": "Хабарламадан бас тартылды. Сұрақ хабарлама енгізу өрісіне қайтарылды.", + "cancelQueuedUpgradeRequired": "Кезектегі хабарламалардан бас тарту Kilo CLI-дің жаңа нұсқасын қажет етеді. Kilo CLI жаңартып, қайта қосылыңыз." }, "remoteSession": { "failedToCreate": "Қашықтағы сессияны құру сәтсіз аяқталды", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Хабарлама кезекте", "userMessage": "Пайдаланушы хабарламасы", "assistantMessage": "Көмекші хабарламасы", - "longPressHint": "Хабарлама мәліметтері үшін ұзақ басыңыз" + "longPressHint": "Хабарлама мәліметтері үшін ұзақ басыңыз", + "cancelQueued": "Хабарламадан бас тарту", + "cancelQueuedAccessibility": "Кезектегі хабарламадан бас тарту", + "restoreQueued": "Қалпына келтіру", + "restoreQueuedAccessibility": "Бас тартылған хабарламаны хабарлама енгізу өрісіне қалпына келтіру" }, "olderMessages": { "arrived": "Алдыңғы хабарламалар жүктелді", diff --git a/apps/mobile/src/i18n/locales/km.json b/apps/mobile/src/i18n/locales/km.json index 3f25c6a2b5..62afc5a90f 100644 --- a/apps/mobile/src/i18n/locales/km.json +++ b/apps/mobile/src/i18n/locales/km.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "កិច្ចការមុនកំពុងបញ្ចប់នៅឡើយ។ សូមរង់ចាំបន្តិច។", "serviceUnavailable": "សេវាកម្មមិនអាចប្រើបាននៅពេលនេះទេ។ សូមព្យាយាមម្តងទៀត។", "connectionTrouble": "បញ្ហាការតភ្ជាប់។ សូមព្យាយាមម្តងទៀតក្នុងពេលបន្តិចទៀត។", - "modelUnavailable": "ម៉ូដែលនេះមិនអាចប្រើជាមួយ Cloud Agent បានទេ។ សូមជ្រើសរើសម៉ូដែលផ្សេង ហើយព្យាយាមម្តងទៀត។" + "modelUnavailable": "ម៉ូដែលនេះមិនអាចប្រើជាមួយ Cloud Agent បានទេ។ សូមជ្រើសរើសម៉ូដែលផ្សេង ហើយព្យាយាមម្តងទៀត។", + "cancelQueuedFailed": "មិនអាចលុបចោលសារបានទេ។", + "cancelQueuedRestoreAvailable": "បានលុបចោលសារ។ ប៉ះ ស្តារ ដើម្បីនាំសារត្រឡប់ទៅអ្នកតែង។", + "cancelQueuedRestored": "បានលុបចោលសារ។ ការជំរុញបានត្រឡប់មកអ្នកតែងវិញហើយ។", + "cancelQueuedUpgradeRequired": "ការលុបចោលសារដែលបានដាក់ក្នុងជួរត្រូវការ Kilo CLI កំណែថ្មីជាងនេះ។ ធ្វើបច្ចុប្បន្នភាព Kilo CLI ហើយភ្ជាប់ឡើងវិញ។" }, "remoteSession": { "failedToCreate": "បរាជ័យក្នុងការបង្កើតវគ្គពីចម្ងាយ", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "សារបានដាក់ក្នុងជួរ", "userMessage": "សារអ្នកប្រើ", "assistantMessage": "សារជំនួយការ", - "longPressHint": "ចុចឱ្យជាប់សម្រាប់ព័ត៌មានលម្អិតនៃសារ" + "longPressHint": "ចុចឱ្យជាប់សម្រាប់ព័ត៌មានលម្អិតនៃសារ", + "cancelQueued": "លុបចោលសារ", + "cancelQueuedAccessibility": "លុបចោលសារដែលបានដាក់ក្នុងជួរ", + "restoreQueued": "ស្តារ", + "restoreQueuedAccessibility": "ស្តារសារដែលបានលុបចោលទៅអ្នកតែង" }, "olderMessages": { "arrived": "បានផ្ទុកសារមុនៗ", diff --git a/apps/mobile/src/i18n/locales/kn.json b/apps/mobile/src/i18n/locales/kn.json index f131e0811a..0912bb6d21 100644 --- a/apps/mobile/src/i18n/locales/kn.json +++ b/apps/mobile/src/i18n/locales/kn.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "ಹಿಂದಿನ ಕಾರ್ಯ ಇನ್ನೂ ಮುಗಿಯುತ್ತಿದೆ. ದಯವಿಟ್ಟು ಸ್ವಲ್ಪ ಕಾಯಿರಿ.", "serviceUnavailable": "ಸೇವೆ ಈಗ ಲಭ್ಯವಿಲ್ಲ. ದಯವಿಟ್ಟು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ.", "connectionTrouble": "ಸಂಪರ್ಕ ಸಮಸ್ಯೆ. ದಯವಿಟ್ಟು ಸ್ವಲ್ಪ ಸಮಯದ ನಂತರ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ.", - "modelUnavailable": "ಈ ಮಾದರಿ Cloud Agent ಗೆ ಲಭ್ಯವಿಲ್ಲ. ಬೇರೆ ಮಾದರಿ ಆಯ್ಕೆಮಾಡಿ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ." + "modelUnavailable": "ಈ ಮಾದರಿ Cloud Agent ಗೆ ಲಭ್ಯವಿಲ್ಲ. ಬೇರೆ ಮಾದರಿ ಆಯ್ಕೆಮಾಡಿ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ.", + "cancelQueuedFailed": "ಸಂದೇಶವನ್ನು ರದ್ದುಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ.", + "cancelQueuedRestoreAvailable": "ಸಂದೇಶವನ್ನು ರದ್ದುಮಾಡಲಾಗಿದೆ. ಅದನ್ನು ಸಂಯೋಜಕಕ್ಕೆ ಹಿಂತಿರುಗಿಸಲು ಮರುಸ್ಥಾಪಿಸಿ ಟ್ಯಾಪ್ ಮಾಡಿ.", + "cancelQueuedRestored": "ಸಂದೇಶವನ್ನು ರದ್ದುಮಾಡಲಾಗಿದೆ. ಪ್ರಾಂಪ್ಟ್ ಮತ್ತೆ ಸಂಯೋಜಕದಲ್ಲಿದೆ.", + "cancelQueuedUpgradeRequired": "ಸರದಿಯಲ್ಲಿರುವ ಸಂದೇಶಗಳನ್ನು ರದ್ದುಮಾಡಲು ಹೊಸ ಆವೃತ್ತಿಯ Kilo CLI ಅಗತ್ಯವಿದೆ. Kilo CLI ಅನ್ನು ನವೀಕರಿಸಿ ಮತ್ತು ಮತ್ತೆ ಸಂಪರ್ಕಿಸಿ." }, "remoteSession": { "failedToCreate": "ರಿಮೋಟ್ ಸೆಶನ್ ರಚಿಸಲು ವಿಫಲವಾಗಿದೆ", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "ಸಂದೇಶ ಕ್ಯೂ ಮಾಡಲಾಗಿದೆ", "userMessage": "ಬಳಕೆದಾರ ಸಂದೇಶ", "assistantMessage": "ಸಹಾಯಕ ಸಂದೇಶ", - "longPressHint": "ಸಂದೇಶ ವಿವರಗಳಿಗಾಗಿ ದೀರ್ಘ ಒತ್ತಿ" + "longPressHint": "ಸಂದೇಶ ವಿವರಗಳಿಗಾಗಿ ದೀರ್ಘ ಒತ್ತಿ", + "cancelQueued": "ಸಂದೇಶವನ್ನು ರದ್ದುಮಾಡಿ", + "cancelQueuedAccessibility": "ಸರದಿಯಲ್ಲಿರುವ ಸಂದೇಶವನ್ನು ರದ್ದುಮಾಡಿ", + "restoreQueued": "ಮರುಸ್ಥಾಪಿಸಿ", + "restoreQueuedAccessibility": "ರದ್ದುಮಾಡಿದ ಸಂದೇಶವನ್ನು ಸಂಯೋಜಕಕ್ಕೆ ಮರುಸ್ಥಾಪಿಸಿ" }, "olderMessages": { "arrived": "ಹಿಂದಿನ ಸಂದೇಶಗಳನ್ನು ಲೋಡ್ ಮಾಡಲಾಗಿದೆ", diff --git a/apps/mobile/src/i18n/locales/ko.json b/apps/mobile/src/i18n/locales/ko.json index 67c33d2049..7d15ec1b5b 100644 --- a/apps/mobile/src/i18n/locales/ko.json +++ b/apps/mobile/src/i18n/locales/ko.json @@ -1688,7 +1688,11 @@ "previousTaskFinishing": "이전 작업이 아직 끝나지 않았습니다. 잠시 기다려 주세요.", "serviceUnavailable": "지금 서비스를 사용할 수 없습니다. 다시 시도하세요.", "connectionTrouble": "연결 문제가 발생했습니다. 잠시 후 다시 시도하세요.", - "modelUnavailable": "이 모델은 Cloud Agent에서 사용할 수 없습니다. 다른 모델을 선택하고 다시 시도하세요." + "modelUnavailable": "이 모델은 Cloud Agent에서 사용할 수 없습니다. 다른 모델을 선택하고 다시 시도하세요.", + "cancelQueuedFailed": "메시지를 취소하지 못했습니다.", + "cancelQueuedRestoreAvailable": "메시지가 취소되었습니다. 복원을 탭하여 메시지 입력란으로 되돌리세요.", + "cancelQueuedRestored": "메시지가 취소되었습니다. 프롬프트가 메시지 입력란으로 돌아왔습니다.", + "cancelQueuedUpgradeRequired": "대기 중인 메시지를 취소하려면 최신 Kilo CLI가 필요합니다. Kilo CLI를 업데이트하고 다시 연결하세요." }, "remoteSession": { "failedToCreate": "원격 세션을 만들지 못했습니다", @@ -2009,7 +2013,11 @@ "queuedAccessibility": "메시지 대기 중", "userMessage": "사용자 메시지", "assistantMessage": "어시스턴트 메시지", - "longPressHint": "메시지 세부 정보를 보려면 길게 누르세요" + "longPressHint": "메시지 세부 정보를 보려면 길게 누르세요", + "cancelQueued": "메시지 취소", + "cancelQueuedAccessibility": "대기 중인 메시지 취소", + "restoreQueued": "복원", + "restoreQueuedAccessibility": "취소된 메시지를 메시지 입력란으로 복원" }, "olderMessages": { "arrived": "이전 메시지 로드됨", diff --git a/apps/mobile/src/i18n/locales/lo.json b/apps/mobile/src/i18n/locales/lo.json index ee52285cf7..3aba9f337b 100644 --- a/apps/mobile/src/i18n/locales/lo.json +++ b/apps/mobile/src/i18n/locales/lo.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "ວຽກກ່ອນໜ້າຍັງບໍ່ທັນສຳເລັດ. ກະລຸນາລໍຖ້າເລັກນ້ອຍ.", "serviceUnavailable": "ບໍລິການບໍ່ສາມາດໃຊ້ໄດ້ໃນຕອນນີ້. ກະລຸນາລອງໃໝ່.", "connectionTrouble": "ມີບັນຫາການເຊື່ອມຕໍ່. ກະລຸນາລອງໃໝ່ໃນອີກເລັກນ້ອຍ.", - "modelUnavailable": "ຮູບແບບນີ້ບໍ່ສາມາດໃຊ້ກັບ Cloud Agent ໄດ້. ກະລຸນາເລືອກຮູບແບບອື່ນ ແລ້ວລອງໃໝ່." + "modelUnavailable": "ຮູບແບບນີ້ບໍ່ສາມາດໃຊ້ກັບ Cloud Agent ໄດ້. ກະລຸນາເລືອກຮູບແບບອື່ນ ແລ້ວລອງໃໝ່.", + "cancelQueuedFailed": "ບໍ່ສາມາດຍົກເລີກຂໍ້ຄວາມໄດ້.", + "cancelQueuedRestoreAvailable": "ຍົກເລີກຂໍ້ຄວາມແລ້ວ. ແຕະ ກູ້ຄືນ ເພື່ອນຳມັນກັບໄປຍັງຊ່ອງປ້ອນຂໍ້ຄວາມ.", + "cancelQueuedRestored": "ຍົກເລີກຂໍ້ຄວາມແລ້ວ. ຂໍ້ຄວາມກັບຄືນມາຢູ່ໃນຊ່ອງປ້ອນຂໍ້ຄວາມແລ້ວ.", + "cancelQueuedUpgradeRequired": "ການຍົກເລີກຂໍ້ຄວາມທີ່ລໍຖ້າສົ່ງຕ້ອງໃຊ້ Kilo CLI ລຸ້ນໃໝ່ກວ່າ. ອັບເດດ Kilo CLI ແລ້ວເຊື່ອມຕໍ່ໃໝ່." }, "remoteSession": { "failedToCreate": "ບໍ່ສາມາດສ້າງເຊດຊັນທາງໄກ", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "ຂໍ້ຄວາມເຂົ້າຄິວ", "userMessage": "ຂໍ້ຄວາມຜູ້ໃຊ້", "assistantMessage": "ຂໍ້ຄວາມຜູ້ຊ່ວຍ", - "longPressHint": "ກົດຄ້າງເພື່ອເບິ່ງລາຍລະອຽດຂໍ້ຄວາມ" + "longPressHint": "ກົດຄ້າງເພື່ອເບິ່ງລາຍລະອຽດຂໍ້ຄວາມ", + "cancelQueued": "ຍົກເລີກຂໍ້ຄວາມ", + "cancelQueuedAccessibility": "ຍົກເລີກຂໍ້ຄວາມທີ່ລໍຖ້າສົ່ງ", + "restoreQueued": "ກູ້ຄືນ", + "restoreQueuedAccessibility": "ກູ້ຄືນຂໍ້ຄວາມທີ່ຍົກເລີກໄປຍັງຊ່ອງປ້ອນຂໍ້ຄວາມ" }, "olderMessages": { "arrived": "ຂໍ້ຄວາມກ່ອນໜ້າໂຫລດແລ້ວ", diff --git a/apps/mobile/src/i18n/locales/lt.json b/apps/mobile/src/i18n/locales/lt.json index 468c7f18f2..57fb4191b6 100644 --- a/apps/mobile/src/i18n/locales/lt.json +++ b/apps/mobile/src/i18n/locales/lt.json @@ -2172,7 +2172,11 @@ "previousTaskFinishing": "Ankstesnė užduotis dar baigiama. Palaukite akimirką.", "serviceUnavailable": "Paslauga šiuo metu nepasiekiama. Bandykite dar kartą.", "connectionTrouble": "Ryšio problema. Bandykite dar kartą po akimirkos.", - "modelUnavailable": "Šis modelis nėra pasiekiamas Cloud Agent. Pasirinkite kitą modelį ir bandykite dar kartą." + "modelUnavailable": "Šis modelis nėra pasiekiamas Cloud Agent. Pasirinkite kitą modelį ir bandykite dar kartą.", + "cancelQueuedFailed": "Nepavyko atšaukti žinutės.", + "cancelQueuedRestoreAvailable": "Žinutė atšaukta. Bakstelėkite „Atkurti“, kad grąžintumėte ją į žinutės įvesties lauką.", + "cancelQueuedRestored": "Žinutė atšaukta. Užklausa grąžinta į žinutės įvesties lauką.", + "cancelQueuedUpgradeRequired": "Norint atšaukti eilėje esančias žinutes, reikia naujesnės Kilo CLI versijos. Atnaujinkite Kilo CLI ir prisijunkite iš naujo." }, "remoteSession": { "failedToCreate": "Nepavyko sukurti nuotolinės sesijos", @@ -2495,7 +2499,11 @@ "queuedAccessibility": "Žinutė įdėta į eilę", "userMessage": "Vartotojo žinutė", "assistantMessage": "Asistento žinutė", - "longPressHint": "Paspauskite ir palaikykite, kad pamatytumėte žinutės detales" + "longPressHint": "Paspauskite ir palaikykite, kad pamatytumėte žinutės detales", + "cancelQueued": "Atšaukti žinutę", + "cancelQueuedAccessibility": "Atšaukti eilėje esančią žinutę", + "restoreQueued": "Atkurti", + "restoreQueuedAccessibility": "Atkurti atšauktą žinutę žinutės įvesties lauke" }, "olderMessages": { "arrived": "Ankstesnės žinutės įkeltos", diff --git a/apps/mobile/src/i18n/locales/lv.json b/apps/mobile/src/i18n/locales/lv.json index 8774baf665..44ca097a3b 100644 --- a/apps/mobile/src/i18n/locales/lv.json +++ b/apps/mobile/src/i18n/locales/lv.json @@ -2157,7 +2157,11 @@ "previousTaskFinishing": "Iepriekšējais uzdevums joprojām tiek pabeigts. Lūdzu, uzgaidi mirkli.", "serviceUnavailable": "Pakalpojums šobrīd nav pieejams. Lūdzu, mēģini vēlreiz.", "connectionTrouble": "Savienojuma problēma. Lūdzu, mēģini vēlreiz pēc mirkļa.", - "modelUnavailable": "Šis modelis nav pieejams Cloud Agent. Izvēlieties citu modeli un mēģiniet vēlreiz." + "modelUnavailable": "Šis modelis nav pieejams Cloud Agent. Izvēlieties citu modeli un mēģiniet vēlreiz.", + "cancelQueuedFailed": "Neizdevās atcelt ziņojumu.", + "cancelQueuedRestoreAvailable": "Ziņojums atcelts. Pieskarieties “Atjaunot”, lai to atgrieztu ziņojuma ievades laukā.", + "cancelQueuedRestored": "Ziņojums atcelts. Jautājums atkal ir ziņojuma ievades laukā.", + "cancelQueuedUpgradeRequired": "Lai atceltu rindā esošus ziņojumus, nepieciešama jaunāka Kilo CLI versija. Atjauniniet Kilo CLI un savienojieties vēlreiz." }, "remoteSession": { "failedToCreate": "Neizdevās izveidot attālo sesiju", @@ -2479,7 +2483,11 @@ "queuedAccessibility": "Ziņojums rindā", "userMessage": "Lietotāja ziņojums", "assistantMessage": "Asistenta ziņojums", - "longPressHint": "Turiet piespiestu ziņojuma detaļām" + "longPressHint": "Turiet piespiestu ziņojuma detaļām", + "cancelQueued": "Atcelt ziņojumu", + "cancelQueuedAccessibility": "Atcelt rindā esošu ziņojumu", + "restoreQueued": "Atjaunot", + "restoreQueuedAccessibility": "Atjaunot atcelto ziņojumu ziņojuma ievades laukā" }, "olderMessages": { "arrived": "Ielādēti agrāki ziņojumi", diff --git a/apps/mobile/src/i18n/locales/mg.json b/apps/mobile/src/i18n/locales/mg.json index 606b1ce1c5..faf95e45f1 100644 --- a/apps/mobile/src/i18n/locales/mg.json +++ b/apps/mobile/src/i18n/locales/mg.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Ny asa teo aloha mbola vita. Andraso kely azafady.", "serviceUnavailable": "Tsy misy ny serivisy amin'izao fotoana izao. Andramo indray azafady.", "connectionTrouble": "Olana amin'ny fifandraisana. Andramo indray rehefa afaka kely.", - "modelUnavailable": "Tsy misy io modely io ho an'ny Cloud Agent. Misafidìa modely hafa ary andramo indray." + "modelUnavailable": "Tsy misy io modely io ho an'ny Cloud Agent. Misafidìa modely hafa ary andramo indray.", + "cancelQueuedFailed": "Tsy afaka nanafoana ny hafatra.", + "cancelQueuedRestoreAvailable": "Nofoanana ny hafatra. Tsindrio ny Avereno mba hamerenana azy ao amin'ny fampidirana hafatra.", + "cancelQueuedRestored": "Nofoanana ny hafatra. Niverina ao amin'ny fampidirana hafatra ny hafatrao.", + "cancelQueuedUpgradeRequired": "Mitaky Kilo CLI vaovao kokoa ny fanafoanana hafatra miandry. Havaozy ny Kilo CLI ary ampifandraiso indray." }, "remoteSession": { "failedToCreate": "Tsy nahomby namorona session lavitra", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Eo amin'ny filaharana ny hafatra", "userMessage": "Hafatra mpampiasa", "assistantMessage": "Hafatra mpanampy", - "longPressHint": "Tsindrio maharitra raha hita ny antsipirian'ny hafatra" + "longPressHint": "Tsindrio maharitra raha hita ny antsipirian'ny hafatra", + "cancelQueued": "Foano ny hafatra", + "cancelQueuedAccessibility": "Foano ny hafatra miandry", + "restoreQueued": "Avereno", + "restoreQueuedAccessibility": "Avereno ao amin'ny fampidirana hafatra ny hafatra nofoanana" }, "olderMessages": { "arrived": "Voaray ny hafatra taloha", diff --git a/apps/mobile/src/i18n/locales/mi.json b/apps/mobile/src/i18n/locales/mi.json index d8fdbe1b11..b2013164c8 100644 --- a/apps/mobile/src/i18n/locales/mi.json +++ b/apps/mobile/src/i18n/locales/mi.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Kei te oti tonu te mahi o mua. Tatari kia iti.", "serviceUnavailable": "Kāore te ratonga e wātea ana ināianei. Whakamātau anō.", "connectionTrouble": "He raruraru hononga. Whakamātau anō i muri i tētahi wā poto.", - "modelUnavailable": "Kāore tēnei tauira e wātea ana mō Cloud Agent. Kōwhiria tētahi atu tauira, whakamātau anō." + "modelUnavailable": "Kāore tēnei tauira e wātea ana mō Cloud Agent. Kōwhiria tētahi atu tauira, whakamātau anō.", + "cancelQueuedFailed": "Kāore i taea te whakakore i te karere.", + "cancelQueuedRestoreAvailable": "Kua whakakorea te karere. Pāwhiri ki Whakaora kia whakahokia ki te kaitito.", + "cancelQueuedRestored": "Kua whakakorea te karere. Kua hoki te pātai ki te kaitito.", + "cancelQueuedUpgradeRequired": "E hiahiatia ana he Kilo CLI hōu ake hei whakakore i ngā karere kua rārangi. Whakamohoatia te Kilo CLI ka hono anō." }, "remoteSession": { "failedToCreate": "I rahua te hanga wātūtū mamao", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Kua rārangi te karere", "userMessage": "Karere kaiwhakamahi", "assistantMessage": "Karere kaiāwhina", - "longPressHint": "Pēhi roa mō ngā taipitopito karere" + "longPressHint": "Pēhi roa mō ngā taipitopito karere", + "cancelQueued": "Whakakore i te karere", + "cancelQueuedAccessibility": "Whakakore i te karere kua rārangi", + "restoreQueued": "Whakaora", + "restoreQueuedAccessibility": "Whakaora i te karere kua whakakorea ki te kaitito" }, "olderMessages": { "arrived": "Kua uta ngā karere o mua", diff --git a/apps/mobile/src/i18n/locales/mk.json b/apps/mobile/src/i18n/locales/mk.json index be5d335fad..78cf0695d1 100644 --- a/apps/mobile/src/i18n/locales/mk.json +++ b/apps/mobile/src/i18n/locales/mk.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Претходната задача сè уште се завршува. Почекај момент.", "serviceUnavailable": "Услугата во моментов не е достапна. Обиди се повторно.", "connectionTrouble": "Проблем со врската. Обиди се повторно по момент.", - "modelUnavailable": "Овој модел не е достапен за Cloud Agent. Избери друг модел и обиди се повторно." + "modelUnavailable": "Овој модел не е достапен за Cloud Agent. Избери друг модел и обиди се повторно.", + "cancelQueuedFailed": "Не можеше да се откаже пораката.", + "cancelQueuedRestoreAvailable": "Пораката е откажана. Допрете „Вратете“ за да ја вратите во полето за внес на пораки.", + "cancelQueuedRestored": "Пораката е откажана. Предлогот е повторно во полето за внес на пораки.", + "cancelQueuedUpgradeRequired": "Откажувањето пораки во чекање бара понов Kilo CLI. Ажурирајте го Kilo CLI и поврзете се повторно." }, "remoteSession": { "failedToCreate": "Не успеа да се создаде далечинска сесија", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Пораката е во ред", "userMessage": "Корисничка порака", "assistantMessage": "Порака од асистентот", - "longPressHint": "Долго притиснете за детали за пораката" + "longPressHint": "Долго притиснете за детали за пораката", + "cancelQueued": "Откажете ја пораката", + "cancelQueuedAccessibility": "Откажете ја пораката во чекање", + "restoreQueued": "Вратете", + "restoreQueuedAccessibility": "Вратете ја откажаната порака во полето за внес на пораки" }, "olderMessages": { "arrived": "Пораните пораки се вчитани", diff --git a/apps/mobile/src/i18n/locales/ml.json b/apps/mobile/src/i18n/locales/ml.json index a54608ddc6..9befacde53 100644 --- a/apps/mobile/src/i18n/locales/ml.json +++ b/apps/mobile/src/i18n/locales/ml.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "മുൻ ജോലി ഇപ്പോഴും പൂർത്തിയാകുന്നു. ഒരു നിമിഷം കാത്തിരിക്കുക.", "serviceUnavailable": "സേവനം ഇപ്പോൾ ലഭ്യമല്ല. വീണ്ടും ശ്രമിക്കുക.", "connectionTrouble": "കണക്ഷൻ പ്രശ്നം. ഒരു നിമിഷത്തിന് ശേഷം വീണ്ടും ശ്രമിക്കുക.", - "modelUnavailable": "ഈ മോഡൽ Cloud Agent-ന് ലഭ്യമല്ല. മറ്റൊരു മോഡൽ തിരഞ്ഞെടുത്ത് വീണ്ടും ശ്രമിക്കുക." + "modelUnavailable": "ഈ മോഡൽ Cloud Agent-ന് ലഭ്യമല്ല. മറ്റൊരു മോഡൽ തിരഞ്ഞെടുത്ത് വീണ്ടും ശ്രമിക്കുക.", + "cancelQueuedFailed": "സന്ദേശം റദ്ദാക്കാൻ കഴിഞ്ഞില്ല.", + "cancelQueuedRestoreAvailable": "സന്ദേശം റദ്ദാക്കി. സന്ദേശ ഇൻപുട്ടിലേക്ക് തിരികെ കൊണ്ടുവരാൻ പുനഃസ്ഥാപിക്കുക എന്നതിൽ ടാപ്പ് ചെയ്യുക.", + "cancelQueuedRestored": "സന്ദേശം റദ്ദാക്കി. പ്രോംപ്റ്റ് സന്ദേശ ഇൻപുട്ടിൽ തിരിച്ചെത്തി.", + "cancelQueuedUpgradeRequired": "ക്യൂവിലുള്ള സന്ദേശങ്ങൾ റദ്ദാക്കാൻ Kilo CLI-യുടെ പുതിയ പതിപ്പ് ആവശ്യമാണ്. Kilo CLI അപ്ഡേറ്റ് ചെയ്ത് വീണ്ടും കണക്റ്റ് ചെയ്യുക." }, "remoteSession": { "failedToCreate": "വിദൂര സെഷൻ സൃഷ്ടിക്കാൻ പരാജയപ്പെട്ടു", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "സന്ദേശം ക്യൂവിൽ", "userMessage": "ഉപയോക്തൃ സന്ദേശം", "assistantMessage": "സഹായി സന്ദേശം", - "longPressHint": "സന്ദേശ വിശദാംശങ്ങൾക്കായി ദീർഘനേരം അമർത്തുക" + "longPressHint": "സന്ദേശ വിശദാംശങ്ങൾക്കായി ദീർഘനേരം അമർത്തുക", + "cancelQueued": "സന്ദേശം റദ്ദാക്കുക", + "cancelQueuedAccessibility": "ക്യൂവിലുള്ള സന്ദേശം റദ്ദാക്കുക", + "restoreQueued": "പുനഃസ്ഥാപിക്കുക", + "restoreQueuedAccessibility": "റദ്ദാക്കിയ സന്ദേശം സന്ദേശ ഇൻപുട്ടിൽ പുനഃസ്ഥാപിക്കുക" }, "olderMessages": { "arrived": "മുൻപത്തെ സന്ദേശങ്ങൾ ലോഡ് ചെയ്തു", diff --git a/apps/mobile/src/i18n/locales/mn.json b/apps/mobile/src/i18n/locales/mn.json index 6c4b2c778d..9df29ad6f0 100644 --- a/apps/mobile/src/i18n/locales/mn.json +++ b/apps/mobile/src/i18n/locales/mn.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Өмнөх ажил дуусаагүй байна. Түр хүлээнэ үү.", "serviceUnavailable": "Үйлчилгээ яг одоо боломжгүй байна. Дахин оролдоно уу.", "connectionTrouble": "Холболтын асуудал. Түр хүлээгээд дахин оролдоно уу.", - "modelUnavailable": "Энэ загвар Cloud Agent-д байхгүй. Өөр загвар сонгоод дахин оролдоно уу." + "modelUnavailable": "Энэ загвар Cloud Agent-д байхгүй. Өөр загвар сонгоод дахин оролдоно уу.", + "cancelQueuedFailed": "Мессежийг цуцалж чадсангүй.", + "cancelQueuedRestoreAvailable": "Мессеж цуцлагдсан. Мессеж оруулах талбарт буцаахын тулд «Сэргээх» товчийг товшино уу.", + "cancelQueuedRestored": "Мессеж цуцлагдсан. Заавар мессеж оруулах талбарт буцаж орсон.", + "cancelQueuedUpgradeRequired": "Дараалалд буй мессежийг цуцлахад Kilo CLI-ийн шинэ хувилбар шаардлагатай. Kilo CLI-ээ шинэчлээд дахин холбоно уу." }, "remoteSession": { "failedToCreate": "Алсын сесс үүсгэхэд амжилтгүй болсон", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Мэдээлэл дараалалд орлоо", "userMessage": "Хэрэглэгчийн мэдээлэл", "assistantMessage": "Туслахын мэдээлэл", - "longPressHint": "Мэдээллийн дэлгэрэнгүйг үзэхийн тулд удаан дарна уу" + "longPressHint": "Мэдээллийн дэлгэрэнгүйг үзэхийн тулд удаан дарна уу", + "cancelQueued": "Мессежийг цуцлах", + "cancelQueuedAccessibility": "Дараалалд буй мессежийг цуцлах", + "restoreQueued": "Сэргээх", + "restoreQueuedAccessibility": "Цуцалсан мессежийг мессеж оруулах талбарт сэргээх" }, "olderMessages": { "arrived": "Өмнөх мэдээллүүд ачаалагдлаа", diff --git a/apps/mobile/src/i18n/locales/mr.json b/apps/mobile/src/i18n/locales/mr.json index 4017f6aee6..4dc393462a 100644 --- a/apps/mobile/src/i18n/locales/mr.json +++ b/apps/mobile/src/i18n/locales/mr.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "मागील कार्य अजून पूर्ण होत आहे. कृपया क्षणभर थांबा.", "serviceUnavailable": "सेवा सध्या अनुपलब्ध आहे. पुन्हा प्रयत्न करा.", "connectionTrouble": "कनेक्शन समस्या. कृपया क्षणभरानंतर पुन्हा प्रयत्न करा.", - "modelUnavailable": "हे मॉडेल Cloud Agent साठी उपलब्ध नाही. दुसरे मॉडेल निवडून पुन्हा प्रयत्न करा." + "modelUnavailable": "हे मॉडेल Cloud Agent साठी उपलब्ध नाही. दुसरे मॉडेल निवडून पुन्हा प्रयत्न करा.", + "cancelQueuedFailed": "संदेश रद्द करता आला नाही.", + "cancelQueuedRestoreAvailable": "संदेश रद्द केला. तो कंपोझरमध्ये परत आणण्यासाठी पुनर्संचयित करा वर टॅप करा.", + "cancelQueuedRestored": "संदेश रद्द केला. प्रॉम्प्ट पुन्हा कंपोझरमध्ये आला आहे.", + "cancelQueuedUpgradeRequired": "रांगेतील संदेश रद्द करण्यासाठी Kilo CLI ची नवीन आवृत्ती आवश्यक आहे. Kilo CLI अद्ययावत करा आणि पुन्हा कनेक्ट करा." }, "remoteSession": { "failedToCreate": "दूरस्थ सत्र तयार करण्यात अयशस्वी", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "संदेश रांगेत आहे", "userMessage": "वापरकर्ता संदेश", "assistantMessage": "सहाय्यक संदेश", - "longPressHint": "संदेश तपशीलांसाठी लांब दाबा" + "longPressHint": "संदेश तपशीलांसाठी लांब दाबा", + "cancelQueued": "संदेश रद्द करा", + "cancelQueuedAccessibility": "रांगेतील संदेश रद्द करा", + "restoreQueued": "पुनर्संचयित करा", + "restoreQueuedAccessibility": "रद्द केलेला संदेश कंपोझरमध्ये पुनर्संचयित करा" }, "olderMessages": { "arrived": "आधीचे संदेश लोड झाले", diff --git a/apps/mobile/src/i18n/locales/ms.json b/apps/mobile/src/i18n/locales/ms.json index b16e8625f6..66bf72446d 100644 --- a/apps/mobile/src/i18n/locales/ms.json +++ b/apps/mobile/src/i18n/locales/ms.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Tugas sebelumnya masih selesai. Sila tunggu sebentar.", "serviceUnavailable": "Perkhidmatan tidak tersedia buat masa ini. Sila cuba semula.", "connectionTrouble": "Masalah sambungan. Sila cuba semula sebentar lagi.", - "modelUnavailable": "Model ini tidak tersedia untuk Cloud Agent. Pilih model lain dan cuba semula." + "modelUnavailable": "Model ini tidak tersedia untuk Cloud Agent. Pilih model lain dan cuba semula.", + "cancelQueuedFailed": "Tidak dapat membatalkan mesej.", + "cancelQueuedRestoreAvailable": "Mesej dibatalkan. Ketik Pulihkan untuk mengembalikannya ke komposer.", + "cancelQueuedRestored": "Mesej dibatalkan. Gesaan kembali dalam komposer.", + "cancelQueuedUpgradeRequired": "Pembatalan mesej dalam baris gilir memerlukan Kilo CLI yang lebih baharu. Kemas kini Kilo CLI dan sambung semula." }, "remoteSession": { "failedToCreate": "Gagal mencipta sesi jauh", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Mesej dalam baris gilir", "userMessage": "Mesej pengguna", "assistantMessage": "Mesej pembantu", - "longPressHint": "Tekan lama untuk butiran mesej" + "longPressHint": "Tekan lama untuk butiran mesej", + "cancelQueued": "Batalkan mesej", + "cancelQueuedAccessibility": "Batalkan mesej dalam baris gilir", + "restoreQueued": "Pulihkan", + "restoreQueuedAccessibility": "Pulihkan mesej yang dibatalkan ke komposer" }, "olderMessages": { "arrived": "Mesej awal dimuatkan", diff --git a/apps/mobile/src/i18n/locales/mt.json b/apps/mobile/src/i18n/locales/mt.json index d65b259eb9..cf76a49b62 100644 --- a/apps/mobile/src/i18n/locales/mt.json +++ b/apps/mobile/src/i18n/locales/mt.json @@ -2187,7 +2187,11 @@ "previousTaskFinishing": "Il-kompitu preċedenti għadu qed jintemm. Jekk jogħġbok stenna mument.", "serviceUnavailable": "Is-servizz mhux disponibbli bħalissa. Jekk jogħġbok erġa' pprova.", "connectionTrouble": "Problema ta' konnessjoni. Jekk jogħġbok erġa' pprova fi ftit.", - "modelUnavailable": "Dan il-mudell mhux disponibbli għal Cloud Agent. Agħżel mudell ieħor u erġa' pprova." + "modelUnavailable": "Dan il-mudell mhux disponibbli għal Cloud Agent. Agħżel mudell ieħor u erġa' pprova.", + "cancelQueuedFailed": "Ma setax jiġi kkanċellat il-messaġġ.", + "cancelQueuedRestoreAvailable": "Il-messaġġ ġie kkanċellat. Tektek Irrestawra biex terġa' tqiegħdu fil-qasam tad-dħul tal-messaġġ.", + "cancelQueuedRestored": "Il-messaġġ ġie kkanċellat. Il-prompt reġa' jinsab fil-qasam tad-dħul tal-messaġġ.", + "cancelQueuedUpgradeRequired": "Il-kanċellazzjoni tal-messaġġi fil-kju teħtieġ Kilo CLI aktar ġdid. Aġġorna l-Kilo CLI u erġa' kkonnetta." }, "remoteSession": { "failedToCreate": "Ma rnexxiliex tinħoloq sessjoni remota", @@ -2511,7 +2515,11 @@ "queuedAccessibility": "Il-messaġġ qiegħed f'kju", "userMessage": "Messaġġ tal-utent", "assistantMessage": "Messaġġ tal-assistent", - "longPressHint": "Agħfas fit-tul għad-dettalji tal-messaġġ" + "longPressHint": "Agħfas fit-tul għad-dettalji tal-messaġġ", + "cancelQueued": "Ikkanċella l-messaġġ", + "cancelQueuedAccessibility": "Ikkanċella l-messaġġ fil-kju", + "restoreQueued": "Irrestawra", + "restoreQueuedAccessibility": "Irrestawra l-messaġġ ikkanċellat fil-qasam tad-dħul tal-messaġġ" }, "olderMessages": { "arrived": "Messaġġi aktar bikrin mibgħuta", diff --git a/apps/mobile/src/i18n/locales/my.json b/apps/mobile/src/i18n/locales/my.json index 6ddb44fd08..fbf77eafe3 100644 --- a/apps/mobile/src/i18n/locales/my.json +++ b/apps/mobile/src/i18n/locales/my.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "ယခင်လုပ်ငန်း ပြီးဆုံးနေဆဲဖြစ်သည်။ ခဏ စောင့်ပါ။", "serviceUnavailable": "ဝန်ဆောင်မှုသည် ယခုအခါ မရရှိနိုင်ပါ။ ထပ်စမ်းကြည့်ပါ။", "connectionTrouble": "ချိတ်ဆက်မှု ပြဿနာ။ ခဏနေမှ ထပ်စမ်းကြည့်ပါ။", - "modelUnavailable": "ဤမော်ဒယ်သည် Cloud Agent အတွက် မရရှိနိုင်ပါ။ အခြားမော်ဒယ်တစ်ခု ရွေးပြီး ထပ်စမ်းကြည့်ပါ။" + "modelUnavailable": "ဤမော်ဒယ်သည် Cloud Agent အတွက် မရရှိနိုင်ပါ။ အခြားမော်ဒယ်တစ်ခု ရွေးပြီး ထပ်စမ်းကြည့်ပါ။", + "cancelQueuedFailed": "စာကို ပယ်ဖျက်၍မရခဲ့ပါ။", + "cancelQueuedRestoreAvailable": "စာကို ပယ်ဖျက်ပြီးပါပြီ။ စာထည့်သွင်းရာအကွက်သို့ ပြန်ယူရန် “ပြန်ယူပါ” ကို နှိပ်ပါ။", + "cancelQueuedRestored": "စာကို ပယ်ဖျက်ပြီးပါပြီ။ ညွှန်ကြားချက်သည် စာထည့်သွင်းရာအကွက်တွင် ပြန်ရှိနေပါပြီ။", + "cancelQueuedUpgradeRequired": "တန်းစီထားသော စာများကို ပယ်ဖျက်ရန် ပိုသစ်သော Kilo CLI လိုအပ်သည်။ Kilo CLI ကို မွမ်းမံပြီး ပြန်ချိတ်ဆက်ပါ။" }, "remoteSession": { "failedToCreate": "အဝေးစက်ရှင်ကို ဖန်တီးရန် မအောင်မြင်ခဲ့ပါ", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "စာတန်းစီထားသည်", "userMessage": "အသုံးပြုသူ စာ", "assistantMessage": "Assistant စာ", - "longPressHint": "စာအသေးစိတ်အတွက် ကြာကြာနှိပ်ပါ" + "longPressHint": "စာအသေးစိတ်အတွက် ကြာကြာနှိပ်ပါ", + "cancelQueued": "စာကို ပယ်ဖျက်ပါ", + "cancelQueuedAccessibility": "တန်းစီထားသော စာကို ပယ်ဖျက်ပါ", + "restoreQueued": "ပြန်ယူပါ", + "restoreQueuedAccessibility": "ပယ်ဖျက်ထားသော စာကို စာထည့်သွင်းရာအကွက်သို့ ပြန်ယူပါ" }, "olderMessages": { "arrived": "အစောပိုင်း စာများ တင်ပြီးပါပြီ", diff --git a/apps/mobile/src/i18n/locales/nb.json b/apps/mobile/src/i18n/locales/nb.json index fa7bbe38b1..d809b7c8fb 100644 --- a/apps/mobile/src/i18n/locales/nb.json +++ b/apps/mobile/src/i18n/locales/nb.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Den forrige oppgaven er fortsatt i gang. Vent et øyeblikk.", "serviceUnavailable": "Tjenesten er utilgjengelig akkurat nå. Prøv igjen.", "connectionTrouble": "Tilkoblingsproblemer. Prøv igjen om et øyeblikk.", - "modelUnavailable": "Denne modellen er ikke tilgjengelig for Cloud Agent. Velg en annen modell og prøv igjen." + "modelUnavailable": "Denne modellen er ikke tilgjengelig for Cloud Agent. Velg en annen modell og prøv igjen.", + "cancelQueuedFailed": "Kunne ikke avbryte meldingen.", + "cancelQueuedRestoreAvailable": "Meldingen er avbrutt. Trykk på Gjenopprett for å legge den tilbake i meldingsfeltet.", + "cancelQueuedRestored": "Meldingen er avbrutt. Forespørselen er tilbake i meldingsfeltet.", + "cancelQueuedUpgradeRequired": "Avbryting av meldinger i kø krever en nyere Kilo CLI. Oppdater Kilo CLI og koble til på nytt." }, "remoteSession": { "failedToCreate": "Klarte ikke å opprette ekstern økt", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Melding satt i kø", "userMessage": "Brukermelding", "assistantMessage": "Assistentmelding", - "longPressHint": "Trykk og hold for meldingsdetaljer" + "longPressHint": "Trykk og hold for meldingsdetaljer", + "cancelQueued": "Avbryt melding", + "cancelQueuedAccessibility": "Avbryt melding i kø", + "restoreQueued": "Gjenopprett", + "restoreQueuedAccessibility": "Gjenopprett avbrutt melding i meldingsfeltet" }, "olderMessages": { "arrived": "Tidligere meldinger lastet inn", diff --git a/apps/mobile/src/i18n/locales/ne.json b/apps/mobile/src/i18n/locales/ne.json index d1035746f8..4e6b92bf0f 100644 --- a/apps/mobile/src/i18n/locales/ne.json +++ b/apps/mobile/src/i18n/locales/ne.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "अघिल्लो कार्य अझै समाप्त हुँदैछ। कृपया एक क्षण पर्खनुहोस्।", "serviceUnavailable": "सेवा अहिले उपलब्ध छैन। फेरि प्रयास गर्नुहोस्।", "connectionTrouble": "जडान समस्या। कृपया केही क्षणमा फेरि प्रयास गर्नुहोस्।", - "modelUnavailable": "यो मोडेल Cloud Agent का लागि उपलब्ध छैन। अर्को मोडेल छानेर फेरि प्रयास गर्नुहोस्।" + "modelUnavailable": "यो मोडेल Cloud Agent का लागि उपलब्ध छैन। अर्को मोडेल छानेर फेरि प्रयास गर्नुहोस्।", + "cancelQueuedFailed": "सन्देश रद्द गर्न सकिएन।", + "cancelQueuedRestoreAvailable": "सन्देश रद्द गरियो। यसलाई सन्देश लेख्ने ठाउँमा फर्काउन “पुनः स्थापित गर्नुहोस्” मा ट्याप गर्नुहोस्।", + "cancelQueuedRestored": "सन्देश रद्द गरियो। प्रम्प्ट सन्देश लेख्ने ठाउँमा फर्किएको छ।", + "cancelQueuedUpgradeRequired": "लाममा रहेका सन्देशहरू रद्द गर्न नयाँ Kilo CLI चाहिन्छ। Kilo CLI अद्यावधिक गरेर पुनः जडान गर्नुहोस्।" }, "remoteSession": { "failedToCreate": "दूरस्थ सत्र सिर्जना गर्न असफल भयो", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "सन्देश लाइनमा राखियो", "userMessage": "प्रयोगकर्ता सन्देश", "assistantMessage": "सहायक सन्देश", - "longPressHint": "सन्देश विवरणका लागि लामो थिच्नुहोस्" + "longPressHint": "सन्देश विवरणका लागि लामो थिच्नुहोस्", + "cancelQueued": "सन्देश रद्द गर्नुहोस्", + "cancelQueuedAccessibility": "लाममा रहेको सन्देश रद्द गर्नुहोस्", + "restoreQueued": "पुनः स्थापित गर्नुहोस्", + "restoreQueuedAccessibility": "रद्द गरिएको सन्देशलाई सन्देश लेख्ने ठाउँमा पुनः स्थापित गर्नुहोस्" }, "olderMessages": { "arrived": "पहिलेका सन्देशहरू लोड भयो", diff --git a/apps/mobile/src/i18n/locales/nl.json b/apps/mobile/src/i18n/locales/nl.json index 795c29e1dd..f21b3df9ff 100644 --- a/apps/mobile/src/i18n/locales/nl.json +++ b/apps/mobile/src/i18n/locales/nl.json @@ -1688,7 +1688,11 @@ "previousTaskFinishing": "De vorige taak is nog bezig. Even geduld.", "serviceUnavailable": "De service is momenteel niet beschikbaar. Probeer het opnieuw.", "connectionTrouble": "Verbindingsprobleem. Probeer het over een moment opnieuw.", - "modelUnavailable": "Dit model is niet beschikbaar voor Cloud Agent. Kies een ander model en probeer het opnieuw." + "modelUnavailable": "Dit model is niet beschikbaar voor Cloud Agent. Kies een ander model en probeer het opnieuw.", + "cancelQueuedFailed": "Bericht kon niet worden geannuleerd.", + "cancelQueuedRestoreAvailable": "Bericht geannuleerd. Tik op Herstellen om het terug te zetten in het berichtinvoerveld.", + "cancelQueuedRestored": "Bericht geannuleerd. De prompt staat weer in het berichtinvoerveld.", + "cancelQueuedUpgradeRequired": "Berichten in de wachtrij annuleren vereist een nieuwere Kilo CLI. Werk Kilo CLI bij en maak opnieuw verbinding." }, "remoteSession": { "failedToCreate": "Externe sessie maken mislukt", @@ -2009,7 +2013,11 @@ "queuedAccessibility": "Bericht in wachtrij", "userMessage": "Gebruikersbericht", "assistantMessage": "Assistentbericht", - "longPressHint": "Lang indrukken voor berichtdetails" + "longPressHint": "Lang indrukken voor berichtdetails", + "cancelQueued": "Bericht annuleren", + "cancelQueuedAccessibility": "Bericht in de wachtrij annuleren", + "restoreQueued": "Herstellen", + "restoreQueuedAccessibility": "Geannuleerd bericht herstellen naar het berichtinvoerveld" }, "olderMessages": { "arrived": "Eerdere berichten geladen", diff --git a/apps/mobile/src/i18n/locales/om.json b/apps/mobile/src/i18n/locales/om.json index 03a4a1cbfb..df05df929c 100644 --- a/apps/mobile/src/i18n/locales/om.json +++ b/apps/mobile/src/i18n/locales/om.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Hojii duraan ture amma iyyuu xumurameera. Mee yeroo xinnoo eegi.", "serviceUnavailable": "Tajaajilli amma hin jiraatu. Mee deebi'ii yaali.", "connectionTrouble": "Hidhannaan rakkoo qaba. Mee yeroo xinnoo booda deebi'ii yaali.", - "modelUnavailable": "Moodelii kun Cloud Agentf hin jiraatu. Mee moodelii biraa filadhuu deebi'ii yaali." + "modelUnavailable": "Moodelii kun Cloud Agentf hin jiraatu. Mee moodelii biraa filadhuu deebi'ii yaali.", + "cancelQueuedFailed": "Ergaa dhaabuu hin danda'ame.", + "cancelQueuedRestoreAvailable": "Ergaan dhaabbateera. Gara iddoo ergaa galchuutti deebisuuf Deebisaa tuqaa.", + "cancelQueuedRestored": "Ergaan dhaabbateera. Gaaffiin gara iddoo ergaa galchuutti deebi'eera.", + "cancelQueuedUpgradeRequired": "Ergaawwan tarree keessa jiran dhaabuun Kilo CLI haaraa barbaada. Kilo CLI haaromsaa, deebi'aatii walqabaa." }, "remoteSession": { "failedToCreate": "Xalayyii fagoo uumuu hin danda'ame", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Ergaan terraa keessa jira", "userMessage": "Ergaa fayyadamaa", "assistantMessage": "Ergaa gargaaraa", - "longPressHint": "Odeeffannoo ergaaf yeroo dheeraa cuqaasa" + "longPressHint": "Odeeffannoo ergaaf yeroo dheeraa cuqaasa", + "cancelQueued": "Ergaa dhaabaa", + "cancelQueuedAccessibility": "Ergaa tarree keessa jiru dhaabaa", + "restoreQueued": "Deebisaa", + "restoreQueuedAccessibility": "Ergaa dhaabbate gara iddoo ergaa galchuutti deebisaa" }, "olderMessages": { "arrived": "Ergaawwan duraanii baay'amanii jiru", diff --git a/apps/mobile/src/i18n/locales/or.json b/apps/mobile/src/i18n/locales/or.json index 44e55a8760..3d1e5483f3 100644 --- a/apps/mobile/src/i18n/locales/or.json +++ b/apps/mobile/src/i18n/locales/or.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "ପୂର୍ବ କାର୍ଯ୍ୟ ଏବେବି ସରୁଛି। ଦୟାକରି ଏକ ମୁହୂର୍ତ୍ତ ଅପେକ୍ଷା କରନ୍ତୁ।", "serviceUnavailable": "ସେବା ଏବେ ଉପଲବ୍ଧ ନାହିଁ। ଦୟାକରି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।", "connectionTrouble": "ସଂଯୋଗ ସମସ୍ୟା। ଦୟାକରି କିଛି ସମୟ ପରେ ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।", - "modelUnavailable": "ଏହି ମଡେଲ୍ Cloud Agent ପାଇଁ ଉପଲବ୍ଧ ନାହିଁ। ଅନ୍ୟ ମଡେଲ୍ ବାଛି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।" + "modelUnavailable": "ଏହି ମଡେଲ୍ Cloud Agent ପାଇଁ ଉପଲବ୍ଧ ନାହିଁ। ଅନ୍ୟ ମଡେଲ୍ ବାଛି ପୁଣି ଚେଷ୍ଟା କରନ୍ତୁ।", + "cancelQueuedFailed": "ବାର୍ତ୍ତା ବାତିଲ ହୋଇପାରିଲା ନାହିଁ।", + "cancelQueuedRestoreAvailable": "ବାର୍ତ୍ତା ବାତିଲ ହେଲା। ଏହାକୁ ବାର୍ତ୍ତା ଲେଖିବା ସ୍ଥାନକୁ ଫେରାଇବାକୁ “ପୁନଃସ୍ଥାପନ କରନ୍ତୁ” ଟାପ୍ କରନ୍ତୁ।", + "cancelQueuedRestored": "ବାର୍ତ୍ତା ବାତିଲ ହେଲା। ପ୍ରମ୍ପ୍ଟ ବାର୍ତ୍ତା ଲେଖିବା ସ୍ଥାନକୁ ଫେରିଛି।", + "cancelQueuedUpgradeRequired": "ଧାଡ଼ିରେ ଥିବା ବାର୍ତ୍ତା ବାତିଲ କରିବାକୁ Kilo CLI ର ଏକ ନୂତନ ସଂସ୍କରଣ ଆବଶ୍ୟକ। Kilo CLI ଅଦ୍ୟତନ କରନ୍ତୁ ଏବଂ ପୁନଃସଂଯୋଗ କରନ୍ତୁ।" }, "remoteSession": { "failedToCreate": "ଦୂରସ୍ଥ ସେସନ୍ ସୃଷ୍ଟି କରିବାରେ ବିଫଳ ହେଲା", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "ବାର୍ତ୍ତା ଧାଡିରେ ଅଛି", "userMessage": "ବ୍ୟବହାରକାରୀ ବାର୍ତ୍ତା", "assistantMessage": "ସହାୟକ ବାର୍ତ୍ତା", - "longPressHint": "ବାର୍ତ୍ତା ବିବରଣୀ ପାଇଁ ଦୀର୍ଘ ଦବାନ୍ତୁ" + "longPressHint": "ବାର୍ତ୍ତା ବିବରଣୀ ପାଇଁ ଦୀର୍ଘ ଦବାନ୍ତୁ", + "cancelQueued": "ବାର୍ତ୍ତା ବାତିଲ କରନ୍ତୁ", + "cancelQueuedAccessibility": "ଧାଡ଼ିରେ ଥିବା ବାର୍ତ୍ତା ବାତିଲ କରନ୍ତୁ", + "restoreQueued": "ପୁନଃସ୍ଥାପନ କରନ୍ତୁ", + "restoreQueuedAccessibility": "ବାତିଲ ହୋଇଥିବା ବାର୍ତ୍ତାକୁ ବାର୍ତ୍ତା ଲେଖିବା ସ୍ଥାନରେ ପୁନଃସ୍ଥାପନ କରନ୍ତୁ" }, "olderMessages": { "arrived": "ପୂର୍ବ ବାର୍ତ୍ତାଗୁଡ଼ିକ ଲୋଡ୍ ହେଲା", diff --git a/apps/mobile/src/i18n/locales/pa.json b/apps/mobile/src/i18n/locales/pa.json index 7210687d76..9230f95b58 100644 --- a/apps/mobile/src/i18n/locales/pa.json +++ b/apps/mobile/src/i18n/locales/pa.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "ਪਿਛਲਾ ਕੰਮ ਅਜੇ ਖਤਮ ਹੋ ਰਿਹਾ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਪਲ ਉਡੀਕੋ।", "serviceUnavailable": "ਸੇਵਾ ਇਸ ਸਮੇਂ ਉਪਲਬਧ ਨਹੀਂ ਹੈ। ਕਿਰਪਾ ਕਰਕੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।", "connectionTrouble": "ਕਨੈਕਸ਼ਨ ਸਮੱਸਿਆ। ਕਿਰਪਾ ਕਰਕੇ ਇੱਕ ਪਲ ਬਾਅਦ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।", - "modelUnavailable": "ਇਹ ਮਾਡਲ Cloud Agent ਲਈ ਉਪਲਬਧ ਨਹੀਂ ਹੈ। ਕੋਈ ਹੋਰ ਮਾਡਲ ਚੁਣੋ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" + "modelUnavailable": "ਇਹ ਮਾਡਲ Cloud Agent ਲਈ ਉਪਲਬਧ ਨਹੀਂ ਹੈ। ਕੋਈ ਹੋਰ ਮਾਡਲ ਚੁਣੋ ਅਤੇ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।", + "cancelQueuedFailed": "ਸੁਨੇਹਾ ਰੱਦ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।", + "cancelQueuedRestoreAvailable": "ਸੁਨੇਹਾ ਰੱਦ ਕੀਤਾ ਗਿਆ। ਇਸਨੂੰ ਕੰਪੋਜ਼ਰ ਵਿੱਚ ਵਾਪਸ ਲਿਆਉਣ ਲਈ ਬਹਾਲ ਕਰੋ 'ਤੇ ਟੈਪ ਕਰੋ।", + "cancelQueuedRestored": "ਸੁਨੇਹਾ ਰੱਦ ਕੀਤਾ ਗਿਆ। ਪ੍ਰੰਪਟ ਕੰਪੋਜ਼ਰ ਵਿੱਚ ਵਾਪਸ ਆ ਗਿਆ ਹੈ।", + "cancelQueuedUpgradeRequired": "ਕਤਾਰ ਵਿੱਚ ਪਏ ਸੁਨੇਹੇ ਰੱਦ ਕਰਨ ਲਈ Kilo CLI ਦੇ ਨਵੇਂ ਸੰਸਕਰਣ ਦੀ ਲੋੜ ਹੈ। Kilo CLI ਅੱਪਡੇਟ ਕਰੋ ਅਤੇ ਮੁੜ ਕਨੈਕਟ ਕਰੋ।" }, "remoteSession": { "failedToCreate": "ਰਿਮੋਟ ਸੈਸ਼ਨ ਬਣਾਉਣ ਵਿੱਚ ਅਸਫਲ", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "ਸੁਨੇਹਾ ਕਤਾਰ ਵਿੱਚ", "userMessage": "ਵਰਤੋਂਕਾਰ ਸੁਨੇਹਾ", "assistantMessage": "ਸਹਾਇਕ ਸੁਨੇਹਾ", - "longPressHint": "ਸੁਨੇਹੇ ਦੇ ਵੇਰਵਿਆਂ ਲਈ ਲੰਬਾ ਦਬਾਓ" + "longPressHint": "ਸੁਨੇਹੇ ਦੇ ਵੇਰਵਿਆਂ ਲਈ ਲੰਬਾ ਦਬਾਓ", + "cancelQueued": "ਸੁਨੇਹਾ ਰੱਦ ਕਰੋ", + "cancelQueuedAccessibility": "ਕਤਾਰ ਵਿੱਚ ਪਿਆ ਸੁਨੇਹਾ ਰੱਦ ਕਰੋ", + "restoreQueued": "ਬਹਾਲ ਕਰੋ", + "restoreQueuedAccessibility": "ਰੱਦ ਕੀਤਾ ਸੁਨੇਹਾ ਕੰਪੋਜ਼ਰ ਵਿੱਚ ਬਹਾਲ ਕਰੋ" }, "olderMessages": { "arrived": "ਪਹਿਲੇ ਸੁਨੇਹੇ ਲੋਡ ਹੋ ਗਏ", diff --git a/apps/mobile/src/i18n/locales/pl.json b/apps/mobile/src/i18n/locales/pl.json index 6482bde7ea..b7f10e0f89 100644 --- a/apps/mobile/src/i18n/locales/pl.json +++ b/apps/mobile/src/i18n/locales/pl.json @@ -1704,7 +1704,11 @@ "previousTaskFinishing": "Poprzednie zadanie wciąż się kończy. Proszę chwilę poczekać.", "serviceUnavailable": "Usługa jest teraz niedostępna. Spróbuj ponownie.", "connectionTrouble": "Problem z połączeniem. Spróbuj ponownie za chwilę.", - "modelUnavailable": "Ten model nie jest dostępny dla Cloud Agent. Wybierz inny model i spróbuj ponownie." + "modelUnavailable": "Ten model nie jest dostępny dla Cloud Agent. Wybierz inny model i spróbuj ponownie.", + "cancelQueuedFailed": "Nie udało się anulować wiadomości.", + "cancelQueuedRestoreAvailable": "Wiadomość anulowana. Dotknij „Przywróć”, aby przywrócić ją do pola wiadomości.", + "cancelQueuedRestored": "Wiadomość anulowana. Polecenie jest ponownie w polu wiadomości.", + "cancelQueuedUpgradeRequired": "Anulowanie wiadomości w kolejce wymaga nowszej wersji Kilo CLI. Zaktualizuj Kilo CLI i połącz się ponownie." }, "remoteSession": { "failedToCreate": "Nie udało się utworzyć sesji zdalnej", @@ -2027,7 +2031,11 @@ "queuedAccessibility": "Wiadomość w kolejce", "userMessage": "Wiadomość użytkownika", "assistantMessage": "Wiadomość asystenta", - "longPressHint": "Przytrzymaj, aby zobaczyć szczegóły wiadomości" + "longPressHint": "Przytrzymaj, aby zobaczyć szczegóły wiadomości", + "cancelQueued": "Anuluj wiadomość", + "cancelQueuedAccessibility": "Anuluj wiadomość w kolejce", + "restoreQueued": "Przywróć", + "restoreQueuedAccessibility": "Przywróć anulowaną wiadomość do pola wiadomości" }, "olderMessages": { "arrived": "Załadowano wcześniejsze wiadomości", diff --git a/apps/mobile/src/i18n/locales/ps.json b/apps/mobile/src/i18n/locales/ps.json index 339a805855..003be69de2 100644 --- a/apps/mobile/src/i18n/locales/ps.json +++ b/apps/mobile/src/i18n/locales/ps.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "مخکینی کار لا هم روان دی. مهرباني وکړئ یوه شېبه انتظار وکړئ.", "serviceUnavailable": "خدمت اوس مهال شتون نلري. مهرباني وکړئ بیا هڅه وکړئ.", "connectionTrouble": "د اړیکې ستونزه. مهرباني وکړئ یوه شېبه وروسته بیا هڅه وکړئ.", - "modelUnavailable": "دا ماډل د Cloud Agent لپاره شتون نلري. بل ماډل غوره کړئ او بیا هڅه وکړئ." + "modelUnavailable": "دا ماډل د Cloud Agent لپاره شتون نلري. بل ماډل غوره کړئ او بیا هڅه وکړئ.", + "cancelQueuedFailed": "پیغام لغوه نه شو.", + "cancelQueuedRestoreAvailable": "پیغام لغوه شو. د پیغام لیکلو ځای ته د بیرته راوړلو لپاره «بیرته راوړئ» ټک کړئ.", + "cancelQueuedRestored": "پیغام لغوه شو. پرامپټ بیرته د پیغام لیکلو په ځای کې دی.", + "cancelQueuedUpgradeRequired": "په کتار کې د پیغامونو لغوه کولو لپاره نوې Kilo CLI اړینه ده. Kilo CLI تازه کړئ او بیا ونښلئ." }, "remoteSession": { "failedToCreate": "لیرې ناسته جوړه نه شوه", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "پیغام په قطار کې ایښودل شوی", "userMessage": "د کاروونکي پیغام", "assistantMessage": "د مرستیال پیغام", - "longPressHint": "د پیغام د تفصیل لپاره اوږد فشار ورکړئ" + "longPressHint": "د پیغام د تفصیل لپاره اوږد فشار ورکړئ", + "cancelQueued": "پیغام لغوه کړئ", + "cancelQueuedAccessibility": "په کتار کې پیغام لغوه کړئ", + "restoreQueued": "بیرته راوړئ", + "restoreQueuedAccessibility": "لغوه شوی پیغام د پیغام لیکلو ځای ته بیرته راوړئ" }, "olderMessages": { "arrived": "پخواني پیغامونه پورته شول", diff --git a/apps/mobile/src/i18n/locales/pt-BR.json b/apps/mobile/src/i18n/locales/pt-BR.json index ff151b25c3..8cdd4d37bd 100644 --- a/apps/mobile/src/i18n/locales/pt-BR.json +++ b/apps/mobile/src/i18n/locales/pt-BR.json @@ -1680,7 +1680,11 @@ "previousTaskFinishing": "A tarefa anterior ainda está terminando. Aguarde um momento.", "serviceUnavailable": "O serviço está indisponível no momento. Tente novamente.", "connectionTrouble": "Problema de conexão. Tente novamente em um momento.", - "modelUnavailable": "Este modelo não está disponível para o Cloud Agent. Escolha outro modelo e tente novamente." + "modelUnavailable": "Este modelo não está disponível para o Cloud Agent. Escolha outro modelo e tente novamente.", + "cancelQueuedFailed": "Não foi possível cancelar a mensagem.", + "cancelQueuedRestoreAvailable": "Mensagem cancelada. Toque em Restaurar para devolvê-la ao campo de mensagem.", + "cancelQueuedRestored": "Mensagem cancelada. O prompt está de volta no campo de mensagem.", + "cancelQueuedUpgradeRequired": "Cancelar mensagens na fila exige uma Kilo CLI mais recente. Atualize a Kilo CLI e reconecte." }, "remoteSession": { "failedToCreate": "Falha ao criar a sessão remota", @@ -2002,7 +2006,11 @@ "queuedAccessibility": "Mensagem na fila", "userMessage": "Mensagem do usuário", "assistantMessage": "Mensagem do assistente", - "longPressHint": "Pressione e segure para ver os detalhes da mensagem" + "longPressHint": "Pressione e segure para ver os detalhes da mensagem", + "cancelQueued": "Cancelar mensagem", + "cancelQueuedAccessibility": "Cancelar mensagem na fila", + "restoreQueued": "Restaurar", + "restoreQueuedAccessibility": "Restaurar mensagem cancelada no campo de mensagem" }, "olderMessages": { "arrived": "Mensagens anteriores carregadas", diff --git a/apps/mobile/src/i18n/locales/pt.json b/apps/mobile/src/i18n/locales/pt.json index 1f98ed0f79..e07ffa8a03 100644 --- a/apps/mobile/src/i18n/locales/pt.json +++ b/apps/mobile/src/i18n/locales/pt.json @@ -2157,7 +2157,11 @@ "previousTaskFinishing": "A tarefa anterior ainda está a terminar. Aguarde um momento.", "serviceUnavailable": "O serviço está indisponível neste momento. Tente novamente.", "connectionTrouble": "Problema de ligação. Tente novamente dentro de um momento.", - "modelUnavailable": "Este modelo não está disponível para o Cloud Agent. Escolha outro modelo e tente novamente." + "modelUnavailable": "Este modelo não está disponível para o Cloud Agent. Escolha outro modelo e tente novamente.", + "cancelQueuedFailed": "Não foi possível cancelar a mensagem.", + "cancelQueuedRestoreAvailable": "Mensagem cancelada. Toque em Restaurar para a devolver ao campo de mensagem.", + "cancelQueuedRestored": "Mensagem cancelada. O prompt está novamente no campo de mensagem.", + "cancelQueuedUpgradeRequired": "Cancelar mensagens em fila requer uma versão mais recente do Kilo CLI. Atualize o Kilo CLI e volte a ligar." }, "remoteSession": { "failedToCreate": "Falha ao criar a sessão remota", @@ -2479,7 +2483,11 @@ "queuedAccessibility": "Mensagem em fila", "userMessage": "Mensagem do utilizador", "assistantMessage": "Mensagem do assistente", - "longPressHint": "Prima sem soltar para ver os detalhes da mensagem" + "longPressHint": "Prima sem soltar para ver os detalhes da mensagem", + "cancelQueued": "Cancelar mensagem", + "cancelQueuedAccessibility": "Cancelar mensagem em fila", + "restoreQueued": "Restaurar", + "restoreQueuedAccessibility": "Restaurar a mensagem cancelada no campo de mensagem" }, "olderMessages": { "arrived": "Mensagens anteriores carregadas", diff --git a/apps/mobile/src/i18n/locales/ro.json b/apps/mobile/src/i18n/locales/ro.json index b9a8ea9e68..19874f2c34 100644 --- a/apps/mobile/src/i18n/locales/ro.json +++ b/apps/mobile/src/i18n/locales/ro.json @@ -2157,7 +2157,11 @@ "previousTaskFinishing": "Sarcina anterioară încă se finalizează. Te rugăm să aștepți un moment.", "serviceUnavailable": "Serviciul nu este disponibil acum. Te rugăm să încerci din nou.", "connectionTrouble": "Probleme de conexiune. Te rugăm să reîncerci într-un moment.", - "modelUnavailable": "Acest model nu este disponibil pentru Cloud Agent. Alege alt model și încearcă din nou." + "modelUnavailable": "Acest model nu este disponibil pentru Cloud Agent. Alege alt model și încearcă din nou.", + "cancelQueuedFailed": "Nu s-a putut anula mesajul.", + "cancelQueuedRestoreAvailable": "Mesaj anulat. Atinge Restabilește pentru a-l readuce în câmpul de mesaj.", + "cancelQueuedRestored": "Mesaj anulat. Promptul este din nou în câmpul de mesaj.", + "cancelQueuedUpgradeRequired": "Anularea mesajelor în așteptare necesită un Kilo CLI mai nou. Actualizează Kilo CLI și reconectează-te." }, "remoteSession": { "failedToCreate": "Nu s-a putut crea sesiunea de la distanță", @@ -2479,7 +2483,11 @@ "queuedAccessibility": "Mesaj pus în coadă", "userMessage": "Mesaj de utilizator", "assistantMessage": "Mesaj de asistent", - "longPressHint": "Atingere lungă pentru detaliile mesajului" + "longPressHint": "Atingere lungă pentru detaliile mesajului", + "cancelQueued": "Anulează mesajul", + "cancelQueuedAccessibility": "Anulează mesajul în așteptare", + "restoreQueued": "Restabilește", + "restoreQueuedAccessibility": "Restabilește mesajul anulat în câmpul de mesaj" }, "olderMessages": { "arrived": "Mesajele anterioare au fost încărcate", diff --git a/apps/mobile/src/i18n/locales/ru.json b/apps/mobile/src/i18n/locales/ru.json index dee0a421fa..3628ab2096 100644 --- a/apps/mobile/src/i18n/locales/ru.json +++ b/apps/mobile/src/i18n/locales/ru.json @@ -1704,7 +1704,11 @@ "previousTaskFinishing": "Предыдущая задача ещё завершается. Подождите немного.", "serviceUnavailable": "Сервис сейчас недоступен. Попробуйте ещё раз.", "connectionTrouble": "Проблемы с подключением. Повторите через мгновение.", - "modelUnavailable": "Эта модель недоступна для Cloud Agent. Выберите другую модель и попробуйте снова." + "modelUnavailable": "Эта модель недоступна для Cloud Agent. Выберите другую модель и попробуйте снова.", + "cancelQueuedFailed": "Не удалось отменить сообщение.", + "cancelQueuedRestoreAvailable": "Сообщение отменено. Нажмите «Восстановить», чтобы вернуть его в поле ввода.", + "cancelQueuedRestored": "Сообщение отменено. Запрос снова в поле ввода.", + "cancelQueuedUpgradeRequired": "Для отмены сообщений в очереди требуется более новая версия Kilo CLI. Обновите Kilo CLI и подключитесь заново." }, "remoteSession": { "failedToCreate": "Не удалось создать удаленный сеанс", @@ -2027,7 +2031,11 @@ "queuedAccessibility": "Сообщение в очереди", "userMessage": "Сообщение пользователя", "assistantMessage": "Сообщение ассистента", - "longPressHint": "Долгое нажатие для сведений о сообщении" + "longPressHint": "Долгое нажатие для сведений о сообщении", + "cancelQueued": "Отменить сообщение", + "cancelQueuedAccessibility": "Отменить сообщение в очереди", + "restoreQueued": "Восстановить", + "restoreQueuedAccessibility": "Восстановить отмененное сообщение в поле ввода" }, "olderMessages": { "arrived": "Более ранние сообщения загружены", diff --git a/apps/mobile/src/i18n/locales/si.json b/apps/mobile/src/i18n/locales/si.json index 4b2f21b535..bb75e71b54 100644 --- a/apps/mobile/src/i18n/locales/si.json +++ b/apps/mobile/src/i18n/locales/si.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "පෙර කාර්යය තවමත් අවසන් වෙමින් පවතී. කරුණාකර මොහොතක් රැඳී සිටින්න.", "serviceUnavailable": "සේවාව දැනට ලබා ගත නොහැක. කරුණාකර නැවත උත්සාහ කරන්න.", "connectionTrouble": "සම්බන්ධතා ගැටලුවකි. කරුණාකර මොහොතකට පසු නැවත උත්සාහ කරන්න.", - "modelUnavailable": "මෙම මාදිලිය Cloud Agent සඳහා ලබා ගත නොහැක. වෙනත් මාදිලියක් තෝරා නැවත උත්සාහ කරන්න." + "modelUnavailable": "මෙම මාදිලිය Cloud Agent සඳහා ලබා ගත නොහැක. වෙනත් මාදිලියක් තෝරා නැවත උත්සාහ කරන්න.", + "cancelQueuedFailed": "පණිවිඩය අවලංගු කළ නොහැකි විය.", + "cancelQueuedRestoreAvailable": "පණිවිඩය අවලංගු විය. එය පණිවිඩ ආදාන කොටුවට ආපසු ගෙන ඒමට ප්‍රතිස්ථාපනය කරන්න තට්ටු කරන්න.", + "cancelQueuedRestored": "පණිවිඩය අවලංගු විය. ඉල්ලීම නැවත පණිවිඩ ආදාන කොටුවේ ඇත.", + "cancelQueuedUpgradeRequired": "පෝලිමේ ඇති පණිවිඩ අවලංගු කිරීමට වඩා නව Kilo CLI අනුවාදයක් අවශ්‍ය වේ. Kilo CLI යාවත්කාලීන කර නැවත සම්බන්ධ කරන්න." }, "remoteSession": { "failedToCreate": "දුරස්ථ සැසියක් සෑදීමට අසාර්ථක විය", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "පණිවිඩය පෝලිමේ ඇත", "userMessage": "පරිශීලක පණිවිඩය", "assistantMessage": "සහායක පණිවිඩය", - "longPressHint": "පණිවිඩ විස්තර සඳහා දිගු ඔබන්න" + "longPressHint": "පණිවිඩ විස්තර සඳහා දිගු ඔබන්න", + "cancelQueued": "පණිවිඩය අවලංගු කරන්න", + "cancelQueuedAccessibility": "පෝලිමේ ඇති පණිවිඩය අවලංගු කරන්න", + "restoreQueued": "ප්‍රතිස්ථාපනය කරන්න", + "restoreQueuedAccessibility": "අවලංගු කළ පණිවිඩය පණිවිඩ ආදාන කොටුවට ප්‍රතිස්ථාපනය කරන්න" }, "olderMessages": { "arrived": "පෙර පණිවිඩ පූරණය විය", diff --git a/apps/mobile/src/i18n/locales/sk.json b/apps/mobile/src/i18n/locales/sk.json index 724b658578..61504f5d7a 100644 --- a/apps/mobile/src/i18n/locales/sk.json +++ b/apps/mobile/src/i18n/locales/sk.json @@ -2172,7 +2172,11 @@ "previousTaskFinishing": "Predchádzajúca úloha sa ešte dokončuje. Počkajte chvíľu.", "serviceUnavailable": "Služba je momentálne nedostupná. Skúste to znova.", "connectionTrouble": "Problém s pripojením. Skúste to znova o chvíľu.", - "modelUnavailable": "Tento model nie je dostupný pre Cloud Agent. Vyberte iný model a skúste to znova." + "modelUnavailable": "Tento model nie je dostupný pre Cloud Agent. Vyberte iný model a skúste to znova.", + "cancelQueuedFailed": "Správu sa nepodarilo zrušiť.", + "cancelQueuedRestoreAvailable": "Správa zrušená. Klepnutím na Obnoviť ju vrátite do poľa na zadávanie správy.", + "cancelQueuedRestored": "Správa zrušená. Prompt je späť v poli na zadávanie správy.", + "cancelQueuedUpgradeRequired": "Zrušenie správ vo fronte vyžaduje novšie Kilo CLI. Aktualizujte Kilo CLI a znovu sa pripojte." }, "remoteSession": { "failedToCreate": "Vzdialené sedenie sa nepodarilo vytvoriť", @@ -2495,7 +2499,11 @@ "queuedAccessibility": "Správa je vo fronte", "userMessage": "Správa používateľa", "assistantMessage": "Správa asistenta", - "longPressHint": "Dlhé stlačenie zobrazí podrobnosti správy" + "longPressHint": "Dlhé stlačenie zobrazí podrobnosti správy", + "cancelQueued": "Zrušiť správu", + "cancelQueuedAccessibility": "Zrušiť správu vo fronte", + "restoreQueued": "Obnoviť", + "restoreQueuedAccessibility": "Obnoviť zrušenú správu v poli na zadávanie správy" }, "olderMessages": { "arrived": "Staršie správy sa načítali", diff --git a/apps/mobile/src/i18n/locales/sl.json b/apps/mobile/src/i18n/locales/sl.json index d34426849c..059e9f3be5 100644 --- a/apps/mobile/src/i18n/locales/sl.json +++ b/apps/mobile/src/i18n/locales/sl.json @@ -2172,7 +2172,11 @@ "previousTaskFinishing": "Prejšnja naloga se še dokončuje. Počakajte trenutek.", "serviceUnavailable": "Storitev trenutno ni na voljo. Poskusite znova.", "connectionTrouble": "Težava s povezavo. Poskusite znova čez trenutek.", - "modelUnavailable": "Ta model ni na voljo za Cloud Agent. Izberite drug model in poskusite znova." + "modelUnavailable": "Ta model ni na voljo za Cloud Agent. Izberite drug model in poskusite znova.", + "cancelQueuedFailed": "Sporočila ni bilo mogoče preklicati.", + "cancelQueuedRestoreAvailable": "Sporočilo je preklicano. Tapnite »Obnovite«, da ga vrnete v polje za vnos sporočila.", + "cancelQueuedRestored": "Sporočilo je preklicano. Zahteva je spet v polju za vnos sporočila.", + "cancelQueuedUpgradeRequired": "Preklic sporočil v čakalni vrsti zahteva novejši Kilo CLI. Posodobite Kilo CLI in se ponovno povežite." }, "remoteSession": { "failedToCreate": "Oddaljene seje ni bilo mogoče ustvariti", @@ -2495,7 +2499,11 @@ "queuedAccessibility": "Sporočilo je v čakalni vrsti", "userMessage": "Sporočilo uporabnika", "assistantMessage": "Sporočilo asistenta", - "longPressHint": "Dolg pritisk za podrobnosti sporočila" + "longPressHint": "Dolg pritisk za podrobnosti sporočila", + "cancelQueued": "Prekličite sporočilo", + "cancelQueuedAccessibility": "Prekličite sporočilo v čakalni vrsti", + "restoreQueued": "Obnovite", + "restoreQueuedAccessibility": "Obnovite preklicano sporočilo v polju za vnos sporočila" }, "olderMessages": { "arrived": "Naložena so bila prejšnja sporočila", diff --git a/apps/mobile/src/i18n/locales/so.json b/apps/mobile/src/i18n/locales/so.json index fb1e131b58..ee5281f76b 100644 --- a/apps/mobile/src/i18n/locales/so.json +++ b/apps/mobile/src/i18n/locales/so.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Hawsha hore weli way dhammaanaysaa. Fadlan sug daqiiqad.", "serviceUnavailable": "Adeeggu hadda ma jiro. Fadlan mar kale isku day.", "connectionTrouble": "Dhibaato xiriir. Fadlan mar kale isku day daqiiqad.", - "modelUnavailable": "Moodeelkani looma heli karo Cloud Agent. Dooro moodeel kale oo mar kale isku day." + "modelUnavailable": "Moodeelkani looma heli karo Cloud Agent. Dooro moodeel kale oo mar kale isku day.", + "cancelQueuedFailed": "Fariinta lama joojin karin.", + "cancelQueuedRestoreAvailable": "Fariinta waa la joojiyay. Taabo Soo celi si aad ugu soo celiso composer-ka.", + "cancelQueuedRestored": "Fariinta waa la joojiyay. Weydintu waxay ku soo noqotay composer-ka.", + "cancelQueuedUpgradeRequired": "Joojinta fariimaha safan waxay u baahan tahay Kilo CLI ka cusub. Cusboonaysii Kilo CLI oo dib u xir." }, "remoteSession": { "failedToCreate": "Abuuridda kalfadhiga fog way fashilantay", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Fariinta waa la saf dhigay", "userMessage": "Fariinta isticmaalaha", "assistantMessage": "Fariinta kaaliyaha", - "longPressHint": "Riix oo hay si aad u aragto faahfaahinta fariinta" + "longPressHint": "Riix oo hay si aad u aragto faahfaahinta fariinta", + "cancelQueued": "Jooji fariinta", + "cancelQueuedAccessibility": "Jooji fariinta safan", + "restoreQueued": "Soo celi", + "restoreQueuedAccessibility": "Ku soo celi fariinta la joojiyay composer-ka" }, "olderMessages": { "arrived": "Fariimihii hore waa la soo dejiyay", diff --git a/apps/mobile/src/i18n/locales/sq.json b/apps/mobile/src/i18n/locales/sq.json index 52dcc3cd82..6a8be213f8 100644 --- a/apps/mobile/src/i18n/locales/sq.json +++ b/apps/mobile/src/i18n/locales/sq.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Detyra e mëparshme ende po përfundon. Ju lutemi prisni një moment.", "serviceUnavailable": "Shërbimi nuk është i disponueshëm tani. Ju lutemi provoni përsëri.", "connectionTrouble": "Probleme me lidhjen. Ju lutemi provoni përsëri pas një momenti.", - "modelUnavailable": "Ky model nuk është i disponueshëm për Cloud Agent. Zgjidhni një model tjetër dhe provoni përsëri." + "modelUnavailable": "Ky model nuk është i disponueshëm për Cloud Agent. Zgjidhni një model tjetër dhe provoni përsëri.", + "cancelQueuedFailed": "Mesazhi nuk mund të anulohej.", + "cancelQueuedRestoreAvailable": "Mesazhi u anulua. Prekni Rivendosni për ta kthyer në fushën e mesazhit.", + "cancelQueuedRestored": "Mesazhi u anulua. Kërkesa është sërish në fushën e mesazhit.", + "cancelQueuedUpgradeRequired": "Anulimi i mesazheve në pritje kërkon një Kilo CLI më të ri. Përditësoni Kilo CLI dhe rilidhuni." }, "remoteSession": { "failedToCreate": "Dështoi krijimi i seancës në distancë", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Mesazhi në radhë", "userMessage": "Mesazh përdoruesi", "assistantMessage": "Mesazh asistenti", - "longPressHint": "Shtypni gjatë për detaje të mesazhit" + "longPressHint": "Shtypni gjatë për detaje të mesazhit", + "cancelQueued": "Anuloni mesazhin", + "cancelQueuedAccessibility": "Anuloni mesazhin në pritje", + "restoreQueued": "Rivendosni", + "restoreQueuedAccessibility": "Rivendosni mesazhin e anuluar në fushën e mesazhit" }, "olderMessages": { "arrived": "U ngarkuan mesazhet e mëparshme", diff --git a/apps/mobile/src/i18n/locales/sr.json b/apps/mobile/src/i18n/locales/sr.json index d006bc41f9..28a398ec03 100644 --- a/apps/mobile/src/i18n/locales/sr.json +++ b/apps/mobile/src/i18n/locales/sr.json @@ -2157,7 +2157,11 @@ "previousTaskFinishing": "Prethodni zadatak se još završava. Sačekaj malo.", "serviceUnavailable": "Usluga trenutno nije dostupna. Pokušaj ponovo.", "connectionTrouble": "Problem s vezom. Pokušaj ponovo za trenutak.", - "modelUnavailable": "Ovaj model nije dostupan za Cloud Agent. Izaberi drugi model i pokušaj ponovo." + "modelUnavailable": "Ovaj model nije dostupan za Cloud Agent. Izaberi drugi model i pokušaj ponovo.", + "cancelQueuedFailed": "Otkazivanje poruke nije uspelo.", + "cancelQueuedRestoreAvailable": "Poruka je otkazana. Dodirnite „Vratite“ da je vratite u kompozitor.", + "cancelQueuedRestored": "Poruka je otkazana. Upit je ponovo u kompozitoru.", + "cancelQueuedUpgradeRequired": "Otkazivanje poruka u redu čekanja zahteva noviji Kilo CLI. Ažurirajte Kilo CLI i ponovo se povežite." }, "remoteSession": { "failedToCreate": "Daljinska sesija se ne može kreirati", @@ -2479,7 +2483,11 @@ "queuedAccessibility": "Poruka u redu čekanja", "userMessage": "Korisnička poruka", "assistantMessage": "Asistentska poruka", - "longPressHint": "Dugi pritisak za detalje poruke" + "longPressHint": "Dugi pritisak za detalje poruke", + "cancelQueued": "Otkažite poruku", + "cancelQueuedAccessibility": "Otkažite poruku u redu čekanja", + "restoreQueued": "Vratite", + "restoreQueuedAccessibility": "Vratite otkazanu poruku u kompozitor" }, "olderMessages": { "arrived": "Ranije poruke učitane", diff --git a/apps/mobile/src/i18n/locales/sv.json b/apps/mobile/src/i18n/locales/sv.json index 734e601690..8eb7dbda79 100644 --- a/apps/mobile/src/i18n/locales/sv.json +++ b/apps/mobile/src/i18n/locales/sv.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Den föregående uppgiften håller fortfarande på att avslutas. Vänta en stund.", "serviceUnavailable": "Tjänsten är inte tillgänglig just nu. Försök igen.", "connectionTrouble": "Anslutningsproblem. Försök igen om en liten stund.", - "modelUnavailable": "Den här modellen är inte tillgänglig för Cloud Agent. Välj en annan modell och försök igen." + "modelUnavailable": "Den här modellen är inte tillgänglig för Cloud Agent. Välj en annan modell och försök igen.", + "cancelQueuedFailed": "Kunde inte avbryta meddelandet.", + "cancelQueuedRestoreAvailable": "Meddelandet avbröts. Tryck på Återställ för att återföra det till meddelandefältet.", + "cancelQueuedRestored": "Meddelandet avbröts. Prompten finns åter i meddelandefältet.", + "cancelQueuedUpgradeRequired": "Att avbryta köade meddelanden kräver en nyare Kilo CLI. Uppdatera Kilo CLI och återanslut." }, "remoteSession": { "failedToCreate": "Det gick inte att skapa fjärrsession", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Meddelandet är i kö", "userMessage": "Användarmeddelande", "assistantMessage": "Assistentmeddelande", - "longPressHint": "Tryck länge för meddelandedetaljer" + "longPressHint": "Tryck länge för meddelandedetaljer", + "cancelQueued": "Avbryt meddelande", + "cancelQueuedAccessibility": "Avbryt köat meddelande", + "restoreQueued": "Återställ", + "restoreQueuedAccessibility": "Återställ avbrutet meddelande till meddelandefältet" }, "olderMessages": { "arrived": "Tidigare meddelanden lästes in", diff --git a/apps/mobile/src/i18n/locales/sw.json b/apps/mobile/src/i18n/locales/sw.json index b6c62a8367..403da103cb 100644 --- a/apps/mobile/src/i18n/locales/sw.json +++ b/apps/mobile/src/i18n/locales/sw.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Kazi iliyotangulia bado inakamilika. Tafadhali subiri kidogo.", "serviceUnavailable": "Huduma haipatikani sasa hivi. Tafadhali jaribu tena.", "connectionTrouble": "Tatizo la muunganisho. Tafadhali jaribu tena baada ya muda mfupi.", - "modelUnavailable": "Mfano huu haupatikani kwa Cloud Agent. Chagua mfano mwingine na ujaribu tena." + "modelUnavailable": "Mfano huu haupatikani kwa Cloud Agent. Chagua mfano mwingine na ujaribu tena.", + "cancelQueuedFailed": "Imeshindwa kughairi ujumbe.", + "cancelQueuedRestoreAvailable": "Ujumbe umeghairiwa. Gusa Rejesha ili kuurudisha kwenye sehemu ya kuandika ujumbe.", + "cancelQueuedRestored": "Ujumbe umeghairiwa. Maagizo yamerudi kwenye sehemu ya kuandika ujumbe.", + "cancelQueuedUpgradeRequired": "Kughairi ujumbe ulio kwenye foleni kunahitaji Kilo CLI mpya zaidi. Sasisha Kilo CLI na unganisha tena." }, "remoteSession": { "failedToCreate": "Imeshindwa kuunda kikao cha mbali", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Ujumbe umepangwa", "userMessage": "Ujumbe wa mtumiaji", "assistantMessage": "Ujumbe wa msaidizi", - "longPressHint": "Bonyeza kwa muda mrefu kwa maelezo ya ujumbe" + "longPressHint": "Bonyeza kwa muda mrefu kwa maelezo ya ujumbe", + "cancelQueued": "Ghairi ujumbe", + "cancelQueuedAccessibility": "Ghairi ujumbe ulio kwenye foleni", + "restoreQueued": "Rejesha", + "restoreQueuedAccessibility": "Rejesha ujumbe ulioghairiwa kwenye sehemu ya kuandika ujumbe" }, "olderMessages": { "arrived": "Ujumbe wa awali umepakiwa", diff --git a/apps/mobile/src/i18n/locales/ta.json b/apps/mobile/src/i18n/locales/ta.json index 9735aa76c2..f11ec05f0d 100644 --- a/apps/mobile/src/i18n/locales/ta.json +++ b/apps/mobile/src/i18n/locales/ta.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "முந்தைய பணி இன்னும் முடிவடைந்து கொண்டிருக்கிறது. சிறிது காத்திருக்கவும்.", "serviceUnavailable": "சேவை இப்போது கிடைக்கவில்லை. மீண்டும் முயற்சிக்கவும்.", "connectionTrouble": "இணைப்பில் சிக்கல். சிறிது நேரம் கழித்து மீண்டும் முயற்சிக்கவும்.", - "modelUnavailable": "இந்த மாதிரி Cloud Agent-க்குக் கிடைக்கவில்லை. வேறு மாதிரியைத் தேர்ந்தெடுத்து மீண்டும் முயற்சிக்கவும்." + "modelUnavailable": "இந்த மாதிரி Cloud Agent-க்குக் கிடைக்கவில்லை. வேறு மாதிரியைத் தேர்ந்தெடுத்து மீண்டும் முயற்சிக்கவும்.", + "cancelQueuedFailed": "செய்தியை ரத்து செய்ய முடியவில்லை.", + "cancelQueuedRestoreAvailable": "செய்தி ரத்து செய்யப்பட்டது. அதைச் செய்தி உள்ளீட்டிற்குத் திருப்ப, “மீட்டமைக்கவும்” என்பதைத் தட்டவும்.", + "cancelQueuedRestored": "செய்தி ரத்து செய்யப்பட்டது. வினவல் மீண்டும் செய்தி உள்ளீட்டில் உள்ளது.", + "cancelQueuedUpgradeRequired": "வரிசையில் உள்ள செய்திகளை ரத்து செய்ய Kilo CLI இன் புதிய பதிப்பு தேவை. Kilo CLI ஐ புதுப்பித்து மீண்டும் இணைக்கவும்." }, "remoteSession": { "failedToCreate": "தொலை அமர்வை உருவாக்க முடியவில்லை", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "செய்தி வரிசையில் உள்ளது", "userMessage": "பயனர் செய்தி", "assistantMessage": "உதவியாளர் செய்தி", - "longPressHint": "செய்தி விவரங்களுக்கு நீண்ட அழுத்தவும்" + "longPressHint": "செய்தி விவரங்களுக்கு நீண்ட அழுத்தவும்", + "cancelQueued": "செய்தியை ரத்து செய்யவும்", + "cancelQueuedAccessibility": "வரிசையில் உள்ள செய்தியை ரத்து செய்யவும்", + "restoreQueued": "மீட்டமைக்கவும்", + "restoreQueuedAccessibility": "ரத்து செய்த செய்தியைச் செய்தி உள்ளீட்டில் மீட்டமைக்கவும்" }, "olderMessages": { "arrived": "முந்தைய செய்திகள் ஏற்றப்பட்டன", diff --git a/apps/mobile/src/i18n/locales/te.json b/apps/mobile/src/i18n/locales/te.json index ae689279b2..22d003e2da 100644 --- a/apps/mobile/src/i18n/locales/te.json +++ b/apps/mobile/src/i18n/locales/te.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "మునుపటి పని ఇంకా పూర్తవుతోంది. దయచేసి కాసేపు వేచి ఉండండి.", "serviceUnavailable": "సేవ ప్రస్తుతం అందుబాటులో లేదు. దయచేసి మళ్లీ ప్రయత్నించండి.", "connectionTrouble": "కనెక్షన్ సమస్య. దయచేసి కాసేపటి తర్వాత మళ్లీ ప్రయత్నించండి.", - "modelUnavailable": "ఈ మోడల్ Cloud Agent కోసం అందుబాటులో లేదు. మరొక మోడల్ని ఎంచుకుని మళ్లీ ప్రయత్నించండి." + "modelUnavailable": "ఈ మోడల్ Cloud Agent కోసం అందుబాటులో లేదు. మరొక మోడల్ని ఎంచుకుని మళ్లీ ప్రయత్నించండి.", + "cancelQueuedFailed": "సందేశాన్ని రద్దు చేయలేకపోయాము.", + "cancelQueuedRestoreAvailable": "సందేశం రద్దు చేయబడింది. దాన్ని సందేశ ఇన్పుట్లోకి తిరిగి తీసుకురావడానికి పునరుద్ధరించండి నొక్కండి.", + "cancelQueuedRestored": "సందేశం రద్దు చేయబడింది. ప్రాంప్ట్ తిరిగి సందేశ ఇన్పుట్లో ఉంది.", + "cancelQueuedUpgradeRequired": "వరుసలో ఉన్న సందేశాలను రద్దు చేయడానికి కొత్త Kilo CLI సంస్కరణ అవసరం. Kilo CLIను నవీకరించి మళ్లీ కనెక్ట్ చేయండి." }, "remoteSession": { "failedToCreate": "రిమోట్ సెషన్‌ను సృష్టించడంలో విఫలమైంది", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "సందేశం క్యూలో ఉంది", "userMessage": "వినియోగదారు సందేశం", "assistantMessage": "సహాయక సందేశం", - "longPressHint": "సందేశ వివరాల కోసం ఎక్కువసేపు నొక్కండి" + "longPressHint": "సందేశ వివరాల కోసం ఎక్కువసేపు నొక్కండి", + "cancelQueued": "సందేశాన్ని రద్దు చేయండి", + "cancelQueuedAccessibility": "వరుసలో ఉన్న సందేశాన్ని రద్దు చేయండి", + "restoreQueued": "పునరుద్ధరించండి", + "restoreQueuedAccessibility": "రద్దు చేసిన సందేశాన్ని సందేశ ఇన్పుట్లో పునరుద్ధరించండి" }, "olderMessages": { "arrived": "మునుపటి సందేశాలు లోడ్ అయ్యాయి", diff --git a/apps/mobile/src/i18n/locales/th.json b/apps/mobile/src/i18n/locales/th.json index 8bbaebb769..8aa0110d0c 100644 --- a/apps/mobile/src/i18n/locales/th.json +++ b/apps/mobile/src/i18n/locales/th.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "งานก่อนหน้ายังไม่เสร็จสิ้น โปรดรอสักครู่", "serviceUnavailable": "ขณะนี้บริการไม่พร้อมใช้งาน โปรดลองอีกครั้ง", "connectionTrouble": "เกิดปัญหาการเชื่อมต่อ โปรดลองอีกครั้งในอีกสักครู่", - "modelUnavailable": "โมเดลนี้ไม่พร้อมใช้งานสำหรับ Cloud Agent โปรดเลือกโมเดลอื่นแล้วลองอีกครั้ง" + "modelUnavailable": "โมเดลนี้ไม่พร้อมใช้งานสำหรับ Cloud Agent โปรดเลือกโมเดลอื่นแล้วลองอีกครั้ง", + "cancelQueuedFailed": "ไม่สามารถยกเลิกข้อความได้", + "cancelQueuedRestoreAvailable": "ยกเลิกข้อความแล้ว แตะ กู้คืน เพื่อส่งข้อความกลับไปยังช่องป้อนข้อความ", + "cancelQueuedRestored": "ยกเลิกข้อความแล้ว พรอมต์กลับมาอยู่ในช่องป้อนข้อความแล้ว", + "cancelQueuedUpgradeRequired": "การยกเลิกข้อความที่อยู่ในคิวต้องใช้ Kilo CLI เวอร์ชันใหม่กว่า อัปเดต Kilo CLI แล้วเชื่อมต่ออีกครั้ง" }, "remoteSession": { "failedToCreate": "ไม่สามารถสร้าง remote session ได้", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "ข้อความอยู่ในคิว", "userMessage": "ข้อความผู้ใช้", "assistantMessage": "ข้อความผู้ช่วย", - "longPressHint": "กดค้างเพื่อดูรายละเอียดข้อความ" + "longPressHint": "กดค้างเพื่อดูรายละเอียดข้อความ", + "cancelQueued": "ยกเลิกข้อความ", + "cancelQueuedAccessibility": "ยกเลิกข้อความที่อยู่ในคิว", + "restoreQueued": "กู้คืน", + "restoreQueuedAccessibility": "กู้คืนข้อความที่ยกเลิกไปยังช่องป้อนข้อความ" }, "olderMessages": { "arrived": "โหลดข้อความก่อนหน้าแล้ว", diff --git a/apps/mobile/src/i18n/locales/tr.json b/apps/mobile/src/i18n/locales/tr.json index 51117703e9..fc63993b82 100644 --- a/apps/mobile/src/i18n/locales/tr.json +++ b/apps/mobile/src/i18n/locales/tr.json @@ -1688,7 +1688,11 @@ "previousTaskFinishing": "Önceki görev hâlâ tamamlanıyor. Lütfen bir dakika bekleyin.", "serviceUnavailable": "Hizmet şu anda kullanılamıyor. Lütfen tekrar deneyin.", "connectionTrouble": "Bağlantı sorunu. Lütfen birazdan tekrar deneyin.", - "modelUnavailable": "Bu model Cloud Agent için kullanılamıyor. Başka bir model seçip tekrar deneyin." + "modelUnavailable": "Bu model Cloud Agent için kullanılamıyor. Başka bir model seçip tekrar deneyin.", + "cancelQueuedFailed": "Mesaj iptal edilemedi.", + "cancelQueuedRestoreAvailable": "Mesaj iptal edildi. Mesaj giriş alanına geri getirmek için “Geri yükleyin” düğmesine dokunun.", + "cancelQueuedRestored": "Mesaj iptal edildi. İstem, mesaj giriş alanına geri getirildi.", + "cancelQueuedUpgradeRequired": "Kuyruktaki mesajları iptal etmek daha yeni bir Kilo CLI gerektirir. Kilo CLI'yı güncelleyin ve yeniden bağlanın." }, "remoteSession": { "failedToCreate": "Uzak oturum oluşturulamadı", @@ -2009,7 +2013,11 @@ "queuedAccessibility": "Mesaj kuyruğa alındı", "userMessage": "Kullanıcı mesajı", "assistantMessage": "Asistan mesajı", - "longPressHint": "Mesaj ayrıntıları için uzun basın" + "longPressHint": "Mesaj ayrıntıları için uzun basın", + "cancelQueued": "Mesajı iptal edin", + "cancelQueuedAccessibility": "Kuyruktaki mesajı iptal edin", + "restoreQueued": "Geri yükleyin", + "restoreQueuedAccessibility": "İptal edilen mesajı mesaj giriş alanına geri yükleyin" }, "olderMessages": { "arrived": "Önceki mesajlar yüklendi", diff --git a/apps/mobile/src/i18n/locales/uk.json b/apps/mobile/src/i18n/locales/uk.json index 28d242bc2a..0e4339e4fa 100644 --- a/apps/mobile/src/i18n/locales/uk.json +++ b/apps/mobile/src/i18n/locales/uk.json @@ -1704,7 +1704,11 @@ "previousTaskFinishing": "Попереднє завдання ще завершується. Зачекайте, будь ласка.", "serviceUnavailable": "Сервіс зараз недоступний. Спробуйте ще раз, будь ласка.", "connectionTrouble": "Проблеми зі з'єднанням. Спробуйте ще раз за мить.", - "modelUnavailable": "Ця модель недоступна для Cloud Agent. Виберіть іншу модель і спробуйте ще раз." + "modelUnavailable": "Ця модель недоступна для Cloud Agent. Виберіть іншу модель і спробуйте ще раз.", + "cancelQueuedFailed": "Не вдалося скасувати повідомлення.", + "cancelQueuedRestoreAvailable": "Повідомлення скасовано. Торкніться «Відновити», щоб повернути його в поле введення повідомлення.", + "cancelQueuedRestored": "Повідомлення скасовано. Запит знову в полі введення повідомлення.", + "cancelQueuedUpgradeRequired": "Скасування повідомлень у черзі потребує новішої версії Kilo CLI. Оновіть Kilo CLI та підключіться знову." }, "remoteSession": { "failedToCreate": "Не вдалося створити віддалений сеанс", @@ -2027,7 +2031,11 @@ "queuedAccessibility": "Повідомлення в черзі", "userMessage": "Повідомлення користувача", "assistantMessage": "Повідомлення асистента", - "longPressHint": "Довге натискання для відомостей про повідомлення" + "longPressHint": "Довге натискання для відомостей про повідомлення", + "cancelQueued": "Скасувати повідомлення", + "cancelQueuedAccessibility": "Скасувати повідомлення в черзі", + "restoreQueued": "Відновити", + "restoreQueuedAccessibility": "Відновити скасоване повідомлення в полі введення повідомлення" }, "olderMessages": { "arrived": "Раніші повідомлення завантажено", diff --git a/apps/mobile/src/i18n/locales/ur.json b/apps/mobile/src/i18n/locales/ur.json index 2e548d9231..4dca812920 100644 --- a/apps/mobile/src/i18n/locales/ur.json +++ b/apps/mobile/src/i18n/locales/ur.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "پچھلا کام ابھی مکمل ہو رہا ہے۔ براہ کرم ایک لمحہ انتظار کریں۔", "serviceUnavailable": "سروس اس وقت دستیاب نہیں ہے۔ براہ کرم دوبارہ کوشش کریں۔", "connectionTrouble": "کنکشن کا مسئلہ۔ براہ کرم ایک لمحے بعد دوبارہ کوشش کریں۔", - "modelUnavailable": "یہ ماڈل Cloud Agent کے لیے دستیاب نہیں ہے۔ کوئی اور ماڈل منتخب کر کے دوبارہ کوشش کریں۔" + "modelUnavailable": "یہ ماڈل Cloud Agent کے لیے دستیاب نہیں ہے۔ کوئی اور ماڈل منتخب کر کے دوبارہ کوشش کریں۔", + "cancelQueuedFailed": "پیغام منسوخ نہیں ہو سکا۔", + "cancelQueuedRestoreAvailable": "پیغام منسوخ ہو گیا۔ اسے پیغام لکھنے کے خانے میں واپس لانے کے لیے «بحال کریں» پر ٹیپ کریں۔", + "cancelQueuedRestored": "پیغام منسوخ ہو گیا۔ پرامپٹ پیغام لکھنے کے خانے میں واپس آ گیا ہے۔", + "cancelQueuedUpgradeRequired": "قطار میں موجود پیغامات منسوخ کرنے کے لیے نئے Kilo CLI کی ضرورت ہے۔ Kilo CLI اپ ڈیٹ کریں اور دوبارہ جڑیں۔" }, "remoteSession": { "failedToCreate": "ریموٹ سیشن بنانا ناکام", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "پیغام قطار میں ہے", "userMessage": "صارف کا پیغام", "assistantMessage": "معاون کا پیغام", - "longPressHint": "پیغام کی تفصیلات کے لیے دیر تک دبائیں" + "longPressHint": "پیغام کی تفصیلات کے لیے دیر تک دبائیں", + "cancelQueued": "پیغام منسوخ کریں", + "cancelQueuedAccessibility": "قطار میں موجود پیغام منسوخ کریں", + "restoreQueued": "بحال کریں", + "restoreQueuedAccessibility": "منسوخ شدہ پیغام کو پیغام لکھنے کے خانے میں بحال کریں" }, "olderMessages": { "arrived": "پہلے کے پیغامات لوڈ ہو گئے", diff --git a/apps/mobile/src/i18n/locales/uz.json b/apps/mobile/src/i18n/locales/uz.json index 5ddeb6cca2..f841d91ee0 100644 --- a/apps/mobile/src/i18n/locales/uz.json +++ b/apps/mobile/src/i18n/locales/uz.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Oldingi vazifa hali ham tugamoqda. Iltimos, bir oz kuting.", "serviceUnavailable": "Xizmat hozir mavjud emas. Iltimos, qayta urinib ko'ring.", "connectionTrouble": "Ulanish muammosi. Iltimos, bir ozdan keyin qayta urinib ko'ring.", - "modelUnavailable": "Bu model Cloud Agent uchun mavjud emas. Boshqa modelni tanlab, qayta urinib ko'ring." + "modelUnavailable": "Bu model Cloud Agent uchun mavjud emas. Boshqa modelni tanlab, qayta urinib ko'ring.", + "cancelQueuedFailed": "Xabarni bekor qilib bo'lmadi.", + "cancelQueuedRestoreAvailable": "Xabar bekor qilindi. Uni xabar kiritish maydoniga qaytarish uchun Tiklash tugmasini bosing.", + "cancelQueuedRestored": "Xabar bekor qilindi. Prompt xabar kiritish maydoniga qaytdi.", + "cancelQueuedUpgradeRequired": "Navbatdagi xabarlarni bekor qilish yangiroq Kilo CLI'ni talab qiladi. Kilo CLI'ni yangilang va qayta ulaning." }, "remoteSession": { "failedToCreate": "Masofaviy sessiya yaratilmadi", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Xabar navbatga qo'yildi", "userMessage": "Foydalanuvchi xabari", "assistantMessage": "Yordamchi xabari", - "longPressHint": "Xabar tafsilotlari uchun uzoq bosing" + "longPressHint": "Xabar tafsilotlari uchun uzoq bosing", + "cancelQueued": "Xabarni bekor qilish", + "cancelQueuedAccessibility": "Navbatdagi xabarni bekor qilish", + "restoreQueued": "Tiklash", + "restoreQueuedAccessibility": "Bekor qilingan xabarni xabar kiritish maydoniga tiklash" }, "olderMessages": { "arrived": "Oldingi xabarlar yuklandi", diff --git a/apps/mobile/src/i18n/locales/vi.json b/apps/mobile/src/i18n/locales/vi.json index c0d5256c20..ad613657b5 100644 --- a/apps/mobile/src/i18n/locales/vi.json +++ b/apps/mobile/src/i18n/locales/vi.json @@ -1688,7 +1688,11 @@ "previousTaskFinishing": "Tác vụ trước vẫn đang hoàn tất. Vui lòng chờ một chút.", "serviceUnavailable": "Dịch vụ hiện không khả dụng. Vui lòng thử lại.", "connectionTrouble": "Sự cố kết nối. Vui lòng thử lại sau giây lát.", - "modelUnavailable": "Mô hình này không khả dụng cho Cloud Agent. Vui lòng chọn mô hình khác và thử lại." + "modelUnavailable": "Mô hình này không khả dụng cho Cloud Agent. Vui lòng chọn mô hình khác và thử lại.", + "cancelQueuedFailed": "Không thể hủy tin nhắn.", + "cancelQueuedRestoreAvailable": "Đã hủy tin nhắn. Chạm vào Khôi phục để đưa tin nhắn trở lại ô nhập tin nhắn.", + "cancelQueuedRestored": "Đã hủy tin nhắn. Lời nhắc đã trở lại ô nhập tin nhắn.", + "cancelQueuedUpgradeRequired": "Việc hủy tin nhắn đang chờ yêu cầu Kilo CLI mới hơn. Hãy cập nhật Kilo CLI và kết nối lại." }, "remoteSession": { "failedToCreate": "Không thể tạo phiên từ xa", @@ -2009,7 +2013,11 @@ "queuedAccessibility": "Tin nhắn đã xếp hàng đợi", "userMessage": "Tin nhắn người dùng", "assistantMessage": "Tin nhắn trợ lý", - "longPressHint": "Nhấn giữ để xem chi tiết tin nhắn" + "longPressHint": "Nhấn giữ để xem chi tiết tin nhắn", + "cancelQueued": "Hủy tin nhắn", + "cancelQueuedAccessibility": "Hủy tin nhắn đang chờ", + "restoreQueued": "Khôi phục", + "restoreQueuedAccessibility": "Khôi phục tin nhắn đã hủy vào ô nhập tin nhắn" }, "olderMessages": { "arrived": "Đã tải các tin nhắn trước đó", diff --git a/apps/mobile/src/i18n/locales/yo.json b/apps/mobile/src/i18n/locales/yo.json index 8b38a720b4..e5620dae08 100644 --- a/apps/mobile/src/i18n/locales/yo.json +++ b/apps/mobile/src/i18n/locales/yo.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Iṣẹ́ ti o ti kọja ṣì ń pari. Jọwọ duro diẹ.", "serviceUnavailable": "Iṣẹ naa ko si lọwọlọwọ. Jọwọ tun gbiyanju.", "connectionTrouble": "Iṣoro asopọ. Jọwọ tun gbiyanju ni iṣẹju diẹ.", - "modelUnavailable": "Awoṣe yii ko si fun Cloud Agent. Jọwọ yan awoṣe miiran ki o tun gbiyanju." + "modelUnavailable": "Awoṣe yii ko si fun Cloud Agent. Jọwọ yan awoṣe miiran ki o tun gbiyanju.", + "cancelQueuedFailed": "Ko le fagile ifiranṣẹ naa.", + "cancelQueuedRestoreAvailable": "A ti fagile ifiranṣẹ. Tẹ Mú pada lati da a pada si aaye kikọ ifiranṣẹ.", + "cancelQueuedRestored": "A ti fagile ifiranṣẹ. Iyanilenu ti pada si aaye kikọ ifiranṣẹ.", + "cancelQueuedUpgradeRequired": "Fifagile awọn ifiranṣẹ ti o wa ni isinyi nilo Kilo CLI tuntun kan. Ṣe imudojuiwọn Kilo CLI ki o si tun so." }, "remoteSession": { "failedToCreate": "Kuna lati ṣẹda akoko jijin", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Ifiranṣẹ n in isinyi", "userMessage": "Ifiranṣẹ olumulo", "assistantMessage": "Ifiranṣẹ oluranlowo", - "longPressHint": "Tẹ pẹ lati wo awọn alaye ifiranṣẹ" + "longPressHint": "Tẹ pẹ lati wo awọn alaye ifiranṣẹ", + "cancelQueued": "Fagile ifiranṣẹ", + "cancelQueuedAccessibility": "Fagile ifiranṣẹ ti o wa ni isinyi", + "restoreQueued": "Mú pada", + "restoreQueuedAccessibility": "Mú ifiranṣẹ ti a fagile pada si aaye kikọ ifiranṣẹ" }, "olderMessages": { "arrived": "Awọn ifiranṣẹ tẹlẹ ti ṣagbewọ", diff --git a/apps/mobile/src/i18n/locales/zh-Hans.json b/apps/mobile/src/i18n/locales/zh-Hans.json index d0e4579447..f1eb2679cc 100644 --- a/apps/mobile/src/i18n/locales/zh-Hans.json +++ b/apps/mobile/src/i18n/locales/zh-Hans.json @@ -1688,7 +1688,11 @@ "previousTaskFinishing": "上一个任务仍在进行中。请稍等片刻。", "serviceUnavailable": "服务当前不可用。请重试。", "connectionTrouble": "连接出现问题。请稍后重试。", - "modelUnavailable": "此模型不适用于 Cloud Agent。请选择其他模型重试。" + "modelUnavailable": "此模型不适用于 Cloud Agent。请选择其他模型重试。", + "cancelQueuedFailed": "无法取消消息。", + "cancelQueuedRestoreAvailable": "消息已取消。点击“恢复”将其放回编辑器。", + "cancelQueuedRestored": "消息已取消。提示词已放回编辑器。", + "cancelQueuedUpgradeRequired": "取消已排队的消息需要更新的 Kilo CLI。请更新 Kilo CLI 并重新连接。" }, "remoteSession": { "failedToCreate": "创建远程会话失败", @@ -2009,7 +2013,11 @@ "queuedAccessibility": "消息已排队", "userMessage": "用户消息", "assistantMessage": "助手消息", - "longPressHint": "长按查看消息详情" + "longPressHint": "长按查看消息详情", + "cancelQueued": "取消消息", + "cancelQueuedAccessibility": "取消已排队的消息", + "restoreQueued": "恢复", + "restoreQueuedAccessibility": "将已取消的消息恢复到编辑器" }, "olderMessages": { "arrived": "已加载较早的消息", diff --git a/apps/mobile/src/i18n/locales/zh-Hant.json b/apps/mobile/src/i18n/locales/zh-Hant.json index 86c7c8a9cd..677eca0e66 100644 --- a/apps/mobile/src/i18n/locales/zh-Hant.json +++ b/apps/mobile/src/i18n/locales/zh-Hant.json @@ -1688,7 +1688,11 @@ "previousTaskFinishing": "上一個工作仍在進行中。請稍等片刻。", "serviceUnavailable": "服務目前無法使用。請重試。", "connectionTrouble": "連線發生問題。請稍後重試。", - "modelUnavailable": "此模型不適用於 Cloud Agent。請選擇其他模型再試一次。" + "modelUnavailable": "此模型不適用於 Cloud Agent。請選擇其他模型再試一次。", + "cancelQueuedFailed": "無法取消訊息。", + "cancelQueuedRestoreAvailable": "訊息已取消。請點按「還原」將其放回撰寫器。", + "cancelQueuedRestored": "訊息已取消。提示已放回撰寫器。", + "cancelQueuedUpgradeRequired": "取消已排入佇列的訊息需要更新的 Kilo CLI。請更新 Kilo CLI 並重新連接。" }, "remoteSession": { "failedToCreate": "無法建立遠端工作階段", @@ -2009,7 +2013,11 @@ "queuedAccessibility": "訊息已排入佇列", "userMessage": "使用者訊息", "assistantMessage": "助理訊息", - "longPressHint": "長按以檢視訊息詳細資料" + "longPressHint": "長按以檢視訊息詳細資料", + "cancelQueued": "取消訊息", + "cancelQueuedAccessibility": "取消已排入佇列的訊息", + "restoreQueued": "還原", + "restoreQueuedAccessibility": "將已取消的訊息還原至撰寫器" }, "olderMessages": { "arrived": "已載入較早的訊息", diff --git a/apps/mobile/src/i18n/locales/zu.json b/apps/mobile/src/i18n/locales/zu.json index 3e6bdd0d18..5503373319 100644 --- a/apps/mobile/src/i18n/locales/zu.json +++ b/apps/mobile/src/i18n/locales/zu.json @@ -2142,7 +2142,11 @@ "previousTaskFinishing": "Umsebenzi wangaphambilini usaqhubeka. Uyacelwa ukuthi ulinde isikhashana.", "serviceUnavailable": "Inkonzo ayitholakali njengamanje. Uyacelwa ukuthi uzame futhi.", "connectionTrouble": "Inkinga yokuxhumana. Uyacelwa ukuthi uzame futhi emuva kwesikhashana.", - "modelUnavailable": "Le modeli ayitholakali ku-Cloud Agent. Khetha enye bese uzama futhi." + "modelUnavailable": "Le modeli ayitholakali ku-Cloud Agent. Khetha enye bese uzama futhi.", + "cancelQueuedFailed": "Akukwazanga ukukhansela umlayezo.", + "cancelQueuedRestoreAvailable": "Umlayezo ukhanseliwe. Thepha u-Buyisela ukuze uwubuyisele ku-composer.", + "cancelQueuedRestored": "Umlayezo ukhanseliwe. Isicelo osibhalile sesibuyile ku-composer.", + "cancelQueuedUpgradeRequired": "Ukukhansela imilayezo efakiwe kuqoqo kudinga i-Kilo CLI entsha. Buyekeza i-Kilo CLI bese uxhumana futhi." }, "remoteSession": { "failedToCreate": "Kwehlulekile ukudala iseshini ekude", @@ -2463,7 +2467,11 @@ "queuedAccessibility": "Umlayezo ufakiwe kuqoqo", "userMessage": "Umlayezo womsebenzisi", "assistantMessage": "Umlayezo womsizi", - "longPressHint": "Cindezela isikhathi eside ukuze ubone imininingwane yomlayezo" + "longPressHint": "Cindezela isikhathi eside ukuze ubone imininingwane yomlayezo", + "cancelQueued": "Khansela umlayezo", + "cancelQueuedAccessibility": "Khansela umlayezo ofakiwe kuqoqo", + "restoreQueued": "Buyisela", + "restoreQueuedAccessibility": "Buyisela umlayezo okhanseliwe ku-composer" }, "olderMessages": { "arrived": "Imilayezo yangaphambili ilayishiwe", diff --git a/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts b/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts index 91bdd490d1..1110bac549 100644 --- a/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts +++ b/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.test.ts @@ -410,6 +410,15 @@ async function settle(): Promise { await Promise.resolve(); } +/** Runs `uploadPending` inside `act` and returns the settled, non-undefined result. */ +async function uploadPendingResult(): Promise>> { + let result: Awaited> | undefined = undefined; + await act(async () => { + result = await hookApi().uploadPending(); + }); + return result as unknown as Awaited>; +} + describe('addCandidates uploads documents at selection (Step 2)', () => { beforeEach(() => { hoisted.uploadOne.mockReset(); @@ -532,6 +541,212 @@ describe('uploadPending', () => { }); renderer.unmount(); }); + + it('retries restored chips under the original upload path after a failed optimistic send', async () => { + const renderer = await mountHook(); + await addDocument(); + await act(async () => { + resolveUpload?.({ key: 'org/2026/08/uuid/doc.pdf' }); + await settle(); + }); + expect(hookApi().attachments[0]?.status).toBe('uploaded'); + + const original = await uploadPendingResult(); + expect(original).toEqual(expect.objectContaining({ ok: true })); + expect(original.ok).toBe(true); + if (!original.ok) { + throw new Error('expected upload to succeed'); + } + const originalPath = original.wire?.path; + expect(originalPath).toBeDefined(); + + // The optimistic clear must keep the path; a transport failure then + // restores the same chips so the retry reuses the original upload path. + const chips = hookApi().attachments; + await act(async () => { + hookApi().clearOptimistic(); + await settle(); + }); + expect(hookApi().attachments).toHaveLength(0); + await act(async () => { + hookApi().restoreChips(chips); + await settle(); + }); + + const retry = await uploadPendingResult(); + expect(retry).toEqual(expect.objectContaining({ ok: true })); + expect(retry.ok).toBe(true); + if (!retry.ok) { + throw new Error('expected retry upload to succeed'); + } + expect(retry.wire?.path).toBe(originalPath); + expect(retry.wire?.files).toEqual(['doc.pdf']); + renderer.unmount(); + }); + + it('rotates the upload path and submission messageUuid after commitSent', async () => { + const renderer = await mountHook(); + await addDocument(); + await act(async () => { + resolveUpload?.({ key: 'org/2026/08/uuid/doc.pdf' }); + await settle(); + }); + expect(hookApi().attachments[0]?.status).toBe('uploaded'); + + const first = await uploadPendingResult(); + expect(first).toEqual(expect.objectContaining({ ok: true })); + expect(first.ok).toBe(true); + if (!first.ok) { + throw new Error('expected first upload to succeed'); + } + const firstPath = first.wire?.path; + const firstUuid = first.submission?.messageUuid; + expect(firstPath).toBeDefined(); + expect(firstUuid).toBeDefined(); + + // A successful send rotates both refs: the next message must presign and + // submit under fresh UUIDs instead of reusing the previous message's. + await act(async () => { + hookApi().commitSent(); + await settle(); + }); + + const second = await uploadPendingResult(); + expect(second).toEqual(expect.objectContaining({ ok: true })); + expect(second.ok).toBe(true); + if (!second.ok) { + throw new Error('expected second upload to succeed'); + } + expect(second.wire?.path).toBeDefined(); + expect(second.wire?.path).not.toBe(firstPath); + expect(second.submission?.messageUuid).not.toBe(firstUuid); + renderer.unmount(); + }); +}); + +describe('restoreFileParts — cancel/restore re-send admission', () => { + beforeEach(() => { + hoisted.uploadOne.mockReset(); + hoisted.releasePendingUploads.mockClear(); + hoisted.announceForA11y.mockReset(); + hoisted.announcingToastError.mockReset(); + hoisted.measureLocalSize.mockReset(); + hoisted.measureLocalSize.mockResolvedValue(1024); + resolveUpload = undefined; + rejectUpload = undefined; + }); + + it('re-admits a restored file chip on the next send with its remote key', async () => { + const renderer = await mountHook(); + const remoteName = '8f14e45f-ceea-4b2a-8c6d-1a2b3c4d5e6f.pdf'; + await act(async () => { + hookApi().restoreFileParts([ + { + filename: remoteName, + mime: 'application/pdf', + url: `file:///tmp/attachments/session-1/user-1/msg-uuid-1/${remoteName}`, + }, + ]); + await settle(); + }); + + const chip = hookApi().attachments[0]; + expect(chip?.status).toBe('uploaded'); + expect(chip?.remoteKey).toBe(`user-1/cloud-agent/msg-uuid-1/${remoteName}`); + expect(chip?.remoteFilename).toBe(remoteName); + + const result = await uploadPendingResult(); + + // The restored chip must appear in the wire and submission, not be + // silently dropped as a "ready" chip that sends nothing. + expect(result).toEqual(expect.objectContaining({ ok: true })); + expect(result.ok).toBe(true); + if (!result.ok) { + throw new Error('expected restored-chip upload to succeed'); + } + expect(result.wire?.files).toEqual([remoteName]); + // The restored chip's objects live under the canceled message's UUID, so + // the wire path must be that UUID, not the rotated current path. + expect(result.wire?.path).toBe('msg-uuid-1'); + expect(result.submission?.files.map(file => file.remoteName)).toEqual([remoteName]); + renderer.unmount(); + }); + + it('re-admits an optimistic cloud-agent file part on the next send', async () => { + const renderer = await mountHook(); + const remoteName = '87654321-4321-4321-8321-cba987654321.md'; + await act(async () => { + hookApi().restoreFileParts([ + { + filename: remoteName, + mime: '', + url: `cloud-agent://12345678-1234-4234-9234-123456789abc/${remoteName}`, + }, + ]); + await settle(); + }); + + const chip = hookApi().attachments[0]; + expect(chip?.status).toBe('uploaded'); + expect(chip?.remoteFilename).toBe(remoteName); + + const result = await uploadPendingResult(); + expect(result).toEqual(expect.objectContaining({ ok: true })); + expect(result.ok).toBe(true); + if (!result.ok) { + throw new Error('expected restored-chip upload to succeed'); + } + expect(result.wire?.files).toEqual([remoteName]); + // The wire path is the original upload messageUuid the optimistic part encoded. + expect(result.wire?.path).toBe('12345678-1234-4234-9234-123456789abc'); + expect(result.submission?.files.map(file => file.remoteName)).toEqual([remoteName]); + renderer.unmount(); + }); + + it('recovers messageUuid and remoteKey from a presigned https URL', async () => { + const renderer = await mountHook(); + const remoteName = '87654321-4321-4321-8321-cba987654321.md'; + const messageUuid = '12345678-1234-4234-9234-123456789abc'; + await act(async () => { + hookApi().restoreFileParts([ + { + filename: 'notes.md', + mime: 'text/markdown', + url: `https://r2.example.com/attachments/user-1/cloud-agent/${messageUuid}/${remoteName}?X-Amz-Expires=3600&X-Amz-Signature=deadbeef`, + }, + ]); + await settle(); + }); + + const chip = hookApi().attachments[0]; + expect(chip?.status).toBe('uploaded'); + // The remote filename comes from the key path, not the picker filename. + expect(chip?.remoteFilename).toBe(remoteName); + // The admission marker is the credential-free key, not the signed URL. + expect(chip?.remoteKey).toBe(`cloud-agent/${messageUuid}/${remoteName}`); + + const result = await uploadPendingResult(); + expect(result).toEqual(expect.objectContaining({ ok: true })); + expect(result.ok).toBe(true); + if (!result.ok) { + throw new Error('expected restored-chip upload to succeed'); + } + expect(result.wire?.files).toEqual([remoteName]); + // The re-send must reuse the canceled message's path, not the rotated one. + expect(result.wire?.path).toBe(messageUuid); + expect(result.submission?.files.map(file => file.remoteName)).toEqual([remoteName]); + renderer.unmount(); + }); + + it('skips a part with no URL (not recoverable)', async () => { + const renderer = await mountHook(); + await act(async () => { + hookApi().restoreFileParts([{ filename: 'ghost.pdf', mime: 'application/pdf', url: '' }]); + await settle(); + }); + expect(hookApi().attachments).toHaveLength(0); + renderer.unmount(); + }); }); describe('useAgentAttachmentUpload — attachment reorder (attachment reorder)', () => { diff --git a/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts b/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts index 68efbe19b0..d8cf99c5ed 100644 --- a/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts +++ b/apps/mobile/src/lib/agent-attachments/use-agent-attachment-upload.ts @@ -16,6 +16,7 @@ import { classifyAttachment, describeClassificationFailure, mimeForExtension, + normalizeAttachmentExtension, } from '@/lib/agent-attachments/validate'; import { type AgentAttachment, @@ -93,6 +94,94 @@ async function runBestEffort(task: () => Promise): Promise { } } +// A cloud-agent sandbox URL encodes the R2 key of a stored attachment: +// file:///tmp/attachments//// +// whose R2 key is `/cloud-agent//` (see +// services/cloud-agent-next/src/utils/attachment-download.ts). The last +// segment is the wire's remote filename. +const RESTORED_ATTACHMENT_URL = /^file:\/\/\/tmp\/attachments\/([^/]+)\/([^/]+)\/([^/]+)\/([^/]+)$/; + +// The optimistic cloud-agent file part's `url` encodes the original upload +// path and remote filename as `cloud-agent:///`. The +// SDK has the upload `attachments.path` but not the userId, so the sandbox +// form above cannot be satisfied; this form is what +// `buildOptimisticFileParts` can produce and lets a cancel-restore recover +// both the filename and the messageUuid (the re-send wire's `path`). +const CLOUD_AGENT_RESTORE_URL = /^cloud-agent:\/\/([^/]+)\/([^/]+)$/; + +// A presigned R2 GET URL (remote attachment parts) carries the object key in +// its path: `///cloud-agent//` +// (see services/cloud-agent-next/src/utils/attachment-download.ts). Recover +// the message UUID and the remote filename from that path so a cancel-restore +// re-sends under the original upload path instead of the rotated one, and so +// the admission marker never carries the signed URL's query credentials. +const SIGNED_CLOUD_AGENT_URL = /\/cloud-agent\/([^/]+)\/([^/]+)$/; + +type ResolvedRestoredAttachmentReference = { + remoteKey: string; + remoteFilename: string; + messageUuid?: string; +}; + +/** + * Derive the R2 object reference a restored file part points at. Restored + * parts are already uploaded and linked, so the chip must be re-admitted on + * the next send without a false "ready" chip that sends nothing. The remote + * key is only an admission marker for an already-linked object (a release of a + * non-pending key is a no-op server-side), so it never needs to be the raw + * signed URL; the message UUID and remote filename are recovered from the + * key's `/cloud-agent//` path segment instead. + */ +function resolveRestoredAttachmentReference(input: { + filename?: string; + url: string; +}): ResolvedRestoredAttachmentReference { + const sandbox = RESTORED_ATTACHMENT_URL.exec(input.url); + if (sandbox) { + const userId = sandbox[2]; + const messageUuid = sandbox[3]; + const filename = sandbox[4]; + if (userId !== undefined && messageUuid !== undefined && filename !== undefined) { + return { + remoteKey: `${userId}/cloud-agent/${messageUuid}/${filename}`, + remoteFilename: filename, + messageUuid, + }; + } + } + const cloudRestore = CLOUD_AGENT_RESTORE_URL.exec(input.url); + if (cloudRestore) { + const messageUuid = cloudRestore[1]; + const filename = cloudRestore[2]; + if (messageUuid !== undefined && filename !== undefined) { + return { + remoteKey: input.url, + remoteFilename: filename, + messageUuid, + }; + } + } + const withoutQueryHash = input.url.split(/[?#]/)[0] ?? input.url; + const signed = SIGNED_CLOUD_AGENT_URL.exec(withoutQueryHash); + if (signed) { + const messageUuid = signed[1]; + const filename = signed[2]; + if (messageUuid !== undefined && filename !== undefined) { + return { + remoteKey: `cloud-agent/${messageUuid}/${filename}`, + remoteFilename: filename, + messageUuid, + }; + } + } + const basename = /[^/]+$/.exec(withoutQueryHash)?.[0]; + const extension = normalizeAttachmentExtension(basename ?? input.filename ?? 'file'); + const remoteFilename = basename + ? normalizeFilename(basename, extension) + : normalizeFilename(input.filename ?? 'file', extension); + return { remoteKey: input.url, remoteFilename }; +} + type UseAgentAttachmentUploadOptions = { organizationId?: string; }; @@ -117,6 +206,14 @@ type UseAgentAttachmentUploadReturn = { reset: () => void; /** Release every admitted-but-unsent key (leave/abandon). Never blocks or throws. */ releaseUnclaimedUploads: () => void; + /** Re-attach recoverable canceled-message file parts as already-uploaded chips. */ + restoreFileParts: (parts: readonly { filename?: string; mime: string; url: string }[]) => void; + /** Clear chips for an optimistic send without deleting their local files. */ + clearOptimistic: () => void; + /** Re-add chips after a failed optimistic send. */ + restoreChips: (chips: readonly AgentAttachment[]) => void; + /** Rotate the upload path and submission messageUuid after a successful send. */ + commitSent: () => void; isUploading: boolean; hasFailedAttachments: boolean; hasUnclaimedAttachments: boolean; @@ -647,6 +744,91 @@ export function useAgentAttachmentUpload( const hasFailedAttachments = hasAnyFailedAttachment(attachments); const hasUnclaimedAttachments = hasAnyUnclaimedAttachment(attachments); + // Re-attach the recoverable file parts of a canceled queued message as + // already-uploaded chips (their remote objects already exist). A file part + // without a URL is not recoverable and is skipped. + const restoreFileParts = useCallback( + (parts: readonly { filename?: string; mime: string; url: string }[]) => { + const recoverable = parts.filter(part => part.url !== ''); + if (recoverable.length === 0) { + return; + } + // All restored parts come from one canceled message, so they share one + // message UUID. Point the current upload path at that UUID so the next + // send's wire `{path, files}` addresses the existing R2 objects instead + // of the rotated path (`commitSent` advanced it after the prior send). + const firstPart = recoverable[0]; + if (firstPart === undefined) { + return; + } + const firstReference = resolveRestoredAttachmentReference(firstPart); + if (firstReference.messageUuid !== undefined) { + pathRef.current = firstReference.messageUuid; + messageUuidRef.current = firstReference.messageUuid; + } + const additions: AgentAttachment[] = recoverable.map(part => { + const extension = normalizeAttachmentExtension(part.filename ?? 'file'); + const filename = normalizeFilename(part.filename ?? 'file', extension); + const kind = part.mime.startsWith('image/') ? 'image' : 'document'; + const { remoteKey, remoteFilename } = resolveRestoredAttachmentReference(part); + return { + id: Crypto.randomUUID(), + filename, + remoteFilename, + remoteKey, + kind, + extension, + mimeType: + part.mime !== '' + ? (part.mime as AgentAttachment['mimeType']) + : mimeForExtension(extension), + size: 0, + localUri: part.url, + localFileOwned: false, + status: 'uploaded', + progress: null, + }; + }); + commitAttachments(current => [...current, ...additions]); + }, + [commitAttachments] + ); + + // Optimistic-send chip clear: like `reset` but keeps the local cache files + // so a transport failure can restore the same chips. Orphaned files on a + // successful send are reclaimed by the temp-file reaper. The path is NOT + // rotated here: the files were already uploaded under the current path, so a + // failed-send restore must retry them under that same path. A genuinely new + // message starts only on `reset`. + const clearOptimistic = useCallback(() => { + generationRef.current += 1; + for (const id of liveIdsRef.current) { + cancelUpload(id); + } + liveIdsRef.current.clear(); + commitAttachments(() => []); + }, [cancelUpload, commitAttachments]); + + // Re-add chips after a failed optimistic send (restore recoverable attachments). + const restoreChips = useCallback( + (chips: readonly AgentAttachment[]) => { + if (chips.length === 0) { + return; + } + commitAttachments(current => [...current, ...chips]); + }, + [commitAttachments] + ); + + // Rotate the upload path and submission messageUuid after a successful send. + // `clearOptimistic` deliberately does NOT rotate (a failed-send restore must + // retry under the original path); only a completed send starts a genuinely + // new message, so the next upload presigns fresh UUIDs. + const commitSent = useCallback(() => { + pathRef.current = Crypto.randomUUID(); + messageUuidRef.current = Crypto.randomUUID(); + }, []); + return useMemo( () => ({ attachments, @@ -657,6 +839,10 @@ export function useAgentAttachmentUpload( reorderAttachments, reset, releaseUnclaimedUploads, + restoreFileParts, + clearOptimistic, + restoreChips, + commitSent, isUploading, hasFailedAttachments, hasUnclaimedAttachments, @@ -671,6 +857,10 @@ export function useAgentAttachmentUpload( reorderAttachments, reset, releaseUnclaimedUploads, + restoreFileParts, + clearOptimistic, + restoreChips, + commitSent, isUploading, hasFailedAttachments, hasUnclaimedAttachments, diff --git a/packages/app-shared/src/opencode.gen.ts b/packages/app-shared/src/opencode.gen.ts index 38f85f1735..2d406478f6 100644 --- a/packages/app-shared/src/opencode.gen.ts +++ b/packages/app-shared/src/opencode.gen.ts @@ -269,6 +269,8 @@ export type FilePart = { filename?: string; url: string; source?: FilePartSource; + /** Kilo extension: marks a local placeholder part to drop when the authoritative file part arrives. */ + synthetic?: boolean; }; export type ToolStatePending = { diff --git a/packages/cloud-agent-sdk/src/cli-live-transport.test.ts b/packages/cloud-agent-sdk/src/cli-live-transport.test.ts index 7d84d5574b..ecb17447c2 100644 --- a/packages/cloud-agent-sdk/src/cli-live-transport.test.ts +++ b/packages/cloud-agent-sdk/src/cli-live-transport.test.ts @@ -1112,7 +1112,9 @@ describe('CliLiveTransport unified user web connection', () => { await Promise.resolve(); jest.mocked(connection.sendCommand).mockClear(); - await transport.dropQueuedMessage?.('msg-drop-1'); + await expect(transport.dropQueuedMessage?.('msg-drop-1')).resolves.toEqual({ + dropped: true, + }); expect(connection.sendCommand).toHaveBeenCalledTimes(1); expect(connection.sendCommand).toHaveBeenCalledWith( diff --git a/packages/cloud-agent-sdk/src/cli-live-transport.ts b/packages/cloud-agent-sdk/src/cli-live-transport.ts index 6dabef97ba..d49bbed569 100644 --- a/packages/cloud-agent-sdk/src/cli-live-transport.ts +++ b/packages/cloud-agent-sdk/src/cli-live-transport.ts @@ -1004,8 +1004,13 @@ function createCliLiveTransport(config: CliLiveTransportConfig): TransportFactor }); }, interrupt: () => sendCommand('interrupt', {}), - dropQueuedMessage: messageId => - sendCommand('drop_queued_message', { protocolVersion: 1, messageID: messageId }), + dropQueuedMessage: async messageId => { + // The kilocode handler ACKs success only when the queue actually + // dropped it (it throws "message not queued" otherwise), so a resolved + // `drop_queued_message` command means the queued message was dropped. + await sendCommand('drop_queued_message', { protocolVersion: 1, messageID: messageId }); + return { dropped: true }; + }, answer: payload => sendCommand('question_reply', { requestID: payload.requestId, diff --git a/packages/cloud-agent-sdk/src/cloud-agent-transport.test.ts b/packages/cloud-agent-sdk/src/cloud-agent-transport.test.ts index 7847cf7a55..c398f2d697 100644 --- a/packages/cloud-agent-sdk/src/cloud-agent-transport.test.ts +++ b/packages/cloud-agent-sdk/src/cloud-agent-transport.test.ts @@ -536,7 +536,9 @@ describe('CloudAgentTransport command delegation', () => { const api = createMockApi(); const { transport } = createTransportWithSinks(undefined, undefined, api); - await transport.dropQueuedMessage!('msg-queued-1'); + await expect(transport.dropQueuedMessage!('msg-queued-1')).resolves.toEqual({ + dropped: true, + }); expect(api.cancelQueuedMessage).toHaveBeenCalledWith({ sessionId: 'ses-1', diff --git a/packages/cloud-agent-sdk/src/session-manager.test.ts b/packages/cloud-agent-sdk/src/session-manager.test.ts index 66a285eab4..6cacfc1746 100644 --- a/packages/cloud-agent-sdk/src/session-manager.test.ts +++ b/packages/cloud-agent-sdk/src/session-manager.test.ts @@ -23,10 +23,12 @@ import type { CloudAgentSessionDismissSuggestionInput, } from './session'; import type { JotaiSessionStorage } from './storage/jotai'; +import { createChatProcessor } from './chat-processor'; import type { AssistantMessage, UserMessage, TextPart } from '@kilocode/app-shared/opencode'; import { kiloId, cloudAgentId, stubUserMessage, stubTextPart, makeSnapshot } from './test-helpers'; import type { CloudStatus, + FilePart, MessageDeliveryState, ResolvedSession, SessionActivity, @@ -1670,7 +1672,7 @@ describe('createSessionManager', () => { }); }); - it('does not write to storage before cloud.message.queued arrives', async () => { + it('inserts one optimistic row before cloud.message.queued arrives', async () => { const config = createMockConfig(); const mgr = createSessionManager(config); @@ -1681,7 +1683,7 @@ describe('createSessionManager', () => { payload: { type: 'prompt', prompt: 'Hello', mode: 'code', model: 'claude-3-5-sonnet' }, }); - expect(atomValue(config.store, mgr.atoms.messagesList)).toHaveLength(0); + expect(atomValue(config.store, mgr.atoms.messagesList)).toHaveLength(1); expect(mockSession.send).toHaveBeenCalledWith({ messageId: expect.stringMatching(/^msg_/), payload: { @@ -1694,6 +1696,61 @@ describe('createSessionManager', () => { }); }); + it('queued event does not duplicate or overwrite the optimistic row', async () => { + const config = createMockConfig(); + const mgr = createSessionManager(config); + + await mgr.switchSession(kiloId('ses-1')); + + mockSession.send.mockImplementation(() => new Promise(() => {})); + void mgr.send({ + payload: { type: 'prompt', prompt: 'Hello', mode: 'code', model: 'claude-3-5-sonnet' }, + }); + + const storage = mockSession.storage; + expect(storage).not.toBeNull(); + const [messageId] = storage!.getMessageIds(); + expect(messageId).toBeDefined(); + + // The server's cloud.message.queued synthesizer must see the optimistic + // row already present and no-op: the prompt text must stay the optimistic + // one, not the server echo. + createChatProcessor(storage!).synthesizeQueuedUserMessage({ + messageId: messageId!, + sessionId: kiloId('ses-1'), + content: 'server echo', + }); + + expect(storage!.getMessageIds()).toHaveLength(1); + expect((storage!.getParts(messageId!)[0] as TextPart).text).toBe('Hello'); + }); + + it('deletes the optimistic row on transport failure', async () => { + const config = createMockConfig(); + const mgr = createSessionManager(config); + + await mgr.switchSession(kiloId('ses-1')); + + let rejectSend: (error: Error) => void; + mockSession.send.mockImplementation( + () => + new Promise((_resolve, reject) => { + rejectSend = reject; + }) + ); + const sendPromise = mgr.send({ + payload: { type: 'prompt', prompt: 'Hello', mode: 'code', model: 'claude-3-5-sonnet' }, + }); + + // The optimistic row is present while the send is in flight… + expect(atomValue(config.store, mgr.atoms.messagesList)).toHaveLength(1); + + // …and is deleted once the transport rejects. + rejectSend!(new Error('ECONNREFUSED')); + await sendPromise; + expect(atomValue(config.store, mgr.atoms.messagesList)).toHaveLength(0); + }); + it('uses cloud-agent model override on send and clears it on switchSession', async () => { const config = createMockConfig(); const mgr = createSessionManager(config); @@ -1795,7 +1852,7 @@ describe('createSessionManager', () => { }); }); - it('does not persist any optimistic message for remote sessions', async () => { + it('persists one optimistic row for remote sessions', async () => { const config = createMockConfig(); const mgr = createSessionManager(config); @@ -1812,7 +1869,130 @@ describe('createSessionManager', () => { payload: { type: 'prompt', prompt: 'Hello', mode: 'code' }, images: undefined, }); - expect(atomValue(config.store, mgr.atoms.messagesList)).toHaveLength(0); + expect(atomValue(config.store, mgr.atoms.messagesList)).toHaveLength(1); + }); + + it('remote retarget keeps one row when the CLI assigns its own id', async () => { + const config = createMockConfig(); + const mgr = createSessionManager(config); + + await mgr.switchSession(kiloId('ses-1')); + mockSessionCallbacks.onResolved?.({ type: 'remote', kiloSessionId: kiloId('ses-1') }); + // The retarget runs in the live phase, after the snapshot replay ends. + mockSessionCallbacks.onReplayComplete?.(); + + mockSession.send.mockImplementation(() => new Promise(() => {})); + void mgr.send({ payload: { type: 'prompt', prompt: 'Hello', mode: 'code' } }); + + const storage = mockSession.storage; + expect(storage).not.toBeNull(); + expect(storage!.getMessageIds()).toHaveLength(1); + const [optimisticId] = storage!.getMessageIds(); + + // Old CLI: it ignores our messageId and materializes the user message + // under its own id. The manager retargets the optimistic row. + const authoritative = stubUserMessage({ + id: 'cli-assigned-id', + sessionID: kiloId('ses-1'), + time: { created: 2 }, + agent: 'test-agent', + model: { providerID: 'test-provider', modelID: 'test-model' }, + }); + storage!.upsertMessage(authoritative); + mockSessionCallbacks.onEvent?.({ + type: 'message.updated', + info: authoritative, + } as NormalizedEvent); + + const ids = storage!.getMessageIds(); + expect(ids).toHaveLength(1); + expect(ids[0]).toBe('cli-assigned-id'); + expect(ids).not.toContain(optimisticId); + }); + + it('keeps the optimistic row when a queue snapshot omits the in-flight id', async () => { + const config = createMockConfig(); + const mgr = createSessionManager(config); + + await mgr.switchSession(kiloId('ses-1')); + mockSessionCallbacks.onResolved?.({ type: 'remote', kiloSessionId: kiloId('ses-1') }); + + mockSession.send.mockImplementation(() => new Promise(() => {})); + void mgr.send({ payload: { type: 'prompt', prompt: 'Hello', mode: 'code' } }); + + const storage = mockSession.storage; + expect(storage).not.toBeNull(); + expect(storage!.getMessageIds()).toHaveLength(1); + const [optimisticId] = storage!.getMessageIds(); + + // A root FIFO snapshot omits the in-flight send and the just-started + // message. It must not delete the optimistic row; only the authoritative + // `message.updated` retarget reconciles it. + mockSessionCallbacks.onEvent?.({ + type: 'queue.changed', + sessionId: kiloId('ses-1'), + queued: [], + } as NormalizedEvent); + + expect(storage!.getMessageIds()).toContain(optimisticId); + }); + + it('keeps the optimistic row when a child-session queue snapshot arrives', async () => { + const config = createMockConfig(); + const mgr = createSessionManager(config); + + await mgr.switchSession(kiloId('ses-1')); + mockSessionCallbacks.onResolved?.({ type: 'remote', kiloSessionId: kiloId('ses-1') }); + + mockSession.send.mockImplementation(() => new Promise(() => {})); + void mgr.send({ payload: { type: 'prompt', prompt: 'Hello', mode: 'code' } }); + + const storage = mockSession.storage; + expect(storage).not.toBeNull(); + expect(storage!.getMessageIds()).toHaveLength(1); + const [optimisticId] = storage!.getMessageIds(); + + // A child/subagent snapshot must not delete the root session's row. + mockSessionCallbacks.onEvent?.({ + type: 'queue.changed', + sessionId: kiloId('child-ses-1'), + queued: [], + } as NormalizedEvent); + + expect(storage!.getMessageIds()).toContain(optimisticId); + }); + + it('keeps the optimistic row when a historical message.updated arrives during replay', async () => { + const config = createMockConfig(); + const mgr = createSessionManager(config); + + await mgr.switchSession(kiloId('ses-1')); + mockSessionCallbacks.onResolved?.({ type: 'remote', kiloSessionId: kiloId('ses-1') }); + + mockSession.send.mockImplementation(() => new Promise(() => {})); + void mgr.send({ payload: { type: 'prompt', prompt: 'Hello', mode: 'code' } }); + + const storage = mockSession.storage; + expect(storage).not.toBeNull(); + expect(storage!.getMessageIds()).toHaveLength(1); + const [optimisticId] = storage!.getMessageIds(); + + // The manager is still replaying (`remoteHistoryReplaying` stays true + // until onReplayComplete), so a historical user message must not + // retarget-delete the in-flight optimistic row. + const historical = stubUserMessage({ + id: 'historical-id', + sessionID: kiloId('ses-1'), + time: { created: 1 }, + agent: 'test-agent', + model: { providerID: 'test-provider', modelID: 'test-model' }, + }); + mockSessionCallbacks.onEvent?.({ + type: 'message.updated', + info: historical, + } as NormalizedEvent); + + expect(storage!.getMessageIds()).toContain(optimisticId); }); it('leaves storage empty and sets error indicator + failedPrompt on failure', async () => { @@ -2170,6 +2350,33 @@ describe('createSessionManager', () => { }); }); + it('records the cloud-agent upload path in the optimistic file part url for cancel-restore', async () => { + const config = createMockConfig(); + const mgr = createSessionManager(config); + const attachments = { + path: '12345678-1234-4234-9234-123456789abc', + files: ['87654321-4321-4321-8321-cba987654321.md'], + }; + + await mgr.switchSession(kiloId('ses-1')); + + mockSession.send.mockImplementation(() => new Promise(() => {})); + void mgr.send({ + payload: { type: 'prompt', prompt: 'Hello', mode: 'code', model: 'claude-3-5-sonnet' }, + attachments, + }); + + const storage = mockSession.storage; + expect(storage).not.toBeNull(); + const [messageId] = storage!.getMessageIds(); + expect(messageId).toBeDefined(); + const filePart = storage!.getParts(messageId!).find(part => part.type === 'file') as + | FilePart + | undefined; + expect(filePart?.url).toBe(`cloud-agent://${attachments.path}/${attachments.files[0]}`); + expect(filePart?.filename).toBe(attachments.files[0]); + }); + it('rejects canonical attachments for resolved remote sessions before transport send', async () => { const onSendFailed = jest.fn(); const config = createMockConfig({ onSendFailed }); @@ -4583,16 +4790,25 @@ describe('createSessionManager', () => { }); describe('cancelQueuedMessage', () => { - it('delegates to the active session without interrupting', async () => { + it('delegates to the active session without interrupting and returns its { dropped } result', async () => { const config = createMockConfig(); const mgr = createSessionManager(config); await mgr.switchSession(kiloId('ses-1')); - await mgr.cancelQueuedMessage('msg-queued-1'); + mockSession.cancelQueuedMessage.mockResolvedValue({ dropped: true }); + await expect(mgr.cancelQueuedMessage('msg-queued-1')).resolves.toEqual({ dropped: true }); expect(mockSession.cancelQueuedMessage).toHaveBeenCalledWith('msg-queued-1'); expect(mockSession.interrupt).not.toHaveBeenCalled(); }); + + it('returns { dropped: false } when no active session exists', async () => { + const config = createMockConfig(); + const mgr = createSessionManager(config); + + await expect(mgr.cancelQueuedMessage('msg-queued-1')).resolves.toEqual({ dropped: false }); + expect(mockSession.cancelQueuedMessage).not.toHaveBeenCalled(); + }); }); // ------------------------------------------------------------------------- @@ -5049,9 +5265,14 @@ describe('createSessionManager', () => { latestStorage?.upsertMessage(stubUserMessage({ id: 'msg_post_clear', sessionID: 'ses-1' })); mockSessionCallbacks.onReplayComplete?.(); - expect( - atomValue(config.store, mgr.atoms.messagesList).map(m => m.info.id) - ).toEqual(['msg_post_clear', 'msg_pre_clear']); + // The successful send left its optimistic row (a `msg_…` id sorts + // before any `msg_p…`), plus the replayed pre- and post-clear history. + const ids = atomValue(config.store, mgr.atoms.messagesList).map( + m => m.info.id + ); + expect(ids).toHaveLength(3); + expect(ids[0]).toMatch(/^msg_[0-9a-f]{12}/); + expect(ids.slice(1)).toEqual(['msg_post_clear', 'msg_pre_clear']); }); it('re-sets marker on a second /clear after send so reconnect purges again', async () => { diff --git a/packages/cloud-agent-sdk/src/session-manager.ts b/packages/cloud-agent-sdk/src/session-manager.ts index 6023b80105..babb25df39 100644 --- a/packages/cloud-agent-sdk/src/session-manager.ts +++ b/packages/cloud-agent-sdk/src/session-manager.ts @@ -52,6 +52,9 @@ import type { MessageDeliveryState, MessageInfo, Part, + FilePart, + TextPart, + UserMessage, OlderMessagesError, PreparationAttempt, } from './types'; @@ -439,6 +442,12 @@ type SessionManager = { * reach the transport. */ attachmentParts?: RemoteAttachmentPart[]; + /** + * Fired synchronously after the optimistic user row is inserted, before the + * transport round-trip. The composer uses it to clear the draft and unlock + * so the prompt never renders in both the transcript and the input. + */ + onOptimisticSend?: () => void; }): Promise; setRemoteModelOverride(override: RemoteModelOverride | null): void; setCloudAgentModelOverride(override: CloudAgentModelOverride | null): void; @@ -448,7 +457,7 @@ type SessionManager = { exitRemoteSession(): Promise; interrupt(): Promise; /** Drop one queued (not yet accepted) message by id without interrupting the active run. */ - cancelQueuedMessage(messageId: string): Promise; + cancelQueuedMessage(messageId: string): Promise<{ dropped: boolean }>; /** * Clear the active session's local transcript view only. Server-side history * is untouched and reappears on re-entry (`switchSession`). No-op without an @@ -535,6 +544,92 @@ function isMessageStreaming(msg: StoredMessage): boolean { }); } +/** + * Build optimistic file parts for a just-sent user message. Remote sessions + * carry full `RemoteAttachmentPart` info (mime/filename/url); cloud-agent + * `attachments` carry only filenames, so those parts render as filename-only + * placeholders until the server echoes the authoritative parts. + * + * For cloud-agent attachments the `url` records a reconstructable + * `cloud-agent:///` reference so a cancel-restore can + * recover the original upload path (`attachments.path`) and remote filename + * and re-admit the already-uploaded object on the next send. The mobile + * resolver recognizes that form; the file-part renderer does not treat it as + * a fetchable URL, so the optimistic part still renders as a placeholder. + */ +const CLOUD_AGENT_RESTORE_URL_PREFIX = 'cloud-agent://'; + +function buildOptimisticFileParts( + messageId: string, + sessionId: string, + input: { attachments?: CloudAgentAttachments; attachmentParts?: RemoteAttachmentPart[] } +): FilePart[] { + if (input.attachmentParts && input.attachmentParts.length > 0) { + return input.attachmentParts.map((part, index) => ({ + id: `${messageId}-file-${index}`, + sessionID: sessionId, + messageID: messageId, + type: 'file', + mime: part.mime, + filename: part.filename, + url: part.url, + synthetic: true, + })); + } + if (input.attachments && input.attachments.files.length > 0) { + const attachments = input.attachments; + return attachments.files.map((filename, index) => ({ + id: `${messageId}-file-${index}`, + sessionID: sessionId, + messageID: messageId, + type: 'file', + mime: '', + filename, + url: `${CLOUD_AGENT_RESTORE_URL_PREFIX}${attachments.path}/${filename}`, + synthetic: true, + })); + } + return []; +} + +/** + * Materialize the optimistic user message row at send time so the transcript + * renders the prompt (and files) before the server or CLI echoes it back. + * Mirrors `synthesizeQueuedUserMessage`'s shape so the authoritative + * `message.updated` overwrites it by id. + */ +function insertOptimisticUserMessage(input: { + storage: JotaiSessionStorage; + sessionId: string; + messageId: string; + messageText: string; + attachments?: CloudAgentAttachments; + attachmentParts?: RemoteAttachmentPart[]; +}): void { + const { storage, sessionId, messageId, messageText } = input; + const syntheticMessage: UserMessage = { + id: messageId, + sessionID: sessionId, + role: 'user', + time: { created: Date.now() }, + agent: '', + model: { providerID: '', modelID: '' }, + }; + storage.upsertMessage(syntheticMessage); + const textPart: TextPart = { + id: `${messageId}-text`, + sessionID: sessionId, + messageID: messageId, + type: 'text', + text: messageText, + synthetic: true, + }; + storage.upsertPart(messageId, textPart); + for (const filePart of buildOptimisticFileParts(messageId, sessionId, input)) { + storage.upsertPart(messageId, filePart); + } +} + // --------------------------------------------------------------------------- // Status → indicator mapping // --------------------------------------------------------------------------- @@ -603,6 +698,12 @@ function removePendingRequest( function createSessionManager(config: SessionManagerConfig): SessionManager { const { store } = config; + // In-flight optimistic user-message ids for the active remote session. New + // CLIs echo our generated id back; old CLIs assign their own, so this Set + // (not a single flag) lets us retarget each optimistic row to the + // authoritative message exactly once. + const remoteOptimisticIds = new Set(); + // Internal atoms const sessionStorageAtom = atom(null); const rootSessionIdAtom = atom(null); @@ -1496,6 +1597,7 @@ function createSessionManager(config: SessionManagerConfig): SessionManager { // Clean slate immediately — the user asked to switch, so clear all // previous session state and show a loading indicator. clearAllAtoms(); + remoteOptimisticIds.clear(); store.set(rootSessionIdAtom, kiloSessionId); store.set(isLoadingAtom, true); @@ -1727,6 +1829,15 @@ function createSessionManager(config: SessionManagerConfig): SessionManager { store.set(availableCommandsAtom, event.commands); return; } + if (event.type === 'queue.changed' && activeSessionType === 'remote') { + // The authoritative reconciliation of an optimistic row is the + // `message.updated` retarget below. A FIFO snapshot omits the + // in-flight send and the just-started message, and child/subagent + // snapshots are forwarded through this same path, so deleting an + // optimistic row merely because its id left `queued` would drop the + // prompt until the CLI echoes it back. + return; + } if (event.type === 'message.updated') { const rootSessionId = store.get(rootSessionIdAtom); if (rootSessionId !== null && event.info.sessionID !== rootSessionId) return; @@ -1743,6 +1854,27 @@ function createSessionManager(config: SessionManagerConfig): SessionManager { const canApplyMessageObservation = !remoteHistoryReplaying || observedModelSource !== 'session'; if (event.info.role === 'user') { + // Remote optimistic retarget (per-id Set, never prompt text). + // Reconcile the synthetic row inserted at send() with the + // authoritative user message the CLI produced. Same id (new CLI + // echoes messageID) → keep the row (the upsert just overwrote it) + // and clear the id. Different id (old CLI) → the synthetic row is + // orphaned; drop the oldest optimistic row and its id. + if ( + activeSessionType === 'remote' && + !remoteHistoryReplaying && + remoteOptimisticIds.size > 0 + ) { + if (remoteOptimisticIds.has(event.info.id)) { + remoteOptimisticIds.delete(event.info.id); + } else { + const oldest = remoteOptimisticIds.values().next().value; + if (oldest !== undefined) { + remoteOptimisticIds.delete(oldest); + session.storage.deleteMessage(oldest); + } + } + } if (canApplyMessageObservation) { const selection = toModelSelection(event.info.model, event.info.variant); updateObservedModel(selection, 'message'); @@ -1819,6 +1951,7 @@ function createSessionManager(config: SessionManagerConfig): SessionManager { attachments?: CloudAgentAttachments; images?: Images; attachmentParts?: RemoteAttachmentPart[]; + onOptimisticSend?: () => void; }): Promise { store.set(errorAtom, null); if (store.get(agentStatusAtom).type !== 'disconnected') { @@ -1888,6 +2021,33 @@ function createSessionManager(config: SessionManagerConfig): SessionManager { }; } + // Optimistic local insert: render the user's prompt (plus any file parts) + // as soon as the transport send is attempted, before the server or CLI + // echoes it back. Reconciliation differs by session type: + // - cloud-agent: the server honors `messageId`, so the later + // `cloud.message.queued` synthesize is a no-op (existing-id guard) and + // the authoritative `message.updated` overwrites this row by id. + // - remote: new CLIs echo `messageId` back; old CLIs assign their own, + // so we track the id in `remoteOptimisticIds` and retarget when the + // authoritative user message lands (see the onEvent handler). + const optimisticStorage = store.get(sessionStorageAtom); + const optimisticSessionId = store.get(rootSessionIdAtom) ?? kiloSessionId; + if (sessionAtSend && optimisticStorage && optimisticSessionId) { + insertOptimisticUserMessage({ + storage: optimisticStorage, + sessionId: optimisticSessionId, + messageId, + messageText, + ...(input.attachments ? { attachments: input.attachments } : {}), + ...(input.attachmentParts ? { attachmentParts: input.attachmentParts } : {}), + }); + if (sessionType === 'remote') { + remoteOptimisticIds.add(messageId); + } + // Signal the composer exactly once, after the row is in the transcript. + input.onOptimisticSend?.(); + } + try { if (!sessionAtSend) throw new Error('No active session'); if (input.attachments && sessionType !== 'cloud-agent') { @@ -1935,6 +2095,10 @@ function createSessionManager(config: SessionManagerConfig): SessionManager { return true; } catch (err) { if (currentSession !== sessionAtSend || activeSessionId !== kiloSessionId) return false; + // Delete the optimistic row on failure so the transcript does not keep a + // ghost prompt. The composer keeps the draft for a retry. + optimisticStorage?.deleteMessage(messageId); + remoteOptimisticIds.delete(messageId); store.set(failedPromptAtom, messageText); store.set(billingFailureAtom, parseCustomerBillingFailure(err)); const message = formatError(err); @@ -2009,13 +2173,13 @@ function createSessionManager(config: SessionManagerConfig): SessionManager { } } - async function cancelQueuedMessage(messageId: string): Promise { - if (!currentSession) return; + async function cancelQueuedMessage(messageId: string): Promise<{ dropped: boolean }> { + if (!currentSession) return { dropped: false }; // Delegate to the session: the cloud-agent transport calls the // `cancelQueuedMessage` tRPC mutation and the remote transport relays // `drop_queued_message`. A remote CLI_UPGRADE_REQUIRED rejection surfaces // to the caller verbatim; this path never falls back to `interrupt()`. - await currentSession.cancelQueuedMessage(messageId); + return currentSession.cancelQueuedMessage(messageId); } function clearTranscript(): void { @@ -2152,6 +2316,7 @@ function createSessionManager(config: SessionManagerConfig): SessionManager { indicatorTimer = null; } clearAllAtoms(); + remoteOptimisticIds.clear(); activeSessionId = null; activeSessionType = null; } diff --git a/packages/cloud-agent-sdk/src/session.ts b/packages/cloud-agent-sdk/src/session.ts index ab0879accd..7958516f74 100644 --- a/packages/cloud-agent-sdk/src/session.ts +++ b/packages/cloud-agent-sdk/src/session.ts @@ -196,7 +196,7 @@ type CloudAgentSession = { // Commands send: (input: CloudAgentSessionSendInput) => unknown | Promise; interrupt: () => unknown | Promise; - cancelQueuedMessage: (messageId: string) => unknown | Promise; + cancelQueuedMessage: (messageId: string) => Promise<{ dropped: boolean }>; answer: (payload: CloudAgentSessionAnswerInput) => unknown | Promise; reject: (payload: CloudAgentSessionRejectInput) => unknown | Promise; respondToPermission: ( diff --git a/packages/cloud-agent-sdk/src/storage/helpers.test.ts b/packages/cloud-agent-sdk/src/storage/helpers.test.ts index 2bba0cdfb8..1807549f85 100644 --- a/packages/cloud-agent-sdk/src/storage/helpers.test.ts +++ b/packages/cloud-agent-sdk/src/storage/helpers.test.ts @@ -9,7 +9,7 @@ import { insertSorted, isSupportedDeltaField, notify, - upsertPartDroppingStaleSyntheticTextParts, + upsertPartDroppingStaleSyntheticParts, } from './helpers'; function makePart(id: string, text = '', messageID = 'm'): Part { @@ -55,12 +55,12 @@ describe('insertPartSorted', () => { }); }); -describe('upsertPartDroppingStaleSyntheticTextParts', () => { +describe('upsertPartDroppingStaleSyntheticParts', () => { test('removes stale synthetic text part when real text part arrives', () => { const syntheticPart = { ...makePart('msg-1-text', 'optimistic'), synthetic: true }; const realPart = makePart('prt-real', 'authoritative'); - const result = upsertPartDroppingStaleSyntheticTextParts([syntheticPart], realPart); + const result = upsertPartDroppingStaleSyntheticParts([syntheticPart], realPart); expect(result).toEqual([realPart]); }); @@ -69,7 +69,7 @@ describe('upsertPartDroppingStaleSyntheticTextParts', () => { const existingSynthetic = { ...makePart('msg-1-text', 'optimistic', 'msg-1'), synthetic: true }; const realPart = makePart('prt-real', 'authoritative', 'msg-2'); - const result = upsertPartDroppingStaleSyntheticTextParts([existingSynthetic], realPart); + const result = upsertPartDroppingStaleSyntheticParts([existingSynthetic], realPart); expect(result.map(part => part.id)).toEqual(['msg-1-text', 'prt-real']); }); @@ -78,10 +78,7 @@ describe('upsertPartDroppingStaleSyntheticTextParts', () => { const existingSynthetic = { ...makePart('msg-1-text', 'optimistic'), synthetic: true }; const incomingSynthetic = { ...makePart('prt-synthetic', 'new'), synthetic: true }; - const result = upsertPartDroppingStaleSyntheticTextParts( - [existingSynthetic], - incomingSynthetic - ); + const result = upsertPartDroppingStaleSyntheticParts([existingSynthetic], incomingSynthetic); expect(result.map(part => part.id)).toEqual(['msg-1-text', 'prt-synthetic']); }); @@ -90,10 +87,58 @@ describe('upsertPartDroppingStaleSyntheticTextParts', () => { const syntheticPart = { ...makePart('msg-1-text', 'optimistic'), synthetic: true }; const toolPart = { id: 'tool-1', sessionID: 's', messageID: 'm', type: 'tool' } as Part; - const result = upsertPartDroppingStaleSyntheticTextParts([syntheticPart], toolPart); + const result = upsertPartDroppingStaleSyntheticParts([syntheticPart], toolPart); expect(result.map(part => part.id)).toEqual(['msg-1-text', 'tool-1']); }); + + test('removes stale synthetic file part when a real file part arrives', () => { + const syntheticFile = { + id: 'msg-1-file-0', + sessionID: 's', + messageID: 'msg-1', + type: 'file', + mime: '', + url: '', + synthetic: true, + } as Part; + const realFile = { + id: 'prt-file', + sessionID: 's', + messageID: 'msg-1', + type: 'file', + mime: 'image/png', + url: 'https://cdn/file.png', + } as Part; + + const result = upsertPartDroppingStaleSyntheticParts([syntheticFile], realFile); + + expect(result.map(part => part.id)).toEqual(['prt-file']); + }); + + test('preserves synthetic file part for a different message', () => { + const syntheticFile = { + id: 'msg-1-file-0', + sessionID: 's', + messageID: 'msg-1', + type: 'file', + mime: '', + url: '', + synthetic: true, + } as Part; + const realFile = { + id: 'prt-file', + sessionID: 's', + messageID: 'msg-2', + type: 'file', + mime: 'image/png', + url: 'https://cdn/file.png', + } as Part; + + const result = upsertPartDroppingStaleSyntheticParts([syntheticFile], realFile); + + expect(result.map(part => part.id)).toEqual(['msg-1-file-0', 'prt-file']); + }); }); describe('isSupportedDeltaField', () => { diff --git a/packages/cloud-agent-sdk/src/storage/helpers.ts b/packages/cloud-agent-sdk/src/storage/helpers.ts index dbaeee24a9..8d14ebe75c 100644 --- a/packages/cloud-agent-sdk/src/storage/helpers.ts +++ b/packages/cloud-agent-sdk/src/storage/helpers.ts @@ -27,16 +27,20 @@ function insertPartSorted(arr: Part[], part: Part): Part[] { return result; } -function upsertPartDroppingStaleSyntheticTextParts(arr: Part[], part: Part): Part[] { +function upsertPartDroppingStaleSyntheticParts(arr: Part[], part: Part): Part[] { const nextPart = clonePart(part); const incomingIsSynthetic = Reflect.get(part, 'synthetic') === true; - const shouldDropSyntheticText = !incomingIsSynthetic && part.type === 'text'; - const filtered = shouldDropSyntheticText + // A non-synthetic text or file part drops the synthetic placeholder parts of + // the same type on its message: the optimistic text row and the optimistic + // file placeholders both reconcile to one authoritative part set. + const shouldDropSynthetic = + !incomingIsSynthetic && (part.type === 'text' || part.type === 'file'); + const filtered = shouldDropSynthetic ? arr.filter( existing => existing.id === part.id || existing.messageID !== part.messageID || - existing.type !== 'text' || + existing.type !== part.type || Reflect.get(existing, 'synthetic') !== true ) : arr; @@ -112,5 +116,5 @@ export { insertSorted, isSupportedDeltaField, notify, - upsertPartDroppingStaleSyntheticTextParts, + upsertPartDroppingStaleSyntheticParts, }; diff --git a/packages/cloud-agent-sdk/src/storage/jotai.ts b/packages/cloud-agent-sdk/src/storage/jotai.ts index 7a8e12e1ef..9181d49742 100644 --- a/packages/cloud-agent-sdk/src/storage/jotai.ts +++ b/packages/cloud-agent-sdk/src/storage/jotai.ts @@ -14,7 +14,7 @@ import { insertSorted, isSupportedDeltaField, notify, - upsertPartDroppingStaleSyntheticTextParts, + upsertPartDroppingStaleSyntheticParts, } from './helpers'; type JotaiStore = ReturnType; @@ -116,7 +116,7 @@ function createJotaiStorage( upsertPart(messageId, part) { flushPendingDeltas(); const arr = partsMap.get(messageId) ?? []; - const nextArr = upsertPartDroppingStaleSyntheticTextParts(arr, part); + const nextArr = upsertPartDroppingStaleSyntheticParts(arr, part); partsMap.set(messageId, nextArr); bumpPartsRevision(); partsSnapshot.set(messageId, null); diff --git a/packages/cloud-agent-sdk/src/storage/memory.ts b/packages/cloud-agent-sdk/src/storage/memory.ts index 4f9b315796..48b9267fe2 100644 --- a/packages/cloud-agent-sdk/src/storage/memory.ts +++ b/packages/cloud-agent-sdk/src/storage/memory.ts @@ -11,7 +11,7 @@ import { insertSorted, isSupportedDeltaField, notify, - upsertPartDroppingStaleSyntheticTextParts, + upsertPartDroppingStaleSyntheticParts, } from './helpers'; function createMemoryStorage(): SessionStorage { @@ -45,7 +45,7 @@ function createMemoryStorage(): SessionStorage { upsertPart(messageId, part) { const arr = parts.get(messageId) ?? []; - parts.set(messageId, upsertPartDroppingStaleSyntheticTextParts(arr, part)); + parts.set(messageId, upsertPartDroppingStaleSyntheticParts(arr, part)); partsSnapshot.set(messageId, null); notify(subscribers, `parts:${messageId}`); }, diff --git a/packages/cloud-agent-sdk/src/transport.ts b/packages/cloud-agent-sdk/src/transport.ts index 8a907d686f..4a9451913e 100644 --- a/packages/cloud-agent-sdk/src/transport.ts +++ b/packages/cloud-agent-sdk/src/transport.ts @@ -157,7 +157,7 @@ type Transport = { * calls the `cancelQueuedMessage` tRPC mutation. Old remotes reject the drop * with CLI_UPGRADE_REQUIRED. */ - dropQueuedMessage?: (messageId: string) => Promise; + dropQueuedMessage?: (messageId: string) => Promise<{ dropped: boolean }>; answer?: (payload: { requestId: string; answers: string[][] }) => Promise; reject?: (payload: { requestId: string }) => Promise; respondToPermission?: (payload: { @@ -195,7 +195,7 @@ type CloudAgentApi = { cancelQueuedMessage?: (payload: { sessionId: CloudAgentSessionId; messageId: string; - }) => Promise; + }) => Promise<{ dropped: boolean }>; answer: (payload: { sessionId: CloudAgentSessionId; requestId: string;