Skip to content

契约事实源与操作查询

Core 的 UamTransactionOperation 与 UAM 模型拥有参数结构和事务语义;Backend 的公开方法签名拥有会话输入、结果和错误类型;CLI 拥有进程 JSON envelope,各 result 复用原工作流类型;MCP 只拥有工具元数据、JSON 传输转换及传输预算。

pnpm contracts:generate 使用仓库已有 TypeScript 编译器读取这些类型,生成 MCP 的结构 schema、operation catalog、契约快照和本页表格。MCP 复用已有 Zod 从 JSON Schema 创建校验器;Core 不依赖 Zod。pnpm contracts:check 只比较、不写文件,检查映射完整性和生成物漂移,已接入仓库自测与 docs:check

读取当前会话模型与资源字节

需要消费当前完整 UAM 时,调用 readSessionState({ sessionId, expectedRevision? });只需局部属性时使用下文的 queryEntity。完整读取返回本次调用时已提交状态的独立副本,包含 project、实际 revisiondirtylastSavedRevisionreadCompletereadDiagnosticsuamFidelity

project 直接派生自 Core 的公开 UAM 类型,只移除每个 asset resource 的正式 sourceBytes 字段,保留 sourcePath、组件完整内容及 Reader 保留的 JSON 扩展字段。不会按属性名递归删除扩展数据中的 sourceBytes。只有这份完整只读模型的输出 schema 允许未列名的对象字段;已列名字段仍保留正式类型,事务输入及现有查询契约不变。读取不重新验证引用、不规范化或修复工程,也不返回内部 Document、锁、文件系统、缓存或清理队列。

通过 readResourceBytes({ sessionId, expectedRevision, selector: { packageId, resourceId } }) 获取单个 asset resource 已在会话中的主文件字节。返回实际 revision、selector 和独立 Uint8Array;MCP 对应数字数组。不读取磁盘、不加载辅助文件、不补齐缺失字节;component 不提供此字节读取。

ts
const state = runtime.readSessionState({ sessionId });
if (!state.ok) throw new Error(state.error.code);
const bytes = runtime.readResourceBytes({
  sessionId,
  expectedRevision: state.data.revision,
  selector: { packageId, resourceId },
});
if (!bytes.ok) throw new Error(bytes.error.code);

每次资源读取必须携带模型的 revision。stale_read 同时返回 expectedRevisionactualRevision;出现后丢弃这一轮未完成的模型/字节组合并重新读取,不能混用不同编辑 revision 的资源。读取不等待尚未提交的事务、不保留历史版本、不预留 revision。保存会更新公开 sourcePath、dirty 等状态,但不推进编辑 revision,因此相同 sessionId + revision 不代表永远相同的完整原始 UAM。

readCompleteuamFidelity 如实反映现有会话标记;纯内存会话即使未提供资源字节也可能是 true / full。这些字段不证明字节齐全、可渲染或可保存。失效/关闭会话返回 session_not_found;其他拒绝使用 session_read_failed.reasoninvalid_querynot_foundambiguousunsupported_resourcebytes_unavailableresponse_budget_exceedednon_json_value。失败不会截断数据为成功结果。

预算上限
read.sessionState.limits完整 data 的紧凑 JSON UTF-8 为 4 MiB,深度 64,节点 500000;克隆前检查
read.resourceBytes.maxBytes单个主文件 1 MiB;复制前检查,空字节数组可以读取
两个新 MCP 工具每个完整 CallToolResult 序列化为 JSON 后为 16 MiB,包含紧凑文本和 structuredContent 两份内容;超限返回 mcp_response_budget_exceeded,不改变 Backend 预算或其他工具

仓库固定 fixture 的实测规模如下(字节;模型为 readSessionState.data,响应为完整 CallToolResult,请求 ID 长度等可造成小幅变化)。这决定了模型与资源分开读取的粒度;不是无限工程大小的承诺。

工程模型 JSON模型 MCP 响应最大主文件该资源 MCP 响应
FairyGUI-Experiments15357338824602593254840
FairyGUI-layabox demo93886220491152544831818508
FairyGUI-unity UIProject117385125613063502002049424
FairyGUI-Editor ui2717892593551518048114842

CLI 机器输出

所有业务命令和 docs 子命令使用同一结构:成功为 {schemaVersion:1,command,success:true,result};失败为 {schemaVersion:1,command,success:false,error:{code,message},result?}command 是规范命令路径(如 docs cat),未知顶层命令为 ofguivalidate 无效/不完整和 doctor 错误/不完整仍在 result 保留完整报告;启动/读取异常没有伪造 result。

退出码统一为 0 成功、1 工作流失败、2 参数错误、3 验证不完整。--json 可位于命令前后;stdout 只有一个 JSON,普通日志进入 stderr。帮助和版本仍为文本。无 --json 时保留人类报告,使用同一退出码。

packages/cli/src/contracts.ts 是输出事实源。生成器提供 13 个命令路径(含 ofgui/docs 的解析失败)的 schema,test:repo 检查命令注册遗漏,消费者按生成 schema 校验真实输出。运行 ofgui docs schema cli/validate --jsonofgui docs cat "cli/docs cat" --json 读取自包含 schema;内容位于 envelope 的 result.text。MCP 对应 openfairygui://docs/cli/{command},空格用 %20 编码。生成快照只收集类型,不增加 Backend 到 CLI 的运行时依赖。

查询精确参数

MCP resources/list 提供 openfairygui://contracts/operations,列出正式 operation 及对应 schema URI;使用 resources/read 读取 openfairygui://contracts/operations/{kind},例如 openfairygui://contracts/operations/addComponent。单项 schema 包含它需要的全部 $defs,不需要仓库源码。

tools/list 的每个工具使用对应 Backend 方法的输入/输出 schema,不再共享宽泛的结果定义。工具 _meta 中的 openfairygui/contractDigest、operation catalog 和下表摘要对应同一份生成快照。参数或注解变化后,未更新快照或双语表格会使检查失败。

MCP 服务工厂暴露固定的 Backend 工具目录;发现声明使用已有 Zod 的 draft-07 definitions 与本地 $ref 复用重复结构,不展开整份事务子树。所有引用包含在单个 schema 内,无需网络解析。调用继续使用原 Zod 校验器和预算检查;这是传输表达优化,不新增或省略字段。原始安装契约/单项操作文档仍提供 draft-2020-12 $defs。真实客户端的发现和执行检查见Agent 评测

查询当前实体

queryEntity / openfairygui_backend_query_entity 在现有只读服务中查询当前值,返回 sessionId、实际 revisiontargetentity。例如:

json
{
  "sessionId": "当前会话 ID",
  "target": {
    "kind": "displayNode",
    "selector": { "packageId": "pkg001", "componentResourceId": "cmp001", "displayNodeId": "n1" }
  }
}

只提供固定的属性投影,不接受任意字段表达式;支持的类型由 read.entityQuery.kinds 声明:

target.kind正式 selectorentity.properties
project不传 selector,target 仅为 { "kind": "project" }projectId 和完整工程 settings
packagepackageIdidname 和完整 settingscompressPNGjpegQualitypublish
resourcepackageIdresourceId资源身份、名称、路径、导出/收藏/分支,以及存在的文件名、尺寸、image/movieClip 属性;不含 source bytes、sourcePath、任意 metadata 或组件内容
componentpackageIdcomponentResourceId组件 sizepropertiescustomData;不展开 displayList、controllers、transitions
displayNodepackageIdcomponentResourceIddisplayNodeId正式 UAM 节点属性(含已建模的引用、relations、gears)
controllerpackageIdcomponentResourceIdcontrollerName完整 UamControllerModel,含当前选择、初始页设置、pages(ID/名称/备注)和 actions
transitionpackageIdcomponentResourceIdtransitionName完整 UamTransitionModel,含播放设置、fps 和有序 items(目标引用、起止值等)

updateProjectSettingsupdatePackageSettings 都替换完整设置快照。先查询对应的 projectpackage,复制 entity.properties.settings,仅修改请求字段,保留其余嵌套设置和可选字段;将完整 settings 与查询得到的 revision 一起提交。包设置还需使用原 packageId selector。出现 stale_write 后重新查询并规划,避免用旧快照覆盖其他编辑。设置查询与其他实体共享相同的响应预算。

查询不改变工程、revision、dirty、缓存或业务事件,返回对象与会话深度隔离。selector 不猜测、不按名称模糊匹配:控制器和动画使用组件范围内区分大小写的精确名称,不虚构 ID;不同组件中的同名对象不冲突。结构不正确、目标不存在或指定范围内身份不唯一时返回 entity_query_failedreason 分别为 invalid_querynot_foundambiguous;关闭或失效会话返回 session_not_found

updateControllerupdateTransition 接收完整快照,而非局部 patch。复制查询的 entity.properties,仅修改请求字段;保留页面 ID、顺序、备注、actions、动画 items 顺序及目标引用,使用原 selector 和查询 revision 提交。事务更新保留未修改页面备注及动画在组件内的顺序。出现 stale_write 后重新查询、重新规划完整快照,不盲目替换 revision。读取成功不代表引用有效;预演与正式校验仍独立执行。gears 已随 displayNode 返回,不需要独立查询工具。

动画 item 的 startValue / endValue 沿用 Core 的 unknown[],查询不会猜测动作类型或强制数值化。当前 XML 读取器将 CSV 值读为字符串数组;例如数值 [120, 64] 写入后回读为 ["120", "64"]。修改时保留未授权字段的原始表示,验收按正式保存回读结果比较。

data 的紧凑 JSON UTF-8 大小不得超过 262144 字节,遍历深度不得超过 32,节点数不得超过 100000;边界已在 read.entityQuery.limits 中声明。不截断属性:超限返回 response_budget_exceeded,非 JSON 值返回 non_json_value,均位于 entity_query_failed.reason。预算在克隆前检查,MCP envelope 和文本副本不计入此数据预算。

预演一次事务

preflightTransaction / openfairygui_backend_preflight_transaction 接受与 applyTransaction 相同的 { sessionId, expectedRevision, operations }。它不是仅查询支持范围:Backend 的 AuthoringService 在现有会话排他队列中检查 revision,深度复制工程和源字节,再调用正式的 applyUamTransactionAppAsync,执行后丢弃新工程。

成功返回 ok: truedata 包含 sessionIdbaseRevisionprojectedRevision(正式 apply 后的 revision,未预留)、mode: 'execute-and-discard'impactpersistence;失败保留正式事务的 error.codestage、operation 定位及 meta.diagnostics。当前基准见 meta.revision;失效或关闭会话返回 session_not_found,revision 不匹配返回 stale_write。输入参数在排队前复制,SharedArrayBuffer 支撑的字节也会脱离共享内存。

成功和失败都不改变 authoritative 工程、revision、dirty、待清理文件记录、缓存或业务事件,也不写入磁盘。

impact.entities 比较当前与预演后的正式 UAM:每项包含精确 targetchange(added/removed/updated)和变更的顶层 fields,不返回属性值或源字节。包与工程有各自 target;子集合在父实体上比较 ID/名称顺序,节点、控制器、动画分别比较自身属性。正式执行产生的引用补全/重写也会列出,不只照抄输入 selector。

impact.files 使用正式 ProjectWriter 在内存中分别序列化两份 UAM,再比较文件内容及空目录,返回工程相对 pathkindchange。它只表示当前 revision 到预演结果的模型差异,不是自上次保存以来的累计 dirty 差异,也不是磁盘清单、实际写入列表或删除授权;实际 save 会重写完整工程并按路径策略清理受控文件。

事务前的已有快照允许含待修复的无效引用,其物化只用于内存比较;事务后的快照仍严格校验。Core materializeUamProject 默认校验,显式 { validate: false } 仅供检查无效快照。writeProjectFromUam、Backend Save 和 Materialize 不跳过校验。无法表示或序列化的快照仍返回 projection_failed

persistence.requiredAfterApply 为 true(空批次 apply 也会增加 revision 并标 dirty)。已有存储会话建议 saveSession;只有运行时适配器的内存会话需宿主显式指定 materializeSession.storage;缺少适配器或 UAM fidelity 不支持时为 host-actionwriteVerified 始终 false。两份内存序列化失败返回 transaction_preview_failed.reason: projection_failed;完整摘要超过 2000 项或 data 紧凑 JSON 超过 262144 UTF-8 字节时返回 response_budget_exceeded,不截断、不伪造成功。

推荐工作流:outline 发现 ID → queryEntity 读取当前属性与 revision → preflightTransaction 预演 → applyTransaction 提交相同批次 → validateSession 检查当前工程 → saveSession 保存。完整可运行代码见带 revision 的修改、保存与回读

预演不预留 revision,不证明后续 apply/save 或发布一定成功。正式 apply 必须再次提交 expectedRevision;期间若有编辑,应重新查询并规划,不能把旧预演当作授权凭证。预演复用当前事务执行路径,不额外执行工程保存、文件权限/目标校验或发布检查;缺少文件系统的内存会话也可以预演。

能力通过 authoring.preflightTransaction 声明为 mode: 'execute-and-discard'reservesRevision: falseimpact: 'model-diff' 和摘要 limitsread.entityQuery.kinds 声明七类实体查询,并包含完整正式诊断恢复指引;当前契约与能力 schema 版本以 getCapabilities 返回值为准。

传输与语义边界

  • Core 中的二进制仍是 Uint8Array。MCP 的正式二进制字段使用整数数组(0–255),通过生成的字段路径显式还原;replaceResourceBytes、资源/包快照和导入工程使用同一转换。扩展 JSON 中同名的 sourceBytes 不会被改写。
  • MCP 不接受宿主对象:openProjectSession.storagesaveSession.fileSystemmaterializeSession.storage/fileSystem/targetPath 不在工具输入中。宿主注入继续通过 Backend API 完成。
  • 结构 schema 保留正式类型声明的开放字段,例如扩展设置、资源 metadata 和部分动态值;它们不是凭空补齐的协议。未知的封闭对象字段会被拒绝,不静默丢弃。
  • 同类型定长元组(例如四个数值的 scale9Grid / cornerRadius)生成单一 items schema,并保留相等的 minItems / maxItems;MCP 工具发现无需解析位置数组,元素类型和固定长度约束不变。不同类型的位置元组仍保留逐位置约束。
  • 输入继续受批次上限(1–1000)、revision 整数、selector 长度及总节点/深度/字符串预算约束。通用预算为深度 32、节点 100000、单个数组/对象 10000 项、单个字符串 1000000 字符、键长 256。仅生成契约声明的字节路径使用整数 0–255 数组,绕过通用数组长度和逐字节节点计数;所有字节字段合计最多 8 MiB。任意 metadata 中同名字节字段不获此豁免。schema 中的单字段限制不覆盖总预算。
  • schema 不替代 Core 的引用、资源内容、字段适用性和合法批次检查;校验成功不表示事务可执行或保存会成功。MCP 不增加第二套事务内核,预演也只映射 Backend 的正式入口。
  • 方法专属结果保留 Backend 的错误分类;适配层抛出的未处理错误使用 backend_unhandled_error,不暴露内部异常详情。响应预算及诊断修复策略不由结构 schema 承诺。
  • MCP 工厂的 toolPolicies 可为指定工具声明 Host failureSchemabeforeCall 检查。检查在输入校验后收到独立的 wire 参数副本;返回 undefined 以原参数调用 Backend 一次,返回已声明的 ok: false 分支则停止。Host 失败同样受工具响应预算约束;Backend 返回值始终按正式 schema 校验。SDK 动态发现包含后注册的 Host 工具及对应策略的输出扩展,openfairygui/hostPolicy 元数据标识策略;固定契约摘要和随包语料仅描述 Backend 分支。

当前生成目录

下表只摘要顶层参数;嵌套字段和具体结果请读取对应 schema。SHA-256 变化表示生成契约发生变化,不等同于包版本号。

SHA-256: 758763ae4b6724a25a82dc0edbe10c7c13478589bdabf84b847dd2d7b745331d

操作参数(? 表示可选)
updateProjectSettingssettings, opId?
updatePackageSettingsselector, settings, opId?
renameResourceselector, newName, opId?
moveResourceselector, toPath, opId?
setResourceFavoriteselector, favorite, opId?
setResourceFolderFavoriteselector, favorite, opId?
setResourceFolderAtlasselector, atlas, opId?
setResourceExportedselector, exported, opId?
addResourceFolderselector, path, branch?, favorite?, atlas?, opId?
renameResourceFolderselector, newName, opId?
moveResourceFolderselector, toPath, opId?
removeResourceFolderselector, opId?
setImageResourcePropsselector, props, opId?
addResourceselector, resource, atIndex?, opId?
addBranchbranch, opId?
renameBranchselector, newName, opId?
removeBranchselector, opId?
addPackagepackage, atIndex, opId?
renamePackageselector, newName, opId?
removePackageselector, opId?
addComponentselector, component, atIndex, opId?
removeComponentselector, opId?
moveComponentselector, toPackageId, toIndex, opId?
replaceResourceBytesselector, sourceBytes, opId?
removeResourceselector, opId?
setDisplayNodePropsselector, props, opId?
setComponentPropsselector, props, opId?
attachDisplayNodeselector, atIndex, node, opId?
detachDisplayNodeselector, opId?
addControllerselector, controller, opId?
updateControllerselector, controller, opId?
removeControllerselector, opId?
addTransitionselector, transition, opId?
updateTransitionselector, transition, opId?
removeTransitionselector, opId?
addLookGearselector, gear, opId?
updateLookGearselector, gear, opId?
removeLookGearselector, opId?
addGearselector, gear, opId?
updateGearselector, gear, opId?
removeGearselector, opId?
Backend 方法MCP 工具参数只读提示
getCapabilitiesopenfairygui_backend_get_capabilitiestrue
openSessionopenfairygui_backend_open_sessionprojectPathfalse
openProjectSessionopenfairygui_backend_open_project_sessionproject, sessionId?, canonicalProjectPath?, canonicalPathKey?false
getSessionopenfairygui_backend_get_sessionsessionIdtrue
getProjectOutlineopenfairygui_backend_get_project_outlinesessionIdtrue
queryEntityopenfairygui_backend_query_entitysessionId, targettrue
readSessionStateopenfairygui_backend_read_session_statesessionId, expectedRevision?true
readResourceBytesopenfairygui_backend_read_resource_bytessessionId, expectedRevision, selectortrue
validateSessionopenfairygui_backend_validate_sessionsessionIdtrue
preflightTransactionopenfairygui_backend_preflight_transactionsessionId, expectedRevision, operationstrue
applyTransactionopenfairygui_backend_apply_transactionsessionId, expectedRevision, operationsfalse
saveSessionopenfairygui_backend_save_sessionsessionId, expectedRevision?, targetPath?, force?, mode?false
materializeSessionopenfairygui_backend_materialize_sessionsessionId, expectedRevision?, mode?, reason?false
closeSessionopenfairygui_backend_close_sessionsessionIdfalse
getEventsopenfairygui_backend_get_eventssessionId, after?, limit?true
getCacheSnapshotopenfairygui_backend_get_cache_snapshotsessionIdtrue
refreshCacheopenfairygui_backend_refresh_cachesessionId, reason?false
CLI 命令已安装输出 Schema
publishcli/publish
validatecli/validate
restorecli/restore
ofguicli/ofgui
docscli/docs
inspectcli/inspect
doctorcli/doctor
backend-capabilitiescli/backend-capabilities
docs lscli/docs ls
docs findcli/docs find
docs catcli/docs cat
docs diagnosticcli/docs diagnostic
docs schemacli/docs schema

新增不支持的 TypeScript 构造会使生成失败,不能降级成任意 payload。新增方法必须同时进入 Backend capability 列表和 MCP 元数据;新增 operation 自动来自 Core union。修改后运行 pnpm contracts:generatepnpm check:ci,验证范围见开发指南

MIT Licensed