插件 API v1
这里列出插件可以依赖的 v1 接口。没列出来的内部模块可能调整,插件不要直接 import。参数编辑器和插件页面另见 plugin-extension-api.md。
NotmyFault 插件 API 版本是整数,当前是 1(notmyfault/plugin_api.py 的 HOST_API_VERSION)。清单可以声明依赖:
"engines": {"notmyfault_api": 1}版本不同时,引擎会跳过插件并把原因写进诊断页。不声明则不检查版本。
清单(action.json / trigger.json)
公共必填:id、name、description、enabled、version_code、version、package_name。
常用可选字段:
| 字段 | 说明 |
|---|---|
| params | 参数定义,type 见 plugin_schema._ALLOWED_PARAM_TYPES |
| permissions | native_api / external_binary / admin 等 |
| platforms / entrypoints | 平台限制与分平台入口 |
| requires_capabilities | 依赖的系统能力,id 见 notmyfault/platform/capabilities.py |
| engines | NotmyFault 插件 API 版本声明 |
| contributes | commands / views / parameter_editors / data_types 四种贡献 |
action 专属:
| 字段 | 说明 |
|---|---|
| execution_api | 必填,值为 "context-v1" |
| cancellation_api | 写 "runtime-v1" 才允许规则配 timeout_seconds |
| idempotent | true 表示可安全重试,false 表示重复执行可能产生额外结果 |
| execution_mode | "isolated" 是实验字段,动作在子进程里跑(见下文) |
trigger 专属:trigger_api(必填,值为 "event-v2")、semantic(state / oneshot)。
触发器还可声明以下字段:
| 字段 | 取值与限制 |
|---|---|
| sampling_api | 可选 "poll-v1";必须同时提供 create_sampler(context),加载时仍检查四参数 run 入口 |
| lifecycle | "continuous" 或 "one_shot",默认 "continuous" |
| event_queue_size | 1–10000 的整数,默认 200 |
| overflow | "drop_oldest" 或 "coalesce",默认 "drop_oldest";后者须声明 coalesce_key 并引用已声明的输出字段 |
| debounce_seconds | 0–3600 秒,默认 0 |
| state_key | semantic: "state" 时必填,引用已声明的输出字段 |
| state_mode | "edge" 或 "level",默认 "edge" |
state_filter、state_initial_value、state_partition_key、state_untracked、 state_hysteresis 和 previous_state_output 的约束见 notmyfault/security/plugin_schema.py。
结构校验都在 notmyfault/security/plugin_schema.py,校验失败的原因会进诊断页。
内置动作显式声明 idempotent。同一插件包含切换、追加等操作时,按整个插件的 行为声明为 false。该字段用于重试提示,不阻止用户配置重试。触发器不使用此字段。
动作
动作入口
def run_with_context(action_info, params, context):
return {"ok": True}返回值会进规则上下文,供后续步骤 $ref 引用。抛异常表示失败。 仓库例子:notmyfault/actions/set_volume/action.py。
引擎在调用动作前解析常量、变量与其他步骤引用。动作入口接收解析后的 params,不需要在插件内再次解析表达式。参数和输出的 value_type、显式转换与 自定义类型使用方法见 data-types.md;插件通过 notmyfault.plugin_api.data_types_api() 访问类型校验和转换。
工作流传给动作的上下文包括当前事件、前面步骤的结果和规则信息。结构见 notmyfault/core/workflow.py 的 build_context。动作模块缺少 run_with_context 时,加载器拒绝加载该插件。
cancellation_api=runtime-v1(稳定)
声明后规则可以给这一步配 timeout_seconds。时限包含该动作的全部执行尝试和重试等待, 到期后结束当前动作,不再发起重试。引擎把取消事件放进 context,动作里的长循环要周期检查:
cancel = context.get("runtime", {}).get("cancellation")
if cancel is not None:
cancel.raise_if_cancelled()辅助类在 notmyfault/core/workflow.py(ActionCancelled、ActionCancellation)。 没有这个声明的动作配了超时,规则校验会报错(rules.py)。
触发器
触发器入口
from notmyfault.triggers.base import PollingTrigger
class MyTrigger(PollingTrigger):
interval = 5.0
def validate(self): ... # 配置非法抛 ValueError
def setup(self): ... # 一次性初始化
def poll(self): ... # 每轮采样,self.emit({...}) 发事件
def teardown(self): ... # 退出时释放热键、连接等资源
def run(meta, config, emit_event, shutdown_event):
MyTrigger(meta, config, emit_event, shutdown_event).run()基类负责轮询、异常隔离和退出清理,文档写在 notmyfault/triggers/base.py 的模块 docstring。仓库例子: notmyfault/triggers/window_title/trigger.py。
引擎逐项比较配置中的键值与事件 payload,区分布尔值与数字,嵌套数组和对象 也适用。数值相同的整数、浮点数和精确小数仍可匹配;不匹配的事件不重置 NOT 等待计时。
贡献(contributes,稳定)
四种贡献都保存在 notmyfault/extensions/registry.py:
- commands:由参数编辑器或页面调用的处理函数
- views:插件自带的 HTML 页面,路径必须位于插件目录内
- parameter_editors:为普通参数或
plugin_data参数提供编辑入口 - data_types:插件私有或共享数据的结构、类型和版本
普通参数编辑器声明 value_type,命令用 context.commit_value() 提交新值。自有数据 编辑器声明 data_type,命令用 context.commit() 提交带归属信息的值。完整协议见 plugin-extension-api.md。数据类型的版本属于值的 $type 标识。宿主不转换已保存的值;更改类型 id 或版本时,插件自行处理已保存的值。
平台服务(稳定)
第三方插件从 notmyfault.plugin_api 取得平台服务,不要直接 import notmyfault.platform.linux_support 或 notmyfault.platform.backends:
from notmyfault.plugin_api import platform_services
def run_with_context(action_info, params, context):
services = platform_services()
services.write_clipboard(str(params.get("text", "")))
return {"backend": services.capability("clipboard.write").backend}capability() 返回 CapabilityStatus,字段是 id、available、backend、 reason 和 degraded。这里的 available 表示公开服务能够调用该能力,系统存在 同类能力不等于宿主已提供对应公开方法。Windows 的内置插件仍使用系统能力探测。除 screen.ocr 外,尚未开放的公开服务返回 available=False,调用时报 unsupported。 Linux 平台服务直接提供这些方法:
- clipboard.read:
read_clipboard(max_chars=None);正整数max_chars限制返回字符数及命令输出读取量 - clipboard.write:
write_clipboard(text) - input.send:
type_text(text)、send_hotkey(parts) - audio.control:
set_volume(percent)、set_mute(muted) - audio.device_query:
default_audio_devices()、set_default_audio_device(flow, device_id) - window.pin:
set_window_pinned(action, target, title) - display.brightness:
set_brightness(percent) - screen.capture:
capture_screen(output_path, mode, fmt) - screen.ocr:
recognize_image(image_path, language),Windows 也提供该方法
recognize_image 调用本机 tesseract 识别已有图片,不负责截图。language 是 tesseract 语言代码,例如 eng 或 chi_sim+eng。tesseract 不在 PATH 上时为 unavailable。
能力缺失、能力 id 不存在、权限拒绝或命令失败都会抛 PlatformServiceError。 kind 会给出 unavailable、unsupported、backend_missing、permission_denied 或 backend_failed。插件不需要 import 内部 backend 的类或异常类型。
引擎事件订阅(稳定)
插件可订阅引擎运行事件,不必轮询:
from notmyfault.plugin_api import engine_events_api
def run(meta, config, emit_event, shutdown_event):
api = engine_events_api()
def on_event(event):
if event.type == "run.succeeded":
emit_event({"status": "succeeded"})
token = api.subscribe(on_event)
try:
shutdown_event.wait()
finally:
api.unsubscribe(token)engine_events_api() 提供 subscribe(callback)、unsubscribe(token) 和 RULE_TERMINAL_EVENTS。回调接收单个 Event 对象,可读取 type、data、run_id、 rule_id 和 step_id。每个回调订阅由独立的 EventSubscriber 线程按队列顺序处理; 回调异常会记日志,处理线程随后继续消费事件。插件退出时在 finally 中取消订阅。
规则运行结束时可订阅的事件名见 RULE_TERMINAL_EVENTS:
| 事件 | 含义 |
|---|---|
run.succeeded | 流程成功完成 |
run.recovered | 原动作失败后补救成功,并按停止设置结束 |
run.failed | 发生未处理失败或补救失败 |
run.cancelled | 运行被取消 |
run.timed_out | 运行超时 |
run.dropped | 运行被丢弃;替换原因记录在事件数据中 |
实现位于 notmyfault/core/engine_events.py。仓库例子: notmyfault/triggers/rule_finished/trigger.py。
插件私有数据(plugin_data,稳定)
参数 type 可以写 plugin_data。值由 parameter_editor 的命令提交。规则校验只接受 package_name、数据类型 id 和版本都匹配的自有数据信封。
plugin_data 参数允许声明 sensitive: true。运行记录会显示“敏感值已隐藏”; 规则编辑器也不会展示摘要。未标记敏感时,编辑器会显示信封中的 summary。用户打开 参数编辑器时,NotmyFault 会把完整值交回拥有该数据类型的插件;规则不能将这项数据绑定到其他 触发器或动作输出。
隔离执行(execution_mode: isolated,experimental)
manifest 写 "execution_mode": "isolated" 的动作不会在引擎进程中导入。每次执行会启动 子进程调用 run_with_context();父子进程之间使用 JSON 标准输入和输出, 新宿主使用 typed-v1 编解码保留大整数、Decimal 和二进制等值。 子进程先发 {"type":"ready","protocol":1},父进程再发送 entry、action_info、 params、最小 context 和整棵插件目录的文件快照。worker 每次运行前复核文件快照, 入口、兄弟模块和扩展命令使用校验时读取的源码;目录内容变化时拒绝执行。 执行结果的 type 是 result,无法通过数据编解码的返回值按动作失败处理。 这只隔离崩溃,不是安全沙箱,也不限制文件、网络、进程或系统调用权限。worker 异常退出时,引擎发布 plugin.worker.crashed 事件,规则中的该步骤失败。默认启动等待为 10 秒,动作执行等待为 120 秒,进程退出等待为 5 秒。 实现位于 notmyfault/core/plugin_worker.py。内置和用户动作均可声明 isolated; 隔离模式不能声明 cancellation_api 或 admin 权限。调用原生库或 ctypes 的第三方动作优先使用此模式, 原生代码仍拥有当前用户的文件、网络和系统调用权限。
隔离动作的扩展命令也在子进程中运行。每个扩展会话使用独立的子进程,同一会话 中的命令共享模块和 context.session.data,与规则执行动作的子进程分开。 每次调用命令前复核文件快照;普通值和自有数据信封使用 typed-v1 编码,状态文字 传回宿主。 关闭会话或停止引擎时,子进程运行 context.register_cleanup() 注册的函数后退出; 未在 5 秒内退出时终止进程。命令执行等待为 120 秒。
引擎启动时,存在保留备份的更新版隔离动作会在 worker 子进程中检查导入、执行入口 参数和全部扩展命令入口。 检查会复核文件快照,但不调用 run_with_context() 或参数校验函数。 导入失败、缺少可调用入口或入口参数不兼容时,启动流程尝试恢复旧版;正常动作执行 仍在每次运行时单独启动子进程。
内置 set_volume 使用 isolated,Windows 的 pycaw COM 调用在子进程中执行。 该动作不声明 cancellation_api,规则不能为它配置协作取消超时。
允许 import 的模块
第三方插件可以 import:
- notmyfault.plugin_api(平台服务、引擎事件订阅、公开错误、能力状态、HOST_API_VERSION)
- notmyfault.triggers.base(PollingTrigger)
- notmyfault.core.workflow(ActionCancelled、ActionCancellation)
- notmyfault.security.sudo(run_as_admin,需声明 admin 权限)
- notmyfault.security.plugin_resources(plugin_resource,读插件自带资源)
- notmyfault.platform.platform_support(show_notification 等 NotmyFault 服务)
除此之外的 notmyfault.* 都是引擎内部实现,不属于稳定插件 API;其中 notmyfault.core.engine、notmyfault.core.trigger_host、 notmyfault.core.workflow_executor、notmyfault.host.*、notmyfault.platform.linux_support 和 notmyfault.platform.backends 明确不要引用。 安全扫描目前只拦危险调用和动态导入,不检查 import 了哪些内部模块, 越界引用不会被扫描器拦下来,但引擎改内部结构时插件会跟着坏。
插件内的 Python 兄弟模块在各自插件的模块名称下加载。import helper 和包内相对 导入可以使用;两个插件包含同名 helper.py 时分别取得自己的模块。插件目录不会 加入全局 sys.path,卸载时清理该插件创建的模块。已验证插件的兄弟模块和扩展命令模块 可使用该插件声明的管理员权限,授权检查绑定实际模块对象。
run_as_admin(..., wait=False) 返回 subprocess.Popen,调用方可用 poll() 或 wait() 检查提权启动程序的状态。wait=True 继续返回 subprocess.CompletedProcess。内置插件也使用包内相对导入, 兄弟模块执行的是验签时读取的源码。直接导入 notmyfault.actions.* 或 notmyfault.triggers.* 会作为跨插件引用报告。
plugin_resource 绑定到当前插件的加载器,返回该加载器内已加载插件的资源路径; 不同加载器使用相同插件 ID 时各自解析自己的目录。
插件包签名与安装
pack_plugin.py 保留 signature.sig、public_key.pem 和 public_key.sig。 安装构建命令也保留这些文件,作者签名必须与构建后的文件内容相符。有构建命令时 先校验插件签名,签名无效则不执行命令。安装端在替换旧版本前使用与加载器相同的 签名规则校验暂存目录;strict 要求有效签名,作者自签的非管理员插件还需本地密钥 副签,管理员插件仅接受 official 签名。
签名覆盖插件源码、资源以及 node_modules、__pypackages__ 内的文件,排除 Python 的 __pycache__ 缓存与签名材料本身。修改依赖内容也需要重新签名。 动作与触发器共用插件 ID 空间,安装预览与安装会检查另一种类型中的同名 ID。 插件文件上传上限为 64 MiB,归档实际解压字节总数上限为 500 MiB。 安装预览返回 installation.required_risk_ids;安装的 confirmed_risk_ids 必须包含 这些 ID,遗漏时返回 HTTP 400 和 risk_confirmation_required。构建命令遗漏确认时 继续使用 build_hook_confirmation_required。 签名结果为 official、author 或 none,插件元数据中的 signature_kind 保存该结果。用户插件的已安装文件清单与文件内容逐项核对。安装构建后的检查和延迟导入前的复查分别执行。
安全扫描的边界
inspect_plugin 汇总插件检查结果,源码由 analyze_plugin_source 解析。能力与风险 使用同一次 AST 遍历,sudo 导入和借壳提权也检查同一棵 AST。这些检查用于提示风险和检查权限声明, 不是 Python 安全沙箱。扫描器只看静态源码,字符串拼接、运行时生成代码和原生 模块都可能超出它的判断范围。安装插件仍等于信任插件以当前用户身份运行。 带 build 钩子且签名有效的插件才会在安装时执行清单里的命令,命令按参数列表执行, 不经过 cmd 或 sh。隔离动作进程只隔开崩溃,不限制文件、网络、进程或系统调用权限。
启动、执行和停止
- setup:trigger 加载后、启动前调一次,返回 False 拒绝启用(action 没有 setup)
- 执行:动作按规则触发;isolated 动作在子进程
- 重复加载:同一个
PluginLoader接受已物化的同 ID 动作新版后,后续调用使用 新版入口、元数据和资源目录,支持在in-process与isolated之间切换。 新版导入或导入前复查失败时恢复旧入口、元数据和资源目录。 - 参数校验:插件定义
validate_params(action_info, params)时,返回错误列表或抛出异常都会拒绝执行本步 - teardown:引擎停止时调用,插件在这里注销热键、关连接
- 引擎停止:扩展会话一并关闭
- isolated worker:引擎 shutdown 时 shutdown_all() 终止所有活着的子进程
通用数据类型
data_types_api() 提供类型声明、校验、转换、共享类型注册和传输编解码。 参数通过 value_type 声明数据类型;输出也可使用此字段精化原有 type。 动作入口为 run_with_context(action_info, params, context),触发器入口为 run(meta, config, emit_event, shutdown_event)。 共享自定义类型由清单声明,宿主在执行前检查类型和依赖。运行上下文中的 constants 与 variables 是传给插件的值副本,修改它们不会给规则变量赋值。 隔离动作协议通过 value_encoding: typed-v1 保留大整数、精确小数和二进制等值。 类型、转换和值编码见 data-types.md。