screen_monitor 是为 Neo-MoFox 定制的屏幕上下文感知插件。它可以在后台观察用户屏幕变化,通过截图或短视频调用视觉模型分析当前活动,并将近期观测整理成可被工具调用的摘要。
当前版本采用 窗口切换触发 + 冷却控制 + 周期观察 + 低变化自动停止 的保守消费模型,目标是在提供屏幕上下文的同时尽量减少 VLM 调用和 token 消耗。
- 窗口变化触发:后台轮询当前活动窗口标题,检测到窗口切换后触发首次屏幕分析。
- 冷却保护:通过
monitor.cooldown_seconds限制两次首次触发之间的最小间隔,避免频繁调用视觉模型。 - 周期观察模式:首次分析后进入周期模式,按
monitor.periodic_interval_seconds持续捕获屏幕。 - 低变化自动停止:画面连续多次变化低于阈值时自动退出周期模式,回到待机状态。
- 截图/视频双模式:支持单帧截图分析,也支持短视频录制后调用视频理解模型。
- 局部变化检测:差异检测采用分块最大值评分,角落通知、局部弹窗等变化更容易被捕捉。
- 观测缓存与状态落盘:最近观测保存在进程内缓存,并将最新分析状态写入
data/screen_monitor,便于排查和后续恢复能力扩展。 - 工具可读摘要:提供
get_screen_observation工具,供 LLM 查询当前屏幕活动和近期变化轨迹。 - 敏感信息模糊化提示:默认提示词要求模型不要转写账号、密码、验证码、证件号、手机号、地址、付款码等敏感内容。
这是一个第三方插件,不建议为了安装插件依赖去修改主项目的 pyproject.toml。插件所需 Python 包应优先写在 manifest.json 的 python_dependencies 中,由 Neo-MoFox 的插件依赖解析器按 config/core.toml 的 plugin_deps.install_command 自动安装。
当前声明的插件依赖为:
"python_dependencies": [
"mss",
"pillow",
"httpx"
]如果关闭了插件依赖自动安装,或需要手动排查依赖问题,可以在当前项目环境中执行:
uv pip install mss pillow httpx不推荐使用
uv add安装第三方插件依赖,因为它会把依赖写入主项目依赖清单,更适合内置功能或核心项目依赖。
- 截图模式依赖
mss,当前实现主要面向桌面环境。 - 窗口切换监听使用 Windows API:
ctypes.windll.user32。 - 视频模式依赖系统已安装
ffmpeg,并且当前录制实现使用 Windows 的gdigrab设备。
FFmpeg 获取入口:
- 官方下载页:ffmpeg.org/download.html
- 官方源码镜像:github.com/FFmpeg/FFmpeg
- Windows 预编译包参考:github.com/BtbN/FFmpeg-Builds/releases
FFmpeg 官方主要提供源码;普通 Windows 用户通常不需要克隆源码仓库自行编译,下载预编译包并把
bin目录加入PATH更直接。
检查 ffmpeg 是否可用:
ffmpeg -version注意:当前完整后台窗口监听与视频录制流程主要面向 Windows。非 Windows 环境建议先关闭插件,或仅在确认截图能力可用后再做适配。
插件启动后,如果 monitor.enabled = true,会创建 ScreenMonitorService 并启动窗口观察器。
整体流程如下:
flowchart TD
A[插件加载] --> B{monitor.enabled?}
B -- 否 --> C[记录禁用日志]
B -- 是 --> D[启动窗口观察器]
D --> E[轮询活动窗口标题]
E --> G{窗口标题变化?}
G -- 否 --> E
G -- 是 --> H[防抖确认]
H --> I{冷却结束且非周期模式?}
I -- 否 --> E
I -- 是 --> J[执行首次截图/录屏分析]
J --> K[进入周期模式]
K --> L[按间隔捕获屏幕]
L --> M{连续低变化达到阈值?}
M -- 是 --> N[退出周期模式]
M -- 否 --> O[VLM/视频模型分析]
O --> P[写入缓存和状态落盘]
P --> L
服务初始状态为 IDLE。此时插件只观察当前活动窗口标题变化,不持续截图。
当检测到窗口标题变化后,会等待 monitor.event_debounce_seconds 秒做防抖确认。如果确认发生切换,并且不在冷却期内,则触发一次屏幕分析。
首次分析后,服务进入 PERIODIC 状态。此时插件会每隔 monitor.periodic_interval_seconds 秒进行一次截图或录屏分析。
如果画面连续 monitor.terminate_after_low_change_count 次低于 monitor.diff_threshold,说明屏幕基本稳定,插件会退出周期模式并回到待机。
LLM 调用 get_screen_observation 工具时,video 模式会即时录屏并分析,确保拿到最新一段操作变化;screenshot 模式才会优先读取 tool.cache_reuse_seconds 时间窗口内的缓存观测。若截图缓存不存在或过旧,则即时截图并分析当前屏幕。
随后工具会读取最近若干条有效观测,并使用 model.summary_task 对近期活动进行整合摘要。工具结果会按 tool.status_detail_level 追加可选状态块,帮助用户确认本次来源是缓存还是新捕获、请求模式和实际链路是否一致、缓存是否仍有效。
当前插件的基础体验已经比较完整:后台窗口变化监听、截图/录屏分析、进程内观测缓存、最新状态落盘和工具读取都已经形成闭环。插件更适合承担“近期屏幕上下文感知”的角色,而不是单纯作为一次性的截图工具。
从日常使用角度看,当前体验特点如下:
- 视频模式体验最佳:
video能观察到短时间内的变化过程,适合理解“用户刚刚怎么操作到当前状态”;当 ffmpeg 或视频接口不可用时,插件会明确提示并回退到screenshot链路。 - 截图模式稳定兜底:
screenshot链路更轻、更通用,适合作为视频依赖不可用时的可靠后备。 - 后台打扰较少:窗口切换触发、冷却时间、周期观察和低变化自动停止能减少频繁模型调用,避免一直截图或持续消耗 token。
- 运行容错较好:不支持 Windows 活动窗口 API 的环境会跳过后台窗口监听;窗口标题读取、锁屏检测、ffmpeg 检测和视频压缩失败都会尽量安全返回,不让后台任务轻易崩掉。
- 第三方依赖边界清晰:Python 依赖声明在
manifest.json,手动兜底使用uv pip install,避免把第三方插件依赖写入主项目依赖清单。 - 专项测试已覆盖主要路径:当前插件专项测试覆盖截图、视频捕获、缓存、工具和服务流程,能帮助避免小改破坏主链路。
仍然会影响体验的地方主要集中在“首次配置”和“视频 provider 兼容性”:
- 首次配置门槛偏高:依赖、模型任务、桌面捕获权限、ffmpeg 和 provider 视频格式任一环节异常,都可能表现为“插件没观察到东西”。
- 视频链路依赖更多:视频录制依赖系统 ffmpeg 与 Windows
gdigrab,视频请求还依赖供应商兼容video_url入参格式。插件会做 ffmpeg 检查并保留最近错误,便于定位。 - 状态可解释性已增强:工具返回可按
off/basic/detailed控制状态颗粒度;默认basic会显示来源、捕获链路和最近观测缓存情况。 - 视频直连接口仍需关注 provider 差异:
_video_api.py仍绕过框架标准LLMRequest,但外部 API 错误响应日志已截断,避免大段响应刷屏。
后续如果继续小步优化,建议优先级为:
- 继续适配更多供应商的视频入参格式,让
video模式更稳。 - 在配置 UI 中进一步解释 ffmpeg、provider、视频模型任务之间的关系。
- 增加更明确的后台健康检查入口,例如一次性输出截图链路、视频链路、模型任务和缓存状态。
推荐在插件配置中使用如下结构:
[monitor]
enabled = true
retention_seconds = 7200
save_screenshot = false
monitor_index = 1
image_max_width = 1024
image_max_height = 1024
jpeg_quality = 80
diff_threshold = 2.0
diff_grid_size = 6
log_enabled = true
# 窗口切换与状态机控制
event_debounce_seconds = 2
cooldown_seconds = 600
periodic_interval_seconds = 180
terminate_after_low_change_count = 2
# 捕获模式:screenshot 或 video;最佳体验建议 video,依赖不可用时会回退 screenshot
capture_mode = "video"
# 视频模式配置,仅 capture_mode = "video" 时使用
video_duration = 5.0
video_fps = 5
video_compress_threshold_mb = 8.0
# 截图分析提示词
prompt = """
请理解这张屏幕截图,并总结用户当前正在做什么、关注什么、所处的场景状态。
重点关注:正在使用的程序或网页、可见文字主题、当前操作焦点、可能的意图或上下文。
请输出一段适合后续摘要参考的简洁中文描述,偏向“用户现在在做什么”而不是逐项念图。
不要输出过多琐碎 UI 细节,不要机械罗列控件。
如果画面中可能含有账号、密码、验证码、身份证号、手机号、家庭住址、付款码等敏感信息,
不要转写具体内容,只需模糊化描述。
"""
# 视频分析提示词
video_prompt = """
以下是一段屏幕录制视频,记录了用户在这段时间内的屏幕操作。
请观察视频中的变化,结合时间维度分析:正在使用的程序或网页、可见文字主题、
操作焦点变化、可能的意图或上下文。
请输出一段简洁中文描述,偏向“用户现在在做什么”而不是逐项念图。
不要输出过多琐碎 UI 细节。如果含有敏感信息,只需模糊化描述。
"""
[model.screenshot]
model_task = "vlm"
models = []
temperature = 0.3
max_tokens = 800
[model.video]
model_task = "video"
models = []
temperature = 0.3
max_tokens = 1500
[model]
summary_task = "utils"
recent_count = 5
summary_prompt = """
你是一个屏幕观察摘要助手。请根据以下屏幕观察信息,生成一段简洁的整合摘要,帮助理解用户近期活动和时间跨度。
要求:
1. 按时间顺序总结这段时间里发生了什么变化,并明确写出关键时间点或时间范围
2. 先写变化轨迹,再写当前正在做什么
3. 用2-3句话描述,突出阶段变化、连续性、起止时间和当前所处阶段,而不是只写单帧画面
请输出整合摘要:
"""
[tool]
get_observation_description = """
查看用户近期屏幕观察状态。
调用此工具可以获取用户近期屏幕观察的整合摘要,适用于需要了解用户当前屏幕状态或近期活动的场景。
返回:整合后的屏幕观察摘要,包含当前活动和近期状态概述。
"""
cache_reuse_seconds = 30
status_detail_level = "basic"
[debug]
log_tool_result = false捕获模式:
screenshot:截图模式,使用框架标准图片 VLM 流程。video:短视频模式,使用 ffmpeg 录制屏幕,并通过视频理解接口分析。它能保留操作过程,是本插件的最佳体验模式。
视频模式下如果 ffmpeg 不可用,插件会回退到截图模式。
截图模式下,工具调用复用最近观测缓存的秒数。默认 30 秒。
video 模式不使用这个 30 秒短期复用窗口;每次工具调用都会重新录制一小段视频,这样才能体现 video 模式“看见过程变化”的最佳体验。
- 值越大:更省模型调用,结果更稳定,但可能不够“此刻”。
- 值越小:更频繁即时捕获,更新更及时,但消耗更高。
- 设为
0:截图模式下也每次工具调用都重新捕获。
工具返回结果中追加状态信息的颗粒度:
off:只返回屏幕摘要,不追加状态块。basic:默认值,追加来源、捕获模式、最近观测和缓存剩余时间。detailed:在basic基础上追加最近错误、参考观测条数、工具缓存策略和 video 最优体验提示。
当 capture_mode = "video" 但实际走了 screenshot,状态块会明确显示“请求 video,实际 screenshot”,方便快速判断双链路是否正常。
窗口切换触发首次分析后的冷却时间。冷却期内新的窗口切换不会再次触发首次分析。
默认值为 600 秒,即 10 分钟。
进入周期模式后,两次捕获之间的间隔。
默认值为 180 秒,即 3 分钟。
画面变化阈值。值越小越敏感,越容易触发分析;值越大越容易跳过变化较小的画面。
差异检测分块数量。插件会取变化最明显区域的平均差异值作为分数。
- 较小:更稳定,不容易误触发。
- 较大:更敏感,适合捕捉局部弹窗、角落通知等变化。
周期模式中,连续多少次画面变化低于阈值后退出周期模式。
默认值为 2。
观测结果保留时间。过期观测不会继续参与工具摘要。
默认值为 7200 秒,即 2 小时。
截图和视频可以使用不同模型配置:
models非空时,按模型名列表解析模型,并覆盖model_task。models为空时,使用model_task从config/model.toml中查找任务对应模型。
工具整合近期观测时使用的摘要模型任务。默认是 utils。
如果找不到可用摘要模型,工具会直接返回当前屏幕活动,不再做二次整合。
插件使用两层存储:
- 进程内缓存:
ScreenObservationStore,最多保留 50 条近期观测,供工具快速读取。 - 磁盘状态落盘:
data/screen_monitor/latest_status,保存最新未过期观测文本,主要用于排查、审计和后续恢复能力扩展;当前工具热路径以进程内缓存为准。
如果启用 monitor.save_screenshot = true,截图或视频会保存到:
data/screenshots
该目录最多保留 100 个 screen_monitor_* 文件,超出后会删除最旧文件。
插件提供工具组件:
get_screen_observation
用途:让 LLM 查询用户近期屏幕观察状态。
工具执行时:
- 检查插件是否启用。
- 如果是
video模式,直接即时录屏并分析。 - 如果是
screenshot模式,优先复用tool.cache_reuse_seconds时间窗口内的最新缓存;缓存过旧时即时截图并分析。 - 将当前分析结果写入缓存。
- 读取最近
recent_count条未过期观测。 - 使用
model.summary_task生成近期活动整合摘要。 - 按
tool.status_detail_level追加可选状态块。
可选参数:
recent_count:读取最近观测条数;为空时使用model.recent_count。
视频模式目前会绕过框架标准 LLMRequest 视频载荷,直接调用 OpenAI 兼容的 /chat/completions 接口。
原因是当前框架的视频类型序列化存在兼容问题,插件临时使用直连方式构造:
{
"type": "video_url",
"video_url": {
"url": "data:video/mp4;base64,...",
"fps": 5,
"max_frames": 25
}
}因此视频模式需要注意:
- 供应商必须兼容上述视频请求格式。
config/model.toml中的视频模型必须配置有效的api_provider。- 视频任务默认使用
model.video.model_task = "video"。 - 如果不同供应商的视频入参格式不同,可能需要调整
_video_api.py。 - 如果 ffmpeg 不可用,工具状态会显示 video 请求已回退到 screenshot 链路。
- 默认不保存截图;除非需要调试,否则建议保持
monitor.save_screenshot = false。 - 如需保存截图或视频,请注意
data/screenshots可能包含敏感屏幕内容。 - 默认提示词已要求模型模糊化敏感信息,但这不能替代真正的数据脱敏。
- 不要在日志或配置中提交明文 API key。
- 视频模式直连 API 时会读取模型 provider 的 API key,请确保相关配置文件不被提交到公开仓库。
检查:
monitor.enabled是否为true。- 是否仍在
monitor.cooldown_seconds冷却期内。 - 当前是否处于锁屏状态。
- 日志中是否有
[Screen Monitor]相关错误。
可能原因:
- 当前环境没有可访问的桌面会话。
mss无法捕获屏幕。- 显示器索引配置不正确。
可尝试:
- 将
monitor.monitor_index设置为1。 - 如果多显示器环境异常,可尝试设置为
0捕获全部显示器。
检查:
ffmpeg -version同时确认:
monitor.capture_mode = "video"。model.video.model_task或model.video.models已正确配置。- 对应 provider 支持视频输入。
- 日志中是否出现
Video API error或Video capture failed。
检查 config/model.toml 中是否存在:
- 截图任务:
vlm - 视频任务:
video - 摘要任务:
utils
也可以在插件配置中显式填写 model.screenshot.models 或 model.video.models。
plugin.py:插件入口和生命周期管理。config.py:插件配置定义。service.py:后台窗口监听、状态机、周期捕获与分析。_capture.py:截图、录屏和模型解析工具。_video_api.py:视频模式直连 API 请求。tool.py:get_screen_observation工具实现。observation_store.py:进程内观测缓存。storage.py:磁盘持久化存储。event_handler.py:启动事件日志处理。