@xmoxmo/bncr 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/index.ts +30 -15
- package/package.json +4 -3
- package/scripts/check-pack.mjs +77 -0
- package/scripts/selfcheck.mjs +10 -0
- package/src/channel.ts +398 -642
- package/src/core/extended-diagnostics.ts +10 -0
- package/src/core/file-ack.ts +9 -0
- package/src/core/file-transfer-payloads.ts +72 -0
- package/src/core/register-trace.ts +79 -0
- package/src/core/targets.ts +10 -1
- package/src/messaging/inbound/commands.ts +20 -10
- package/src/messaging/inbound/context-facts.ts +200 -0
- package/src/messaging/inbound/dispatch.ts +66 -14
- package/src/messaging/inbound/gate.ts +66 -26
- package/src/messaging/inbound/runtime-compat.ts +41 -0
- package/src/messaging/inbound/session-label.ts +7 -7
- package/src/messaging/outbound/durable-message-adapter.ts +107 -0
- package/src/messaging/outbound/durable-queue-adapter.ts +157 -0
- package/src/messaging/outbound/session-route.ts +2 -2
- package/src/openclaw/config-runtime.ts +52 -0
- package/src/openclaw/inbound-session-runtime.ts +94 -0
- package/src/openclaw/ingress-runtime.ts +35 -0
- package/src/openclaw/media-runtime.ts +73 -0
- package/src/openclaw/reply-runtime.ts +104 -0
- package/src/openclaw/routing-runtime.ts +48 -0
- package/src/openclaw/sdk-helpers.ts +20 -0
- package/src/openclaw/session-route-runtime.ts +15 -0
- package/src/plugin/capabilities.ts +8 -0
- package/src/plugin/config.ts +35 -0
- package/src/plugin/gateway-methods.ts +12 -0
- package/src/plugin/gateway-runtime.ts +11 -0
- package/src/plugin/message-policy.ts +4 -0
- package/src/plugin/message-send.ts +13 -0
- package/src/plugin/messaging.ts +142 -0
- package/src/plugin/meta.ts +10 -0
- package/src/plugin/outbound.ts +51 -0
- package/src/plugin/setup.ts +24 -0
- package/src/plugin/status.ts +38 -0
- package/src/runtime/log-dedupe.ts +56 -0
- package/src/runtime/outbound-ack-timeout.ts +96 -0
- package/src/runtime/outbound-flags.ts +81 -0
- package/src/runtime/outbox-transitions.ts +119 -0
- package/src/runtime/status-snapshots.ts +108 -0
- package/src/runtime/status-worker.ts +172 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
type RuntimeConfigApi = {
|
|
2
|
+
current?: () => unknown;
|
|
3
|
+
get?: () => unknown;
|
|
4
|
+
mutateConfigFile?: (params: {
|
|
5
|
+
afterWrite?: { mode?: string };
|
|
6
|
+
mutate: (draft: Record<string, unknown>) => void;
|
|
7
|
+
}) => Promise<unknown> | unknown;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
type RuntimeApiHolder = {
|
|
11
|
+
runtime?: {
|
|
12
|
+
config?: RuntimeConfigApi;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function resolveConfigApi(api: RuntimeApiHolder): RuntimeConfigApi {
|
|
17
|
+
const config = api?.runtime?.config;
|
|
18
|
+
if (!config) throw new Error('OpenClaw runtime config API is unavailable');
|
|
19
|
+
return config;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getOpenClawRuntimeConfig(api: RuntimeApiHolder): unknown {
|
|
23
|
+
const config = resolveConfigApi(api);
|
|
24
|
+
if (typeof config.current === 'function') return config.current();
|
|
25
|
+
if (typeof config.get === 'function') return config.get();
|
|
26
|
+
throw new Error('OpenClaw runtime config read API is unavailable');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getOpenClawRuntimeConfigOrDefault<T>(
|
|
30
|
+
api: RuntimeApiHolder,
|
|
31
|
+
fallback: T,
|
|
32
|
+
): unknown | T {
|
|
33
|
+
try {
|
|
34
|
+
return getOpenClawRuntimeConfig(api);
|
|
35
|
+
} catch {
|
|
36
|
+
return fallback;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function mutateOpenClawRuntimeConfigFile(
|
|
41
|
+
api: RuntimeApiHolder,
|
|
42
|
+
params: {
|
|
43
|
+
afterWrite?: { mode?: string };
|
|
44
|
+
mutate: (draft: Record<string, unknown>) => void;
|
|
45
|
+
},
|
|
46
|
+
): Promise<unknown> {
|
|
47
|
+
const config = resolveConfigApi(api);
|
|
48
|
+
if (typeof config.mutateConfigFile !== 'function') {
|
|
49
|
+
throw new Error('OpenClaw runtime config mutate API is unavailable');
|
|
50
|
+
}
|
|
51
|
+
return config.mutateConfigFile(params);
|
|
52
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { recordInboundSession as sdkRecordInboundSession } from 'openclaw/plugin-sdk/conversation-runtime';
|
|
2
|
+
import { resolvePinnedMainDmOwnerFromAllowlist as sdkResolvePinnedMainDmOwnerFromAllowlist } from 'openclaw/plugin-sdk/security-runtime';
|
|
3
|
+
import {
|
|
4
|
+
recordSessionMetaFromInbound as sdkRecordSessionMetaFromInbound,
|
|
5
|
+
resolveStorePath as sdkResolveStorePath,
|
|
6
|
+
updateSessionStoreEntry as sdkUpdateSessionStoreEntry,
|
|
7
|
+
} from 'openclaw/plugin-sdk/session-store-runtime';
|
|
8
|
+
|
|
9
|
+
type ResolveStorePathFn = (storeConfig: unknown, options: { agentId: string }) => string;
|
|
10
|
+
type RecordInboundSessionFn = typeof sdkRecordInboundSession;
|
|
11
|
+
type RecordSessionMetaFromInboundFn = typeof sdkRecordSessionMetaFromInbound;
|
|
12
|
+
type UpdateSessionStoreEntryFn = typeof sdkUpdateSessionStoreEntry;
|
|
13
|
+
type ReadSessionUpdatedAtFn = (params: { storePath: string; sessionKey: string }) => unknown;
|
|
14
|
+
type ResolvePinnedMainDmOwnerFromAllowlistFn = typeof sdkResolvePinnedMainDmOwnerFromAllowlist;
|
|
15
|
+
|
|
16
|
+
type BncrInboundSessionRuntime = {
|
|
17
|
+
resolveStorePath: ResolveStorePathFn;
|
|
18
|
+
recordInboundSession: RecordInboundSessionFn;
|
|
19
|
+
recordSessionMetaFromInbound: RecordSessionMetaFromInboundFn;
|
|
20
|
+
updateSessionStoreEntry: UpdateSessionStoreEntryFn;
|
|
21
|
+
readSessionUpdatedAt?: ReadSessionUpdatedAtFn;
|
|
22
|
+
resolvePinnedMainDmOwnerFromAllowlist: ResolvePinnedMainDmOwnerFromAllowlistFn;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
let testRuntimeOverride: Partial<BncrInboundSessionRuntime> | null = null;
|
|
26
|
+
|
|
27
|
+
function resolveRuntime(): BncrInboundSessionRuntime {
|
|
28
|
+
return {
|
|
29
|
+
resolveStorePath: testRuntimeOverride?.resolveStorePath ?? sdkResolveStorePath,
|
|
30
|
+
recordInboundSession: testRuntimeOverride?.recordInboundSession ?? sdkRecordInboundSession,
|
|
31
|
+
recordSessionMetaFromInbound:
|
|
32
|
+
testRuntimeOverride?.recordSessionMetaFromInbound ?? sdkRecordSessionMetaFromInbound,
|
|
33
|
+
updateSessionStoreEntry:
|
|
34
|
+
testRuntimeOverride?.updateSessionStoreEntry ?? sdkUpdateSessionStoreEntry,
|
|
35
|
+
readSessionUpdatedAt: testRuntimeOverride?.readSessionUpdatedAt,
|
|
36
|
+
resolvePinnedMainDmOwnerFromAllowlist:
|
|
37
|
+
testRuntimeOverride?.resolvePinnedMainDmOwnerFromAllowlist ??
|
|
38
|
+
sdkResolvePinnedMainDmOwnerFromAllowlist,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveBncrInboundSessionStorePath(args: {
|
|
43
|
+
storeConfig: unknown;
|
|
44
|
+
agentId: string;
|
|
45
|
+
}): string {
|
|
46
|
+
return resolveRuntime().resolveStorePath(args.storeConfig, { agentId: args.agentId });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function recordBncrInboundSession(
|
|
50
|
+
params: Parameters<RecordInboundSessionFn>[0],
|
|
51
|
+
): ReturnType<RecordInboundSessionFn> {
|
|
52
|
+
return resolveRuntime().recordInboundSession(params);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function recordBncrSessionMetaFromInbound(
|
|
56
|
+
params: Parameters<RecordSessionMetaFromInboundFn>[0],
|
|
57
|
+
): ReturnType<RecordSessionMetaFromInboundFn> {
|
|
58
|
+
return resolveRuntime().recordSessionMetaFromInbound(params);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function updateBncrSessionStoreEntry(
|
|
62
|
+
params: Parameters<UpdateSessionStoreEntryFn>[0],
|
|
63
|
+
): ReturnType<UpdateSessionStoreEntryFn> {
|
|
64
|
+
return resolveRuntime().updateSessionStoreEntry(params);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function readBncrSessionUpdatedAt(
|
|
68
|
+
api: { runtime?: { channel?: { session?: { readSessionUpdatedAt?: ReadSessionUpdatedAtFn } } } },
|
|
69
|
+
params: { storePath: string; sessionKey: string },
|
|
70
|
+
): unknown {
|
|
71
|
+
const runtime = resolveRuntime();
|
|
72
|
+
if (runtime.readSessionUpdatedAt) return runtime.readSessionUpdatedAt(params);
|
|
73
|
+
const readSessionUpdatedAt = api?.runtime?.channel?.session?.readSessionUpdatedAt;
|
|
74
|
+
if (typeof readSessionUpdatedAt !== 'function') {
|
|
75
|
+
throw new Error('OpenClaw channel session readSessionUpdatedAt API is unavailable');
|
|
76
|
+
}
|
|
77
|
+
return readSessionUpdatedAt(params);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function resolveBncrPinnedMainDmOwnerFromAllowlist(
|
|
81
|
+
params: Parameters<ResolvePinnedMainDmOwnerFromAllowlistFn>[0],
|
|
82
|
+
): ReturnType<ResolvePinnedMainDmOwnerFromAllowlistFn> {
|
|
83
|
+
return resolveRuntime().resolvePinnedMainDmOwnerFromAllowlist(params);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function setBncrInboundSessionRuntimeForTest(
|
|
87
|
+
runtime: Partial<BncrInboundSessionRuntime> | null,
|
|
88
|
+
): () => void {
|
|
89
|
+
const previous = testRuntimeOverride;
|
|
90
|
+
testRuntimeOverride = runtime;
|
|
91
|
+
return () => {
|
|
92
|
+
testRuntimeOverride = previous;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineStableChannelIngressIdentity as sdkDefineStableChannelIngressIdentity,
|
|
3
|
+
resolveChannelMessageIngress as sdkResolveChannelMessageIngress,
|
|
4
|
+
} from 'openclaw/plugin-sdk/channel-ingress-runtime';
|
|
5
|
+
|
|
6
|
+
export function defineOpenClawStableChannelIngressIdentity(params: {
|
|
7
|
+
key: string;
|
|
8
|
+
kind: string;
|
|
9
|
+
normalize: (value: string) => string | null;
|
|
10
|
+
sensitivity: 'public' | 'private' | 'pii' | string;
|
|
11
|
+
entryIdPrefix?: string;
|
|
12
|
+
aliases?: Array<{
|
|
13
|
+
key: string;
|
|
14
|
+
kind: string;
|
|
15
|
+
normalize: (value: string) => string | null;
|
|
16
|
+
sensitivity: 'public' | 'private' | 'pii' | string;
|
|
17
|
+
}>;
|
|
18
|
+
}) {
|
|
19
|
+
return sdkDefineStableChannelIngressIdentity(params as any);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function resolveOpenClawChannelMessageIngress(params: {
|
|
23
|
+
channelId: string;
|
|
24
|
+
accountId: string;
|
|
25
|
+
identity: unknown;
|
|
26
|
+
subject: unknown;
|
|
27
|
+
conversation: unknown;
|
|
28
|
+
event: unknown;
|
|
29
|
+
policy: unknown;
|
|
30
|
+
allowFrom?: string[];
|
|
31
|
+
groupAllowFrom?: string[];
|
|
32
|
+
accessGroups?: unknown;
|
|
33
|
+
}): Promise<any> {
|
|
34
|
+
return sdkResolveChannelMessageIngress(params as any);
|
|
35
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
type RuntimeMediaLoaded = {
|
|
2
|
+
buffer: Buffer;
|
|
3
|
+
contentType?: string;
|
|
4
|
+
fileName?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
type RuntimeMediaApi = {
|
|
8
|
+
loadWebMedia?: (
|
|
9
|
+
mediaUrl: string,
|
|
10
|
+
options?: { localRoots?: readonly string[]; maxBytes?: number },
|
|
11
|
+
) => Promise<RuntimeMediaLoaded>;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
type RuntimeChannelMediaApi = {
|
|
15
|
+
readRemoteMediaBuffer?: (options: {
|
|
16
|
+
url: string;
|
|
17
|
+
maxBytes?: number;
|
|
18
|
+
}) => Promise<RuntimeMediaLoaded>;
|
|
19
|
+
saveMediaBuffer?: (
|
|
20
|
+
buffer: Buffer,
|
|
21
|
+
mimeType: string | undefined,
|
|
22
|
+
direction: 'inbound' | 'outbound',
|
|
23
|
+
maxBytes: number,
|
|
24
|
+
fileName?: string,
|
|
25
|
+
) => Promise<{ path: string }>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type RuntimeApiHolder = {
|
|
29
|
+
runtime?: {
|
|
30
|
+
media?: RuntimeMediaApi;
|
|
31
|
+
channel?: {
|
|
32
|
+
media?: RuntimeChannelMediaApi;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type OpenClawLoadedMedia = RuntimeMediaLoaded;
|
|
38
|
+
|
|
39
|
+
export function isOpenClawRemoteHttpMediaUrl(mediaUrl: string): boolean {
|
|
40
|
+
return /^https?:\/\//i.test(String(mediaUrl || '').trim());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function loadOpenClawWebMedia(
|
|
44
|
+
api: RuntimeApiHolder,
|
|
45
|
+
mediaUrl: string,
|
|
46
|
+
options?: { localRoots?: readonly string[]; maxBytes?: number },
|
|
47
|
+
): Promise<RuntimeMediaLoaded> {
|
|
48
|
+
const readRemoteMediaBuffer = api?.runtime?.channel?.media?.readRemoteMediaBuffer;
|
|
49
|
+
if (isOpenClawRemoteHttpMediaUrl(mediaUrl) && typeof readRemoteMediaBuffer === 'function') {
|
|
50
|
+
return readRemoteMediaBuffer({ url: mediaUrl, maxBytes: options?.maxBytes });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const loadWebMedia = api?.runtime?.media?.loadWebMedia;
|
|
54
|
+
if (typeof loadWebMedia !== 'function') {
|
|
55
|
+
throw new Error('OpenClaw runtime media loadWebMedia API is unavailable');
|
|
56
|
+
}
|
|
57
|
+
return loadWebMedia(mediaUrl, options);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function saveOpenClawChannelMediaBuffer(
|
|
61
|
+
api: RuntimeApiHolder,
|
|
62
|
+
buffer: Buffer,
|
|
63
|
+
mimeType: string | undefined,
|
|
64
|
+
direction: 'inbound' | 'outbound',
|
|
65
|
+
maxBytes: number,
|
|
66
|
+
fileName?: string,
|
|
67
|
+
): Promise<{ path: string }> {
|
|
68
|
+
const saveMediaBuffer = api?.runtime?.channel?.media?.saveMediaBuffer;
|
|
69
|
+
if (typeof saveMediaBuffer !== 'function') {
|
|
70
|
+
throw new Error('OpenClaw channel media saveMediaBuffer API is unavailable');
|
|
71
|
+
}
|
|
72
|
+
return saveMediaBuffer(buffer, mimeType, direction, maxBytes, fileName);
|
|
73
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
type RuntimeReplyApi = {
|
|
2
|
+
formatAgentEnvelope?: (params: {
|
|
3
|
+
channel: string;
|
|
4
|
+
from: string;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
previousTimestamp?: unknown;
|
|
7
|
+
envelope?: unknown;
|
|
8
|
+
body: string;
|
|
9
|
+
}) => string;
|
|
10
|
+
resolveEnvelopeFormatOptions?: (cfg: unknown) => unknown;
|
|
11
|
+
dispatchReplyWithBufferedBlockDispatcher?: (params: {
|
|
12
|
+
ctx: unknown;
|
|
13
|
+
cfg: unknown;
|
|
14
|
+
dispatcherOptions: {
|
|
15
|
+
deliver: (
|
|
16
|
+
payload: {
|
|
17
|
+
text?: string;
|
|
18
|
+
mediaUrl?: string;
|
|
19
|
+
mediaUrls?: string[];
|
|
20
|
+
audioAsVoice?: boolean;
|
|
21
|
+
},
|
|
22
|
+
info?: { kind?: 'tool' | 'block' | 'final' },
|
|
23
|
+
) => Promise<void> | void;
|
|
24
|
+
onError?: (err: unknown) => void;
|
|
25
|
+
};
|
|
26
|
+
replyOptions?: {
|
|
27
|
+
disableBlockStreaming?: boolean;
|
|
28
|
+
shouldEmitToolResult?: () => boolean;
|
|
29
|
+
};
|
|
30
|
+
}) => Promise<unknown> | unknown;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type RuntimeApiHolder = {
|
|
34
|
+
runtime?: {
|
|
35
|
+
channel?: {
|
|
36
|
+
reply?: RuntimeReplyApi;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function resolveReplyApi(api: RuntimeApiHolder): RuntimeReplyApi {
|
|
42
|
+
const reply = api?.runtime?.channel?.reply;
|
|
43
|
+
if (!reply) throw new Error('OpenClaw channel reply API is unavailable');
|
|
44
|
+
return reply;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function resolveOpenClawEnvelopeFormatOptions(
|
|
48
|
+
api: RuntimeApiHolder,
|
|
49
|
+
cfg: unknown,
|
|
50
|
+
): unknown {
|
|
51
|
+
const reply = resolveReplyApi(api);
|
|
52
|
+
if (typeof reply.resolveEnvelopeFormatOptions !== 'function') {
|
|
53
|
+
throw new Error('OpenClaw channel reply resolveEnvelopeFormatOptions API is unavailable');
|
|
54
|
+
}
|
|
55
|
+
return reply.resolveEnvelopeFormatOptions(cfg);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function formatOpenClawAgentEnvelope(
|
|
59
|
+
api: RuntimeApiHolder,
|
|
60
|
+
params: {
|
|
61
|
+
channel: string;
|
|
62
|
+
from: string;
|
|
63
|
+
timestamp: number;
|
|
64
|
+
previousTimestamp?: unknown;
|
|
65
|
+
envelope?: unknown;
|
|
66
|
+
body: string;
|
|
67
|
+
},
|
|
68
|
+
): string {
|
|
69
|
+
const reply = resolveReplyApi(api);
|
|
70
|
+
if (typeof reply.formatAgentEnvelope !== 'function') {
|
|
71
|
+
throw new Error('OpenClaw channel reply formatAgentEnvelope API is unavailable');
|
|
72
|
+
}
|
|
73
|
+
return reply.formatAgentEnvelope(params);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function dispatchOpenClawReplyWithBufferedBlockDispatcher(
|
|
77
|
+
api: RuntimeApiHolder,
|
|
78
|
+
params: {
|
|
79
|
+
ctx: unknown;
|
|
80
|
+
cfg: unknown;
|
|
81
|
+
dispatcherOptions: {
|
|
82
|
+
deliver: (
|
|
83
|
+
payload: {
|
|
84
|
+
text?: string;
|
|
85
|
+
mediaUrl?: string;
|
|
86
|
+
mediaUrls?: string[];
|
|
87
|
+
audioAsVoice?: boolean;
|
|
88
|
+
},
|
|
89
|
+
info?: { kind?: 'tool' | 'block' | 'final' },
|
|
90
|
+
) => Promise<void> | void;
|
|
91
|
+
onError?: (err: unknown) => void;
|
|
92
|
+
};
|
|
93
|
+
replyOptions?: {
|
|
94
|
+
disableBlockStreaming?: boolean;
|
|
95
|
+
shouldEmitToolResult?: () => boolean;
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
): Promise<unknown> {
|
|
99
|
+
const reply = resolveReplyApi(api);
|
|
100
|
+
if (typeof reply.dispatchReplyWithBufferedBlockDispatcher !== 'function') {
|
|
101
|
+
throw new Error('OpenClaw channel reply dispatchReplyWithBufferedBlockDispatcher API is unavailable');
|
|
102
|
+
}
|
|
103
|
+
return reply.dispatchReplyWithBufferedBlockDispatcher(params);
|
|
104
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { resolveInboundLastRouteSessionKey } from 'openclaw/plugin-sdk/routing';
|
|
2
|
+
|
|
3
|
+
type RuntimeRoutingApi = {
|
|
4
|
+
resolveAgentRoute?: (params: {
|
|
5
|
+
cfg: any;
|
|
6
|
+
channel: string;
|
|
7
|
+
accountId: string;
|
|
8
|
+
peer: unknown;
|
|
9
|
+
}) => any;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type RuntimeApiHolder = {
|
|
13
|
+
runtime?: {
|
|
14
|
+
channel?: {
|
|
15
|
+
routing?: RuntimeRoutingApi;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function resolveRoutingApi(api: RuntimeApiHolder): RuntimeRoutingApi {
|
|
21
|
+
const routing = api?.runtime?.channel?.routing;
|
|
22
|
+
if (!routing) throw new Error('OpenClaw channel routing API is unavailable');
|
|
23
|
+
return routing;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolveOpenClawAgentRoute(
|
|
27
|
+
api: RuntimeApiHolder,
|
|
28
|
+
params: {
|
|
29
|
+
cfg: any;
|
|
30
|
+
channel: string;
|
|
31
|
+
accountId: string;
|
|
32
|
+
peer: unknown;
|
|
33
|
+
},
|
|
34
|
+
): any {
|
|
35
|
+
const routing = resolveRoutingApi(api);
|
|
36
|
+
if (typeof routing.resolveAgentRoute !== 'function') {
|
|
37
|
+
throw new Error('OpenClaw channel routing resolveAgentRoute API is unavailable');
|
|
38
|
+
}
|
|
39
|
+
return routing.resolveAgentRoute(params);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveOpenClawInboundLastRouteSessionKey(params: {
|
|
43
|
+
route: unknown;
|
|
44
|
+
sessionKey: string;
|
|
45
|
+
}): string {
|
|
46
|
+
return resolveInboundLastRouteSessionKey(params as any);
|
|
47
|
+
}
|
|
48
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readBooleanParam as sdkReadBooleanParam } from 'openclaw/plugin-sdk/boolean-param';
|
|
2
|
+
import {
|
|
3
|
+
applyAccountNameToChannelSection as sdkApplyAccountNameToChannelSection,
|
|
4
|
+
jsonResult as sdkJsonResult,
|
|
5
|
+
setAccountEnabledInConfigSection as sdkSetAccountEnabledInConfigSection,
|
|
6
|
+
} from 'openclaw/plugin-sdk/core';
|
|
7
|
+
import { readJsonFileWithFallback as sdkReadJsonFileWithFallback, writeJsonFileAtomically as sdkWriteJsonFileAtomically } from 'openclaw/plugin-sdk/json-store';
|
|
8
|
+
import { readStringParam as sdkReadStringParam } from 'openclaw/plugin-sdk/param-readers';
|
|
9
|
+
import { createDefaultChannelRuntimeState as sdkCreateDefaultChannelRuntimeState } from 'openclaw/plugin-sdk/status-helpers';
|
|
10
|
+
import { extractToolSend as sdkExtractToolSend } from 'openclaw/plugin-sdk/tool-send';
|
|
11
|
+
|
|
12
|
+
export const readOpenClawBooleanParam = sdkReadBooleanParam;
|
|
13
|
+
export const readOpenClawStringParam = sdkReadStringParam;
|
|
14
|
+
export const readOpenClawJsonFileWithFallback = sdkReadJsonFileWithFallback;
|
|
15
|
+
export const writeOpenClawJsonFileAtomically = sdkWriteJsonFileAtomically;
|
|
16
|
+
export const createOpenClawDefaultChannelRuntimeState = sdkCreateDefaultChannelRuntimeState;
|
|
17
|
+
export const extractOpenClawToolSend = sdkExtractToolSend;
|
|
18
|
+
export const openClawJsonResult = sdkJsonResult;
|
|
19
|
+
export const applyOpenClawAccountNameToChannelSection = sdkApplyAccountNameToChannelSection;
|
|
20
|
+
export const setOpenClawAccountEnabledInConfigSection = sdkSetAccountEnabledInConfigSection;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { buildChannelOutboundSessionRoute } from 'openclaw/plugin-sdk/core';
|
|
2
|
+
|
|
3
|
+
export function buildOpenClawChannelOutboundSessionRoute(params: {
|
|
4
|
+
cfg: any;
|
|
5
|
+
agentId: string;
|
|
6
|
+
channel: string;
|
|
7
|
+
accountId?: string;
|
|
8
|
+
peer: unknown;
|
|
9
|
+
chatType: 'direct' | 'group';
|
|
10
|
+
from: string;
|
|
11
|
+
to: string;
|
|
12
|
+
threadId?: string;
|
|
13
|
+
}): Record<string, unknown> {
|
|
14
|
+
return buildChannelOutboundSessionRoute(params as any) as Record<string, unknown>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CHANNEL_ID,
|
|
3
|
+
listAccountIds,
|
|
4
|
+
resolveAccount,
|
|
5
|
+
resolveDefaultDisplayName,
|
|
6
|
+
} from '../core/accounts.ts';
|
|
7
|
+
import { resolveBncrChannelPolicy } from '../core/policy.ts';
|
|
8
|
+
import { setOpenClawAccountEnabledInConfigSection } from '../openclaw/sdk-helpers.ts';
|
|
9
|
+
|
|
10
|
+
export const BNCR_CONFIG_SURFACE = {
|
|
11
|
+
listAccountIds,
|
|
12
|
+
resolveAccount,
|
|
13
|
+
setAccountEnabled: ({ cfg, accountId, enabled }: any) =>
|
|
14
|
+
setOpenClawAccountEnabledInConfigSection({
|
|
15
|
+
cfg,
|
|
16
|
+
sectionKey: CHANNEL_ID,
|
|
17
|
+
accountId,
|
|
18
|
+
enabled,
|
|
19
|
+
allowTopLevel: true,
|
|
20
|
+
}),
|
|
21
|
+
isEnabled: (account: any, cfg: any) => {
|
|
22
|
+
const policy = resolveBncrChannelPolicy(cfg?.channels?.[CHANNEL_ID] || {});
|
|
23
|
+
return policy.enabled !== false && account?.enabled !== false;
|
|
24
|
+
},
|
|
25
|
+
isConfigured: () => true,
|
|
26
|
+
describeAccount: (account: any) => {
|
|
27
|
+
const displayName = resolveDefaultDisplayName(account?.name, account?.accountId);
|
|
28
|
+
return {
|
|
29
|
+
accountId: account.accountId,
|
|
30
|
+
name: displayName,
|
|
31
|
+
enabled: account.enabled !== false,
|
|
32
|
+
configured: true,
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type BncrGatewayAccountBridge = {
|
|
2
|
+
channelStartAccount: (ctx: any) => unknown | Promise<unknown>;
|
|
3
|
+
channelStopAccount: (ctx: any) => unknown | Promise<unknown>;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export function createBncrGatewayRuntime(getBridge: () => BncrGatewayAccountBridge) {
|
|
7
|
+
return {
|
|
8
|
+
startAccount: async (ctx: any) => getBridge().channelStartAccount(ctx),
|
|
9
|
+
stopAccount: async (ctx: any) => getBridge().channelStopAccount(ctx),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type BncrMessageSendBridge = {
|
|
2
|
+
channelMessageSendText: (ctx: any) => unknown | Promise<unknown>;
|
|
3
|
+
channelMessageSendMedia: (ctx: any) => unknown | Promise<unknown>;
|
|
4
|
+
channelMessageSendPayload: (ctx: any) => unknown | Promise<unknown>;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function createBncrMessageSend(getBridge: () => BncrMessageSendBridge) {
|
|
8
|
+
return {
|
|
9
|
+
text: async (ctx: any) => getBridge().channelMessageSendText(ctx),
|
|
10
|
+
media: async (ctx: any) => getBridge().channelMessageSendMedia(ctx),
|
|
11
|
+
payload: async (ctx: any) => getBridge().channelMessageSendPayload(ctx),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { BNCR_DEFAULT_ACCOUNT_ID, normalizeAccountId } from '../core/accounts.ts';
|
|
2
|
+
import {
|
|
3
|
+
formatDisplayScope,
|
|
4
|
+
formatTargetDisplay,
|
|
5
|
+
parseExplicitTarget,
|
|
6
|
+
} from '../core/targets.ts';
|
|
7
|
+
import { resolveBncrOutboundSessionRoute } from '../messaging/outbound/session-route.ts';
|
|
8
|
+
import {
|
|
9
|
+
looksLikeBncrExplicitTarget,
|
|
10
|
+
resolveBncrOutboundTarget,
|
|
11
|
+
} from '../messaging/outbound/target-resolver.ts';
|
|
12
|
+
|
|
13
|
+
type BncrMessagingRuntimeBridge = {
|
|
14
|
+
canonicalAgentId?: string;
|
|
15
|
+
ensureCanonicalAgentId: (params: { cfg: any; accountId: string }) => string;
|
|
16
|
+
resolveRouteBySession: (raw: string, accountId: string) => any;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function asString(v: unknown, fallback = ''): string {
|
|
20
|
+
return typeof v === 'string' ? v : fallback;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function normalizeBncrMessagingTarget(raw: string) {
|
|
24
|
+
const input = asString(raw).trim();
|
|
25
|
+
return input || undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function formatBncrMessagingTargetDisplay({ target }: any) {
|
|
29
|
+
return formatTargetDisplay(target);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveMessagingAccountId(accountId: unknown) {
|
|
33
|
+
return normalizeAccountId(asString(accountId || BNCR_DEFAULT_ACCOUNT_ID));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function resolveMessagingCanonicalAgentId(
|
|
37
|
+
runtimeBridge: BncrMessagingRuntimeBridge,
|
|
38
|
+
cfg: any,
|
|
39
|
+
accountId: string,
|
|
40
|
+
) {
|
|
41
|
+
return (
|
|
42
|
+
runtimeBridge.canonicalAgentId ||
|
|
43
|
+
runtimeBridge.ensureCanonicalAgentId({ cfg, accountId })
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createBncrMessagingExplicitTargetParser(
|
|
48
|
+
getBridge: () => BncrMessagingRuntimeBridge,
|
|
49
|
+
) {
|
|
50
|
+
return ({ raw, accountId, cfg }: any) => {
|
|
51
|
+
const resolvedAccountId = resolveMessagingAccountId(accountId);
|
|
52
|
+
const runtimeBridge = getBridge();
|
|
53
|
+
const canonicalAgentId = resolveMessagingCanonicalAgentId(
|
|
54
|
+
runtimeBridge,
|
|
55
|
+
cfg,
|
|
56
|
+
resolvedAccountId,
|
|
57
|
+
);
|
|
58
|
+
return parseExplicitTarget(asString(raw).trim(), { canonicalAgentId });
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createBncrMessagingSessionTargetResolver(
|
|
63
|
+
getBridge: () => BncrMessagingRuntimeBridge,
|
|
64
|
+
) {
|
|
65
|
+
return ({ id, accountId, cfg }: any) => {
|
|
66
|
+
const raw = asString(id).trim();
|
|
67
|
+
if (!raw) return undefined;
|
|
68
|
+
const resolvedAccountId = resolveMessagingAccountId(accountId);
|
|
69
|
+
const runtimeBridge = getBridge();
|
|
70
|
+
const canonicalAgentId = resolveMessagingCanonicalAgentId(
|
|
71
|
+
runtimeBridge,
|
|
72
|
+
cfg,
|
|
73
|
+
resolvedAccountId,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
let parsed = parseExplicitTarget(raw, { canonicalAgentId });
|
|
77
|
+
if (!parsed) {
|
|
78
|
+
const route = runtimeBridge.resolveRouteBySession(raw, resolvedAccountId);
|
|
79
|
+
if (route) {
|
|
80
|
+
parsed = parseExplicitTarget(formatDisplayScope(route), { canonicalAgentId });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return parsed?.displayScope || undefined;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function createBncrMessagingOutboundSessionRouteResolver(
|
|
88
|
+
getBridge: () => BncrMessagingRuntimeBridge,
|
|
89
|
+
) {
|
|
90
|
+
return (params: any) => {
|
|
91
|
+
const accountId = resolveMessagingAccountId(params?.accountId);
|
|
92
|
+
const runtimeBridge = getBridge();
|
|
93
|
+
const canonicalAgentId = resolveMessagingCanonicalAgentId(
|
|
94
|
+
runtimeBridge,
|
|
95
|
+
params?.cfg,
|
|
96
|
+
accountId,
|
|
97
|
+
);
|
|
98
|
+
return resolveBncrOutboundSessionRoute({
|
|
99
|
+
...params,
|
|
100
|
+
canonicalAgentId,
|
|
101
|
+
resolveRouteBySession: (raw: string, acc: string) =>
|
|
102
|
+
runtimeBridge.resolveRouteBySession(raw, acc),
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function createBncrMessagingSurface(getBridge: () => BncrMessagingRuntimeBridge) {
|
|
108
|
+
return {
|
|
109
|
+
// 接收任意标签输入;不在 normalize 阶段做格式门槛,统一下沉到发送前验证。
|
|
110
|
+
normalizeTarget: normalizeBncrMessagingTarget,
|
|
111
|
+
parseExplicitTarget: createBncrMessagingExplicitTargetParser(getBridge),
|
|
112
|
+
formatTargetDisplay: formatBncrMessagingTargetDisplay,
|
|
113
|
+
resolveSessionTarget: createBncrMessagingSessionTargetResolver(getBridge),
|
|
114
|
+
resolveOutboundSessionRoute: createBncrMessagingOutboundSessionRouteResolver(getBridge),
|
|
115
|
+
targetResolver: createBncrMessagingTargetResolver(getBridge),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function createBncrMessagingTargetResolver(getBridge: () => BncrMessagingRuntimeBridge) {
|
|
120
|
+
return {
|
|
121
|
+
looksLikeId: (raw: string, normalized?: string) => {
|
|
122
|
+
return looksLikeBncrExplicitTarget(asString(normalized || raw).trim());
|
|
123
|
+
},
|
|
124
|
+
resolveTarget: async ({ accountId, input, normalized }: any) => {
|
|
125
|
+
const runtimeBridge = getBridge();
|
|
126
|
+
const resolved = resolveBncrOutboundTarget({
|
|
127
|
+
target: asString(normalized || input).trim(),
|
|
128
|
+
accountId: resolveMessagingAccountId(accountId),
|
|
129
|
+
resolveRouteBySession: (raw: string, acc: string) =>
|
|
130
|
+
runtimeBridge.resolveRouteBySession(raw, acc),
|
|
131
|
+
});
|
|
132
|
+
if (!resolved) return null;
|
|
133
|
+
return {
|
|
134
|
+
to: resolved.displayScope,
|
|
135
|
+
kind: resolved.kind,
|
|
136
|
+
display: resolved.displayScope,
|
|
137
|
+
source: 'normalized' as const,
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
hint: 'Standard to=Bncr:<platform>:<group>:<user> or Bncr:<platform>:<user>; sessionKey keeps existing strict/legacy compatibility, canonical sessionKey=agent:<agentId>:bncr:direct:<hex>',
|
|
141
|
+
};
|
|
142
|
+
}
|