修改github eta 2.6.1 兼容android13 去掉超级小爱hook
Some checks failed
Eta Build / 构建 Debug 与 Release APK (push) Has been cancelled

This commit is contained in:
DelLevin-Home
2026-08-18 20:36:17 +08:00
commit 8a0e5e4bd3
429 changed files with 80043 additions and 0 deletions

137
docs/AGENT_RUNTIME.md Normal file
View File

@@ -0,0 +1,137 @@
# Agent Runtime
Eta 的 Agent Runtime 负责把一次用户输入组织为模型回合、工具执行和可持久化的增量 transcript。它运行在模块自身进程Hook 进程只负责识别入口、发送请求和接收结果。
## 代码边界
- `AgentModelClient`:稳定门面、配置与跨进程会话 DTO。
- `AgentLoop`:单次 run 的状态机,不依赖 Android Service、Room 或 Compose。
- `AgentPromptBuilder`系统约束、Skill 索引、历史和当前用户输入。
- `AgentConversationCodec`Provider JSON 与稳定会话 DTO 的转换。
- `AgentToolCatalog` 及分组目录:模型可见的工具 schema不执行工具。
- `AgentTraceFormatter`:只生成可展示、可记录的脱敏摘要。
- `AgentProviderClient`OpenAI-compatible、Anthropic 等协议边界。
- `AgentRunController`:取消、暂停和 steering 队列。
- `AgentRuntimeSession`:每个 run 自持 reply channel并保证唯一最终结果。
- `AgentRuntimeRunExecutor`:从 Skill/工具初始化到模型执行、资源清理和终态提交的统一异常边界。
- `AgentRuntimeService`Android 生命周期、入口 IPC 和浮层宿主;不再内联 Agent 执行循环。
- `ShellProcessSupervisor`Android/Alpine Shell 进程的接纳、独立进程组、取消和回收;终端协议不承担进程所有权细节。
## Loop 语义
一个 turn 是“一次 assistant 响应 + 该响应提交的完整工具批次”。循环遵守以下顺序:
```text
pending steering
→ provider response
→ assistant history
→ tool batch按模型顺序串行执行
→ contiguous tool results
→ optional image observations
→ next turn / final result
```
关键不变量:
- steering 默认逐条排队,只在当前 turn 完整结束后注入;它不会取消当前 HTTP 请求或关闭工具资源。
- 同一 assistant 消息中的全部 tool result 必须连续写入,再追加不受 Provider 原生 tool-result image 支持的图片观察。
- `finish_reason=length``max_tokens` 且包含工具调用时,不执行任何可能被截断的参数;为每个调用写入结构化错误结果,让模型重新规划。
- 只有明确的 `tool_calls` / `tool_use` 终止原因才允许执行工具;`stop`、内容过滤或未知终止原因中夹带的调用一律作为协议矛盾拒绝。
- 工具参数在执行前按本轮实际下发的 JSON Schema 重新校验。模型输出不是可信输入,缺少坐标等必填字段时不得调用设备执行器。
- transcript 只返回本次 run 新增的 assistant、tool 和运行中 steering 消息,不重复旧 history 或本轮初始用户消息。
- GUI/终端工具保持串行。Android 前台状态和会话式 Shell 都不具备可安全并行的通用语义。
- 单次 run 当前最多 64 个模型回合、256 个工具调用,防止异常模型形成无界循环。
- 最后一个允许回合不会再启动工具副作用,因为其结果已经没有下一回合可以消费。
- cancel 是终止信号pause 是检查点阻塞steering 是下一回合输入。三者不能互相模拟。
- cancel 的主线程路径只做原子终态与资源关闭:共享浏览器按 runId 校验归属;终端立即封闭新的进程接纳,并在后台按独立进程组终止同步命令、会话和 async job再完成线程与流回收。Android 上 `setsid` 或 PID/PGID ownership 握手不可用时会 fail closed非 Android 测试环境才允许父子树快照回退。终止前还会核验随机 ownership token避免陈旧 PGID 复用后误杀无关进程。
- 最终 steering 检查会原子关闭接收入口Loop 返回后不会再把无人消费的补充指令误报为已接收。补充指令也不会解除 pause。
- 新 run 替换旧 run、用户取消和正常完成都通过 `AgentRuntimeSession``RUNNING → COMMITTING → TERMINAL` 状态机竞争唯一终态;提交胜者独占 outbox、归档和最终发布客户端另有 30 分钟兜底超时。
- 入口请求只能缩小工具能力不能自行授权。Runtime 在开始 run 时裁剪配置,在每次浏览器、终端和设备工具执行前重新读取用户开关,并在 thinking 关闭时移除自定义请求体中的 reasoning/thinking 覆盖字段。
- 设备工具分为直达工具、敏感读取工具和敏感操作工具当前均默认开启。Runtime 在每次执行前重新读取用户开关;开关允许且参数通过 schema 与执行器校验后即可执行,不再用固定关键词二次匹配用户原话。
- 微信发送不提供专用工具、参数协议或额外策略层,完全使用通用 GUI 工具观察和操作微信界面。
- 通知、短信验证码、WiFi 凭据和日志属于瞬时敏感工具数据。当前模型回合可以使用原始值,但持久 transcript 会同时替换对应工具参数和结果,避免进入会话数据库或后续 IPC。
## Provider 协议
OpenAI-compatible Provider 可在配置页选择 `Chat Completions``Responses API`。新安装和重置后的内置 OpenAI 默认使用 Responses数据库中已有 Provider 不会被默认值覆盖。自定义 Provider 和其他内置 Provider 默认仍使用 Chat Completions。
Responses 请求固定使用 `stream:true``store:false`,不发送 `previous_response_id`。Runtime 把本地历史转换为 typed input Items并在同一次 run 的工具回合之间精确回放 Provider 返回的完整 output Items因此 encrypted reasoning、服务端工具状态等 opaque 数据只存在于内存,不进入 IPC transcript、Room、日志或运行归档。持久会话只保留规范化回答、可见推理内容和 Eta 工具记录,后续 run 由这些稳定数据重新构建上下文。
兼容接口若在 `response.completed` 中省略 `output` 或返回空数组Runtime 只使用同一 SSE 流中已经收到的标准文本、推理摘要和函数调用增量完成当前轮次;非空终态始终是权威结果,且本地恢复结果不会冒充 Provider 的 opaque output Items。
推理界面展示的是 Provider 返回的 reasoning summary它不是原始思维链也不会由 Eta 伪造。兼容 Provider 若按 Responses 协议返回 `reasoning_text`Runtime 会把它作为可见推理内容展示。Responses 只对精确命中官方目录且未被远端显式标记为 `reasoning:false` 的模型补齐推理能力,不会因 Endpoint 类型而假定所有模型支持推理。
服务端网页搜索是 Responses Provider 的独立开关,默认关闭。开启后请求只增加 `web_search` 托管工具;搜索开始和结束作为独立运行事件投影到 UI不进入 Eta 本地工具执行器。最终回答中的 `url_citation` 会去重并转换为可点击 Markdown 引用;偏移无效时降级为回答末尾的来源列表。当前不接入 file search、code interpreter、MCP 或其他托管工具。
## 长期记忆
长期记忆保存在 App 私有目录的单一 `MEMORY.md` 中。文件使用 UTF-8安全上限为 1 MiB仓库在进程内锁中应用变更并通过 `AtomicFile` 覆盖完整文件。模型写入携带当前内容的 SHA-256 revisionrevision 不一致时返回 `MEMORY_CONFLICT`,不会覆盖并发更新。
每次 run 只把 `# 核心记忆` 的预算内内容、一级/二级标题索引和 revision 放入系统背景。核心预算为 `min(32000, max(4000, contextWindow / 16))` 个字符;模型窗口未知时按 128K 计算。没有 `# 核心记忆` 标题时不自动注入正文。其余内容由 `memory_get` 按行分页或按文本检索,单次最多返回 32000 字符。
`memory_write` 支持行区间替换、独立章节追加与清空;单次模型生成内容最多 3500 字符,设置页的用户手动编辑不受此单次工具限制。关闭记忆不会删除文件,后续 run 不再注入或暴露工具;已开始的 run 在每次执行记忆工具前也会重新检查开关。
记忆内容只作为可编辑背景,不具有指令优先级。记忆工具原始参数与结果可供当前 Agent Loop 使用,但对应工具调用在持久 transcript 中整体脱敏;运行事件只保存操作类型、行数、字节数和错误码,不保存正文或查询词。
## 终端环境
`terminal``environment` 明确区分设备控制与通用 Linux 工具,默认值为 `android`
- `android` 继续使用系统 Shell。`user` 身份不升级权限;`root` 身份在 `su` 内探测 Magisk、KernelSU、APatch 或系统 BusyBox并优先进入 standalone `ash`,因此 BusyBox applet 不要求预先加入 PATH。旧 `run_command`、文件读写和目录操作保持这一环境,避免改变既有 Android 路径与命令语义。
- `linux` 仅允许 `root`,并要求用户先在设置中安装 Eta 管理的 Alpine 环境。每个命令或会话进入独立 mount namespace挂载必要的 `/proc``/dev` 以及可用的共享存储后再 chroot`/workspace` 绑定 Eta 的 Android 工作目录 `/data/local/tmp/fuck_andes`,并作为 Linux 默认工作目录,`/sdcard` 继续指向共享存储。进程结束时命名空间一并销毁,不把 bind mount 留在 Android 全局。chroot 只提供 Linux userland不构成安全沙箱。
安装器只接受代码中固定版本、大小和 SHA-256 的 Alpine 官方 minirootfs先在临时目录校验并解压再原子替换 App 私有 rootfs。默认工具档案包含 Agent 高频使用的搜索、差异、补丁、Git/SSH、传输、结构化数据、进程与压缩工具以及 Python、pip、venv、pipx、uv 和 Ruff。工具档案使用版本化完成标记旧安装会保留 rootfs 和用户文件,只补装当前档案。工具安装完成前不会写入完成标记,失败后可继续安装。
APK 分析是用户主动安装的独立档案。JADX、Apktool、smali 与 baksmali 使用固定官方 Release URL、大小和 SHA-256下载完整校验后才进入 App 可写的 cache staging不能把下载或解包暂存目录放进由 Root 创建的 Alpine 管理目录。GitHub 实际制品域名不可达时,安装器按固定顺序尝试 HTTPS 下载代理,但仍只接受与官方清单 SHA-256 完全一致的字节。JADX 只解出 CLI 脚本、运行库与许可证,成功验证全部命令后再原子切换当前版本。档案安装 OpenJDK 17但不安装全局 Gradle、Android SDK 或 NDK。由于官方与 Apktool 随附的 Linux AAPT2 都不能直接用于手机 arm64 环境,`apktool build` 在当前档案中稳定拒绝;解码、代码查看和独立 Smali 汇编/反汇编不受影响。
## 上下文与续接
App 在发起请求前已经把当前用户消息写入会话 history因此 Runtime 返回的 transcript 必须保持“增量”语义。已完成 run 的补充请求由 `AgentContinuationBuilder` 使用以下顺序重建上下文:
```text
旧 history
→ 原始用户消息
→ 完整增量 transcript
→ 新补充消息
```
图片只在需要它的当前模型回合中传递;持久 transcript 会删除 data URL并写入稳定的省略说明避免截图 base64 同时膨胀 Binder、Room 和后续上下文。外部入口归档可以另外保存有界的小预览用于还原用户消息 UI但预览不会重新进入模型历史。启动请求在发送前按实际 `Parcel` 大小校验,超过 768 KiB 时会明确拒绝并提示减少图片数量或分辨率。运行归档 transcript 上限为 100 万字符;会话上下文检查点和直接 IPC transcript 上限为 9.6 万字符outbox 批量 drain 使用更紧的单项预算,确保最坏 8 条待交付结果仍处于 Binder 事务预算内。任何容量压缩都会在保留的 history 前插入明确的 Eta system notice不会把删头后的 transcript 冒充成完整上下文。会话元数据、逐条展示消息和有界上下文检查点分别存储;会话列表查询不读取上下文正文,启动时也不会因单个长期会话阻塞全部会话恢复。
浮层在已完成结果后发起的 continuation 会在 handoff 中只携带本次新增的 prompt supplement不累计复制旧补充。App 回到前台时 drain outbox把该用户消息和增量 transcript 一起写回 history。
自动重试、上下文压缩和跨 run、跨 Provider 的 opaque reasoning 状态尚未由当前 Loop 冒充实现Responses output Items 只在当前 run 内回放,不能作为持久会话状态。
## Skills 安装边界
Skill 安装工具始终向模型提供,不再根据顶层用户输入的固定关键词决定是否暴露或执行。网页、仓库 README 和已安装 Skill 仍只是数据,不能改变工具参数或执行边界。
- AI 安装只访问公开 GitHub HTTPS 地址curated 默认来自 `openai/skills``skills/.curated`。安装路径必须来自当前 run 对同一仓库与 ref 的检查结果,最多 20 个。
- 本地 ZIP 由 Skills 页面通过系统文件选择器读取,不申请共享存储权限,也不把归档复制到公开目录;每个 ZIP 只允许包含一个 Skill。
- GitHub 下载与本地 ZIP 共用受限解包和校验流程:拒绝路径穿越、绝对路径、重复条目、嵌套 Skill、非法 frontmatter以及超过条目数、单文件、归档或总解压预算的输入。
- 安装先在 App 私有临时目录完整验证,再提交到正式 Skills 目录。文件系统与 Room 变更由持久事务日志协调,进程异常退出后会在下次变更前恢复;批量安装任一步失败都会回滚。同名用户 Skill 默认保持不变GitHub 单冲突替换绑定仓库、提交、路径和 Skill ID可在同一 run 精确重试;内置 Skill 永远不能被导入包覆盖。
- 安装只保存文件、登记索引并默认启用,不执行 `scripts/`,也不改变终端/文件工具开关。本轮 Skill 索引在模型调用前已经冻结,因此新 Skill 从下一轮对话开始可用。
已安装 Skill 的附属文本资源通过独立的有界读取工具访问读取时再次做相对路径、canonical root、UTF-8 与大小检查;脚本和二进制 asset 不会借此被执行或当作无限文本送入上下文。
待确认结果和外部入口归档会把 transcript 一并写入 Room。数据库 6 → 7 使用显式非破坏迁移为旧记录补 transcript7 → 8 为会话增加已应用 run 标记10 → 11 将会话上下文迁入独立的有界检查点并清理旧的大字段。恢复幂等性不再靠比较 history 尾部猜测;保存任务严格按调用顺序串行,只有包含对应标记的快照落盘后才 ACK outbox。旧 6.x 结果仍可用已有 assistant 内容合成兼容 history。
## 验证
核心回归测试位于:
- `AgentModelClientLoopTest`
- `AgentConversationCodecTest`
- `AgentRunControllerTest`
- `AgentContinuationBuilderTest`
- `AgentRuntimePolicyTest`
- `AgentRuntimeSessionTest`
- `AgentToolCatalogTest`
- `AgentMemoryStoreTest`
- `AgentMemoryContextBuilderTest`
- `FuckAndesDatabaseMigrationTest`
最终验证仍运行项目统一命令:
```bash
./gradlew :app:assembleDebug :app:testDebugUnitTest :app:lintDebug
```

169
docs/TECHNICAL.md Normal file
View File

@@ -0,0 +1,169 @@
# 技术实现
模块按进程与功能域安装针对性的 Hook。入口只负责生命周期、进程筛选、配置注入与安装结果汇总具体目标定位和拦截逻辑留在各自功能域中。
## Hook 安装与诊断
- 每个功能域通过 `HookRegistrar` 注册 Hook并使用稳定 ID、`PROTECTIVE` 异常模式和统一优先级策略。
- 安装结果区分 `INSTALLED``MISSING``FAILED``SKIPPED`,保留 `HookHandle`,便于定位 ROM 或目标 App 升级后的签名漂移。
- 普通目标缺失与反射异常按功能域失败开放;`HookFailedError` 等框架级 `Error` 不会被普通异常隔离层吞掉。
- `ModuleMain` 会尽早过滤无关进程并调用 `detach()`,避免在不需要的进程中继续保留生命周期回调。
## 日志与 Release 裁剪
Eta 使用同一组四级日志语义并按运行环境选择后端App 与 Agent Runtime 通过 `AndroidAgentLogger` 写入 logcatHook 进程通过 `ModuleLogger` 写入 Xposed 日志。业务代码不得直接调用 `android.util.Log``XposedModule.log`
| 级别 | 使用范围 | Release |
| --- | --- | --- |
| `DEBUG` | 高频正常流程、目标匹配、重试细节、尺寸与计数 | 全部裁剪 |
| `INFO` | 低频生命周期、Hook 安装汇总、特权动作的结构化摘要 | 保留 |
| `WARN` | 可恢复降级、fallback、目标签名漂移、重试耗尽 | 保留;高频事件必须节流 |
| `ERROR` | 当前请求或功能确定无法完成、关键不变量被破坏 | 保留 |
取消、功能关闭和可选目标缺失不记为 `ERROR``debug` 只接受惰性 suppliersupplier 必须是纯观察代码,不能执行 Hook、反射写入、状态变更或其他业务副作用因为 Release 的 R8 会删除整次调用。
任何级别都禁止记录 Prompt、请求或响应正文、API Key、认证 Header、Cookie、工具参数与结果、原始命令、stdout/stderr、URI、文件路径、图片内容、应用清单和原始运行标识。异常默认只记录类型不拼接 `Throwable.message`;外部或模型生成的名称必须先转成受长度和字符集约束的安全 token。只有确认不承载用户数据的框架或反射异常才允许附带完整堆栈。
Release 裁剪以 `app/proguard-rules.pro` 为唯一可执行事实来源,规则边界如下:
- `-maximumremovedandroidloglevel 3 class fuck.andes.** { *; }` 只删除 Eta 自有代码中的 Android `VERBOSE/DEBUG`,不影响依赖库。
-`AgentLogger.debug(Function0)``AndroidAgentLogger.debug(Function0)``ModuleLogger.debug(Function0)` 使用精确的 `-assumenosideeffects`,覆盖 R8 无法识别的 Xposed 日志后端。
- 不为 `INFO/WARN/ERROR` 声明无副作用,不使用 `*Logger` 或全局 `android.util.Log` 通配裁剪规则。
- 每次修改规则后同时构建 Debug 与 Release并检查 R8 configuration/usage、DEX 日志调用、代表性日志字符串和 Xposed 入口元数据。
上述策略依据 Android 官方的 [R8 附加规则](https://developer.android.com/topic/performance/app-optimization/additional-rule-types)、[日志信息泄露防护](https://developer.android.com/privacy-and-security/risks/log-info-disclosure)、AOSP [日志级别约定](https://source.android.com/docs/core/tests/debug/understanding-logging)、OWASP [运行时日志测试](https://mas.owasp.org/MASTG/tests/android/MASVS-STORAGE/MASTG-TEST-0203/) 与 [CWE-532](https://cwe.mitre.org/data/definitions/532.html)。
## Eta 原生数字助理
Manifest 注册 `VoiceInteractionService`、独立进程的 `VoiceInteractionSessionService`、全屏 `TYPE_APPLICATION_OVERLAY` 助理浮窗以及 Android 助理角色资格要求的 `RecognitionService`。设置页只负责打开系统数字助理选择界面;当前浮窗不请求麦克风权限。
`VoiceInteractionSession` 只承接系统入口并关闭自身 UI`EtaAssistantOverlayService` 持有全屏窗口、彩色边缘动画和键盘输入。窗口通过 `setFitInsetsTypes(0)` 绘制到状态栏、导航栏与显示开孔后方,可交互内容再通过 `WindowInsetsRulers.SafeDrawing``Ime` 保持可触达,避免给根容器增加 Insets 后截断 edge-to-edge 背景。用户提交的文本交给 `AgentRuntimeClient`;请求、流式结果、前台工具收起、取消与归档沿用既有 Runtime 协议,当前不执行语音识别或语音朗读。
`:voice``:voice_session``:recognition` 进程只初始化本地偏好不预热数据库、Skills 或 Xposed UI 服务。`RecognitionService` 仅保留 Android 数字助理角色资格所需声明,不由当前浮窗调用。
## system_server
- **电源键接管**Hook `PhoneWindowManagerExtImpl$OplusSpeechHandler.handleMessage()` 处理系统分发给小布的唤醒消息(`what == 0x3F3`)。目标为小布时直接执行原方法;目标为 Eta 时才拦截并分发到对应入口。
- **兼容配置**:三态目标写入字符串键;键不存在或值非法时读取旧 `POWER_KEY_TAKEOVER` 布尔协议,`true` 继续表示启用本项目助手(归 Eta`false` 表示小布。新安装默认保持小布,旧用户不会因新增 Eta 被改写目标。
- **数字助理配置修复**:独立自动设置开关开启后,开机、解锁、切用户及启动失败恢复时,通过 `AssistantManager` 异步校正当前 Eta 目标的 `android.app.role.ASSISTANT` 与 secure settings。小布模式和开关关闭时不写系统配置校验缓存及异步回调同时核对用户与目标旧目标任务不会继续覆盖新选择。
- **唤起逻辑优化**Eta 优先使用活动 `voiceinteraction` 会话,再在已经配置为默认助理时尝试同包 `ACTION_ASSIST` 桥。所有路径失败后立即执行小布原逻辑,不阻塞系统回调。
- **无障碍保护**:复用已验证的 `SystemServer.startOtherServices(TimingsTraceAndSlog)` 生命周期点,在系统服务启动完成后接入事件驱动保护。后台工作复用 Android `BackgroundThread`,不开模块线程、不轮询;开关默认关闭,开启请求需同时通过 signature 权限、真实发送 UID、服务声明与 APK signer 钉扎校验。保护只维护 owner 用户中的 Eta 组件和总开关,保留其他服务;断连时通过仅允许 `system` UID 调用的健康 Provider 确认,并对 Eta 做带次数上限和冷却的定向重绑。
## 无障碍保护
「强制保持无障碍」默认关闭。开启后,注入 `system_server` 的保护后端会校验 Eta 的服务声明、调用 UID 与 APK 签名并在无障碍服务列表、总开关、Eta 安装包或 owner 用户解锁状态变化时校正配置。它保留其他无障碍服务,不依赖 App 自启动,也不执行周期轮询。
如果服务仍在启用列表中但没有真实连接,保护后端只重启 Eta 自身并最多逐步尝试三轮持续失败后冷却一分钟。ColorOS 持续反删设置时,写回间隔会从 300 ms 退避到 30 秒,稳定一分钟后恢复。关闭开关只停止保护,不会替用户关闭当前服务。
GUI 工具执行前仍会确认真实服务连接。保护未开启、system 作用域未生效或重绑超时时,本次动作会明确失败,不会改用 Root 或 Shell 偷偷修改无障碍设置。
若 App 控制入口不可用,可用 ADB 停止保护后再从系统设置关闭服务:
```bash
adb shell settings put global eta_accessibility_protection_enabled 0
```
删除该设置会恢复默认关闭状态。开发阶段主动更换签名且确认 APK 来源可信时,还需清除旧 signer 钉扎值:
```bash
adb shell settings delete global eta_app_signer_sha256
```
## 小布记忆
ColorOS 系统记忆存在 `com.oplus.aimemory``ai_memory` 数据库中。Eta 只在小布记忆默认进程保留模块生命周期Hook 其 `DataShareProvider.call(String, String, Bundle)` 安装内部查询桥。Runtime 通过 Root 以固定 method 调用该 ProviderHook 在拥有数据库权限的目标进程内以只读模式执行固定查询。非 Eta method 会原样进入小布记忆自身逻辑;内部 method 只接受 UID 0不向模型暴露任意 URI、表名或 SQL。
查询协议只允许系统记忆、个人订单和已保存地点三种操作,请求与结果都有 UTF-8 字节上限。数据库层只查询预定义的表和字段SQLite 标识符统一引用,以兼容 `shipments.order` 等与 SQL 保留字重名的字段。结果继续按敏感工具处理,不写入持久会话。
进程内查询桥不可用时Runtime 才回退到 Root 快照路径:将主数据库及存在的 WAL、SHM 或 journal 边车文件限大复制到 Eta 缓存,用同一查询引擎只读打开,并在查询结束后立即删除。
## 配置与实时生效
模块 UI 基于 Miuix 0.9.3。配置链路如下:
设置页与标准二级页面的顶栏使用 Miuix `LayerBackdrop` 捕获滚动内容并生成背景模糊;设备不支持 RuntimeShader 时回退为主题 `surface` 纯色。标准设置列表统一启用 Miuix 越界滚动和滚动末端触感反馈。
- **Eta Runtime 配置**:默认思考、网页浏览、设备直达、敏感信息读取、敏感设备操作和终端/文件工具保存在 App 私有配置中,不依赖 LSPosed。Runtime 在请求开始和每次工具执行前读取当前值;升级时会兼容迁移已有 RemotePreferences 值。
- **Hook 配置**`FuckAndesApp``Application.onCreate` 注册 `XposedServiceHelper`,框架通过 `XposedProvider` 推送 binder 后拿到 `XposedService`。系统助手接管、厂商助手兼容等 Hook 开关通过 `XposedService.getRemotePreferences()` 写入 LSPosed 数据库;服务未连接时这些开关保持不可修改。
- **Hook 进程**`ModuleMain.onModuleLoaded` 调用 `XposedInterface.getRemotePreferences()` 缓存只读 `SharedPreferences``Prefs`。各 Hook 拦截回调入口直接读 `Prefs.isEnabled(key)`,关闭则走原逻辑;因此正常使用时,配置切换后的下一次相关触发表现为实时生效。这里的实时生效来自 Hook 入口读取当前配置,不是 libxposed API 102 的 hot reload 特性。
- **延迟任务复查**:已排队的后台配置修复会在执行前再次检查对应开关,避免用户在任务排队期间关闭开关后被旧任务绕过。
## 个人数据直达
个人数据检索复用“敏感设备信息读取”开关;模型调用时可读取原始结果,但工具参数与结果不会持久化进会话记录。每个能力都固定到已验证的 Provider、投影字段和排序方式只接受受长度限制的关键词与返回数量不向模型暴露任意 URI、表名或 SQL。
Runtime 提示要求模型在用户目标会明显受益于本机上下文时主动调用已公开的只读工具。对于“了解我”、近期活动、习惯偏好和个性化建议等宽泛任务,模型应从多个相关来源按时间与代表性取样后再归纳;工具已公开即表示对应能力已由用户开启,不重复询问授权,也不因单个来源为空就直接停止。专用工具不存在、结果不足或数据源不可用时,如果 Root Shell、文件或终端工具已公开模型会继续定位并只读检查相关应用私有文件与数据库先识别格式和 schema再执行有界查询不修改源数据。
- 标准 Android Provider相册图片、音频、共享文件、日历、通讯录、通话记录、短信和下载记录。
- ColorOS 数据源:通过固定 Provider 读取便签正文、待办、普通录音、通话录音与录音摘要;系统记忆优先由小布记忆进程内的只读 Hook 桥查询,再在桥不可用时回退到包含 SQLite 边车文件的 Root 临时快照。两条路径共用固定查询引擎,可检索记忆正文及其账单、日程、取件码、快递、地点和附件。
- 个人上下文:位置按需读取最近系统位置;应用活动与使用时长依赖用户授予的使用情况访问权;闹钟、计时器、输入法剪贴板历史和 Health Connect 聚合值通过固定数据库只读快照查询。健康工具只返回指定时间窗口的汇总,不返回原始测量序列。
- 通知历史系统自身没有可用历史时不伪造旧记录。用户授予通知使用权后Eta 从授权时点开始在独立本机数据库中保存标题、正文、来源包和时间,保留 7 天且最多 1000 条;查询结果仍按敏感工具规则从持久会话移除。
- 个人订单:优先检索系统记忆已经识别的外卖、购物、快递、票券和出行信息。第三方应用导出的进程通信 Provider 不等于订单查询合同Eta 不依赖其易变私有订单库。
- QQ 与微信专用目录:仅扫描已验证的聊天图片缓存目录,按最近修改时间返回有界的文件元数据;不扫描视频、消息数据库、消息正文或任意其他应用私有目录。
- 设备上缺少相应应用或 Provider 合同变动时,工具返回结构化不可用错误;不会改用遍历其他应用私有目录的方式猜测数据。
## 文件视觉
`read_image` 属于通用文件视觉能力,随“终端/文件工具”开关公开不依赖个人数据直达。它接受用户或其他工具已明确提供的任意本地绝对路径、file URI 或系统相册 URI本机路径由 Root 读取。Root 将单张、大小受限且非符号链接的文件复制到 Eta 临时缓存;发送给模型前会仅为视觉请求缩放压缩,以避免多张原图撑大 OpenAI 兼容请求体原始文件不会被修改。当前回合结束后立即删除临时文件。QQ/微信检索工具只负责提供可传入的图片路径。
运行时提示与工具描述共同要求模型每轮最多调用一次 `read_image`。需要查看多张图片时,模型必须先消费当前图片的视觉结果,再在下一轮读取下一张,避免同一请求携带多张工具图片导致部分 OpenAI 兼容服务长时间无响应。
## 内置浏览器
`browser_use` 是运行在 Eta 内的 Agent 浏览器,基于共享离屏 WebView不是简单调用系统 `ACTION_VIEW`。它可以在不抢占前台的情况下加载 JavaScript 网页、提取保留标题/段落/列表/链接等结构的正文、查找并操作页面元素、提交表单、滚动和截图;用户想查看过程时,可在 App 中挂载同一个 WebView 直接接管。外部打开链接仍由独立的 `open_uri` 工具负责,两种能力不会混淆。
Eta 不对浏览器请求执行额外的 URL、DNS、IP、主机数量、请求方法、重定向或 Service Worker 拦截,页面直接交给系统 WebView 加载。浏览器允许本地内容、混合内容、第三方 Cookie、自动媒体播放和表单提交系统 WebView 与 Android 平台自身的协议支持、TLS 校验和权限行为保持不变。网页工具可在设置中关闭。
## 终端与文件
在用户授权下执行 `user``root` shell 命令,读写文件、列目录、跑脚本、查日志、改配置。会话式 shell 保持 cwd 和环境变量,异步任务后台执行并分段读取输出。聊天中的终端工具卡片可展开查看并复制实际执行的完整命令;运行日志仍只记录长度等受控摘要,不记录命令正文。
终端按用途分为两个环境:
- `android` 是原生 Android Shell负责系统、应用、日志、Magisk 和设备文件操作。Root 会话会自动发现 Magisk、KernelSU 或 APatch 提供的 BusyBox并以 standalone `ash` 补齐不在系统 PATH 中的 applet。
- `linux` 是可选安装的 Alpine 工具环境,预装模型高频使用的 `rg``fd`、Git/SSH、diff/patch、curl、rsync、jq、SQLite、常用压缩工具与 Python 工具链。Eta 下载固定版本的官方 minirootfs 并校验 SHA-256在 App 私有目录中解压,通过独立 mount namespace + Root chroot 运行Linux 默认在映射到 Eta Android 工作目录的 `/workspace` 中执行,共享存储位于 `/sdcard`。它不是安全沙箱,也不会取代 Android 环境。
聊天输入栏可以引用内部存储与 `/data/local/tmp` 下的文件或文件夹发送后以附件名称和原始请求分开展示。Eta 只把经过 Root 校验的规范绝对路径写入模型上下文,不上传、不复制或缓存原文件;模型再按任务调用文件或终端工具读取。系统文件选择器会解析内部存储文档,以及能转换为本地媒体库路径的“最近”文件;云盘和其他只有 `content://` URI 的来源不会降级为上传。
## 长期记忆
跨对话长期记忆保存在 App 私有目录中的单一 `MEMORY.md`,不额外调用提取模型或运行后台整理任务。当前对话的主模型根据需要调用 `memory_get``memory_write`,负责去重、修正冲突和删除过期信息。
- **按需注入**:每轮只自动提供预算内的 `# 核心记忆`、标题索引和文件 revision详细章节由模型按任务检索不把整个文件反复塞进上下文
- **动态预算**:核心注入量根据当前模型上下文窗口计算;窗口未知时按 128K 处理,并始终为历史、工具、图片和回复预留空间
- **原子更新**:文件上限为 1 MiB UTF-8 字节,模型使用 revision 进行局部更新,冲突时必须重新读取;完整文件通过 `AtomicFile` 覆盖,失败保留旧内容
- **用户控制**:设置页可查看用量、编辑完整 Markdown、清空或关闭记忆关闭不会删除文件但 Runtime 会立即停止注入并拒绝新的记忆工具调用
## 会话级 Thinking Effort
聊天会话保存独立的 `ReasoningEffort`,输入栏按当前 Provider、端点和模型能力显示 `Thinking · Off / Default / Low / Medium / High / XHigh / Max` 的实际子集。模型不支持推理时不显示入口;强制推理且没有可调档位的模型只显示不可点击的 `Thinking · Default`。模型切换或远端能力刷新后,已保存但不再合法的档位会向下裁剪到最近的有效档位,没有可比档位时回到 `Default`
能力解析依次采用远端精确元数据、内置模型目录、Provider 与模型家族规则,最后安全降级。`Default` 保留供应商或高级自定义请求体的默认行为显式档位在请求体合并完成后应用因此会话选择是最终覆盖。Room、Runtime Bundle、RemotePreferences JSON 和外部归档同时保留旧 `thinkingEnabled` 布尔投影,旧 `true/false` 分别解释为 `Default/Off`;强制推理模型收到 `Off` 时直接报告配置错误。
## 聊天流式渲染
模型的 SSE 文本增量先在 App 状态层按 50 ms 合并,减少高频列表状态写入;思考、工具调用和块边界事件仍会立即刷新,事件顺序不变。聊天渲染使用增量 Markdown AST但不会把尚未显示的大段网络 backlog 一次性交给布局:解析器每次最多追加 12 个 Unicode 字素,批与批之间让出一拍供重组排版,供给节奏与显现速度解耦;消息高度始终由显现进度驱动,解析领先不会提前撑高回答。输入器使用 state-based `TextFieldState` 在组件内持有编辑缓冲区,逐字编辑不会回写聊天页面状态,也不经过旧版 `CoreTextField` 管线。
逐字效果由单个回答级帧时钟驱动。段落、标题、代码块和表格单元格先完成一次真实排版,再由 `DrawModifierNode` 按字素边界裁剪 `TextLayoutResult`;帧间推进只使绘制失效,不重建 Markdown AST、`AnnotatedString` 或文本布局。显现速度随积压自适应48240 字素/秒积压时允许单帧补多个字素避免输出稳定滞后于模型。行内语法闭合加粗、行内代码、链接折叠导致渲染文本变短时显现进度保持单调前进不回退重打已显示的文字。前缀路径按字素增量累计只有显现跨入新行时才触发一次测量并增加消息高度因此隐藏文本不会提前把当前回答顶出视口。Emoji ZWJ、肤色修饰、组合音标、国旗和代理对均作为完整字素显示不会从 UTF-16 中间断开。
底部跟随只在用户真实拖动时解除,并仅在消息实际增高、底部哨兵离开视口时滚动;纯网络状态更新不会反复触发滚动。网络结束、解析追平且显现队列排空后,回答切换到稳定 Markdown 状态恢复完整链接解析与文本选择。token 用量事件只更新用量字段,不触碰消息的流式标记,避免流式/静态视图在轮次边界反复切换。
## 功耗与开销
追求极简,绝不给系统增加额外负担:
- 不轮询、不保活 Google 进程、不持续写日志
- 无障碍保护默认关闭;开启后只响应设置、包、用户生命周期与 Runtime 明确上报,设置争抢采用退避,断连重绑有次数和冷却上限
- 热路径只保留当前机型实际验证有效的 `OplusSpeechHandler` hook
- 默认助理配置检查带 15 秒冷却
- 高频成功路径使用 `DEBUG`Debug 构建可诊断Release 由 R8 确定性裁剪
- 电源键拦截路径不执行休眠、轮询或阻塞等待;本次触发只做快速启动尝试,失败即回退系统原逻辑
- 默认助理修复异步执行并按用户串行,完成时重新核验 role、目标与开关状态
## 预期行为
电源键目标为小布时ColorOS 长按电源键保持厂商原始行为且不修改当前默认助理。目标为 Eta 且 Eta 已是默认数字助理时,长按会打开 edge-to-edge 全屏助理浮窗并自动聚焦键盘输入框;入口会在浮窗与 IME 出现前准备一张屏幕截图,只有用户选择后才作为下一条消息的图片上下文发送。用户提交文本后,工具执行、流式结果和归档仍由主进程中的 Agent Runtime 负责,当前流程不执行 ASR 或 TTS。
Eta 尚未成为默认助理且自动设置关闭时,按既定策略直接回到小布,不创建平行 Activity 会话。自动设置开启时,失败触发只在后台修复当前选择,当前长按仍立即回退;后续触发使用修复后的主路径。
配置界面按消费边界保存开关Agent 与本地工具写入 App 私有配置Hook 能力写入 LSPosed 侧 RemotePreferences。Hook 回调和延迟任务执行前都会读取对应开关,所以后续触发按当前配置执行。

View File

@@ -0,0 +1,74 @@
# 第三方声明
## Lobe Icons
模型与提供商品牌图标来自
[Lobe Icons](https://github.com/lobehub/lobe-icons) 的
`@lobehub/icons-static-avatar` 1.13.0。原始 1280×1280 WebP 素材在不改变颜色和比例的前提下,无损缩放为 128×128 后随 Eta 本地打包。
| Eta 资源 | Lobe Icons Avatar |
| --- | --- |
| `provider_logo_openai.webp` | `openai.webp` |
| `provider_logo_anthropic.webp` | `anthropic.webp` |
| `provider_logo_bailian.webp` | `bailian.webp` |
| `provider_logo_deepseek.webp` | `deepseek.webp` |
| `provider_logo_kimi.webp` | `kimi.webp` |
| `provider_logo_mimo.webp` | `xiaomimimo.webp` |
| `provider_logo_minimax.webp` | `minimax.webp` |
| `provider_logo_stepfun.webp` | `stepfun.webp` |
| `provider_logo_siliconflow.webp` | `siliconcloud.webp` |
| `provider_logo_openrouter.webp` | `openrouter.webp` |
| `model_logo_claude.webp` | `claude.webp` |
| `model_logo_qwen.webp` | `qwen.webp` |
| `model_logo_zai.webp` | `zai.webp` |
| `model_logo_chatglm.webp` | `chatglm.webp` |
| `model_logo_gemini.webp` | `gemini.webp` |
| `model_logo_gemma.webp` | `gemma.webp` |
| `model_logo_grok.webp` | `grok.webp` |
| `model_logo_meta.webp` | `meta.webp` |
| `model_logo_mistral.webp` | `mistral.webp` |
| `model_logo_doubao.webp` | `doubao.webp` |
| `model_logo_hunyuan.webp` | `hunyuan.webp` |
| `model_logo_yi.webp` | `yi.webp` |
Lobe Icons 使用 MIT License
```text
MIT License
Copyright (c) 2023 LobeHub
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
```
OpenAI、Anthropic、Claude、阿里云百炼、Qwen、DeepSeek、Kimi、Xiaomi MiMo、MiniMax、StepFun、Z.ai、ChatGLM、Gemini、Gemma、Grok、Meta、Mistral、豆包、混元、Yi、硅基流动和 OpenRouter 的名称、图标及其他品牌标识归各自权利人所有。Eta 展示这些图标仅用于准确标识用户正在配置的模型服务,不表示这些厂商对 Eta 的赞助、认可或合作关系。OpenAI 图标的使用还应遵循其[品牌规范](https://openai.com/brand/)。
## 可选 APK 分析工具
Eta 不把下列工具打包进 APK。用户在 Linux 工具环境页面主动安装“APK 分析”时Eta 从固定官方 Release 下载并校验制品;工具保存在 Eta 管理的 Alpine 环境中,适用各自许可证:
| 工具 | 来源 | 许可证 |
| --- | --- | --- |
| JADX | [skylot/jadx](https://github.com/skylot/jadx) | Apache License 2.0 |
| Apktool | [iBotPeaches/Apktool](https://github.com/iBotPeaches/Apktool) | Apache License 2.0 |
| smali / baksmali | [google/smali](https://github.com/google/smali) | BSD 3-Clause License |
JADX 的发行包许可证会随所需 CLI 文件一并保留Apktool、smali 与 baksmali 的许可证和第三方声明保留在各自 JAR 制品中。Eta 仅提供经过校验的安装、命令入口和能力边界,不对这些工具重新授权。
GitHub 的实际制品域名不可达时,安装器可能依次通过 `ghfast.top``gh-proxy.com` 请求同一个公开 Release URL。代理会获知用户的网络地址及所请求的公开制品Eta 不向代理发送账号、Cookie、API Key 或其他 Eta 数据,并在落盘前继续校验内置的官方制品大小与 SHA-256。不希望使用下载代理的用户可以不安装该可选档案。