From e36cdbe5d8ce53b131d40448ca24f7058cb81f5e Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:01:50 -0700 Subject: [PATCH] Remove cross-authority move support for automations An automation's authority (the Orca instance that stores and schedules it) cannot change; edits now only offer hosts within the same authority and move logic is removed entirely. This simplifies the destination picker and removes move-specific UI messaging. --- .../AutomationDestinationField.test.tsx | 18 +- .../AutomationDestinationField.tsx | 9 +- ...utomationsPage.create-destination.test.tsx | 85 +-------- .../automations/AutomationsPage.tsx | 164 ++++-------------- .../automations/automation-host-client.ts | 9 - .../use-automation-create-destination.ts | 2 - src/renderer/src/i18n/locales/en.json | 5 +- src/renderer/src/i18n/locales/es.json | 5 +- src/renderer/src/i18n/locales/ja.json | 5 +- src/renderer/src/i18n/locales/ko.json | 5 +- src/renderer/src/i18n/locales/zh.json | 5 +- 11 files changed, 48 insertions(+), 264 deletions(-) diff --git a/src/renderer/src/components/automations/AutomationDestinationField.test.tsx b/src/renderer/src/components/automations/AutomationDestinationField.test.tsx index 48eff9cc900..cc46c81873f 100644 --- a/src/renderer/src/components/automations/AutomationDestinationField.test.tsx +++ b/src/renderer/src/components/automations/AutomationDestinationField.test.tsx @@ -61,12 +61,10 @@ const LEGACY_ENTRY: AutomationHostCatalogEntry = { function control( projects: Repo[], - entries: AutomationHostCatalogEntry[] = [ENTRY], - note?: string + entries: AutomationHostCatalogEntry[] = [ENTRY] ): AutomationCreateDestinationControl { return { entries, - note, resolution: { status: 'ready', authority: OWNER.authority, @@ -78,11 +76,11 @@ function control( } } -function render(projects: Repo[], entries?: AutomationHostCatalogEntry[], note?: string): void { +function render(projects: Repo[], entries?: AutomationHostCatalogEntry[]): void { act(() => { root.render( - + ) }) @@ -119,16 +117,6 @@ describe('AutomationDestinationField', () => { expect(container.querySelector('[data-testid="automation-create-update-required"]')).toBeNull() }) - it('states a move in place of the host it would otherwise just name', () => { - render([{ id: 'repo-1' } as Repo], undefined, 'Saving creates this automation on GPU box.') - - // Both lines name the same host; the one that mentions the delete wins. - expect(container.querySelector('[data-testid="automation-host-move"]')?.textContent).toContain( - 'GPU box' - ) - expect(container.textContent).not.toContain('Stored and scheduled by') - }) - it('names the host without a tense, so editing reads as a move', () => { render([{ id: 'repo-1' } as Repo]) diff --git a/src/renderer/src/components/automations/AutomationDestinationField.tsx b/src/renderer/src/components/automations/AutomationDestinationField.tsx index 0f222f1dd5f..f7956e55c0d 100644 --- a/src/renderer/src/components/automations/AutomationDestinationField.tsx +++ b/src/renderer/src/components/automations/AutomationDestinationField.tsx @@ -20,8 +20,7 @@ import { groupAutomationHostEntriesByAuthority } from './automation-host-picker- import type { AutomationCreateDestinationControl } from './use-automation-create-destination' // The host that stores and schedules the automation, not the workspace's -// execution host — the two routinely differ. Serves create and edit alike; on an -// existing record a save that lands elsewhere is a move. +// execution host — the two routinely differ. export function AutomationDestinationField({ control, @@ -79,12 +78,6 @@ export function AutomationDestinationField({ 'No projects are set up on {host}. Add one there, or choose another host.' ).replace('{host}', selected.label)}

- ) : control.note ? ( - // Replaces the storedOn line: both name the same host, and the move is - // the consequential half. -

- {control.note} -

) : (

{selected diff --git a/src/renderer/src/components/automations/AutomationsPage.create-destination.test.tsx b/src/renderer/src/components/automations/AutomationsPage.create-destination.test.tsx index d10a6441c98..fb9b359ec1d 100644 --- a/src/renderer/src/components/automations/AutomationsPage.create-destination.test.tsx +++ b/src/renderer/src/components/automations/AutomationsPage.create-destination.test.tsx @@ -414,7 +414,7 @@ describe('AutomationsPage edit dialog projects', () => { }) describe('AutomationsPage edit destination', () => { - it('names the edited row’s own host, and offers every host it could move to', async () => { + it('names the edited row’s own host, and only its authority’s hosts', async () => { const automation = makeAutomation({ id: 'a-1' }) api.automations.list.mockResolvedValue([]) scopedList([automation]) @@ -430,84 +430,11 @@ describe('AutomationsPage edit destination', () => { const resolution = mocks.editorDialog?.editDestination?.resolution expect(resolution?.status === 'ready' && resolution.entry.stableKey).toBe(DESKTOP_SELF_KEY) - // Another authority is a legal destination — reached by a create there and a - // delete here — so restricting the list to this one would hide a real move. - expect(mocks.editorDialog?.editDestination?.entries.map((entry) => entry.stableKey)).toContain( - RUNTIME_SELF_KEY - ) - expect(mocks.editorDialog?.editDestination?.note).toBeFalsy() - }) - - it('moves the record across authorities by creating there and deleting here', async () => { - const automation = makeAutomation({ id: 'a-1' }) - api.automations.list.mockResolvedValue([]) - scopedList([automation]) - runtimeHost([], []) - addRuntimeProject() - runtimeCreateReturns(makeAutomation({ id: 'a-moved', projectId: RUNTIME_REPO_ID })) - - await renderPage() - await settleHostQueries() - await act(async () => { - void mocks.listPanel?.openEditDialog(listedRow(automation.id)) - }) - await act(async () => { - mocks.editorDialog?.editDestination?.onSelect(RUNTIME_SELF_KEY) - }) - - // The move is stated before it happens: the record is deleted from here. - expect(mocks.editorDialog?.editDestination?.note).toBeTruthy() - - await act(async () => { - mocks.editorDialog?.onDraftChange((current) => ({ - ...(current as Record), - projectId: RUNTIME_REPO_ID, - workspaceMode: 'existing', - workspaceId: RUNTIME_WORKSPACE_ID - })) - }) - await save() - - // `update` reaches only the authority already holding the record, so a move - // is the pair — and leaving the original behind would run it twice. - expect(api.automations.update).not.toHaveBeenCalled() - expect(runtimeCreateCalls()).toHaveLength(1) - expect(runtimeCreateCalls()[0]?.[2]).toMatchObject({ repo: `id:${RUNTIME_REPO_ID}` }) - expect(api.automations.delete).toHaveBeenCalledWith(expect.objectContaining({ id: 'a-1' })) - }) - - it('reports a kept original as two live copies, never as a completed move', async () => { - const automation = makeAutomation({ id: 'a-1' }) - api.automations.list.mockResolvedValue([]) - scopedList([automation]) - runtimeHost([], []) - addRuntimeProject() - runtimeCreateReturns(makeAutomation({ id: 'a-moved', projectId: RUNTIME_REPO_ID })) - api.automations.delete.mockRejectedValue(new Error('gone away')) - - await renderPage() - await settleHostQueries() - await act(async () => { - void mocks.listPanel?.openEditDialog(listedRow(automation.id)) - }) - await act(async () => { - mocks.editorDialog?.editDestination?.onSelect(RUNTIME_SELF_KEY) - }) - await act(async () => { - mocks.editorDialog?.onDraftChange((current) => ({ - ...(current as Record), - projectId: RUNTIME_REPO_ID, - workspaceMode: 'existing', - workspaceId: RUNTIME_WORKSPACE_ID - })) - }) - await save() - - // The new copy is live and the old one still runs; "moved" would hide that. - expect(runtimeCreateCalls()).toHaveLength(1) - expect(api.automations.delete).toHaveBeenCalled() - expect(mocks.toastError).toHaveBeenCalledWith(expect.stringContaining('could not be deleted')) - expect(mocks.toastSuccess).not.toHaveBeenCalled() + // A record cannot move between authorities, so the runtime is not offered. + expect(mocks.editorDialog?.editDestination?.entries.map((entry) => entry.stableKey)).toEqual([ + DESKTOP_SELF_KEY, + SSH_HOST_KEY + ]) }) it('moves the record to the host the user picks, clearing the stranded project', async () => { diff --git a/src/renderer/src/components/automations/AutomationsPage.tsx b/src/renderer/src/components/automations/AutomationsPage.tsx index 9fae9cdf407..f82eee32cdd 100644 --- a/src/renderer/src/components/automations/AutomationsPage.tsx +++ b/src/renderer/src/components/automations/AutomationsPage.tsx @@ -75,7 +75,6 @@ import { import { deleteAutomationForTarget, type AutomationHostTarget, - getAutomationAuthorityTarget, getAutomationHostTargetFromKey, getAutomationHostTargetKey, getAutomationListTarget, @@ -1025,36 +1024,25 @@ export default function AutomationsPage(): React.JSX.Element { })() const isOrcaForm = createTarget === 'orca' && editingExternalTarget === null const dialogAuthorityRepos = getAutomationCreateRepos(repos, automationDialogTarget) - // The authority storing the edited record; a save landing on another one is a move. + // An update moves the record's execution target, never the authority that + // stores it, so the edit picker offers only that authority's own hosts. const dialogAuthorityKey = automationAuthorityCatalogKey( automationDialogTarget.kind === 'environment' ? { kind: 'runtime', environmentId: automationDialogTarget.environmentId } : { kind: 'desktop' } ) - const editHostEntries = hostCatalog.entries + const editHostEntries = hostCatalog.entries.filter( + (entry) => automationAuthorityCatalogKey(entry.stableRef.authority) === dialogAuthorityKey + ) const editHostResolution = resolveAutomationCreateDestination( editingHostStableKey ? editHostEntries.find((entry) => entry.stableKey === editingHostStableKey) : null ) - // Projects follow the picked host's authority, not the record's: a move offers - // what the target stores, and nothing on the old authority is reachable there. const editHostProjects = editHostResolution.status === 'ready' - ? automationCreateEligibleProjects( - repoTables, - editHostResolution, - getAutomationCreateRepos( - repos, - getAutomationAuthorityTarget(editHostResolution.authority) - ) - ) + ? automationCreateEligibleProjects(repoTables, editHostResolution, dialogAuthorityRepos) : dialogAuthorityRepos - const editMoveTargetEntry = - editHostResolution.status === 'ready' && - automationAuthorityCatalogKey(editHostResolution.authority) !== dialogAuthorityKey - ? editHostResolution.entry - : null // Picking a host strands a project that lives on another one; clearing it here // states the move instead of deferring the same refusal to submit. const handleEditHostChange = (stableKey: string): void => { @@ -1077,14 +1065,7 @@ export default function AutomationsPage(): React.JSX.Element { entries: editHostEntries, resolution: editHostResolution, onSelect: handleEditHostChange, - projects: editHostProjects, - // Replacer fn: a literal replacement would expand `$` patterns in host labels. - note: editMoveTargetEntry - ? translate( - 'auto.components.automations.createDestination.move', - 'Saving creates this automation on {host} and deletes the original. Its run history stays on the old host.' - ).replace('{host}', () => editMoveTargetEntry.authorityLabel) - : null + projects: editHostProjects } const dialogRepos = isOrcaForm ? editingAutomationId !== null @@ -1751,7 +1732,6 @@ export default function AutomationsPage(): React.JSX.Element { currentAutomation = (reread.ok ? reread.value : null) ?? currentAutomation } let editDestination: AutomationDestination | undefined - let moveTarget: AutomationCreateDestination | null = null const destinationChanged = currentAutomation && (currentAutomation.projectId !== draft.projectId || @@ -1796,11 +1776,6 @@ export default function AutomationsPage(): React.JSX.Element { return } editDestination = revalidated.destination - // `update` reaches only the authority already holding the record, so a - // save landing on another one is a create there plus a delete here. - if (automationAuthorityCatalogKey(revalidated.authority) !== dialogAuthorityKey) { - moveTarget = revalidated - } } const updates: AutomationUpdateInput = { name: draft.name, @@ -1836,39 +1811,29 @@ export default function AutomationsPage(): React.JSX.Element { reuseSession: draft.workspaceMode === 'existing' && draft.reuseSession, timezone, rrule, - dtstart: updates.dtstart ?? currentAutomation?.dtstart ?? now, + dtstart: now, missedRunGraceMinutes } const editSource = currentAutomation - const move = moveTarget - ? await moveAutomationToDestination(editSource, moveTarget, { - ...createInput, - // A move carries the record across; it does not resume a paused one. - enabled: editSource?.enabled ?? true, - sourceContext: editSource?.sourceContext ?? null - }) - : null - const saved = move - ? move.saved - : editingAutomationId - ? await dispatchAutomationUpdate( - automationDispatchContext, - { rowKey: editingRowKey ?? '', automationId: editingAutomationId }, - updates, - () => { - // Nothing names a host: no captured owner and no record to read one - // from, so the save is refused rather than sent unfenced. - if (!editSource) { - throw new Error( - automationOwnerConflictMessage(AUTOMATION_OWNER_CONFLICT_CODES.ownerChanged) - ) - } - return updateAutomationForTarget(editSource, updates, automationHostTarget) - }, - 'save', - editDestination - ) - : await createDraftAutomation(createInput) + const saved = editingAutomationId + ? await dispatchAutomationUpdate( + automationDispatchContext, + { rowKey: editingRowKey ?? '', automationId: editingAutomationId }, + updates, + () => { + // Nothing names a host: no captured owner and no record to read one + // from, so the save is refused rather than sent unfenced. + if (!editSource) { + throw new Error( + automationOwnerConflictMessage(AUTOMATION_OWNER_CONFLICT_CODES.ownerChanged) + ) + } + return updateAutomationForTarget(editSource, updates, automationHostTarget) + }, + 'save', + editDestination + ) + : await createDraftAutomation(createInput) if (!saved.ok) { setEditorNotice(saved.notice) return @@ -1887,9 +1852,7 @@ export default function AutomationsPage(): React.JSX.Element { }) setDraft((current) => ({ ...current, name: '', prompt: '' })) await refresh() - // A move leaves the edited row behind: the record it selects is a new one - // on another host, so its key is found rather than restored. - if (editingAutomationId && editingRowKey && !moveTarget) { + if (editingAutomationId && editingRowKey) { setSelectedAutomationRunPageId(null) setSelectedRowKey(editingRowKey) setSelectedId(automation.id) @@ -1900,25 +1863,13 @@ export default function AutomationsPage(): React.JSX.Element { if (!editingAutomationId) { useAppStore.getState().recordFeatureInteraction('automation-created') } - // A kept original already raised its own toast; "moved" would contradict it. - if (move && !move.originalRemoved) { - return - } toast.success( - moveTarget + editingAutomationId ? translate( - 'auto.components.automations.AutomationsPage.moved', - 'Automation moved to {host}.' - ).replace('{host}', () => moveTarget.entry.authorityLabel) - : editingAutomationId - ? translate( - 'auto.components.automations.AutomationsPage.244727e655', - 'Automation updated.' - ) - : translate( - 'auto.components.automations.AutomationsPage.2a20596d6b', - 'Automation saved.' - ) + 'auto.components.automations.AutomationsPage.244727e655', + 'Automation updated.' + ) + : translate('auto.components.automations.AutomationsPage.2a20596d6b', 'Automation saved.') ) } catch (error) { if (isHermesSave) { @@ -1937,55 +1888,6 @@ export default function AutomationsPage(): React.JSX.Element { } } - /** - * A record cannot change the authority that stores it, so a save aimed at - * another one creates the automation there and deletes the original. A failed - * delete leaves two live copies, which is said rather than reported as a move. - */ - const moveAutomationToDestination = async ( - source: Automation | null, - target: AutomationCreateDestination, - input: AutomationCreateInput - ): Promise<{ saved: AutomationDispatchResult; originalRemoved: boolean }> => { - if (!source) { - return { - saved: { - ok: false, - notice: { - message: automationOwnerConflictMessage(AUTOMATION_OWNER_CONFLICT_CODES.ownerChanged), - recovery: 'retry', - severity: 'owner' - } - }, - originalRemoved: false - } - } - const created = toDispatchResult( - await createAutomationAtDestination(target.authority, input, target.destination) - ) - if (!created.ok) { - return { saved: created, originalRemoved: false } - } - invalidateWrittenHost(target.entry.stableRef, 'definition') - const removed = await dispatchAutomationDelete( - automationDispatchContext, - { rowKey: editingRowKey ?? '', automationId: source.id }, - // The dialog's own target, which names the edited row's host even when no - // owner was captured to fence the delete with. - () => deleteAutomationForTarget(source, automationDialogTarget) - ) - if (!removed.ok) { - // Both copies are live; said as such rather than reported as a move. - toast.error( - translate( - 'auto.components.automations.AutomationsPage.moveOriginalKept', - 'Created on {host}, but the original could not be deleted. Remove it on the old host.' - ).replace('{host}', () => target.entry.authorityLabel) - ) - } - return { saved: created, originalRemoved: removed.ok } - } - /** Creation states its destination and re-checks it at submit; it never infers one. */ const createDraftAutomation = async ( input: AutomationCreateInput diff --git a/src/renderer/src/components/automations/automation-host-client.ts b/src/renderer/src/components/automations/automation-host-client.ts index e7ef5a44770..2b7c3b7c102 100644 --- a/src/renderer/src/components/automations/automation-host-client.ts +++ b/src/renderer/src/components/automations/automation-host-client.ts @@ -5,7 +5,6 @@ import type { AutomationRun, AutomationUpdateInput } from '../../../../shared/automations-types' -import type { AutomationAuthorityRef } from '../../../../shared/automation-owner-ref' import { parseExecutionHostId } from '../../../../shared/execution-host' import type { GlobalSettings } from '../../../../shared/global-settings-types' @@ -50,14 +49,6 @@ export function getAutomationTargetFromHostId( : { kind: 'local' } } -export function getAutomationAuthorityTarget( - authority: AutomationAuthorityRef -): AutomationHostTarget { - return authority.kind === 'runtime' - ? { kind: 'environment', environmentId: authority.environmentId } - : { kind: 'local' } -} - export function getAutomationListTarget( settings: Pick | null | undefined ): AutomationHostTarget { diff --git a/src/renderer/src/components/automations/use-automation-create-destination.ts b/src/renderer/src/components/automations/use-automation-create-destination.ts index 9ae52aa6729..72175fb1f1e 100644 --- a/src/renderer/src/components/automations/use-automation-create-destination.ts +++ b/src/renderer/src/components/automations/use-automation-create-destination.ts @@ -35,8 +35,6 @@ export type AutomationCreateDestinationControl = { onSelect: (stableKey: string) => void /** Projects the resolved destination can hold; the whole list until one resolves. */ projects: readonly Repo[] - /** Stated consequence of saving here, when it is not the plain one. */ - note?: string | null } export type AutomationCreateDestinationCheck = diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json index 86ed852cc78..e8be1452f02 100644 --- a/src/renderer/src/i18n/locales/en.json +++ b/src/renderer/src/i18n/locales/en.json @@ -15376,8 +15376,6 @@ "b11170a008": "Failed to save automation.", "2a20596d6b": "Automation saved.", "244727e655": "Automation updated.", - "moved": "Automation moved to {host}.", - "moveOriginalKept": "Created on {host}, but the original could not be deleted. Remove it on the old host.", "77b81bc4ac": "Hermes automation created.", "08efc3ae12": "Hermes automation updated.", "e431bb85d4": "Choose a workspace on the same host as this Hermes automation.", @@ -15692,8 +15690,7 @@ "stale": "{host} changed while this form was open. Choose it again before saving.", "projectMismatch": "That project is not on {host}. Choose a project on that host, or another host.", "noProjects": "No projects are set up on {host}. Add one there, or choose another host.", - "updateRequired": "Update the Orca server on {hosts} to store automations there.", - "move": "Saving creates this automation on {host} and deletes the original. Its run history stays on the old host." + "updateRequired": "Update the Orca server on {hosts} to store automations there." } }, "agent": { diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index a00e1bdd24e..d3198f1700f 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -13888,8 +13888,6 @@ "b11170a008": "No se pudo guardar la automatización.", "2a20596d6b": "Automatización guardada.", "244727e655": "Automatización actualizada.", - "moved": "Automatización movida a {host}.", - "moveOriginalKept": "Se creó en {host}, pero no se pudo eliminar la original. Elimínela en el host anterior.", "77b81bc4ac": "Se crea la automatización de Hermes.", "08efc3ae12": "Automatización Hermes actualizada.", "e431bb85d4": "Elija un espacio de trabajo en el mismo host que esta automatización de Hermes.", @@ -14097,8 +14095,7 @@ "stale": "{host} cambió mientras este formulario estaba abierto. Elíjalo de nuevo antes de guardar.", "projectMismatch": "Ese proyecto no está en {host}. Elija un proyecto en ese host, u otro host.", "noProjects": "No hay proyectos configurados en {host}. Agregue uno allí o elija otro host.", - "updateRequired": "Actualice el servidor de Orca en {hosts} para almacenar automatizaciones allí.", - "move": "Al guardar se crea esta automatización en {host} y se elimina la original. Su historial de ejecuciones permanece en el host anterior." + "updateRequired": "Actualice el servidor de Orca en {hosts} para almacenar automatizaciones allí." } }, "agent": { diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json index 1c0f8a9a748..f000f02561b 100644 --- a/src/renderer/src/i18n/locales/ja.json +++ b/src/renderer/src/i18n/locales/ja.json @@ -13879,8 +13879,6 @@ "b11170a008": "オートメーションを保存できませんでした。", "2a20596d6b": "オートメーションが保存されました。", "244727e655": "自動化が更新されました。", - "moved": "自動化を {host} に移動しました。", - "moveOriginalKept": "{host} に作成しましたが、元の自動化を削除できませんでした。元のホストで削除してください。", "77b81bc4ac": "Hermes オートメーションが作成されました。", "08efc3ae12": "Hermes オートメーションが更新されました。", "e431bb85d4": "この Hermes オートメーションと同じホスト上のワークスペースを選択します。", @@ -14088,8 +14086,7 @@ "stale": "このフォームを開いている間に {host} が変更されました。保存する前にもう一度選択してください。", "projectMismatch": "そのプロジェクトは {host} にありません。そのホスト上のプロジェクト、または別のホストを選択してください。", "noProjects": "{host} にはプロジェクトが設定されていません。そこに追加するか、別のホストを選択してください。", - "updateRequired": "{hosts} の Orca サーバーを更新すると、そこに自動化を保存できます。", - "move": "保存すると、この自動化は {host} に作成され、元の自動化は削除されます。実行履歴は元のホストに残ります。" + "updateRequired": "{hosts} の Orca サーバーを更新すると、そこに自動化を保存できます。" } }, "agent": { diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json index 175735ba5e5..421c6770464 100644 --- a/src/renderer/src/i18n/locales/ko.json +++ b/src/renderer/src/i18n/locales/ko.json @@ -13911,8 +13911,6 @@ "b11170a008": "자동화를 저장하지 못했습니다.", "2a20596d6b": "자동화가 저장되었습니다.", "244727e655": "자동화가 업데이트되었습니다.", - "moved": "자동화를 {host}(으)로 이동했습니다.", - "moveOriginalKept": "{host}에 생성했지만 원본을 삭제하지 못했습니다. 이전 호스트에서 삭제하세요.", "77b81bc4ac": "Hermes 자동화가 생성되었습니다.", "08efc3ae12": "Hermes 자동화가 업데이트되었습니다.", "e431bb85d4": "이 Hermes 자동화와 동일한 호스트에서 워크스페이스를 선택하세요.", @@ -14120,8 +14118,7 @@ "stale": "이 양식이 열려 있는 동안 {host}이(가) 변경되었습니다. 저장하기 전에 다시 선택하세요.", "projectMismatch": "해당 프로젝트는 {host}에 없습니다. 그 호스트의 프로젝트를 선택하거나 다른 호스트를 선택하세요.", "noProjects": "{host}에 설정된 프로젝트가 없습니다. 거기에 추가하거나 다른 호스트를 선택하세요.", - "updateRequired": "{hosts}의 Orca 서버를 업데이트하면 거기에 자동화를 저장할 수 있습니다.", - "move": "저장하면 이 자동화가 {host}에 생성되고 원본은 삭제됩니다. 실행 기록은 이전 호스트에 남습니다." + "updateRequired": "{hosts}의 Orca 서버를 업데이트하면 거기에 자동화를 저장할 수 있습니다." } }, "agent": { diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json index 2ad8a4a2b99..ceb26968d58 100644 --- a/src/renderer/src/i18n/locales/zh.json +++ b/src/renderer/src/i18n/locales/zh.json @@ -13935,8 +13935,6 @@ "b11170a008": "无法保存自动化。", "2a20596d6b": "自动化已保存。", "244727e655": "自动化已更新。", - "moved": "自动化已移动到 {host}。", - "moveOriginalKept": "已在 {host} 上创建,但无法删除原有自动化。请在原主机上删除它。", "77b81bc4ac": "Hermes 自动化创建。", "08efc3ae12": "Hermes 自动化已更新。", "e431bb85d4": "选择与此 Hermes 自动化位于同一主机上的工作区。", @@ -14144,8 +14142,7 @@ "stale": "此表单打开期间 {host} 已更改。保存前请重新选择。", "projectMismatch": "该项目不在 {host} 上。请选择该主机上的项目,或选择另一台主机。", "noProjects": "{host} 上未设置任何项目。请在该主机上添加一个,或选择另一台主机。", - "updateRequired": "更新 {hosts} 上的 Orca 服务器即可在其中存储自动化。", - "move": "保存后会在 {host} 上创建此自动化并删除原有自动化。运行历史仍保留在原主机上。" + "updateRequired": "更新 {hosts} 上的 Orca 服务器即可在其中存储自动化。" } }, "agent": {