环境
- Operit 1.12.1(源码 main @ fea1daa,2026-09-16)
- 设备:小米 marble / MIUI V14(Android 13)
- 场景:主 Agent 通过内置包 extended_chat 的
chat_with_agent 工具,让另一张角色卡(子代理)在独立会话中处理任务
现象
FloatingChatService 未运行时,AI 调用 chat_with_agent 会启动 FloatingChatService:
- 屏幕左上角出现 WINDOW 模式悬浮小窗(显示当前会话内容);
- 通知栏出现前台服务常驻通知「AI助手悬浮窗 / AI助手正在后台运行」;
- 用户关闭小窗后服务进程随窗销毁(onDestroy),下次调用
chat_with_agent 重复以上过程。
复现步骤
- 关闭悬浮窗(或等待系统回收后台),使 FloatingChatService 未运行;
- 对 AI 说「用子代理查一下 XX」,使其调用
chat_with_agent(不传 chat_id,触发新建会话路径);
- 观察:左上角弹出 WINDOW 小窗 + 常驻通知;日志中
FloatingChatService onCreate、模式 WINDOW。
根因分析(源码已全部定位,附 main 分支链接)
① chat_with_agent_impl 无条件启动服务(弹窗直接调用点)
app/src/main/assets/packages/extended_chat.js 约 L377:
await Tools.Chat.startService(); // 每次工具调用无条件执行
② 真正需要服务的只有 create_chat 工具的前置检查,而该检查并非必需
StandardChatManagerTool.kt 约 L1354-1362(createNewChat):
if (!ensureServiceConnected()) {
return ToolResult(..., error = "Service not connected")
}
但检查通过后实际执行的是 core.createNewChat()——core 来自 ChatServiceCore(纯 Kotlin 核心,与悬浮窗服务无绑定)。UI 路径(主界面手动新建对话)直接调用同一 core,没有此检查,服务未运行时也能正常创建,可见该前置检查并非技术必需。
③ 发送消息链路完全不依赖 FloatingChatService
同文件 sendMessageToAIStream 约 L1721-1736:
val core = chatRuntimeHolder.getCore(runtimeSlot ?: ChatRuntimeSlot.FLOATING)
...
// 后台发送到指定对话,不切换 UI
core.sendUserMessage(...)
ChatRuntimeHolder(api/chat/ChatRuntimeHolder.kt)的 core 独立于服务运行。实测验证:FloatingChatService 已销毁(onDestroy)后向既有会话发送消息,消息仍正常送达并得到子代理完整回复。
④ 服务一旦启动必然可见(无 headless 模式)
因此工具链路中任何一次 startService 都必然造成可见的 UI/通知。
完整因果链
chat_with_agent(不传 chat_id)→ ① 插件无条件 startService(弹窗根源)。即便移除 ①,新建会话仍会被 ② 的前置检查拒绝——这就是插件被迫无条件启动服务的原因。但 ③ 证明消息收发无需服务,② 的检查又是非必需的:整条工具链路完全可以不触碰 FloatingChatService。
期望行为
工具路径(chat_with_agent / create_chat / send_message_to_ai)在服务未运行时也能完成「新建会话 + 发送消息 + 等待回复」全流程,不启动 FloatingChatService、不产生任何可见 UI/通知(与 UI 路径行为对齐)。
修复建议(任一即可,组合更佳)
create_chat 工具移除 ensureServiceConnected() 前置检查,改用 chatRuntimeHolder.getCore(...) 获取 core(与 UI 路径一致);
- 修复 1 落地后,
chat_with_agent_impl 可安全移除无条件 startService();
- 可选:为 FloatingChatService 提供 headless 启动参数(如
INITIAL_MODE=NONE),供程序化调用使用。
附:验证日志(2026-09-17,本机)
- 06:23:32 调用 chat_with_agent →
FloatingChatService onCreate,以 WINDOW 模式启动;
- 06:31:58 手动关闭小窗 →
WakeLock released → onDestroy(服务销毁);
- 服务销毁后,同会话的子代理回复照常完成(证明 ③:发送链路不依赖服务)。
环境
chat_with_agent工具,让另一张角色卡(子代理)在独立会话中处理任务现象
FloatingChatService 未运行时,AI 调用
chat_with_agent会启动 FloatingChatService:chat_with_agent重复以上过程。复现步骤
chat_with_agent(不传 chat_id,触发新建会话路径);FloatingChatService onCreate、模式WINDOW。根因分析(源码已全部定位,附 main 分支链接)
① chat_with_agent_impl 无条件启动服务(弹窗直接调用点)
app/src/main/assets/packages/extended_chat.js约 L377:② 真正需要服务的只有 create_chat 工具的前置检查,而该检查并非必需
StandardChatManagerTool.kt约 L1354-1362(createNewChat):但检查通过后实际执行的是
core.createNewChat()——core 来自 ChatServiceCore(纯 Kotlin 核心,与悬浮窗服务无绑定)。UI 路径(主界面手动新建对话)直接调用同一 core,没有此检查,服务未运行时也能正常创建,可见该前置检查并非技术必需。③ 发送消息链路完全不依赖 FloatingChatService
同文件
sendMessageToAIStream约 L1721-1736:ChatRuntimeHolder(
api/chat/ChatRuntimeHolder.kt)的 core 独立于服务运行。实测验证:FloatingChatService 已销毁(onDestroy)后向既有会话发送消息,消息仍正常送达并得到子代理完整回复。④ 服务一旦启动必然可见(无 headless 模式)
ui/floating/FloatingMode.ktL3-10:六种模式(WINDOW/BALL/VOICE_BALL/FULLSCREEN/RESULT_DISPLAY/SCREEN_OCR)全部带 UI;因此工具链路中任何一次
startService都必然造成可见的 UI/通知。完整因果链
chat_with_agent(不传 chat_id)→ ① 插件无条件 startService(弹窗根源)。即便移除 ①,新建会话仍会被 ② 的前置检查拒绝——这就是插件被迫无条件启动服务的原因。但 ③ 证明消息收发无需服务,② 的检查又是非必需的:整条工具链路完全可以不触碰 FloatingChatService。期望行为
工具路径(
chat_with_agent/create_chat/send_message_to_ai)在服务未运行时也能完成「新建会话 + 发送消息 + 等待回复」全流程,不启动 FloatingChatService、不产生任何可见 UI/通知(与 UI 路径行为对齐)。修复建议(任一即可,组合更佳)
create_chat工具移除ensureServiceConnected()前置检查,改用chatRuntimeHolder.getCore(...)获取 core(与 UI 路径一致);chat_with_agent_impl可安全移除无条件startService();INITIAL_MODE=NONE),供程序化调用使用。附:验证日志(2026-09-17,本机)
FloatingChatService onCreate,以 WINDOW 模式启动;WakeLock released→onDestroy(服务销毁);