基于Kuikly的DeepSeek Harness移动端控制面板

基于Kuikly的DeepSeek Harness移动端控制面板 先交代一下背景。DeepSeek Harness这个玩意儿去年年底开始一直在折腾定位类似Codex Harness是一套把DeepSeek模型能力封装成可执行Agent的框架。简单说就是你丢给它一个任务描述它能自己拆步骤、读写文件、调命令行、改代码像雇了个远程实习生。我用它在电脑上跑编码代理任务效率确实高但问题也来了——每次出门就断了只能在工位上用。后来我琢磨了一个事能不能把DeepSeek Harness的服务跑在电脑或服务器上然后出门用手机也能看任务状态、发新任务、看日志一开始想的是直接装个Termux开SSH但体验太糙而且手机上的操作界面基本等于没有。后来看到一个腾讯开源的跨端框架Kuikly用Kotlin写UI逻辑、DSL声明界面一个工程同时出Android和iOS包脑子一热就决定自己动手。于是就有了这个项目用Kuikly做了一个移动端控制面板把DeepSeek Harness的服务接进去让AI编码助手能跟着我出门。如果你也有一台常开的电脑或者主机平时习惯用这类Agent框架跑任务又不想只是远程桌面操作那这篇文章应该对你有用。我会把这个项目的架构思路、选型原因、实操步骤、还有踩过的坑全部拆开讲。1. 先想明白把DeepSeek Harness装进口袋到底装的是什么动手写代码之前我花了很长时间琢磨一个问题这个项目要做的到底是在手机上跑DeepSeek Harness还是别的什么。想清楚这个后面所有技术选型才有依据。1.1 拆解DeepSeek Harness的核心能力先说DeepSeek Harness到底是什么。很多人第一次看到harness这个词会懵其实它在Agent类工具里指的就是把模型能力封装起来的那层壳。打个比方DeepSeek的API是一台发动机而Harness是整套车架、方向盘、仪表盘、油路。你自己调API相当于手动去拧发动机用Harness相当于你只需要握着方向盘告诉它去哪儿其余细节它自己处理。DeepSeek Harness的核心能力大概有这么几块任务拆解输入一个高层需求比如重构这个项目的登录模块把token过期处理统一收口它能拆成多个子步骤。文件读写能直接扫当前仓库目录读取文件内容、定位关键代码位置然后做修改。命令执行在沙箱或指定目录里跑shell命令比如跑测试、装依赖、查报错日志。上下文管理维护多轮对话的内存把之前做过的操作记录成短期记忆避免重复犯错。工具调用按需调用外部工具或插件比如搜索、格式化、静态检查之类的。这类工具通常以三种形态提供纯CLI命令行、桌面端应用可以选工作目录、看实时日志、VSCode插件直接在编辑器里对话。我日常用得最多的就是CLI和桌面端跑一些批量任务比如代码迁移、测试补全、技术栈升级。跟Codex Harness对比的话DeepSeek Harness更偏向DeepSeek模型的本地化封装模型成本相对低而且因为DeepSeek本身中文能力强在处理中文注释项目时理解更准。但工具形态上两家很相似都是模型能力 工具调用 任务循环的整合体。1.2 装进口袋的本质是移动端远程控制我一开始有个误区觉得装进口袋等于在手机上跑DeepSeek Harness。试了一下就知道不现实手机端的性能、内存、电池都不允许跑一个完整的Agent循环而且DeepSeek Harness在移动端真机编译的适配还不成熟大量依赖了桌面环境的文件系统和shell能力。所以真正的方案是把Harness留在电脑或服务器上跑手机端只做远程控制面板。换个说法这个项目的本质不是把DeepSeek Harness移植到手机而是给DeepSeek Harness做一个移动端遥控器。手机端负责展示Harness服务当前状态空闲、运行中、报错下发新任务输入任务描述和参数实时查看任务日志、步骤进度查看历史任务记录Harness服务端负责所有重活模型调用、任务拆解、文件操作、命令执行。这样手机端只做通信和展示复杂度大幅下降也不用操心在手机上适配终端的各种奇怪边缘情况。想明白这一点之后整个项目的边界就清楚了后面的技术选型也变得非常顺畅。2. 为什么选Kuikly不是Flutter也不是React Native现在移动端跨端方案喊得最多的就三个Flutter、React Native、Kotlin Multiplatform。其实还有个隐藏选手叫Kuikly是腾讯开源的。我最终选了Kuikly下面说说我的思路。2.1 三条路线的横向对比先做一张对比表方便直观感受方案UI渲染方式逻辑开发语言生态成熟度性能表现学习成本Flutter自绘引擎(Skia)Dart非常高优秀但包体积偏大中需要学DartReact Native原生组件桥接JavaScript/TypeScript非常高视交互复杂度而定低前端栈友好Kuikly原生组件DSLKotlin尚在成长接近原生低Kotlin开发者友好Flutter的最大问题是包体积和Dart语言隔离。包体积直接奔着20MB朝上对于一个遥控器类型的轻量App来说有点重。React Native倒是轻但桥接层有时候调原生能力比较费劲而且JS引擎在启动和内存上多少有点开销。Kuikly的优势在于它本身就是Kotlin Multiplatform生态里的方案UI用DSL渲染原生组件逻辑层直接用Kotlin写不引入Dart、不引入JS所有业务代码都是纯Kotlin。2.2 腾讯Kuikly的杀手锏我实际用下来Kuikly有几点特别戳中我第一Kotlin统一语言链。我的Harness服务端本身有一部分逻辑用Kotlin写的后端接口也是Kotlin系现在移动端再用Kotlin整个项目的语言栈完全统一。编码时的状态切换成本几乎为零不像之前用Flutter写UI是一种语言写服务端是另一种来回切换脑子有点累。第二DSL声明UI但渲染的是原生组件。Kuikly不是WebView套壳也不是自绘引擎。它的UI代码最终会编译成原生组件渲染。这意味着性能和交互手感接近原生不会出现WebView那种滚动卡顿、长列表掉帧的问题。第三单工程多端输出。一个Kotlin工程配置好之后可以同时产出Android和iOS的安装包。对于我这种没有精力维护两套原生代码的个人开发者来说省下大量时间。第四状态更新机制比较清晰。Kuikly借鉴了类似Compose的状态观察思路数据变了UI自动更新。在展示任务日志、进度变化这些高频动态场景下写起来很顺手。2.3 什么时候选Kuikly才值Kuikly也不是万能的。你要是完全不会Kotlin只有前端背景那Flutter或RN的曲线会更平滑。但如果你符合这些条件选Kuikly非常值已经有Kotlin或Android开发基础不想再学一门新语言项目需要跨Android和iOS但又不想维护两套原生代码未来可能要和原生能力深度交互比如调用摄像头、蓝牙、系统通知更在意安装包体积和性能对轻量有要求我自己的情况正好都满足加上Kuikly又是腾讯在持续维护的开源项目踩坑有人管就决定赌一把。3. 架构设计与方案拆解三个组件怎么分工选型定了之后就要画架构。我不喜欢一上来就写代码先把数据流和模块边界定死写起来会顺畅很多。3.1 整体架构三层分工整个系统分成三层移动端控制层Kuikly App。负责界面展示、用户输入、任务状态轮询、日志流展示。通信层局域网HTTP/WebSocket服务。负责把移动端的请求转发给Harness服务端并把Harness的实时输出回传给移动端。服务层DeepSeek Harness运行时。跑在电脑或服务器上真正干活的层。请求链路大概是这样用户在手机App上填写任务描述点击发送。Kuikly App通过HTTP POST把任务请求发到通信层。通信层拿到请求后把它转交给Harness服务端。Harness开始跑任务把日志和状态以流式形式吐给通信层通信层再通过WebSocket或SSEServer-Sent Events实时推回手机端。手机端收到数据更新到UI上。反过来也是一样用户想终止任务手机端发一个取消指令通信层调Harness的终止接口Harness停止当前循环把结果写回。3.2 为什么用本地服务远程App而不是App里直接嵌队列有人可能问为什么不直接在App里集成一个任务队列把Harness作为库跑到手机里这个我在1.2说过一部分但这里可以展开讲。第一隔离变化。Harness本身迭代速度非常快今天这个版本明天可能就换了接口。如果App直接依赖Harness内部的库那Harness升级一次App就要跟着重新编译一次发布一次。用服务端隔离之后Harness随便升级只要通信层的接口不变App完全不受影响。第二复用已有的CLI和桌面端能力。DeepSeek Harness跑在电脑上有完整的能力生态比如文件系统访问、Shell执行、环境变量管理等。这些东西在手机iOS的沙箱环境下根本实现不了硬移植就是给自己找麻烦。第三降功耗降内存。模型推理和Agent循环都在电脑服务器上跑手机只做IO展示耗电基本可以忽略。实测下来手机端连续开两小时日志页掉电不到10%这个体验太关键了。3.3 通信协议设计不只是传数据还要传状态通信层写起来不复杂但要注意传的不只是任务数据还有状态机。我定义了几个关键状态IDLEHarness空闲可以接收新任务RUNNING任务正在执行PAUSED任务被挂起比如等待用户确认某一步ERROR任务执行过程中出错DONE任务执行完成App端维护一个状态机UI根据状态变化显示不同内容空闲时按钮可点运行中显示进度和取消按钮出错时显示重试和错误堆栈。日志数据我用SSE推送每条日志带上时间戳、日志级别INFO/WARNING/ERROR和内容。App端拿到之后追加到日志列表同时做关键词过滤比如出现ERROR就标红。协议设计这块我的经验是数据格式要尽量松耦合但状态字段一定要严格约定。宁可多一个冗余的状态也不要让两端对状态理解不一致否则排查问题会想哭。4. 实操从零把DeepSeek Harness拉进手机架构想清楚了剩下的就是动手。这一节我直接按实操顺序讲环境准备、工程创建、UI编写、通信对接、打包验证。每一步都给到可复制的方案。4.1 准备阶段环境与依赖电脑上需要准备的东西JDK 11或更高版本Android SDK、GradleNode.jsHarness的CLI部分依赖后面服务启动要用Kuikly的命令行工具手机和电脑要在同一个局域网内或者你有内网穿透工具把它暴露到公网但注意安全后面会说。我把DeepSeek Harness的服务跑成一个常驻进程监听端口默认选的8620绑定地址是0.0.0.0这样局域网内所有设备都能访问。首次启动时要设置一个访问TokenApp端连接时要用。启动服务的命令很简单Kotlin和Node两种方式我都试过最终用的是Node版本因为日志输出更友好启动速度也快。启动完之后可以用curl http://localhost:8620/health确认服务活着。4.2 创建Kuikly工程初始化Kuikly工程其实跟创建Android工程差不多它支持从模板创建。我用的是Kotlin DSL方式kuikly create app --name HarnessPocket --package com.example.harnesspocket创建完之后目录结构大致是这样HarnessPocket/ ├── build.gradle.kts ├── settings.gradle.kts ├── src/ │ ├── commonMain/ │ │ ├── kotlin/com/example/harnesspocket/ │ │ │ ├── App.kt │ │ │ ├── screen/ │ │ │ └── viewmodel/ │ │ └── ... │ ├── androidMain/ │ └── iosMain/核心代码都写在commonMain里平台相关的东西比如网络权限、通知权限再放到各自平台目录。这个结构对个人项目来说非常清爽。4.3 编写UI用Kotlin DSL画界面Kuikly的UI写法类似Compose但有自己的DSL风格。我直接用DSL画了一个三页面的App首页连接状态任务输入、日志页实时日志流、历史页任务记录。主页的关键代码大概长这样Flexbox( modifier Modifier.fillMaxSize().padding(16.dp), flexDirection FlexDirection.COLUMN, justifyContent JustifyContent.FLEX_START ) { child( Text( text Harness Pocket, style TextStyle(fontSize 24.sp, fontWeight FontWeight.BOLD) ) ) child( Text( text viewModel.connectionState, style TextStyle(fontSize 16.sp) ) ) child( TextField( value viewModel.taskInput, onValueChange { viewModel.taskInput it }, placeholder 输入任务描述比如检查项目里所有TODO并生成报告 ) ) child( Button( text 下发任务, onClick { viewModel.submitTask() } ) ) }看起来跟Compose很接近但底层渲染是原生组件。写UI的体验比较舒服状态驱动更新不需要手动操作findViewById之类的东西。日志页我用了LazyList渲染长列表因为日志量可能上千条用普通ScrollView会卡。Kuikly的LazyList机制跟Compose的LazyColumn类似只渲染可见区域性能有保障。4.4 与服务层通信通信这块我用了两个东西HTTP请求 SSE流式日志。HTTP请求用Kotlin的HttpClient直接发POST带JSON body。代码不复杂val response client.post(http://$host:$port/task) { contentType(ContentType.Application.Json) setBody(taskJson) }SSE连接用的是OkHttp的EventSource或者用ktor-client的SSE支持。我图省事直接用了OkHttp的SSE接收服务端发来的每行日志解析成数据类追加到ViewModel的状态里UI自动刷新。注意点HTTP和SSE要复用同一个基础URL和Token不然会出现能发任务但看不到日志这种诡异问题。我当时就是HTTP请求走了内网IPSSE连接走了另一个域名结果日志一直空白排查了半天。4.5 打包、签名与真机验证Android打包没太多说的Gradle构建出APK然后安装到手机。Android默认允许安装未知来源应用记得在手机设置里打开。iOS打包麻烦一点需要Xcode环境和开发者签名。我一开始只在Android上测试后来想试试iOS用一条MacBook编了一次包能出来但真机安装需要开发者证书。如果你只用Android就不用折腾这步了。装到手机上之后第一步先确认网络通了。手机浏览器直接访问http://电脑IP:8620/health能看到JSON返回说明网络没问题。然后再打开App填Token连接。整个流程走通之后我当时的反应是这手机上的界面比我想象中流畅太多了。列表滚动、输入、状态切换基本没有明显掉帧。5. 实战中踩过的坑与排查速查表直接说结论。这个项目从开始到稳定运行我踩了大概五六个坑每一个都花了不少时间去查。单独拎出来说希望你能避开。5.1 编译类Kotlin版本不一致Kuikly对Kotlin版本要求比较严格我一开始建工程时用了自己本机默认的Kotlin版本结果编译报错一堆DSL的API找不到。后来查文档发现Kuikly需要匹配特定的Kotlin版本直接用了它的build.gradle.kts模板里的版本号才编译通过。建议创建工程后先不要升级任何依赖版本先用模板默认的版本把App跑起来后续再按需升级。5.2 网络类防火墙拦截局域网端口第一次真机测试时手机连不上电脑端服务但我用浏览器在电脑本机访问localhost:8620/health是正常的。排查了一圈最后发现是电脑系统防火墙把8620端口拦了局域网外网进不来。解决方法是在防火墙入站规则里放行8620端口或者临时允许Java/Node进程访问局域网。这一步看起来小但最容易卡住新手。5.3 输出异常胡乱冒字问题的排查这个坑我最想详细说因为搜索热词里也频繁出现deepseek harness胡乱冒字出来。我在日志流里也遇到过类似现象。具体表现是日志内容突然出现大段乱码或者模型输出里夹杂着诡异的字符组合有点像编码问题但又不像。排查下来原因是日志编码不一致。Harness服务端输出的日志默认是UTF-8编码但有一部分中间步骤会以GBK或者其他编码输出到stdout通信层不加处理直接推送到App端就是乱码。解决方法是在通信层做统一的编码转换统一转成UTF-8之后再推给App。另外还有一种胡乱冒字的情况不是编码问题是模型输出内容本身出现了重复片段。这个其实是DeepSeek模型在长上下文场景下的一个已知现象可以通过调整采样参数比如降低temperature增加重复惩罚来缓解。我在Harness服务端的配置里加了一组参数效果挺明显。5.4 性能与耗电长连接保活策略SSE连接本身是长连接但手机系统为了省电会挂起后台网络。表现就是App切后台一段时间再回到前台时日志流断了。我开始的解决思路是加心跳包每30秒发一次ping保持连接活性。实测下来有效但耗电会稍微增加。后来改进方案切到后台时主动断开SSE连接回到前台时重新连接并把断开期间的日志通过HTTP轮询补拉一次。这样既不耗电也不会丢日志。5.5 排查速查表现象可能原因排查方向App连不上服务防火墙、IP段、端口占用先浏览器访问health接口能发任务但日志空白HTTP和SSE地址不一致检查连接URL和Token日志出现乱码编码格式不统一通信层统一转UTF-8后台一段时间断流系统挂起后台网络回前台重连补拉日志模型输出重复字符采样参数问题调低temperature、增大重复惩罚iOS编译报错签名、版本匹配问题查看具体错误堆栈匹配Xcode版本6. 跑起来之后我到底得到了什么这个项目做完到现在跑了快一个月已经成为我日常工作的固定一环。说几个真实使用场景你就知道它解决了什么问题。场景一午休的时候我在外面用手机给Harness下发一个任务——review一下某个仓库里所有带FIXME的注释按严重程度排序并生成报告。等我下午回到工位报告已经躺在服务端指定目录里了手机上也看到了完成通知。场景二家里电脑在跑一个耗时很长的重构任务我出门坐地铁时用手机打开日志页随时盯着进度。哪一步卡住了立刻远程终止重新写个参数再下发不用等回家才发现问题。还有一个意外收获我把Harness Pocket给团队里两个非技术同事用了。他们不需要懂CLI只需要在手机上填任务描述就能让电脑上的Agent帮忙整理文档、汇总周报、批量重命名文件。Kuikly的低门槛UI正好把Harness的能力封装成了傻瓜化接口这对团队效率的提升比我自己用还大。再聊一点关于安全和技术栈的个人感受。把Harness服务暴露在局域网安全是个必须考虑的点。我的方案是服务端启动时要求一个强TokenApp端所有请求都带Token校验。如果确实需要从公网访问我强烈建议挂一层反向代理做TLS和身份认证不要直接把端口裸奔到公网。还有日志里偶尔会包含文件路径、报错信息等敏感内容手机丢了之前先想想怎么远程清除App数据我个人的方法是给App设了启动PIN码避免了直接打开就能看日志的尴尬。从技术选型角度说Kuikly这次用下来整体是超出预期的。它的生态确实没有Flutter那那么大社区资料也不多但只要你对Kotlin有基础文档里的例子基本够用。遇到实在没文档的地方直接翻源码也能找到答案这也是开源项目的优势。最后再说一个细节但我觉得挺重要的给手机App加上任务重试按钮是我整个项目里最值的一个功能。Agent跑任务挂掉太常见了之前在电脑上要手动找日志、定位为什么挂、重新拼命令。现在手机上点一下重试就行还能改参数再下发这个操作路径短到让人觉得顺手过头了。如果你也想做类似的项目我的建议是先不用急着完美第一步哪怕只做一个能发任务、能看日志的最简版本就够用了。真正让这个项目变好用的是你日复一日使用过程中不断加的小功能。工具就是这样先跑起来再变顺手。