@xmoxmo/bncr 0.2.6 → 0.2.7
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 +61 -0
- package/scripts/selfcheck.mjs +10 -0
- package/src/channel.ts +130 -46
- 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 +39 -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
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { defineChannelMessageAdapter } from 'openclaw/plugin-sdk/channel-outbound';
|
|
2
|
+
import type {
|
|
3
|
+
ChannelMessageAdapterShape,
|
|
4
|
+
ChannelMessageSendMediaContext,
|
|
5
|
+
ChannelMessageSendPayloadContext,
|
|
6
|
+
ChannelMessageSendResult,
|
|
7
|
+
ChannelMessageSendTextContext,
|
|
8
|
+
} from 'openclaw/plugin-sdk/channel-outbound';
|
|
9
|
+
|
|
10
|
+
import { buildFileTransferOutboxEntry, buildTextOutboxEntry } from '../../core/outbox-entry-builders.ts';
|
|
11
|
+
import type { BncrRoute, OutboxEntry } from '../../core/types.ts';
|
|
12
|
+
import { buildBncrDurableQueuedResult } from './durable-queue-adapter.ts';
|
|
13
|
+
|
|
14
|
+
export type BncrDurableMessageQueuedAdapterDeps<TConfig = unknown> = {
|
|
15
|
+
enqueueText: (ctx: ChannelMessageSendTextContext<TConfig>) => Promise<OutboxEntry> | OutboxEntry;
|
|
16
|
+
enqueueMedia?: (ctx: ChannelMessageSendMediaContext<TConfig>) => Promise<OutboxEntry> | OutboxEntry;
|
|
17
|
+
enqueuePayload?: (ctx: ChannelMessageSendPayloadContext<TConfig>) => Promise<OutboxEntry> | OutboxEntry;
|
|
18
|
+
now?: () => number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type BncrDurableMessageQueuedAdapterBuilderDeps<TConfig = unknown> = {
|
|
22
|
+
createMessageId: () => string;
|
|
23
|
+
now: () => number;
|
|
24
|
+
normalizeAccountId: (accountId?: string | null) => string;
|
|
25
|
+
normalizeReplyToId: (value?: string | null) => string;
|
|
26
|
+
resolveTarget: (ctx: ChannelMessageSendTextContext<TConfig>) => {
|
|
27
|
+
route: BncrRoute;
|
|
28
|
+
sessionKey: string;
|
|
29
|
+
accountId?: string | null;
|
|
30
|
+
};
|
|
31
|
+
filePushEvent?: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// This adapter intentionally models only the OpenClaw -> bncr-plugin handoff.
|
|
35
|
+
// Once a message is accepted into bncr's own outbox, OpenClaw should stop managing it;
|
|
36
|
+
// client/platform ACK, retry, and deadLetter remain owned by the bncr service framework.
|
|
37
|
+
export function createBncrDurableMessageQueuedAdapter<TConfig = unknown>(
|
|
38
|
+
deps: BncrDurableMessageQueuedAdapterDeps<TConfig>,
|
|
39
|
+
): ChannelMessageAdapterShape<TConfig, ChannelMessageSendResult> {
|
|
40
|
+
return defineChannelMessageAdapter({
|
|
41
|
+
id: 'bncr-queued-outbox',
|
|
42
|
+
receive: {
|
|
43
|
+
defaultAckPolicy: 'manual',
|
|
44
|
+
supportedAckPolicies: ['manual'],
|
|
45
|
+
},
|
|
46
|
+
send: {
|
|
47
|
+
text: async (ctx) => toChannelMessageSendResult(await deps.enqueueText(ctx), deps.now),
|
|
48
|
+
media: deps.enqueueMedia
|
|
49
|
+
? async (ctx) => toChannelMessageSendResult(await deps.enqueueMedia?.(ctx), deps.now)
|
|
50
|
+
: undefined,
|
|
51
|
+
payload: deps.enqueuePayload
|
|
52
|
+
? async (ctx) => toChannelMessageSendResult(await deps.enqueuePayload?.(ctx), deps.now)
|
|
53
|
+
: undefined,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function createBncrDurableMessageQueuedAdapterFromBuilders<TConfig = unknown>(
|
|
59
|
+
deps: BncrDurableMessageQueuedAdapterBuilderDeps<TConfig>,
|
|
60
|
+
): ChannelMessageAdapterShape<TConfig, ChannelMessageSendResult> {
|
|
61
|
+
return createBncrDurableMessageQueuedAdapter<TConfig>({
|
|
62
|
+
now: deps.now,
|
|
63
|
+
enqueueText: (ctx) => {
|
|
64
|
+
const resolved = deps.resolveTarget(ctx);
|
|
65
|
+
return buildTextOutboxEntry({
|
|
66
|
+
createMessageId: deps.createMessageId,
|
|
67
|
+
now: deps.now,
|
|
68
|
+
normalizeAccountId: deps.normalizeAccountId,
|
|
69
|
+
normalizeReplyToId: deps.normalizeReplyToId,
|
|
70
|
+
accountId: resolved.accountId ?? ctx.accountId ?? undefined,
|
|
71
|
+
sessionKey: resolved.sessionKey,
|
|
72
|
+
route: resolved.route,
|
|
73
|
+
text: ctx.text,
|
|
74
|
+
kind: 'final',
|
|
75
|
+
replyToId: ctx.replyToId ?? undefined,
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
enqueueMedia: (ctx) => {
|
|
79
|
+
const resolved = deps.resolveTarget(ctx);
|
|
80
|
+
return buildFileTransferOutboxEntry({
|
|
81
|
+
createMessageId: deps.createMessageId,
|
|
82
|
+
now: deps.now,
|
|
83
|
+
normalizeAccountId: deps.normalizeAccountId,
|
|
84
|
+
pushEvent: deps.filePushEvent ?? 'bncr.file.push',
|
|
85
|
+
accountId: resolved.accountId ?? ctx.accountId ?? undefined,
|
|
86
|
+
sessionKey: resolved.sessionKey,
|
|
87
|
+
route: resolved.route,
|
|
88
|
+
mediaUrl: ctx.mediaUrl,
|
|
89
|
+
mediaLocalRoots: ctx.mediaLocalRoots,
|
|
90
|
+
text: ctx.text,
|
|
91
|
+
asVoice: ctx.audioAsVoice,
|
|
92
|
+
audioAsVoice: ctx.audioAsVoice,
|
|
93
|
+
kind: 'final',
|
|
94
|
+
replyToId: ctx.replyToId ?? undefined,
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function toChannelMessageSendResult(entry: OutboxEntry | undefined, now?: () => number): ChannelMessageSendResult {
|
|
101
|
+
if (!entry) throw new Error('bncr durable message adapter did not receive an outbox entry');
|
|
102
|
+
const queued = buildBncrDurableQueuedResult({ entry, sentAt: now?.() });
|
|
103
|
+
return {
|
|
104
|
+
receipt: queued.receipt as any,
|
|
105
|
+
messageId: queued.receipt.primaryPlatformMessageId,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { BncrRoute, OutboxEntry } from '../../core/types.ts';
|
|
2
|
+
import { normalizeOutboundReplyToId } from './reply-target-policy.ts';
|
|
3
|
+
|
|
4
|
+
export type BncrDurableQueuedReceipt = {
|
|
5
|
+
primaryPlatformMessageId: string;
|
|
6
|
+
platformMessageIds: string[];
|
|
7
|
+
parts: Array<{
|
|
8
|
+
platformMessageId: string;
|
|
9
|
+
kind: 'text' | 'media' | 'voice' | 'unknown';
|
|
10
|
+
index: number;
|
|
11
|
+
threadId?: string;
|
|
12
|
+
replyToId?: string;
|
|
13
|
+
raw: {
|
|
14
|
+
channel: 'bncr';
|
|
15
|
+
channelId: 'bncr';
|
|
16
|
+
messageId: string;
|
|
17
|
+
chatId: string;
|
|
18
|
+
conversationId: string;
|
|
19
|
+
timestamp: number;
|
|
20
|
+
meta: BncrDurableQueuedReceiptMeta;
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
23
|
+
threadId?: string;
|
|
24
|
+
replyToId?: string;
|
|
25
|
+
sentAt: number;
|
|
26
|
+
raw: Array<{
|
|
27
|
+
channel: 'bncr';
|
|
28
|
+
channelId: 'bncr';
|
|
29
|
+
messageId: string;
|
|
30
|
+
chatId: string;
|
|
31
|
+
conversationId: string;
|
|
32
|
+
timestamp: number;
|
|
33
|
+
meta: BncrDurableQueuedReceiptMeta;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type BncrDurableQueuedReceiptMeta = {
|
|
38
|
+
status: 'accepted';
|
|
39
|
+
deliveryStage: 'queued';
|
|
40
|
+
queue: 'bncr.outbox';
|
|
41
|
+
finalAckManagedBy: 'bncr-outbox';
|
|
42
|
+
ackSemantics: 'plugin-accepted-not-client-acked';
|
|
43
|
+
accountId: string;
|
|
44
|
+
sessionKey: string;
|
|
45
|
+
route: BncrRoute;
|
|
46
|
+
outboxPayloadType?: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type BncrDurableQueuedResult = {
|
|
50
|
+
status: 'sent';
|
|
51
|
+
results: Array<{
|
|
52
|
+
channel: 'bncr';
|
|
53
|
+
channelId: 'bncr';
|
|
54
|
+
messageId: string;
|
|
55
|
+
chatId: string;
|
|
56
|
+
conversationId: string;
|
|
57
|
+
timestamp: number;
|
|
58
|
+
meta: BncrDurableQueuedReceiptMeta;
|
|
59
|
+
}>;
|
|
60
|
+
receipt: BncrDurableQueuedReceipt;
|
|
61
|
+
payloadOutcomes: Array<{
|
|
62
|
+
index: number;
|
|
63
|
+
status: 'sent';
|
|
64
|
+
results: BncrDurableQueuedResult['results'];
|
|
65
|
+
}>;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export function buildBncrDurableQueuedResult(args: {
|
|
69
|
+
entry: OutboxEntry;
|
|
70
|
+
index?: number;
|
|
71
|
+
threadId?: string;
|
|
72
|
+
replyToId?: string;
|
|
73
|
+
sentAt?: number;
|
|
74
|
+
}): BncrDurableQueuedResult {
|
|
75
|
+
const sentAt = Number.isFinite(args.sentAt) ? Number(args.sentAt) : args.entry.createdAt;
|
|
76
|
+
const platformMessageId = args.entry.messageId;
|
|
77
|
+
const replyToId = normalizeOutboundReplyToId({ replyToId: args.replyToId ?? extractReplyToId(args.entry) }) || undefined;
|
|
78
|
+
const chatId = formatQueuedReceiptChatId(args.entry.route);
|
|
79
|
+
const meta: BncrDurableQueuedReceiptMeta = {
|
|
80
|
+
status: 'accepted',
|
|
81
|
+
deliveryStage: 'queued',
|
|
82
|
+
queue: 'bncr.outbox',
|
|
83
|
+
finalAckManagedBy: 'bncr-outbox',
|
|
84
|
+
ackSemantics: 'plugin-accepted-not-client-acked',
|
|
85
|
+
accountId: args.entry.accountId,
|
|
86
|
+
sessionKey: args.entry.sessionKey,
|
|
87
|
+
route: args.entry.route,
|
|
88
|
+
outboxPayloadType: extractPayloadType(args.entry),
|
|
89
|
+
};
|
|
90
|
+
const result = {
|
|
91
|
+
channel: 'bncr' as const,
|
|
92
|
+
channelId: 'bncr' as const,
|
|
93
|
+
messageId: platformMessageId,
|
|
94
|
+
chatId,
|
|
95
|
+
conversationId: args.entry.sessionKey,
|
|
96
|
+
timestamp: sentAt,
|
|
97
|
+
meta,
|
|
98
|
+
};
|
|
99
|
+
const receipt: BncrDurableQueuedReceipt = {
|
|
100
|
+
primaryPlatformMessageId: platformMessageId,
|
|
101
|
+
platformMessageIds: [platformMessageId],
|
|
102
|
+
parts: [
|
|
103
|
+
{
|
|
104
|
+
platformMessageId,
|
|
105
|
+
kind: inferReceiptKind(args.entry),
|
|
106
|
+
index: args.index ?? 0,
|
|
107
|
+
threadId: args.threadId,
|
|
108
|
+
replyToId,
|
|
109
|
+
raw: result,
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
threadId: args.threadId,
|
|
113
|
+
replyToId,
|
|
114
|
+
sentAt,
|
|
115
|
+
raw: [result],
|
|
116
|
+
};
|
|
117
|
+
return {
|
|
118
|
+
status: 'sent',
|
|
119
|
+
results: [result],
|
|
120
|
+
receipt,
|
|
121
|
+
payloadOutcomes: [
|
|
122
|
+
{
|
|
123
|
+
index: args.index ?? 0,
|
|
124
|
+
status: 'sent',
|
|
125
|
+
results: [result],
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function extractPayloadType(entry: OutboxEntry): string | undefined {
|
|
132
|
+
const payload = entry.payload as any;
|
|
133
|
+
return typeof payload?.type === 'string' ? payload.type : undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function extractReplyToId(entry: OutboxEntry): string | undefined {
|
|
137
|
+
const payload = entry.payload as any;
|
|
138
|
+
const metaReply = payload?._meta?.replyToId;
|
|
139
|
+
const replyToId = payload?.replyToId ?? metaReply;
|
|
140
|
+
return typeof replyToId === 'string' ? replyToId : undefined;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function inferReceiptKind(entry: OutboxEntry): 'text' | 'media' | 'voice' | 'unknown' {
|
|
144
|
+
const payload = entry.payload as any;
|
|
145
|
+
if (payload?._meta?.kind === 'file-transfer') {
|
|
146
|
+
if (payload?._meta?.asVoice === true || payload?._meta?.audioAsVoice === true) return 'voice';
|
|
147
|
+
return 'media';
|
|
148
|
+
}
|
|
149
|
+
if (payload?.message?.type === 'text') return 'text';
|
|
150
|
+
return 'unknown';
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function formatQueuedReceiptChatId(route: BncrRoute): string {
|
|
154
|
+
const platform = route.platform || 'unknown';
|
|
155
|
+
if (route.groupId) return `Bncr:${platform}:${route.groupId}:${route.userId}`;
|
|
156
|
+
return `Bncr:${platform}:${route.userId}`;
|
|
157
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { buildOpenClawChannelOutboundSessionRoute } from '../../openclaw/session-route-runtime.ts';
|
|
2
2
|
import {
|
|
3
3
|
buildCanonicalBncrSessionKey,
|
|
4
4
|
formatDisplayScope,
|
|
@@ -72,7 +72,7 @@ export function resolveBncrOutboundSessionRoute(params: ResolveBncrOutboundSessi
|
|
|
72
72
|
const sessionKey = buildCanonicalBncrSessionKey(route, canonicalAgentId);
|
|
73
73
|
const displayTo = formatDisplayScope(route);
|
|
74
74
|
|
|
75
|
-
const built =
|
|
75
|
+
const built = buildOpenClawChannelOutboundSessionRoute({
|
|
76
76
|
cfg: params.cfg,
|
|
77
77
|
agentId: canonicalAgentId,
|
|
78
78
|
channel: params.channel,
|
|
@@ -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
|
+
|