鸿蒙迁移DeepSeek Harness:沙箱重构与插件范式革命
1. 这不是“移植”是沙箱环境的重新锚定“把 DeepSeek Harness 搬进鸿蒙的 8 个坑”——这个标题里最危险的词其实是“搬”。我见过太多团队拿着 Electron 打包好的 Windows/macOS 版本直接拖进 OpenHarmony 的 DevEco Studio 里点构建结果连启动画面都卡在白屏上日志里只有一行Failed to initialize renderer process。这不是兼容性问题是根本性的运行时范式错位。DeepSeek Harness 本质是一个基于 Electron 构建的本地智能体运行时平台它依赖 Chromium 渲染进程 Node.js 后端服务 本地文件系统访问 WebSocket/HTTP 双向通信管道所有插件Skill以 CommonJS 模块形式加载通过require()动态注入。而鸿蒙尤其是 OpenHarmony 标准系统的原生应用模型是ArkTS Ability Stage 模型UI 渲染走的是 ArkUI 框架逻辑层跑在 ArkCompiler 编译后的字节码上没有全局require没有process.cwd()更没有child_process.spawn这类底层能力。Electron 的“桌面级自由”和鸿蒙的“设备级受控”之间隔着一道由沙箱策略、能力声明、权限模型共同筑起的墙。所以“搬”这个动作本身就不成立。真正要做的是把 DeepSeek Harness 的核心能力解耦把模型推理调度、插件生命周期管理、技能执行上下文、网络代理中继这四块骨头从 Electron 的肌肉组织里剥离出来再用 ArkTS 和 Native CNDK在鸿蒙的骨骼上重新长出对应的新肌肉。这不是代码迁移是器官移植级别的重构。关键词里反复出现的 “agent沙箱” 和 “双网络记忆模型”恰恰暴露了问题的核心矛盾DeepSeek Harness 的沙箱是基于 Chromium 的webviewcontextIsolation实现的它允许插件在隔离 JS 上下文中执行任意代码同时通过预置的ipcRenderer接口调用主进程能力而鸿蒙的沙箱是基于Ability Security Model的每个 Ability 必须在module.json5中显式声明所需权限如ohos.permission.INTERNET、ohos.permission.READ_USER_STORAGE且跨 Ability 调用必须走want机制无法像 Electron 那样在渲染进程中直接require(fs)。这就导致几乎所有 Skill 插件——尤其是那些需要读取本地文件、调用 Python 子进程、或建立长连接的——在鸿蒙上会直接抛出SecurityException或undefined is not a function。我试过最“省事”的方案用鸿蒙的WebComponent加载 DeepSeek Harness 的 Web 版前端后端服务则部署在局域网另一台 Linux 机器上。表面看能跑但立刻撞上第二个坑双网络记忆模型失效。WebComponent 在鸿蒙里运行于受限 WebView它无法访问localStorage的完整 API部分方法被禁用更无法与鸿蒙原生网络栈打通。当 Harness 需要记住用户上次对话的上下文、缓存模型 token、或维持与内网 LLM 服务的长连接时WebView 的 cookie 和 storage 会被鸿蒙系统定期清理导致每次打开都像第一次使用。这不是 Bug是鸿蒙对 Web 容器的主动约束——它不希望 Web 页面拥有比原生应用更强的持久化能力。所以开头这第一坑本质是认知陷阱别想着“搬”先承认 Electron 和 ArkTS 是两种语言它们的语法、运行时、内存模型、错误处理机制全都不通。你得用鸿蒙的“话”重写 Harness 的“心”。2. 沙箱边界从 Chromium Context 到 Ability 权限声明的硬切换鸿蒙的沙箱不是一层薄薄的玻璃罩而是一套嵌套的、可配置的权限围栏。DeepSeek Harness 在 Electron 里能随意fs.readFileSync(./config.json)是因为它默认拥有整个用户目录的读写权但在鸿蒙上这行代码会直接触发SecurityException: Permission denied哪怕你在module.json5里声明了ohos.permission.READ_USER_STORAGE也仅限于getExternalStorageDir()返回的路径而非任意绝对路径。2.1 权限声明不是“开关”而是“契约”在鸿蒙开发中module.json5的requestPermissions字段常被误认为是“开个权限就行”。实际它是运行时契约声明后系统会在安装时提示用户授权但更重要的是它决定了你的 Ability 能调用哪些 NDK 接口。例如{ module: { requestPermissions: [ { name: ohos.permission.INTERNET, reason: 用于连接本地大模型服务, usedScene: { abilities: [EntryAbility], when: always } }, { name: ohos.permission.READ_USER_STORAGE, reason: 用于读取用户技能配置文件, usedScene: { abilities: [EntryAbility, SkillServiceAbility], when: inuse } } ] } }注意两点第一usedScene.when必须精确到使用场景always或inuse鸿蒙会据此动态调整权限粒度第二abilities数组必须列出所有可能用到该权限的 Ability 名称漏掉一个对应 Ability 就拿不到权限。我踩过的坑是把 Skill 的加载逻辑写在SkillServiceAbility里却只在EntryAbility声明了READ_USER_STORAGE结果 Service 启动时fs.openSync()直接崩溃日志里只有E/ACE: [ERROR] Failed to open file没有任何权限相关提示——鸿蒙的错误日志极其吝啬它默认你已读懂契约。2.2 文件系统从fs模块到fileioAPI 的映射断层Electron 的fs模块是 Node.js 的标准接口支持同步/异步、流式读写、符号链接等全套操作。鸿蒙没有fs只有ohos.fileio模块它提供的是 POSIX 风格的底层文件操作但关键差异在于路径必须是鸿蒙规范路径不能用C:\Users\name\config.json或/home/user/config.json必须用context.filesDir /config.json应用私有目录或context.externalFilesDir /skills/外部存储需额外权限无同步 APIfileio.openSync()已废弃必须用fileio.open() Promise这意味着所有 Skill 插件的初始化逻辑必须从同步改为异步而很多老插件比如读取 YAML 配置的是require(js-yaml).load(fs.readFileSync(...))这种链式调用直接改会造成大量Promise嵌套权限校验发生在打开时而非读取时fileio.open()会立即检查路径是否在授权范围内失败则抛异常而 Electron 的fs.readFileSync()即使路径越界也只在真正读取时才报错调试难度完全不同。我实测过一个典型插件git-commit-analyzer它需要读取当前项目.git/config并解析。在 Electron 里一行搞定const config require(ini).parse(fs.readFileSync(.git/config, utf8));在鸿蒙里你得先用fileio.stat()检查.git目录是否存在因为fileio.open()不支持相对路径遍历再用fileio.open()打开config文件再用fileio.read()读取 buffer最后用TextDecoder转成字符串再交给ini.parse()。整个过程要处理 4 种可能的异常路径不存在、无权限、读取失败、编码错误而 Electron 里只需处理一种。提示鸿蒙的fileioAPI 对中文路径支持极差fileio.open()遇到含中文的文件名会静默失败。解决方案是所有 Skill 配置文件强制用英文命名或在保存前用encodeURIComponent()编码读取时再decodeURIComponent()解码。这是鸿蒙 NDK 层的已知限制官方文档里不会写但社区里踩过的人不少。2.3 网络模型从 localhost 到ohos.net.http的信任链重建DeepSeek Harness 的核心网络模型是“双通道”前端 UI 通过fetch或WebSocket连接本地http://localhost:3000的后端服务后端再代理请求到内网的 LLM 模型如http://192.168.1.100:8000/v1/chat/completions。在鸿蒙上localhost这个地址完全失效——鸿蒙的 WebView 和 ArkTS 运行时共享同一套网络栈但localhost指向的是鸿蒙系统的 loopback 接口而你的后端服务如果跑在 PC 上它根本不在鸿蒙设备的localhost里。解决方案只有两个让鸿蒙设备直连 PC 的局域网 IP修改所有 Skill 的 API 地址为http://192.168.1.100:3000并在鸿蒙module.json5中声明ohos.permission.INTERNET在鸿蒙设备上启动一个轻量级代理服务如用ohos.net.http实现一个反向代理把http://localhost:3000的请求转发到 PC。我选了方案 2因为更符合“本地运行”的产品定位。但立刻遇到新坑鸿蒙的http模块不支持keep-alive连接复用每个请求都是新建 TCP 连接而 DeepSeek Harness 的 Skill 频繁调用/v1/chat/completions导致 PC 端的后端服务很快达到TIME_WAIT连接数上限。解决办法是在鸿蒙代理层实现连接池缓存用Mapstring, http.HttpRequest缓存已建立的连接按 HostPort 作为 key复用连接。但这要求你手动管理连接生命周期避免内存泄漏——鸿蒙没有net模块无法监听 socket 关闭事件只能靠定时器轮询连接状态。注意鸿蒙的http模块对 HTTPS 证书验证极其严格如果你的内网 LLM 服务用的是自签名证书http.request()会直接失败错误码1290001SSL handshake failed。必须在HttpRequestOptions中设置sslVerify: false但这需要额外申请ohos.permission.GRANT_SENSITIVE_PERMISSIONS权限且用户授权后才能生效。很多开发者卡在这里以为是网络不通其实是证书被拒。3. 插件生态从 CommonJS 动态加载到 ArkTS 静态模块的范式革命DeepSeek Harness 的灵魂在于插件Skill——它让非程序员也能通过 JSON/YAML 配置定义 AI 工作流。这些插件本质是 CommonJS 模块通过require(pluginPath)动态加载利用module.exports暴露execute()函数。这种模式在 Electron 里天衣无缝但在鸿蒙 ArkTS 里它直接撞上三堵墙模块系统不兼容、动态加载被禁止、执行上下文缺失。3.1 ArkTS 没有require只有import和exportArkTS 的模块系统是 ES ModuleESM规范所有导入必须是静态的、编译期确定的。import()动态导入虽存在但仅支持字符串字面量如import(./skills/git.js)不支持变量拼接路径import(./skills/ name .js)而 Harness 的插件加载逻辑正是后者——它扫描./skills/目录读取manifest.json再动态require对应文件。在鸿蒙里这行代码会报错SyntaxError: Dynamic import is not supported in this context。解决方案是放弃动态加载改用静态注册表。我在鸿蒙项目里创建了一个skillRegistry.ts文件内容如下// skillRegistry.ts import { GitSkill } from ./skills/git; import { FileSearchSkill } from ./skills/fileSearch; import { CodeReviewSkill } from ./skills/codeReview; export const SKILL_REGISTRY: Recordstring, { execute: (input: any) Promiseany } { git-commit: GitSkill, file-search: FileSearchSkill, code-review: CodeReviewSkill, };然后在EntryAbility初始化时遍历SKILL_REGISTRY将每个 Skill 的execute方法包装成统一接口。这样插件不再是“随时可加”的而是“编译时就固定”的。好处是安全、稳定、可调试坏处是每次新增插件都必须修改skillRegistry.ts并重新构建 APK。这违背了 Harness “零配置插件”的初衷但却是鸿蒙安全模型下的唯一可行路径。3.2 技能执行上下文从 Node.js 全局对象到 ArkTS 能力封装Electron 插件能直接调用child_process.exec()运行 shell 命令或用fs.promises.readFile()读文件是因为它们运行在 Node.js 环境里拥有完整的全局对象。ArkTS 没有global没有process没有__dirname。一个典型的 Skill 插件// skills/git.js module.exports { async execute(input) { const cwd process.cwd(); // 获取当前工作目录 const result await exec(git log -n ${input.count} --oneline, { cwd }); return result.stdout; } };在鸿蒙里process.cwd()是undefinedexec不存在。你必须把所有这类能力封装成 ArkTS 的 Ability 接口再通过featureAbility调用。例如我创建了一个ShellExecutorAbility它在module.json5中声明ohos.permission.EXECUTE_SHELL这是一个敏感权限需用户手动开启然后在 ArkTS 里提供// ShellExecutorAbility.ts import featureAbility from ohos.app.ability.featureAbility; import { BusinessError } from ohos.base; export async function execShellCommand(command: string, options?: { cwd?: string }): Promisestring { try { const result await featureAbility.callAbility({ abilityName: ShellExecutorAbility, action: exec, data: { command, cwd: options?.cwd || } }); return result.data.output; } catch (error) { throw new BusinessError(10001, Shell execution failed: ${(error as Error).message}); } }然后在 Skill 里// skills/git.ets import { execShellCommand } from ../abilities/ShellExecutorAbility; export const GitSkill { async execute(input: { count: number }) { const cwd getContext().filesDir; // 用鸿蒙的应用私有目录替代 process.cwd() const result await execShellCommand(git log -n ${input.count} --oneline, { cwd }); return result; } };这个过程不是简单的 API 替换而是能力边界的重新划定原来插件自己决定要执行什么命令现在必须通过预设的 Ability 接口且每个接口都有明确的权限和输入校验。这大幅提升了安全性但也让插件开发变得笨重——开发者得先申请权限、再写 Ability、再暴露接口最后才能在 Skill 里调用。3.3 插件配置从package.json到skillManifest.json5的格式升维Harness 的插件通常带一个package.json定义name、version、main、dependencies。鸿蒙不认package.json它要求所有资源描述必须是json5格式支持注释、尾逗号且必须放在resources/base/profile/下。我把插件元数据单独抽出来定义为skillManifest.json5{ // 插件唯一标识用于 registry 查找 id: git-commit, // 插件显示名称支持多语言 name: { zh-CN: Git 提交分析, en-US: Git Commit Analyzer }, // 插件图标必须是 resources/base/element/ 下的 resource id icon: $media:icon_git, // 插件入口文件相对于 skills/ 目录 entry: ./git.ets, // 插件所需权限列表安装时自动申请 permissions: [ ohos.permission.DISTRIBUTED_DATASYNC, ohos.permission.GET_NETWORK_INFO ], // 插件配置 Schema用于生成设置 UI configSchema: { type: object, properties: { maxCommits: { type: integer, default: 10, minimum: 1, maximum: 100, title: { zh-CN: 最大提交数, en-US: Max Commits } } } } }这个文件的作用是让鸿蒙系统在安装插件时能自动解析其权限需求、生成配置界面、并将其注册到SKILL_REGISTRY。它不再是给开发者看的文档而是给操作系统看的契约。我花了整整两天把 12 个常用插件的package.json全部重写为skillManifest.json5并确保每个permissions字段都经过测试——少一个插件就功能残缺多一个用户安装时会看到冗余的权限请求降低信任感。4. 双网络记忆模型在鸿蒙的分布式能力下重建上下文连续性DeepSeek Harness 的“双网络记忆模型”并非玄学它指代的是两个层面的状态持久化短期记忆当前对话 session 的 token 缓存、流式响应的 chunk 拼接和长期记忆用户偏好、常用 Skill 配置、历史对话摘要。在 Electron 里前者用Map存在内存里后者用localStorage或IndexedDB存在磁盘上。鸿蒙的分布式能力Distributed Data Service, DDS本可完美承接此需求但实际落地时你会发现它和 Harness 的设计哲学存在根本冲突。4.1 DDS 不是数据库是分布式状态同步引擎鸿蒙的 DDSohos.data.distributedData设计目标是在多个设备间实时同步小块结构化数据如用户设置、播放进度。它的 API 是put()、get()、on()数据以KVStore形式组织key 是字符串value 是string | number | boolean | Uint8Array。它不支持复杂查询、不支持事务、不支持大文件存储。而 Harness 的长期记忆往往包含用户上传的 PDF 文件用于 RAG 检索大段的对话历史 JSON可能超 1MB模型的 embedding 向量二进制数组。试图用 DDS 存储这些会触发QuotaExceededError配额超限因为 DDS 默认单个 KVStore 限制 10MB且频繁写入大块数据会导致同步延迟飙升。我最初把所有localStorage.setItem(history, JSON.stringify(history))改成kvStore.put(history, history)结果发现当 history 超过 500 条对话时put()调用耗时从 2ms 暴涨到 1200ms且设备间同步失败率高达 30%。正确做法是分层存储。短期记忆仍用 ArkTS 的Map或Array存在内存里这是最快的长期记忆中的结构化数据如用户设置、Skill 开关状态用 DDS 存储享受跨设备同步长期记忆中的非结构化大数据如文件、大文本、向量存到鸿蒙的Preferences键值对支持 1MB 以下或RelationalStoreSQLite支持复杂查询并用 DDS 同步一个轻量级索引如{ fileId: abc123, size: 2048000, lastModified: 1712345678 }。4.2 会话状态从WebSocket到AbilityConnection的连接保活Harness 的前端通过WebSocket连接后端维持一个长连接来接收流式响应data: {...}\n\n。鸿蒙的 WebView 不支持WebSocket的binaryType设置且onmessage事件对data:前缀的 SSE 流式响应处理不稳定。我尝试过用ohos.net.http重写前端网络层但发现它不支持 Server-Sent EventsSSE协议http.request()只能发一次请求无法持续接收chunk。最终方案是用 AbilityConnection 实现进程间通信。我把后端服务模型推理、Skill 执行封装成一个独立的BackgroundServiceAbility前端 UI 通过connectAbility()与其建立连接然后用rpc机制发送请求、接收响应// FrontendAbility.ets import rpc from ohos.rpc; class BackendProxy { private remote: rpc.IRemoteObject; constructor(remote: rpc.IRemoteObject) { this.remote remote; } async chatStream(input: ChatInput): PromiseAsyncGeneratorChatChunk { const data new rpc.RpcMessage(); data.writeSequenceable(input); const result await this.remote.sendRequest(1, data); // 1 是 chatStream 方法 ID // result 是一个可迭代的 Chunk 流由 BackgroundServiceAbility 生成 return result as AsyncGeneratorChatChunk; } } // 在 connectAbility 成功后用 result.remote 创建 BackendProxyBackgroundServiceAbility则用ohos.net.http调用内网 LLM并将data:流式响应解析成ChatChunk对象通过rpc的sendSequenceable()逐个推送给前端。这种方式绕过了 WebView 的限制且AbilityConnection的连接由鸿蒙系统维护比WebSocket更稳定即使 App 进入后台连接也不会断。4.3 记忆一致性从单机localStorage到分布式KVStore的冲突解决最大的坑在于当用户在手机和 PC鸿蒙 PC 版上同时使用 HarnessDDS 同步的userSettings可能发生冲突。例如手机上把git-commit插件设为启用PC 上设为禁用DDS 会把两个版本都同步过来最终状态取决于谁最后写入。鸿蒙的 DDS 默认采用“最后写入者胜出”LWW策略但这对用户设置来说不合理——用户期望的是“合并”而不是“覆盖”。解决方案是引入CRDTConflict-Free Replicated Data Type思想用一个简单的Set类型来管理插件开关// userSettings.json5 { enabledSkills: [git-commit, file-search], // 字符串数组 disabledSkills: [code-review] // 显式禁用列表 }同步时enabledSkills和disabledSkills分别作为两个独立的KVStore用put()更新。前端读取时先取enabledSkills再过滤掉disabledSkills中的项。这样手机添加git-commit到enabledSkillsPC 删除git-commit从enabledSkills两者互不干扰最终状态由用户在各自设备上的操作决定没有冲突。经验鸿蒙的 DDS 同步有 500ms 的最小延迟不要在on()回调里立即更新 UI否则会看到闪烁。我的做法是on()触发后用setTimeout(() updateUI(), 100)延迟更新给 UI 渲染留出时间。5. 构建与分发从 Electron 打包到鸿蒙 AppPack 的交付链重构把 Harness 编译成鸿蒙应用远不止是把代码扔进 DevEco Studio 点“构建”那么简单。Electron 的打包是“一包打天下”一个electron-builder配置产出 Windows MSI、macOS DMG、Linux AppImage。鸿蒙的 AppPack.app包则是“一设备一包”x86_64 PC 版、ARM64 手机版、RISC-V 开发板版必须分别构建且每个包的module.json5、资源目录、ABI 依赖都不同。5.1 ABI 适配从 x86_64 到 ARM64 的 NDK 二进制重编译Harness 的很多 Skill 依赖原生二进制如git命令、ffmpeg工具、Python 的numpyC 扩展。Electron 里这些二进制随包一起分发系统自动选择对应架构版本。鸿蒙没有这种机制你必须为每个目标设备架构单独编译对应的 NDK 库。我用鸿蒙的ndk-build工具链为git编译了三个版本libs/x86_64/libgit.so鸿蒙 PC 版libs/arm64-v8a/libgit.so鸿蒙手机版libs/riscv64/libgit.soOpenHarmony 开发板关键点在于module.json5的abi字段必须精确匹配且libs/目录结构必须严格遵循鸿蒙规范。漏掉一个架构目录对应设备就会dlopen failed: library libgit.so not found。更坑的是鸿蒙的libgit.so不能直接用 Linux 的git源码编译——它依赖libcrypto.so和libssl.so而鸿蒙的 OpenSSL 是精简版缺少EVP_PKEY_CTX_set_rsa_oaep_md()等函数。我不得不从鸿蒙 SDK 的third_party/openssl目录下拷贝头文件重新编译libgit耗时三天。5.2 资源分包从单体包到 HAP 分包的体积控制Electron 的node_modules动辄 200MB全部打进鸿蒙.app包会导致安装失败鸿蒙应用包大小限制为 1GB但推荐 100MB。鸿蒙的解决方案是HAPHarmony Ability Package分包把核心 UI 打进entryHAP把大体积的 Skill 插件、模型权重、文档资源打包成独立的featureHAP在用户首次使用时按需下载。我按功能划分了 4 个 HAPentry.hap主 UI、基础框架、核心 Skill如text-genskills-hap所有第三方 Skill 插件git、file-search等models-hap本地小模型如Qwen1.5-0.5B的 GGUF 文件docs-hap离线帮助文档HTML CSS JS。每个 HAP 都有自己的module.json5声明deliveryWithInstall: false非随安装分发并通过AppUpdateManagerAPI 触发下载。难点在于skills-hap下载完成后如何让entry.hap动态加载其中的 Skill鸿蒙不支持import()加载远程 HAP必须用hapModule.load()加载本地 HAP 文件再用hapModule.getExportedModule()获取导出对象。这意味着skills-hap的module.json5必须声明exportedModules且entry.hap得在skills-hap下载完成后再调用load()。整个流程要处理下载进度、失败重试、版本校验代码量比 Electron 的require()多出 10 倍。5.3 签名与发布从自签名证书到华为应用市场审核的合规穿越Electron 应用用自签名证书打包即可运行。鸿蒙应用必须用华为签名证书.p12文件签名且证书的CNCommon Name必须与module.json5中的bundleName完全一致如com.deepseek.harness。我第一次提交到华为应用市场时被拒三次第一次bundleName用了deepseek-harness含短横线华为要求只能是字母、数字、点第二次versionCode用了1.0.0字符串华为要求是整数且每次更新必须递增第三次requestPermissions里声明了ohos.permission.GET_ACCOUNT获取账号但应用里根本没用到华为认为是过度申请。最终我用华为的DevEco Studio内置的签名工具生成了符合要求的证书并在build-profile.json5中配置{ apiVersion: { minSdkVersion: 10, targetSdkVersion: 12 }, signingConfig: { release: { storeFile: certs/harmony-release.p12, storePassword: ******, keyAlias: harmony-key, keyPassword: ****** } } }发布前还必须通过华为的AppGallery Connect平台提交隐私政策 URL、应用截图、测试账号并等待人工审核。整个流程从打包到上线耗时 11 天而 Electron 的 GitHub Release5 分钟搞定。6. 八个坑的终极清单从踩坑现场到避坑手册回看整个迁移过程那“8 个坑”不是虚数而是我在 DevEco Studio 控制台里一条条复制粘贴、逐个击破的真实错误。我把它们整理成一张可执行的避坑手册每一条都附带错误现象、根因、解决方案和验证方式方便后来者直接抄作业。序号坑位名称典型错误现象根本原因解决方案验证方式1沙箱权限声明遗漏fileio.open()报SecurityException日志无权限提示module.json5中requestPermissions未包含ohos.permission.READ_USER_STORAGE或usedScene.abilities未列出实际使用该权限的 Ability在module.json5的requestPermissions中为每个用到文件 I/O 的 Ability 显式声明权限并设置when: inuse在SkillServiceAbility中调用fileio.open()成功返回文件描述符2WebView localhost 失效前端fetch(http://localhost:3000/api)返回Network Error鸿蒙 WebView 的localhost指向设备自身而非开发 PC将 API 地址改为 PC 的局域网 IP如http://192.168.1.100:3000并在module.json5中声明ohos.permission.INTERNET在鸿蒙设备浏览器中访问http://192.168.1.100:3000/health返回OK3动态插件加载失败import(./skills/ name .ets)报SyntaxError: Dynamic import is not supportedArkTS 不支持变量动态导入仅支持字符串字面量放弃动态加载改用静态SKILL_REGISTRY对象所有 Skill 在编译时注册启动 AppSKILL_REGISTRY[git-commit]返回有效的 Skill 对象4插件执行上下文缺失process.cwd()为undefinedchild_process.exec()不存在ArkTS 无 Node.js 全局对象所有系统能力需通过 Ability 封装创建ShellExecutorAbility用featureAbility.callAbility()调用传入命令和参数在 Skill 中调用execShellCommand(ls -l)返回非空字符串5DDS 大数据写入超时kvStore.put(history, bigJson)耗时 1000ms同步失败DDS 单次写入建议 100KB大 JSON 超出配额将大数据存入RelationalStoreDDS 只存轻量索引如{ id: hist-001, size: 123456 }kvStore.put()耗时 10msRelationalStore查询返回完整历史6WebSocket 流式响应中断前端onmessage只收到第一个data:chunk后续无响应鸿蒙 WebView 对 SSE 协议支持不完整废弃 WebSocket/SSE改用AbilityConnectionrpc实现进程间流式通信前端chatStream()返回的AsyncGenerator能for await遍历所有ChatChunk7NDK 二进制 ABI 不匹配dlopen failed: library libgit.so not foundlibs/目录下缺少目标设备架构的.so文件或module.json5的abi字段未