@xmoxmo/bncr 0.3.3 → 0.3.5

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 (61) hide show
  1. package/dist/index.js +7 -3
  2. package/index.ts +11 -10
  3. package/openclaw.plugin.json +21 -0
  4. package/package.json +4 -4
  5. package/scripts/check-pack.mjs +112 -22
  6. package/scripts/check-register-drift.mjs +91 -65
  7. package/scripts/selfcheck.mjs +79 -3
  8. package/src/channel.ts +549 -810
  9. package/src/core/accounts.ts +1 -1
  10. package/src/core/connection-capability.ts +2 -2
  11. package/src/core/connection-reachability.ts +112 -1
  12. package/src/core/dead-letter-diagnostics.ts +91 -0
  13. package/src/core/diagnostic-counters.ts +61 -0
  14. package/src/core/diagnostics.ts +9 -5
  15. package/src/core/downlink-health.ts +15 -10
  16. package/src/core/extended-diagnostics.ts +4 -0
  17. package/src/core/file-transfer-payloads.ts +1 -4
  18. package/src/core/logging.ts +98 -0
  19. package/src/core/outbox-entry-builders.ts +15 -2
  20. package/src/core/outbox-file-transfer-bookkeeping.ts +1 -1
  21. package/src/core/outbox-file-transfer-failure.ts +2 -5
  22. package/src/core/outbox-file-transfer-success.ts +1 -4
  23. package/src/core/outbox-text-push-failure.ts +2 -4
  24. package/src/core/outbox-text-push-success.ts +1 -1
  25. package/src/core/persisted-outbox-entry.ts +53 -0
  26. package/src/core/probe.ts +33 -13
  27. package/src/core/register-trace.ts +48 -0
  28. package/src/core/status-meta.ts +77 -0
  29. package/src/core/status.ts +50 -57
  30. package/src/messaging/inbound/commands.ts +42 -94
  31. package/src/messaging/inbound/dispatch.ts +25 -54
  32. package/src/messaging/inbound/last-route.ts +46 -0
  33. package/src/messaging/inbound/native-command.ts +49 -0
  34. package/src/messaging/inbound/native-reply-delivery.ts +43 -0
  35. package/src/messaging/inbound/parse.ts +3 -3
  36. package/src/messaging/inbound/runtime-compat.ts +8 -2
  37. package/src/messaging/outbound/build-send-action.ts +1 -2
  38. package/src/messaging/outbound/diagnostics.ts +221 -2
  39. package/src/messaging/outbound/durable-message-adapter.ts +15 -5
  40. package/src/messaging/outbound/durable-queue-adapter.ts +3 -1
  41. package/src/messaging/outbound/media.ts +2 -1
  42. package/src/messaging/outbound/queue-selectors.ts +19 -6
  43. package/src/messaging/outbound/reasons.ts +2 -0
  44. package/src/messaging/outbound/reply-enqueue.ts +29 -2
  45. package/src/messaging/outbound/reply-target-policy.ts +4 -1
  46. package/src/messaging/outbound/retry-policy.ts +16 -8
  47. package/src/messaging/outbound/send-params.ts +56 -0
  48. package/src/messaging/outbound/session-route.ts +1 -1
  49. package/src/openclaw/reply-runtime.ts +4 -5
  50. package/src/openclaw/routing-runtime.ts +0 -1
  51. package/src/openclaw/runtime-surface.ts +29 -0
  52. package/src/openclaw/sdk-helpers.ts +4 -1
  53. package/src/plugin/gateway-methods.ts +2 -0
  54. package/src/plugin/messaging.ts +2 -9
  55. package/src/plugin/status.ts +15 -5
  56. package/src/runtime/outbound-ack-timeout.ts +73 -0
  57. package/src/runtime/outbound-flags.ts +1 -1
  58. package/src/runtime/outbox-transitions.ts +4 -4
  59. package/src/runtime/register-trace-runtime.ts +102 -0
  60. package/src/runtime/status-snapshots.ts +10 -4
  61. package/src/runtime/status-worker.ts +78 -13
package/dist/index.js CHANGED
@@ -134,7 +134,6 @@ var BNCR_BRIDGE_OWNER = /* @__PURE__ */ Symbol.for("bncr.bridge.owner");
134
134
  var BNCR_GATEWAY_RUNTIME = /* @__PURE__ */ Symbol.for("bncr.gateway.runtime");
135
135
  var MODULE_EPOCH = `${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
136
136
  var runtime = null;
137
- var activeServiceStop = null;
138
137
  var identityIds = /* @__PURE__ */ new WeakMap();
139
138
  var identitySeq = 0;
140
139
  var tryExec = (command, args) => {
@@ -288,7 +287,9 @@ var loadRuntimeSync = () => {
288
287
  return runtime;
289
288
  } catch (error) {
290
289
  const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
291
- throw new Error(`bncr failed to load channel runtime after dependency bootstrap from ${runtimeSourceDir}: ${detail}`);
290
+ throw new Error(
291
+ `bncr failed to load channel runtime after dependency bootstrap from ${runtimeSourceDir}: ${detail}`
292
+ );
292
293
  }
293
294
  };
294
295
  var getIdentityId = (obj, prefix) => {
@@ -375,6 +376,8 @@ var gatewayMethodDispatchers = {
375
376
  "bncr.activity": (bridge, opts) => bridge.handleActivity(opts),
376
377
  "bncr.ack": (bridge, opts) => bridge.handleAck(opts),
377
378
  "bncr.diagnostics": (bridge, opts) => bridge.handleDiagnostics(opts),
379
+ "bncr.deadLetter.inspect": (bridge, opts) => bridge.handleDeadLetterInspect(opts),
380
+ "bncr.deadLetter.prune": (bridge, opts) => bridge.handleDeadLetterPrune(opts),
378
381
  "bncr.file.init": (bridge, opts) => bridge.handleFileInit(opts),
379
382
  "bncr.file.chunk": (bridge, opts) => bridge.handleFileChunk(opts),
380
383
  "bncr.file.complete": (bridge, opts) => bridge.handleFileComplete(opts),
@@ -719,7 +722,6 @@ var plugin = {
719
722
  },
720
723
  stop: serviceStopHandler
721
724
  });
722
- activeServiceStop = serviceStopHandler;
723
725
  gatewayRuntime.serviceRegistered = true;
724
726
  gatewayRuntime.serviceOwnerApiInstanceId = apiInstanceId;
725
727
  meta.service = true;
@@ -747,6 +749,8 @@ var plugin = {
747
749
  ensureGatewayMethodRegistered(api, "bncr.activity", debugLog);
748
750
  ensureGatewayMethodRegistered(api, "bncr.ack", debugLog);
749
751
  ensureGatewayMethodRegistered(api, "bncr.diagnostics", debugLog);
752
+ ensureGatewayMethodRegistered(api, "bncr.deadLetter.inspect", debugLog);
753
+ ensureGatewayMethodRegistered(api, "bncr.deadLetter.prune", debugLog);
750
754
  ensureGatewayMethodRegistered(api, "bncr.file.init", debugLog);
751
755
  ensureGatewayMethodRegistered(api, "bncr.file.chunk", debugLog);
752
756
  ensureGatewayMethodRegistered(api, "bncr.file.complete", debugLog);
package/index.ts CHANGED
@@ -76,6 +76,8 @@ type GatewayMethodName =
76
76
  | 'bncr.activity'
77
77
  | 'bncr.ack'
78
78
  | 'bncr.diagnostics'
79
+ | 'bncr.deadLetter.inspect'
80
+ | 'bncr.deadLetter.prune'
79
81
  | 'bncr.file.init'
80
82
  | 'bncr.file.chunk'
81
83
  | 'bncr.file.complete'
@@ -111,7 +113,6 @@ type BncrGatewayRuntime = {
111
113
  };
112
114
 
113
115
  let runtime: LoadedRuntime | null = null;
114
- let activeServiceStop: (() => Promise<void>) | null = null;
115
116
  const identityIds = new WeakMap<object, string>();
116
117
  let identitySeq = 0;
117
118
 
@@ -297,7 +298,9 @@ const loadRuntimeSync = (): LoadedRuntime => {
297
298
  return runtime;
298
299
  } catch (error) {
299
300
  const detail = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
300
- throw new Error(`bncr failed to load channel runtime after dependency bootstrap from ${runtimeSourceDir}: ${detail}`);
301
+ throw new Error(
302
+ `bncr failed to load channel runtime after dependency bootstrap from ${runtimeSourceDir}: ${detail}`,
303
+ );
301
304
  }
302
305
  };
303
306
 
@@ -365,14 +368,9 @@ const getGatewayRuntime = (): BncrGatewayRuntime => {
365
368
  };
366
369
 
367
370
  const getProcessOwnerApiInstanceId = (gatewayRuntime: BncrGatewayRuntime) =>
368
- gatewayRuntime.serviceOwnerApiInstanceId ||
369
- gatewayRuntime.channelOwnerApiInstanceId ||
370
- undefined;
371
+ gatewayRuntime.serviceOwnerApiInstanceId || gatewayRuntime.channelOwnerApiInstanceId || undefined;
371
372
 
372
- const shouldAdoptProcessOwner = (
373
- apiInstanceId: string,
374
- gatewayRuntime: BncrGatewayRuntime,
375
- ) => {
373
+ const shouldAdoptProcessOwner = (apiInstanceId: string, gatewayRuntime: BncrGatewayRuntime) => {
376
374
  const existingOwnerApiInstanceId = getProcessOwnerApiInstanceId(gatewayRuntime);
377
375
  const hasSingletonOwner =
378
376
  Boolean(gatewayRuntime.serviceRegistered) || Boolean(gatewayRuntime.channelRegistered);
@@ -409,6 +407,8 @@ const gatewayMethodDispatchers: Record<
409
407
  'bncr.activity': (bridge, opts) => bridge.handleActivity(opts),
410
408
  'bncr.ack': (bridge, opts) => bridge.handleAck(opts),
411
409
  'bncr.diagnostics': (bridge, opts) => bridge.handleDiagnostics(opts),
410
+ 'bncr.deadLetter.inspect': (bridge, opts) => bridge.handleDeadLetterInspect(opts),
411
+ 'bncr.deadLetter.prune': (bridge, opts) => bridge.handleDeadLetterPrune(opts),
412
412
  'bncr.file.init': (bridge, opts) => bridge.handleFileInit(opts),
413
413
  'bncr.file.chunk': (bridge, opts) => bridge.handleFileChunk(opts),
414
414
  'bncr.file.complete': (bridge, opts) => bridge.handleFileComplete(opts),
@@ -845,7 +845,6 @@ const plugin = {
845
845
  },
846
846
  stop: serviceStopHandler,
847
847
  });
848
- activeServiceStop = serviceStopHandler;
849
848
  gatewayRuntime.serviceRegistered = true;
850
849
  gatewayRuntime.serviceOwnerApiInstanceId = apiInstanceId;
851
850
  meta.service = true;
@@ -875,6 +874,8 @@ const plugin = {
875
874
  ensureGatewayMethodRegistered(api, 'bncr.activity', debugLog);
876
875
  ensureGatewayMethodRegistered(api, 'bncr.ack', debugLog);
877
876
  ensureGatewayMethodRegistered(api, 'bncr.diagnostics', debugLog);
877
+ ensureGatewayMethodRegistered(api, 'bncr.deadLetter.inspect', debugLog);
878
+ ensureGatewayMethodRegistered(api, 'bncr.deadLetter.prune', debugLog);
878
879
  ensureGatewayMethodRegistered(api, 'bncr.file.init', debugLog);
879
880
  ensureGatewayMethodRegistered(api, 'bncr.file.chunk', debugLog);
880
881
  ensureGatewayMethodRegistered(api, 'bncr.file.complete', debugLog);
@@ -22,6 +22,22 @@
22
22
  "type": "array",
23
23
  "items": { "type": "string" }
24
24
  },
25
+ "debug": {
26
+ "type": "object",
27
+ "additionalProperties": true,
28
+ "properties": {
29
+ "verbose": {
30
+ "type": "boolean",
31
+ "default": false,
32
+ "description": "Enable verbose debug logs for bncr channel runtime."
33
+ }
34
+ }
35
+ },
36
+ "allowTool": {
37
+ "type": "boolean",
38
+ "default": false,
39
+ "description": "Allow tool messages to be forwarded when streaming is enabled. Defaults to false; only explicit true enables forwarding. When enabled, bncr also requests upstream tool summaries/results."
40
+ },
25
41
  "requireMention": { "type": "boolean" },
26
42
  "outboundRequireAck": { "type": "boolean" },
27
43
  "accounts": {
@@ -81,6 +97,11 @@
81
97
  "default": false,
82
98
  "description": "Whether group messages must explicitly mention the bot before bncr handles them. Default false. Current version keeps this as a reserved field and does not enforce it yet."
83
99
  },
100
+ "outboundRequireAck": {
101
+ "type": "boolean",
102
+ "default": true,
103
+ "description": "Whether outbound text waits for bncr.ack before leaving the retry queue. Default true to preserve current ack/dead-letter behavior."
104
+ },
84
105
  "accounts": {
85
106
  "type": "object",
86
107
  "additionalProperties": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmoxmo/bncr",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,9 +39,9 @@
39
39
  "openclaw": ">=2026.5.27"
40
40
  },
41
41
  "devDependencies": {
42
- "@biomejs/biome": "^1.9.4",
43
- "openclaw": ">=2026.5.27",
44
- "esbuild": "^0.28.0"
42
+ "@biomejs/biome": "^2.4.16",
43
+ "esbuild": "^0.28.0",
44
+ "openclaw": ">=2026.5.27"
45
45
  },
46
46
  "openclaw": {
47
47
  "extensions": [
@@ -7,35 +7,115 @@ const __filename = fileURLToPath(import.meta.url);
7
7
  const __dirname = path.dirname(__filename);
8
8
  const root = path.resolve(__dirname, '..');
9
9
 
10
- const requiredPackFiles = [
11
- 'README.md',
12
- 'index.ts',
13
- 'openclaw.plugin.json',
14
- 'package.json',
15
- 'scripts/selfcheck.mjs',
10
+ const requiredRootFiles = ['index.ts', 'openclaw.plugin.json'];
11
+
12
+ const requiredScriptFiles = [
13
+ 'scripts/check-pack.mjs',
16
14
  'scripts/check-register-drift.mjs',
15
+ 'scripts/selfcheck.mjs',
16
+ ];
17
+
18
+ const requiredSourceFiles = [
17
19
  'src/channel.ts',
18
- 'src/plugin/config.ts',
19
- 'src/plugin/message-policy.ts',
20
- 'src/plugin/messaging.ts',
21
- 'src/plugin/gateway-runtime.ts',
22
- 'src/plugin/message-send.ts',
23
- 'src/plugin/outbound.ts',
24
- 'src/plugin/setup.ts',
25
- 'src/plugin/status.ts',
26
- 'src/runtime/outbound-ack-timeout.ts',
27
- 'src/runtime/outbound-flags.ts',
28
- 'src/runtime/outbox-transitions.ts',
20
+ 'src/core/types.ts',
21
+ 'src/core/accounts.ts',
22
+ 'src/core/connection-capability.ts',
23
+ 'src/core/connection-reachability.ts',
24
+ 'src/core/dead-letter-diagnostics.ts',
25
+ 'src/core/diagnostic-counters.ts',
26
+ 'src/core/diagnostics.ts',
27
+ 'src/core/downlink-health.ts',
28
+ 'src/core/extended-diagnostics.ts',
29
+ 'src/core/file-ack.ts',
30
+ 'src/core/file-transfer-payloads.ts',
31
+ 'src/core/lease-state.ts',
32
+ 'src/core/logging.ts',
33
+ 'src/core/outbox-enqueue.ts',
34
+ 'src/core/outbox-entry-builders.ts',
35
+ 'src/core/outbox-file-transfer-bookkeeping.ts',
36
+ 'src/core/outbox-file-transfer-failure.ts',
37
+ 'src/core/outbox-file-transfer-guards.ts',
38
+ 'src/core/outbox-file-transfer-prep.ts',
39
+ 'src/core/outbox-file-transfer-success.ts',
40
+ 'src/core/outbox-push-args.ts',
41
+ 'src/core/outbox-queue.ts',
42
+ 'src/core/outbox-summary.ts',
43
+ 'src/core/outbox-text-push-failure.ts',
44
+ 'src/core/outbox-text-push-guards.ts',
45
+ 'src/core/outbox-text-push-prep.ts',
46
+ 'src/core/outbox-text-push-success.ts',
47
+ 'src/core/persisted-outbox-entry.ts',
48
+ 'src/core/targets.ts',
49
+ 'src/core/status.ts',
50
+ 'src/core/status-meta.ts',
51
+ 'src/core/probe.ts',
52
+ 'src/core/config-schema.ts',
53
+ 'src/core/policy.ts',
54
+ 'src/core/permissions.ts',
55
+ 'src/core/register-trace.ts',
56
+ 'src/messaging/inbound/commands.ts',
57
+ 'src/messaging/inbound/context-facts.ts',
58
+ 'src/messaging/inbound/parse.ts',
59
+ 'src/messaging/inbound/gate.ts',
60
+ 'src/messaging/inbound/dispatch.ts',
61
+ 'src/messaging/inbound/last-route.ts',
62
+ 'src/messaging/inbound/native-command.ts',
63
+ 'src/messaging/inbound/native-reply-delivery.ts',
64
+ 'src/messaging/inbound/reply-config.ts',
65
+ 'src/messaging/inbound/runtime-compat.ts',
66
+ 'src/messaging/inbound/session-label.ts',
67
+ 'src/messaging/outbound/send.ts',
68
+ 'src/messaging/outbound/media.ts',
69
+ 'src/messaging/outbound/actions.ts',
70
+ 'src/messaging/outbound/build-send-action.ts',
71
+ 'src/messaging/outbound/diagnostics.ts',
29
72
  'src/messaging/outbound/durable-message-adapter.ts',
30
73
  'src/messaging/outbound/durable-queue-adapter.ts',
74
+ 'src/messaging/outbound/media-dedupe.ts',
75
+ 'src/messaging/outbound/queue-selectors.ts',
76
+ 'src/messaging/outbound/reasons.ts',
77
+ 'src/messaging/outbound/reply-enqueue.ts',
78
+ 'src/messaging/outbound/reply-target-policy.ts',
79
+ 'src/messaging/outbound/retry-policy.ts',
80
+ 'src/messaging/outbound/send-params.ts',
81
+ 'src/messaging/outbound/session-route.ts',
82
+ 'src/messaging/outbound/target-resolver.ts',
31
83
  'src/openclaw/config-runtime.ts',
32
84
  'src/openclaw/inbound-session-runtime.ts',
33
85
  'src/openclaw/ingress-runtime.ts',
34
86
  'src/openclaw/media-runtime.ts',
35
87
  'src/openclaw/reply-runtime.ts',
36
88
  'src/openclaw/routing-runtime.ts',
89
+ 'src/openclaw/runtime-surface.ts',
37
90
  'src/openclaw/sdk-helpers.ts',
38
91
  'src/openclaw/session-route-runtime.ts',
92
+ 'src/plugin/capabilities.ts',
93
+ 'src/plugin/config.ts',
94
+ 'src/plugin/gateway-methods.ts',
95
+ 'src/plugin/gateway-runtime.ts',
96
+ 'src/plugin/message-policy.ts',
97
+ 'src/plugin/message-send.ts',
98
+ 'src/plugin/messaging.ts',
99
+ 'src/plugin/meta.ts',
100
+ 'src/plugin/outbound.ts',
101
+ 'src/plugin/setup.ts',
102
+ 'src/plugin/status.ts',
103
+ 'src/runtime/log-dedupe.ts',
104
+ 'src/runtime/outbound-ack-timeout.ts',
105
+ 'src/runtime/outbound-flags.ts',
106
+ 'src/runtime/outbox-transitions.ts',
107
+ 'src/runtime/register-trace-runtime.ts',
108
+ 'src/runtime/status-snapshots.ts',
109
+ 'src/runtime/status-worker.ts',
110
+ ];
111
+
112
+ const requiredPackFiles = [
113
+ 'LICENSE',
114
+ 'README.md',
115
+ 'package.json',
116
+ ...requiredRootFiles,
117
+ ...requiredScriptFiles,
118
+ ...requiredSourceFiles,
39
119
  ];
40
120
 
41
121
  const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'));
@@ -48,19 +128,29 @@ const [pack] = JSON.parse(output);
48
128
  const packedFiles = new Set((pack?.files ?? []).map((file) => file.path));
49
129
  const missing = requiredPackFiles.filter((file) => !packedFiles.has(file));
50
130
  const channelSource = fs.readFileSync(path.join(root, 'src/channel.ts'), 'utf8');
51
- const messagePolicySource = fs.readFileSync(path.join(root, 'src/plugin/message-policy.ts'), 'utf8');
131
+ const messagePolicySource = fs.readFileSync(
132
+ path.join(root, 'src/plugin/message-policy.ts'),
133
+ 'utf8',
134
+ );
52
135
  const messageSendSource = fs.readFileSync(path.join(root, 'src/plugin/message-send.ts'), 'utf8');
53
136
  const channelMessageChecks = {
54
137
  registered: channelSource.includes('message: {'),
55
- text: channelSource.includes('createBncrMessageSend') && messageSendSource.includes('channelMessageSendText'),
56
- media: channelSource.includes('createBncrMessageSend') && messageSendSource.includes('channelMessageSendMedia'),
57
- payload: channelSource.includes('createBncrMessageSend') && messageSendSource.includes('channelMessageSendPayload'),
138
+ text:
139
+ channelSource.includes('createBncrMessageSend') &&
140
+ messageSendSource.includes('channelMessageSendText'),
141
+ media:
142
+ channelSource.includes('createBncrMessageSend') &&
143
+ messageSendSource.includes('channelMessageSendMedia'),
144
+ payload:
145
+ channelSource.includes('createBncrMessageSend') &&
146
+ messageSendSource.includes('channelMessageSendPayload'),
58
147
  manualAck:
59
148
  channelSource.includes('BNCR_MESSAGE_RECEIVE_POLICY') &&
60
149
  messagePolicySource.includes("defaultAckPolicy: 'manual'") &&
61
150
  messagePolicySource.includes("supportedAckPolicies: ['manual']"),
62
151
  genericActionsPreserved: channelSource.includes('actions: messageActions'),
63
- noDurableFinal: !channelSource.includes('durableFinal:') && !messagePolicySource.includes('durableFinal:'),
152
+ noDurableFinal:
153
+ !channelSource.includes('durableFinal:') && !messagePolicySource.includes('durableFinal:'),
64
154
  };
65
155
  const channelMessageOk = Object.values(channelMessageChecks).every(Boolean);
66
156
 
@@ -1,39 +1,48 @@
1
1
  import { execFileSync } from 'node:child_process';
2
+ import { fileURLToPath } from 'node:url';
2
3
 
3
4
  const readNumber = (value, fallback) => {
4
5
  const n = Number(value);
5
6
  return Number.isFinite(n) ? n : fallback;
6
7
  };
7
8
 
8
- const args = process.argv.slice(2);
9
- const options = {
9
+ const readText = (value, fallback) => {
10
+ const text = String(value ?? '').trim();
11
+ return text || fallback;
12
+ };
13
+
14
+ const DEFAULT_OPTIONS = {
10
15
  durationSec: 300,
11
16
  intervalSec: 15,
12
17
  accountId: 'Primary',
13
18
  gatewayBin: 'openclaw',
14
19
  };
15
20
 
16
- for (let i = 0; i < args.length; i += 1) {
17
- const arg = args[i];
18
- if (arg === '--duration-sec') options.durationSec = readNumber(args[++i], options.durationSec);
19
- else if (arg === '--interval-sec')
20
- options.intervalSec = readNumber(args[++i], options.intervalSec);
21
- else if (arg === '--account-id') options.accountId = args[++i] || options.accountId;
22
- else if (arg === '--gateway-bin') options.gatewayBin = args[++i] || options.gatewayBin;
23
- else if (arg === '--help' || arg === '-h') {
24
- console.log(
25
- 'Usage: node ./scripts/check-register-drift.mjs [--duration-sec 300] [--interval-sec 15] [--account-id Primary] [--gateway-bin openclaw]\n\nSamples bncr.diagnostics over time and reports whether register counters drift after warmup.',
26
- );
27
- process.exit(0);
21
+ export function parseCheckRegisterDriftOptions(args, defaults = DEFAULT_OPTIONS) {
22
+ const options = { ...defaults };
23
+
24
+ for (let i = 0; i < args.length; i += 1) {
25
+ const arg = args[i];
26
+ if (arg === '--duration-sec') options.durationSec = readNumber(args[++i], options.durationSec);
27
+ else if (arg === '--interval-sec')
28
+ options.intervalSec = readNumber(args[++i], options.intervalSec);
29
+ else if (arg === '--account-id') options.accountId = readText(args[++i], options.accountId);
30
+ else if (arg === '--gateway-bin') options.gatewayBin = readText(args[++i], options.gatewayBin);
31
+ else if (arg === '--help' || arg === '-h') options.help = true;
28
32
  }
33
+
34
+ return options;
29
35
  }
30
36
 
31
- if (options.durationSec <= 0) throw new Error('durationSec must be > 0');
32
- if (options.intervalSec <= 0) throw new Error('intervalSec must be > 0');
37
+ const printHelp = () => {
38
+ console.log(
39
+ 'Usage: node ./scripts/check-register-drift.mjs [--duration-sec 300] [--interval-sec 15] [--account-id Primary] [--gateway-bin openclaw]\n\nSamples bncr.diagnostics over time and reports whether register counters drift after warmup.',
40
+ );
41
+ };
33
42
 
34
43
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
35
44
 
36
- const fetchDiagnostics = () => {
45
+ const fetchDiagnostics = (options) => {
37
46
  const raw = execFileSync(
38
47
  options.gatewayBin,
39
48
  [
@@ -63,55 +72,72 @@ const fetchDiagnostics = () => {
63
72
  };
64
73
  };
65
74
 
66
- const startedAt = Date.now();
67
- const samples = [];
68
- const deadline = startedAt + options.durationSec * 1000;
75
+ export async function runCheckRegisterDrift(options) {
76
+ if (options.durationSec <= 0) throw new Error('durationSec must be > 0');
77
+ if (options.intervalSec <= 0) throw new Error('intervalSec must be > 0');
78
+
79
+ const startedAt = Date.now();
80
+ const samples = [];
81
+ const deadline = startedAt + options.durationSec * 1000;
82
+
83
+ while (true) {
84
+ const sample = fetchDiagnostics(options);
85
+ samples.push(sample);
86
+ const nextAt = Date.now() + options.intervalSec * 1000;
87
+ if (nextAt > deadline) break;
88
+ await sleep(Math.max(0, nextAt - Date.now()));
89
+ }
69
90
 
70
- while (true) {
71
- const sample = fetchDiagnostics();
72
- samples.push(sample);
73
- const nextAt = Date.now() + options.intervalSec * 1000;
74
- if (nextAt > deadline) break;
75
- await sleep(Math.max(0, nextAt - Date.now()));
91
+ const first = samples[0] || {};
92
+ const last = samples[samples.length - 1] || {};
93
+ const deltaRegisterCount = (last.registerCount ?? 0) - (first.registerCount ?? 0);
94
+ const deltaApiGeneration = (last.apiGeneration ?? 0) - (first.apiGeneration ?? 0);
95
+ const deltaPostWarmupRegisterCount =
96
+ (last.postWarmupRegisterCount ?? 0) - (first.postWarmupRegisterCount ?? 0);
97
+ const historicalWarmupExternalDrift = Boolean(first.unexpectedRegisterAfterWarmup);
98
+ const newWarmupExternalDriftDuringWindow = deltaPostWarmupRegisterCount > 0;
99
+ const newDriftDuringWindow =
100
+ deltaRegisterCount > 0 || deltaApiGeneration > 0 || newWarmupExternalDriftDuringWindow;
101
+ const driftDetected = historicalWarmupExternalDrift || newDriftDuringWindow;
102
+
103
+ return {
104
+ ok: true,
105
+ accountId: options.accountId,
106
+ durationSec: options.durationSec,
107
+ intervalSec: options.intervalSec,
108
+ startedAt,
109
+ endedAt: Date.now(),
110
+ sampleCount: samples.length,
111
+ first,
112
+ last,
113
+ delta: {
114
+ registerCount: deltaRegisterCount,
115
+ apiGeneration: deltaApiGeneration,
116
+ postWarmupRegisterCount: deltaPostWarmupRegisterCount,
117
+ },
118
+ historicalWarmupExternalDrift,
119
+ newWarmupExternalDriftDuringWindow,
120
+ newDriftDuringWindow,
121
+ driftDetected,
122
+ conclusion: newDriftDuringWindow
123
+ ? 'new register drift was observed during this sampling window'
124
+ : historicalWarmupExternalDrift
125
+ ? 'no new drift during this window, but warmup-external drift had already happened before sampling began'
126
+ : 'register counters stayed stable during this window and no warmup-external drift was flagged',
127
+ samples,
128
+ };
76
129
  }
77
130
 
78
- const first = samples[0] || {};
79
- const last = samples[samples.length - 1] || {};
80
- const deltaRegisterCount = (last.registerCount ?? 0) - (first.registerCount ?? 0);
81
- const deltaApiGeneration = (last.apiGeneration ?? 0) - (first.apiGeneration ?? 0);
82
- const deltaPostWarmupRegisterCount =
83
- (last.postWarmupRegisterCount ?? 0) - (first.postWarmupRegisterCount ?? 0);
84
- const historicalWarmupExternalDrift = Boolean(first.unexpectedRegisterAfterWarmup);
85
- const newWarmupExternalDriftDuringWindow = deltaPostWarmupRegisterCount > 0;
86
- const newDriftDuringWindow =
87
- deltaRegisterCount > 0 || deltaApiGeneration > 0 || newWarmupExternalDriftDuringWindow;
88
- const driftDetected = historicalWarmupExternalDrift || newDriftDuringWindow;
89
-
90
- const result = {
91
- ok: true,
92
- accountId: options.accountId,
93
- durationSec: options.durationSec,
94
- intervalSec: options.intervalSec,
95
- startedAt,
96
- endedAt: Date.now(),
97
- sampleCount: samples.length,
98
- first,
99
- last,
100
- delta: {
101
- registerCount: deltaRegisterCount,
102
- apiGeneration: deltaApiGeneration,
103
- postWarmupRegisterCount: deltaPostWarmupRegisterCount,
104
- },
105
- historicalWarmupExternalDrift,
106
- newWarmupExternalDriftDuringWindow,
107
- newDriftDuringWindow,
108
- driftDetected,
109
- conclusion: newDriftDuringWindow
110
- ? 'new register drift was observed during this sampling window'
111
- : historicalWarmupExternalDrift
112
- ? 'no new drift during this window, but warmup-external drift had already happened before sampling began'
113
- : 'register counters stayed stable during this window and no warmup-external drift was flagged',
114
- samples,
115
- };
131
+ async function main() {
132
+ const options = parseCheckRegisterDriftOptions(process.argv.slice(2));
133
+ if (options.help) {
134
+ printHelp();
135
+ return;
136
+ }
137
+
138
+ console.log(JSON.stringify(await runCheckRegisterDrift(options), null, 2));
139
+ }
116
140
 
117
- console.log(JSON.stringify(result, null, 2));
141
+ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
142
+ await main();
143
+ }
@@ -8,36 +8,104 @@ const __dirname = path.dirname(__filename);
8
8
  const root = path.resolve(__dirname, '..');
9
9
  const require = createRequire(import.meta.url);
10
10
 
11
- const requiredFiles = [
12
- 'index.ts',
13
- 'openclaw.plugin.json',
11
+ const requiredRootFiles = ['index.ts', 'openclaw.plugin.json'];
12
+
13
+ const requiredSourceFiles = [
14
14
  'src/channel.ts',
15
15
  'src/core/types.ts',
16
16
  'src/core/accounts.ts',
17
+ 'src/core/connection-capability.ts',
18
+ 'src/core/connection-reachability.ts',
19
+ 'src/core/dead-letter-diagnostics.ts',
20
+ 'src/core/diagnostic-counters.ts',
21
+ 'src/core/diagnostics.ts',
22
+ 'src/core/downlink-health.ts',
23
+ 'src/core/extended-diagnostics.ts',
24
+ 'src/core/file-ack.ts',
25
+ 'src/core/file-transfer-payloads.ts',
26
+ 'src/core/lease-state.ts',
27
+ 'src/core/logging.ts',
28
+ 'src/core/outbox-enqueue.ts',
29
+ 'src/core/outbox-entry-builders.ts',
30
+ 'src/core/outbox-file-transfer-bookkeeping.ts',
31
+ 'src/core/outbox-file-transfer-failure.ts',
32
+ 'src/core/outbox-file-transfer-guards.ts',
33
+ 'src/core/outbox-file-transfer-prep.ts',
34
+ 'src/core/outbox-file-transfer-success.ts',
35
+ 'src/core/outbox-push-args.ts',
36
+ 'src/core/outbox-queue.ts',
37
+ 'src/core/outbox-summary.ts',
38
+ 'src/core/outbox-text-push-failure.ts',
39
+ 'src/core/outbox-text-push-guards.ts',
40
+ 'src/core/outbox-text-push-prep.ts',
41
+ 'src/core/outbox-text-push-success.ts',
42
+ 'src/core/persisted-outbox-entry.ts',
17
43
  'src/core/targets.ts',
18
44
  'src/core/status.ts',
45
+ 'src/core/status-meta.ts',
19
46
  'src/core/probe.ts',
20
47
  'src/core/config-schema.ts',
21
48
  'src/core/policy.ts',
22
49
  'src/core/permissions.ts',
50
+ 'src/core/register-trace.ts',
51
+ 'src/messaging/inbound/commands.ts',
52
+ 'src/messaging/inbound/context-facts.ts',
23
53
  'src/messaging/inbound/parse.ts',
24
54
  'src/messaging/inbound/gate.ts',
25
55
  'src/messaging/inbound/dispatch.ts',
56
+ 'src/messaging/inbound/last-route.ts',
57
+ 'src/messaging/inbound/native-command.ts',
58
+ 'src/messaging/inbound/native-reply-delivery.ts',
59
+ 'src/messaging/inbound/reply-config.ts',
60
+ 'src/messaging/inbound/runtime-compat.ts',
61
+ 'src/messaging/inbound/session-label.ts',
26
62
  'src/messaging/outbound/send.ts',
27
63
  'src/messaging/outbound/media.ts',
28
64
  'src/messaging/outbound/actions.ts',
65
+ 'src/messaging/outbound/build-send-action.ts',
66
+ 'src/messaging/outbound/diagnostics.ts',
29
67
  'src/messaging/outbound/durable-message-adapter.ts',
30
68
  'src/messaging/outbound/durable-queue-adapter.ts',
69
+ 'src/messaging/outbound/media-dedupe.ts',
70
+ 'src/messaging/outbound/queue-selectors.ts',
71
+ 'src/messaging/outbound/reasons.ts',
72
+ 'src/messaging/outbound/reply-enqueue.ts',
73
+ 'src/messaging/outbound/reply-target-policy.ts',
74
+ 'src/messaging/outbound/retry-policy.ts',
75
+ 'src/messaging/outbound/send-params.ts',
76
+ 'src/messaging/outbound/session-route.ts',
77
+ 'src/messaging/outbound/target-resolver.ts',
31
78
  'src/openclaw/config-runtime.ts',
32
79
  'src/openclaw/inbound-session-runtime.ts',
33
80
  'src/openclaw/ingress-runtime.ts',
34
81
  'src/openclaw/media-runtime.ts',
35
82
  'src/openclaw/reply-runtime.ts',
36
83
  'src/openclaw/routing-runtime.ts',
84
+ 'src/openclaw/runtime-surface.ts',
37
85
  'src/openclaw/sdk-helpers.ts',
38
86
  'src/openclaw/session-route-runtime.ts',
87
+ 'src/plugin/capabilities.ts',
88
+ 'src/plugin/config.ts',
89
+ 'src/plugin/gateway-methods.ts',
90
+ 'src/plugin/gateway-runtime.ts',
91
+ 'src/plugin/message-policy.ts',
92
+ 'src/plugin/message-send.ts',
93
+ 'src/plugin/messaging.ts',
94
+ 'src/plugin/meta.ts',
95
+ 'src/plugin/outbound.ts',
96
+ 'src/plugin/setup.ts',
97
+ 'src/plugin/status.ts',
98
+ 'src/runtime/log-dedupe.ts',
99
+ 'src/runtime/outbound-ack-timeout.ts',
100
+ 'src/runtime/outbound-flags.ts',
101
+ 'src/runtime/outbox-transitions.ts',
102
+ 'src/runtime/register-trace-runtime.ts',
103
+ 'src/runtime/status-snapshots.ts',
104
+ 'src/runtime/status-worker.ts',
39
105
  ];
40
106
 
107
+ const requiredFiles = [...requiredRootFiles, ...requiredSourceFiles];
108
+
41
109
  const readPackageVersion = () => {
42
110
  const pkgPath = path.join(root, 'package.json');
43
111
  const raw = fs.readFileSync(pkgPath, 'utf8');
@@ -46,12 +114,20 @@ const readPackageVersion = () => {
46
114
  };
47
115
 
48
116
  const requiredOpenClawSdkSubpaths = [
117
+ 'openclaw/plugin-sdk',
118
+ 'openclaw/plugin-sdk/boolean-param',
49
119
  'openclaw/plugin-sdk/channel-outbound',
120
+ 'openclaw/plugin-sdk/channel-ingress-runtime',
50
121
  'openclaw/plugin-sdk/channel-message',
51
122
  'openclaw/plugin-sdk/routing',
52
123
  'openclaw/plugin-sdk/conversation-runtime',
53
124
  'openclaw/plugin-sdk/session-store-runtime',
54
125
  'openclaw/plugin-sdk/core',
126
+ 'openclaw/plugin-sdk/json-store',
127
+ 'openclaw/plugin-sdk/param-readers',
128
+ 'openclaw/plugin-sdk/security-runtime',
129
+ 'openclaw/plugin-sdk/status-helpers',
130
+ 'openclaw/plugin-sdk/tool-send',
55
131
  ];
56
132
 
57
133
  const resolveOpenClawSdkSubpaths = () => {