fix(reef): discover trusted peers as conversations (#109905)

* fix(reef): discover trusted peer conversations

* test(reef): document temporary state cleanup

* test(gateway): include conversation discovery in method order

* chore: leave changelog to release automation

* fix(gateway): preserve configured conversation discovery

* fix(gateway): validate turns before session binding

* fix(gateway): preserve directory conversation identity
This commit is contained in:
Peter Steinberger
2026-07-17 13:55:30 +01:00
committed by GitHub
parent 62407b36d7
commit 8020dd3e08
26 changed files with 1337 additions and 119 deletions
+63 -1
View File
@@ -1,5 +1,20 @@
import { describe, expect, it } from "vitest";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import type { OpenKeyedStoreOptions } from "openclaw/plugin-sdk/plugin-state-runtime";
import {
createPluginStateSyncKeyedStoreForTests,
resetPluginStateStoreForTests,
} from "openclaw/plugin-sdk/plugin-state-test-runtime";
import { createPluginRuntimeMock } from "openclaw/plugin-sdk/plugin-test-runtime";
import { defaultRuntime } from "openclaw/plugin-sdk/runtime";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { generateIdentity } from "../protocol/index.js";
import { reefPlugin } from "./channel.js";
import { resolveReefConfig } from "./config-schema.js";
import { resolveReefInboundDispatchContent } from "./inbound.js";
import { setReefRuntime } from "./runtime.js";
import { openReefTrustStore } from "./trust-store.js";
describe("Reef inbound dispatch content", () => {
it("keeps provenance model-visible without storing it in the transcript body", () => {
@@ -41,3 +56,50 @@ describe("Reef inbound dispatch content", () => {
});
});
});
describe("Reef conversation directory", () => {
let stateDir = "";
beforeEach(() => {
resetPluginStateStoreForTests();
// openclaw-temp-dir: allow Reef directory tests need an on-disk state root; afterEach removes it.
stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "reef-directory-"));
const runtime = createPluginRuntimeMock();
runtime.state.openSyncKeyedStore = <T>(options: OpenKeyedStoreOptions) =>
createPluginStateSyncKeyedStoreForTests<T>("reef", {
...options,
env: { OPENCLAW_STATE_DIR: stateDir },
});
setReefRuntime(runtime);
const identity = generateIdentity();
openReefTrustStore(runtime, resolveReefConfig({ channels: { reef: { handle: "clawd" } } })).set(
"molty",
{
autonomy: "bounded",
ed25519PublicKey: identity.signing.publicKey,
x25519PublicKey: identity.encryption.publicKey,
keyEpoch: 1,
safetyNumberChanged: false,
approvedAt: 1_752_537_600_000,
},
);
});
afterEach(() => {
resetPluginStateStoreForTests();
fs.rmSync(stateDir, { recursive: true, force: true });
});
it("exposes locally trusted peers as routable directory entries", async () => {
const cfg = { channels: { reef: { handle: "clawd" } } };
await expect(
reefPlugin.directory?.listPeers?.({
cfg,
accountId: "default",
query: "@molty",
limit: 10,
runtime: defaultRuntime,
}),
).resolves.toEqual([{ kind: "user", id: "molty", name: "@molty's agent", handle: "@molty" }]);
});
});
+28 -2
View File
@@ -9,7 +9,7 @@ import {
buildChannelOutboundSessionRoute,
type ChannelPlugin,
} from "openclaw/plugin-sdk/core";
import { createEmptyChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
import { createChannelDirectoryAdapter } from "openclaw/plugin-sdk/directory-runtime";
import {
ReefChannelConfigSchema,
autonomyBudget,
@@ -64,6 +64,24 @@ function listTrustedPeers(config: ReefAccount["config"]): string[] {
: [];
}
function listTrustedPeerDirectoryEntries(params: {
config: ReefAccount["config"];
query: string | null | undefined;
limit: number | null | undefined;
}) {
const query = normalizeReefTarget(params.query ?? "") ?? params.query?.trim().toLowerCase();
const peers = listTrustedPeers(params.config).filter(
(peer) => !query || peer === query || peer.includes(query),
);
const limit = params.limit == null ? peers.length : Math.max(0, params.limit);
return peers.slice(0, limit).map((peer) => ({
kind: "user" as const,
id: peer,
name: `@${peer}'s agent`,
handle: `@${peer}`,
}));
}
function replyText(payload: unknown): string {
if (!payload || typeof payload !== "object" || !("text" in payload)) {
return "";
@@ -147,7 +165,15 @@ export const reefPlugin: ChannelPlugin<ReefAccount> = {
: null;
},
},
directory: createEmptyChannelDirectoryAdapter(),
directory: createChannelDirectoryAdapter({
listPeers: async ({ cfg, query, limit }) =>
listTrustedPeerDirectoryEntries({
config: resolveReefConfig(cfg as ReefCoreConfig),
query,
limit,
}),
listGroups: async () => [],
}),
message: reefMessageAdapter,
outbound: reefOutboundAdapter,
pairing: {