状态和系统
运行时配置快照、持久化的插件作用域存储、系统工具、事件订阅和日志记录。本文属于 插件运行时辅助 参考的一部分;配置和实用工具 涵盖了更广泛的配置读写指南。
状态、配置和系统命名空间¶
api.runtime.config
当前运行时配置快照和事务性配置写入。优先使用已经通过活动调用路径传入的配置;仅当处理程序需要直接获取进程快照时,才使用 current()。
const cfg = api.runtime.config.current();
await api.runtime.config.mutateConfigFile({
afterWrite: { mode: "auto" },
mutate(draft) {
draft.plugins ??= {};
},
});
mutateConfigFile(...) 和 replaceConfigFile(...) 返回一个 followUp 值,例如 { mode: "restart", requiresRestart: true, reason },该值记录写入者的意图,而不会从网关手中夺走重启控制权。
api.runtime.system
系统级工具。
const accepted = api.runtime.system.enqueueSystemEvent(text, options);
api.runtime.system.requestHeartbeat({
source: "other",
intent: "event",
reason: "plugin-event",
});
api.runtime.system.requestHeartbeatNow({ reason: "plugin-event" }); // Deprecated compatibility alias.
const heartbeatResult = await api.runtime.system.runHeartbeatOnce({
reason: "plugin-triggered-check",
});
const output = await api.runtime.system.runCommandWithTimeout(cmd, args, opts);
const hint = api.runtime.system.formatNativeDependencyHint(pkg);
requestHeartbeatNow(...) 在 兼容性注册表 中被跟踪为 plugin-runtime-api-compat-aliases,removeAfter 日期为 2026-10-01;在新代码中请使用 requestHeartbeat({ source, intent, reason })。
openclaw/plugin-sdk/system-event-runtime 辅助模块在 SDK 边界解析遗留会话别名。将已解析的 agentId 与 sessionKey 一起传给 api.runtime.system.enqueueSystemEvent(...),以保留插件运行时的生命周期检查。独立调用者可以使用 enqueueRoutedSystemEvent(text, { agentId, sessionKey })。使用 peekSystemEventEntries(sessionKey, agentId) 读取同一所有者的事件;这样可为每个智能体保持 global 队列相互独立。没有显式所有者的调用仍会按已配置的所有者进行别名解析,并拒绝歧义的智能体选择。无法规范化为智能体 ID 的显式所有者将被拒绝。
runHeartbeatOnce(...) 立即运行一次心跳周期,绕过常规的合并计时器。投递默认发送到已配置的操作员私信(commands.ownerAllowFrom,然后是频道 allowFrom);如需仅内部运行,请传入 { heartbeat: { target: "none" } }。
runCommandWithTimeout(...) 返回捕获的 stdout 和 stderr、可选的截断计数、code、signal、killed、termination 和 noOutputTimedOut。当子进程未提供非零退出码时,超时和无输出超时结果会报告 code: 124。非超时的信号退出仍可能返回 code: null,因此请使用 termination 和 noOutputTimedOut 来区分各种超时原因。
api.runtime.events
事件订阅。
api.runtime.logging
日志记录。
使用 openclaw/plugin-sdk/secure-random-runtime 中的 generateSecureToken({ bytes: 32, redact: true }) 生成私有传输令牌。对象形式要求至少 16 个随机字节,并在返回前将生成的值注册到精确诊断脱敏流程中。现有的数字调用保持其普通的 ID 行为。这不会授予任何凭证访问权限或请求授权;请保留实时的协议值,只在展示边界处进行脱敏。
api.runtime.state
状态目录解析和基于 SQLite 的键值存储。
```typescript
const stateDir = api.runtime.state.resolveStateDir(process.env);
const store = api.runtime.state.openKeyedStore<MyRecord>({
namespace: "my-feature",
maxEntries: 200,
defaultTtlMs: 15 * 60_000,
});
await store.register("key-1", { value: "hello" });
const claimed = await store.registerIfAbsent("dedupe-key", { value: "first" });
const value = await store.lookup("key-1");
await store.consume("key-1");
await store.clear();
const blobs = api.runtime.state.openBlobStore<MyBlobMetadata>({
namespace: "rendered-artifacts",
maxEntries: 100,
maxBytesPerEntry: 4 * 1024 * 1024,
maxBytesPerNamespace: 64 * 1024 * 1024,
defaultTtlMs: 15 * 60_000,
});
await blobs.register(
"artifact-1",
new TextEncoder().encode("binary or text payload"),
{ contentType: "text/plain" },
);
const blob = await blobs.lookup("artifact-1");
```
对于命令拥有的写入,`store.register(key, value, { assertCurrent })` 和 `store.delete(key, { assertCurrent })` 会在工作线程准备和准入阶段携带已捕获的所有者断言。该断言保留在宿主中;绝不会被序列化到存储的数据中。撤销操作会阻止待处理写入被准入,而已被工作线程接受的写入仍会正常完成。
键值存储在重启后仍然保留,并按运行时绑定的插件 ID 进行隔离。使用 `registerIfAbsent(...)` 进行原子去重声明:当键缺失或已过期并完成注册时返回 `true`;当已存在有效值且不覆盖其值、创建时间或 TTL 时返回 `false`。当变更依赖当前值时,请将 `observe(...)` 与 `compareAndApply(...)` 配合使用;比较和变更会在同一个 SQLite 工作线程事务中运行。每个命名空间拥有自己的 `maxEntries` 保留策略和可选的 TTL 过期机制;插件各命名空间之间没有聚合行数限制。JSON 值限制为 1 MiB 的 UTF-8 编码 JSON。默认情况下,超出 `maxEntries` 的写入只会从该命名空间中淘汰最旧的存活行。对于绝不能失效的持久化所有权记录,请设置 `overflowPolicy: "reject-new"`:当达到命名空间上限时,新键会写入失败,而已有键仍可更新。兄弟缓存的增长无法拒绝或淘汰这些所有权记录。升级时,现有数据库无需迁移或清理;其中存储的行会被保留。
若要保留记录而不进行基于计数的淘汰,请使用异步 opener,并传入 retention: "retained" 而不是 maxEntries:
const history = api.runtime.state.openKeyedStore<MyRecord>({
namespace: "conversation-history",
retention: "retained",
});
await history.register("room-a:0000000042", { value: "hello" });
OpenKeyedStoreOptions 仍然是有界选项类型。OpenRetainedKeyedStoreOptions 描述保留设置,OpenAsyncKeyedStoreOptions 是异步 opener 的联合类型。同步 opener 仅接受有界设置。
保留存储使用现有的 SQLite 表,并位于内部 @retained. 命名空间前缀下,该前缀不会与调用方提供的有效命名空间冲突。它们不消耗有界存储的行配额。它们会拒绝 maxEntries、overflowPolicy、默认 TTL 和每次写入的 TTL;记录会一直保留,直到被显式删除或清除。每条值的 JSON 限制仍然适用,插件负责磁盘增长和删除策略。调用方提供的命名空间保持其现有的验证和长度限制。
entriesInKeyRange({ keyStartInclusive, keyEndExclusive, limit, order }) 读取一个词法键范围,包含下界但不包含上界。limit 必须为正的安全整数;order 默认为 "asc" 或 "desc"。存储会在返回值之前应用排序和限制。当原生标识符不能按词法排序时,请对可排序键进行编码。读取持续增长的保留存储时,应使用有界分页而不是 entries()。
moveEntriesFrom({ namespace, entries: [{ sourceKey, targetKey }] }) 将同一插件拥有的有界命名空间中的至多 10,000 行提升到接收方保留存储中。一个事务会重新读取并移动源记录,而不会解码或重写其负载。目标位置的现有记录优先,缺失的源记录为空操作,已完成的移动后重试是幂等的。带有 TTL 的活跃源记录会使整个操作失败;过期记录不会被复活。返回的数字表示已处理的源行数。此操作不会创建另一张表,也不需要 Doctor 步骤。
对于现有适配器而言,这两个方法在公共存储类型中仍然是可选的。使用保留存储的插件必须声明它所需的主机能力;不要静默回退到逐出存储,也不要通过不同路径重试失败的读取。当保留运行时句柄的所属能力关闭后,该句柄会拒绝后续操作。
Warning
保留存储不会添加数据库版本栅栏。较旧的 OpenClaw 二进制版本仍会应用较旧的缓存和插件配额规则,并且不得写入扩展后的保留状态。在降级之前,请恢复兼容的升级前备份;仅凭匹配的 SQLite schema 版本不足以建立安全的保留行为。
lookupMany(keys) 是一项可选的键值存储能力,每次调用最多可精确查找 10,000 个键。结果的长度和顺序与输入一致,包括重复键。每个位置是一个 Result<T | undefined, PluginStateStoreError>:成功时为 { ok: true, value },其中缺失或过期的键对应 value: undefined;存储的 JSON 损坏时则为 { ok: false, error }。空请求返回 []。键使用与 lookup 相同的修剪规则和 512 字节 UTF-8 限制;无效键或超限请求会在读取之前以 PLUGIN_STATE_INVALID_INPUT 和操作 lookup 失败。数据库获取和查询错误会使整个调用失败。损坏 JSON 的错误在每个键的结果中保留 lookup 的错误码和操作。仅在读取器到达该位置时检查每个结果,如果 result.error 不是 ok,则抛出 result.error;这样读取器可以在较早的缺失或无效块处停止,而不会引发后续的损坏错误。每次调用在同一个插件和命名空间中使用一个过期截止时间和一个 SQLite 选择,且不会创建缺失的数据库。不同的调用(包括元数据读取)不共享快照;分块格式必须保留其代际、摘要和读取器生命周期检查。
当前的主机工厂提供 lookupMany,但公共存储类型仍将其保持为可选,以便现有的第三方适配器和声明了较旧主机版本的宿主使用。支持这些主机的插件必须检查该方法是否存在,并在缺失时使用其现有的顺序 lookup 路径;绝不要通过该路径重试失败的批量读取。Matrix、Microsoft Teams 和 Voice Call 会保留此兼容性,直到它们声明的最低主机版本提供该能力为止。不要为了检测此方法而从较旧主机导入新的辅助导出。
count() 返回运行时所绑定的插件和命名空间中存活存储行的数量,而不会加载或解码这些行的 JSON 值。当某行的过期时间戳等于或早于调用时的截止时间,该行即过期。计数不会删除过期的行、创建缺失的数据库,也不会加入可写数据库的生命周期。损坏的 JSON 仍然占有一个存活行并被计数;lookup 和 entries 保留其解码错误。数据库获取和查询失败会以操作 count 传播。计数和后续的写入是独立操作;写入仍然负责执行容量限制。
当前的主机工厂提供 count,但在下一个 Plugin SDK 主版本之前,对于已发布的主机和适配器,它在公共异步和同步存储类型中仍然是可选的。支持这些存储的调用方可以使用 store.count ? await store.count() : (await store.entries()).length;同步调用方省略 await。该回退方案保留旧存储的枚举和解码行为。仅当方法不存在时才回退,绝不要在计数失败后回退。
openSyncKeyedStore<T>(...) 仍然可供无法使用 await 的调用方使用,并保持其现有的同步返回值和错误。它通过 next-plugin-sdk-major 兼容性门禁被弃用。参见 同步键值存储迁移。
openBlobStore<TMetadata>(...) 在共享 SQLite 中存储有界二进制负载,不使用 base64 或文件 sidecar。它要求按条目、按命名空间的字节数和行数限制;在 API 边界处复制字节数组;并且列出元数据时不会加载每个 BLOB。register(...) 是显式 upsert,包括针对已过期键。registerIfAbsent(...) 提供冲突安全的创建:已过期键保持被占用,直到其所有者使用 deleteExpiredKey(key) 或 deleteExpired() 认领它,并保留在 SQLite 提交后删除相关命名工件所需的元数据。任何带有 TTL 的行都是瞬态的,即使在过期前也会从备份/恢复中排除;对于持久、可恢复的状态,请省略 TTL。主机熔断器将每个 BLOB 限制为 100 MiB,每个插件限制为 512 MiB 的物理存储 BLOB,以及每个插件限制为 50,000 行物理存储行,包括等待所有者清理的已过期行。当外部物化结果不得因替换或逐出而被静默孤立时,请使用带有 overflowPolicy: "reject-new" 的 registerIfAbsent(...)。
Blob 变更使用共享 SQLite worker,并将配额检查和变更保留在一个事务中。`lookup` 和 `entries` 使用保留的只读 worker 路径。缺失的存储保持不存在。普通未选择快照的读取会观察到独立提交的数据;无关的缓存原生游标可能保留较旧的视图。显式选择的快照会保持其私有源直至完成。在发布依赖工件或删除其存储之前,请等待所有方法完成。共享读取器准入是有界的:按顺序或以有界并发处理清单,并在报告批量失败或完成关闭之前,等待所有已启动的操作完成。Worker 错误会保留 `PluginBlobStoreError` 分类、操作、路径和因果错误。字节复制、元数据序列化和完整结果物化仍使用调用方内存;这不是流式 BLOB API。
`openChannelIngressQueue<TPayload>(...)` 打开一个作用域限定于调用插件的持久化 ingress 队列,用于缓冲需要跨重启至少一次处理的入站事件。当过期声明恢复使用 `shouldRecover` 时,如果损坏的已声明负载应被隔离,请同时提供 `shouldRecoverCorrupt`:其独立于负载的声明身份可让插件在队列将该行标记为墓碑之前保留实时所有者和 lane 策略。
主机 ingress 队列提供 `listUnsettled({ orderBy })`,从共享状态 broker 的一个快照返回 `{ pending, claims }`,并与队列变更保持有序。共享 drain 使用此一致视图,因此检查期间释放的声明不会让后续事件超越其 lane 头部。对于现有外部队列实现,该方法在下一个 Plugin SDK 主要版本之前仍为可选;只有这些实现保留独立的 `listPending`/`listClaims` 路径。快照读取失败时,绝不会回退到独立读取。
插件状态租约已在 2026.8.1 中移除。对于原子数据库工作,请使用短 SQLite 事务;对于有界持久状态,请使用插件作用域的键控存储(`openKeyedStore` 或 `openSyncKeyedStore`)。
`openChannelIngressDrain(...)` 在该队列上打开核心通道无关 worker(如果未提供队列,则创建一个队列)。drain 负责过期声明恢复、按 lane 的声明序列化、在采纳时完成或在 dispatch 返回时完成、重试/死信处置、可选的采纳前取代,以及 claim→adoption 停滞超时。使用 `turnAdoptionLifecycle` 将声明所有权接入回复生成(通过 `plugin-sdk/channel-outbound` 中的 `bindIngressLifecycleToReplyOptions`)。通道插件保留接受侧入队、lane 派生、不可重试分类以及任何取代授权策略。
共享 ingress 监视器在关闭期间保持其 drain 存活,直到已启动的完成、
释放和失败写入都已结算。如果写入失败
而 drain 仍拥有该声明,关闭会报告错误并保留
该所有权。
!!! warning
`openBlobStore`、`openKeyedStore`、`openSyncKeyedStore`、`openChannelIngressQueue` 和 `openChannelIngressDrain` 在本版本中仅对捆绑插件和受信任的官方插件安装可用。拒绝信息包含已记录的原因、注册表数据库路径、来源以及安装来源/规范;`plugins inspect` 会报告相同的信任事实。选择已记录官方安装的加载路径会保留信任;未跟踪的本地副本则不会。有关 doctor 迁移和针对具体原因的补救措施,请参阅 [拒绝可信插件状态](../../tools/plugin.md#trusted-plugin-state-refused)。不受信任通道的 ingress 监视器会使通道启动失败,而不是在没有持久队列的情况下运行。
同步键控存储迁移¶
api.runtime.state.openSyncKeyedStore 和 PluginStateSyncKeyedStore 自 2026 年 9 月 11 日起已弃用。
现有的 createPluginStateSyncKeyedStore 工厂是
名为 plugin-state-sync-keyed-store 的兼容性适配器。现有方法
在下一个 Plugin SDK 主要版本之前仍受支持;移除还需要
受支持的外部插件迁移以及明确的破坏性发布批准。
使用相同的命名空间和选项调用 api.runtime.state.openKeyedStore,然后
等待其操作。打开器本身仍同步返回存储。
两个接口使用相同的插件作用域数据,因此无需数据迁移。
没有绑定插件 API 的延迟运行时代码可以从 openclaw/plugin-sdk/plugin-state-store-runtime 导入
createPluginStateKeyedStore。
传入插件 ID 和相同的命名空间选项,然后等待每个操作。
保持此导入为惰性导入,因为该工厂会加载状态数据库运行时。
const store = api.runtime.state.openKeyedStore<MyRecord>({
namespace: "my-feature",
maxEntries: 200,
});
await store.register("key-1", { value: "hello" });
const value = await store.lookup("key-1");
对于代表当前工具调用或其他可撤销操作的写入,
在开始副作用之前要求 store.withCurrent。将宿主提供的
断言与任何操作特定的权限检查绑定在一起:
if (!store.withCurrent) {
throw new Error("Update OpenClaw to authorize this state mutation.");
}
const actionStore = store.withCurrent({
assertCurrent: () => {
context.assertInvocationCurrent();
assertActionAllowed();
},
});
await actionStore.register("key-1", { value: "hello" });
返回的 PluginStateKeyedStore<T, 2> 是对同一
命名空间、设置和插件生命周期的不可变绑定。它仅暴露数据操作;
它没有 update、deleteIf 或重新绑定方法。断言保留在
宿主上,并在读取之后以及写入的事务准入和最终提交
准入时进行检查,包括有界存储。为每个
操作创建单独的视图;不要在一个共享服务上保留某个调用方的权限。旧版
PluginStateKeyedStore<T> 为旧宿主和
适配器将此能力保留为可选。需要它的操作在缺失时必须拒绝。
observe 和比较冲突会在不提交所请求变更的情况下返回观察结果;它们在返回该数据时也需要当前权限。
在提交授权之前的拒绝会回滚变更。一旦提交被授权,之后的撤销不会将已确定的写入变为拒绝。 在下一个外部副作用之前重新检查权限,并保留已记录的结果;永远不要重试已提交或结果未知的写入来补偿撤销。
异步存储的 update 更新器和 deleteIf 谓词是已弃用的
兼容性方法。它们仍然在主线程上同步运行,位于包含权威读取和变更的事务中,并将在下一个 Plugin SDK 主要版本中继续受支持。
在调用这些方法之前完成异步规划;不要使它们的回调变为异步,也不要使用单独的查找和写入来替换原子操作。
从更新器返回 undefined 会保持条目不变。update、
deleteIf、lookupMany 和 count 在公共存储类型中仍为可选,因此请为受支持的旧宿主和第三方适配器保留能力检查。
对于新的原子变更,使用可选的 observe 和 compareAndApply
方法。observe(key) 通过规范的可写数据库准入准备变更,并可能创建或打开状态。它返回 { value, comparison };对于普通的、不创建的读取,请使用 lookup。在调用方准备下一个值时,没有事务保持打开。
compareAndApply(key, comparison, intent) 在同一个由工作线程拥有的事务中,在修改当前活动行之前对其进行比较。不透明的比较绑定实际的数据库、插件、命名空间、键、存储的 JSON 字节、创建时间和过期时间。它比较内容和元数据;它不是化身令牌或操作权限。另一个存储或键会以 PLUGIN_STATE_INVALID_INPUT 拒绝该比较。
意图是显式的:
{ operation: "update", action: "set", value, ttlMs? }写入一个已定义的值,并刷新其创建时间和 TTL,即使值未改变。{ operation: "update", action: "keep" }保持条目不变,同时保留可写准入和现有命名空间过期清理。{ operation: "delete", action: "delete" }删除匹配的活动条目。{ operation: "delete", action: "keep" }保留可写准入,而不进行过期清理或条目变更。
结果是 { status: "applied" }、{ status: "unchanged" } 或 { status: "conflict", current }。冲突不会更改插件状态行,并提供一个新的观察结果。在观察之后过期的条目会产生冲突;缺失和过期条目在其他情况下共享逻辑缺失语义。现有配额、驱逐顺序、验证和存储错误仍然适用。
在显式冲突时,插件可以从 current.value 重新计算一个命名的纯决策并重试。在该决策之外准备时钟、随机性和外部副作用。永远不要重试传输失败、未知结果或任意回调。在使用此能力之前检查这两个可选方法;不存在由单独查找和无条件写入组成的安全回退。
registerIfAbsent 和可选的 deleteIfEqual(key, expected) 操作使用共享状态 SQLite 工作线程。deleteIfEqual 接受字符串、有限数字、布尔值或 null,并在与删除相同的事务中将其与解码后的活动值进行比较。缺失或过期条目返回 false;格式错误的存储 JSON 仍然是类型化存储错误。这些操作与旧版同步存储共享现有数据、限制和过期规则。
register、lookup、lookupMany、consume、delete、entries、count 和 clear 也在同一工作线程中执行 SQLite。读取保留缺失存储的行为,而不创建数据库。lookupMany 为每个输入键返回一个结果,包括重复项和按键的损坏值错误。consume 原子地读取并删除;解码失败会回滚删除。存储创建、输入验证和 JSON 序列化仍保留在调用线程上。
基于回调的 update 和 deleteIf 保留原生同步事务;不要用单独的查找和写入替换其中任何一个。工作线程错误保留 PluginStateStoreError 代码、操作和路径。规范状态错误使用其现有编解码器;其他原生原因保留有界因果消息、错误代码和数值 errno 值。结构化文件日志包括在 owner 中构造插件状态错误的进程 ID、线程 ID 和 OpenClaw 版本,以及嵌套原因详情。命令分发之前的失败会在调用方线程上包装。原生原因代码在这些记录中显示为 errorCode;内存中的错误保留其原始 code。现有日志脱敏仍然适用。任意自定义属性和原始堆栈不会跨越工作线程边界。PLUGIN_STATE_OPEN_FAILED 可以描述 SQLite 打开之前被拒绝的准入;在诊断文件错误之前,请检查原因和 owner。
Discord 和 Slack 在放弃在线状态冷却时使用标量条件删除。在不具备该可选能力的旧主机上,它们会让其过期,而不是冒险删除较新的预留。
FaceTime 按调用顺序持久化待处理的拨号快照,并使用 worker 比较仅清除匹配的拨号。辅助分发等待持久化意图,关闭会等待已接受的持久化完成。其受支持的 2026.9.4 主机在没有比较功能时保留原子 deleteIf 清理;失败的 worker 操作绝不会选择该兼容性路径。命名空间、存储记录和保留策略保持不变,因此此次切换无需数据迁移。
此次弃用添加了编辑器注释、文档和兼容性清单元数据。它不添加运行时警告,也不更改信任资格:运行时打开器仍仅限于捆绑插件和受信任的官方安装。运行时警告应等待可操作的受支持升级。只有上述识别的操作在 worker 上执行。在此迁移期间,回调执行保持不变。
每个代理的 SQLite 写入¶
已经使用私有 sqlite-runtime 外观的捆绑插件和官方插件可以从 openclaw/plugin-sdk/sqlite-runtime 导入 withOpenClawAgentDatabaseWrite。这仍然是内部运行时外观,而不是面向第三方插件的类型化公共 SDK 入口点。
在异步生产者中调用它,然后再进入同步 SQLite。它与会话写入器和离线程回收共享代理数据库的进程内写入准入,使 Gateway 线程可用于授权回收提交。
异步 AgentSession 消息、模型、压缩和树操作使用此准入进行其转录写入。嵌入式提示准备、重放修复和工具结果清理在发布依赖结果或释放其资源之前等待其写入。模型选择钩子在写入准入释放后运行。SessionManager appendModelChange 和 appendThinkingLevelChange 返回其已提交条目 ID 的 promise;AgentSession 和扩展 setThinkingLevel 返回 Promise<void>。在使用由此产生的模型或思考状态之前,等待这些操作。其他同步 SessionManager 操作仍需要适当的调用方拥有的写入边界。
SessionManager.appendMessageToTranscript 是一个已弃用的公共 SDK 兼容性方法,保留给使用 v2026.9.5 契约的插件。它接受普通消息、自定义消息和 Bash 执行消息,并同步返回持久化的消息 ID。它委托给规范追加内核,并可以在调用线程上执行 SQLite 工作。移除需要一个版本化的 SDK 替换和一个插件迁移窗口;捆绑的失败图像路径不会调用它。
核心失败图像结算使用内部 appendSessionTranscriptNote 操作,该操作接受自定义消息,并返回一个 promise,包含其持久化的 messageId、规范 message、追加所有者的 appended 结果,以及来自同一快照的 currentTail 事实。
尾部事实使用事务的可见叶和代:侧元数据不会抑制重试的发布,而较晚的可见条目会。
基于文件的注释使用相同的规范代理 worker 和写入器队列,在异步目标准备之前预留其轮次。嵌入式运行器会先等待其失败图像注释,然后再在实时上下文或已完成结果中发布该存储消息;条件是所有者已追加它,或确认在丢失回复后它仍是当前尾部。幂等的历史结果不会重新引入被压缩省略的注释。输入和目标捕获先于等待的工作;事务和发布检查保留原始写入器和会话绑定。已知提交后跟发布失败会保留其消息 ID,并防止模型回退重放该追加。隐身注释在其现有进程持有的原生写入所有者下使用相同的规范追加快照,直到其 actor 切换;此路径仍执行调用线程 SQLite 工作。它保持管理器的已加载视图不变,并应用相同的新追加/当前尾部发布规则。分离注释继续通过其内存管理器所有者。规范存储关闭会撤销待处理的异步注释,并在释放存储前加入其目标准备、已接受的工作和清理。失败图像注释使用现有消息幂等键,以在删除处理和同一运行重试中存活。现有无键注释保留其运行元数据匹配。
SessionManager.open、openBounded 和 setSessionTarget 在读取转录或调用 onTruncated 之前,将 storePath 捕获为绝对词法定位器。相对定位器在入口处相对于进程工作目录解析;getSessionTarget() 返回该捕获的定位器。后续工作目录更改使管理器仍绑定到其原始存储。该绑定还捕获已解析的状态目录和 supervisor 模式;环境更改无法重定向后续写入。现有 sessions.json 和自定义存储路由以及符号链接拼写均保持不变。
基于文件的模型和思考转录写入通过规范代理数据库 worker 执行。排队的扩展操作通过事务和提交准入保留其原始运行时和会话权限。同步会话打开、最终模型上下文验证和隐身转录持久化仍使用其原生所有者;异步方法并不意味着封闭会话流中的每个存储操作都在离线程上运行。
已提交的元数据在异步清理之前,随转录视图采用一起更新绑定会话的模型或思考状态。设置 setter 保留其现有持久化队列。如果视图重建、本地发布或依赖的思考更改在追加提交后失败,错误会保留已提交的条目,并防止模型回退重放它。失败的视图重建会使现有管理器拒绝进一步的转录访问;在解决读取失败后,丢弃它并通过会话所有者重新打开。重试追加会重复一个已提交的写入。
签名是 withOpenClawAgentDatabaseWrite(options, operation, expectedDatabase?)。
options 使用现有的代理数据库选项,包括必需的
agentId 和可选的具体 path。同步的 operation 会接收
OpenClawAgentDatabase;返回的 Promise 会在操作完成后解析为其结果。如果没有
expectedDatabase,该助手还负责异步数据库打开准入。助手会在等待之前捕获环境、所选状态
目录和数据库路径,因此更改工作目录不会重新定位已排队的写入。仅打开或借用句柄并不会准入写入。
对于已借用的句柄,请将其确切的 DatabaseSync 作为第三个参数传入。等待之后,助手会拒绝已关闭或已被替换的句柄,而不是代表它打开一个替换句柄。在操作完成之前,保持原始借用处于活动状态。调用方仍然拥有事务和授权。
对于大型原生发布,openOpenClawAgentSqliteWorkerStore(options, borrowedDb, { moduleUrl, input })
会保留原始借用句柄和物理标识。其
run(operation, assertCurrent) 会加入现有代理写入队列,并为完整操作借用标准代理执行器。该模块导出
bindSqliteWorkerBackend(input, { databasePath, database, admit });它使用提供的连接,并且只关闭其自身的临时状态。它不得打开或关闭代理数据库。该操作只会接收绑定后端的
execute 方法;在调用 close() 之前完成它。客户端关闭会撤销新工作,
排空其已接受的操作,并释放其原始借用。标准执行器拥有原生连接、租约、空闲复用和最终关闭。
与该所有者一起使用的后端会在 BEGIN 之后通过提供的 admit 回调请求 transaction 准入,并在 COMMIT 之前立即请求 commit 准入。
宿主会在两个时点检查标准连接和当前调用者权限,而不会同步等待原生事务。已接受的
commit 授权会将 commit 排在后续撤销之前;较早的拒绝会回滚。调用方必须保留已提交或未知结果,并且绝不能重放它们。
私有文件所有者可以使用 runSqliteWorkerStoreWrite 并拥有自己的准入和生命周期;它不提供共享的代理队列或租约。
runSqliteWorkerStoreOperation(store, operation, undefined, assertCurrent) 会保留一个现有 worker actor,并通过 broker 准入检查所提供的权限。当私有存储已异步准备好命令时,请使用它;仅在打开 worker 之前检查会使待处理工作仍由旧快照授权。此操作助手会保留已接受的原生结算,并且不会为未实现该握手的后端添加 transaction/commit 握手。
Worker 后端可以在 prepare(command) 中异步加载模块前置条件。
准备阶段会携带捕获的状态/运行时事实,并且不执行任何原生工作。
在其完成后,execute(command) 会进入新的同步权限作用域;与连接绑定的执行会在原生工作之前重新验证权限。扩展加载、事务和领域回调仍保持同步。代理连接策略(包括 TEMP 存储)属于标准连接所有者,并且在发布绑定时无法重置。
其故障处理可能留下不可用原生连接的后端应实现同步的 assertSettled()。broker 会在命令返回或抛出异常后调用它。断言失败会使 Worker 退役,并在释放操作准入之前等待原生退出。使用 assertTransactionUsable(db) 检测事务所有者保留的故障,并拒绝任何仍然存活的打开事务。已安全回滚的普通故障仍可以返回其领域结果。
例如,给定一个现有的 borrowedDb、一个存活所有者的 assertCurrent() 检查以及同步的 applyPreparedChanges(db):
import {
runSqliteImmediateTransactionSync,
withOpenClawAgentDatabaseWrite,
} from "openclaw/plugin-sdk/sqlite-runtime";
await withOpenClawAgentDatabaseWrite(
{ agentId, path: databasePath },
({ db }) =>
runSqliteImmediateTransactionSync(db, () => {
assertCurrent();
applyPreparedChanges(db);
}),
borrowedDb,
);
在请求写入准入之前,准备好文件、嵌入、网络结果和钩子决策。保持已准入的回调为同步;不要返回 Promise,也不要跨 provider 调用或整个异步钩子持有准入。在回调内部、变更之前立即重新检查适用的 manager/run 所有权和取消。代理身份和句柄相等性不是授权。
对于可能在 SQLite 锁争用后重复的准备,
runSqliteImmediateTransaction(db, prepare, options, admit) 接受同一所有者的准入回调。prepare 在准入之前运行,并返回一个同步事务回调。将 (write) =>
withOpenClawAgentDatabaseWrite(databaseOptions, write, borrowedDb) 作为 admit 传入;不要在已准入的回调内放置异步准备。助手会在等待后重新检查事务状态,并且绝不重复一个已经进入其事务的回调。
withOpenClawAgentDatabaseWrite 不会启动事务、授予权限租约或协调无关进程。准入之外的原始 SQLite 调用会绕过它,现有同步 API 也不会自动变为异步。被拒绝的过期所有者写入必须返回其生命周期所有者以进行恢复,而不是使用替换句柄重试。有关存储设计和迁移要求,请参阅 数据库模式。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw