You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by zh...@apache.org on 2026/09/11 02:49:13 UTC

(maka) branch main updated: fix(runtime-host): make managed WSL recovery actionable (#5175)

This is an automated email from the ASF dual-hosted git repository.

me2seeks pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/main by this push:
     new a49ba7544c fix(runtime-host): make managed WSL recovery actionable (#5175)
a49ba7544c is described below

commit a49ba7544c3bdd1ef648ec90c69b8f39f3e881c0
Author: Zhang <96...@users.noreply.github.com>
AuthorDate: Fri Sep 11 10:49:09 2026 +0800

    fix(runtime-host): make managed WSL recovery actionable (#5175)
    
    Tell source TUI users how to return to the managing Desktop for an explicit Host handoff. Run Desktop setup and update through the same WSL user login environment so current zsh, fish, or bash configuration can provide Node and npm.
    
    Show validated development archive versions instead of package integrity. When recovery fails, direct users to the diagnostic and avoid implying that unchanged retries will repair a deterministic failure.
    
    Refs #5143, #5161
    
    Generated-by: OpenAI Codex
---
 .../__tests__/runtime-host-setup-package.test.ts   |  7 +++--
 .../__tests__/runtime-host-wsl-controller.test.ts  |  3 +++
 .../__tests__/runtime-host-wsl-handoff.test.ts     | 24 ++++++++++++++++-
 .../desktop/src/main/runtime-host-setup-package.ts | 31 +++++++++++++++++++---
 .../src/main/runtime-host-wsl-controller.ts        |  9 ++++++-
 apps/desktop/src/main/runtime-host-wsl-handoff.ts  | 11 ++++++--
 .../src/__tests__/runtime-host-cli-context.test.ts |  5 ++--
 .../src/__tests__/host-handoff.test.ts             | 25 +++++++++++++++++
 .../runtime-host/src/client/host-handoff-copy.ts   | 15 ++++++-----
 9 files changed, 113 insertions(+), 17 deletions(-)

diff --git a/apps/desktop/src/main/__tests__/runtime-host-setup-package.test.ts b/apps/desktop/src/main/__tests__/runtime-host-setup-package.test.ts
index 6b3334113b..fdc0acca29 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-setup-package.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-setup-package.test.ts
@@ -32,7 +32,7 @@ test('development setup lazily caches CLI archives by peer target unless overrid
   const repoRoot = resolve('/workspace');
   const directory = await mkdtemp(join(tmpdir(), 'maka-runtime-host-setup-package-'));
   t.after(() => rm(directory, { recursive: true, force: true }));
-  const archive = join(directory, 'maka-agent-dev.tgz');
+  const archive = join(directory, 'maka-agent-0.2.0-dev-abcdef012345.tgz');
   await writeFile(archive, ARCHIVE_BYTES);
   const canonicalArchive = await realpath(archive);
   let builds = 0;
@@ -64,18 +64,20 @@ test('development setup lazily caches CLI archives by peer target unless overrid
       kind: 'development_archive',
       path: canonicalArchive,
       integrity: ARCHIVE_INTEGRITY,
+      displayVersion: '0.2.0-dev-abcdef012345',
     },
     {
       kind: 'development_archive',
       path: canonicalArchive,
       integrity: ARCHIVE_INTEGRITY,
+      displayVersion: '0.2.0-dev-abcdef012345',
     },
   ]);
   await resolvePackage.resolve('none');
   assert.equal(builds, 2);
   assert.deepEqual(targets, ['linux-x64', 'none']);
 
-  const override = join(directory, 'explicit.tgz');
+  const override = join(directory, 'maka-agent-0.3.0-dev-fedcba543210.tgz');
   await writeFile(override, ARCHIVE_BYTES);
   const resolveOverride = createRuntimeHostSetupPackageResolver({
     isPackaged: false,
@@ -87,6 +89,7 @@ test('development setup lazily caches CLI archives by peer target unless overrid
   assert.equal(snapshot.kind, 'development_archive');
   assert.notEqual(snapshot.path, await realpath(override));
   assert.equal(snapshot.integrity, ARCHIVE_INTEGRITY);
+  assert.equal(snapshot.displayVersion, '0.3.0-dev-fedcba543210');
   await writeFile(override, 'replacement archive');
   assert.deepEqual(await readFile(snapshot.path), ARCHIVE_BYTES);
   assert.deepEqual(await resolveOverride.resolve('none'), snapshot);
diff --git a/apps/desktop/src/main/__tests__/runtime-host-wsl-controller.test.ts b/apps/desktop/src/main/__tests__/runtime-host-wsl-controller.test.ts
index 07447f8a28..67e0c24cdc 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-wsl-controller.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-wsl-controller.test.ts
@@ -180,6 +180,7 @@ test('WSL setup forwards the development archive and its exact evidence', async
     'C:\\maka-development.tgz',
   ]);
   const setupCommand = launches[1]?.at(-1) ?? '';
+  assert.match(setupCommand, /\$\{SHELL:-\/bin\/sh\}.*-lic/u);
   assert.match(setupCommand, new RegExp(`${RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY_ENV}=`, 'u'));
   assert.ok(setupCommand.includes(integrity));
   assert.match(setupCommand, /--update-existing/u);
@@ -248,6 +249,8 @@ test('WSL update cancellation closes retirement input without killing the transa
   });
   await new Promise<void>((resolve) => setImmediate(resolve));
   assert.equal(stdin.writableEnded, false);
+  assert.match(launch.at(-1)!, /\$\{SHELL:-\/bin\/sh\}.*-lic/u);
+  assert.match(launch.at(-1)!, /npx.*--package.*maka-agent@0\.3\.0/u);
   assert.match(launch.at(-1)!, /--expected-config-fingerprint/u);
   assert.match(launch.at(-1)!, /--expected-host-json/u);
   assert.doesNotMatch(launch.at(-1)!, /--allow-interrupt-active-tasks/u);
diff --git a/apps/desktop/src/main/__tests__/runtime-host-wsl-handoff.test.ts b/apps/desktop/src/main/__tests__/runtime-host-wsl-handoff.test.ts
index 4c4697026b..4d82b92e7a 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-wsl-handoff.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-wsl-handoff.test.ts
@@ -96,7 +96,10 @@ test('legacy operator without a configuration fingerprint remains fenced by sour
   const { configurationFingerprint: _fingerprint, ...legacyService } = service;
   const blocker = await resolveDesktopWslHostHandoff(profile, incompatible(), new AbortController().signal, {
     resolveBinding: async () => binding,
-    resolvePackage: async () => ({ kind: 'development_archive', path: '/selected.tgz', integrity: 'sha512-selected' }),
+    resolvePackage: async () => ({
+      kind: 'development_archive', path: '/selected.tgz', integrity: 'sha512-selected',
+      displayVersion: '0.3.0-dev-abcdef012345',
+    }),
     status: async () => ({ schemaVersion: 1, kind: 'result', action: 'status', service: legacyService }),
     update: async (input) => {
       assert.equal(input.expectedConfigFingerprint, undefined);
@@ -107,5 +110,24 @@ test('legacy operator without a configuration fingerprint remains fenced by sour
     },
   });
   assert.ok(blocker.replacement);
+  assert.deepEqual(blocker.packageChange, {
+    current: legacyService.installedVersion,
+    target: '0.3.0-dev-abcdef012345',
+  });
   assert.deepEqual(await blocker.replacement.execute('refuse_active_work', () => {}, 'explicit'), { kind: 'changed' });
 });
+
+test('development handoff never presents package integrity as a version', async () => {
+  const blocker = await resolveDesktopWslHostHandoff(profile, incompatible(), new AbortController().signal, {
+    resolveBinding: async () => binding,
+    resolvePackage: async () => ({
+      kind: 'development_archive', path: '/selected.tgz', integrity: 'sha512-selected',
+    }),
+    status: async () => ({ schemaVersion: 1, kind: 'result', action: 'status', service }),
+    update: async () => assert.fail('displaying the package change must not start an update'),
+  });
+  assert.deepEqual(blocker.packageChange, {
+    current: service.installedVersion,
+    target: 'selected development build',
+  });
+});
diff --git a/apps/desktop/src/main/runtime-host-setup-package.ts b/apps/desktop/src/main/runtime-host-setup-package.ts
index 697ee54747..533cc3d151 100644
--- a/apps/desktop/src/main/runtime-host-setup-package.ts
+++ b/apps/desktop/src/main/runtime-host-setup-package.ts
@@ -22,11 +22,12 @@ import { createHash } from 'node:crypto';
 import { createReadStream, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
 import { copyFile, mkdtemp, realpath, rm, stat } from 'node:fs/promises';
 import { tmpdir } from 'node:os';
-import { isAbsolute, join, relative, resolve } from 'node:path';
+import { basename, isAbsolute, join, relative, resolve } from 'node:path';
 import {
   DEFAULT_PROCESS_TERMINATION_GRACE_MS,
   terminateChildProcessTree,
 } from '@maka/runtime/process-tree-terminator';
+import { isProductReleaseVersion } from '@maka/runtime-host/operator';
 const DEVELOPMENT_ARCHIVE_ENV = 'MAKA_RUNTIME_HOST_SETUP_ARCHIVE';
 
 export type DesktopRuntimeHostSetupPackage =
@@ -35,6 +36,8 @@ export type DesktopRuntimeHostSetupPackage =
       readonly kind: 'development_archive';
       readonly path: string;
       readonly integrity: string;
+      /** Presentation only. Package validation remains authoritative during staging. */
+      readonly displayVersion?: string;
     };
 
 function isExactRuntimeHostSetupPackageSpecifier(value: unknown): value is string {
@@ -53,6 +56,14 @@ export function runtimeHostSetupPackageVersion(
   return setupPackage.specifier.slice('maka-agent@'.length);
 }
 
+export function runtimeHostSetupPackageDisplayVersion(
+  setupPackage: DesktopRuntimeHostSetupPackage,
+): string | undefined {
+  return setupPackage.kind === 'development_archive'
+    ? setupPackage.displayVersion
+    : runtimeHostSetupPackageVersion(setupPackage);
+}
+
 interface DevelopmentArchiveBuild {
   readonly result: Promise<string>;
   close(): Promise<void>;
@@ -324,15 +335,19 @@ function startDevelopmentArchiveBuild(
   };
 }
 
-async function developmentSetupPackage(path: string): Promise<DesktopRuntimeHostSetupPackage> {
+async function developmentSetupPackage(
+  path: string,
+): Promise<Extract<DesktopRuntimeHostSetupPackage, { readonly kind: 'development_archive' }>> {
   const archive = await realpath(path);
   if (!(await stat(archive)).isFile() || !archive.endsWith('.tgz')) {
     throw new Error('Runtime Host development package must be a .tgz file');
   }
+  const displayVersion = developmentArchiveDisplayVersion(archive);
   return {
     kind: 'development_archive',
     path: archive,
     integrity: await sha512Integrity(archive),
+    ...(displayVersion ? { displayVersion } : {}),
   };
 }
 
@@ -348,13 +363,23 @@ async function snapshotDevelopmentSetupPackage(path: string): Promise<{
   const snapshot = join(root, 'package.tgz');
   try {
     await copyFile(source, snapshot);
-    return { root, setupPackage: await developmentSetupPackage(snapshot) };
+    const setupPackage = await developmentSetupPackage(snapshot);
+    const displayVersion = developmentArchiveDisplayVersion(source);
+    return {
+      root,
+      setupPackage: displayVersion ? { ...setupPackage, displayVersion } : setupPackage,
+    };
   } catch (error) {
     await rm(root, { recursive: true, force: true });
     throw error;
   }
 }
 
+function developmentArchiveDisplayVersion(path: string): string | undefined {
+  const match = /^maka-agent-(.+)\.tgz$/u.exec(basename(path));
+  return match?.[1] && isProductReleaseVersion(match[1]) ? match[1] : undefined;
+}
+
 function sha512Integrity(path: string): Promise<string> {
   return new Promise((resolveIntegrity, reject) => {
     const hash = createHash('sha512');
diff --git a/apps/desktop/src/main/runtime-host-wsl-controller.ts b/apps/desktop/src/main/runtime-host-wsl-controller.ts
index ba04338317..ba440ef916 100644
--- a/apps/desktop/src/main/runtime-host-wsl-controller.ts
+++ b/apps/desktop/src/main/runtime-host-wsl-controller.ts
@@ -255,7 +255,10 @@ export async function runDesktopRuntimeHostWslUpdate(
   const environment = `${RUNTIME_HOST_OPERATOR_RETIREMENT_CANCELLATION_ENV}=1 ` +
     (setupPackage.integrity ? `${RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY_ENV}=${quotePosix(setupPackage.integrity)} ` : '');
   const command = `maka_prefix=$(mktemp -d) || exit 1; trap 'rm -rf -- "$maka_prefix"' EXIT; cd "$maka_prefix" || exit 1; ${environment}${args.map(quotePosix).join(' ')}`;
-  const child = processFactory(executable, ['--distribution', distribution, '--exec', '/bin/sh', '-lc', `exec /bin/sh -c ${quotePosix(command)}`]);
+  const child = processFactory(executable, [
+    '--distribution', distribution, '--exec', '/bin/sh', '-lc',
+    runtimeHostWslLoginCommand(command),
+  ]);
   const terminal = await runWslFramedProcess({
     child, signal: input.signal, retirementCancellation: true,
     prefix: RUNTIME_HOST_SERVICE_MANAGEMENT_FRAME_PREFIX,
@@ -336,6 +339,10 @@ function runtimeHostWslSetupCommand(
     ? `${RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY_ENV}=${quotePosix(setupPackage.integrity)} `
     : '';
   const command = `maka_prefix=$(mktemp -d) || exit 1; trap 'rm -rf -- "$maka_prefix"' EXIT; cd "$maka_prefix" || exit 1; ${environment}${invocation}`;
+  return runtimeHostWslLoginCommand(command);
+}
+
+function runtimeHostWslLoginCommand(command: string): string {
   const loginCommand = `exec /bin/sh -c ${quotePosix(command)}`;
   return `exec "\${SHELL:-/bin/sh}" -lic ${quotePosix(loginCommand)}`;
 }
diff --git a/apps/desktop/src/main/runtime-host-wsl-handoff.ts b/apps/desktop/src/main/runtime-host-wsl-handoff.ts
index bd7921838a..e1847646ec 100644
--- a/apps/desktop/src/main/runtime-host-wsl-handoff.ts
+++ b/apps/desktop/src/main/runtime-host-wsl-handoff.ts
@@ -21,7 +21,7 @@ import type { UiLocale } from '@maka/core/ui-locale';
 import type { EnvironmentRuntimeHostProfile, HostHandoffBlocker, HostHandoffPhase, RuntimeHostRemoteCompatibilityError } from '@maka/runtime-host/client';
 import { compareProductReleaseVersions } from '@maka/runtime-host/operator';
 import type { DesktopRuntimeHostManagedServiceBinding } from './runtime-host-managed-services.js';
-import { runtimeHostSetupPackageVersion, type DesktopRuntimeHostSetupPackage } from './runtime-host-setup-package.js';
+import { runtimeHostSetupPackageDisplayVersion, runtimeHostSetupPackageVersion, type DesktopRuntimeHostSetupPackage } from './runtime-host-setup-package.js';
 import { runDesktopRuntimeHostWslManagement, runDesktopRuntimeHostWslUpdate } from './runtime-host-wsl-controller.js';
 
 /** A WSL route is a capability only after its persisted management binding is validated. */
@@ -68,6 +68,7 @@ export async function resolveDesktopWslHostHandoff(
   }
   const setupPackage = await deps.resolvePackage(signal);
   const targetVersion = runtimeHostSetupPackageVersion(setupPackage);
+  const targetDisplayVersion = runtimeHostSetupPackageDisplayVersion(setupPackage);
   if (targetVersion && (/(?:-|\.)dev-[0-9a-f]{12}$/u.test(status.service.installedVersion) || compareProductReleaseVersions(targetVersion, status.service.installedVersion) <= 0)) {
     return { ...base, operatorStep: guidance.target };
   }
@@ -79,7 +80,10 @@ export async function resolveDesktopWslHostHandoff(
   const identity = JSON.stringify([base.identity, binding.deployment, expectedHost, currentVersion, expectedConfigFingerprint, setupPackage]);
   return {
     ...base, identity,
-    packageChange: { current: currentVersion, target: targetVersion ?? (setupPackage.kind === 'development_archive' ? setupPackage.integrity : setupPackage.specifier) },
+    packageChange: {
+      current: currentVersion,
+      target: targetDisplayVersion ?? guidance.development,
+    },
     replacement: {
       kind: 'replace', canReplaceIdle: true, canInterrupt: true, requiresExplicitSelection: true,
       execute: async (policy, progress, _consent, retirementSignal) => {
@@ -112,17 +116,20 @@ const GUIDANCE = {
     binding: 'The WSL management binding is unavailable. Restore it through Runtime Host settings before updating.',
     source: 'The installed WSL operator could not verify the running on-demand Host. Recheck its service status in Runtime Host settings.',
     target: 'This Desktop cannot verify a suitable replacement package. Update Desktop or manage the selected development artifact explicitly; the existing Host will be preserved.',
+    development: 'selected development build',
   },
   'zh-CN': {
     client: '请更新 Desktop,使它与此 Host 兼容。不会降级现有 Host。',
     binding: 'WSL 管理绑定不可用。请先在 Runtime Host 设置中恢复管理入口,再进行更新。',
     source: '已安装的 WSL 管理程序无法验证正在运行的按需 Host。请在 Runtime Host 设置中检查服务状态。',
     target: '当前 Desktop 无法验证合适的替换包。请更新 Desktop,或通过开发环境明确管理选定的开发包;现有 Host 会被保留。',
+    development: '选定的开发构建',
   },
   'zh-TW': {
     client: '請更新 Desktop,使它與此 Host 相容。不會降級現有 Host。',
     binding: 'WSL 管理綁定不可用。請先在 Runtime Host 設定中恢復管理入口,再進行更新。',
     source: '已安裝的 WSL 管理程式無法驗證正在執行的按需 Host。請在 Runtime Host 設定中檢查服務狀態。',
     target: '目前 Desktop 無法驗證合適的替換套件。請更新 Desktop,或透過開發環境明確管理選定的開發套件;現有 Host 會被保留。',
+    development: '選定的開發構建',
   },
 } as const;
diff --git a/packages/cli/src/__tests__/runtime-host-cli-context.test.ts b/packages/cli/src/__tests__/runtime-host-cli-context.test.ts
index 136ec2828d..7f91046998 100644
--- a/packages/cli/src/__tests__/runtime-host-cli-context.test.ts
+++ b/packages/cli/src/__tests__/runtime-host-cli-context.test.ts
@@ -340,7 +340,7 @@ test('CLI explains a service Host without inventing resident work', async () =>
       assert.ok(error instanceof HostHandoffRequiredError);
       assert.equal(error.view.reason, 'operator_required');
       assert.equal(error.view.mayExitNaturally, false);
-      assert.match(error.message, /operator/);
+      assert.match(error.message, /managed by the Maka installation that created it/);
       assert.doesNotMatch(error.message, /not idle/);
       return true;
     },
@@ -811,6 +811,7 @@ test('activated managed Host incompatibility stays operator-owned', async () =>
       {
         connectOrSpawn: async () => ({ kind: 'failed', reason: 'managed_root_requires_operator' }),
         activateLocalManagedHost: async () => {},
+        resolveManagedAuthority: async () => ({ record: {} }) as never,
         connectActivatedHost: async () => ({
           kind: 'incompatible',
           registration: hostRegistration(),
@@ -822,7 +823,7 @@ test('activated managed Host incompatibility stays operator-owned', async () =>
       assert.ok(error instanceof HostHandoffRequiredError);
       assert.equal(error.view.reason, 'operator_required');
       assert.deepEqual(error.view.actions, ['cancel', 'retry']);
-      assert.match(error.message, /operator/);
+      assert.match(error.message, /Desktop.*Stop old service and continue/su);
       return true;
     },
   );
diff --git a/packages/runtime-host/src/__tests__/host-handoff.test.ts b/packages/runtime-host/src/__tests__/host-handoff.test.ts
index 0a3375e8ae..6b61174fce 100644
--- a/packages/runtime-host/src/__tests__/host-handoff.test.ts
+++ b/packages/runtime-host/src/__tests__/host-handoff.test.ts
@@ -120,6 +120,29 @@ test('handoff copy exposes background work even with zero operations and keeps l
   }
 });
 
+test('managed handoff copy gives the user an executable Desktop recovery path', () => {
+  const view: HostHandoffView = {
+    revision: 'managed',
+    target,
+    state: 'attention',
+    reason: 'operator_required',
+    mayExitNaturally: false,
+    actions: ['cancel', 'retry'],
+    defaultAction: 'cancel',
+    recoveryBlocker: 'managed',
+  };
+  for (const [locale, expected] of [
+    [
+      'en',
+      'Desktop installed it, open this workspace there and choose Stop old service and continue',
+    ],
+    ['zh-CN', '在该 Desktop 中打开此工作区,然后选择“停止旧服务并继续”'],
+    ['zh-TW', '在該 Desktop 中開啟此工作區,然後選擇「停止舊服務並繼續」'],
+  ] as const) {
+    assert.match(formatHostHandoff(view, locale).description, new RegExp(expected, 'u'));
+  }
+});
+
 test('maintenance evidence distinguishes idle retention without guessing for legacy activity', () => {
   const retention = { ...idle, residencies: [{ label: 'process-retention', count: 1 }] };
   assert.equal(isHostActivityIdle(decodeHostActivitySnapshot(retention)), false);
@@ -486,6 +509,8 @@ test('transaction failure remains a repair outcome instead of an automatic retry
       assert.ok(error instanceof HostHandoffRequiredError);
       assert.equal(error.view.reason, 'repair_required');
       assert.equal(error.view.diagnostic, 'Writer release was not verified');
+      assert.match(formatHostHandoff(error.view, 'zh-CN').description, /修复原因后再重试/u);
+      assert.match(formatHostHandoff(error.view, 'en').description, /without a state change/u);
       return true;
     },
   );
diff --git a/packages/runtime-host/src/client/host-handoff-copy.ts b/packages/runtime-host/src/client/host-handoff-copy.ts
index e14e1ef337..b09b4b8b66 100644
--- a/packages/runtime-host/src/client/host-handoff-copy.ts
+++ b/packages/runtime-host/src/client/host-handoff-copy.ts
@@ -66,7 +66,8 @@ export function formatHostHandoff(
         activity_unknown:
           '背景服務與目前用戶端不相容,且無法確認有哪些工作仍在執行。停止並繼續會重新啟動服務,可能中斷其他視窗或裝置上的工作。',
         operator_required: `目前無法從這裡更新背景服務。請透過 ${view.target.name} 上管理此服務的應用程式或命令更新,再重試。`,
-        repair_required: '上次啟動或交接未能完成。可以安全重試;若仍失敗,請複製診斷資訊。',
+        repair_required:
+          '上次啟動或交接失敗。請查看下方診斷資訊,修復原因後再重試;如果狀態沒有改變,重複重試通常不會解決問題。',
         retry_required: '服務狀態發生了變化,或交接尚未完成。可以安全重試,不會預設中斷工作。',
       }
     : zh
@@ -77,7 +78,8 @@ export function formatHostHandoff(
           activity_unknown:
             '后台服务与当前客户端不兼容,且无法确认有哪些工作仍在运行。停止并继续会重新启动服务,可能中断其他窗口或设备上的工作。',
           operator_required: `目前无法从这里更新后台服务。请通过 ${view.target.name} 上管理此服务的应用或命令更新,然后重试。`,
-          repair_required: '上次启动或交接未能完成。可以安全重试;若仍失败,请复制诊断信息。',
+          repair_required:
+            '上次启动或交接失败。请查看下方诊断信息,修复原因后再重试;如果状态没有变化,重复重试通常不会解决问题。',
           retry_required: '服务状态发生了变化,或交接尚未完成。可以安全重试,不会默认中断工作。',
         }
       : {
@@ -88,7 +90,7 @@ export function formatHostHandoff(
             'The background service is incompatible with this client, and its active work is unknown. Stop and continue restarts it and may interrupt work in other windows or devices.',
           operator_required: `This client cannot update the background service here. Use its managing app or operator command on ${view.target.name}, then retry.`,
           repair_required:
-            'The last startup or handoff did not finish. Retry safely, or copy diagnostics if it still fails.',
+            'The last startup or handoff failed. Review the diagnostic below, correct the cause, then retry. Repeating the same action without a state change will usually fail again.',
           retry_required:
             'The service changed or the handoff has not finished. A safe retry will not interrupt work by default.',
         };
@@ -97,7 +99,7 @@ export function formatHostHandoff(
       ? tw
         ? {
             managed:
-              '此背景服務由已安裝的管理程式維護。請在服務的管理應用程式中更新,再回到這裡重試。',
+              '此背景服務由安裝它的 Maka 管理。如果由 Desktop 安裝,請在該 Desktop 中開啟此工作區,然後選擇「停止舊服務並繼續」;否則請使用安裝它的 Maka 更新服務,再回到這裡重試。',
             owner: '此背景服務屬於另一個 Maka 安裝。請使用管理它的 Maka 安裝更新,再重試。',
             installation:
               '這次啟動使用暫存安裝,無法接管現有背景服務。請使用已安裝的 Maka 更新服務,再重試。',
@@ -105,7 +107,8 @@ export function formatHostHandoff(
               '無法確認舊背景服務的處理程序身分,因此不能安全停止它。請關閉啟動它的 Maka 或透過其管理程式停止服務,再重試。',
           }
         : {
-            managed: '此后台服务由已安装的管理程序维护。请在服务的管理应用中更新,再回到这里重试。',
+            managed:
+              '此后台服务由安装它的 Maka 管理。如果由 Desktop 安装,请在该 Desktop 中打开此工作区,然后选择“停止旧服务并继续”;否则请使用安装它的 Maka 更新服务,再回到这里重试。',
             owner: '此后台服务属于另一个 Maka 安装。请使用管理它的 Maka 安装更新,然后重试。',
             installation:
               '本次启动使用临时安装,无法接管现有后台服务。请使用已安装的 Maka 更新服务,然后重试。',
@@ -114,7 +117,7 @@ export function formatHostHandoff(
           }
       : {
           managed:
-            'An installed operator manages this background service. Update it through its managing app, then return here and retry.',
+            'This background service is managed by the Maka installation that created it. If Desktop installed it, open this workspace there and choose Stop old service and continue. Otherwise update the service with that Maka installation, then return here and retry.',
           owner:
             'Another Maka installation owns this background service. Update it using that installation, then retry.',
           installation:


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org