@xmoxmo/bncr 0.2.5 → 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.
Files changed (41) hide show
  1. package/README.md +9 -3
  2. package/index.ts +30 -15
  3. package/package.json +4 -3
  4. package/scripts/check-pack.mjs +61 -0
  5. package/scripts/selfcheck.mjs +10 -0
  6. package/src/channel.ts +892 -255
  7. package/src/core/connection-reachability.ts +41 -14
  8. package/src/core/diagnostics.ts +7 -2
  9. package/src/core/downlink-health.ts +7 -2
  10. package/src/core/outbox-entry-builders.ts +3 -2
  11. package/src/core/policy.ts +9 -0
  12. package/src/core/register-trace.ts +6 -1
  13. package/src/core/status.ts +7 -2
  14. package/src/core/targets.ts +10 -1
  15. package/src/core/types.ts +1 -0
  16. package/src/messaging/inbound/commands.ts +330 -77
  17. package/src/messaging/inbound/context-facts.ts +200 -0
  18. package/src/messaging/inbound/dispatch.ts +429 -119
  19. package/src/messaging/inbound/gate.ts +66 -26
  20. package/src/messaging/inbound/parse.ts +8 -0
  21. package/src/messaging/inbound/runtime-compat.ts +39 -0
  22. package/src/messaging/inbound/session-label.ts +115 -0
  23. package/src/messaging/outbound/diagnostics.ts +16 -0
  24. package/src/messaging/outbound/durable-message-adapter.ts +107 -0
  25. package/src/messaging/outbound/durable-queue-adapter.ts +157 -0
  26. package/src/messaging/outbound/media.ts +3 -1
  27. package/src/messaging/outbound/queue-selectors.ts +7 -2
  28. package/src/messaging/outbound/reasons.ts +4 -0
  29. package/src/messaging/outbound/reply-enqueue.ts +2 -2
  30. package/src/messaging/outbound/reply-target-policy.ts +13 -0
  31. package/src/messaging/outbound/retry-policy.ts +12 -3
  32. package/src/messaging/outbound/send.ts +6 -0
  33. package/src/messaging/outbound/session-route.ts +2 -2
  34. package/src/openclaw/config-runtime.ts +52 -0
  35. package/src/openclaw/inbound-session-runtime.ts +94 -0
  36. package/src/openclaw/ingress-runtime.ts +35 -0
  37. package/src/openclaw/media-runtime.ts +73 -0
  38. package/src/openclaw/reply-runtime.ts +104 -0
  39. package/src/openclaw/routing-runtime.ts +48 -0
  40. package/src/openclaw/sdk-helpers.ts +20 -0
  41. package/src/openclaw/session-route-runtime.ts +15 -0
@@ -1,5 +1,11 @@
1
1
  import type { BncrRoute } from '../../channel.ts';
2
2
 
3
+ function finiteNonNegativeIntegerOr(value: unknown, fallback: number): number {
4
+ const n = Number(value);
5
+ if (!Number.isFinite(n) || n < 0) return fallback;
6
+ return Math.floor(n);
7
+ }
8
+
3
9
  export type RetryRerouteDecisionInput = {
4
10
  nowMs: number;
5
11
  maxRetry: number;
@@ -51,7 +57,9 @@ export function computeRetryRerouteDecision(
51
57
  const hasUntriedAlternative = availableConnIds.some((connId) => !attemptedConnIds.includes(connId));
52
58
  const shouldFastReroute = input.requireAck && input.currentFastReroutePending !== true && hasUntriedAlternative;
53
59
 
54
- const nextRetryCount = Number(input.currentRetryCount || 0) + 1;
60
+ const currentRetryCount = finiteNonNegativeIntegerOr(input.currentRetryCount, 0);
61
+ const currentRouteAttemptRound = finiteNonNegativeIntegerOr(input.currentRouteAttemptRound, 0);
62
+ const nextRetryCount = currentRetryCount + 1;
55
63
  const lastAttemptAt = input.nowMs;
56
64
  const terminalReason =
57
65
  input.lastError || (input.requireAck ? 'push-ack-timeout' : 'push-delivery-unconfirmed');
@@ -67,7 +75,7 @@ export function computeRetryRerouteDecision(
67
75
 
68
76
  const nextAttemptAt = shouldFastReroute ? input.nowMs + 1_000 : input.nowMs + deps.backoffMs(nextRetryCount);
69
77
  const lastError = input.requireAck ? 'push-ack-timeout' : 'push-delivery-unconfirmed';
70
- const routeAttemptRound = hasUntriedAlternative ? Number(input.currentRouteAttemptRound || 0) : Number(input.currentRouteAttemptRound || 0) + 1;
78
+ const routeAttemptRound = hasUntriedAlternative ? currentRouteAttemptRound : currentRouteAttemptRound + 1;
71
79
  const fastReroutePending = hasUntriedAlternative ? shouldFastReroute || input.currentFastReroutePending === true : false;
72
80
 
73
81
  return {
@@ -111,7 +119,8 @@ export function computePushFailureDecision(
111
119
  input: PushFailureDecisionInput,
112
120
  deps: { backoffMs: (retryCount: number) => number },
113
121
  ): PushFailureDecision {
114
- const nextRetryCount = Number(input.currentRetryCount || 0) + 1;
122
+ const currentRetryCount = finiteNonNegativeIntegerOr(input.currentRetryCount, 0);
123
+ const nextRetryCount = currentRetryCount + 1;
115
124
  const lastAttemptAt = input.nowMs;
116
125
 
117
126
  if (nextRetryCount > input.maxRetry) {
@@ -3,6 +3,7 @@ export async function sendBncrText(params: {
3
3
  accountId: string;
4
4
  to: string;
5
5
  text: string;
6
+ kind?: 'tool' | 'block' | 'final';
6
7
  replyToId?: string;
7
8
  mediaLocalRoots?: readonly string[];
8
9
  resolveVerifiedTarget: (
@@ -18,6 +19,7 @@ export async function sendBncrText(params: {
18
19
  text?: string;
19
20
  mediaUrl?: string;
20
21
  mediaUrls?: string[];
22
+ kind?: 'tool' | 'block' | 'final';
21
23
  replyToId?: string;
22
24
  };
23
25
  mediaLocalRoots?: readonly string[];
@@ -33,6 +35,7 @@ export async function sendBncrText(params: {
33
35
  route: verified.route,
34
36
  payload: {
35
37
  text: params.text,
38
+ kind: params.kind,
36
39
  replyToId: params.replyToId,
37
40
  },
38
41
  mediaLocalRoots: params.mediaLocalRoots,
@@ -54,6 +57,7 @@ export async function sendBncrMedia(params: {
54
57
  mediaUrls?: string[];
55
58
  asVoice?: boolean;
56
59
  audioAsVoice?: boolean;
60
+ kind?: 'tool' | 'block' | 'final';
57
61
  replyToId?: string;
58
62
  mediaLocalRoots?: readonly string[];
59
63
  resolveVerifiedTarget: (
@@ -71,6 +75,7 @@ export async function sendBncrMedia(params: {
71
75
  mediaUrls?: string[];
72
76
  asVoice?: boolean;
73
77
  audioAsVoice?: boolean;
78
+ kind?: 'tool' | 'block' | 'final';
74
79
  replyToId?: string;
75
80
  };
76
81
  mediaLocalRoots?: readonly string[];
@@ -90,6 +95,7 @@ export async function sendBncrMedia(params: {
90
95
  mediaUrls: params.mediaUrls?.length ? params.mediaUrls : undefined,
91
96
  asVoice: params.asVoice === true ? true : undefined,
92
97
  audioAsVoice: params.audioAsVoice === true ? true : undefined,
98
+ kind: params.kind,
93
99
  replyToId: params.replyToId,
94
100
  },
95
101
  mediaLocalRoots: params.mediaLocalRoots,
@@ -1,4 +1,4 @@
1
- import { buildChannelOutboundSessionRoute } from 'openclaw/plugin-sdk/core';
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 = buildChannelOutboundSessionRoute({
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
+
@@ -0,0 +1,20 @@
1
+ import { readBooleanParam as sdkReadBooleanParam } from 'openclaw/plugin-sdk/boolean-param';
2
+ import {
3
+ applyAccountNameToChannelSection as sdkApplyAccountNameToChannelSection,
4
+ jsonResult as sdkJsonResult,
5
+ setAccountEnabledInConfigSection as sdkSetAccountEnabledInConfigSection,
6
+ } from 'openclaw/plugin-sdk/core';
7
+ import { readJsonFileWithFallback as sdkReadJsonFileWithFallback, writeJsonFileAtomically as sdkWriteJsonFileAtomically } from 'openclaw/plugin-sdk/json-store';
8
+ import { readStringParam as sdkReadStringParam } from 'openclaw/plugin-sdk/param-readers';
9
+ import { createDefaultChannelRuntimeState as sdkCreateDefaultChannelRuntimeState } from 'openclaw/plugin-sdk/status-helpers';
10
+ import { extractToolSend as sdkExtractToolSend } from 'openclaw/plugin-sdk/tool-send';
11
+
12
+ export const readOpenClawBooleanParam = sdkReadBooleanParam;
13
+ export const readOpenClawStringParam = sdkReadStringParam;
14
+ export const readOpenClawJsonFileWithFallback = sdkReadJsonFileWithFallback;
15
+ export const writeOpenClawJsonFileAtomically = sdkWriteJsonFileAtomically;
16
+ export const createOpenClawDefaultChannelRuntimeState = sdkCreateDefaultChannelRuntimeState;
17
+ export const extractOpenClawToolSend = sdkExtractToolSend;
18
+ export const openClawJsonResult = sdkJsonResult;
19
+ export const applyOpenClawAccountNameToChannelSection = sdkApplyAccountNameToChannelSection;
20
+ export const setOpenClawAccountEnabledInConfigSection = sdkSetAccountEnabledInConfigSection;
@@ -0,0 +1,15 @@
1
+ import { buildChannelOutboundSessionRoute } from 'openclaw/plugin-sdk/core';
2
+
3
+ export function buildOpenClawChannelOutboundSessionRoute(params: {
4
+ cfg: any;
5
+ agentId: string;
6
+ channel: string;
7
+ accountId?: string;
8
+ peer: unknown;
9
+ chatType: 'direct' | 'group';
10
+ from: string;
11
+ to: string;
12
+ threadId?: string;
13
+ }): Record<string, unknown> {
14
+ return buildChannelOutboundSessionRoute(params as any) as Record<string, unknown>;
15
+ }