@xmoxmo/bncr 0.0.9 → 0.1.1
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 +38 -2
- package/index.ts +208 -56
- package/package.json +1 -1
- package/src/channel.ts +3 -1
- package/src/messaging/inbound/commands.ts +6 -2
- package/src/messaging/inbound/dispatch.ts +6 -2
- package/src/messaging/outbound/media.ts +2 -0
package/README.md
CHANGED
|
@@ -34,6 +34,14 @@ openclaw plugins update bncr
|
|
|
34
34
|
openclaw gateway restart
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
> 兼容范围:`openclaw >= 2026.3.22`
|
|
38
|
+
>
|
|
39
|
+
> 如果你是从精确版本升级,或本地安装记录仍钉在旧版本,也可以显式执行:
|
|
40
|
+
>
|
|
41
|
+
> ```bash
|
|
42
|
+
> openclaw plugins install @xmoxmo/bncr@0.1.1
|
|
43
|
+
> openclaw gateway restart
|
|
44
|
+
> ```
|
|
37
45
|
|
|
38
46
|
### Bncr / 无界侧
|
|
39
47
|
|
|
@@ -137,7 +145,35 @@ openclaw health --json
|
|
|
137
145
|
|
|
138
146
|
---
|
|
139
147
|
|
|
140
|
-
## 8.
|
|
148
|
+
## 8. 常见安装/加载问题
|
|
149
|
+
|
|
150
|
+
### 报错:`Cannot find module 'openclaw/plugin-sdk/core'`
|
|
151
|
+
|
|
152
|
+
这通常不是 bncr 没装上,而是:
|
|
153
|
+
|
|
154
|
+
- bncr 已经安装到 `~/.openclaw/extensions/bncr`
|
|
155
|
+
- 但插件目录当前解析不到宿主 `openclaw` 包
|
|
156
|
+
- 因而在加载 `openclaw/plugin-sdk/core` 时失败
|
|
157
|
+
|
|
158
|
+
bncr 0.1.1 会先尝试自动修复插件目录下的 `node_modules/openclaw` 解析链;如果仍失败,可手动执行:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
mkdir -p ~/.openclaw/extensions/bncr/node_modules
|
|
162
|
+
ln -s "$(npm root -g)/openclaw" ~/.openclaw/extensions/bncr/node_modules/openclaw
|
|
163
|
+
openclaw gateway restart
|
|
164
|
+
openclaw plugins inspect bncr
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
如果 `npm root -g` 指向的不是实际宿主位置,请先检查:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
which openclaw
|
|
171
|
+
npm root -g
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
然后把 `openclaw` 的真实安装目录软链接到 `~/.openclaw/extensions/bncr/node_modules/openclaw`。
|
|
175
|
+
|
|
176
|
+
## 9. 自检与测试
|
|
141
177
|
|
|
142
178
|
```bash
|
|
143
179
|
cd plugins/bncr
|
|
@@ -164,7 +200,7 @@ npm pack
|
|
|
164
200
|
- 测试通过
|
|
165
201
|
- 自检通过
|
|
166
202
|
- 可以正常打包
|
|
167
|
-
- 本地版本号与 npm /
|
|
203
|
+
- 本地版本号与 npm / 发布目标一致(版本号修改应优先在工作仓完成,再同步到发布仓)
|
|
168
204
|
- 运行态 `linked / pending / deadLetter` 正常
|
|
169
205
|
|
|
170
206
|
---
|
package/index.ts
CHANGED
|
@@ -1,25 +1,207 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { BncrConfigSchema } from './src/core/config-schema.ts';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const pluginFile = fileURLToPath(import.meta.url);
|
|
9
|
+
const pluginDir = path.dirname(pluginFile);
|
|
10
|
+
const pluginRequire = createRequire(import.meta.url);
|
|
11
|
+
const sdkCoreSpecifier = 'openclaw/plugin-sdk/core';
|
|
12
|
+
const linkType = process.platform === 'win32' ? 'junction' : 'dir';
|
|
13
|
+
|
|
14
|
+
type ChannelModule = typeof import('./src/channel.ts');
|
|
15
|
+
type OpenClawPluginApi = Parameters<ChannelModule['createBncrBridge']>[0];
|
|
16
|
+
type BridgeSingleton = ReturnType<ChannelModule['createBncrBridge']>;
|
|
17
|
+
|
|
18
|
+
type LoadedRuntime = {
|
|
19
|
+
createBncrBridge: ChannelModule['createBncrBridge'];
|
|
20
|
+
createBncrChannelPlugin: ChannelModule['createBncrChannelPlugin'];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
let runtime: LoadedRuntime | null = null;
|
|
24
|
+
|
|
25
|
+
const tryExec = (command: string, args: string[]) => {
|
|
26
|
+
try {
|
|
27
|
+
return execFileSync(command, args, {
|
|
28
|
+
encoding: 'utf8',
|
|
29
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
30
|
+
}).trim();
|
|
31
|
+
} catch {
|
|
32
|
+
return '';
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const readOpenClawPackageName = (pkgPath: string) => {
|
|
37
|
+
try {
|
|
38
|
+
const raw = fs.readFileSync(pkgPath, 'utf8');
|
|
39
|
+
const parsed = JSON.parse(raw);
|
|
40
|
+
return typeof parsed?.name === 'string' ? parsed.name : '';
|
|
41
|
+
} catch {
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const findOpenClawPackageRoot = (startPath: string) => {
|
|
47
|
+
let current = startPath;
|
|
48
|
+
try {
|
|
49
|
+
current = fs.realpathSync(startPath);
|
|
50
|
+
} catch {
|
|
51
|
+
// keep original path when realpath fails
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let cursor = current;
|
|
55
|
+
while (true) {
|
|
56
|
+
const statPath = fs.existsSync(cursor) ? cursor : path.dirname(cursor);
|
|
57
|
+
const pkgPath = path.join(statPath, 'package.json');
|
|
58
|
+
if (fs.existsSync(pkgPath) && readOpenClawPackageName(pkgPath) === 'openclaw') {
|
|
59
|
+
return statPath;
|
|
60
|
+
}
|
|
61
|
+
const parent = path.dirname(statPath);
|
|
62
|
+
if (parent === statPath) break;
|
|
63
|
+
cursor = parent;
|
|
64
|
+
}
|
|
65
|
+
return '';
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const unique = (items: string[]) => {
|
|
69
|
+
const seen = new Set<string>();
|
|
70
|
+
const out: string[] = [];
|
|
71
|
+
for (const item of items) {
|
|
72
|
+
if (!item) continue;
|
|
73
|
+
const normalized = path.normalize(item);
|
|
74
|
+
if (seen.has(normalized)) continue;
|
|
75
|
+
seen.add(normalized);
|
|
76
|
+
out.push(normalized);
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const collectOpenClawCandidates = () => {
|
|
82
|
+
const directCandidates = [
|
|
83
|
+
path.join(pluginDir, 'node_modules', 'openclaw'),
|
|
84
|
+
path.join('/usr/lib/node_modules', 'openclaw'),
|
|
85
|
+
path.join('/usr/local/lib/node_modules', 'openclaw'),
|
|
86
|
+
path.join('/opt/homebrew/lib/node_modules', 'openclaw'),
|
|
87
|
+
path.join(process.env.HOME || '', '.npm-global/lib/node_modules', 'openclaw'),
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const npmRoot = tryExec('npm', ['root', '-g']);
|
|
91
|
+
if (npmRoot) directCandidates.push(path.join(npmRoot, 'openclaw'));
|
|
92
|
+
|
|
93
|
+
const nodePathEntries = (process.env.NODE_PATH || '')
|
|
94
|
+
.split(path.delimiter)
|
|
95
|
+
.map((entry) => entry.trim())
|
|
96
|
+
.filter(Boolean);
|
|
97
|
+
for (const entry of nodePathEntries) {
|
|
98
|
+
directCandidates.push(path.join(entry, 'openclaw'));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const openclawBin = tryExec('which', ['openclaw']);
|
|
102
|
+
if (openclawBin) {
|
|
103
|
+
directCandidates.push(openclawBin);
|
|
104
|
+
directCandidates.push(path.dirname(openclawBin));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const packageRoots = unique(
|
|
108
|
+
directCandidates
|
|
109
|
+
.map((candidate) => findOpenClawPackageRoot(candidate))
|
|
110
|
+
.filter(Boolean),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
return packageRoots.filter((candidate) => {
|
|
114
|
+
const pkgJson = path.join(candidate, 'package.json');
|
|
115
|
+
return fs.existsSync(pkgJson) && readOpenClawPackageName(pkgJson) === 'openclaw';
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const canResolveSdkCore = () => {
|
|
120
|
+
try {
|
|
121
|
+
pluginRequire.resolve(sdkCoreSpecifier);
|
|
122
|
+
return true;
|
|
123
|
+
} catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const ensurePluginNodeModulesLink = (targetRoot: string) => {
|
|
129
|
+
const nodeModulesDir = path.join(pluginDir, 'node_modules');
|
|
130
|
+
const linkPath = path.join(nodeModulesDir, 'openclaw');
|
|
131
|
+
fs.mkdirSync(nodeModulesDir, { recursive: true });
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const stat = fs.lstatSync(linkPath);
|
|
135
|
+
if (stat.isSymbolicLink()) {
|
|
136
|
+
const existingTarget = fs.realpathSync(linkPath);
|
|
137
|
+
const normalizedExisting = path.normalize(existingTarget);
|
|
138
|
+
const normalizedTarget = path.normalize(fs.realpathSync(targetRoot));
|
|
139
|
+
if (normalizedExisting === normalizedTarget) return;
|
|
140
|
+
fs.unlinkSync(linkPath);
|
|
141
|
+
} else {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
// missing link is fine
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fs.symlinkSync(targetRoot, linkPath, linkType as fs.symlink.Type);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const ensureOpenClawSdkResolution = () => {
|
|
152
|
+
if (canResolveSdkCore()) return;
|
|
153
|
+
|
|
154
|
+
let lastError = '';
|
|
155
|
+
const candidates = collectOpenClawCandidates();
|
|
156
|
+
for (const candidate of candidates) {
|
|
157
|
+
try {
|
|
158
|
+
ensurePluginNodeModulesLink(candidate);
|
|
159
|
+
if (canResolveSdkCore()) return;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
lastError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const suffix = candidates.length
|
|
166
|
+
? ` Tried candidates: ${candidates.join(', ')}.`
|
|
167
|
+
: ' No openclaw package root candidates were found from npm root, NODE_PATH, common global paths, or the openclaw binary path.';
|
|
168
|
+
const extra = lastError ? ` Last repair error: ${lastError}.` : '';
|
|
169
|
+
throw new Error(
|
|
170
|
+
`bncr failed to resolve ${sdkCoreSpecifier} from ${pluginDir}.${suffix}${extra} ` +
|
|
171
|
+
`You can repair manually with: mkdir -p ${path.join(pluginDir, 'node_modules')} && ln -s "$(npm root -g)/openclaw" ${path.join(pluginDir, 'node_modules', 'openclaw')}`,
|
|
172
|
+
);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const loadRuntimeSync = (): LoadedRuntime => {
|
|
176
|
+
if (runtime) return runtime;
|
|
177
|
+
ensureOpenClawSdkResolution();
|
|
178
|
+
try {
|
|
179
|
+
const mod = pluginRequire('./src/channel.ts') as ChannelModule;
|
|
180
|
+
runtime = {
|
|
181
|
+
createBncrBridge: mod.createBncrBridge,
|
|
182
|
+
createBncrChannelPlugin: mod.createBncrChannelPlugin,
|
|
183
|
+
};
|
|
184
|
+
return runtime;
|
|
185
|
+
} catch (error) {
|
|
186
|
+
const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
187
|
+
throw new Error(`bncr failed to load channel runtime after dependency bootstrap: ${detail}`);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
9
190
|
|
|
10
191
|
const getBridgeSingleton = (api: OpenClawPluginApi) => {
|
|
192
|
+
const loaded = loadRuntimeSync();
|
|
11
193
|
const g = globalThis as typeof globalThis & { __bncrBridge?: BridgeSingleton };
|
|
12
|
-
if (!g.__bncrBridge) g.__bncrBridge = createBncrBridge(api);
|
|
13
|
-
return g.__bncrBridge;
|
|
194
|
+
if (!g.__bncrBridge) g.__bncrBridge = loaded.createBncrBridge(api);
|
|
195
|
+
return { bridge: g.__bncrBridge, runtime: loaded };
|
|
14
196
|
};
|
|
15
197
|
|
|
16
198
|
const plugin = {
|
|
17
|
-
id:
|
|
18
|
-
name:
|
|
19
|
-
description:
|
|
199
|
+
id: 'bncr',
|
|
200
|
+
name: 'Bncr',
|
|
201
|
+
description: 'Bncr channel plugin',
|
|
20
202
|
configSchema: BncrConfigSchema,
|
|
21
203
|
register(api: OpenClawPluginApi) {
|
|
22
|
-
const bridge = getBridgeSingleton(api);
|
|
204
|
+
const { bridge, runtime } = getBridgeSingleton(api);
|
|
23
205
|
const debugLog = (...args: any[]) => {
|
|
24
206
|
if (!bridge.isDebugEnabled?.()) return;
|
|
25
207
|
api.logger.info?.(...args);
|
|
@@ -37,7 +219,7 @@ const plugin = {
|
|
|
37
219
|
};
|
|
38
220
|
|
|
39
221
|
api.registerService({
|
|
40
|
-
id:
|
|
222
|
+
id: 'bncr-bridge-service',
|
|
41
223
|
start: async (ctx) => {
|
|
42
224
|
const debug = await resolveDebug();
|
|
43
225
|
await bridge.startService(ctx, debug);
|
|
@@ -45,48 +227,18 @@ const plugin = {
|
|
|
45
227
|
stop: bridge.stopService,
|
|
46
228
|
});
|
|
47
229
|
|
|
48
|
-
api.registerChannel({ plugin: createBncrChannelPlugin(bridge) });
|
|
49
|
-
|
|
50
|
-
api.registerGatewayMethod(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
);
|
|
54
|
-
api.registerGatewayMethod(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
);
|
|
58
|
-
api.registerGatewayMethod(
|
|
59
|
-
|
|
60
|
-
(opts: GatewayRequestHandlerOptions) => bridge.handleActivity(opts),
|
|
61
|
-
);
|
|
62
|
-
api.registerGatewayMethod(
|
|
63
|
-
"bncr.ack",
|
|
64
|
-
(opts: GatewayRequestHandlerOptions) => bridge.handleAck(opts),
|
|
65
|
-
);
|
|
66
|
-
api.registerGatewayMethod(
|
|
67
|
-
"bncr.diagnostics",
|
|
68
|
-
(opts: GatewayRequestHandlerOptions) => bridge.handleDiagnostics(opts),
|
|
69
|
-
);
|
|
70
|
-
api.registerGatewayMethod(
|
|
71
|
-
"bncr.file.init",
|
|
72
|
-
(opts: GatewayRequestHandlerOptions) => bridge.handleFileInit(opts),
|
|
73
|
-
);
|
|
74
|
-
api.registerGatewayMethod(
|
|
75
|
-
"bncr.file.chunk",
|
|
76
|
-
(opts: GatewayRequestHandlerOptions) => bridge.handleFileChunk(opts),
|
|
77
|
-
);
|
|
78
|
-
api.registerGatewayMethod(
|
|
79
|
-
"bncr.file.complete",
|
|
80
|
-
(opts: GatewayRequestHandlerOptions) => bridge.handleFileComplete(opts),
|
|
81
|
-
);
|
|
82
|
-
api.registerGatewayMethod(
|
|
83
|
-
"bncr.file.abort",
|
|
84
|
-
(opts: GatewayRequestHandlerOptions) => bridge.handleFileAbort(opts),
|
|
85
|
-
);
|
|
86
|
-
api.registerGatewayMethod(
|
|
87
|
-
"bncr.file.ack",
|
|
88
|
-
(opts: GatewayRequestHandlerOptions) => bridge.handleFileAck(opts),
|
|
89
|
-
);
|
|
230
|
+
api.registerChannel({ plugin: runtime.createBncrChannelPlugin(bridge) });
|
|
231
|
+
|
|
232
|
+
api.registerGatewayMethod('bncr.connect', (opts) => bridge.handleConnect(opts));
|
|
233
|
+
api.registerGatewayMethod('bncr.inbound', (opts) => bridge.handleInbound(opts));
|
|
234
|
+
api.registerGatewayMethod('bncr.activity', (opts) => bridge.handleActivity(opts));
|
|
235
|
+
api.registerGatewayMethod('bncr.ack', (opts) => bridge.handleAck(opts));
|
|
236
|
+
api.registerGatewayMethod('bncr.diagnostics', (opts) => bridge.handleDiagnostics(opts));
|
|
237
|
+
api.registerGatewayMethod('bncr.file.init', (opts) => bridge.handleFileInit(opts));
|
|
238
|
+
api.registerGatewayMethod('bncr.file.chunk', (opts) => bridge.handleFileChunk(opts));
|
|
239
|
+
api.registerGatewayMethod('bncr.file.complete', (opts) => bridge.handleFileComplete(opts));
|
|
240
|
+
api.registerGatewayMethod('bncr.file.abort', (opts) => bridge.handleFileAbort(opts));
|
|
241
|
+
api.registerGatewayMethod('bncr.file.ack', (opts) => bridge.handleFileAck(opts));
|
|
90
242
|
},
|
|
91
243
|
};
|
|
92
244
|
|
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -1606,7 +1606,7 @@ class BncrBridgeRuntime {
|
|
|
1606
1606
|
accountId: string;
|
|
1607
1607
|
sessionKey: string;
|
|
1608
1608
|
route: BncrRoute;
|
|
1609
|
-
payload: { text?: string; mediaUrl?: string; mediaUrls?: string[]; asVoice?: boolean; audioAsVoice?: boolean };
|
|
1609
|
+
payload: { text?: string; mediaUrl?: string; mediaUrls?: string[]; asVoice?: boolean; audioAsVoice?: boolean; kind?: 'block' | 'final' };
|
|
1610
1610
|
mediaLocalRoots?: readonly string[];
|
|
1611
1611
|
}) {
|
|
1612
1612
|
const { accountId, sessionKey, route, payload, mediaLocalRoots } = params;
|
|
@@ -1643,6 +1643,7 @@ class BncrBridgeRuntime {
|
|
|
1643
1643
|
mimeType: media.mimeType,
|
|
1644
1644
|
}),
|
|
1645
1645
|
hintedType: wantsVoice ? 'voice' : undefined,
|
|
1646
|
+
kind: payload.kind,
|
|
1646
1647
|
now: now(),
|
|
1647
1648
|
});
|
|
1648
1649
|
|
|
@@ -1675,6 +1676,7 @@ class BncrBridgeRuntime {
|
|
|
1675
1676
|
groupId: route.groupId,
|
|
1676
1677
|
userId: route.userId,
|
|
1677
1678
|
type: 'text',
|
|
1679
|
+
kind: payload.kind,
|
|
1678
1680
|
msg: text,
|
|
1679
1681
|
path: '',
|
|
1680
1682
|
base64: '',
|
|
@@ -114,7 +114,8 @@ export async function handleBncrNativeCommand(params: {
|
|
|
114
114
|
},
|
|
115
115
|
dispatcherOptions: {
|
|
116
116
|
deliver: async (payload: { text?: string; mediaUrl?: string; mediaUrls?: string[]; audioAsVoice?: boolean }, info?: { kind?: 'tool' | 'block' | 'final' }) => {
|
|
117
|
-
|
|
117
|
+
const kind = info?.kind;
|
|
118
|
+
if (kind && kind !== 'final') return;
|
|
118
119
|
const hasPayload = Boolean(payload?.text || payload?.mediaUrl || (Array.isArray(payload?.mediaUrls) && payload.mediaUrls.length > 0));
|
|
119
120
|
if (!hasPayload) return;
|
|
120
121
|
responded = true;
|
|
@@ -122,7 +123,10 @@ export async function handleBncrNativeCommand(params: {
|
|
|
122
123
|
accountId,
|
|
123
124
|
sessionKey,
|
|
124
125
|
route,
|
|
125
|
-
payload
|
|
126
|
+
payload: {
|
|
127
|
+
...payload,
|
|
128
|
+
kind: kind as 'block' | 'final' | undefined,
|
|
129
|
+
},
|
|
126
130
|
});
|
|
127
131
|
},
|
|
128
132
|
},
|
|
@@ -154,13 +154,17 @@ export async function dispatchBncrInbound(params: {
|
|
|
154
154
|
payload: { text?: string; mediaUrl?: string; mediaUrls?: string[]; audioAsVoice?: boolean },
|
|
155
155
|
info?: { kind?: 'tool' | 'block' | 'final' },
|
|
156
156
|
) => {
|
|
157
|
-
|
|
157
|
+
const kind = info?.kind;
|
|
158
|
+
if (kind && kind !== 'final') return;
|
|
158
159
|
|
|
159
160
|
await enqueueFromReply({
|
|
160
161
|
accountId,
|
|
161
162
|
sessionKey,
|
|
162
163
|
route,
|
|
163
|
-
payload
|
|
164
|
+
payload: {
|
|
165
|
+
...payload,
|
|
166
|
+
kind: kind as 'block' | 'final' | undefined,
|
|
167
|
+
},
|
|
164
168
|
});
|
|
165
169
|
},
|
|
166
170
|
onError: (err: unknown) => {
|
|
@@ -36,6 +36,7 @@ export function buildBncrMediaOutboundFrame(params: {
|
|
|
36
36
|
mediaMsg: string;
|
|
37
37
|
fileName: string;
|
|
38
38
|
hintedType?: string;
|
|
39
|
+
kind?: 'block' | 'final';
|
|
39
40
|
now: number;
|
|
40
41
|
}) {
|
|
41
42
|
return {
|
|
@@ -53,6 +54,7 @@ export function buildBncrMediaOutboundFrame(params: {
|
|
|
53
54
|
hasPayload: !!(params.media.path || params.media.mediaBase64),
|
|
54
55
|
hintedType: params.hintedType,
|
|
55
56
|
}),
|
|
57
|
+
kind: params.kind,
|
|
56
58
|
mimeType: params.media.mimeType || '',
|
|
57
59
|
msg: params.mediaMsg,
|
|
58
60
|
path: params.media.path || params.mediaUrl,
|