You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/08/28 13:48:32 UTC

(maka) branch main updated: feat(runtime-host): target development host packages (#4087)

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

M4n5ter 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 cb60d416f feat(runtime-host): target development host packages (#4087)
cb60d416f is described below

commit cb60d416fa0ac72272f1c287219c7ec542ff106f
Author: Wang <m4...@gmail.com>
AuthorDate: Fri Aug 28 21:48:28 2026 +0800

    feat(runtime-host): target development host packages (#4087)
    
    Generated-by: Codex
---
 .../main/__tests__/runtime-host-management.test.ts |   6 ++
 .../main/__tests__/runtime-host-onboarding.test.ts |   9 ++
 .../__tests__/runtime-host-setup-package.test.ts   |  24 +++--
 .../__tests__/runtime-host-ssh-terminal.test.ts    |  32 +++++-
 apps/desktop/src/main/runtime-host-boot.ts         |  14 ++-
 apps/desktop/src/main/runtime-host-management.ts   |  19 +++-
 apps/desktop/src/main/runtime-host-onboarding.ts   |  32 +++++-
 .../desktop/src/main/runtime-host-setup-package.ts | 107 +++++++++++++++------
 apps/desktop/src/main/runtime-host-ssh-terminal.ts |  85 ++++++++++++++++
 native/runtime-host-peer/build.mjs                 |  13 ++-
 scripts/release-cli-package.mjs                    |  85 +++++++++++++---
 11 files changed, 366 insertions(+), 60 deletions(-)

diff --git a/apps/desktop/src/main/__tests__/runtime-host-management.test.ts b/apps/desktop/src/main/__tests__/runtime-host-management.test.ts
index b82385efd..e7d234745 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-management.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-management.test.ts
@@ -425,6 +425,9 @@ test('publishes update progress and waits for the managed profile to reconnect',
     runUpdatePolicy: async () => assert.fail('update policy is not expected'),
     runUpdateReconciliation: async () =>
       assert.fail('update reconciliation is not expected'),
+    setupPackageMode: 'published',
+    resolveSshDevelopmentPeerTarget: async () =>
+      assert.fail('published update must not inspect the development target'),
     resolveUpdatePackage: () => ({ kind: 'npm', specifier: 'maka-agent@1.3.0' }),
     currentHostEpoch: () => 'host-before-update',
     awaitUpdatedConnection: async (...args) => {
@@ -1175,6 +1178,9 @@ function unusedUpdateDependencies() {
     runPeerManagement: async (): Promise<never> =>
       assert.fail('direct peer management is not expected'),
     directPeerClientAvailable: false,
+    setupPackageMode: 'published' as const,
+    resolveSshDevelopmentPeerTarget: async (): Promise<never> =>
+      assert.fail('published update must not inspect the development target'),
     resolveUpdatePackage: () => ({ kind: 'npm', specifier: 'maka-agent@1.2.3' } as const),
     currentHostEpoch: () => undefined,
     awaitUpdatedConnection: async () => undefined,
diff --git a/apps/desktop/src/main/__tests__/runtime-host-onboarding.test.ts b/apps/desktop/src/main/__tests__/runtime-host-onboarding.test.ts
index 2eae7b7d4..c40b096d2 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-onboarding.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-onboarding.test.ts
@@ -94,6 +94,7 @@ test('persists a verified on-demand SSH profile without endpoint or credential p
 
 test('onboards WSL as a credential-free environment profile', async () => {
   let saved: DesktopRuntimeHostProfileAddInput | undefined;
+  const peerTargets: string[] = [];
   const harness = createHarness({
     profiles: {
       addAndEnable: async (input) => {
@@ -108,6 +109,10 @@ test('onboards WSL as a credential-free environment profile', async () => {
         operatorPath: '/home/operator/.local/share/maka/operator',
       };
     },
+    resolveSetupPackage: (peerTarget) => {
+      peerTargets.push(peerTarget);
+      return { kind: 'npm', specifier: 'maka-agent@0.2.0' };
+    },
   });
 
   const result = await harness.invoke('runtime-host-onboarding:start', {
@@ -125,6 +130,7 @@ test('onboards WSL as a credential-free environment profile', async () => {
     rootId: 'a'.repeat(64),
     operatorPath: '/home/operator/.local/share/maka/operator',
   });
+  assert.deepEqual(peerTargets, ['none']);
   await harness.onboarding.close();
 });
 
@@ -294,6 +300,9 @@ function createHarness(overrides: HarnessOverrides = {}) {
       addAndEnableVerified: async () => assert.fail('profile must not be saved'),
       ...profiles,
     },
+    setupPackageMode: 'published',
+    resolveSshDevelopmentPeerTarget: async () =>
+      assert.fail('published setup must not inspect the development target'),
     resolveSetupPackage: () => ({ kind: 'npm', specifier: 'maka-agent@0.2.0' }),
     runSetup: async () => assert.fail('SSH must not start'),
     runWslSetup: async () => assert.fail('WSL must not start'),
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 c5e9ecec4..6827082f2 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
@@ -23,17 +23,19 @@ import { join, resolve } from 'node:path';
 import { test } from 'node:test';
 import { createRuntimeHostSetupPackageResolver } from '../runtime-host-setup-package.js';
 
-test('development setup lazily builds one local CLI archive unless explicitly overridden', async () => {
+test('development setup lazily caches CLI archives by peer target unless overridden', async () => {
   const repoRoot = resolve('/workspace');
   const archive = join(repoRoot, 'packages', 'cli', 'release', 'maka-agent-dev.tgz');
   let builds = 0;
   let closes = 0;
+  const targets: string[] = [];
   const resolvePackage = createRuntimeHostSetupPackageResolver({
     isPackaged: false,
     appPath: join(repoRoot, 'apps', 'desktop'),
     environment: {},
-    startDevelopmentArchiveBuild: (resolvedRoot) => {
+    startDevelopmentArchiveBuild: (resolvedRoot, target) => {
       builds += 1;
+      targets.push(target);
       assert.equal(resolvedRoot, repoRoot);
       return {
         result: Promise.resolve(archive),
@@ -44,7 +46,11 @@ test('development setup lazily builds one local CLI archive unless explicitly ov
     },
   });
 
-  assert.deepEqual(await Promise.all([resolvePackage.resolve(), resolvePackage.resolve()]), [
+  assert.equal(resolvePackage.mode, 'development');
+  assert.deepEqual(await Promise.all([
+    resolvePackage.resolve('linux-x64'),
+    resolvePackage.resolve('linux-x64'),
+  ]), [
     {
       kind: 'development_archive',
       path: archive,
@@ -54,7 +60,9 @@ test('development setup lazily builds one local CLI archive unless explicitly ov
       path: archive,
     },
   ]);
-  assert.equal(builds, 1);
+  await resolvePackage.resolve('none');
+  assert.equal(builds, 2);
+  assert.deepEqual(targets, ['linux-x64', 'none']);
 
   const override = join(tmpdir(), 'explicit.tgz');
   const resolveOverride = createRuntimeHostSetupPackageResolver({
@@ -63,12 +71,12 @@ test('development setup lazily builds one local CLI archive unless explicitly ov
     environment: { MAKA_RUNTIME_HOST_SETUP_ARCHIVE: override },
     startDevelopmentArchiveBuild: () => assert.fail('override must bypass the local build'),
   });
-  assert.deepEqual(await resolveOverride.resolve(), {
+  assert.deepEqual(await resolveOverride.resolve('none'), {
     kind: 'development_archive',
     path: override,
   });
   await Promise.all([resolvePackage.close(), resolveOverride.close()]);
-  assert.equal(closes, 1);
+  assert.equal(closes, 2);
 });
 
 test('cancelling the last waiter closes its build before a new setup starts', async () => {
@@ -107,10 +115,10 @@ test('cancelling the last waiter closes its build before a new setup starts', as
     },
   });
 
-  const first = resolver.resolve(cancelled.signal);
+  const first = resolver.resolve('linux-x64', cancelled.signal);
   cancelled.abort(new Error('setup cancelled'));
   await closeStarted;
-  const second = resolver.resolve();
+  const second = resolver.resolve('linux-x64');
   await Promise.resolve();
   assert.equal(builds, 1);
 
diff --git a/apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts b/apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts
index 3bbc6c564..c5a918c24 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-ssh-terminal.test.ts
@@ -33,7 +33,37 @@ import {
   RUNTIME_HOST_OPERATOR_PEER_MANAGEMENT_CAPABILITY,
   RUNTIME_HOST_SETUP_FRAME_PREFIX,
 } from '@maka/runtime-host/operator';
-import { createDesktopRuntimeHostSshTerminal } from '../runtime-host-ssh-terminal.js';
+import {
+  createDesktopRuntimeHostSshTerminal,
+  runtimeHostDevelopmentPeerTargetFromUname,
+} from '../runtime-host-ssh-terminal.js';
+
+test('maps supported SSH uname identities to development peer targets', () => {
+  assert.equal(runtimeHostDevelopmentPeerTargetFromUname('Linux', 'x86_64'), 'linux-x64');
+  assert.equal(runtimeHostDevelopmentPeerTargetFromUname('Linux', 'aarch64'), 'linux-arm64');
+  assert.equal(runtimeHostDevelopmentPeerTargetFromUname('Darwin', 'arm64'), 'darwin-arm64');
+  assert.throws(
+    () => runtimeHostDevelopmentPeerTargetFromUname('Linux', 'riscv64'),
+    /not available/u,
+  );
+});
+
+test('detects the development peer target through the bounded SSH preflight', async () => {
+  const harness = createHarness('pending');
+  const detection = harness.terminal.resolveDevelopmentPeerTarget({
+    destination: 'operator@example.com',
+  });
+  await waitFor(() => harness.pty.hasDataListener());
+  const command = harness.launchArgs[0]?.at(-1) ?? '';
+  const marker = command.match(/__MAKA_RUNTIME_HOST_TARGET_[0-9a-f]+__/u)?.[0];
+  assert.ok(marker);
+  harness.pty.emitData(`${marker}Linux:x86_64\r\n`);
+  harness.pty.exit(0);
+
+  assert.equal(await detection, 'linux-x64');
+  assert.doesNotMatch(JSON.stringify(harness.events), /MAKA_RUNTIME_HOST_TARGET/u);
+  await harness.terminal.close();
+});
 
 test('keeps a connecting SSH prompt observable across renderer presentation changes', async () => {
   const harness = createHarness('pending');
diff --git a/apps/desktop/src/main/runtime-host-boot.ts b/apps/desktop/src/main/runtime-host-boot.ts
index 0bbce55db..5476d7e02 100644
--- a/apps/desktop/src/main/runtime-host-boot.ts
+++ b/apps/desktop/src/main/runtime-host-boot.ts
@@ -172,7 +172,10 @@ import {
   createDesktopRuntimeHostSshTerminal,
 } from "./runtime-host-ssh-terminal.js";
 import { runDesktopRuntimeHostWslSetup } from './runtime-host-wsl-controller.js';
-import { createRuntimeHostSetupPackageResolver } from "./runtime-host-setup-package.js";
+import {
+  createRuntimeHostSetupPackageResolver,
+  desktopRuntimeHostDevelopmentPeerTarget,
+} from "./runtime-host-setup-package.js";
 import { configureDesktopRuntimeHostPeerClient } from './runtime-host-peer-client.js';
 import { createDesktopRuntimeHostLocalOperator } from './runtime-host-local-operator.js';
 import { createDesktopLocalRuntimeHostRemoteAccess } from './runtime-host-local-remote-access.js';
@@ -399,7 +402,10 @@ const localRuntimeHostRemoteAccess = createDesktopLocalRuntimeHostRemoteAccess({
   rootId: startupLocalStorageRoot.rootId,
   directPeerAvailable: runtimeHostDirectPeerAvailable,
   manager: () => runtimeHostManager,
-  resolveSetupPackage: runtimeHostSetupPackage.resolve,
+  resolveSetupPackage: (signal) => runtimeHostSetupPackage.resolve(
+    desktopRuntimeHostDevelopmentPeerTarget(),
+    signal,
+  ),
   operator: localRuntimeHostOperator,
 });
 const native = assembleDesktopNativeCapabilities({
@@ -474,6 +480,8 @@ const runtimeHostOnboarding = createDesktopRuntimeHostOnboarding({
   runSetup: runtimeHostSshTerminal.runSetup,
   runWslSetup: runDesktopRuntimeHostWslSetup,
   listWslDistributions: listRuntimeHostWslDistributions,
+  setupPackageMode: runtimeHostSetupPackage.mode,
+  resolveSshDevelopmentPeerTarget: runtimeHostSshTerminal.resolveDevelopmentPeerTarget,
   resolveSetupPackage: runtimeHostSetupPackage.resolve,
   send: (snapshot) =>
     mainWindowController.send("runtime-host-onboarding:changed", snapshot),
@@ -487,6 +495,8 @@ const runtimeHostManagement = createDesktopRuntimeHostManagement({
   runUpdate: runtimeHostSshTerminal.runUpdate,
   runUpdatePolicy: runtimeHostSshTerminal.runUpdatePolicy,
   runUpdateReconciliation: runtimeHostSshTerminal.runUpdateReconciliation,
+  setupPackageMode: runtimeHostSetupPackage.mode,
+  resolveSshDevelopmentPeerTarget: runtimeHostSshTerminal.resolveDevelopmentPeerTarget,
   resolveUpdatePackage: runtimeHostSetupPackage.resolve,
   currentHostEpoch: (profileId) =>
     runtimeHostManager?.current(profileId)?.candidate?.client.hostEpoch,
diff --git a/apps/desktop/src/main/runtime-host-management.ts b/apps/desktop/src/main/runtime-host-management.ts
index 3b82eb0b9..c1e82c737 100644
--- a/apps/desktop/src/main/runtime-host-management.ts
+++ b/apps/desktop/src/main/runtime-host-management.ts
@@ -54,6 +54,7 @@ import type {
   RuntimeHostServiceUpdateReconciliationTerminalFrame,
   RuntimeHostServiceUpdateTerminalFrame,
 } from './runtime-host-ssh-terminal.js';
+import type { DesktopRuntimeHostDevelopmentPeerTarget } from './runtime-host-setup-package.js';
 
 const MANAGEMENT_ACTIONS = new Set<DesktopRuntimeHostManagementAction>([
   'status',
@@ -104,7 +105,15 @@ export function createDesktopRuntimeHostManagement(input: {
     input: DesktopRuntimeHostSshUpdateReconciliationInput,
     onProgress: (phase: DesktopRuntimeHostManagementProgress['phase']) => void,
   ) => Promise<RuntimeHostServiceUpdateReconciliationTerminalFrame>;
-  readonly resolveUpdatePackage: () =>
+  readonly setupPackageMode: 'published' | 'development';
+  readonly resolveSshDevelopmentPeerTarget: (input: {
+    readonly destination: string;
+    readonly sshPort?: number;
+    readonly signal?: AbortSignal;
+  }) => Promise<Exclude<DesktopRuntimeHostDevelopmentPeerTarget, 'none'>>;
+  readonly resolveUpdatePackage: (
+    peerTarget: DesktopRuntimeHostDevelopmentPeerTarget,
+  ) =>
     | DesktopRuntimeHostSetupPackage
     | Promise<DesktopRuntimeHostSetupPackage>;
   readonly currentHostEpoch: (profileId: string) => string | undefined;
@@ -466,7 +475,13 @@ export function createDesktopRuntimeHostManagement(input: {
       await managedMutationTarget(profileIdValue);
     const previousHostEpoch = input.currentHostEpoch(profileId);
     input.sendProgress({ profileId, phase: 'preparing_cli' });
-    const setupPackage = await input.resolveUpdatePackage();
+    const peerTarget = input.setupPackageMode === 'development'
+      ? await input.resolveSshDevelopmentPeerTarget({
+          destination: transport.destination,
+          ...(transport.sshPort === undefined ? {} : { sshPort: transport.sshPort }),
+        })
+      : 'none';
+    const setupPackage = await input.resolveUpdatePackage(peerTarget);
     const response = await input.runUpdate(
       {
         destination: transport.destination,
diff --git a/apps/desktop/src/main/runtime-host-onboarding.ts b/apps/desktop/src/main/runtime-host-onboarding.ts
index 1c9674b8e..fd39a4276 100644
--- a/apps/desktop/src/main/runtime-host-onboarding.ts
+++ b/apps/desktop/src/main/runtime-host-onboarding.ts
@@ -33,6 +33,7 @@ import type {
   DesktopRuntimeHostSshSetupInput,
 } from './runtime-host-ssh-terminal.js';
 import type { DesktopRuntimeHostWslSetupInput } from './runtime-host-wsl-controller.js';
+import type { DesktopRuntimeHostDevelopmentPeerTarget } from './runtime-host-setup-package.js';
 import { requireProjectDirectoryRoots } from '../shared/runtime-host-project-directory-policy.js';
 
 type OnboardingState = DesktopRuntimeHostOnboardingSnapshot extends infer Snapshot
@@ -65,7 +66,14 @@ export function createDesktopRuntimeHostOnboarding(input: {
   ) => Promise<{ readonly rootId: string; readonly operatorPath: string }>;
   readonly listWslDistributions: () => Promise<readonly string[]>;
   readonly send: (snapshot: DesktopRuntimeHostOnboardingSnapshot) => void;
+  readonly setupPackageMode: 'published' | 'development';
+  readonly resolveSshDevelopmentPeerTarget: (input: {
+    readonly destination: string;
+    readonly sshPort?: number;
+    readonly signal?: AbortSignal;
+  }) => Promise<Exclude<DesktopRuntimeHostDevelopmentPeerTarget, 'none'>>;
   readonly resolveSetupPackage: (
+    peerTarget: DesktopRuntimeHostDevelopmentPeerTarget,
     signal?: AbortSignal,
   ) => DesktopRuntimeHostSetupPackage | Promise<DesktopRuntimeHostSetupPackage>;
 }): { close(): Promise<void> } {
@@ -113,8 +121,14 @@ export function createDesktopRuntimeHostOnboarding(input: {
     signal: AbortSignal,
   ): Promise<DesktopRuntimeHostOnboardingSnapshot> => {
     try {
-      const setupPackage = await input.resolveSetupPackage(signal);
-      if (request.kind === 'wsl') return await runWsl(request, setupPackage, signal);
+      if (request.kind === 'wsl') {
+        const setupPackage = await input.resolveSetupPackage('none', signal);
+        return await runWsl(request, setupPackage, signal);
+      }
+      const peerTarget = input.setupPackageMode === 'development'
+        ? await resolveSshDevelopmentPeerTarget(request, signal)
+        : 'none';
+      const setupPackage = await input.resolveSetupPackage(peerTarget, signal);
       const lifecycle = setupPackage.kind === 'npm' ? 'on_demand' : 'supervised';
       signal.throwIfAborted();
       publish({ kind: 'running', phase: 'connecting_ssh' });
@@ -203,6 +217,20 @@ export function createDesktopRuntimeHostOnboarding(input: {
     }
   };
 
+  const resolveSshDevelopmentPeerTarget = async (
+    request: Extract<DesktopRuntimeHostOnboardingInput, { readonly kind: 'ssh' }>,
+    signal: AbortSignal,
+  ): Promise<Exclude<DesktopRuntimeHostDevelopmentPeerTarget, 'none'>> => {
+    publish({ kind: 'running', phase: 'connecting_ssh' });
+    const target = await input.resolveSshDevelopmentPeerTarget({
+      destination: request.destination,
+      ...(request.sshPort === undefined ? {} : { sshPort: request.sshPort }),
+      signal,
+    });
+    publish({ kind: 'running', phase: 'preparing_cli' });
+    return target;
+  };
+
   const runWsl = async (
     request: Extract<DesktopRuntimeHostOnboardingInput, { readonly kind: 'wsl' }>,
     setupPackage: DesktopRuntimeHostSetupPackage,
diff --git a/apps/desktop/src/main/runtime-host-setup-package.ts b/apps/desktop/src/main/runtime-host-setup-package.ts
index 7a9ca7f10..5a182ace5 100644
--- a/apps/desktop/src/main/runtime-host-setup-package.ts
+++ b/apps/desktop/src/main/runtime-host-setup-package.ts
@@ -36,33 +36,68 @@ interface DevelopmentArchiveBuild {
   close(): Promise<void>;
 }
 
+interface DevelopmentBuildState {
+  readonly task: DevelopmentArchiveBuild;
+  readonly result: Promise<DesktopRuntimeHostSetupPackage>;
+  waiters: number;
+  settled: boolean;
+  closing?: Promise<void>;
+}
+
+export type DesktopRuntimeHostDevelopmentPeerTarget =
+  | 'none'
+  | 'darwin-arm64'
+  | 'linux-arm64'
+  | 'linux-x64'
+  | 'win32-x64';
+
 export interface RuntimeHostSetupPackageResolver {
-  resolve(signal?: AbortSignal): Promise<DesktopRuntimeHostSetupPackage>;
+  readonly mode: 'published' | 'development';
+  resolve(
+    peerTarget: DesktopRuntimeHostDevelopmentPeerTarget,
+    signal?: AbortSignal,
+  ): Promise<DesktopRuntimeHostSetupPackage>;
   close(): Promise<void>;
 }
 
+export function desktopRuntimeHostDevelopmentPeerTarget(
+  platform: NodeJS.Platform = process.platform,
+  arch: string = process.arch,
+): Exclude<DesktopRuntimeHostDevelopmentPeerTarget, 'none'> {
+  const target = `${platform}-${arch}`;
+  if (
+    target !== 'darwin-arm64' &&
+    target !== 'linux-arm64' &&
+    target !== 'linux-x64' &&
+    target !== 'win32-x64'
+  ) {
+    throw new Error(`Direct peer is not available on ${target}`);
+  }
+  return target;
+}
+
 export function createRuntimeHostSetupPackageResolver(input: {
   readonly isPackaged: boolean;
   readonly appPath: string;
   readonly environment: NodeJS.ProcessEnv;
-  readonly startDevelopmentArchiveBuild?: (repoRoot: string) => DevelopmentArchiveBuild;
+  readonly startDevelopmentArchiveBuild?: (
+    repoRoot: string,
+    peerTarget: DesktopRuntimeHostDevelopmentPeerTarget,
+  ) => DevelopmentArchiveBuild;
 }): RuntimeHostSetupPackageResolver {
   let closed = false;
-  let developmentBuild:
-    | {
-        readonly task: DevelopmentArchiveBuild;
-        readonly result: Promise<DesktopRuntimeHostSetupPackage>;
-        waiters: number;
-        settled: boolean;
-        closing?: Promise<void>;
-      }
-    | undefined;
+  const developmentBuilds = new Map<
+    DesktopRuntimeHostDevelopmentPeerTarget,
+    DevelopmentBuildState
+  >();
 
-  const startBuild = () => {
+  const startBuild = (
+    peerTarget: DesktopRuntimeHostDevelopmentPeerTarget,
+  ): DevelopmentBuildState => {
     const repoRoot = resolve(input.appPath, '..', '..');
-    const task = input.startDevelopmentArchiveBuild?.(repoRoot) ??
-      startDevelopmentArchiveBuild(repoRoot, input.environment);
-    const build = {
+    const task = input.startDevelopmentArchiveBuild?.(repoRoot, peerTarget) ??
+      startDevelopmentArchiveBuild(repoRoot, input.environment, peerTarget);
+    const build: DevelopmentBuildState = {
       task,
       result: task.result.then((path) => ({
         kind: 'development_archive' as const,
@@ -71,62 +106,71 @@ export function createRuntimeHostSetupPackageResolver(input: {
       waiters: 0,
       settled: false,
     };
-    developmentBuild = build;
+    developmentBuilds.set(peerTarget, build);
     void build.result.then(
       () => {
         build.settled = true;
       },
       () => {
         build.settled = true;
-        if (developmentBuild === build) developmentBuild = undefined;
+        if (developmentBuilds.get(peerTarget) === build) {
+          developmentBuilds.delete(peerTarget);
+        }
       },
     );
     return build;
   };
 
-  const stopBuild = async (build: NonNullable<typeof developmentBuild>) => {
+  const stopBuild = async (
+    peerTarget: DesktopRuntimeHostDevelopmentPeerTarget,
+    build: DevelopmentBuildState,
+  ) => {
     build.closing ??= build.task.close().finally(() => {
-      if (developmentBuild === build) developmentBuild = undefined;
+      if (developmentBuilds.get(peerTarget) === build) developmentBuilds.delete(peerTarget);
     });
     await build.closing;
     await build.result.catch(() => undefined);
   };
 
-  const acquireBuild = async (signal?: AbortSignal) => {
+  const acquireBuild = async (
+    peerTarget: DesktopRuntimeHostDevelopmentPeerTarget,
+    signal?: AbortSignal,
+  ) => {
     while (true) {
       if (closed) throw new Error('Runtime Host setup package resolver is closed');
-      const build = developmentBuild;
-      if (!build) return startBuild();
+      const build = developmentBuilds.get(peerTarget);
+      if (!build) return startBuild(peerTarget);
       if (!build.closing) return build;
       await waitForPackage(build.closing, signal);
     }
   };
 
   return {
-    async resolve(signal) {
+    mode: input.isPackaged ? 'published' : 'development',
+    async resolve(peerTarget, signal) {
       if (closed) throw new Error('Runtime Host setup package resolver is closed');
       if (input.isPackaged) return packagedSetupPackage(input.appPath);
 
       const override = input.environment[DEVELOPMENT_ARCHIVE_ENV];
       if (override) return { kind: 'development_archive', path: override };
 
-      const build = await acquireBuild(signal);
+      const build = await acquireBuild(peerTarget, signal);
       build.waiters += 1;
       try {
         return await waitForPackage(build.result, signal);
       } finally {
         build.waiters -= 1;
         if (signal?.aborted && build.waiters === 0 && !build.settled) {
-          await stopBuild(build);
+          await stopBuild(peerTarget, build);
         }
       }
     },
     async close() {
       if (closed) return;
       closed = true;
-      const build = developmentBuild;
-      developmentBuild = undefined;
-      if (build) await stopBuild(build);
+      const builds = [...developmentBuilds.entries()];
+      developmentBuilds.clear();
+      await Promise.all(builds.map(([peerTarget, build]) => stopBuild(peerTarget, build)));
     },
   };
 }
@@ -144,6 +188,7 @@ function packagedSetupPackage(appPath: string): DesktopRuntimeHostSetupPackage {
 function startDevelopmentArchiveBuild(
   repoRoot: string,
   environment: NodeJS.ProcessEnv,
+  peerTarget: DesktopRuntimeHostDevelopmentPeerTarget,
 ): DevelopmentArchiveBuild {
   const script = join(repoRoot, 'scripts', 'release-cli-package.mjs');
   const nodeExecutable = environment.npm_node_execpath?.trim() || 'node';
@@ -153,7 +198,11 @@ function startDevelopmentArchiveBuild(
   const child = spawn(nodeExecutable, [script, '--development'], {
     cwd: repoRoot,
     detached: process.platform !== 'win32',
-    env: { ...environment, MAKA_CLI_DEVELOPMENT_OUTPUT_ROOT: outputRoot },
+    env: {
+      ...environment,
+      MAKA_CLI_DEVELOPMENT_OUTPUT_ROOT: outputRoot,
+      MAKA_CLI_DEVELOPMENT_PEER_TARGET: peerTarget,
+    },
     stdio: ['ignore', 'pipe', 'pipe'],
     windowsHide: true,
   });
diff --git a/apps/desktop/src/main/runtime-host-ssh-terminal.ts b/apps/desktop/src/main/runtime-host-ssh-terminal.ts
index 1b2d82e21..97b133c39 100644
--- a/apps/desktop/src/main/runtime-host-ssh-terminal.ts
+++ b/apps/desktop/src/main/runtime-host-ssh-terminal.ts
@@ -67,6 +67,7 @@ import type {
   DesktopRuntimeHostSshTerminalSnapshot,
 } from '../preload/bridge-contract.js';
 import { createRuntimeHostFramedOutputFilter } from './runtime-host-framed-output.js';
+import type { DesktopRuntimeHostDevelopmentPeerTarget } from './runtime-host-setup-package.js';
 
 interface ActiveTerminal {
   readonly sessionId: string;
@@ -105,6 +106,12 @@ export interface DesktopRuntimeHostSshSetupInput {
   readonly signal?: AbortSignal;
 }
 
+export interface DesktopRuntimeHostSshTargetInput {
+  readonly destination: string;
+  readonly sshPort?: number;
+  readonly signal?: AbortSignal;
+}
+
 export interface DesktopRuntimeHostSshManagementInput {
   readonly destination: string;
   readonly sshPort?: number;
@@ -236,6 +243,9 @@ export function createDesktopRuntimeHostSshTerminal(input: {
     input: RuntimeHostSshOperatorActivationInput,
   ): Promise<RuntimeHostActivationResult>;
   openSshTunnel(input: RuntimeHostSshTunnelInput): Promise<RuntimeHostSshTunnel>;
+  resolveDevelopmentPeerTarget(
+    input: DesktopRuntimeHostSshTargetInput,
+  ): Promise<Exclude<DesktopRuntimeHostDevelopmentPeerTarget, 'none'>>;
   runSetup(
     input: DesktopRuntimeHostSshSetupInput,
     onProgress: (frame: Extract<RuntimeHostSetupFrame, { kind: 'progress' }>) => void,
@@ -603,6 +613,66 @@ export function createDesktopRuntimeHostSshTerminal(input: {
       }
       return tunnel;
     },
+    resolveDevelopmentPeerTarget: async (targetInput) => {
+      if (closed) throw new Error('Runtime Host SSH terminal is closed');
+      targetInput.signal?.throwIfAborted();
+      const destination = normalizeRuntimeHostSshDestination(targetInput.destination);
+      const sshPort = targetInput.sshPort === undefined
+        ? undefined
+        : requireSetupPort(targetInput.sshPort);
+      const marker = `__MAKA_RUNTIME_HOST_TARGET_${randomUUID().replaceAll('-', '')}__`;
+      const remoteCommand = `printf '${marker}%s:%s\\n' "$(uname -s)" "$(uname -m)"`;
+      let target: Exclude<DesktopRuntimeHostDevelopmentPeerTarget, 'none'> | undefined;
+      let failure: Error | undefined;
+      const filter = createRuntimeHostFramedOutputFilter({
+        prefix: marker,
+        pendingMaxBytes: 256,
+        decode: (line) => line.slice(marker.length).replaceAll('\r', '').trimEnd(),
+        label: 'Remote Runtime Host target detection',
+        onFrame: (identity) => {
+          if (target) {
+            failure = new Error('Remote Runtime Host target detection returned multiple results');
+            return;
+          }
+          const [system, machine, ...extra] = identity.split(':');
+          if (!system || !machine || extra.length > 0) {
+            failure = new Error('Remote Runtime Host target detection returned an invalid result');
+            return;
+          }
+          try {
+            target = runtimeHostDevelopmentPeerTargetFromUname(system, machine);
+          } catch (error) {
+            failure = error instanceof Error ? error : new Error(String(error));
+          }
+        },
+        onError: (error) => {
+          failure = error;
+        },
+      });
+      const { process, terminal } = startTerminalProcess(
+        'ssh',
+        sshRemoteCommandArgs(destination, sshPort, remoteCommand),
+        filter.push,
+        true,
+      );
+      const wait = await waitForTerminalProcess(process, {
+        signal: targetInput.signal,
+        timeoutMs: input.managementTimeoutMs ?? MANAGEMENT_TIMEOUT_MS,
+        stopGraceMs: input.processStopGraceMs,
+        onAbort: () => dismissPresentation(terminal),
+      }, input.terminateProcessTree);
+      if (wait.timedOut) throw new Error('Remote Runtime Host target detection timed out');
+      if (wait.exit.code !== 0) {
+        throw new Error(
+          `Remote Runtime Host target detection exited with code ${String(wait.exit.code)}`,
+        );
+      }
+      filter.finish();
+      if (failure) throw failure;
+      completePresentation(terminal);
+      if (!target) throw new Error('Remote Runtime Host target detection returned no result');
+      return target;
+    },
     runSetup: async (setupInput, onProgress, onComplete) => {
       if (closed) throw new Error('Runtime Host SSH terminal is closed');
       setupInput.signal?.throwIfAborted();
@@ -842,6 +912,21 @@ export function createDesktopRuntimeHostSshTerminal(input: {
   };
 }
 
+export function runtimeHostDevelopmentPeerTargetFromUname(
+  system: string,
+  machine: string,
+): Exclude<DesktopRuntimeHostDevelopmentPeerTarget, 'none'> {
+  const normalizedMachine = machine.toLowerCase();
+  if (system === 'Darwin' && normalizedMachine === 'arm64') return 'darwin-arm64';
+  if (system === 'Linux') {
+    if (normalizedMachine === 'x86_64') return 'linux-x64';
+    if (normalizedMachine === 'aarch64' || normalizedMachine === 'arm64') {
+      return 'linux-arm64';
+    }
+  }
+  throw new Error(`Direct peer is not available on ${system}/${machine}`);
+}
+
 function cancellableUntilComplete(signal: AbortSignal | undefined): {
   readonly signal: AbortSignal;
   commit(): boolean;
diff --git a/native/runtime-host-peer/build.mjs b/native/runtime-host-peer/build.mjs
index b103cfe15..9c9d77ffe 100644
--- a/native/runtime-host-peer/build.mjs
+++ b/native/runtime-host-peer/build.mjs
@@ -53,6 +53,13 @@ const targetPlatform = cargoTarget
       ? 'darwin'
       : 'linux'
   : process.platform;
+const targetArch = cargoTarget
+  ? cargoTarget.startsWith('aarch64-')
+    ? 'arm64'
+    : cargoTarget.startsWith('x86_64-')
+      ? 'x64'
+      : undefined
+  : process.arch;
 
 const library =
   targetPlatform === 'win32'
@@ -67,7 +74,11 @@ if (targetPlatform === 'darwin' && process.platform === 'darwin') {
     ...process.env,
   });
   await run('strip', ['-x', destination], root, { ...process.env });
-} else if (targetPlatform === 'linux' && process.platform === 'linux') {
+} else if (
+  targetPlatform === 'linux' &&
+  process.platform === 'linux' &&
+  targetArch === process.arch
+) {
   await run('strip', ['--strip-unneeded', destination], root, { ...process.env });
 }
 if ((await readFile(destination)).includes(Buffer.from(root))) {
diff --git a/scripts/release-cli-package.mjs b/scripts/release-cli-package.mjs
index 2a6a12a22..da67bac7e 100644
--- a/scripts/release-cli-package.mjs
+++ b/scripts/release-cli-package.mjs
@@ -58,6 +58,9 @@ const releaseRoot = join(cliSource, 'release');
 const artifactRoot = developmentBuild ? createDevelopmentArtifactRoot() : releaseRoot;
 const stageRoot = join(artifactRoot, 'package');
 const peerPrebuildTargets = ['darwin-arm64', 'linux-arm64', 'linux-x64', 'win32-x64'];
+const privatePeerTarget = developmentBuild
+  ? resolveDevelopmentPeerTarget()
+  : `${process.platform}-${process.arch}`;
 const unsupportedArguments = process.argv
   .slice(2)
   .filter((argument) => !['--allow-dirty', '--development'].includes(argument));
@@ -157,7 +160,7 @@ function packageCli(publishable) {
     entryCount: pack.entryCount,
   });
   const tarballPath = join(artifactRoot, pack.filename);
-  validatePackedFiles(pack.files, expectedDependencyManifests);
+  validatePackedFiles(pack.files, expectedDependencyManifests, publishable);
   const sha256 = digestFile(tarballPath);
   writeFileSync(`${tarballPath}.sha256`, `${sha256}  ${pack.filename}\n`, 'utf8');
   writeFileSync(
@@ -542,16 +545,15 @@ function copyReleaseDocuments() {
 function copyRuntimeHostPeerPrebuilds(publishable) {
   let sourceRoot = process.env.MAKA_RUNTIME_HOST_PEER_PREBUILDS?.trim();
   let generatedRoot;
-  let targets = peerPrebuildTargets;
+  const targets = publishable
+    ? peerPrebuildTargets
+    : privatePeerTarget === 'none'
+      ? []
+      : [privatePeerTarget];
+  if (targets.length === 0) return;
   if (!sourceRoot && !publishable) {
-    execFileSync(process.execPath, [join(repoRoot, 'native/runtime-host-peer/build.mjs')], {
-      cwd: repoRoot,
-      stdio: 'inherit',
-    });
-    const target = `${process.platform}-${process.arch}`;
-    if (!peerPrebuildTargets.includes(target)) {
-      throw new Error(`Direct peer is not supported on ${target}`);
-    }
+    const [target] = targets;
+    buildDevelopmentPeerAddon(target);
     sourceRoot = generatedRoot = mkdtempSync(join(tmpdir(), 'maka-runtime-host-peer-prebuilds-'));
     const targetRoot = join(sourceRoot, target);
     mkdirSync(targetRoot, { recursive: true, mode: 0o755 });
@@ -559,7 +561,6 @@ function copyRuntimeHostPeerPrebuilds(publishable) {
       join(repoRoot, 'native/runtime-host-peer/target/release/maka_runtime_host_peer.node'),
       join(targetRoot, 'maka_runtime_host_peer.node'),
     );
-    targets = [target];
   }
   if (!sourceRoot) {
     throw new Error('MAKA_RUNTIME_HOST_PEER_PREBUILDS must contain all release platform addons');
@@ -580,6 +581,60 @@ function copyRuntimeHostPeerPrebuilds(publishable) {
   }
 }
 
+function resolveDevelopmentPeerTarget() {
+  const configured = process.env.MAKA_CLI_DEVELOPMENT_PEER_TARGET?.trim();
+  const target = configured || `${process.platform}-${process.arch}`;
+  if (target !== 'none' && !peerPrebuildTargets.includes(target)) {
+    throw new Error(
+      `MAKA_CLI_DEVELOPMENT_PEER_TARGET must be none or a supported target; found ${target}`,
+    );
+  }
+  return target;
+}
+
+function buildDevelopmentPeerAddon(target) {
+  const hostTarget = `${process.platform}-${process.arch}`;
+  const buildScript = join(repoRoot, 'native/runtime-host-peer/build.mjs');
+  if (target === hostTarget) {
+    execFileSync(process.execPath, [buildScript], { cwd: repoRoot, stdio: 'inherit' });
+    return;
+  }
+  const rustTarget = {
+    'linux-arm64': 'aarch64-unknown-linux-gnu.2.28',
+    'linux-x64': 'x86_64-unknown-linux-gnu.2.28',
+  }[target];
+  if (!rustTarget) {
+    throw new Error(
+      `Cannot build the ${target} direct-peer addon from ${hostTarget}; run Desktop on that target or provide MAKA_RUNTIME_HOST_PEER_PREBUILDS`,
+    );
+  }
+  requireDevelopmentCommand(
+    'zig',
+    ['version'],
+    `Cross-compiling the ${target} direct-peer addon requires Zig on PATH (CI uses 0.16.x)`,
+  );
+  requireDevelopmentCommand(
+    'cargo-zigbuild',
+    ['--version'],
+    `Cross-compiling the ${target} direct-peer addon requires cargo-zigbuild (cargo install cargo-zigbuild --version 0.23.2 --locked)`,
+  );
+  execFileSync(process.execPath, [buildScript], {
+    cwd: repoRoot,
+    env: {
+      ...process.env,
+      MAKA_RUNTIME_HOST_PEER_CARGO_SUBCOMMAND: 'zigbuild',
+      MAKA_RUNTIME_HOST_PEER_CARGO_TARGET: rustTarget,
+    },
+    stdio: 'inherit',
+  });
+}
+
+function requireDevelopmentCommand(command, args, message) {
+  const result = spawnSync(command, args, { cwd: repoRoot, encoding: 'utf8' });
+  if (result.status === 0) return;
+  throw new Error(`${message}; install it before setting up this development Runtime Host`);
+}
+
 function writeReleaseManifest(cli, publishable) {
   const source = readJson(join(cliSource, 'package.json'));
   const root = readJson(join(repoRoot, 'package.json'));
@@ -690,9 +745,9 @@ function validateStaging(publishable) {
         (target) => `native/runtime-host-peer/prebuilds/${target}/maka_runtime_host_peer.node`,
       ),
     );
-  } else {
+  } else if (privatePeerTarget !== 'none') {
     required.push(
-      `native/runtime-host-peer/prebuilds/${process.platform}-${process.arch}/maka_runtime_host_peer.node`,
+      `native/runtime-host-peer/prebuilds/${privatePeerTarget}/maka_runtime_host_peer.node`,
     );
   }
   for (const path of required) {
@@ -745,7 +800,7 @@ function validateStaging(publishable) {
   }
 }
 
-function validatePackedFiles(files, expectedDependencyManifests) {
+function validatePackedFiles(files, expectedDependencyManifests, publishable) {
   const paths = files.map((file) => file.path);
   for (const file of files) {
     const { path } = file;
@@ -780,7 +835,7 @@ function validatePackedFiles(files, expectedDependencyManifests) {
     'node_modules/@maka/runtime/dist/workers/filesystem-worker.js',
     'node_modules/@maka/runtime-host/dist/execution-candidate-main.js',
     'packages/eval/harbor/relay_agent.py',
-    'native/runtime-host-peer/prebuilds/',
+    ...(publishable || privatePeerTarget !== 'none' ? ['native/runtime-host-peer/prebuilds/'] : []),
   ];
   for (const suffix of requiredPacked) {
     if (


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