一、前言:从"仓库内能跑"到"外部能消费"

很多鸿蒙 SDK 开发者最初的打包流程是这样的:在 DevEco Studio 里点一下 Build,看到 BUILD SUCCESSFUL,就把 build/.../outputs/default/xxx.har 拷给别人。直到真正发布那天,问题才集中爆发:

  1. HAR 拷给别人,import 报错:外部工程 import { AgentRuntime } from '@arkagent/core' 报"找不到模块"。

  2. 依赖解析失败ohpm install@arkagent/core@1.0.0 找不到——因为你的包根本没发布到 OHPM 仓库。

  3. 嵌套依赖断裂:消费者引入了 @arkagent/eval,但 eval 内部声明 "@arkagent/core": "1.0.0",ohpm 解析不了。

  4. daemon 模式 SDK 报错:命令行构建偶发 00303217 Configuration Error,提示 DEVECO_SDK_HOME 无效,但明明环境变量设了。

  5. 本仓库编译通过,独立工程编译不过:因为仓库里有 file:../agent_core 这种 monorepo 相对路径,外部根本没有这个目录。

根因只有一句话:"在本仓库里编译通过"和"作为独立产物被外部消费"是两件完全不同的事。前者依赖的是 monorepo 的源码相对路径,后者依赖的是 HAR 包内的二进制产物和包元数据。

ArkAgent 在阶段 9 发布前专门设计了 verify-har-consumer.sh,用 mktemp 建一个完全独立的临时工程,真实地编译一遍 HAR,就是为了关掉"假成功"这道门。本文把这套发布门禁的每个环节拆开讲清楚。


二、HAR 与 HAP:两种产物的本质区别

在动手打包之前,先把基础概念理清楚。HarmonyOS 工程有两类核心产物,它们的定位、内容、依赖关系完全不同。

2.1 一张表看清差异

维度

HAR(HarmonyOS Archive)

HAP(HarmonyOS Ability Package)

定位

SDK 二进制产物,供其他模块依赖

完整应用安装包,装到设备上

构建命令

hvigorw assembleHar

hvigorw assembleHap

模块类型

module.json5"type": "har"

module.json5"type": "entry""feature"

能否独立运行

❌ 不能,必须被 HAP 引入

✅ 能,安装即可启动

内容

ArkTS 字节码 modules.abc + 类型声明 .d.ets + oh-package.json5 + module.json

应用代码 + 资源 + Ability 配置 + 签名

被谁依赖

被其他 HAR 或 HAP 通过 ohpm 引入

不被依赖,是终端产物

是否签名

是(debug/release 签名)

典型产物

agent_core.haragent_eval.har

entry-default-signed.hap

2.2 ArkAgent 的三模块产物

ArkAgent 工程有三个模块,对应两种产物:

┌─────────────────────────────────────────────────────────────┐
│                     ArkAgent 工程                            │
├─────────────────────────────────────────────────────────────┤
│  agent_core/   →  agent_core.har   (@arkagent/core@1.0.0)  │
│  agent_eval/   →  agent_eval.har   (@arkagent/eval@1.0.0)  │
│  entry/        →  entry-default-signed.hap  (示例 App)     │
└─────────────────────────────────────────────────────────────┘

build-profile.json5 里,三个模块都被注册为 products 的构建目标:

{
  "app": {
    "products": [
      {
        "name": "default",
        "targetSdkVersion": "6.1.1(24)",
        "compatibleSdkVersion": "6.1.0(23)",
        "runtimeOS": "HarmonyOS",
        "buildOption": {
          "strictMode": {
            "caseSensitiveCheck": true,
            "useNormalizedOHMUrl": true   // ← HAR 消费的关键开关
          }
        }
      }
    ]
  },
  "modules": [
    { "name": "entry",      "srcPath": "./entry" },
    { "name": "agent_core", "srcPath": "./agent_core" },
    { "name": "agent_eval", "srcPath": "./agent_eval" }
  ]
}

关键点:

  • agent_coreagent_evalhvigorfile.ts 用的是 harTasks(HAR 构建插件),而根工程和 entry 用的是 appTasks/entryTasks。这决定了 assembleHar 能否产出 HAR。

  • useNormalizedOHMUrl: true 是 HAR 能被外部正确解析的前提,它让模块路径采用规范化 OHM URL,避免大小写或路径格式差异导致消费失败。

  • targetSdkVersion: "6.1.1(24)" 表示 API 24,这是 HAR 元数据里 compatibleSdkVersion 的来源。

2.3 模块的 hvigorfile 决定产物类型

HAR 模块和 HAP 模块的 hvigorfile.ts 内容不同,这是 hvigor 决定走哪条构建流水线的依据:

// agent_core/hvigorfile.ts 和 agent_eval/hvigorfile.ts —— HAR 模块
import { harTasks } from '@ohos/hvigor-ohos-plugin';

export default {
  system: harTasks,   // ← harTasks 才会产出 .har
  plugins: []
}
// 根工程 hvigorfile.ts —— App 壳
import { appTasks } from '@ohos/hvigor-ohos-plugin';

export default {
  system: appTasks,
  plugins: []
}

如果一个模块的 hvigorfile.ts 里写的是 harTaskshvigorw assembleHar 才会为它产出 HAR;如果写的是 entryTasks/featureTasks,那只有 assembleHap 会产出 HAP。混用会导致"命令跑了但没产物"的迷惑现象。


三、模块依赖铁律:core / eval / entry 的有向无环图

HAR 打包最容易翻车的地方不是构建命令,而是依赖方向。依赖方向错了,轻则循环依赖编译失败,重则 SDK 边界污染、产物膨胀。ArkAgent 把模块依赖钉死成一条单向链。

3.1 依赖方向图

                    ┌──────────────────────┐
                    │      entry (HAP)     │   ← 示例 App / 端到端验收
                    │  烘焙助手 + 评测 UI   │
                    └──────────┬───────────┘
                          ┌────┴────┐
                  entry → core   entry → eval(仅评测场景)
                          ▲           │
                          │   eval → core
                          │           │
                    ┌─────┴───────────┴─────┐
                    │   agent_core (HAR)    │   ← Agent Runtime
                    │   LLM / 工具 / 状态   │   ★ 不得依赖 entry 或 eval
                    └───────────────────────┘
                              ▲
                              │
                    ┌─────────┴─────────────┐
                    │   agent_eval (HAR)    │   ← 评估框架
                    │   Record/Replay/Metrics│  依赖 core,但 core 不依赖它
                    └───────────────────────┘

三条铁律(来自 ADR-0001 模块边界 + ADR-0015 Eval 边界):

规则

允许

禁止

entry → core

——

entry → eval(仅评测场景)

——

eval → core

——

core → eval

——

❌ 反向依赖

core → entry

——

❌ 反向依赖

3.2 oh-package.json5 里的依赖声明

依赖方向直接体现在每个模块的 oh-package.json5 里。先看生产者(HAR 模块)的声明:

// agent_core/oh-package.json5 —— 不依赖任何 ArkAgent 模块
{
  "name": "@arkagent/core",
  "version": "1.0.0",
  "description": "HarmonyOS local-first agent runtime: state, hooks, tool approval, loop detection, multimodal providers.",
  "main": "src/main/ets/Index.ets",
  "author": "ArkAgent",
  "license": "MIT",
  "dependencies": {}    // ← 空,core 是底层
}
// agent_eval/oh-package.json5 —— 依赖 core
{
  "name": "@arkagent/eval",
  "version": "1.0.0",
  "description": "ArkAgent evaluation, recording/replay, metrics, reporting and observability.",
  "main": "src/main/ets/Index.ets",
  "author": "ArkAgent",
  "license": "MIT",
  "dependencies": {
    "@arkagent/core": "1.0.0"   // ← eval 用 SemVer 声明对 core 的依赖
  }
}

注意这里有个微妙之处:agent_eval 在仓库内部用 "@arkagent/core": "1.0.0" 声明依赖(SemVer 版本号),而不是 file:../agent_core。这是因为 SemVer 声明更符合"发布后的真实消费场景"——外部消费者也是按版本号理解的。但在仓库内部构建时,hvigor 会通过 monorepo 的模块注册把 1.0.0 解析到本地的 agent_core 模块。这个机制只在 monorepo 内有效,外部消费者直接拿 eval.har 用会遇到解析不了的问题(后面踩坑会讲)。

再看消费者(HAP 模块)的声明:

// entry/oh-package.json5 —— 用 file: 引用源码模块
{
  "name": "entry",
  "version": "1.0.0",
  "dependencies": {
    "@arkagent/core": "file:../agent_core",   // ← monorepo 内部用源码路径
    "@arkagent/eval": "file:../agent_eval"
  }
}

entry 在仓库内部开发时用 file:../agent_core 直接引用源码模块,这样改 core 代码 entry 立即生效,不用每次重新打 HAR。但发布给外部消费者时绝不能用这种相对路径,因为外部根本没有 ../agent_core 这个目录。

3.3 自动化守卫:check-eval-deps.sh

依赖铁律不能只靠人记,必须有自动化检查兜底。ArkAgent 用一个简单的脚本扫描 agent_core/src 下所有 .ets 文件,发现任何对 @arkagent/eval 的引用就 fail:

#!/usr/bin/env bash
# scripts/check-eval-deps.sh
# Fail if agent_core imports agent_eval (dependency inversion check).
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
if rg -n "@arkagent/eval|agent_eval" "$ROOT/agent_core/src" --glob '*.ets' 2>/dev/null; then
  echo "FAIL: agent_core must not depend on agent_eval" >&2
  exit 1
fi
echo "OK: agent_core does not depend on agent_eval"

这个脚本是发布门禁的一部分。rg(ripgrep)扫描源码,一旦命中就立即退出非 0。设计上只检查 agent_core/src(core 的实现代码),不检查 eval——因为方向是单向的,只需要守住"core 不能反向依赖 eval"这一条。

为什么不用 hvigor 的依赖图检查?因为 hvigor 的依赖图只能识别 oh-package.json5 里声明的依赖,识别不了源码 import。理论上 core 可以在 dependencies 里不声明 eval,却在某个 .ets 文件里偷偷 import { EvalRunner } from '@arkagent/eval'——这种"未声明但 import"的情况在 ArkTS 编译期可能因为模块可见性而暂时不报错,但发布后会成为定时炸弹。源码扫描是最后一道防线。


四、assembleHar 命令行构建:每一步都有讲究

理解了产物和依赖,现在进入真正的构建环节。ArkAgent 的标准构建命令长这样:

# agent_core HAR 构建
DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
  /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw assembleHar --no-daemon

# 标准 debug HAP 构建
DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
  /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw assembleHap --no-daemon

# 全部本地单元测试
DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
  /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw test --no-daemon

这三条命令在 2026-07-12 于 HarmonyOS 6.1.1 / API 24 工程上验证通过。看起来只是"跑个命令",但每个参数都有理由。

4.1 为什么是绝对路径的 hvigorw?

DevEco Studio 自带了 hvigor 工具链,位于 /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw。命令行构建必须显式指向这个路径,原因有两个:

  1. 普通 shell 没有 hvigorw 的 PATH:DevEco Studio 内部的终端会自动配置工具链路径,但外部 shell(iTerm、Terminal、CI runner)不会。

  2. 保证版本一致:用 IDE 内置的 hvigorw,保证命令行构建和 IDE 构建用的是同一个工具链版本,避免"IDE 能编译、命令行不能"的诡异差异。

如果你的 DevEco Studio 装在别的位置,调整这两个绝对路径即可,但 DEVECO_SDK_HOME 必须指向安装包内的 Contents/sdk

4.2 ⚠️ 踩坑一:DEVECO_SDK_HOME 与 daemon 继承的无效变量

症状:首次在命令行跑 hvigorw assembleHap,Hvigor daemon 正常启动,但配置阶段返回 00303217 Configuration Error,提示 shell 里的 DEVECO_SDK_HOME 无效。明明环境变量已经 export 了,为什么还说无效?

根因:这是 HAR spike(docs/research/spikes/phase-00-har.md)阶段 0 就踩到的坑。Hvigor 默认用 daemon 模式加速构建——首次启动一个 daemon 进程,后续构建复用这个进程。问题在于:daemon 进程在首次启动时捕获了当时的环境变量快照。如果你第一次启动 daemon 时 DEVECO_SDK_HOME 没设(或指向旧路径),这个 daemon 就会一直带着错误的 SDK 路径。后续你再 export DEVECO_SDK_HOME=正确路径,当前 shell 是对的,但已有的 daemon 进程仍然用旧的环境变量快照。

DevEco Studio 内部已经正确配置了 SDK,但普通 shell 没有指向那个目录;旧 daemon 也可能继承错误环境。两者叠加,就出现了"环境变量设了但 daemon 不认"的现象。

修复:两个动作组合起来才能稳定解决:

# 1. 单次命令内显式设置 SDK 路径(不依赖 shell 的 export)
# 2. 加 --no-daemon,强制本次构建不复用任何已有 daemon
DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
  /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw assembleHar --no-daemon

--no-daemon 的作用是:本次构建用一个全新的、临时的 hvigor 进程,它会读取当前命令行传入的环境变量,不继承任何历史 daemon 的状态。构建完进程就退出,不留下"脏 daemon"。

被拒绝的方案

  • kill 掉所有 hvigor daemon 再构建:治标不治本,下次忘了又翻车,而且 daemon 进程不好精准识别。

  • ❌ 把 DEVECO_SDK_HOME 写进 ~/.zshrc 永久 export:会让所有 HarmonyOS 工程(可能用不同 SDK 版本)共享同一个 SDK 路径,多版本切换时翻车。

  • ❌ 只在 IDE 里构建:CI 跑不了,发布门禁脚本也没法复用。

显式环境变量 + --no-daemon 是最可靠的组合,ArkAgent 所有构建命令、所有发布门禁脚本统一采用这套写法。构建结果稳定为 BUILD SUCCESSFUL,约 6-7 秒。

4.3 三条命令的职责边界

命令

作用

产出

适用场景

assembleHar

编译 HAR 模块,打包成 .har

agent_core.haragent_eval.har

发布 SDK、外部消费

assembleHap

编译 entry 模块,打包+签名成 .hap

entry-default-signed.hap

装机、端到端验收

test

跑本地单元测试(entry + core)

测试报告

质量门禁

一个容易混淆的点:根目录跑 assembleHar同时agent_coreagent_eval 两个 HAR 模块产出 HAR,因为它们都注册在 build-profile.json5modules 里。而 assembleHap 只会为 entry 产出 HAP。test 在根目录跑会同时执行 entry 和 agent_core 的本地测试——阶段 1 验证结果是 32 个 Core 用例全部通过。

4.4 产物路径

构建成功后,HAR 产物位于:

agent_core/build/default/outputs/default/agent_core.har
agent_eval/build/default/outputs/default/agent_eval.har

注意路径里有 default(product name)和 default(target name)两层,这对应 build-profile.json5 里的配置。debug/release 模式可能产出不同子路径,但 HAR 模块本身通常只配 default target。

实测产物大小(2026-07-13 第七轮构建):

HAR

大小

说明

agent_core.har

~404 KB

Runtime 主体,含 LLM/工具/状态/记忆等

agent_eval.har

~154 KB

评估框架,依赖 core

core 比 eval 大是合理的——core 是 Runtime 主体,eval 是建立在 core 之上的评估层。


五、HAR 产物内部结构: HAR 里到底装了什么?

很多开发者把 HAR 当成一个"黑盒 zip",出了问题不知道往里看。其实 HAR 本质上是一个 gzip tar 包,解开后结构清晰。理解内部结构是诊断消费问题的前提。

5.1 解包看结构

# HAR 本质是 tar.gz
tar -xzf agent_core.har -C /tmp/core-inspect

解包后的目录结构:

package/
├── oh-package.json5          ← 包元数据(name/version/依赖/构建标志)
├── ets/
│   ├── modules.abc            ← ArkTS 字节码(编译产物,核心)
│   └── sourceMaps.map         ← source map(调试用)
├── src/main/
│   ├── module.json            ← 模块清单(type: har)
│   └── ets/
│       ├── Index.d.ets        ← 公共 API 类型声明(消费方 import 的入口)
│       ├── codec/DomainCodec.d.ets
│       ├── controller/AgentController.d.ets
│       ├── core/Json.d.ets
│       ├── domain/Message.d.ets
│       ├── runtime/AgentRuntime.d.ets
│       ├── tool/ToolRegistry.d.ets
│       └── ...                ← 每个公共导出对应一个 .d.ets
└── libs/                      ← HAR 依赖的 native so(如果有)

5.2 三个关键文件

文件

作用

消费方如何使用

ets/modules.abc

ArkTS 编译后的字节码,HAR 的"可执行体"

运行时由 OHM(OpenHarmony Module)加载

src/main/ets/Index.d.ets

公共 API 的类型声明

编译期用于类型检查,消费方 import { AgentRuntime } 就靠它

oh-package.json5

包元数据(name/version/依赖/构建标志)

ohpm 解析依赖、hvigor 决定构建行为

modules.abcIndex.d.ets 是 HAR 能被消费的两个充要条件——缺任何一个,消费者要么编译不过(缺声明),要么运行不了(缺字节码)。verify-har-consumer.sh 专门检查这两个文件的存在。

5.3 oh-package.json5 元数据:HAR 的"身份证"

解包后 HAR 内的 oh-package.json5 比源码里的丰富很多,多出来的是构建期注入的元数据。下面是 agent_core.har 解包后的真实内容(格式化后):

{
  "name": "@arkagent/core",
  "version": "1.0.0",
  "description": "HarmonyOS local-first agent runtime: ...",
  "author": "ArkAgent",
  "license": "MIT",
  "dependencies": {},
  "types": "src/main/ets/Index.d.ets",        // ← 类型声明入口
  "artifactType": "obfuscation",
  "metadata": {
    "byteCodeHar": true,                       // ← 字节码 HAR(区别于源码 HAR)
    "sourceRoots": ["./src/main"],
    "debug": true,
    "dependencyPkgVersion": {},                // ← 依赖版本快照
    "declarationEntry": [],
    "useNormalizedOHMUrl": true                // ← 规范化 OHM URL
  },
  "compatibleSdkVersion": 23,                  // ← 对应 API 23 (6.1.0)
  "compatibleSdkType": "HarmonyOS",
  "obfuscated": false                          // ← 未混淆
}

agent_eval.har 的元数据类似,但 dependenciesdependencyPkgVersion 非空:

{
  "name": "@arkagent/eval",
  "version": "1.0.0",
  "dependencies": {
    "@arkagent/core": "1.0.0"                  // ← 嵌套依赖声明
  },
  "metadata": {
    "byteCodeHar": true,
    "dependencyPkgVersion": {
      "@arkagent/core": "1.0.0"                // ← 依赖版本快照
    },
    "useNormalizedOHMUrl": true
  },
  "compatibleSdkVersion": 23
}

关键元数据解读:

字段

含义

消费影响

byteCodeHar: true

HAR 内是字节码而非源码

消费方无需重新编译 HAR 内部代码,只编译自己的代码

useNormalizedOHMUrl: true

模块路径规范化

避免大小写/路径格式导致的解析失败

compatibleSdkVersion: 23

兼容的最低 API 版本

消费方工程的 SDK 不能低于此版本

dependencyPkgVersion

HAR 构建时的依赖版本快照

消费方需提供兼容版本(这里是 core@1.0.0

obfuscated: false

未混淆

调试友好;release 发布可考虑混淆

byteCodeHar: true 是个重要信号——它意味着 HAR 里装的是 ArkTS 字节码(.abc),消费方编译时不会重新编译 HAR 内部的代码,只编译消费方自己的代码并链接到 HAR 的字节码。这让 HAR 消费"又快又稳":快是因为不用重编译 SDK,稳是因为 SDK 的字节码是生产者锁定的,消费方环境差异不会影响 SDK 内部行为。


六、本地依赖声明:file:./libs 的正确写法

构建出 HAR 后,下一步是让消费方(你的 App 工程)能用上它。在鸿蒙里,本地 HAR 依赖用 file: 协议声明。

6.1 monorepo 内部 vs 外部消费的两种写法

ArkAgent 在仓库内部和外部消费用了两种不同的 file: 写法,对应两种场景:

场景 A:monorepo 内部开发(entry 消费源码模块)
  entry/oh-package.json5:
    "@arkagent/core": "file:../agent_core"      ← 引用源码目录,改 core 立即生效

场景 B:外部工程消费(独立 App 引用 HAR 产物)
  MyApp/entry/oh-package.json5:
    "@arkagent/core": "file:./libs/agent_core.har"   ← 引用 HAR 文件

为什么 monorepo 内部用源码目录而不是 HAR? 因为开发期 core 还在频繁改动,每次改完重新打 HAR 太慢。file:../agent_core 直接引用源码模块,core 改一行 entry 立即重新编译生效。这种"源码依赖"只在 monorepo 内有效,因为 hvigor 通过 build-profile.json5modules 注册表知道 ../agent_core 是个本地模块。

为什么外部消费必须用 HAR 文件? 因为外部工程没有 agent_core 源码目录,file:../agent_core 会指向一个不存在的路径。外部消费方拿到的就是一个 .har 文件,必须用 file:./libs/agent_core.har 指向它。

6.2 外部消费的完整接入步骤

MyHarmonyApp/
└── entry/
    ├── libs/
    │   └── agent_core.har      ← 把 HAR 复制到这里
    ├── oh-package.json5        ← 声明 file:./libs/agent_core.har
    └── src/

entry/oh-package.json5

{
  "name": "entry",
  "version": "1.0.0",
  "dependencies": {
    "@arkagent/core": "file:./libs/agent_core.har"
  }
}

安装依赖:

/Applications/DevEco-Studio.app/Contents/tools/ohpm/bin/ohpm install

验证接入(能编译通过就说明 HAR 接入成功):

import { AgentRuntime } from '@arkagent/core'

6.3 同时消费 core 和 eval 的嵌套依赖问题

如果外部工程同时需要 core 和 eval,会撞到嵌套依赖问题。eval 的 oh-package.json5 内部声明了:

"dependencies": {
  "@arkagent/core": "1.0.0"
}

ohpm 在解析 eval 的依赖时,会尝试从 OHPM registry 下载 @arkagent/core@1.0.0——但你的包根本没发布到 OHPM,于是解析失败。这是 HAR 分发里最经典的坑,下一章详细讲。


七、独立消费者验证:verify-har-consumer.sh 的设计哲学

这是本文最重要的一章。ArkAgent 阶段 9 发布前专门重写了 verify-har-consumer.sh,用独立临时工程真实编译 HAR。这个脚本是发布门禁的核心,它的设计哲学直接回答了"HAR 到底能不能用"这个问题。

7.1 为什么需要独立消费者验证?

验证层次

能发现什么问题

不能发现什么问题

本仓库 assembleHar 通过

HAR 能产出、本仓库编译无错

外部消费时的依赖解析、嵌套依赖、路径问题

本仓库 entry 能 import

源码 API 正确

外部工程没有 ../agent_core 源码路径

独立消费者真实编译

上述所有问题 + 嵌套依赖 + ohpm 解析 + 路径隔离

——

只有独立消费者验证才能覆盖"本仓库编译通过但外部用不了"的所有情况。因为本仓库里有 monorepo 的源码路径兜底,掩盖了 HAR 作为独立产物的真实状态。

7.2 ⚠️ 踩坑二:verify-har 的"假成功"

症状:阶段 9 第六轮验收前,verify-har-consumer.sh 报"通过",但 Codex 复查时发现消费者根本没真正编译——ohpm install 实际上失败了,脚本却用 manual symlink 绕过失败,然后只检查了 modules.abc 文件存在就判定成功。

根因:旧版脚本的逻辑是:

  1. ohpm install 失败 → 手动 ln -s 创建符号链接到 oh_modules

  2. 只检查 oh_modules/@arkagent/core/ets/modules.abc 文件存在

  3. 判定"成功"

这套逻辑的问题:ohpm install 失败意味着依赖图本身有问题(比如嵌套依赖解析不了),manual symlink 只是让"文件存在",并没有真正解决依赖关系。更严重的是,symlink 可能指向 monorepo 源码(../agent_core),这等于让消费者偷偷用了源码而不是 HAR 产物——完全违背了"验证 HAR 能被消费"的初衷。只查 modules.abc 存在更是形同虚设,文件在 HAR 里本来就有,复制过去就在了,跟"能否编译"毫无关系。

修复:第七轮重写脚本,三条硬约束:

  1. ohpm install 失败即失败,没有任何 manual symlink fallback 成功路径。

  2. 必须跑真实的 hvigor assembleHar 编译消费代码(import { AgentRuntime } / EvalRunner),BUILD SUCCESSFUL 才算通过。

  3. 检查 oh_modules 解析结果不能指向 monorepo 源码/agent_core/agent_eval 路径直接 fail)。

# ohpm install 失败即失败——无 fallback
set +e
(
  cd "$TMP"
  "$OHPM_BIN" install
  cd "$TMP/consumer"
  "$OHPM_BIN" install
) > "$TMP/ohpm-out.txt" 2>&1
OHPM_RC=$?
set -e
if [[ $OHPM_RC -ne 0 ]]; then
  log "FAIL: ohpm install failed (rc=$OHPM_RC) — no symlink fallback"
  exit 1
fi

# 真实编译——必须 BUILD SUCCESSFUL
set +e
(
  cd "$TMP"
  "$HVIGORW" assembleHar --no-daemon
) > "$TMP/hvigor-out.txt" 2>&1
HV_RC=$?
set -e
if [[ $HV_RC -ne 0 ]]; then
  log "FAIL: independent consumer hvigor assembleHar failed (rc=$HV_RC)"
  exit 1
fi
if ! grep -q "BUILD SUCCESSFUL" "$TMP/hvigor-out.txt"; then
  log "FAIL: hvigor finished without BUILD SUCCESSFUL"
  exit 1
fi

# 解析结果不能指向 monorepo 源码
for pkg in core eval; do
  target="$TMP/consumer/oh_modules/@arkagent/$pkg"
  if [[ -L "$target" ]]; then
    resolved="$(readlink "$target")"
    case "$resolved" in
      *"/agent_core"|*"/agent_core/"*|*"/agent_eval"|*"/agent_eval/"*)
        log "FAIL: resolved to monorepo source"
        exit 1
        ;;
    esac
  fi
done

7.3 脚本全流程拆解

verify-har-consumer.sh 完整流程分 8 步:

┌─────────────────────────────────────────────────────────────┐
│ Step 1: mktemp 建独立临时工程(/tmp/arkagent-har-consumer-XXXXXX)│
│         trap EXIT 时 rm -rf,绝不在 repo 落 vendor HAR         │
├─────────────────────────────────────────────────────────────┤
│ Step 2: monorepo assembleHar 产出两个 HAR                     │
│         find agent_core/build & agent_eval/build 找到 .har    │
├─────────────────────────────────────────────────────────────┤
│ Step 3: tar -xzf 解包 HAR → vendor_pkgs/{core,eval}           │
│         检查 modules.abc + Index.d.ets + name@1.0.0 元数据    │
├─────────────────────────────────────────────────────────────┤
│ Step 4: 改写 eval 嵌套依赖                                   │
│         eval/oh-package.json5 里 "@arkagent/core":"1.0.0"     │
│         → "@arkagent/core":"file:../core"  (可复现改写)     │
├─────────────────────────────────────────────────────────────┤
│ Step 5: scaffold 临时 Harmony 工程                            │
│         build-profile.json5 + AppScope + consumer 模块        │
│         consumer 依赖 file:../vendor_pkgs/{core,eval}         │
├─────────────────────────────────────────────────────────────┤
│ Step 6: ohpm install(root + consumer)                       │
│         失败即 fail,无 symlink fallback                      │
├─────────────────────────────────────────────────────────────┤
│ Step 7: hvigor assembleHar(独立消费者真实编译)              │
│         编译 import { AgentRuntime, EvalRunner } 代码          │
│         BUILD SUCCESSFUL 才算通过                             │
├─────────────────────────────────────────────────────────────┤
│ Step 8: 证据日志(剥离 $ROOT/$HOME/$TMP 绝对路径)            │
│         temp 目录 trap 删除,repo 不留 vendor HAR             │
└─────────────────────────────────────────────────────────────┘

7.4 嵌套依赖改写:解决"未发布 SemVer"的关键技巧

这是整个脚本里最精妙的一步。eval 的 HAR 内部声明了对 core 的 SemVer 依赖:

// eval HAR 内的 oh-package.json5
"dependencies": {
  "@arkagent/core": "1.0.0"   // ← SemVer,但 core 没发布到 OHPM
}

ohpm 看到这个声明会去 OHPM registry 找 @arkagent/core@1.0.0,找不到就失败。脚本用一段 Python 把这个声明改写成相对路径:

# 改写 eval 的 oh-package.json5
p = Path(sys.argv[1])  # vendor_pkgs/eval/oh-package.json5
t = p.read_text()
t2, n = re.subn(
    r'"@arkagent/core"\s*:\s*"[^"]*"',
    '"@arkagent/core": "file:../core"',
    t,
)
if n < 1:
    raise SystemExit("FAIL: could not rewrite @arkagent/core dependency")
p.write_text(t2)

改写后:

// eval HAR 内的 oh-package.json5(改写后)
"dependencies": {
  "@arkagent/core": "file:../core"   // ← 指向同级的 vendor_pkgs/core
}

为什么这么改?因为 vendor_pkgs 目录结构是:

vendor_pkgs/
├── core/      ← 解包自 agent_core.har 的 package/
└── eval/      ← 解包自 agent_eval.har 的 package/

eval 用 file:../core 就能解析到同级的 core 目录,无需 OHPM registry。这个改写是可复现的——脚本每次运行都做同样的替换,不依赖任何外部状态。这也是为什么脚本要求 n >= 1(至少替换一处),如果替换数为 0 说明 eval 的依赖声明格式变了,脚本会 fail 提醒维护者更新。

这个改写技巧也是外部消费者的推荐做法。docs/api/versioning.md 明确写了外部工程的标准步骤:

# 外部工程消费 HAR 的推荐步骤
mkdir -p vendor_pkgs
# tar -xzf *.har 后将 package/ 拷入 vendor_pkgs/{core,eval}
# 改写 eval/oh-package.json5 中 @arkagent/core → file:../core
# consumer oh-package.json5:
#   "@arkagent/core": "file:../vendor_pkgs/core",
#   "@arkagent/eval": "file:../vendor_pkgs/eval"
ohpm install
hvigorw assembleHar --no-daemon   # 或 assembleHap

7.5 消费者的编译表面(compile surface)

临时工程的 consumer/src/main/ets/Index.ets 不是空文件,它真实 import 了 core 和 eval 的公共 API:

/**
 * Minimal consumer compile surface: public APIs from packaged HARs only.
 * Must not resolve monorepo source paths (../agent_core).
 */
import { AgentRuntime, ModelConfig, JsonObject } from '@arkagent/core'
import {
  EvalRunner, EvalRunConfig, EchoHarnessFactory, EvalEnvironment, FakeEvalClock
} from '@arkagent/eval'

export function consumerSmoke(): string {
  const env = new EvalEnvironment(new FakeEvalClock())
  const runner = new EvalRunner(env, new EchoHarnessFactory())
  const cfg = new EvalRunConfig('consumer-smoke', 1, 1000)
  void runner
  void cfg
  void AgentRuntime
  void ModelConfig
  void JsonObject
  return 'har-consumer-ok'
}

这段代码的设计意图:

  1. 覆盖 core 和 eval 两个包:任一包的导出有问题都会编译失败。

  2. import 真实类名AgentRuntimeEvalRunner 等是 SDK 的核心公共 API,能编译通过说明 Index.d.ets 类型声明正确。

  3. 实例化对象:不只 import,还要 new 出来,确保字节码链接正常(不只是声明对,运行时符号也对)。

  4. 注释明确"不得解析 monorepo 源码路径":这是给脚本里的路径守卫检查做语义注解。

7.6 证据可移植性:剥离绝对路径

脚本最后一步是证据日志处理。CI 环境和开发者本机的路径不同(/Users/wangzhe/... vs /home/runner/...),如果证据日志里带绝对路径,CI 上无法复现对比。脚本用 log() 函数把三个敏感路径替换成占位符:

log() {
  local line="$1"
  line="${line//$ROOT/\$ROOT}"     # 仓库根 → $ROOT
  line="${line//$HOME/\$HOME}"     # 用户家目录 → $HOME
  line="${line//$TMP/\$TMP}"       # 临时目录 → $TMP
  echo "$line" | tee -a "$EVIDENCE_LOG"
}

这样证据日志在任何机器上都是一致的格式,便于 CI 比对和 issue 复现。这也是阶段 9 第六轮踩坑"证据含本机绝对路径"的修复。


八、版本管理:SemVer 与 1.0 封板

HAR 是产物,版本是产物的"身份"。ArkAgent 用 SemVer 管理版本,1.0.0 是首个正式封板的版本。

8.1 SemVer 策略

版本号变化

触发条件

示例

MAJOR(x.0.0)

@arkagent/core@arkagent/eval 的公共 API 破坏性变更

删除公共类、改方法签名

MINOR(1.x.0)

向后兼容的新功能

新增工具、新增 Provider Profile

PATCH(1.0.x)

bug 修复、文档、非破坏性内部重构

修复流式解码 bug

1.0.0 冻结了 docs/api/ 描述的公共 API 表面和包 Index 导出。这意味着 1.0.x 的 patch 更新不会动公共 API,消费者可以放心升级。

8.2 版本声明的三个位置

版本号不是只在 HAR 里出现,它需要三个位置保持一致:

位置

声明

作用

agent_core/oh-package.json5

"version": "1.0.0"

源码模块版本,构建时注入 HAR

HAR 内 package/oh-package.json5

"version": "1.0.0"

HAR 产物的版本(构建期注入)

agent_eval/oh-package.json5

"@arkagent/core": "1.0.0"

eval 对 core 的依赖版本

构建期,hvigor 会把源码模块的 version 注入到 HAR 的元数据里。verify-har-consumer.sh 专门检查 HAR 内的版本:

name_expect="@arkagent/$side"
if ! grep -q "$name_expect" "$TMP/vendor_pkgs/$side/oh-package.json5" || \
   ! grep -q '1.0.0' "$TMP/vendor_pkgs/$side/oh-package.json5"; then
  log "FAIL: $name_expect@1.0.0 not found in package metadata"
  exit 1
fi

8.3 1.0 发布的完整门禁

ArkAgent 阶段 9(1.0 发布)的验收门禁表(2026-07-13 Codex 第七轮终验):

门禁项

结果

clean + assembleHar

通过

root test

通过(BUILD SUCCESSFUL)

assembleHap

通过

check-eval-deps.sh

OK(core 不依赖 eval)

verify-har-consumer.sh

真实 compile OK(mktemp + ohpm + hvigor assembleHar BUILD SUCCESSFUL)

scan-hap-secrets.sh

OK clean(HAP 无 API Key 泄漏)

git diff --check

干净

用户功能在线/真机

2026-07-13 已完成

发布安全抽检(Codex)

通过

注意门禁里有两个构建验证assembleHar(本仓库能产出 HAR)和 verify-har-consumer.sh(外部能消费 HAR)。两者都通过才允许封板。这正是"本仓库编译通过 ≠ 外部能用"这条红线的工程化落地。


九、最佳实践清单

9.1 模块依赖

  • ✅ core 是底层,不依赖任何 ArkAgent 模块。

  • ✅ eval 依赖 core,用 SemVer 声明("@arkagent/core": "1.0.0")。

  • ✅ entry 可依赖 core 和 eval,monorepo 内用 file:../module

  • ✅ 用 check-eval-deps.sh 源码扫描兜底"未声明但 import"的反向依赖。

  • ❌ core 不得 import 任何 @arkagent/eval 符号。

  • ❌ 不要在 HAR 模块的 dependencies 里写 file: 相对路径(发布后外部解析不了)。

9.2 构建命令

  • ✅ 命令行构建显式设置 DEVECO_SDK_HOME 指向 Contents/sdk

  • ✅ 所有构建命令统一加 --no-daemon

  • ✅ 用 IDE 内置的绝对路径 hvigorw,保证工具链版本一致。

  • ✅ HAR 模块的 hvigorfile.tsharTasks,HAP 模块用对应 task。

  • ❌ 不要依赖 shell 的 export DEVECO_SDK_HOME(daemon 继承问题)。

  • ❌ 不要混用不同来源的 hvigorw(IDE 内置 vs npm 全局安装)。

9.3 HAR 消费

  • ✅ 外部消费用 file:./libs/xxx.har 指向 HAR 文件。

  • ✅ 同时消费 core + eval 时,改写 eval 的嵌套依赖为 file:../core

  • ✅ 消费方工程 build-profile.json5 开启 useNormalizedOHMUrl: true

  • ✅ 消费方 SDK 版本不低于 HAR 的 compatibleSdkVersion

  • ❌ 外部工程不要用 file:../agent_core(那是 monorepo 内部路径)。

  • ❌ 不要假设 HAR 内有源码(byteCodeHar: true 意味着只有字节码)。

9.4 发布门禁

  • ✅ 发布前跑 verify-har-consumer.sh,独立工程真实编译。

  • ✅ ohpm install 失败即 fail,禁止 manual symlink fallback。

  • ✅ 检查 oh_modules 解析不指向 monorepo 源码路径。

  • ✅ 证据日志剥离 $ROOT/$HOME/$TMP 绝对路径。

  • ✅ temp 工程 trap 删除,repo 不留 vendor HAR。

  • ❌ 不要只检查 modules.abc 文件存在就判定成功。

  • ❌ 不要把临时工程的 vendor HAR 提交进 repo。

9.5 版本管理

  • ✅ SemVer:MAJOR 破坏性 / MINOR 新功能 / PATCH 修复。

  • ✅ 源码 oh-package.json5 的 version 与 HAR 内 version 一致。

  • ✅ 1.0.0 封板后 patch 不动公共 API。

  • ✅ 依赖声明用精确 SemVer(1.0.0),不用 ^/~ 范围。

  • ❌ 不要在 HAR 的 dependencies 里留未发布版本号(外部解析不了)。


十、常见错误对照表

错误做法

问题

正确做法

外部工程用 file:../agent_core

外部没有源码目录,解析失败

file:./libs/agent_core.har

构建命令省略 DEVECO_SDK_HOME

daemon 继承无效 SDK 路径,00303217 Configuration Error

命令行显式设置 + --no-daemon

HAR 模块 hvigorfile.tsappTasks

assembleHar 不产出 HAR

harTasks

ohpm install 失败后手动 symlink

掩盖依赖图问题,假成功

失败即 fail,修复依赖声明

只检查 modules.abc 存在判定消费成功

文件复制过去就在了,跟编译无关

跑真实 hvigor assembleHar

eval HAR 内留 "@arkagent/core":"1.0.0" 不改写

外部 ohpm 找不到 registry 包

改写为 file:../core

core 源码偷偷 import @arkagent/eval

反向依赖,发布后污染边界

check-eval-deps.sh 扫描拦截

消费方关闭 useNormalizedOHMUrl

模块路径大小写/格式差异导致解析失败

开启 useNormalizedOHMUrl: true

证据日志含 /Users/wangzhe/...

CI 无法复现对比

剥离为 $ROOT/$HOME/$TMP

monorepo 内 entry 用 HAR 而非源码

改 core 要重新打 HAR,开发慢

开发期用 file:../agent_core

把临时 vendor HAR 提交进 repo

repo 膨胀,版本混乱

temp 工程 trap 删除

HAR dependenciesfile: 相对路径

外部消费者路径不存在

用 SemVer,外部消费时改写

build-profile.json5 不注册 HAR 模块

assembleHar 不产出该模块 HAR

modules 里注册


十一、验证清单(发布前 Review)

模块依赖

  • agent_core/oh-package.json5dependencies 为空或不含 ArkAgent 模块

  • agent_eval/oh-package.json5 声明 "@arkagent/core": "1.0.0"

  • check-eval-deps.sh 输出 OK: agent_core does not depend on agent_eval

  • 依赖方向是单向的:entry → core/eval,eval → core

HAR 构建

  • assembleHar --no-daemon 产出 agent_core.haragent_eval.har

  • HAR 路径 build/default/outputs/default/*.har

  • HAR 解包后有 ets/modules.abc + src/main/ets/Index.d.ets

  • HAR 内 oh-package.json5version 是预期版本

  • HAR 内 metadata.byteCodeHartrue

独立消费者验证

  • verify-har-consumer.shmktemp 临时工程跑通

  • ohpm install 成功(无 symlink fallback)

  • hvigor assembleHar BUILD SUCCESSFUL

  • oh_modules 解析不指向 /agent_core/agent_eval

  • 证据日志无绝对路径($ROOT/$HOME/$TMP 占位符)

  • temp 目录已清理,repo 无 vendor HAR 残留

HAP 构建

  • assembleHap --no-daemon 产出签名 HAP

  • scan-hap-secrets.sh 无 API Key 命中

  • HAP 内无 provider_keys.local.json 等敏感文件

版本一致性

  • 源码 oh-package.json5 version == HAR 内 version

  • eval 对 core 的依赖版本与 core 实际版本一致

  • changelog 与版本号对应


十二、构建验证

12.1 HAR 构建

DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
  /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw assembleHar --no-daemon

构建结果:

> hvigor version: ...
> hvigor assembleHar --no-daemon
...
CompileArkTS passed
PackageHar passed
BUILD SUCCESSFUL

产物:

agent_core/build/default/outputs/default/agent_core.har   (~404 KB)
agent_eval/build/default/outputs/default/agent_eval.har   (~154 KB)

12.2 HAP 构建

DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
  /Applications/DevEco-Studio.app/Contents/tools/hvigor/bin/hvigorw assembleHap --no-daemon

构建结果:

CompileArkTS passed
PackageHap passed
BUILD SUCCESSFUL

12.3 独立消费者验证

DEVECO_SDK_HOME=/Applications/DevEco-Studio.app/Contents/sdk \
  bash scripts/verify-har-consumer.sh

关键输出(证据日志,路径已脱敏):

verify-har-consumer 2026-07-13T...Z
mode=real-hvigor-compile temp=$TMP
==> assembleHar (monorepo producers)
OK: found agent_core.har and agent_eval.har
OK: CORE_SIZE=... EVAL_SIZE=...
OK: @arkagent/core package surface (modules.abc + Index.d.ets + 1.0.0)
OK: @arkagent/eval package surface (modules.abc + Index.d.ets + 1.0.0)
OK: eval declares @arkagent/core (file:../core after rewrite)
OK: consumer depends only on file:../vendor_pkgs/* from extracted HARs
==> ohpm install (consumer module)
ohpm: ...
==> hvigor assembleHar (independent consumer)
hvigor: ...
hvigor: BUILD SUCCESSFUL
OK: consumer assembleHar produced output HAR
OK: verify-har-consumer completed (real compile)

12.4 依赖方向守卫

bash scripts/check-eval-deps.sh

输出:

OK: agent_core does not depend on agent_eval

12.5 修复迭代记录

阶段 9 的 HAR 消费者验证经历了多轮修复,每轮的失败原因和修复:

轮次

失败现象

根因

修复

早期

verify-har 报通过但外部不能用

ohpm 失败后 symlink + 只查 abc 文件存在

重写脚本:ohpm 失败即 fail + 真实 hvigor 编译

早期

ohpm 解析 @arkagent/core@1.0.0 失败

core 未发布到 OHPM registry

解包后改写 eval 依赖为 file:../core

早期

证据日志含 /Users/wangzhe/...

log 函数直接写绝对路径

log() 剥离 $ROOT/$HOME/$TMP

早期

temp 目录的 vendor HAR 残留进 repo

无清理机制

trap cleanup EXIT + fixtures 只留模板

第七轮

全部门禁通过

——

1.0 封板


十三、写在最后

HAR 打包与分发表面上是"跑一条 assembleHar 命令",背后却是一整套工程纪律:模块依赖要有向无环、构建环境要显式隔离、HAR 产物要内部自洽、消费验证要独立真实、版本管理要可追溯。任何一环松懈,都会在发布那天变成"本仓库能跑但外部用不了"的黑洞。

ArkAgent 1.0 的发布门禁之所以把 verify-har-consumer.sh 列为硬性门禁,是因为我们吃过"假成功"的亏——ohpm 失败被 symlink 掩盖、文件存在被当成编译通过。这种假成功比明确的失败危险十倍,因为它会让团队误以为可以发布。真正的发布信心来自"在完全没有 monorepo 上下文的临时工程里,真实编译通过"。

记住这几条口诀:

HAR 是字节码包,abc 加上 d.ets,缺一不可消费。 HAP 是安装包,签名才能装机,HAR 永不独立跑。 依赖方向单向流,core 不靠 eval,eval 只能向上求。 构建必带 SDK 路径,no-daemon 斩旧环境,daemon 不背历史锅。 本仓编译不算数,独立工程真编译,ohpm 失败即失败。 嵌套依赖巧改写,SemVer 变 file:,未发布也能消费。 证据剥离绝对路径,temp 清理不留痕,CI 处处可复现。 SemVer 三段义,major 破 minor 新 patch 修,1.0 封板不动 API。

本文是 ArkAgent 鸿蒙教程系列的第三篇。前两篇讲了架构全景和快速接入,这篇聚焦"把 SDK 打包出去给别人用"这个发布维度。当你能稳定地用一条命令产出 HAR、用独立消费者验证它真能用、用 SemVer 管理它的演进——你的鸿蒙 SDK 就具备了真正可分发的工程基础。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐