让小艺替我回答“还有多久下班“——摸鱼鸭的 HarmonyOS 7 Skill 接入实战

让小艺替我回答"还有多久下班"------摸鱼鸭的 HarmonyOS 7 Skill 接入实战

一个独立开发的鸿蒙桌面小组件应用,把已有功能封装成小艺端侧 Skill 的完整过程,含 4 个真实踩坑记录。

下午三点,工位上,阳光斜着打在键盘上。你不想解锁手机、不想点开 App,只想问一句:

"还有多久下班?"

在 HarmonyOS 7 之前,这个问题的答案在小组件里------你得亮屏、瞄一眼桌面。而现在,答案在小艺里------你只需要开口问。

我做的应用叫摸鱼鸭,一个打工人桌面小组件 App:下班倒计时、周末倒计时、发薪倒计时、假日倒计时、时薪计算,外加一碗随机的毒鸡汤。它不催你自律,只负责给你盼头。

这次借着 HarmonyOS 7 开放的小艺 Skill 能力,我把摸鱼鸭的查询功能接进了小艺。这篇文章记录了完整的接入过程------包括我在真机上调了半天才跑通的几个坑,希望能帮你少走弯路。

一、先想清楚:小艺 Skill 是一条什么样的路

动手之前,我先研究了 HarmonyOS 7 给开发者开放的智能化路径。对小艺来说,大致分两类:

  • 云侧能力:逻辑跑在云端,适合调 API、查信息类的服务;
  • 端侧 Skill :Skill 的脚本会被编译成字节码打进你的 HAP,在小艺触发时于你的应用进程内执行。

说人话就是:你把你的skill给云端小艺,app端也写好代码,让他俩沟通

摸鱼鸭的全部数据(上下班时间、发薪日、月薪配置)都存在本地 Preferences 里,不联网、不上传。这个"本地优先"的产品原则,恰好和端侧 Skill 完美匹配------数据不出设备,用户隐私零风险,连网络权限都不用申请。

更妙的是架构上的发现:Skill 脚本可以 import 应用自己的业务模块 。这意味着我不需要为小艺重写一遍倒计时逻辑,只需要给既有的 CardDataService 包一层"出入口"。说人话就是,不用重新逻辑直接代码引用旧版逻辑。

二、Skill 的本质:一份写给 AI 看的说明书

理解 Skill 的关键,是换一个视角:

桌面小组件是写给人眼看的 UI;Skill 是写给大模型看的"能力说明书"。

小艺背后是一个 Agent。它拿到用户的话术后,要去判断两件事:

  1. 该不该用你这个 Skill? ------看 frontmatter 里的 description
  2. 怎么用?------看正文里的能力契约(工具怎么调、参数是什么、返回什么)

所以整个开发过程,一半时间在写代码,另一半时间在用大模型能理解的方式描述自己的应用。这是和传统开发很不一样的心智:你面向的"调用方"不是确定性代码,而是一个会推理、会选择、也会误解的模型。

目录结构

按《HarmonyOS Skill 开发规范》,一个 Skill 就是一个目录,必须包含两个文件:

bash 复制代码
entry/skills/moyuya-assistant/
├── SKILL.md          # 元数据 + 指令(写给 Agent 看)
├── module.json       # 平台配置(写给运行时框架看)
└── scripts/
    └── MoyuSkill.ets # 端侧脚本入口

然后在应用的 module.json5 里注册:

json 复制代码
"skillProfiles": [
  {
    "name": "moyuya-assistant",
    "abilityName": "EntryAbility",
    "srcEntries": [
      "../../skills/moyuya-assistant/scripts/MoyuSkill.ets"
    ],
    "version": "1.0.0"
  }
]

注意 srcEntries 指向的脚本会被框架编译进 HAP------这一点后面踩坑时会再提到。

SKILL.md:description 是激活的钥匙

frontmatter 只有两个必填字段:

yaml 复制代码
---
name: moyuya-assistant
description: 摸鱼鸭打工人助手,提供下班倒计时、发薪倒计时、时薪计算、假日倒计时与毒鸡汤文案能力,响应"还有多久下班"、"还有几天发工资"、"我一小时挣多少钱"等打工人查询类指令
---

别小看这两行。规范里明确写着:description 是 Agent 判断是否激活该 Skill 的唯一语义来源。触发关键词由平台在发布时自动从中提取,所以我把用户可能的各种问法------正式的、口语的、缩略的------都塞了进去。

正文部分,我为 5 个能力分别写了"能力契约"。以毒鸡汤为例:

bash 复制代码
### 场景5:毒鸡汤(getRandomToxicSoup)

从本地文案库随机返回一句毒鸡汤文案。

#### 执行参数

exec-cli(command: ohos-arktsScript --skillName 'moyuya-assistant'
  --scriptPath 'scripts/MoyuSkill.ets'
  --functionName 'getRandomToxicSoup' --args '{}')

#### 执行返回值

{
  "type": "object",
  "required": ["type", "status"],
  "properties": {
    "type": { "type": "string", "const": "result" },
    "status": { "type": "string", "enum": ["success", "failed"] },
    "data": {
      "type": "object",
      "properties": {
        "soupText": { "type": "string", "description": "随机毒鸡汤文案" }
      },
      "required": ["soupText"]
    }
  }
}

每个能力 = 一个 exec-cli 调用模板 + 参数 Schema + 返回值 Schema。Agent 照着契约调用,脚本的返回值也严格按 Schema 回包------双向的类型约定,这一点对 Agent 稳定运行至关重要。

还有一个容易被忽略的细节:写清楚"不调用的情况"。我在正文里明确列出:

  • "设置我的上班时间为九点"------意图是修改配置,本 Skill 仅支持只读查询;
  • "明天天气怎么样"------与打工人查询无关,走系统其他能力;
  • "上班好累啊"------情绪表达,无需调用。

负向边界和正向触发同样重要。没有它,用户随口感慨一句,小艺也可能笨拙地把你的 Skill 拉起来。

三、入口脚本:做一层"薄到透明"的封装

脚本层是最爽的部分------因为业务逻辑早就有了。

摸鱼鸭的正常架构是 CardDataService 统一计算各种卡片数据,小组件和页面都从它取数。Skill 入口脚本要做的,只是接住小艺的调用、复用服务层、把结果回传:

typescript 复制代码
import { scriptManager } from '@kit.AbilityKit';
import { CardDataService } from '../../../src/main/ets/services/CardDataService';
import { PreferencesUtil } from '../../../src/main/ets/utils/PreferencesUtil';

export default class MoyuSkill {
  // 能力1:下班倒计时
  public async queryOffWorkCountdown(
    info: scriptManager.ArkTSScriptInfo, ...argv: string[]
  ): Promise<void> {
    try {
      await PreferencesUtil.init(info.context);   // 接入本地数据
      const data = await CardDataService.getInstance().computeOffWorkData();
      await this.reportSuccess(info, {
        'countdownText': data.countdownText,       // "距下班2小时15分"
        'phase': data.timePhase,                  // working / lunch / day_off...
        'isWorkday': data.isWorkday,
        'quoteText': data.quoteText               // 配套文案
      });
    } catch (e) {
      await this.reportInternalError(info, e);
    }
  }
  // ... 其余 4 个能力同理
}

几个设计决策值得展开:

1. 薄封装,不重复实现。 五个能力全部是"调 CardDataService 已有方法 → 组装结果 → 回包"。业务逻辑改了(比如以后支持调休班),Skill 自动跟随,零维护成本。

2. 失败也是语义。 时薪和假日是会员功能,未开通时返回的不是干巴巴的 error,而是结构化的错误码 + 引导语:

typescript 复制代码
await this.reportFailed(info, 'ERR_PREMIUM_REQUIRED',
  '时薪计算为摸鱼鸭会员功能,请先在摸鱼鸭App中开通会员');

Agent 拿到 errCodesuggestion,能自然地把引导语组织成人话告诉用户------错误处理也是给 AI 写的,这又是一个思维转变。

3. 统一回包出口。 所有结果最终通过 scriptManager.completeArkTSScriptInApp(info.context, info.requestCode, result) 上报,成功失败走同一个出口,避免分支遗漏。

四、真机联调踩坑实录

代码写完只是开始。

坑 1:我的配置如下,还要兼容老系统

json 复制代码
        "targetSdkVersion": "26.0.0",
        "compatibleSdkVersion": "6.1.0(23)",

升级后要同步确认本机 DevEco Studio 装了 HarmonyOS 26 的 SDK,并且真机系统是 HarmonyOS 7。Skill 能力意味着你的应用最低系统版本被抬到了 API 26,老设备用户要考虑好降级策略

坑 2:真机"调不起来"------最后倒在一份 161 字节的文件上

这是最隐蔽的一个。编译通过、HAP 安装成功、平台也导入了 Skill,但对着小艺说"还有多久下班",它自顾自用大模型泛泛回答,完全没调起我的 App

我先怀疑注册问题,把 module.json5skillProfiles 翻来覆去检查------没问题,解包 HAP 确认 skill 目录已打入、脚本已编译成 ABC。又怀疑是平台流程问题,在小艺开放平台重新发布了真机测试,还是不行。

请注意 一定要参考官方文档:developer.huawei.com/consumer/cn...

Skill 是一个目录,必须包含 SKILL.md 和 module.json 两个文件。

我的 skill 目录里只有 SKILL.md,没有 module.json。

这份文件是给端侧运行时框架看的路由配置:小艺匹配到 Skill 后,靠它知道该拉起哪个 Ability、执行哪个脚本:

json 复制代码
{
  "version": "1.0.0",
  "availableOn": ["phone", "tablet"],
  "abilityName": "EntryAbility",
  "srcEntries": ["scripts/MoyuSkill.ets"],
  "minAPIVersion": 26,
  "visibility": "system"
}

补上这份 161 字节的文件、重新打包上传、重新发布真机测试------链路终于通了。

教训:SKILL.md 写给 Agent,module.json 写给框架,缺一个都不行。前者决定"想不想调",后者决定"能不能调"。

坑 3:装上 HAP ≠ 小艺认识你

还有一个认知必须纠正:仅把 Skill 打进 HAP 装到手机上,小艺是不知道它存在的。 必须走小艺开放平台的注册发布流程:

  1. 华为开发者联盟 → 生态服务 → 小艺开放平台;
  2. 【Skill】→【新建Skill】→【导入Skill】,上传打包好的 Skill 目录 zip;
  3. 新建真机测试用户组,把自己的华为账号加进去;
  4. 勾选用户组,点【发布真机测试】(有效期 15 天);
  5. 测试设备登录该账号,重启小艺------发布后新对话才会出现带"skill真机测试中"标签的能力。记得要点选一下。

期间我还咨询过华为技术支持,对方一度回复说"小艺 Skill 无法读取 App 本地私有数据,需要走 Intents Kit 意图接口"。后来确认那是云侧 Skill 的约束------端侧 Skill 的脚本就跑在应用进程内,info.context 就是应用上下文,读本地 Preferences 完全没有问题。遇到问题先分清自己是端侧还是云侧路线,能省不少沟通成本。

五、跑通之后:一些更深的产品思考

链路跑通后对着小艺说"还有多久下班",它回我:"距下班还有 2 小时 15 分------下午的效率取决于今天的鱼好不好摸。"

这一刻我意识到这次接入真正的价值所在:

交互入口被"折叠"了。 以前是:亮屏 → 找小组件 → 用眼睛读数。现在是:开口问 → 得到包含数据、语境和情绪的一句话。小组件时代我设计了卡片圆角和配色,而 Skill 时代我设计的是回答的语气 ------返回值里的 quoteText 字段,就是让 Agent 替鸭子说话。

本地数据的价值被放大了。 摸鱼鸭"不联网"的原则原本是隐私卖点,现在成了端侧 Skill 的天然优势------数据不出设备,AI 进设备里来算。

只读是克制的开始。 第一版我只开放了查询能力,"设置上班时间"这类写入操作明确划在边界外。让 AI 改用户配置的风险收益比,值得每个接入者认真掂量------先把"答得准"做好,再考虑"做得对"。

下一步我计划把"开始摸鱼计时"也做成 Skill,让小艺成为摸鱼的发起者而不只是播报员。当 Agent 能连接应用、数据和设备,"还有多久下班"这类问题的答案,或许会越来越懂每一个具体的打工人。

写在最后

从桌面小组件到小艺 Skill,摸鱼鸭的这次升级让我真切感受到 HarmonyOS 7 的开发范式变化:应用不再是功能的容器,而是 Agent 可调度的能力节点。

给后来者的极简清单:

  • ✅ 一个 Skill 一个目录:SKILL.md + module.json + scripts/
  • description 写全各种问法,正文写清"不调用的情况"
  • module.json5 注册 skillProfiles,SDK 升到 26
  • ✅ 脚本薄封装,复用既有业务层
  • ✅ 错误码带 suggestion,让 AI 能好好转述
  • ✅ 开放平台导入 + 真机测试用户组 + 重启小艺
  • ⚠️ 千万别漏了 skill 目录里的 module.json

摸鱼鸭已在华为应用市场上架:下班倒计时、周末倒计时、发薪倒计时、假日倒计时、时薪计算,圆角卡片、无广告、不社交、不联网。鸿蒙桌面上工位里,让一只小鸭子陪你等下班。

去应用市场搜索"摸鱼鸭",对小艺说一句"来句毒鸡汤"试试。

相关推荐
用户0934077735142 小时前
HarmonyOS WPS Open SDK 实践:把水印与修订收成打开策略层
android·typescript·harmonyos
Veer Han4 小时前
DevEco CLI 实战:鸿蒙 App「宝贝日程表」从 0 开发到正式上架
华为·harmonyos
李游Leo4 小时前
【共创稿事节】 HarmonyOS 7 空间化 UI 实战:从平面布局到空间层级的界面重构
ui·平面·harmonyos
凡泰AI5 小时前
如何通过小程序多端框架,让一个小程序同时运行在iOS、安卓、鸿蒙和微信客户端,实现开发层面的降本增效
android·ios·微信小程序·小程序·harmonyos
less_121387 小时前
HarmonyOS WPS Open SDK:enableEdit 只读与可编辑打开模式
华为·sdk·harmonyos·wps·鸿蒙开发·文档编辑
贾伟康1 天前
【天体运行模拟|12】HarmonyOS ArkTS 本地数据文件实战:让离线资源读取失败可见可恢复
数据恢复·harmonyos·arkts·preferences·arkdata
~远在太平洋~1 天前
05-鸿蒙 faultlog 崩溃日志分析
华为·harmonyos
Magic-ZYJ1 天前
HarmonyOS 文件选择与读写:DocumentViewPicker、URI、沙箱目录一次搞清
深度学习·华为·harmonyos
2501_919749031 天前
华为鸿蒙免费提醒APP—小羊提醒
华为·harmonyos·鸿蒙