























@@ -5,7 +5,13 @@ import os from "node:os";
55import path from "node:path";
66import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
77import { setActivePluginRegistry } from "../../plugins/runtime.js";
8+import type { DB as OpenClawStateKyselyDatabase } from "../../state/openclaw-state-db.generated.js";
9+import {
10+closeOpenClawStateDatabaseForTest,
11+runOpenClawStateWriteTransaction,
12+} from "../../state/openclaw-state-db.js";
813import { createTestRegistry } from "../../test-utils/channel-plugins.js";
14+import { executeSqliteQuerySync, getNodeSqliteKysely } from "../kysely-sync.js";
915import {
1016testing,
1117bindGenericCurrentConversation,
@@ -17,6 +23,11 @@ import {
1723} from "./current-conversation-bindings.js";
1824import type { SessionBindingRecord } from "./session-binding.types.js";
192526+type CurrentConversationBindingDatabase = Pick<
27+OpenClawStateKyselyDatabase,
28+"current_conversation_bindings"
29+>;
30+2031function expectSessionBinding(bound: SessionBindingRecord | null): SessionBindingRecord {
2132if (bound === null) {
2233throw new Error("Expected current-conversation binding");
@@ -45,6 +56,40 @@ function expectBindingMetadata(
4556}
4657}
475859+function buildConversationKey(ref: SessionBindingRecord["conversation"]): string {
60+return [ref.channel, ref.accountId, ref.parentConversationId ?? "", ref.conversationId].join(
61+"\u241f",
62+);
63+}
64+65+function seedPersistedBinding(record: SessionBindingRecord): void {
66+runOpenClawStateWriteTransaction(({ db }) => {
67+const bindingDb = getNodeSqliteKysely<CurrentConversationBindingDatabase>(db);
68+executeSqliteQuerySync(
69+db,
70+bindingDb.insertInto("current_conversation_bindings").values({
71+binding_key: buildConversationKey(record.conversation),
72+binding_id: record.bindingId,
73+target_agent_id: "codex",
74+target_session_id: null,
75+target_session_key: record.targetSessionKey,
76+channel: record.conversation.channel,
77+account_id: record.conversation.accountId,
78+conversation_kind: "current",
79+parent_conversation_id: record.conversation.parentConversationId ?? null,
80+conversation_id: record.conversation.conversationId,
81+target_kind: record.targetKind,
82+status: record.status,
83+bound_at: record.boundAt,
84+expires_at: record.expiresAt ?? null,
85+metadata_json: record.metadata ? JSON.stringify(record.metadata) : null,
86+record_json: JSON.stringify(record),
87+updated_at: record.boundAt,
88+}),
89+);
90+});
91+}
92+4893function setMinimalCurrentConversationRegistry(): void {
4994setActivePluginRegistry(
5095createTestRegistry([
@@ -82,6 +127,7 @@ describe("generic current-conversation bindings", () => {
82127testing.resetCurrentConversationBindingsForTests({
83128deletePersistedFile: true,
84129});
130+closeOpenClawStateDatabaseForTest();
85131if (previousStateDir == null) {
86132delete process.env.OPENCLAW_STATE_DIR;
87133} else {
@@ -155,31 +201,21 @@ describe("generic current-conversation bindings", () => {
155201});
156202157203it("normalizes persisted target session keys on reload", async () => {
158-const filePath = testing.resolveBindingsFilePath();
159-await fs.mkdir(path.dirname(filePath), { recursive: true });
160-await fs.writeFile(
161-filePath,
162-JSON.stringify({
163-version: 1,
164-bindings: [
165-{
166-bindingId: "generic:workspace\u241fdefault\u241f\u241fuser:U123",
167-targetSessionKey: " agent:codex:acp:workspace-dm ",
168-targetKind: "session",
169-conversation: {
170-channel: "workspace",
171-accountId: "default",
172-conversationId: "user:U123",
173-},
174-status: "active",
175-boundAt: 1234,
176-metadata: {
177-label: "workspace-dm",
178-},
179-},
180-],
181-}),
182-);
204+seedPersistedBinding({
205+bindingId: "generic:workspace\u241fdefault\u241f\u241fuser:U123",
206+targetSessionKey: " agent:codex:acp:workspace-dm ",
207+targetKind: "session",
208+conversation: {
209+channel: "workspace",
210+accountId: "default",
211+conversationId: "user:U123",
212+},
213+status: "active",
214+boundAt: 1234,
215+metadata: {
216+label: "workspace-dm",
217+},
218+});
183219184220const resolved = resolveGenericCurrentConversationBinding({
185221channel: "workspace",
@@ -237,32 +273,22 @@ describe("generic current-conversation bindings", () => {
237273});
238274239275it("migrates persisted legacy self-parent binding ids on load", async () => {
240-const filePath = testing.resolveBindingsFilePath();
241-await fs.mkdir(path.dirname(filePath), { recursive: true });
242-await fs.writeFile(
243-filePath,
244-JSON.stringify({
245-version: 1,
246-bindings: [
247-{
248-bindingId: "generic:forum\u241fdefault\u241f6098642967\u241f6098642967",
249-targetSessionKey: "agent:codex:acp:forum-dm",
250-targetKind: "session",
251-conversation: {
252-channel: "forum",
253-accountId: "default",
254-conversationId: "6098642967",
255-parentConversationId: "6098642967",
256-},
257-status: "active",
258-boundAt: 1234,
259-metadata: {
260-label: "forum-dm",
261-},
262-},
263-],
264-}),
265-);
276+seedPersistedBinding({
277+bindingId: "generic:forum\u241fdefault\u241f6098642967\u241f6098642967",
278+targetSessionKey: "agent:codex:acp:forum-dm",
279+targetKind: "session",
280+conversation: {
281+channel: "forum",
282+accountId: "default",
283+conversationId: "6098642967",
284+parentConversationId: "6098642967",
285+},
286+status: "active",
287+boundAt: 1234,
288+metadata: {
289+label: "forum-dm",
290+},
291+});
266292267293const resolved = resolveGenericCurrentConversationBinding({
268294channel: "forum",
@@ -328,29 +354,19 @@ describe("generic current-conversation bindings", () => {
328354});
329355330356it("drops persisted bindings with invalid expiration timestamps", async () => {
331-const filePath = testing.resolveBindingsFilePath();
332-await fs.mkdir(path.dirname(filePath), { recursive: true });
333-await fs.writeFile(
334-filePath,
335-JSON.stringify({
336-version: 1,
337-bindings: [
338-{
339-bindingId: "generic:workspace\u241fdefault\u241f\u241fuser:U123",
340-targetSessionKey: "agent:codex:acp:workspace-dm",
341-targetKind: "session",
342-conversation: {
343-channel: "workspace",
344-accountId: "default",
345-conversationId: "user:U123",
346-},
347-status: "active",
348-boundAt: 1234,
349-expiresAt: 8_640_000_000_000_001,
350-},
351-],
352-}),
353-);
357+seedPersistedBinding({
358+bindingId: "generic:workspace\u241fdefault\u241f\u241fuser:U123",
359+targetSessionKey: "agent:codex:acp:workspace-dm",
360+targetKind: "session",
361+conversation: {
362+channel: "workspace",
363+accountId: "default",
364+conversationId: "user:U123",
365+},
366+status: "active",
367+boundAt: 1234,
368+expiresAt: 8_640_000_000_000_001,
369+});
354370355371expect(
356372resolveGenericCurrentConversationBinding({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。