Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions docs/android-background-execution.md
Original file line number Diff line number Diff line change
@@ -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,不在此重复宣称通过。
90 changes: 90 additions & 0 deletions docs/superpowers/plans/2026-10-25-android-background-execution.md
Original file line number Diff line number Diff line change
@@ -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<Record<string, string | undefined>>): { 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 仅包含这两份文档。
Loading