@xmoxmo/bncr 0.6.0 → 0.6.2

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.
Files changed (35) hide show
  1. package/README.md +8 -0
  2. package/dist/index.js +2 -0
  3. package/index.ts +1 -0
  4. package/package.json +4 -1
  5. package/scripts/check-pack.mjs +41 -1
  6. package/scripts/selfcheck.mjs +4 -1
  7. package/src/bootstrap/register-runtime-gateway.ts +1 -0
  8. package/src/bootstrap/register-runtime.ts +1 -0
  9. package/src/channel.ts +66 -5
  10. package/src/core/types.ts +41 -0
  11. package/src/messaging/inbound/context-facts.ts +88 -58
  12. package/src/messaging/inbound/{group-history.ts → conversation-history.ts} +92 -25
  13. package/src/messaging/inbound/dispatch.ts +90 -47
  14. package/src/messaging/inbound/native-command.ts +1 -1
  15. package/src/messaging/inbound/outbound-replay-cache.ts +312 -0
  16. package/src/messaging/inbound/scene-admin.ts +24 -9
  17. package/src/messaging/inbound/turn-context.ts +105 -33
  18. package/src/plugin/ack-outbox-runtime-group.ts +2 -0
  19. package/src/plugin/bridge-call.ts +168 -0
  20. package/src/plugin/bridge-outbox-facade.ts +88 -1
  21. package/src/plugin/channel-components.ts +10 -5
  22. package/src/plugin/channel-plugin-bridge-group.ts +43 -1
  23. package/src/plugin/channel-plugin-surface-group.ts +82 -7
  24. package/src/plugin/channel-runtime-builders-delivery.ts +4 -2
  25. package/src/plugin/channel-runtime-types.ts +34 -8
  26. package/src/plugin/channel-send.ts +12 -19
  27. package/src/plugin/client-rpc-runtime.ts +129 -0
  28. package/src/plugin/gateway-methods.ts +1 -0
  29. package/src/plugin/inbound-acceptance.ts +9 -24
  30. package/src/plugin/inbound-handlers.ts +8 -4
  31. package/src/plugin/inbound-surface-handlers-group.ts +7 -2
  32. package/src/plugin/outbox-ack-outcome.ts +2 -0
  33. package/src/plugin/scene-registry.ts +4 -4
  34. package/src/plugin/state-store.ts +153 -29
  35. package/src/plugin/state-transient-runtime-group.ts +10 -3
package/README.md CHANGED
@@ -81,6 +81,13 @@ openclaw devices approve --latest
81
81
  - 状态诊断
82
82
  - 文件互传
83
83
 
84
+ ### 0.6.1 新增说明
85
+
86
+ - `bncr.outbound.recent`:本地 bridge 方法,可直接查询最近的出站记录;该记录来自出站重放缓存,不额外维护一份独立最近出站存储。
87
+ - 出站重放缓存:最近出站消息会在下轮入站上下文回灌时带给 agent,用于让 agent 知道自己刚发过什么;查询和回灌共享同一份 `outboundReplayCache`。
88
+ - bridge 方法调用:`message` 工具支持传入 `bridgeMethod`,可调用插件本地方法(如 `bncr.status.*`、`bncr.outbound.recent`)或转发到 OpenClawClient RPC。
89
+ - unsend / delete:`message` 工具支持撤回/删除动作,按发送缓存映射回真实平台消息,不需要业务侧单独拼平台 message id。
90
+
84
91
  补充口径:
85
92
 
86
93
  - 入站会话按正式 scene / route 语义接入 OpenClaw,区分私聊与群聊会话。
@@ -285,6 +292,7 @@ npm pack
285
292
  - `npm run typecheck`:执行 `tsconfig.typecheck.json`,确保类型边界与 OpenClaw surface 对齐
286
293
  - `npm run selfcheck`:检查插件骨架是否完整,并验证关键 OpenClaw SDK subpath 可解析
287
294
  - `npm run check-pack`:执行 `npm pack --dry-run --json`,确认发布包包含关键入口与 OpenClaw adapter 文件
295
+ - `tests/integrity/source-manifest.test.mjs`:确保 `selfcheck` / `check-pack` 的源文件清单与当前 `src/**/*.ts` 一致,新增源文件不会漏进发布清单
288
296
  - `npm run format:check`:执行 Biome `format` 只读检查(不带 `--write`),防止格式漂移混进发布门禁
289
297
  - `npm pack`:确认当前版本可正常打包
290
298
  - `npm run check-register-drift -- --duration-sec 300 --interval-sec 15`:静置采样 `bncr.diagnostics`,观察 `registerCount / apiGeneration / postWarmupRegisterCount` 是否在 warmup 后继续增长
package/dist/index.js CHANGED
@@ -626,6 +626,7 @@ function createBncrRegisterRuntime() {
626
626
  "bncr.diagnostics": (bridge, opts) => bridge.handleDiagnostics(opts),
627
627
  "bncr.deadLetter.inspect": (bridge, opts) => bridge.handleDeadLetterInspect(opts),
628
628
  "bncr.deadLetter.prune": (bridge, opts) => bridge.handleDeadLetterPrune(opts),
629
+ "bncr.rpc.response": (bridge, opts) => bridge.handleRpcResponse(opts),
629
630
  "bncr.file.init": (bridge, opts) => bridge.handleFileInit(opts),
630
631
  "bncr.file.chunk": (bridge, opts) => bridge.handleFileChunk(opts),
631
632
  "bncr.file.complete": (bridge, opts) => bridge.handleFileComplete(opts),
@@ -936,6 +937,7 @@ var plugin = {
936
937
  ensureGatewayMethodRegistered(api, "bncr.diagnostics", debugLog);
937
938
  ensureGatewayMethodRegistered(api, "bncr.deadLetter.inspect", debugLog);
938
939
  ensureGatewayMethodRegistered(api, "bncr.deadLetter.prune", debugLog);
940
+ ensureGatewayMethodRegistered(api, "bncr.rpc.response", debugLog);
939
941
  ensureGatewayMethodRegistered(api, "bncr.file.init", debugLog);
940
942
  ensureGatewayMethodRegistered(api, "bncr.file.chunk", debugLog);
941
943
  ensureGatewayMethodRegistered(api, "bncr.file.complete", debugLog);
package/index.ts CHANGED
@@ -208,6 +208,7 @@ const plugin = {
208
208
  ensureGatewayMethodRegistered(api, 'bncr.diagnostics', debugLog);
209
209
  ensureGatewayMethodRegistered(api, 'bncr.deadLetter.inspect', debugLog);
210
210
  ensureGatewayMethodRegistered(api, 'bncr.deadLetter.prune', debugLog);
211
+ ensureGatewayMethodRegistered(api, 'bncr.rpc.response', debugLog);
211
212
  ensureGatewayMethodRegistered(api, 'bncr.file.init', debugLog);
212
213
  ensureGatewayMethodRegistered(api, 'bncr.file.chunk', debugLog);
213
214
  ensureGatewayMethodRegistered(api, 'bncr.file.complete', debugLog);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmoxmo/bncr",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -62,5 +62,8 @@
62
62
  "runtimeExtensions": [
63
63
  "./dist/index.js"
64
64
  ]
65
+ },
66
+ "dependencies": {
67
+ "typebox": "^1.3.10"
65
68
  }
66
69
  }
@@ -48,7 +48,6 @@ const requiredSourceFiles = [
48
48
  'src/core/outbox-summary.ts',
49
49
  'src/core/outbox-text-push-failure.ts',
50
50
  'src/core/outbox-text-push-guards.ts',
51
- 'src/core/outbox-text-push-prep.ts',
52
51
  'src/core/outbox-text-push-success.ts',
53
52
  'src/core/permissions.ts',
54
53
  'src/core/persisted-outbox-entry.ts',
@@ -68,6 +67,7 @@ const requiredSourceFiles = [
68
67
  'src/messaging/inbound/media-url-download.ts',
69
68
  'src/messaging/inbound/native-command.ts',
70
69
  'src/messaging/inbound/native-reply-delivery.ts',
70
+ 'src/messaging/inbound/outbound-replay-cache.ts',
71
71
  'src/messaging/inbound/parse.ts',
72
72
  'src/messaging/inbound/remote-media.ts',
73
73
  'src/messaging/inbound/reply-config.ts',
@@ -109,6 +109,8 @@ const requiredSourceFiles = [
109
109
  'src/plugin/channel-runtime-types.ts',
110
110
  'src/plugin/channel-send.ts',
111
111
  'src/plugin/channel-utils.ts',
112
+ 'src/plugin/bridge-call.ts',
113
+ 'src/plugin/client-rpc-runtime.ts',
112
114
  'src/plugin/config.ts',
113
115
  'src/plugin/connection-handlers.ts',
114
116
  'src/plugin/connection-state.ts',
@@ -168,6 +170,44 @@ const requiredSourceFiles = [
168
170
  'src/runtime/register-trace-runtime.ts',
169
171
  'src/runtime/status-snapshots.ts',
170
172
  'src/runtime/status-worker.ts',
173
+ 'src/bootstrap/register-runtime-gateway.ts',
174
+ 'src/bootstrap/register-runtime-singleton.ts',
175
+ 'src/core/value-sanitize.ts',
176
+ 'src/messaging/inbound/contracts.ts',
177
+ 'src/messaging/inbound/conversation-history.ts',
178
+ 'src/messaging/inbound/native-command-runtime.ts',
179
+ 'src/messaging/inbound/reply-dispatch-serial.ts',
180
+ 'src/messaging/inbound/scene-admin.ts',
181
+ 'src/messaging/inbound/session-meta-task.ts',
182
+ 'src/messaging/outbound/marker-parser.ts',
183
+ 'src/messaging/outbound/normalize-outbound-send.ts',
184
+ 'src/messaging/outbound/reply-enqueue-media.ts',
185
+ 'src/openclaw/channel-runtime-contracts.ts',
186
+ 'src/plugin/bridge-ack-facade.ts',
187
+ 'src/plugin/bridge-connection-facade.ts',
188
+ 'src/plugin/bridge-diagnostics-facade.ts',
189
+ 'src/plugin/bridge-drain-facade.ts',
190
+ 'src/plugin/bridge-extended-diagnostics-facade.ts',
191
+ 'src/plugin/bridge-lifecycle.ts',
192
+ 'src/plugin/bridge-media-facade.ts',
193
+ 'src/plugin/bridge-outbox-facade.ts',
194
+ 'src/plugin/bridge-runtime-helpers.ts',
195
+ 'src/plugin/bridge-runtime-snapshots.ts',
196
+ 'src/plugin/bridge-runtime-surface-facade.ts',
197
+ 'src/plugin/bridge-status-facade.ts',
198
+ 'src/plugin/bridge-status-worker-facade.ts',
199
+ 'src/plugin/bridge-support-runtime.ts',
200
+ 'src/plugin/channel-runtime-builders-delivery.ts',
201
+ 'src/plugin/channel-runtime-builders-status.ts',
202
+ 'src/plugin/connection-handlers-helpers.ts',
203
+ 'src/plugin/connection-state-helpers.ts',
204
+ 'src/plugin/error-message.ts',
205
+ 'src/plugin/file-transfer-orchestrator-chunk.ts',
206
+ 'src/plugin/outbox-unified-push-flow.ts',
207
+ 'src/plugin/runtime-diagnostics-assembler.ts',
208
+ 'src/plugin/runtime-diagnostics-helpers.ts',
209
+ 'src/plugin/runtime-diagnostics-payload-builders.ts',
210
+ 'src/plugin/scene-registry.ts',
171
211
  ];
172
212
 
173
213
  const requiredPackFiles = [
@@ -63,12 +63,13 @@ const requiredSourceFiles = [
63
63
  'src/messaging/inbound/dispatch-prep.ts',
64
64
  'src/messaging/inbound/dispatch.ts',
65
65
  'src/messaging/inbound/gate.ts',
66
- 'src/messaging/inbound/group-history.ts',
66
+ 'src/messaging/inbound/conversation-history.ts',
67
67
  'src/messaging/inbound/last-route.ts',
68
68
  'src/messaging/inbound/media-url-download.ts',
69
69
  'src/messaging/inbound/native-command-runtime.ts',
70
70
  'src/messaging/inbound/native-command.ts',
71
71
  'src/messaging/inbound/native-reply-delivery.ts',
72
+ 'src/messaging/inbound/outbound-replay-cache.ts',
72
73
  'src/messaging/inbound/parse.ts',
73
74
  'src/messaging/inbound/remote-media.ts',
74
75
  'src/messaging/inbound/reply-config.ts',
@@ -106,6 +107,7 @@ const requiredSourceFiles = [
106
107
  'src/openclaw/session-route-runtime.ts',
107
108
  'src/plugin/ack-outbox-runtime-group.ts',
108
109
  'src/plugin/bridge-ack-facade.ts',
110
+ 'src/plugin/bridge-call.ts',
109
111
  'src/plugin/bridge-connection-facade.ts',
110
112
  'src/plugin/bridge-diagnostics-facade.ts',
111
113
  'src/plugin/bridge-drain-facade.ts',
@@ -133,6 +135,7 @@ const requiredSourceFiles = [
133
135
  'src/plugin/channel-runtime-types.ts',
134
136
  'src/plugin/channel-send.ts',
135
137
  'src/plugin/channel-utils.ts',
138
+ 'src/plugin/client-rpc-runtime.ts',
136
139
  'src/plugin/config.ts',
137
140
  'src/plugin/connection-handlers-helpers.ts',
138
141
  'src/plugin/connection-handlers.ts',
@@ -23,6 +23,7 @@ export type BncrGatewayMethodName =
23
23
  | 'bncr.diagnostics'
24
24
  | 'bncr.deadLetter.inspect'
25
25
  | 'bncr.deadLetter.prune'
26
+ | 'bncr.rpc.response'
26
27
  | 'bncr.file.init'
27
28
  | 'bncr.file.chunk'
28
29
  | 'bncr.file.complete'
@@ -97,6 +97,7 @@ export function createBncrRegisterRuntime() {
97
97
  'bncr.diagnostics': (bridge, opts) => bridge.handleDiagnostics(opts),
98
98
  'bncr.deadLetter.inspect': (bridge, opts) => bridge.handleDeadLetterInspect(opts),
99
99
  'bncr.deadLetter.prune': (bridge, opts) => bridge.handleDeadLetterPrune(opts),
100
+ 'bncr.rpc.response': (bridge, opts) => bridge.handleRpcResponse(opts),
100
101
  'bncr.file.init': (bridge, opts) => bridge.handleFileInit(opts),
101
102
  'bncr.file.chunk': (bridge, opts) => bridge.handleFileChunk(opts),
102
103
  'bncr.file.complete': (bridge, opts) => bridge.handleFileComplete(opts),
package/src/channel.ts CHANGED
@@ -4,7 +4,13 @@ import type {
4
4
  OpenClawPluginApi,
5
5
  OpenClawPluginServiceContext,
6
6
  } from 'openclaw/plugin-sdk/core';
7
- import { BNCR_DEFAULT_ACCOUNT_ID, CHANNEL_ID, normalizeAccountId } from './core/accounts.ts';
7
+ import { DEFAULT_GROUP_HISTORY_LIMIT as DEFAULT_HISTORY_LIMIT } from 'openclaw/plugin-sdk/reply-history';
8
+ import {
9
+ BNCR_DEFAULT_ACCOUNT_ID,
10
+ CHANNEL_ID,
11
+ normalizeAccountId,
12
+ resolveAccount,
13
+ } from './core/accounts.ts';
8
14
  import {
9
15
  countInvalidOutboxSessionKeys as countInvalidOutboxSessionKeysFromRuntime,
10
16
  countLegacyAccountResidue as countLegacyAccountResidueFromRuntime,
@@ -38,7 +44,8 @@ import type {
38
44
  FileSendTransferState,
39
45
  OutboxEntry,
40
46
  } from './core/types.ts';
41
- import type { BncrGroupHistoryMap } from './messaging/inbound/group-history.ts';
47
+ import type { BncrConversationHistoryMap } from './messaging/inbound/conversation-history.ts';
48
+ import type { BncrOutboundReplayCache } from './messaging/inbound/outbound-replay-cache.ts';
42
49
  import type { parseBncrInboundParams } from './messaging/inbound/parse.ts';
43
50
  import { buildEnqueueFromReplyDebugInfo } from './messaging/outbound/diagnostics.ts';
44
51
  import { buildBncrMediaOutboundFrame } from './messaging/outbound/media.ts';
@@ -160,6 +167,7 @@ import {
160
167
  now,
161
168
  resolveOutboundFileName,
162
169
  } from './plugin/channel-utils.ts';
170
+ import { createBncrClientRpcRuntime } from './plugin/client-rpc-runtime.ts';
163
171
  import { BNCR_CONFIG_SURFACE } from './plugin/config.ts';
164
172
  import { createBncrConnectionStateRuntimeGroup } from './plugin/connection-state-runtime-group.ts';
165
173
  import type { FileAckPayloadState } from './plugin/file-ack-runtime.ts';
@@ -286,7 +294,8 @@ class BncrBridgeRuntime {
286
294
  getSceneRegistry(): Map<string, BncrSceneRecord> {
287
295
  return this.sceneRegistry;
288
296
  }
289
- private groupHistories: BncrGroupHistoryMap = new Map();
297
+ private conversationHistories: BncrConversationHistoryMap = new Map();
298
+ private outboundReplayCache: BncrOutboundReplayCache = new Map();
290
299
  private routeAliases = new Map<
291
300
  string,
292
301
  { accountId: string; route: BncrRoute; updatedAt: number }
@@ -373,6 +382,14 @@ class BncrBridgeRuntime {
373
382
  >();
374
383
  private earlyFileAcks = new Map<string, FileAckPayloadState>();
375
384
 
385
+ private readonly clientRpcRuntime = createBncrClientRpcRuntime({
386
+ resolvePushConnIds: (accountId) => this.resolvePushConnIds(accountId),
387
+ broadcastToConnIds: (event, payload, connIds) =>
388
+ this.gatewayContext?.broadcastToConnIds(event, payload, connIds),
389
+ now,
390
+ logInfo: (scope, message, options) => this.logInfo(scope, message, options),
391
+ });
392
+
376
393
  private readonly bridgeSupportRuntime = createBridgeSupportRuntime({
377
394
  isStopped: () => this.stopped,
378
395
  hasSaveTimer: () => Boolean(this.saveTimer),
@@ -851,6 +868,7 @@ class BncrBridgeRuntime {
851
868
  }
852
869
 
853
870
  private cleanupRuntimeWaitersAndTimers(reason: string) {
871
+ this.clientRpcRuntime.shutdown();
854
872
  cleanupBncrBridgeRuntime(
855
873
  {
856
874
  bridgeId: this.bridgeId,
@@ -1759,6 +1777,7 @@ class BncrBridgeRuntime {
1759
1777
  setOutboxEntry: (messageId, entry) => this.outbox.set(messageId, entry),
1760
1778
  resolveMessageAck: (messageId, result) => this.resolveMessageAck(messageId, result),
1761
1779
  moveToDeadLetter: (entry, reason) => this.moveToDeadLetter(entry, reason),
1780
+ markRecentOutboundAcked: (entry) => this.markRecentOutboundAcked(entry),
1762
1781
  recordAckTimeoutTelemetry: (accountId) => this.recordAckTimeoutTelemetry(accountId),
1763
1782
  degradeOutboundCapability: (args) => this.degradeOutboundCapability(args),
1764
1783
  flushPushQueueBestEffort: (args) => this.flushPushQueueBestEffort(args),
@@ -1883,7 +1902,8 @@ class BncrBridgeRuntime {
1883
1902
  maxSessionRouteEntries: MAX_SESSION_ROUTE_ENTRIES,
1884
1903
  maxAccountActivityEntries: MAX_ACCOUNT_ACTIVITY_ENTRIES,
1885
1904
  sceneRegistry: this.sceneRegistry,
1886
- groupHistories: this.groupHistories,
1905
+ conversationHistories: this.conversationHistories,
1906
+ outboundReplayCache: this.outboundReplayCache,
1887
1907
  outbox: this.outbox,
1888
1908
  getDeadLetter: () => this.deadLetter,
1889
1909
  setDeadLetter: (entries) => {
@@ -2148,6 +2168,27 @@ class BncrBridgeRuntime {
2148
2168
  lastPrePushGuardSkipReasonByAccount: this.lastPrePushGuardSkipReasonByAccount,
2149
2169
  deadLetterSinceStartByAccount: this.deadLetterSinceStartByAccount,
2150
2170
  lastOutboundByAccount: this.lastOutboundByAccount,
2171
+ outboundReplayCache: this.outboundReplayCache,
2172
+ conversationHistories: this.conversationHistories,
2173
+ resolveOutboundHistoryLimit: (entry) => {
2174
+ const platform = entry.route?.platform || '';
2175
+ const groupId = entry.route?.groupId || '0';
2176
+ const userId = entry.route?.userId || '0';
2177
+ const sceneKey = groupId !== '0' ? `${platform}:${groupId}` : `${platform}:${userId}`;
2178
+ const sceneLimit = this.sceneRegistry.get(sceneKey)?.historyLimit;
2179
+ return typeof sceneLimit === 'number' && Number.isFinite(sceneLimit) && sceneLimit >= 0
2180
+ ? Math.floor(sceneLimit)
2181
+ : DEFAULT_HISTORY_LIMIT;
2182
+ },
2183
+ resolveOutboundSender: (entry) => {
2184
+ try {
2185
+ const account = resolveAccount(getOpenClawRuntimeConfig(this.api), entry.accountId);
2186
+ return { sender: account.name, senderId: entry.accountId };
2187
+ } catch {
2188
+ return { sender: 'OpenClaw', senderId: entry.accountId };
2189
+ }
2190
+ },
2191
+ isOutboundAckRequired: (accountId) => this.isOutboundAckRequired(accountId),
2151
2192
  scheduleSave: () => this.scheduleSave(),
2152
2193
  flushPushQueueBestEffort: (args) => this.flushPushQueueBestEffort(args),
2153
2194
  logInfo: (scope, message, options) => this.logInfo(scope, message, options),
@@ -2722,6 +2763,18 @@ class BncrBridgeRuntime {
2722
2763
  return this.bridgeMediaFacade.enqueueFromReply(params);
2723
2764
  }
2724
2765
 
2766
+ listRecentOutbound(sessionKey: string) {
2767
+ return this.bridgeOutboxFacade.listRecentOutbound(sessionKey);
2768
+ }
2769
+
2770
+ listRecentOutboundByAccount(accountId: string) {
2771
+ return this.bridgeOutboxFacade.listRecentOutboundByAccount(accountId);
2772
+ }
2773
+
2774
+ private markRecentOutboundAcked(entry: OutboxEntry) {
2775
+ this.bridgeOutboxFacade.markRecentOutboundAcked(entry);
2776
+ }
2777
+
2725
2778
  private logEnqueueFromReply(args: {
2726
2779
  accountId: string;
2727
2780
  sessionKey: string;
@@ -2852,7 +2905,8 @@ class BncrBridgeRuntime {
2852
2905
  defaultAdminAgentId: (args) => this.defaultAdminAgentId(args),
2853
2906
  defaultPublicAgentId: () => this.defaultPublicAgentId(),
2854
2907
  sceneRegistry: this.sceneRegistry,
2855
- groupHistories: this.groupHistories,
2908
+ conversationHistories: this.conversationHistories,
2909
+ outboundReplayCache: this.outboundReplayCache,
2856
2910
  prepareInboundAcceptance: (args) => this.prepareInboundAcceptance(args),
2857
2911
  logInboundSummary: (args) => this.logInboundSummary(args),
2858
2912
  flushPushQueueBestEffort: (args) => this.flushPushQueueBestEffort(args),
@@ -2922,6 +2976,12 @@ class BncrBridgeRuntime {
2922
2976
  handleDeadLetterPrune = async (ctx: GatewayRequestHandlerOptions) =>
2923
2977
  this.diagnosticsHandlers.handleDeadLetterPrune(ctx);
2924
2978
 
2979
+ handleRpcResponse = async (ctx: GatewayRequestHandlerOptions) =>
2980
+ this.clientRpcRuntime.handleResponse(ctx);
2981
+
2982
+ callClientRpc = async (method: string, args: Record<string, unknown>, accountId: string) =>
2983
+ this.clientRpcRuntime.call(method, args, normalizeAccountId(accountId));
2984
+
2925
2985
  handleFileInit = async (ctx: GatewayRequestHandlerOptions) =>
2926
2986
  this.fileInboundHandlers.handleFileInit(ctx);
2927
2987
 
@@ -3024,6 +3084,7 @@ export function createBncrChannelPlugin(getBridge: () => BncrBridgeRuntime) {
3024
3084
  getStatusBridge: bridgeGroup.getStatusBridge,
3025
3085
  getToolActionBridge: bridgeGroup.getToolActionBridge,
3026
3086
  getGatewayBridge: bridgeGroup.getGatewayBridge,
3087
+ getBridgeCallBridge: bridgeGroup.getBridgeCallBridge,
3027
3088
  channelMeta: BNCR_CHANNEL_META,
3028
3089
  channelCapabilities: BNCR_CHANNEL_CAPABILITIES,
3029
3090
  gatewayMethods: BNCR_GATEWAY_METHODS,
package/src/core/types.ts CHANGED
@@ -118,6 +118,47 @@ export type OutboxEntry = {
118
118
  awaitingRetryPush?: boolean;
119
119
  };
120
120
 
121
+ export type BncrRecentOutboundEntry = {
122
+ messageId: string;
123
+ accountId: string;
124
+ sessionKey: string;
125
+ route: BncrRoute;
126
+ type?: string;
127
+ kind?: string;
128
+ text: string;
129
+ mediaUrl?: string;
130
+ createdAt: number;
131
+ lastPushAt?: number;
132
+ lastError?: string;
133
+ ackedAt?: number;
134
+ status: 'queued' | 'pushed' | 'failed' | 'acked';
135
+ };
136
+
137
+ export type BncrOutboundReplayMediaEntry = {
138
+ path?: string;
139
+ contentType?: string;
140
+ kind?: 'image' | 'video' | 'audio' | 'document' | 'unknown';
141
+ messageId?: string;
142
+ };
143
+
144
+ export type BncrOutboundReplayStatus = 'pushed' | 'acked';
145
+
146
+ export type BncrOutboundReplayEntry = {
147
+ sender: string;
148
+ senderId?: string;
149
+ body: string;
150
+ timestamp?: number;
151
+ messageId?: string;
152
+ media?: BncrOutboundReplayMediaEntry[];
153
+ accountId?: string;
154
+ sessionKey?: string;
155
+ route?: BncrRoute;
156
+ type?: string;
157
+ mediaUrl?: string;
158
+ createdAt?: number;
159
+ status?: BncrOutboundReplayStatus;
160
+ };
161
+
121
162
  export type BncrDiagnosticsSummary = {
122
163
  health: {
123
164
  connected: boolean;
@@ -1,5 +1,37 @@
1
1
  import path from 'node:path';
2
2
 
3
+ type BncrConversationContextMedia = {
4
+ type?: string;
5
+ path?: string;
6
+ contentType?: string;
7
+ kind?: string;
8
+ messageId?: string;
9
+ };
10
+
11
+ type BncrConversationContextEntry = {
12
+ messageId?: string;
13
+ timestamp?: number;
14
+ role?: 'user' | 'assistant' | 'system';
15
+ sender?: string;
16
+ senderId?: string;
17
+ content?: string;
18
+ media?: BncrConversationContextMedia[];
19
+ };
20
+
21
+ type BncrParticipantRecord = Record<
22
+ string,
23
+ {
24
+ name: string;
25
+ username?: string;
26
+ isBot?: boolean;
27
+ isAdmin?: boolean;
28
+ isOwner?: boolean;
29
+ isAuthorizedSender?: boolean;
30
+ role?: 'owner' | 'admin' | 'user';
31
+ displayName?: string;
32
+ }
33
+ >;
34
+
3
35
  export type BncrStructuredContextFactsInput = {
4
36
  channelId: string;
5
37
  accountId: string;
@@ -57,18 +89,10 @@ export type BncrStructuredContextFactsInput = {
57
89
  kind?: string;
58
90
  messageId?: string;
59
91
  }>;
60
- pendingMediaContext?: Array<{
61
- messageId?: string;
62
- sender?: string;
63
- senderId?: string;
64
- body?: string;
65
- media?: Array<{
66
- path?: string;
67
- contentType?: string;
68
- kind?: string;
69
- messageId?: string;
70
- }>;
71
- }>;
92
+ conversationContext?: BncrConversationContextEntry[];
93
+ participants?: BncrParticipantRecord | null;
94
+ isGroupChat?: boolean;
95
+ groupSubject?: string;
72
96
  };
73
97
 
74
98
  function toBncrPromptMediaPath(id: string): string | undefined {
@@ -166,18 +190,24 @@ export function buildBncrStructuredContextFacts(input: BncrStructuredContextFact
166
190
  kind: item.kind,
167
191
  messageId: item.messageId,
168
192
  })),
169
- pendingMediaContext: (input.pendingMediaContext || []).map((entry) => ({
193
+ conversationContext: (input.conversationContext || []).map((entry) => ({
170
194
  messageId: entry.messageId,
195
+ timestamp: entry.timestamp,
196
+ role: entry.role,
171
197
  sender: entry.sender,
172
198
  senderId: entry.senderId,
173
- body: entry.body,
199
+ content: entry.content,
174
200
  media: (entry.media || []).map((item) => ({
201
+ type: item.type || item.kind || 'document',
175
202
  path: item.path,
176
203
  contentType: item.contentType,
177
- kind: item.kind,
178
204
  messageId: item.messageId,
179
205
  })),
180
206
  })),
207
+ participants: input.participants ?? null,
208
+ isGroupChat: input.isGroupChat,
209
+ groupSubject: input.groupSubject,
210
+ accountId: input.accountId,
181
211
  };
182
212
  }
183
213
 
@@ -210,18 +240,11 @@ export function buildBncrPromptVisibleContextFacts(
210
240
  kind?: string;
211
241
  messageId?: string;
212
242
  }>;
213
- pendingMediaContext?: Array<{
214
- messageId?: string;
215
- sender?: string;
216
- senderId?: string;
217
- body?: string;
218
- media?: Array<{
219
- path?: string;
220
- contentType?: string;
221
- kind?: string;
222
- messageId?: string;
223
- }>;
224
- }>;
243
+ conversation_context?: Array<Record<string, unknown>>;
244
+ participants?: BncrParticipantRecord;
245
+ is_group_chat?: boolean;
246
+ group_subject?: string;
247
+ account_id?: string;
225
248
  } = {};
226
249
 
227
250
  if (facts.platform) {
@@ -272,27 +295,39 @@ export function buildBncrPromptVisibleContextFacts(
272
295
  return Object.values(payload).some((field) => field !== undefined) ? [payload] : [];
273
296
  });
274
297
  }
275
- if (facts.pendingMediaContext?.length) {
276
- result.pendingMediaContext = facts.pendingMediaContext.flatMap((entry) => {
277
- const media = (entry.media || []).flatMap((item) => {
298
+ if (facts.conversationContext?.length) {
299
+ result.conversation_context = facts.conversationContext.flatMap((entry) => {
300
+ const items: Array<Record<string, unknown>> = [];
301
+ if (entry.content) {
302
+ items.push({
303
+ messageId: entry.messageId,
304
+ timestamp: entry.timestamp,
305
+ role: entry.role,
306
+ sender: entry.sender,
307
+ senderId: entry.senderId,
308
+ content: entry.content,
309
+ });
310
+ }
311
+ for (const item of entry.media || []) {
278
312
  const promptMediaPath = resolveBncrPromptMediaPath(item.path);
279
313
  const payload = {
314
+ messageId: item.messageId ?? entry.messageId,
315
+ timestamp: entry.timestamp,
316
+ role: entry.role,
317
+ sender: entry.sender,
318
+ senderId: entry.senderId,
319
+ media_type: item.type || 'document',
280
320
  ...(promptMediaPath ? { path: promptMediaPath } : {}),
281
- contentType: item.contentType,
282
- kind: item.kind,
283
- messageId: item.messageId,
321
+ ...(item.contentType ? { contentType: item.contentType } : {}),
284
322
  };
285
- return Object.values(payload).some((field) => field !== undefined) ? [payload] : [];
286
- });
287
- const payload = {
288
- messageId: entry.messageId,
289
- sender: entry.sender,
290
- senderId: entry.senderId,
291
- body: entry.body,
292
- ...(media.length > 0 ? { media } : {}),
293
- };
294
- return Object.values(payload).some((field) => field !== undefined) ? [payload] : [];
323
+ if (Object.values(payload).some((field) => field !== undefined)) items.push(payload);
324
+ }
325
+ return items;
295
326
  });
327
+ if (facts.participants) result.participants = facts.participants;
328
+ if (facts.isGroupChat !== undefined) result.is_group_chat = facts.isGroupChat;
329
+ if (facts.groupSubject) result.group_subject = facts.groupSubject;
330
+ if (facts.accountId) result.account_id = facts.accountId;
296
331
  }
297
332
 
298
333
  return result;
@@ -358,25 +393,17 @@ export type BncrStructuredContextFactsFromInboundPartsInput = {
358
393
  bridgeSenderName?: string;
359
394
  senderIsOwner?: boolean;
360
395
  senderIsAuthorized?: boolean;
361
- pendingHistoryContext?: Array<{
362
- messageId?: string;
363
- sender?: string;
364
- senderId?: string;
365
- body?: string;
366
- media?: Array<{
367
- path?: string;
368
- contentType?: string;
369
- kind?: string;
370
- messageId?: string;
371
- }>;
372
- }>;
396
+ conversationHistoryContext?: BncrConversationContextEntry[];
397
+ participants?: BncrParticipantRecord | null;
398
+ isGroupChat?: boolean;
399
+ groupSubject?: string;
373
400
  };
374
401
  export function buildBncrStructuredContextFactsFromInboundParts(
375
402
  input: BncrStructuredContextFactsFromInboundPartsInput,
376
403
  ) {
377
404
  const mediaItems = Array.isArray(input.prepared.mediaItems) ? input.prepared.mediaItems : [];
378
- const pendingHistoryContext = Array.isArray(input.pendingHistoryContext)
379
- ? input.pendingHistoryContext
405
+ const conversationHistoryContext = Array.isArray(input.conversationHistoryContext)
406
+ ? input.conversationHistoryContext
380
407
  : [];
381
408
  return buildBncrStructuredContextFacts({
382
409
  channelId: input.channelId,
@@ -435,6 +462,9 @@ export function buildBncrStructuredContextFactsFromInboundParts(
435
462
  kind: item.kind || inferBncrStructuredMediaKind(item.contentType),
436
463
  messageId: input.parsed.msgId,
437
464
  })),
438
- pendingMediaContext: pendingHistoryContext,
465
+ conversationContext: conversationHistoryContext,
466
+ participants: input.participants ?? null,
467
+ isGroupChat: input.isGroupChat,
468
+ groupSubject: input.groupSubject,
439
469
  });
440
470
  }