@xmoxmo/bncr 0.4.6 → 0.4.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 +8 -0
- package/package.json +1 -1
- package/src/channel.ts +41 -2
- package/src/core/targets.ts +106 -17
- package/src/messaging/inbound/commands.ts +421 -51
- package/src/messaging/inbound/context-facts.ts +126 -14
- package/src/messaging/inbound/contracts.ts +24 -0
- package/src/messaging/inbound/dispatch-prep.ts +214 -39
- package/src/messaging/inbound/dispatch.ts +71 -5
- package/src/messaging/inbound/gate.ts +56 -86
- package/src/messaging/inbound/group-history.ts +189 -0
- package/src/messaging/inbound/native-command-runtime.ts +77 -61
- package/src/messaging/inbound/native-command.ts +192 -8
- package/src/messaging/inbound/parse.ts +113 -8
- package/src/messaging/inbound/reply-dispatch-serial.ts +62 -0
- package/src/messaging/inbound/reply-dispatch.ts +252 -77
- package/src/messaging/inbound/scene-admin.ts +300 -0
- package/src/messaging/inbound/session-label.ts +122 -13
- package/src/messaging/inbound/session-meta-task.ts +17 -0
- package/src/messaging/inbound/turn-context.ts +184 -71
- package/src/openclaw/channel-runtime-contracts.ts +1 -0
- package/src/plugin/channel-components.ts +35 -1
- package/src/plugin/channel-inbound-helpers.ts +9 -2
- package/src/plugin/channel-runtime-builders-delivery.ts +24 -1
- package/src/plugin/channel-runtime-types.ts +42 -0
- package/src/plugin/file-inbound-init.ts +27 -12
- package/src/plugin/file-inbound-runtime.ts +2 -0
- package/src/plugin/inbound-acceptance.ts +82 -1
- package/src/plugin/inbound-handlers.ts +97 -2
- package/src/plugin/inbound-surface-handlers-group.ts +16 -0
- package/src/plugin/messaging.ts +22 -5
- package/src/plugin/scene-registry.ts +171 -0
- package/src/plugin/state-store.ts +133 -0
- package/src/plugin/state-transient-runtime-group.ts +5 -0
- package/src/plugin/target-runtime.ts +2 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { emitBncrLogLine } from '../../core/logging.ts';
|
|
2
|
+
import type { BncrSceneRecord } from '../../plugin/channel-runtime-types.ts';
|
|
2
3
|
import { handleBncrNativeCommand } from './commands.ts';
|
|
3
4
|
import type {
|
|
4
5
|
BncrEnqueueFromReply,
|
|
@@ -15,6 +16,12 @@ import {
|
|
|
15
16
|
prepareBncrInboundDispatch,
|
|
16
17
|
resolveBncrInboundConversation,
|
|
17
18
|
} from './dispatch-prep.ts';
|
|
19
|
+
import {
|
|
20
|
+
type BncrGroupHistoryMap,
|
|
21
|
+
clearBncrPendingGroupHistory,
|
|
22
|
+
recordBncrPendingGroupMedia,
|
|
23
|
+
recordBncrPendingGroupText,
|
|
24
|
+
} from './group-history.ts';
|
|
18
25
|
import { runBncrInboundReplyDispatch } from './reply-dispatch.ts';
|
|
19
26
|
import { buildBncrInboundTurnContext } from './turn-context.ts';
|
|
20
27
|
|
|
@@ -31,6 +38,14 @@ export async function dispatchBncrInbound(params: {
|
|
|
31
38
|
cfg: BncrInboundConfig;
|
|
32
39
|
parsed: ParsedInbound;
|
|
33
40
|
canonicalAgentId: string;
|
|
41
|
+
shouldDispatch?: boolean;
|
|
42
|
+
shouldAccumulate?: boolean;
|
|
43
|
+
resolvedAgentId?: string;
|
|
44
|
+
sceneRegistry: Map<string, BncrSceneRecord>;
|
|
45
|
+
groupHistories: BncrGroupHistoryMap;
|
|
46
|
+
defaultAdminAgentId: string;
|
|
47
|
+
defaultPublicAgentId: string;
|
|
48
|
+
now: () => number;
|
|
34
49
|
rememberSessionRoute: BncrRememberSessionRoute;
|
|
35
50
|
enqueueFromReply: BncrEnqueueFromReply;
|
|
36
51
|
setInboundActivity: (accountId: string, at: number) => void;
|
|
@@ -43,6 +58,14 @@ export async function dispatchBncrInbound(params: {
|
|
|
43
58
|
cfg,
|
|
44
59
|
parsed,
|
|
45
60
|
canonicalAgentId,
|
|
61
|
+
shouldDispatch = true,
|
|
62
|
+
shouldAccumulate = shouldDispatch,
|
|
63
|
+
resolvedAgentId,
|
|
64
|
+
sceneRegistry,
|
|
65
|
+
groupHistories = new Map(),
|
|
66
|
+
defaultAdminAgentId,
|
|
67
|
+
defaultPublicAgentId,
|
|
68
|
+
now,
|
|
46
69
|
rememberSessionRoute,
|
|
47
70
|
enqueueFromReply,
|
|
48
71
|
setInboundActivity,
|
|
@@ -57,6 +80,11 @@ export async function dispatchBncrInbound(params: {
|
|
|
57
80
|
cfg,
|
|
58
81
|
parsed,
|
|
59
82
|
canonicalAgentId,
|
|
83
|
+
resolvedAgentId,
|
|
84
|
+
sceneRegistry,
|
|
85
|
+
defaultAdminAgentId,
|
|
86
|
+
defaultPublicAgentId,
|
|
87
|
+
now,
|
|
60
88
|
rememberSessionRoute,
|
|
61
89
|
enqueueFromReply,
|
|
62
90
|
logger,
|
|
@@ -79,12 +107,23 @@ export async function dispatchBncrInbound(params: {
|
|
|
79
107
|
cfg,
|
|
80
108
|
parsed,
|
|
81
109
|
canonicalAgentId,
|
|
110
|
+
resolvedAgentId,
|
|
82
111
|
rememberSessionRoute,
|
|
83
112
|
});
|
|
84
|
-
const {
|
|
85
|
-
|
|
113
|
+
const {
|
|
114
|
+
resolution,
|
|
115
|
+
prepared,
|
|
116
|
+
replyRouteFact,
|
|
117
|
+
senderIdForContext,
|
|
118
|
+
senderDisplayName,
|
|
119
|
+
ownerAllowFrom,
|
|
120
|
+
bridgeSenderId,
|
|
121
|
+
bridgeSenderName,
|
|
122
|
+
} = preparedDispatch;
|
|
86
123
|
const { dispatchSessionKey: sessionKey } = resolution;
|
|
87
|
-
const { storePath,
|
|
124
|
+
const { storePath, mediaItems, rawBody } = prepared;
|
|
125
|
+
const primaryMedia = mediaItems[0];
|
|
126
|
+
const mediaContentType = primaryMedia?.contentType;
|
|
88
127
|
if (!clientId) {
|
|
89
128
|
emitBncrLogLine(
|
|
90
129
|
'warn',
|
|
@@ -93,18 +132,39 @@ export async function dispatchBncrInbound(params: {
|
|
|
93
132
|
}
|
|
94
133
|
const ctxPayload = await buildBncrInboundTurnContext({
|
|
95
134
|
api,
|
|
135
|
+
cfg,
|
|
96
136
|
channelId,
|
|
97
137
|
parsed,
|
|
98
138
|
msgId,
|
|
99
|
-
mimeType: mediaContentType || mimeType,
|
|
100
|
-
mediaPath,
|
|
101
139
|
peer,
|
|
102
140
|
senderIdForContext,
|
|
103
141
|
senderDisplayName,
|
|
142
|
+
ownerAllowFrom,
|
|
143
|
+
bridgeSenderId,
|
|
144
|
+
bridgeSenderName,
|
|
104
145
|
resolution,
|
|
105
146
|
prepared,
|
|
147
|
+
groupHistories,
|
|
148
|
+
shouldDispatch,
|
|
106
149
|
});
|
|
107
150
|
|
|
151
|
+
if (!shouldDispatch && shouldAccumulate) {
|
|
152
|
+
recordBncrPendingGroupText({
|
|
153
|
+
historyMap: groupHistories,
|
|
154
|
+
parsed,
|
|
155
|
+
senderDisplayName,
|
|
156
|
+
bodyText: rawBody,
|
|
157
|
+
});
|
|
158
|
+
await recordBncrPendingGroupMedia({
|
|
159
|
+
historyMap: groupHistories,
|
|
160
|
+
parsed,
|
|
161
|
+
senderDisplayName,
|
|
162
|
+
bodyText: rawBody,
|
|
163
|
+
mediaItems,
|
|
164
|
+
mediaContentType: mediaContentType || mimeType,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
108
168
|
await runBncrInboundReplyDispatch({
|
|
109
169
|
api,
|
|
110
170
|
channelId,
|
|
@@ -118,11 +178,17 @@ export async function dispatchBncrInbound(params: {
|
|
|
118
178
|
resolution,
|
|
119
179
|
replyRouteFact,
|
|
120
180
|
senderIdForContext,
|
|
181
|
+
senderDisplayName,
|
|
182
|
+
shouldDispatch,
|
|
121
183
|
setInboundActivity,
|
|
122
184
|
scheduleSave,
|
|
123
185
|
enqueueFromReply,
|
|
124
186
|
});
|
|
125
187
|
|
|
188
|
+
if (shouldDispatch) {
|
|
189
|
+
clearBncrPendingGroupHistory({ historyMap: groupHistories, parsed });
|
|
190
|
+
}
|
|
191
|
+
|
|
126
192
|
return {
|
|
127
193
|
accountId,
|
|
128
194
|
sessionKey,
|
|
@@ -1,59 +1,46 @@
|
|
|
1
1
|
import { normalizeAccountId } from '../../core/accounts.ts';
|
|
2
|
-
import { resolveBncrChannelPolicy } from '../../core/policy.ts';
|
|
3
|
-
import { buildDisplayScopeCandidates } from '../../core/targets.ts';
|
|
4
|
-
import type { BncrRoute } from '../../core/types.ts';
|
|
5
|
-
import {
|
|
6
|
-
defineOpenClawStableChannelIngressIdentity,
|
|
7
|
-
resolveOpenClawChannelMessageIngress,
|
|
8
|
-
} from '../../openclaw/ingress-runtime.ts';
|
|
9
2
|
import type { BncrInboundConfig, BncrInboundParamsInput } from './contracts.ts';
|
|
10
3
|
|
|
11
|
-
type RouteLike = Partial<Pick<BncrRoute, 'groupId' | 'userId' | 'platform'>>;
|
|
12
|
-
type AccessGroupsLike = Parameters<typeof resolveOpenClawChannelMessageIngress>[0]['accessGroups'];
|
|
13
|
-
|
|
14
4
|
export type BncrGateResult = { allowed: true } | { allowed: false; reason: string };
|
|
15
5
|
|
|
6
|
+
const REQUIRED_PROTOCOL_VERSION = 'scene-routing-v1';
|
|
7
|
+
const REQUIRED_CAPABILITY = 'scene-routing-v1';
|
|
8
|
+
|
|
16
9
|
function asString(v: unknown, fallback = ''): string {
|
|
17
10
|
if (typeof v === 'string') return v;
|
|
18
11
|
if (v == null) return fallback;
|
|
19
12
|
return String(v);
|
|
20
13
|
}
|
|
21
14
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
sensitivity: 'pii',
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
});
|
|
15
|
+
function asBoolean(v: unknown, fallback = false): boolean {
|
|
16
|
+
if (typeof v === 'boolean') return v;
|
|
17
|
+
if (typeof v === 'number') return v !== 0;
|
|
18
|
+
if (typeof v === 'string') {
|
|
19
|
+
const raw = v.trim().toLowerCase();
|
|
20
|
+
if (!raw) return fallback;
|
|
21
|
+
if (['true', '1', 'yes', 'y', 'on'].includes(raw)) return true;
|
|
22
|
+
if (['false', '0', 'no', 'n', 'off'].includes(raw)) return false;
|
|
23
|
+
}
|
|
24
|
+
return fallback;
|
|
25
|
+
}
|
|
37
26
|
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
case 'group_policy_not_allowlisted':
|
|
48
|
-
case 'group_policy_empty_allowlist':
|
|
49
|
-
return 'group allowlist blocked';
|
|
50
|
-
default:
|
|
51
|
-
return reasonCode || 'ingress blocked';
|
|
27
|
+
function asCapabilities(v: unknown): string[] {
|
|
28
|
+
if (Array.isArray(v)) {
|
|
29
|
+
return v.map((item) => asString(item).trim()).filter(Boolean);
|
|
30
|
+
}
|
|
31
|
+
if (typeof v === 'string') {
|
|
32
|
+
return v
|
|
33
|
+
.split(',')
|
|
34
|
+
.map((item) => item.trim())
|
|
35
|
+
.filter(Boolean);
|
|
52
36
|
}
|
|
37
|
+
return [];
|
|
53
38
|
}
|
|
54
39
|
|
|
55
40
|
export async function checkBncrMessageGate(params: {
|
|
56
|
-
parsed: BncrInboundParamsInput & {
|
|
41
|
+
parsed: BncrInboundParamsInput & {
|
|
42
|
+
route?: Partial<{ groupId: string; userId: string; platform: string }>;
|
|
43
|
+
};
|
|
57
44
|
cfg: BncrInboundConfig;
|
|
58
45
|
account: { accountId: string; enabled?: boolean };
|
|
59
46
|
}): Promise<BncrGateResult> {
|
|
@@ -61,57 +48,40 @@ export async function checkBncrMessageGate(params: {
|
|
|
61
48
|
const accountId = normalizeAccountId(account?.accountId);
|
|
62
49
|
const channelCfg = cfg?.channels?.bncr || {};
|
|
63
50
|
const accountCfg = channelCfg?.accounts?.[accountId] || {};
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
51
|
+
if (
|
|
52
|
+
channelCfg?.enabled === false ||
|
|
53
|
+
account?.enabled === false ||
|
|
54
|
+
accountCfg?.enabled === false
|
|
55
|
+
) {
|
|
67
56
|
return { allowed: false, reason: 'account disabled' };
|
|
68
57
|
}
|
|
69
58
|
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
const protocolVersion = asString(parsed?.protocolVersion || '').trim();
|
|
60
|
+
const capabilities = asCapabilities(parsed?.capabilities);
|
|
61
|
+
if (
|
|
62
|
+
protocolVersion !== REQUIRED_PROTOCOL_VERSION ||
|
|
63
|
+
!capabilities.includes(REQUIRED_CAPABILITY)
|
|
64
|
+
) {
|
|
65
|
+
return { allowed: false, reason: 'client protocol outdated' };
|
|
73
66
|
}
|
|
74
|
-
const resolvedRoute: BncrRoute = {
|
|
75
|
-
platform: route.platform,
|
|
76
|
-
groupId: route.groupId,
|
|
77
|
-
userId: route.userId,
|
|
78
|
-
};
|
|
79
|
-
const isGroup = asString(route?.groupId || '0') !== '0';
|
|
80
|
-
|
|
81
|
-
const candidates = buildDisplayScopeCandidates(resolvedRoute);
|
|
82
|
-
const displayScope = candidates[0] || '';
|
|
83
|
-
const routeKey = candidates.find((candidate) => candidate !== displayScope) || displayScope;
|
|
84
67
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
conversation: {
|
|
98
|
-
kind: isGroup ? 'group' : 'direct',
|
|
99
|
-
id: isGroup ? asString(route?.groupId) : asString(route?.userId || displayScope),
|
|
100
|
-
},
|
|
101
|
-
event: { kind: 'message', authMode: 'inbound', mayPair: !isGroup },
|
|
102
|
-
policy: {
|
|
103
|
-
dmPolicy: policy.dmPolicy,
|
|
104
|
-
groupPolicy: policy.groupPolicy,
|
|
105
|
-
groupAllowFromFallbackToAllowFrom: false,
|
|
106
|
-
},
|
|
107
|
-
allowFrom: policy.dmPolicy === 'open' ? ['*', ...policy.allowFrom] : policy.allowFrom,
|
|
108
|
-
groupAllowFrom: policy.groupAllowFrom,
|
|
109
|
-
accessGroups: cfg?.accessGroups as AccessGroupsLike,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
if (resolved.ingress.admission === 'dispatch' || resolved.ingress.admission === 'observe') {
|
|
113
|
-
return { allowed: true };
|
|
68
|
+
const platform = asString(parsed?.platform || '').trim();
|
|
69
|
+
const userId = asString(parsed?.userId || '').trim();
|
|
70
|
+
const clientId = asString(parsed?.clientId || '').trim();
|
|
71
|
+
const groupId = asString(parsed?.groupId || '').trim();
|
|
72
|
+
const isGroupProvided = parsed && Object.hasOwn(parsed, 'isGroup');
|
|
73
|
+
const isAdminProvided = parsed && Object.hasOwn(parsed, 'isAdmin');
|
|
74
|
+
const isGroup = asBoolean(parsed?.isGroup, false);
|
|
75
|
+
if (!platform || !userId || !clientId || !isGroupProvided || !isAdminProvided) {
|
|
76
|
+
return { allowed: false, reason: 'inbound schema incomplete' };
|
|
77
|
+
}
|
|
78
|
+
if (isGroup && !groupId) {
|
|
79
|
+
return { allowed: false, reason: 'inbound schema incomplete' };
|
|
114
80
|
}
|
|
115
81
|
|
|
116
|
-
|
|
82
|
+
const route = parsed?.route;
|
|
83
|
+
if (!route?.platform || !route?.groupId || !route?.userId) {
|
|
84
|
+
return { allowed: false, reason: 'invalid route' };
|
|
85
|
+
}
|
|
86
|
+
return { allowed: true };
|
|
117
87
|
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createChannelHistoryWindow,
|
|
3
|
+
DEFAULT_GROUP_HISTORY_LIMIT,
|
|
4
|
+
type HistoryEntry,
|
|
5
|
+
type HistoryMediaEntry,
|
|
6
|
+
} from 'openclaw/plugin-sdk/reply-history';
|
|
7
|
+
import { formatOpenClawAgentEnvelope } from '../../openclaw/reply-runtime.ts';
|
|
8
|
+
import type { BncrInboundApi } from './contracts.ts';
|
|
9
|
+
import type { ParsedInbound } from './dispatch-prep.ts';
|
|
10
|
+
|
|
11
|
+
export type BncrGroupHistoryMap = Map<string, HistoryEntry[]>;
|
|
12
|
+
|
|
13
|
+
type BncrHistoryMediaKind = NonNullable<HistoryMediaEntry['kind']>;
|
|
14
|
+
|
|
15
|
+
function normalizeTextBody(value: string): string {
|
|
16
|
+
return String(value || '')
|
|
17
|
+
.replace(/\s+/g, ' ')
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function buildBncrGroupHistoryKey(parsed: ParsedInbound): string | null {
|
|
22
|
+
if (parsed.peer.kind !== 'group') return null;
|
|
23
|
+
const platform = String(parsed.platform || '').trim();
|
|
24
|
+
const groupId = String(parsed.groupId || '').trim();
|
|
25
|
+
if (!platform || !groupId || groupId === '0') return null;
|
|
26
|
+
return `${platform}:${groupId}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function recordBncrPendingGroupText(args: {
|
|
30
|
+
historyMap: BncrGroupHistoryMap;
|
|
31
|
+
parsed: ParsedInbound;
|
|
32
|
+
senderDisplayName: string;
|
|
33
|
+
bodyText: string;
|
|
34
|
+
}) {
|
|
35
|
+
const historyKey = buildBncrGroupHistoryKey(args.parsed);
|
|
36
|
+
const body = normalizeTextBody(args.bodyText);
|
|
37
|
+
if (!historyKey || !body || args.parsed.msgType !== 'text') return;
|
|
38
|
+
createChannelHistoryWindow({ historyMap: args.historyMap }).record({
|
|
39
|
+
historyKey,
|
|
40
|
+
limit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
41
|
+
entry: {
|
|
42
|
+
sender: args.senderDisplayName,
|
|
43
|
+
body,
|
|
44
|
+
timestamp: Date.now(),
|
|
45
|
+
messageId: args.parsed.msgId,
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function inferBncrHistoryMediaKind(args: {
|
|
51
|
+
msgType?: string;
|
|
52
|
+
mediaContentType?: string;
|
|
53
|
+
}): BncrHistoryMediaKind {
|
|
54
|
+
const msgType = String(args.msgType || '')
|
|
55
|
+
.trim()
|
|
56
|
+
.toLowerCase();
|
|
57
|
+
const contentType = String(args.mediaContentType || '')
|
|
58
|
+
.trim()
|
|
59
|
+
.toLowerCase();
|
|
60
|
+
if (msgType === 'image' || contentType.startsWith('image/')) return 'image';
|
|
61
|
+
if (msgType === 'video' || contentType.startsWith('video/')) return 'video';
|
|
62
|
+
if (msgType === 'audio' || msgType === 'voice' || contentType.startsWith('audio/'))
|
|
63
|
+
return 'audio';
|
|
64
|
+
if (msgType === 'file' || msgType === 'document') return 'document';
|
|
65
|
+
if (contentType) return 'document';
|
|
66
|
+
return 'unknown';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function buildBncrHistoryMediaBody(kind: BncrHistoryMediaKind): string {
|
|
70
|
+
return `<media:${kind}>`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function recordBncrPendingGroupMedia(args: {
|
|
74
|
+
historyMap: BncrGroupHistoryMap;
|
|
75
|
+
parsed: ParsedInbound;
|
|
76
|
+
senderDisplayName: string;
|
|
77
|
+
bodyText: string;
|
|
78
|
+
mediaItems?: Array<{
|
|
79
|
+
path: string;
|
|
80
|
+
contentType?: string;
|
|
81
|
+
kind?: BncrHistoryMediaKind;
|
|
82
|
+
}>;
|
|
83
|
+
mediaContentType?: string;
|
|
84
|
+
}) {
|
|
85
|
+
const historyKey = buildBncrGroupHistoryKey(args.parsed);
|
|
86
|
+
if (!historyKey || args.parsed.msgType === 'text') return;
|
|
87
|
+
const normalizedMediaItems = Array.isArray(args.mediaItems) ? args.mediaItems : [];
|
|
88
|
+
const itemKinds = normalizedMediaItems
|
|
89
|
+
.map((item) =>
|
|
90
|
+
String(item?.kind || '')
|
|
91
|
+
.trim()
|
|
92
|
+
.toLowerCase(),
|
|
93
|
+
)
|
|
94
|
+
.filter(Boolean);
|
|
95
|
+
const kind = itemKinds[0]
|
|
96
|
+
? itemKinds.every((candidate) => candidate === itemKinds[0])
|
|
97
|
+
? (itemKinds[0] as BncrHistoryMediaKind)
|
|
98
|
+
: 'document'
|
|
99
|
+
: inferBncrHistoryMediaKind({
|
|
100
|
+
msgType: args.parsed.msgType,
|
|
101
|
+
mediaContentType: args.mediaContentType,
|
|
102
|
+
});
|
|
103
|
+
const body = normalizeTextBody(args.bodyText) || buildBncrHistoryMediaBody(kind);
|
|
104
|
+
if (normalizedMediaItems.length === 0 || kind !== 'image') {
|
|
105
|
+
createChannelHistoryWindow({ historyMap: args.historyMap }).record({
|
|
106
|
+
historyKey,
|
|
107
|
+
limit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
108
|
+
entry: {
|
|
109
|
+
sender: args.senderDisplayName,
|
|
110
|
+
body,
|
|
111
|
+
timestamp: Date.now(),
|
|
112
|
+
messageId: args.parsed.msgId,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
await createChannelHistoryWindow({ historyMap: args.historyMap }).recordWithMedia({
|
|
118
|
+
historyKey,
|
|
119
|
+
limit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
120
|
+
entry: {
|
|
121
|
+
sender: args.senderDisplayName,
|
|
122
|
+
body,
|
|
123
|
+
timestamp: Date.now(),
|
|
124
|
+
messageId: args.parsed.msgId,
|
|
125
|
+
},
|
|
126
|
+
messageId: args.parsed.msgId,
|
|
127
|
+
media: normalizedMediaItems.map(
|
|
128
|
+
(item) =>
|
|
129
|
+
({
|
|
130
|
+
path: item.path,
|
|
131
|
+
contentType: item.contentType || args.mediaContentType || 'image/*',
|
|
132
|
+
kind: 'image',
|
|
133
|
+
messageId: args.parsed.msgId,
|
|
134
|
+
}) satisfies HistoryMediaEntry,
|
|
135
|
+
),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function buildBncrPendingGroupContext(args: {
|
|
140
|
+
api: BncrInboundApi;
|
|
141
|
+
historyMap: BncrGroupHistoryMap;
|
|
142
|
+
parsed: ParsedInbound;
|
|
143
|
+
channelLabel: string;
|
|
144
|
+
currentTimestamp: number;
|
|
145
|
+
previousTimestamp?: unknown;
|
|
146
|
+
envelope?: unknown;
|
|
147
|
+
currentMessage: string;
|
|
148
|
+
}) {
|
|
149
|
+
const historyKey = buildBncrGroupHistoryKey(args.parsed);
|
|
150
|
+
if (!historyKey) return args.currentMessage;
|
|
151
|
+
return createChannelHistoryWindow({ historyMap: args.historyMap }).buildPendingContext({
|
|
152
|
+
historyKey,
|
|
153
|
+
limit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
154
|
+
currentMessage: args.currentMessage,
|
|
155
|
+
formatEntry: (entry) =>
|
|
156
|
+
formatOpenClawAgentEnvelope(args.api, {
|
|
157
|
+
channel: 'Bncr',
|
|
158
|
+
from: args.channelLabel,
|
|
159
|
+
timestamp: entry.timestamp || args.currentTimestamp,
|
|
160
|
+
previousTimestamp: args.previousTimestamp,
|
|
161
|
+
envelope: args.envelope,
|
|
162
|
+
body: entry.body,
|
|
163
|
+
}),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function buildBncrInboundHistory(args: {
|
|
168
|
+
historyMap: BncrGroupHistoryMap;
|
|
169
|
+
parsed: ParsedInbound;
|
|
170
|
+
}) {
|
|
171
|
+
const historyKey = buildBncrGroupHistoryKey(args.parsed);
|
|
172
|
+
if (!historyKey) return undefined;
|
|
173
|
+
return createChannelHistoryWindow({ historyMap: args.historyMap }).buildInboundHistory({
|
|
174
|
+
historyKey,
|
|
175
|
+
limit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function clearBncrPendingGroupHistory(args: {
|
|
180
|
+
historyMap: BncrGroupHistoryMap;
|
|
181
|
+
parsed: ParsedInbound;
|
|
182
|
+
}) {
|
|
183
|
+
const historyKey = buildBncrGroupHistoryKey(args.parsed);
|
|
184
|
+
if (!historyKey) return;
|
|
185
|
+
createChannelHistoryWindow({ historyMap: args.historyMap }).clear({
|
|
186
|
+
historyKey,
|
|
187
|
+
limit: DEFAULT_GROUP_HISTORY_LIMIT,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
@@ -53,12 +53,12 @@ export function buildNativeCommandHandledResult(args: {
|
|
|
53
53
|
|
|
54
54
|
export function buildBncrNativeCommandSessionState(args: {
|
|
55
55
|
parsed: ParsedInbound;
|
|
56
|
-
|
|
56
|
+
sessionAgentId: string;
|
|
57
57
|
resolvedRoute: { sessionKey: string };
|
|
58
58
|
}) {
|
|
59
|
-
const { parsed,
|
|
59
|
+
const { parsed, sessionAgentId, resolvedRoute } = args;
|
|
60
60
|
const baseSessionKey =
|
|
61
|
-
normalizeInboundSessionKey(parsed.sessionKeyfromroute, parsed.route,
|
|
61
|
+
normalizeInboundSessionKey(parsed.sessionKeyfromroute, parsed.route, sessionAgentId) ||
|
|
62
62
|
resolvedRoute.sessionKey;
|
|
63
63
|
const taskSessionKey = withTaskSessionKey(baseSessionKey, parsed.extracted.taskKey);
|
|
64
64
|
const sessionKey = taskSessionKey || baseSessionKey;
|
|
@@ -87,71 +87,87 @@ export function createNativeCommandTurnContext(args: {
|
|
|
87
87
|
senderDisplayName: string;
|
|
88
88
|
body: string;
|
|
89
89
|
}): BncrInboundContextPayload | Promise<BncrInboundContextPayload> {
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
id: args.peer.id,
|
|
106
|
-
label: args.displayTo,
|
|
107
|
-
routePeer: {
|
|
90
|
+
return Promise.resolve(
|
|
91
|
+
resolveBncrChannelInboundRuntime(args.api).buildContext({
|
|
92
|
+
channel: args.channelId,
|
|
93
|
+
provider: args.channelId,
|
|
94
|
+
surface: args.channelId,
|
|
95
|
+
accountId: args.accountId,
|
|
96
|
+
messageId: args.msgId,
|
|
97
|
+
timestamp: Date.now(),
|
|
98
|
+
from: args.senderIdForContext,
|
|
99
|
+
sender: {
|
|
100
|
+
id: args.senderIdForContext,
|
|
101
|
+
name: args.senderDisplayName,
|
|
102
|
+
username: args.senderDisplayName,
|
|
103
|
+
},
|
|
104
|
+
conversation: {
|
|
108
105
|
kind: args.peer.kind,
|
|
109
106
|
id: args.peer.id,
|
|
107
|
+
label: args.displayTo,
|
|
108
|
+
routePeer: {
|
|
109
|
+
kind: args.peer.kind,
|
|
110
|
+
id: args.peer.id,
|
|
111
|
+
},
|
|
110
112
|
},
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
113
|
+
route: {
|
|
114
|
+
agentId: args.resolvedRoute.agentId,
|
|
115
|
+
accountId: args.accountId,
|
|
116
|
+
routeSessionKey: args.resolvedRoute.sessionKey,
|
|
117
|
+
dispatchSessionKey: args.sessionKey,
|
|
118
|
+
mainSessionKey: args.resolvedRoute.mainSessionKey,
|
|
119
|
+
},
|
|
120
|
+
reply: {
|
|
121
|
+
to: args.displayTo,
|
|
122
|
+
originatingTo: args.originatingTo,
|
|
123
|
+
replyToId: args.msgId,
|
|
124
|
+
},
|
|
125
|
+
message: {
|
|
126
|
+
inboundEventKind: 'user_request',
|
|
127
|
+
body: args.body,
|
|
128
|
+
rawBody: args.body,
|
|
129
|
+
bodyForAgent: args.body,
|
|
130
|
+
commandBody: args.body,
|
|
131
|
+
envelopeFrom: args.originatingTo,
|
|
132
|
+
senderLabel: args.senderDisplayName,
|
|
133
|
+
},
|
|
134
|
+
commandTurn: {
|
|
135
|
+
kind: 'native',
|
|
136
|
+
source: 'native',
|
|
137
|
+
authorized: true,
|
|
138
|
+
body: args.body,
|
|
139
|
+
},
|
|
140
|
+
access: {
|
|
141
|
+
mentions: {
|
|
142
|
+
canDetectMention: true,
|
|
143
|
+
wasMentioned: true,
|
|
144
|
+
effectiveWasMentioned: true,
|
|
145
|
+
},
|
|
146
|
+
commands: {
|
|
147
|
+
authorized: true,
|
|
148
|
+
allowTextCommands: true,
|
|
149
|
+
useAccessGroups: false,
|
|
150
|
+
authorizers: [],
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
extra: {
|
|
154
|
+
OriginatingChannel: args.channelId,
|
|
155
|
+
},
|
|
156
|
+
}),
|
|
157
|
+
).then((ctx) => {
|
|
158
|
+
ctx.From = args.senderIdForContext;
|
|
159
|
+
ctx.To = args.displayTo;
|
|
160
|
+
ctx.SenderId = args.senderIdForContext;
|
|
161
|
+
ctx.OriginatingChannel = args.channelId;
|
|
162
|
+
ctx.CommandAuthorized = true;
|
|
163
|
+
ctx.CommandSource = 'native';
|
|
164
|
+
ctx.CommandTurn = {
|
|
134
165
|
kind: 'native',
|
|
135
166
|
source: 'native',
|
|
136
167
|
authorized: true,
|
|
137
168
|
body: args.body,
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
mentions: {
|
|
141
|
-
canDetectMention: true,
|
|
142
|
-
wasMentioned: true,
|
|
143
|
-
effectiveWasMentioned: true,
|
|
144
|
-
},
|
|
145
|
-
commands: {
|
|
146
|
-
authorized: true,
|
|
147
|
-
allowTextCommands: true,
|
|
148
|
-
useAccessGroups: false,
|
|
149
|
-
authorizers: [],
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
extra: {
|
|
153
|
-
OriginatingChannel: args.channelId,
|
|
154
|
-
},
|
|
169
|
+
};
|
|
170
|
+
return ctx;
|
|
155
171
|
});
|
|
156
172
|
}
|
|
157
173
|
|