@xmoxmo/bncr 0.3.6 → 0.3.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 (164) hide show
  1. package/README.md +5 -0
  2. package/dist/index.js +28 -5
  3. package/index.ts +55 -721
  4. package/package.json +8 -4
  5. package/scripts/check-pack.mjs +93 -18
  6. package/scripts/check-register-drift.mjs +35 -13
  7. package/scripts/selfcheck.mjs +80 -11
  8. package/src/bootstrap/channel-plugin-runtime.ts +81 -0
  9. package/src/bootstrap/cli.ts +97 -0
  10. package/src/bootstrap/register-runtime-gateway.ts +129 -0
  11. package/src/bootstrap/register-runtime-helpers.ts +140 -0
  12. package/src/bootstrap/register-runtime-singleton.ts +137 -0
  13. package/src/bootstrap/register-runtime.ts +201 -0
  14. package/src/bootstrap/runtime-discovery.ts +187 -0
  15. package/src/bootstrap/runtime-loader.ts +54 -0
  16. package/src/channel.ts +1590 -4967
  17. package/src/core/accounts.ts +23 -4
  18. package/src/core/dead-letter-diagnostics.ts +37 -5
  19. package/src/core/diagnostics.ts +31 -15
  20. package/src/core/downlink-health.ts +3 -11
  21. package/src/core/extended-diagnostics.ts +78 -36
  22. package/src/core/file-transfer-payloads.ts +1 -1
  23. package/src/core/logging.ts +1 -0
  24. package/src/core/outbox-enqueue.ts +13 -2
  25. package/src/core/outbox-entry-builders.ts +2 -0
  26. package/src/core/outbox-summary.ts +75 -3
  27. package/src/core/permissions.ts +15 -2
  28. package/src/core/persisted-outbox-entry.ts +21 -6
  29. package/src/core/policy.ts +45 -4
  30. package/src/core/probe.ts +3 -15
  31. package/src/core/register-trace.ts +3 -3
  32. package/src/core/status.ts +43 -4
  33. package/src/core/targets.ts +216 -205
  34. package/src/core/types.ts +221 -0
  35. package/src/core/value-sanitize.ts +29 -0
  36. package/src/messaging/inbound/commands.ts +147 -172
  37. package/src/messaging/inbound/context-facts.ts +4 -2
  38. package/src/messaging/inbound/contracts.ts +70 -0
  39. package/src/messaging/inbound/dispatch-prep.ts +303 -0
  40. package/src/messaging/inbound/dispatch.ts +49 -462
  41. package/src/messaging/inbound/gate.ts +18 -5
  42. package/src/messaging/inbound/last-route.ts +10 -4
  43. package/src/messaging/inbound/media-url-download.ts +109 -0
  44. package/src/messaging/inbound/native-command-runtime.ts +225 -0
  45. package/src/messaging/inbound/parse.ts +2 -1
  46. package/src/messaging/inbound/remote-media.ts +49 -0
  47. package/src/messaging/inbound/reply-config.ts +16 -4
  48. package/src/messaging/inbound/reply-dispatch.ts +162 -0
  49. package/src/messaging/inbound/runtime-compat.ts +31 -10
  50. package/src/messaging/inbound/session-label.ts +15 -7
  51. package/src/messaging/inbound/turn-context.ts +131 -0
  52. package/src/messaging/outbound/actions.ts +24 -10
  53. package/src/messaging/outbound/diagnostics-debug-builders.ts +365 -0
  54. package/src/messaging/outbound/diagnostics.ts +31 -355
  55. package/src/messaging/outbound/durable-message-adapter.ts +20 -16
  56. package/src/messaging/outbound/durable-queue-adapter.ts +20 -7
  57. package/src/messaging/outbound/media.ts +24 -13
  58. package/src/messaging/outbound/reply-enqueue-media.ts +181 -0
  59. package/src/messaging/outbound/reply-enqueue.ts +46 -155
  60. package/src/messaging/outbound/send-params.ts +3 -0
  61. package/src/messaging/outbound/send.ts +19 -10
  62. package/src/messaging/outbound/session-route.ts +18 -3
  63. package/src/openclaw/channel-runtime-contracts.ts +76 -0
  64. package/src/openclaw/config-runtime.ts +13 -7
  65. package/src/openclaw/inbound-session-runtime.ts +7 -3
  66. package/src/openclaw/ingress-runtime.ts +17 -27
  67. package/src/openclaw/reply-runtime.ts +54 -59
  68. package/src/openclaw/routing-runtime.ts +35 -18
  69. package/src/openclaw/runtime-surface.ts +156 -12
  70. package/src/openclaw/sdk-helpers.ts +8 -1
  71. package/src/openclaw/session-route-runtime.ts +12 -12
  72. package/src/plugin/ack-outbox-runtime-group.ts +264 -0
  73. package/src/plugin/bridge-ack-facade.ts +137 -0
  74. package/src/plugin/bridge-connection-facade.ts +111 -0
  75. package/src/plugin/bridge-diagnostics-facade.ts +23 -0
  76. package/src/plugin/bridge-drain-facade.ts +98 -0
  77. package/src/plugin/bridge-extended-diagnostics-facade.ts +149 -0
  78. package/src/plugin/bridge-file-transfer-push-facade.ts +140 -0
  79. package/src/plugin/bridge-lifecycle.ts +156 -0
  80. package/src/plugin/bridge-media-facade.ts +241 -0
  81. package/src/plugin/bridge-outbox-facade.ts +182 -0
  82. package/src/plugin/bridge-runtime-helpers.ts +266 -0
  83. package/src/plugin/bridge-runtime-snapshots.ts +104 -0
  84. package/src/plugin/bridge-runtime-surface-facade.ts +8 -0
  85. package/src/plugin/bridge-status-facade.ts +76 -0
  86. package/src/plugin/bridge-status-worker-facade.ts +72 -0
  87. package/src/plugin/bridge-support-runtime.ts +137 -0
  88. package/src/plugin/bridge-surface-handlers-group.ts +242 -0
  89. package/src/plugin/bridge-surface-helpers.ts +28 -0
  90. package/src/plugin/capabilities.ts +1 -3
  91. package/src/plugin/channel-components.ts +289 -0
  92. package/src/plugin/channel-inbound-helpers.ts +149 -0
  93. package/src/plugin/channel-plugin-bridge-group.ts +129 -0
  94. package/src/plugin/channel-plugin-surface-group.ts +202 -0
  95. package/src/plugin/channel-runtime-builders-delivery.ts +513 -0
  96. package/src/plugin/channel-runtime-builders-status.ts +331 -0
  97. package/src/plugin/channel-runtime-builders.ts +25 -0
  98. package/src/plugin/channel-runtime-constants.ts +40 -0
  99. package/src/plugin/channel-runtime-types.ts +146 -0
  100. package/src/plugin/channel-send-runtime-group.ts +37 -0
  101. package/src/plugin/channel-send.ts +226 -0
  102. package/src/plugin/channel-utils.ts +102 -0
  103. package/src/plugin/config.ts +24 -3
  104. package/src/plugin/connection-handlers-helpers.ts +254 -0
  105. package/src/plugin/connection-handlers.ts +440 -0
  106. package/src/plugin/connection-state-helpers.ts +159 -0
  107. package/src/plugin/connection-state-runtime-group.ts +51 -0
  108. package/src/plugin/connection-state.ts +527 -0
  109. package/src/plugin/diagnostics-handlers.ts +211 -0
  110. package/src/plugin/error-message.ts +15 -0
  111. package/src/plugin/file-ack-runtime.ts +284 -0
  112. package/src/plugin/file-inbound-abort.ts +112 -0
  113. package/src/plugin/file-inbound-chunk.ts +146 -0
  114. package/src/plugin/file-inbound-complete.ts +153 -0
  115. package/src/plugin/file-inbound-handlers.ts +19 -0
  116. package/src/plugin/file-inbound-init.ts +122 -0
  117. package/src/plugin/file-inbound-runtime.ts +51 -0
  118. package/src/plugin/file-inbound-state.ts +62 -0
  119. package/src/plugin/file-transfer-logs.ts +227 -0
  120. package/src/plugin/file-transfer-orchestrator-chunk.ts +135 -0
  121. package/src/plugin/file-transfer-orchestrator.ts +304 -0
  122. package/src/plugin/file-transfer-runtime-group.ts +102 -0
  123. package/src/plugin/file-transfer-send.ts +89 -0
  124. package/src/plugin/file-transfer-setup.ts +206 -0
  125. package/src/plugin/gateway-event-context.ts +41 -0
  126. package/src/plugin/gateway-runtime.ts +14 -4
  127. package/src/plugin/inbound-acceptance.ts +107 -0
  128. package/src/plugin/inbound-handlers.ts +248 -0
  129. package/src/plugin/inbound-surface-handlers-group.ts +152 -0
  130. package/src/plugin/media-dedupe-runtime.ts +90 -0
  131. package/src/plugin/media-orchestrators-runtime-group.ts +316 -0
  132. package/src/plugin/message-ack-runtime.ts +284 -0
  133. package/src/plugin/message-send.ts +16 -6
  134. package/src/plugin/messaging.ts +98 -36
  135. package/src/plugin/outbound.ts +50 -8
  136. package/src/plugin/outbox-ack-logs.ts +136 -0
  137. package/src/plugin/outbox-ack-outcome.ts +128 -0
  138. package/src/plugin/outbox-drain-ack.ts +145 -0
  139. package/src/plugin/outbox-drain-failure.ts +84 -0
  140. package/src/plugin/outbox-drain-loop.ts +554 -0
  141. package/src/plugin/outbox-drain-post-push.ts +159 -0
  142. package/src/plugin/outbox-drain-runtime.ts +141 -0
  143. package/src/plugin/outbox-drain-schedule.ts +116 -0
  144. package/src/plugin/outbox-file-push-flow.ts +69 -0
  145. package/src/plugin/outbox-push-route-runtime-group.ts +81 -0
  146. package/src/plugin/outbox-push.ts +267 -0
  147. package/src/plugin/outbox-route.ts +181 -0
  148. package/src/plugin/outbox-text-push-flow.ts +90 -0
  149. package/src/plugin/runtime-diagnostics-assembler.ts +183 -0
  150. package/src/plugin/runtime-diagnostics-helpers.ts +302 -0
  151. package/src/plugin/runtime-diagnostics-payload-builders.ts +171 -0
  152. package/src/plugin/runtime-diagnostics-snapshot.ts +31 -0
  153. package/src/plugin/setup.ts +33 -6
  154. package/src/plugin/state-store.ts +249 -0
  155. package/src/plugin/state-transient-runtime-group.ts +105 -0
  156. package/src/plugin/status-runtime.ts +251 -0
  157. package/src/plugin/status.ts +33 -7
  158. package/src/plugin/target-runtime.ts +141 -0
  159. package/src/plugin/target-status-runtime-group.ts +130 -0
  160. package/src/plugin/transient-state-runtime.ts +82 -0
  161. package/src/runtime/outbound-ack-timeout.ts +5 -3
  162. package/src/runtime/outbound-flags.ts +24 -8
  163. package/src/runtime/status-snapshots.ts +36 -7
  164. package/src/runtime/status-worker.ts +34 -4
@@ -1,6 +1,8 @@
1
+ import type { BncrChannelConfigRoot } from '../plugin/channel-runtime-types.ts';
2
+
1
3
  type RuntimeConfigApi = {
2
- current?: () => unknown;
3
- get?: () => unknown;
4
+ current?: () => BncrChannelConfigRoot;
5
+ get?: () => BncrChannelConfigRoot;
4
6
  mutateConfigFile?: (params: {
5
7
  afterWrite?: { mode?: string };
6
8
  mutate: (draft: Record<string, unknown>) => void;
@@ -9,17 +11,21 @@ type RuntimeConfigApi = {
9
11
 
10
12
  type RuntimeApiHolder = {
11
13
  runtime?: {
12
- config?: RuntimeConfigApi;
14
+ config?: unknown;
15
+ [key: string]: unknown;
13
16
  };
17
+ [key: string]: unknown;
14
18
  };
15
19
 
16
20
  function resolveConfigApi(api: RuntimeApiHolder): RuntimeConfigApi {
17
21
  const config = api?.runtime?.config;
18
- if (!config) throw new Error('OpenClaw runtime config API is unavailable');
19
- return config;
22
+ if (!config || typeof config !== 'object') {
23
+ throw new Error('OpenClaw runtime config API is unavailable');
24
+ }
25
+ return config as RuntimeConfigApi;
20
26
  }
21
27
 
22
- export function getOpenClawRuntimeConfig(api: RuntimeApiHolder): unknown {
28
+ export function getOpenClawRuntimeConfig(api: RuntimeApiHolder): BncrChannelConfigRoot {
23
29
  const config = resolveConfigApi(api);
24
30
  if (typeof config.current === 'function') return config.current();
25
31
  if (typeof config.get === 'function') return config.get();
@@ -29,7 +35,7 @@ export function getOpenClawRuntimeConfig(api: RuntimeApiHolder): unknown {
29
35
  export function getOpenClawRuntimeConfigOrDefault<T>(
30
36
  api: RuntimeApiHolder,
31
37
  fallback: T,
32
- ): unknown | T {
38
+ ): BncrChannelConfigRoot | T {
33
39
  try {
34
40
  return getOpenClawRuntimeConfig(api);
35
41
  } catch {
@@ -5,8 +5,12 @@ import {
5
5
  resolveStorePath as sdkResolveStorePath,
6
6
  updateSessionStoreEntry as sdkUpdateSessionStoreEntry,
7
7
  } from 'openclaw/plugin-sdk/session-store-runtime';
8
+ import type { OpenClawChannelRuntimeApiHolder } from './channel-runtime-contracts.ts';
8
9
 
9
- type ResolveStorePathFn = (storeConfig: unknown, options: { agentId: string }) => string;
10
+ type ResolveStorePathFn = (
11
+ storeConfig?: string,
12
+ options?: { agentId?: string; env?: NodeJS.ProcessEnv },
13
+ ) => string;
10
14
  type RecordInboundSessionFn = typeof sdkRecordInboundSession;
11
15
  type RecordSessionMetaFromInboundFn = typeof sdkRecordSessionMetaFromInbound;
12
16
  type UpdateSessionStoreEntryFn = typeof sdkUpdateSessionStoreEntry;
@@ -40,7 +44,7 @@ function resolveRuntime(): BncrInboundSessionRuntime {
40
44
  }
41
45
 
42
46
  export function resolveBncrInboundSessionStorePath(args: {
43
- storeConfig: unknown;
47
+ storeConfig?: string;
44
48
  agentId: string;
45
49
  }): string {
46
50
  return resolveRuntime().resolveStorePath(args.storeConfig, { agentId: args.agentId });
@@ -65,7 +69,7 @@ export function updateBncrSessionStoreEntry(
65
69
  }
66
70
 
67
71
  export function readBncrSessionUpdatedAt(
68
- api: { runtime?: { channel?: { session?: { readSessionUpdatedAt?: ReadSessionUpdatedAtFn } } } },
72
+ api: OpenClawChannelRuntimeApiHolder,
69
73
  params: { storePath: string; sessionKey: string },
70
74
  ): unknown {
71
75
  const runtime = resolveRuntime();
@@ -3,33 +3,23 @@ import {
3
3
  resolveChannelMessageIngress as sdkResolveChannelMessageIngress,
4
4
  } from 'openclaw/plugin-sdk/channel-ingress-runtime';
5
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);
6
+ type StableChannelIngressIdentityParams = Parameters<
7
+ typeof sdkDefineStableChannelIngressIdentity
8
+ >[0];
9
+
10
+ type ResolveChannelMessageIngressParams = Parameters<typeof sdkResolveChannelMessageIngress>[0];
11
+ type ResolveChannelMessageIngressResult = Awaited<
12
+ ReturnType<typeof sdkResolveChannelMessageIngress>
13
+ >;
14
+
15
+ export function defineOpenClawStableChannelIngressIdentity(
16
+ params: StableChannelIngressIdentityParams,
17
+ ) {
18
+ return sdkDefineStableChannelIngressIdentity(params);
20
19
  }
21
20
 
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);
21
+ export async function resolveOpenClawChannelMessageIngress(
22
+ params: ResolveChannelMessageIngressParams,
23
+ ): Promise<ResolveChannelMessageIngressResult> {
24
+ return sdkResolveChannelMessageIngress(params);
35
25
  }
@@ -1,59 +1,32 @@
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
- };
1
+ import type {
2
+ OpenClawChannelRuntimeApiHolder,
3
+ OpenClawChannelRuntimeContext,
4
+ OpenClawReplyDispatcherPayload,
5
+ OpenClawReplyDispatchInfo,
6
+ } from './channel-runtime-contracts.ts';
40
7
 
41
- function resolveReplyApi(api: RuntimeApiHolder): RuntimeReplyApi {
8
+ function resolveReplyApi(api: OpenClawChannelRuntimeApiHolder): Record<string, unknown> {
42
9
  const reply = api?.runtime?.channel?.reply;
43
- if (!reply) throw new Error('OpenClaw channel reply API is unavailable');
44
- return reply;
10
+ if (!reply || typeof reply !== 'object') {
11
+ throw new Error('OpenClaw channel reply API is unavailable');
12
+ }
13
+ return reply as Record<string, unknown>;
45
14
  }
46
15
 
47
- export function resolveOpenClawEnvelopeFormatOptions(api: RuntimeApiHolder, cfg: unknown): unknown {
16
+ export function resolveOpenClawEnvelopeFormatOptions(
17
+ api: OpenClawChannelRuntimeApiHolder,
18
+ cfg: unknown,
19
+ ): unknown {
48
20
  const reply = resolveReplyApi(api);
49
- if (typeof reply.resolveEnvelopeFormatOptions !== 'function') {
21
+ const resolveEnvelopeFormatOptions = reply.resolveEnvelopeFormatOptions;
22
+ if (typeof resolveEnvelopeFormatOptions !== 'function') {
50
23
  throw new Error('OpenClaw channel reply resolveEnvelopeFormatOptions API is unavailable');
51
24
  }
52
- return reply.resolveEnvelopeFormatOptions(cfg);
25
+ return resolveEnvelopeFormatOptions(cfg);
53
26
  }
54
27
 
55
28
  export function formatOpenClawAgentEnvelope(
56
- api: RuntimeApiHolder,
29
+ api: OpenClawChannelRuntimeApiHolder,
57
30
  params: {
58
31
  channel: string;
59
32
  from: string;
@@ -64,26 +37,31 @@ export function formatOpenClawAgentEnvelope(
64
37
  },
65
38
  ): string {
66
39
  const reply = resolveReplyApi(api);
67
- if (typeof reply.formatAgentEnvelope !== 'function') {
40
+ const formatAgentEnvelope = reply.formatAgentEnvelope as
41
+ | ((params: {
42
+ channel: string;
43
+ from: string;
44
+ timestamp: number;
45
+ previousTimestamp?: unknown;
46
+ envelope?: unknown;
47
+ body: string;
48
+ }) => string)
49
+ | undefined;
50
+ if (typeof formatAgentEnvelope !== 'function') {
68
51
  throw new Error('OpenClaw channel reply formatAgentEnvelope API is unavailable');
69
52
  }
70
- return reply.formatAgentEnvelope(params);
53
+ return formatAgentEnvelope(params);
71
54
  }
72
55
 
73
56
  export async function dispatchOpenClawReplyWithBufferedBlockDispatcher(
74
- api: RuntimeApiHolder,
57
+ api: OpenClawChannelRuntimeApiHolder,
75
58
  params: {
76
- ctx: unknown;
59
+ ctx: OpenClawChannelRuntimeContext;
77
60
  cfg: unknown;
78
61
  dispatcherOptions: {
79
62
  deliver: (
80
- payload: {
81
- text?: string;
82
- mediaUrl?: string;
83
- mediaUrls?: string[];
84
- audioAsVoice?: boolean;
85
- },
86
- info?: { kind?: 'tool' | 'block' | 'final' },
63
+ payload: OpenClawReplyDispatcherPayload,
64
+ info?: OpenClawReplyDispatchInfo,
87
65
  ) => Promise<void> | void;
88
66
  onError?: (err: unknown) => void;
89
67
  };
@@ -94,10 +72,27 @@ export async function dispatchOpenClawReplyWithBufferedBlockDispatcher(
94
72
  },
95
73
  ): Promise<unknown> {
96
74
  const reply = resolveReplyApi(api);
97
- if (typeof reply.dispatchReplyWithBufferedBlockDispatcher !== 'function') {
75
+ const dispatchReplyWithBufferedBlockDispatcher = reply.dispatchReplyWithBufferedBlockDispatcher as
76
+ | ((params: {
77
+ ctx: OpenClawChannelRuntimeContext;
78
+ cfg: unknown;
79
+ dispatcherOptions: {
80
+ deliver: (
81
+ payload: OpenClawReplyDispatcherPayload,
82
+ info?: OpenClawReplyDispatchInfo,
83
+ ) => Promise<void> | void;
84
+ onError?: (err: unknown) => void;
85
+ };
86
+ replyOptions?: {
87
+ disableBlockStreaming?: boolean;
88
+ shouldEmitToolResult?: () => boolean;
89
+ };
90
+ }) => Promise<unknown> | unknown)
91
+ | undefined;
92
+ if (typeof dispatchReplyWithBufferedBlockDispatcher !== 'function') {
98
93
  throw new Error(
99
94
  'OpenClaw channel reply dispatchReplyWithBufferedBlockDispatcher API is unavailable',
100
95
  );
101
96
  }
102
- return reply.dispatchReplyWithBufferedBlockDispatcher(params);
97
+ return dispatchReplyWithBufferedBlockDispatcher(params);
103
98
  }
@@ -1,37 +1,43 @@
1
+ import { buildChannelOutboundSessionRoute } from 'openclaw/plugin-sdk/core';
1
2
  import { resolveInboundLastRouteSessionKey } from 'openclaw/plugin-sdk/routing';
3
+ import type { BncrChannelConfigRoot } from '../plugin/channel-runtime-types.ts';
4
+ import type {
5
+ OpenClawChannelRuntimeApiHolder,
6
+ OpenClawResolvedAgentRoute,
7
+ } from './channel-runtime-contracts.ts';
2
8
 
3
- type RuntimeRoutingApi = {
9
+ export type { OpenClawResolvedAgentRoute } from './channel-runtime-contracts.ts';
10
+
11
+ type OpenClawRoutingApi = {
4
12
  resolveAgentRoute?: (params: {
5
- cfg: any;
13
+ cfg: BncrChannelConfigRoot;
6
14
  channel: string;
7
15
  accountId: string;
8
16
  peer: unknown;
9
- }) => any;
17
+ }) => OpenClawResolvedAgentRoute;
10
18
  };
11
19
 
12
- type RuntimeApiHolder = {
13
- runtime?: {
14
- channel?: {
15
- routing?: RuntimeRoutingApi;
16
- };
17
- };
18
- };
20
+ type BuildChannelOutboundSessionRouteParams = Parameters<
21
+ typeof buildChannelOutboundSessionRoute
22
+ >[0];
19
23
 
20
- function resolveRoutingApi(api: RuntimeApiHolder): RuntimeRoutingApi {
24
+ function resolveRoutingApi(api: OpenClawChannelRuntimeApiHolder): OpenClawRoutingApi {
21
25
  const routing = api?.runtime?.channel?.routing;
22
- if (!routing) throw new Error('OpenClaw channel routing API is unavailable');
23
- return routing;
26
+ if (!routing || typeof routing !== 'object') {
27
+ throw new Error('OpenClaw channel routing API is unavailable');
28
+ }
29
+ return routing as OpenClawRoutingApi;
24
30
  }
25
31
 
26
32
  export function resolveOpenClawAgentRoute(
27
- api: RuntimeApiHolder,
33
+ api: OpenClawChannelRuntimeApiHolder,
28
34
  params: {
29
- cfg: any;
35
+ cfg: BncrChannelConfigRoot;
30
36
  channel: string;
31
37
  accountId: string;
32
38
  peer: unknown;
33
39
  },
34
- ): any {
40
+ ): OpenClawResolvedAgentRoute {
35
41
  const routing = resolveRoutingApi(api);
36
42
  if (typeof routing.resolveAgentRoute !== 'function') {
37
43
  throw new Error('OpenClaw channel routing resolveAgentRoute API is unavailable');
@@ -40,8 +46,19 @@ export function resolveOpenClawAgentRoute(
40
46
  }
41
47
 
42
48
  export function resolveOpenClawInboundLastRouteSessionKey(params: {
43
- route: unknown;
49
+ route: { lastRoutePolicy?: 'main' | 'session'; mainSessionKey: string };
44
50
  sessionKey: string;
45
51
  }): string {
46
- return resolveInboundLastRouteSessionKey(params as any);
52
+ return resolveInboundLastRouteSessionKey(
53
+ params as {
54
+ route: { lastRoutePolicy: 'main' | 'session'; mainSessionKey: string };
55
+ sessionKey: string;
56
+ },
57
+ );
58
+ }
59
+
60
+ export function buildOpenClawChannelOutboundSessionRoute(
61
+ params: BuildChannelOutboundSessionRouteParams,
62
+ ) {
63
+ return buildChannelOutboundSessionRoute(params);
47
64
  }
@@ -1,4 +1,103 @@
1
+ type RuntimeSurfaceContractKind = 'object' | 'function' | 'functionAnyOf';
2
+
3
+ type RuntimeSurfaceContract = {
4
+ key: string;
5
+ kind: RuntimeSurfaceContractKind;
6
+ path?: readonly string[];
7
+ anyOf?: readonly (readonly string[])[];
8
+ parent?: readonly string[];
9
+ };
10
+
11
+ export const OPENCLAW_RUNTIME_SURFACE_CONTRACTS = [
12
+ { key: 'runtime.config', kind: 'object', path: ['runtime', 'config'] },
13
+ {
14
+ key: 'runtime.config.current|get',
15
+ kind: 'functionAnyOf',
16
+ anyOf: [
17
+ ['runtime', 'config', 'current'],
18
+ ['runtime', 'config', 'get'],
19
+ ],
20
+ parent: ['runtime', 'config'],
21
+ },
22
+ {
23
+ key: 'runtime.config.mutateConfigFile',
24
+ kind: 'function',
25
+ path: ['runtime', 'config', 'mutateConfigFile'],
26
+ parent: ['runtime', 'config'],
27
+ },
28
+ { key: 'runtime.media', kind: 'object', path: ['runtime', 'media'] },
29
+ {
30
+ key: 'runtime.media.loadWebMedia',
31
+ kind: 'function',
32
+ path: ['runtime', 'media', 'loadWebMedia'],
33
+ parent: ['runtime', 'media'],
34
+ },
35
+ { key: 'runtime.channel.inbound', kind: 'object', path: ['runtime', 'channel', 'inbound'] },
36
+ {
37
+ key: 'runtime.channel.inbound.buildContext',
38
+ kind: 'function',
39
+ path: ['runtime', 'channel', 'inbound', 'buildContext'],
40
+ parent: ['runtime', 'channel', 'inbound'],
41
+ },
42
+ {
43
+ key: 'runtime.channel.inbound.run',
44
+ kind: 'function',
45
+ path: ['runtime', 'channel', 'inbound', 'run'],
46
+ parent: ['runtime', 'channel', 'inbound'],
47
+ },
48
+ { key: 'runtime.channel.media', kind: 'object', path: ['runtime', 'channel', 'media'] },
49
+ {
50
+ key: 'runtime.channel.media.readRemoteMediaBuffer',
51
+ kind: 'function',
52
+ path: ['runtime', 'channel', 'media', 'readRemoteMediaBuffer'],
53
+ parent: ['runtime', 'channel', 'media'],
54
+ },
55
+ {
56
+ key: 'runtime.channel.media.saveMediaBuffer',
57
+ kind: 'function',
58
+ path: ['runtime', 'channel', 'media', 'saveMediaBuffer'],
59
+ parent: ['runtime', 'channel', 'media'],
60
+ },
61
+ { key: 'runtime.channel.reply', kind: 'object', path: ['runtime', 'channel', 'reply'] },
62
+ {
63
+ key: 'runtime.channel.reply.resolveEnvelopeFormatOptions',
64
+ kind: 'function',
65
+ path: ['runtime', 'channel', 'reply', 'resolveEnvelopeFormatOptions'],
66
+ parent: ['runtime', 'channel', 'reply'],
67
+ },
68
+ {
69
+ key: 'runtime.channel.reply.formatAgentEnvelope',
70
+ kind: 'function',
71
+ path: ['runtime', 'channel', 'reply', 'formatAgentEnvelope'],
72
+ parent: ['runtime', 'channel', 'reply'],
73
+ },
74
+ {
75
+ key: 'runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher',
76
+ kind: 'function',
77
+ path: ['runtime', 'channel', 'reply', 'dispatchReplyWithBufferedBlockDispatcher'],
78
+ parent: ['runtime', 'channel', 'reply'],
79
+ },
80
+ { key: 'runtime.channel.routing', kind: 'object', path: ['runtime', 'channel', 'routing'] },
81
+ {
82
+ key: 'runtime.channel.routing.resolveAgentRoute',
83
+ kind: 'function',
84
+ path: ['runtime', 'channel', 'routing', 'resolveAgentRoute'],
85
+ parent: ['runtime', 'channel', 'routing'],
86
+ },
87
+ { key: 'runtime.channel.session', kind: 'object', path: ['runtime', 'channel', 'session'] },
88
+ {
89
+ key: 'runtime.channel.session.readSessionUpdatedAt',
90
+ kind: 'function',
91
+ path: ['runtime', 'channel', 'session', 'readSessionUpdatedAt'],
92
+ parent: ['runtime', 'channel', 'session'],
93
+ },
94
+ ] as const satisfies readonly RuntimeSurfaceContract[];
95
+
1
96
  export type OpenClawChannelRuntimeSurfaceDiagnostics = {
97
+ runtime: {
98
+ config: boolean;
99
+ media: boolean;
100
+ };
2
101
  channel: {
3
102
  inbound: boolean;
4
103
  media: boolean;
@@ -6,24 +105,69 @@ export type OpenClawChannelRuntimeSurfaceDiagnostics = {
6
105
  routing: boolean;
7
106
  session: boolean;
8
107
  };
108
+ channelMedia: {
109
+ readRemoteMediaBuffer: boolean;
110
+ saveMediaBuffer: boolean;
111
+ };
112
+ contract: Record<string, boolean>;
9
113
  missing: string[];
10
114
  };
11
115
 
116
+ function getPathValue(root: unknown, path: readonly string[] | undefined): unknown {
117
+ let current: unknown = root;
118
+ for (const key of path ?? []) {
119
+ if (!current || typeof current !== 'object') return undefined;
120
+ current = (current as Record<string, unknown>)[key];
121
+ }
122
+ return current;
123
+ }
124
+
125
+ function hasObject(root: unknown, path: readonly string[] | undefined): boolean {
126
+ const value = getPathValue(root, path);
127
+ return Boolean(value && typeof value === 'object');
128
+ }
129
+
130
+ function hasFunction(root: unknown, path: readonly string[] | undefined): boolean {
131
+ return typeof getPathValue(root, path) === 'function';
132
+ }
133
+
134
+ function evaluateRuntimeSurfaceContract(root: unknown, spec: RuntimeSurfaceContract): boolean {
135
+ if (spec.parent && !hasObject(root, spec.parent)) return false;
136
+ if (spec.kind === 'object') return hasObject(root, spec.path);
137
+ if (spec.kind === 'function') return hasFunction(root, spec.path);
138
+ return Boolean(spec.anyOf?.some((path) => hasFunction(root, path)));
139
+ }
140
+
12
141
  export function buildOpenClawChannelRuntimeSurfaceDiagnostics(
13
142
  api: unknown,
14
143
  ): OpenClawChannelRuntimeSurfaceDiagnostics {
15
- const channelRuntime = (api as any)?.runtime?.channel;
16
- const surfaces = {
17
- inbound: Boolean(channelRuntime?.inbound),
18
- media: Boolean(channelRuntime?.media),
19
- reply: Boolean(channelRuntime?.reply),
20
- routing: Boolean(channelRuntime?.routing),
21
- session: Boolean(channelRuntime?.session),
22
- };
144
+ const contract = Object.fromEntries(
145
+ OPENCLAW_RUNTIME_SURFACE_CONTRACTS.map((spec) => [
146
+ spec.key,
147
+ evaluateRuntimeSurfaceContract(api, spec),
148
+ ]),
149
+ ) as Record<string, boolean>;
150
+ const missing = OPENCLAW_RUNTIME_SURFACE_CONTRACTS.filter((spec) => !contract[spec.key]).map(
151
+ (spec) => spec.key,
152
+ );
153
+
23
154
  return {
24
- channel: surfaces,
25
- missing: Object.entries(surfaces)
26
- .filter(([, present]) => !present)
27
- .map(([name]) => name),
155
+ runtime: {
156
+ config: contract['runtime.config'],
157
+ media: contract['runtime.media'],
158
+ },
159
+ channel: {
160
+ inbound: contract['runtime.channel.inbound'],
161
+ media: contract['runtime.channel.media'],
162
+ reply: contract['runtime.channel.reply'],
163
+ routing: contract['runtime.channel.routing'],
164
+ session: contract['runtime.channel.session'],
165
+ },
166
+ channelMedia: {
167
+ readRemoteMediaBuffer: contract['runtime.channel.media.readRemoteMediaBuffer'],
168
+ saveMediaBuffer: contract['runtime.channel.media.saveMediaBuffer'],
169
+ },
170
+ contract,
171
+ missing,
28
172
  };
29
173
  }
@@ -10,14 +10,21 @@ import {
10
10
  } from 'openclaw/plugin-sdk/json-store';
11
11
  import { readStringParam as sdkReadStringParam } from 'openclaw/plugin-sdk/param-readers';
12
12
  import { createDefaultChannelRuntimeState as sdkCreateDefaultChannelRuntimeState } from 'openclaw/plugin-sdk/status-helpers';
13
+ import type { ChannelToolSend } from 'openclaw/plugin-sdk/tool-send';
13
14
  import { extractToolSend as sdkExtractToolSend } from 'openclaw/plugin-sdk/tool-send';
14
15
 
16
+ export type OpenClawChannelToolSend = ChannelToolSend;
17
+
15
18
  export const readOpenClawBooleanParam = sdkReadBooleanParam;
16
19
  export const readOpenClawStringParam = sdkReadStringParam;
17
20
  export const readOpenClawJsonFileWithFallback = sdkReadJsonFileWithFallback;
18
21
  export const writeOpenClawJsonFileAtomically = sdkWriteJsonFileAtomically;
19
22
  export const createOpenClawDefaultChannelRuntimeState = sdkCreateDefaultChannelRuntimeState;
20
- export const extractOpenClawToolSend = sdkExtractToolSend;
23
+ export const extractOpenClawToolSend = (
24
+ args: Record<string, unknown>,
25
+ fallbackAction?: string,
26
+ ): ChannelToolSend | null =>
27
+ (sdkExtractToolSend(args, fallbackAction) as ChannelToolSend | null) || null;
21
28
  export const openClawJsonResult = sdkJsonResult;
22
29
  export const applyOpenClawAccountNameToChannelSection = sdkApplyAccountNameToChannelSection;
23
30
  export const setOpenClawAccountEnabledInConfigSection = sdkSetAccountEnabledInConfigSection;
@@ -1,15 +1,15 @@
1
1
  import { buildChannelOutboundSessionRoute } from 'openclaw/plugin-sdk/core';
2
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>;
3
+ export type OpenClawChannelOutboundSessionRouteParams = Parameters<
4
+ typeof buildChannelOutboundSessionRoute
5
+ >[0];
6
+
7
+ export type OpenClawChannelOutboundSessionRouteResult = ReturnType<
8
+ typeof buildChannelOutboundSessionRoute
9
+ >;
10
+
11
+ export function buildOpenClawChannelOutboundSessionRoute(
12
+ params: OpenClawChannelOutboundSessionRouteParams,
13
+ ): OpenClawChannelOutboundSessionRouteResult {
14
+ return buildChannelOutboundSessionRoute(params);
15
15
  }