diff --git a/docs/android-background-execution.md b/docs/android-background-execution.md new file mode 100644 index 0000000..fdf1e02 --- /dev/null +++ b/docs/android-background-execution.md @@ -0,0 +1,76 @@ +# Android 后台执行:主线架构与真机验收 + +核对日期:2026 年 10 月 25 日。源码基线:`a48d170309f8587c733a232f6d4fd12c7cc3d6c5`。实施与验收步骤见[工作计划](superpowers/plans/2026-10-25-android-background-execution.md)。 + +本轮最初方案基于落后远端 55 个提交的本地副本。同步读取远端后确认,单一进程级运行时、Android 前台服务、Headless JS、通知和中断恢复已经存在。本文取代此前尚未提交的重新建设方案,以当前主线为依据;本 PR 只更新方案和验证计划,不修改应用行为。 + +## 技术决定 + +继续使用 Expo 原生开发客户端与 Prebuild。沿用现有 React Native/Hermes、Pi Durable 和 SQLite,以及 `modules/moose-background`;不重新创建执行服务或第二个 RuntimeHost。Termux 和 iOS iSH 是当前已有的可选工具执行环境,保留原有边界。本设计不要求迁移框架或替换执行环境。 + +```text +Android 应用进程 / 现有 ReactContext / Hermes + ├─ React Native 界面:观察已提交状态 + ├─ RuntimeOwner:唯一运行时所有者 + │ └─ MobileEngine / Pi Durable / SQLite + └─ MooseExecutionService / Headless JS + └─ ExecutionOwner:共享有限执行租约、通知、唤醒锁 + +可选工具环境:设备文件工具 / Termux +``` + +核心问题已经从“有没有后台执行资格”转为“当前 APK 在具体手机的省电和联网条件下是否可靠,以及失败停在哪一层”。模拟器记录是历史证据,真机不能直接沿用其结论。 + +## 当前源码的准确行为 + +| 位置 | 主线实现与边界 | +| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | +| `src/runtime/execution-owner.ts` | RuntimeOwner 在进程内复用一次打开;ExecutionOwner 用 token 和引用计数共享服务租约,界面不拥有运行时关闭权。 | +| `src/platform/open.ts`、`src/platform/background.ts` | 接入执行保护、待处理停止与通知观察;界面回到前台只刷新视图,不重新提交输入。 | +| `modules/moose-background/android/src/main/AndroidManifest.xml` | 服务类型为 specialUse,同进程、exported=false、stopWithTask=false。 | +| `MooseExecutionService.kt` | 复用既有 ReactContext,通过 HeadlessJsTaskContext 启动 MooseExecution;不创建第二个 ReactHost。 | +| 服务时限 | 每次连续服务会话预算 30 分钟;唤醒锁上限为预算加 15 秒;启动握手超时 15 秒。这是现有行为,本 PR 不改成 heartbeat 续租。 | +| 通知权限 | Android 启动任务需要通知权限;拒绝时阻止执行并显示错误,运行中权限丢失触发取消。本 PR 不改变该产品决策。 | +| 通知停止 | 停止所有回答和重复任务,先持久化停止原因,JS 确认取消后释放服务;10 秒未确认时终止进程,下次手动启动先处理停止标记。 | +| 重复任务等待 | 等待期间不持有服务。后台到期时,只有已有其他活跃租约才继续,否则等待 Moose 可见。不作精确后台唤醒承诺。 | +| 进程回收 | START_NOT_STICKY;没有开机接收器或自动服务复活。用户重新打开后按持久状态恢复。 | +| 工具恢复 | 未知副作用阻止后续修改,需用户明确检查并允许;保留安全重放边界,不能把断网恢复等同于重跑所有工具。 | +| Termux | 独立应用进程,Moose 的服务不会替它保活。需要分别检查连接、自检和执行结果。 | +| iOS | 不从 Android 服务推导 iOS 后台能力;已有 iSH 工具环境不等于持续后台执行资格。 | + +当前恢复、重试与通知语义以[后台恢复文档](background-recovery.md)为准。不要用本地旧副本的 AppState/engine.close 逻辑解释当前发布版本。 + +## 从作者公开资料得到的依据 + +作者的 Android 原型明确使用[前台服务](https://x.com/badlogicgames/status/2106485523317489902),说明[锁屏后继续执行](https://x.com/badlogicgames/status/2111214825154179397),并[复用 Termux 的部分实现](https://x.com/badlogicgames/status/2111366160205987847),尤其是[后台进程能力](https://x.com/badlogicgames/status/2111369852111786259)。他的界面使用[内置 WebView](https://x.com/badlogicgames/status/2111385729368740337)。这些资料支持把执行生命周期移出界面,不能证明 Moose 必须换成相同框架。 + +系统规则仍需遵守:[Android 服务类型](https://developer.android.com/develop/background-work/services/fgs/service-types)、[用户停止服务应用](https://developer.android.com/develop/background-work/services/fgs/handle-user-stopping)、[Doze](https://developer.android.com/training/monitoring-device-state/doze-standby)、[React Native Headless JS](https://reactnative.dev/docs/headless-js-android)。specialUse 的商店审核是未来发布条件,不代表后台联网或无限运行保证。 + +## 下一步:先验证发布版本,再针对证据改代码 + +用户已表示可以在 Android 真机验证。第一轮使用当前发布 APK,记录版本、手机型号、Android 版本、通知权限、电池策略和工具环境。先维持系统默认省电设置,出现问题后再单独改变一个设置比较;不自动要求电池优化豁免。 + +现有开发探针 `src/dev/BackgroundSmoke.tsx` 受 **DEV** 和环境变量共同限制,正式 Release APK 中不能靠设置环境变量打开它。内存受控 SSE 探针与真实 Expo fetch 网络探针也必须区分。后者当前固定连接模拟器地址 `10.0.2.2:8106`,真机需要专门的受控测试构建和明确的端口路由。 + +本 PR 不声称提供新的受控 Release 测试包。准备该测试包是后续可独立审查的代码变更,不能伪造已支持的构建命令。 + +## 真机验收表 + +| 场景 | 预期与需记录的证据 | +| ---------------- | --------------------------------------------------------------------------------------------------- | +| 普通切后台/锁屏 | 分别验证 5 分钟和 10 分钟;解锁前的完成通知或持久化完成时间证明后台已完成,不能只看解锁后出现答案。 | +| 多会话 | 两个任务共用服务;结束一个不会停止另一个,最后一个结束释放运行通知和唤醒锁。 | +| Activity 重建 | 重新附加当前结果,无重复用户输入、模型准入或第二个 engine。 | +| 通知停止 | 全局取消,未产生完成结果提醒;再次打开不恢复已取消工作。 | +| 预算到期 | 单独验证 30 分钟预算,保留 budget_expired 原因;不能把它归为随机锁屏失败。 | +| 断网与恢复 | 记录 attempt、重试次数和最终状态;认证、额度与用户取消不自动重试。 | +| 系统回收/强停 | 分开记录进程回收后重开、系统任务管理器 Stop 和设置 force-stop;没有自动复活承诺。 | +| 重复任务 | 后台等待不会占服务;逾期不集中补跑;下一轮需要前台时应按现有规则等待。 | +| Termux | 先运行无模型自检,之后再验证工具环境;独立记录 Termux 不可用与 Moose 本身中断。 | +| 空闲与耗电 | 无工作后服务/唤醒锁释放,30 分钟空闲不常驻;记录实际观察,不引用作者的低耗电体验作为结论。 | + +自动化与受控验证不使用真实凭据、不转发模型请求。使用正常聊天验证可能消耗账户额度,应由用户自行发起并注明,这不构成代理执行真实计费请求的授权。 + +## 当前验证状态 + +本轮只读取远端源码、已有验证记录及发布资产,并核对文档。没有重新运行应用测试、Hermes 导出、原生编译、模拟器或真机测试,也没有真实模型请求。现有历史验证详见 background-recovery.md,不在此重复宣称通过。 diff --git a/docs/superpowers/plans/2026-10-25-android-background-execution.md b/docs/superpowers/plans/2026-10-25-android-background-execution.md new file mode 100644 index 0000000..8a53fad --- /dev/null +++ b/docs/superpowers/plans/2026-10-25-android-background-execution.md @@ -0,0 +1,90 @@ +# Android 后台执行真机验收 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 验证最新 Moose APK 在用户 Android 手机上的锁屏执行,按观测修复剩余问题。 + +**Architecture:** 保留主线 RuntimeOwner、ExecutionOwner、MooseExecutionService 和 Headless JS。先验证发布版本,补足受控真机/Release 验证入口,再依据失败证据做最小修复。 + +**Tech Stack:** 以当前主线锁文件为准,继续使用 Expo、React Native、Hermes、Pi Durable、SQLite 和已有可选工具环境。 + +**Spec:** [主线架构与真机验收](../../android-background-execution.md)。 + +## Global Constraints + +- 源码基线为 a48d170309f8587c733a232f6d4fd12c7cc3d6c5;本地旧副本落后远端 55 个提交。 +- 已有后台服务与执行所有权,复用现有模块,不创建第二套 RuntimeHost、服务或调度器。 +- 现有连续预算 30 分钟、握手 15 秒、停止确认 10 秒;通知权限是准入条件,本轮不改这些策略。 +- 重复任务等待不持服务,START_NOT_STICKY,无开机恢复或自动复活。 +- 普通锁屏、Doze、网络断开、任务预算与 Termux 进程失效分开诊断。 +- 自动验证使用隔离 SQLite/工作区与受控传输;真实账户请求由用户自行发起。 +- 本 PR 只有方案和计划,不包含新 APK 或产品行为修改。 + +## Review Focus + +- 运行的 APK 过旧:Task 1 记录实际版本,不用新主线解释旧 APK。 +- 解锁后才执行:Task 3 用解锁前通知与持久化时间判断,不能凭答案最终出现判成功。 +- 受控流与真实网络混淆:Task 2 分别验证内存流、Expo fetch 和真机路由。 +- 有界执行被误判为锁屏故障:Task 3 单独验证 30 分钟预算与通知停止原因。 +- Termux 失效误归 Moose:Task 3 将设备文件环境与 Termux 分开测试。 + +### Task 1: 核对当前基线与用户测试包 + +**Files:** Read `package.json`、`src/runtime/execution-owner.ts`、`src/platform/background.ts`、`modules/moose-background/android/src/main/java/dev/verysmallwoods/background/MooseExecutionService.kt`、`docs/background-recovery.md`;后续 Create `docs/android-device-validation.md`。 + +**Interfaces:** 测试记录包含版本、源码 SHA、设备型号、系统版本、通知授权、电池策略、工具环境、是否开发客户端。 + +- [x] 核对最新 origin/main,确认已有后台所有权和原生执行服务;校正此前尚未提交的旧架构计划。 +- [x] 核对 v0.8.0 发布页,确认存在 app-release.apk;此步骤不等于安装或验证该 APK。 +- [ ] 用户确认手机安装版本。需要更新时使用[发布页](https://github.com/sugarforever/moose/releases/tag/v0.8.0)中的 APK,优先覆盖安装,避免卸载删除数据。 +- [ ] 新建真机记录,待测项目全部标为未执行;不复制历史模拟器通过结果。 + +### Task 2: 准备无需模型账户的受控真机验证构建 + +**Files:** Modify `index.ts`、`src/dev/BackgroundSmoke.tsx`、`scripts/background-probe-server.mjs`、`.github/workflows/build.yml`;Create `tests/background-probe-config.test.ts`、`src/dev/background-probe-config.ts`。 + +**Interfaces:** `backgroundProbeConfig(env: Readonly>): { enabled: boolean; endpoint: string | null }`。仅专用诊断构建可 enabled;生产构建为 false。真机端点固定为 http://127.0.0.1:8106/,模拟器保留 http://10.0.2.2:8106/,由显式构建选项选择,运行时不能接受任意地址。 + +- [ ] 先写配置回归:生产构建不启用诊断入口、专用 Release 诊断构建启用、仅允许两个固定受控端点、缺配置拒绝运行网络探针。确认失败后实现解析。 +- [ ] 将探针准入从单纯 **DEV** 改成显式专用构建选择;正常发布仍打开 App。测试包使用独立应用 ID 和名称,避免覆盖正常 Moose;沿用独立 background-smoke.sqlite 和受限工作区。 +- [ ] 真机通过 USB 运行 `adb reverse tcp:8106 tcp:8106`,主机端运行现有 probe server;验证 Expo fetch 实际经过受控网络。服务器只返回固定 fixture,不连接模型、不接受/记录真实凭据。 +- [ ] 增加请求计数和请求关联的最小控制协议回归,再实现所需观测;读最终提交时间、工作区结果和请求数,证明解锁前完成。内存流单独标识。 +- [ ] 手动诊断构建工作流输出独立 APK,普通 tag 发布不带诊断入口;明确 EXPO_PUBLIC 值会编入 JS,不能包含秘密。 +- [ ] 运行目标测试、typecheck、bundle、原生编译和 Release 启动验证后,单独提交这项代码变更并提供测试 APK。当前文档 PR 不宣称这些步骤已完成。 + +### Task 3: 用户执行手机验收 + +**Files:** Update `docs/android-device-validation.md`;参考 `docs/background-recovery.md`。 + +**Interfaces:** 每项记录开始时间、锁屏/后台时间、解锁时间、完成时间、结果状态、服务通知和错误原因。截图遮去会话正文与账户信息。 + +- [ ] 正常发布 APK 先做短任务,确认通知可见、后台完成提示和打开结果正确。此轮正常聊天由用户主动发起,注明是否消耗模型额度。 +- [ ] 专用受控构建运行 5/10 分钟锁屏探针;等待超过延时后先观察锁屏通知,再解锁读取已保存结果和最终提交时间。 +- [ ] 运行两个并发探针、通知停止、拒绝通知权限、Activity 重建;分别核对服务共享、取消范围和无重复输入。 +- [ ] 单独验证 30 分钟预算到期、断网/恢复、进程回收后重开、系统 Stop 和 force-stop;保留 first cancellation reason。 +- [ ] 先选设备文件环境,再选 Termux 并运行现有无模型自检,分别验证;重复任务按现有后台等待语义记录,不期待精确唤醒。 +- [ ] 可连接 adb 时核对 dumpsys activity services 和 dumpsys power;强制 Doze 由开发者执行并在结束后 unforce。用户手动锁屏结果不写成强制 Doze 测试。 +- [ ] 测试结束移除 adb reverse,恢复测试中改动的网络/省电设置,记录空闲资源释放和实际耗电观察。 + +### Task 4: 按失败层定位并补回归 + +**Files:** 只修改证据所指模块及其现有测试:`tests/background.test.ts`、`tests/background-platform.test.ts`、`tests/background-reasons.test.ts`、`tests/background-sse.test.ts`、`tests/execution-recovery.test.ts`,或原生服务相关文件。 + +**Interfaces:** 每个修复需要失败用例、明确根因、最小变化和同设备复测结果。 + +- [ ] 把失败定位到准入、JS 生命周期、模型传输、服务预算、持久化或 Termux,区分实际错误和当前产品边界。 +- [ ] 先用真实 Harness/SQLite 或原生受控探针复现;回归失败后实现最小修复,不按旧计划重复引入 Host。 +- [ ] 新代码运行目标回归;涉及 runtime 时重新 bundle。恢复不得改变 requestId、自动重放未知副作用或无限重试。 +- [ ] 单独提交可审查的修复,再提供对应 APK,重复最初失败的手机用例。 + +### Task 5: 完整检查与交付 + +**Files:** Update `docs/android-device-validation.md`、`docs/background-recovery.md`;能力变化时同步 `README.md`。 + +- [ ] 有代码变更时运行 npm run verify、Expo Doctor、改动文件 Prettier 和 Android 原生编译。仅文档变更核对格式、引用和事实即可。 +- [ ] 分别报告行为测试、Hermes 发布资源导出、原生构建、模拟器、真机和真实模型请求。未执行保留未执行,不复用历史结果。 +- [ ] 交付记录明确适用手机、系统、电池设置、30 分钟预算、重复任务边界和剩余问题。没有真机结果时不宣称锁屏问题解决。 + +## 本轮状态 + +本轮完成主线核查、架构校正和验证计划。用户可以承担真机操作;受控 Release 测试包仍待 Task 2 实现。保留原工作区业务改动,PR 仅包含这两份文档。