Appearance
受管桌面程序
真实 PC 的本地临时 Companion 还要求 Host 支持私有 local-dev lease。该 lease 由 Host 记录进程 provenance 并负责失联后的自清理;旧客户端缺少该能力时安全拒绝临时 Companion,Browser Fake 和普通 network 调试仍可用。
companion 用于准备和启动随当前小程序审核版本发布的桌面辅助应用。V1 仅支持新 PC Host 的 windows-x64 与 macos-arm64;老 PC、Mobile、Web 和 Linux 不在支持范围内。
声明与构建
项目必须在 package.json 显式声明高风险权限:
json
{
"heybox": {
"permissions": {
"companion": { "enabled": true }
},
"companions": {
"runner": {
"displayName": "桌面辅助程序",
"capabilities": ["stdio"],
"targets": {
"windows-x64": {
"source": "companion/runner-windows-x64",
"entrypoint": { "kind": "executable", "path": "runner.exe" },
"elevation": "none",
"args": []
}
}
}
}
}
}归档统一通过 package.json#heybox.companions 配置,source 相对该 package.json 所在目录解析。 目标平台写在 package.json#heybox.platforms;Vite 中只注册无参的 miniappManifest(),不要在 Vite 配置中复制 Companion 声明或平台声明。 构建和本地调试读取同一份声明。构建会检查 ZIP 结构和固定入口,复制到 dist/companions/<alias>/<target>.zip,并生成大小与 SHA-256;Web 文件和 Companion 文件进入同一上传 session 与同一审核版本。页面不能读取部署后的 manifest.json,也不能取得真实 CDN URL 或本机安装路径。
dist/companions/ 下全部归档的总大小上限为 500 MiB(500 × 1024 × 1024 bytes);该上限针对归档总量, 不是单个文件。超限时 hb-sdk build 以“Companion 归档总大小超过 500MiB 上限,请缩减归档后重新 build” 失败,hb-sdk remote deploy 以“Companion 归档总大小超过 500MiB 上限,请缩减归档后重新 deploy”失败, 需要先缩减桌面辅助应用产物再重新构建或部署。
hb-sdk dev 的真实 PC 联调可以直接使用这里声明的本地 ZIP,不要求先上传版本或获得线上平台批准。 项目必须已登录、绑定且声明权限及目标平台;每次打开会话需确认临时权限,准备与启动的可信手势仍分别保留。 dev server 只向 loopback 提供固定 Manifest/ZIP 路由,CLI 用短期票据绑定当前 dev 进程的 canonical snapshot。ZIP 或配置变化会校验新快照并重建 Runtime,临时进程不会热换二进制; 新产物必须重新准备并启动。校验失败时调试台显示阻塞原因,不会悄悄使用旧文件或线上产物。 单次开发服务最多保留 32 份快照、累计 ZIP 大小 1 GiB;达到上限后停止接纳新快照, 请先结束调试 Session,再重启开发服务,已有快照不会被自动逐出。 未配置本地 Companion 时使用 Browser Fake 做状态联调,真实 PC 不会回退到线上 candidate。
构建与 PC 安装都会检查真实二进制格式:Windows 为 AMD64 PE,macOS 为包含 arm64 的 Mach-O; 脚本即使改名或有执行权限也会被拒绝。授权弹窗展示产物来源、签名状态、目标平台、人类可读归档 大小、精确字节数与完整 SHA-256;本地 ZIP 明确标记“本地开发产物,未经过版本审核”。SHA-256 只用于 Host 可信授权展示,不进入小程序可读取的 CompanionInfo。 .app 从 XML 或 binary Info.plist 的 CFBundleExecutable 精确选择主入口。
Manifest 只接受固定入口和固定 args。V1 仅支持普通权限启动,elevation 只能省略或设为 none; required 会在构建时明确失败,管理员权限启动留待后续版本。公开 API 不接受 executable path、动态 args、 cwd、environment、shell 或任意命令;不要把 files.sandbox 中的普通文件当作可执行对象。 displayName 最多 120 个 Unicode code point;args 最多 64 项,单项最多 1024 UTF-8 bytes、总计最多 16384 UTF-8 bytes,并拒绝 NUL、CR 和 LF。
准备与启动
ts
import { companion } from '@heybox/hb-sdk'
// 分别绑定到「准备」与「启动」按钮,不能在一个点击中串联。
function prepareFromUserAction() {
return companion.prepare('runner', {
onProgress(progress) {
renderProgress(progress)
},
})
}
function launchFromUserAction() {
return companion.launch('runner')
}prepare() 和 launch() 必须分别由新的可信用户手势触发。每次真正开始新的准备操作前,Runtime 展示一次 Host 可信授权;已 ready 或加入同一在途准备时不会重复授权。每次真正创建新进程前,launch() 再展示独立 授权,不能复用 prepare 的手势或授权。launch() 不会隐式调用 prepare(),未准备时稳定失败。
准备操作属于 Host:页面 reload、最小化或进入后台不会取消。新页面可用 getPreparationStatus() 查询状态,并通过相同 alias 加入在途操作;显式取消使用 cancelPreparation(alias) 或 AbortSignal。页面 reload 只断开观察者,不会向 Host 发送取消;原子提交已经开始时,最终提交结果优先于取消意图。
prepare() 与 getInfo() 返回的 CompanionInfo.source 标明 Host 确认的产物来源:local-dev 是本地开发产物,preview 是审核前预览产物,released 是已发布版本产物;旧 Host 缺省时 source 为 undefined,表示来源未知。不要用 alias、displayName 或文件路径自行推断来源,也不要把 preview 产物当作已发布版本;当前 PC Host 不支持审核前预览时以 COMPANION_PREVIEW_UNSUPPORTED 失败。
准备状态是可恢复的 Host 状态,不等于当前页面 Promise 的状态:
getPreparationStatus() 状态 | 含义 | 推荐动作 |
|---|---|---|
not-prepared | 当前版本产物尚未准备 | 等待新的可信用户操作调用 prepare() |
preparing | Host 正在下载、校验或解压 | 展示 progress;需要继续观察时再次调用 prepare() 加入同一操作 |
ready | 产物已原子提交,可独立触发 launch() | 展示可启动状态;preparedAt 是 Host 提供的时间戳 |
failed | 最近一次准备失败 | 根据 error.retryable 和 error.code 决定重试或停止 |
AbortSignal 取消的是当前调用并向 Host 发出取消意图,不是页面本地的同步回滚。若原子提交已开始,当前 Promise 可能因取消而失败,但稍后查询仍可能得到 ready;取消后需要用 getPreparationStatus() 对账,不要直接把 UI 固定为 not-prepared。
Session 与 stdio
同一小程序 identity 与 alias 同时最多有一个活动 Session。getActiveSession(alias) 会取得当前活动 Session 的控制权;新 Runtime generation 接管后,旧 controller 的写入、关闭 stdin、终止、附件和通知操作都会失败。 进程退出后 getActiveSession() 返回 undefined,业务结果应由小程序自行持久化。
CompanionSession.state 只有 running 和 exited。onExit() 在 Session 已退出后注册时会立即收到保存的退出结果, waitForExit() 对同一 Session 始终等待同一次退出;组件销毁时仍应调用所有 on*() 返回的取消订阅函数。 失去 controller 不等于进程退出,也不会让 waitForExit() 提前完成。收到 onOwnershipLost() 后应停止写入;确有继续 控制的需要时重新调用 getActiveSession(alias),不要对旧 controller 操作做无界重试。
stdio 仅在 Manifest 声明 stdio 时存在,输入输出都是原始 Uint8Array。SDK 不做 UTF-8、换行、NDJSON、 RPC 或业务状态解析。输出采用 at-least-once 投递,收到 chunk 后 SDK 会确认 sequence;断连与重取控制权可能 导致重复,因此业务 framing 必须携带可去重标识,并能处理拆包、粘包和重放。
ts
const session = await companion.getActiveSession('runner')
if (session?.stdio) {
const stopOutput = session.stdio.onStdout(bytes => decodeBusinessFrame(bytes))
const stopOwnership = session.onOwnershipLost(() => setControlsEnabled(false))
await session.stdio.write(encodeBusinessFrame({ type: 'status' }))
// 组件销毁时调用 stopOutput() 和 stopOwnership();业务退出协议可先写入,再 closeStdin()。
}平台不提供语义跨平台不稳定的 request-close。业务可先通过自身 stdio 协议请求正常退出,或关闭 stdin; terminate() 是强制结束完整受管进程树。页面隐藏或 reload 不结束 Session,用户真正关闭小程序窗口时由 Host 处理完整进程树生命周期。
可选能力
attachments:session.attachments.open(id)返回只读FileHandle;opaque id 来自桌面程序自身业务协议,页面无法指定物理路径。notifications:页面解析业务协议后,可请求 Host 展示固定类别通知;页面完全断开时不承诺业务通知。stdio:仅提供原始字节双向流,不提供文本编码或 RPC。
只有 Manifest 声明且 Host 实现的能力才出现在 Session。不要根据操作系统或属性存在性猜测支持情况。
调试与验收
调试台不展示浏览器、手机与 PC Companion 的汇总就绪状态、本地 alias、平台或产物摘要;真实入口不可用时只在对应入口内说明原因。 真实本地调试持续验证 CLI 会话在线;连续失联约 30 秒、票据到期或快照变化会销毁旧 Runtime,关闭临时 stdio 并终止临时进程树。恢复后重新确认、准备与启动。调试台持续提示本地可用不代表线上批准。companion 等需要平台批准的权限当前均未开放,开发者申请入口将在近期开放;入口开放并取得批准前,线上与预览不可用。
Browser Dev Host 只提供确定性的 Fake 场景,用于开发准备进度、Session 状态、stdio framing、错误 UI 与恢复逻辑。 Fake 不选择、不下载、不解压、不启动本机程序,也不经过操作系统安全检查或真实进程组。 因此 Browser Fake、Mobile 或 Web 的结果不能作为桌面发布证据;发布前必须分别在目标 Windows/macOS 新 PC Host 完成归档摘要、准备取消、启动、stdio、退出与进程树清理验收。
失败处理
USER_GESTURE_REQUIRED:当前 prepare 或 launch 缺少新的可信用户手势。USER_CANCELLED:用户拒绝或关闭本次 Host 授权,应正常结束流程。COMPANION_NOT_PREPARED:先由新的用户操作显式调用prepare()。COMPANION_INTEGRITY_FAILED/COMPANION_ARCHIVE_INVALID:停止重试并提交新审核版本,不能绕过摘要或系统安全检查。COMPANION_SESSION_OWNERSHIP_LOST:当前 Runtime 已失去 controller;需要时重新调用getActiveSession()。COMPANION_DESCRIPTOR_UNAVAILABLE:授权或描述符暂时不可用,可在有界重试策略下重试。COMPANION_ELEVATION_DENIED:为未来管理员权限启动保留的兼容错误码;V1 构建不接受elevation: 'required'。METHOD_FORBIDDEN/COMPANION_UNSUPPORTED:当前 Host 或平台未实现,不要循环重试或用任意执行替代。COMPANION_PREVIEW_UNSUPPORTED:当前 PC Host 不支持审核前预览 Companion,需要升级客户端后重试。
完整类型与方法见 API Reference。
完成检查
- 权限、目标 ZIP、固定入口和固定参数都随同一版本提交审核。
- prepare 与 launch 使用两次独立可信用户操作和 Host 授权。
- stdio 业务协议处理了原始字节 framing、at-least-once 去重和断连恢复。
- 未向页面暴露或传入 URL、物理路径、动态 args、环境变量、shell 或 PID。
- Browser Fake 只用于开发,目标平台均已完成新 PC 真机验收。