跳转到正文

运行时、命令与动作回执 ​

插件通过 PluginContext 读取安装状态、调用服务和准备游戏动作。配套 Host 与协议版本见版本页;调用前声明对应权限并连接目标实例。按任务查找便利入口见作者便利接口。

Plugin 与 PluginContext ​

从 neomega_runtime 导入 Plugin、PluginContext、Event。普通插件继承 Plugin,由 Host 启动:

python
from neomega_runtime import Plugin

class HelloPlugin(Plugin):
    async def on_start(self, ctx):
        ctx.log.info("插件已激活,代际=%d", ctx.installation.generation)

if __name__ == "__main__":
    HelloPlugin().run()

Plugin.run(self) 是阻塞入口,内部运行 serve(self, peer=None, *, data_dir=None) 异步方法;不要在已有事件循环内调用 run()。serve 默认使用 Host 提供的 stdio IPC 及安装数据目录。stdout 属于协议,日志使用 ctx.log,不要 print()。

上下文数据 ​

成员返回值与使用阶段
ctx.configconfig_type 指定的 dataclass 实例;在 on_validate 前加载。未设置类型时为 None。
ctx.data_dir当前安装的 pathlib.Path;Host 未提供时抛 RuntimeError。普通文件不参加状态事务。
ctx.installation不可变 HostSession,所有生命周期回调均可读取。
ctx.capabilities激活时 capabilities.get 返回的字典;激活前为 None。包含用于动作的游戏 session。
ctx.log / ctx.peerWorker 日志器 / 当前受管理 IPC 连接。普通插件优先用具名 SDK 方法,不直接消费 peer 的事件队列。

HostSession 字段为 installation_id: str、generation: int、capabilities: tuple、max_frame_bytes: int、outgoing_requests: int、inbound_requests: int、queue_frames: int、protocol_major: int = 1、protocol_minor: int = 0。它是握手快照,不是实时授权凭证。installation_id、generation 与 ctx.operations.session 的游戏会话不是同一个标识。

功能入口 ​

上下文入口用途
scopes、bus多资源会话、原生 Bus与可恢复投影。
storage、business、services安装内 JSON 状态、持久业务回执、在线插件调用;见存储与服务。
commands、queries、scoreboard命令、查询与计分板;operations 的统一回执见本文。
notifications分段通知和标题。
world、artifacts世界写入、制品上传与构建。
inventory、containers、item_actions库存、容器、物品动作。
digging、crafting、actors挖掘与合成、实体。
positions、player_actions、player_abilities玩家位置、动作与能力观察和请求。
environment、recipes环境快照与配方目录。
command_queries、tickingareas、control命令查询与机器人控制。
packets、raw_packets协议包订阅、预览和发送。
chat、chat_waiters、events聊天命令、回复等待与事件分发。
secrets密钥引用。

async ctx.player(*, name=None, uuid=None, xuid=None) -> PlayerHandle、async ctx.players() -> list[PlayerIdentity]、async ctx.resolve_player(*, name=None, uuid=None, xuid=None) -> PlayerIdentity、async ctx.robot()、async ctx.observe_players(**options)、async ctx.player_events(**options) 是上下文上的便利入口。玩家与机器人方法见玩家,玩家观察流见玩家观察与事件流,流由调用方关闭。ctx.spawn(coroutine, *, name=None)、ctx.later(seconds, callback, *, name=None) 与 ctx.every(seconds, callback, *, immediate=False, name=None) 返回受 Worker 管理的 asyncio.Task,见生命周期。

operations 及依赖它的游戏入口激活前抛 RuntimeError。on_validate 必须保持只读,即使某个对象已经存在,也不能据此在验证期间写状态或调用有副作用的服务。

Operations 完整调用流程 ​

从 neomega_runtime.operations 导入 Operations、OperationIntent。普通插件直接使用 ctx.operations,不自行创建第二套会话。

text
prepare(operation: str, arguments: dict, *,
        idempotency_key: str, deadline: str) -> OperationIntent
async submit(intent: OperationIntent, *, timeout=10) -> str
async execute(intent: OperationIntent, *, submit_timeout=10, timeout=30,
              poll_interval=0.05) -> OperationReceipt
async get(operation_id: str, *, timeout=10) -> OperationReceipt
async wait(operation_id: str, *, timeout=30,
           poll_interval=0.05) -> OperationReceipt
async cancel(operation_id: str, *, timeout=10) -> CancellationReceipt

这些是 Operations 的实例方法签名,省略 self。

参数 / 返回合同
operation、arguments完整操作名及 JSON 对象。操作名非空且不超过 256 UTF-8 字节;参数还需满足该操作的 Host 校验。
idempotency_key1–128 个 ASCII 字母、数字、_ 或 -。绑定一次业务意图,不要每次尝试重新生成。
deadline带时区的 RFC3339 字符串;固定后与意图一起保存。SDK 校验格式,Host 校验受理时效。
timeout / poll_interval有限正数秒;不是动作 deadline,Python 等待到期不取消动作。
OperationIntent.payload()新的独立字典,包含 operation/arguments/session/idempotency_key/deadline;可能含敏感业务数据。

prepare 仅构造并快照请求,不执行 IO;完整 JSON 请求上限为 256 KiB。submit 发起一次受理,返回 operation_id,不等于游戏动作完成。权限来自 manifest 的 permissions.operations,Host 逐次校验授权与会话,不因本地 prepare 成功而放行。

execute 提交一次意图并等待同一 ID 的终态,返回值和 wait 相同;异常保留 .intent 与已知的 .operation_id。需要跨重启恢复时,调用前先持久保存意图。

wait 只轮询同一个 ID,返回所有终态,包括 failed、cancelled、unknown;它不会替你只筛选成功。cancel 返回取消请求回执,不回滚已经产生的效果。

一次提交并保存核对信息 ​

以下函数在已激活回调中调用。business_key 和 fixed_deadline 由业务确定一次;示例发现已有记录就停止,交给恢复流程读取原意图,避免重放。

python
async def submit_command_once(ctx, business_key, fixed_deadline):
    key = "command/" + business_key
    tx = await ctx.storage.transaction(keys=[key])
    if tx.get(key) is not None:
        return {"state": "reconcile_required"}
    intent = ctx.commands.prepare(
        "list", idempotency_key=business_key, deadline=fixed_deadline,
    )
    tx.set(key, {"intent": intent.payload(), "state": "prepared"})
    # 持久意图与动作受理一起提交;不会在保存与单独 submit 之间丢失关联。
    tx.action(intent)
    committed = await tx.save()
    operation_id = committed["operation_ids"][0]
    return await ctx.operations.wait(operation_id, timeout=30)

该示例需要 framework.command.execute 权限与支持选择性状态读取的 Host。tx.save() 若发生版本冲突或结果不确定,异常向上传递,不自动重算。提交回执可按原 commit_id 找回动作 ID,详见事务恢复。

Commands.prepare ​

text
ctx.commands.prepare(command: str, *, idempotency_key: str,
                     deadline: str, identity: str = "ws",
                     wait: bool = True, timeout: float = 30) -> OperationIntent

返回操作 framework.command.execute 的意图,参数中的命令字段叫 cmd。identity 可用 player、ws、wo、ai;player/ws/ai 的 wait=True 请求实际命令回应,wo 无回应保证。这里的 timeout 是命令等待预算,Host 上限 300 秒;Operations.submit/wait 各自的 timeout 则控制客户端等待。

便利方法默认 wait=True;直接用 Operations.prepare 构造命令时,应显式填写 wait,不要依赖 Host 的默认 false。无等待或 wo 正常返回后可能是 unknown / response_omitted,不能转换成成功。命令等待不会自动修改 sendcommandfeedback。

此权限允许执行服务器认可的任意命令,只有计分板需求时优先使用范围更窄的计分板能力。把玩家输入拼成命令前需按具体字段校验;需要通知时使用通知助手处理精确姓名与 JSON 转义。

Commands.execute 与结果解析 ​

await ctx.commands.execute(command, *, idempotency_key, deadline, identity="ws", wait=True, command_timeout=30, submit_timeout=10, timeout=30, poll_interval=0.05) 将准备、提交和等待合并为一次调用。command_timeout 控制服务器回应等待;timeout 控制 SDK 轮询。它返回原 OperationReceipt,不自动重试。

ctx.commands.result(receipt) 返回 CommandResult:读取 state/evidence/reason、output、messages/errors/message_texts 和 is_success。消息保留原始翻译键与独立参数,errors 为空也不能单独判定成功。is_success 要求成功终态、acknowledged、正成功数、无失败消息及部分效果。receipt 属性返回原回执的副本;protocol_output() 提取实际协议回应,缺失时抛错。

类型化回执 ​

OperationReceipt、ActionResult、CancellationReceipt 可从 neomega_runtime 导入。它们是 TypedDict,运行时仍是普通字典,不是带方法的对象。

类型字段
OperationReceiptrequest_id: str、operation_id: str、state: OperationState、expires_at: str、result: ActionResult | None、can_retry: Literal[False]。
ActionResult必填 state: TerminalState;可选 evidence: str、reason: str、result: Any、partial_effects: Any、recovery_required: bool。
CancellationReceiptrequest_id: str、operation_id: str、state: OperationState、cancellation_requested: bool。

OperationState 为 accepted/dispatching/running/succeeded/failed/cancelled/unknown;TerminalState 为最后四项。SDK 检查回执结构以及终态内外 state 一致,非法回应抛 ProtocolError。具体证据含义仍由各操作定义,命令回应不能证明玩家客户端看见效果。

提交回应丢失保留 .intent,等待超时保留 .operation_id。不要把 unknown、超时、Python 取消或 cancellation_requested=True 理解成可以换新键重发;处理表见错误与结果核对。

日志与 SDK 来源 ​

neomega_runtime.diagnostics.get_logger(name="plugin", *, installation_id=None, generation=None, level=logging.INFO) 返回独立 logging.LoggerAdapter;普通插件优先使用已绑定安装身份的 ctx.log。输出是 stderr 上的 JSON 行,每条最多 16 KiB,不配置 root logger,也不占用 IPC stdout。只有 extra 中的 event、operation_id 会作为自定义字段输出。

python
import logging

ctx.log.setLevel(logging.DEBUG)
ctx.log.info("插件就绪", extra={"event": "plugin.ready"})

异常日志保留异常类型和堆栈位置,不输出局部变量、源码行或异常正文。这不保证任意日志消息安全:不要把凭据、命令正文、玩家数据或整个请求放入 message、格式化参数或字段。

neomega_runtime.buildinfo.current() 读取当前导入 SDK 的构建信息,用于排查包被修改或导入路径错误。matches_package 为 True 表示文件与包内摘要匹配,False 表示不匹配,字符串 "unknown" 表示缺少可用信息;不要按真假值判断。此调用不请求 Host。

临时脚本入口 ​

neomega_runtime.task_runner.run(path="task.py", *, temporary=True) 运行 Host 托管的可信 Python 脚本,复用正常 Worker,不单独登录游戏。脚本声明:

python
async def main(ctx):
    return {"ready": True}

main 的结果必须可编码为标准 JSON,按 ensure_ascii=True 编码后不超过 12000 字节。临时模式向 Host 报告 succeeded 或 failed;temporary=False 时,main 返回后仍保留插件回调。订阅事件的脚本还必须提供 async on_event(ctx, event) 并显式确认,可提供 async on_stop(ctx)。

验证阶段只编译脚本,激活后才加载并执行;不要在模块顶层做副作用。这个专用 runner 会把脚本 stdout 转到 stderr,但普通 Plugin.run() 没有这项转换。任务发布、执行与结果读取由工具包完成。

底层运行时模块 ​

这些模块供受管理协议适配器使用。普通插件通过 Plugin 与 PluginContext 即可使用其生命周期、身份、日志和任务管理;直接构造底层对象不会授予额外权限。

模块可调用入口与边界
session不可变 HostSession 与 parse_session(value);解析 worker 1.0 的 hello,非法输入为 ValueError。不要伪造安装身份。
managedManagedPeer(reader, writer, *, max_frame_bytes=1 << 20) 与异步 open_stdio();Peer 负责单一 IPC 连接,帧上限受握手进一步收紧。
workerWorker(peer, *, on_event, on_validate, on_activate, on_drain, on_service=None, on_maintenance=None, max_background_tasks=32);回调接收 (peer, payload),run() 为异步且对象只能运行一次。
tasksTaskOwner(on_failure, limit) 是 Worker 内部任务所有权实现;业务使用 ctx.spawn/later/every,不要自行 open/seal/join 操作 Worker 的任务域。
threadedThreadCalls(peer, *, max_pending=128):必须在 Peer 事件循环中构造,只能由其他线程同步调用 call(method, payload, *, timeout=10)。

ManagedPeer 的公开方法包括异步 handshake(*, timeout=10)、call(method, payload, *, timeout=10)、next_event()、next_request()、next_notification()、close();reply(request_id, result) 和 reject(request_id, code, *, details=None) 为同步方法。握手后才可发正常请求;一个 Worker 拥有其 Peer 和事件队列,外部代码不要与它争抢 next_event() 或手动二次握手。底层错误为 IPCError / IPCRejected,后者带 code/request_id/retryable。

Worker 串行处理各自的事件队列和控制请求,事件处理可与控制回调、后台工作并发。任务上限可设为 1–1024,普通 Plugin 使用 32;spawn 只接收尚未运行的 coroutine,拒绝已有 Task/Future。详见退出边界。

ThreadCalls 的待处理上限为 1–128;满额会抛 IPCError。在所属事件循环同步调用会抛 RuntimeError。线程等待超时可能发生在请求已产生效果之后,不会自动取消已发请求,也不能自动重试。它不拥有线程或事件循环,调用方必须在关闭事件循环前结束线程并关闭连接。

完整签名见机器可读 API 目录;桥接运行时另见兼容层。

Neomega · 使用、开发与参考文档