@openclaw/plugin-inspector 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/sdk-mock.js CHANGED
@@ -11,8 +11,11 @@ export const mockSdkSubpathExports = {
11
11
  "emptyPluginConfigSchema",
12
12
  ],
13
13
  core: [
14
+ "buildChannelOutboundSessionRoute",
14
15
  "buildChannelConfigSchema",
15
16
  "buildPluginConfigSchema",
17
+ "createActionGate",
18
+ "createChannelPluginBase",
16
19
  "createChatChannelPlugin",
17
20
  "createDedupeCache",
18
21
  "defineChannelPluginEntry",
@@ -22,12 +25,24 @@ export const mockSdkSubpathExports = {
22
25
  "emptyPluginConfigSchema",
23
26
  "jsonResult",
24
27
  "readNumberParam",
28
+ "readReactionParams",
29
+ "readStringArrayParam",
30
+ "readStringParam",
31
+ ],
32
+ "channel-actions": [
33
+ "createActionGate",
34
+ "jsonResult",
35
+ "readNumberParam",
36
+ "readReactionParams",
37
+ "readStringArrayParam",
25
38
  "readStringParam",
26
39
  ],
27
40
  "channel-core": [
28
41
  "buildChannelConfigSchema",
42
+ "buildChannelOutboundSessionRoute",
29
43
  "buildThreadAwareOutboundSessionRoute",
30
44
  "clearAccountEntryFields",
45
+ "createChannelPluginBase",
31
46
  "createChatChannelPlugin",
32
47
  "defineChannelPluginEntry",
33
48
  "defineSetupPluginEntry",
@@ -718,6 +733,10 @@ function mockSdkSource() {
718
733
  return typeof entry === "function" ? { register: entry } : entry;
719
734
  }
720
735
 
736
+ function normalizeRegistrationMode(api) {
737
+ return api?.registrationMode ?? "full";
738
+ }
739
+
721
740
  function isPlainObject(value) {
722
741
  return value !== null && typeof value === "object" && !Array.isArray(value);
723
742
  }
@@ -754,15 +773,139 @@ export function definePluginEntry(entry) {
754
773
  }
755
774
 
756
775
  export function defineChannelPluginEntry(entry) {
757
- return normalizeEntry(entry);
776
+ if (!isPlainObject(entry) || !entry.plugin) {
777
+ return normalizeEntry(entry);
778
+ }
779
+ const resolved = {
780
+ id: entry.id,
781
+ name: entry.name,
782
+ description: entry.description,
783
+ configSchema: createConfigSchema(entry.configSchema),
784
+ channelPlugin: entry.plugin,
785
+ register(api) {
786
+ const mode = normalizeRegistrationMode(api);
787
+ if (mode === "cli-metadata") {
788
+ entry.registerCliMetadata?.(api);
789
+ return;
790
+ }
791
+ api.registerChannel?.({ plugin: entry.plugin });
792
+ entry.setRuntime?.(api.runtime);
793
+ if (mode === "discovery") {
794
+ entry.registerCliMetadata?.(api);
795
+ return;
796
+ }
797
+ if (mode !== "full") {
798
+ return;
799
+ }
800
+ entry.registerCliMetadata?.(api);
801
+ entry.registerFull?.(api);
802
+ },
803
+ };
804
+ if (entry.setRuntime) {
805
+ resolved.setChannelRuntime = entry.setRuntime;
806
+ }
807
+ return resolved;
758
808
  }
759
809
 
760
810
  export function defineSetupPluginEntry(entry) {
761
- return normalizeEntry(entry);
811
+ return isPlainObject(entry) && entry.plugin ? entry : { plugin: entry };
762
812
  }
763
813
 
764
814
  export function createChatChannelPlugin(entry) {
765
- return normalizeEntry(entry);
815
+ if (!isPlainObject(entry) || !entry.base) {
816
+ return normalizeEntry(entry);
817
+ }
818
+ return {
819
+ ...entry.base,
820
+ conversationBindings: {
821
+ supportsCurrentConversationBinding: true,
822
+ ...(entry.base.conversationBindings ?? {}),
823
+ },
824
+ ...(entry.security ? { security: resolveChannelSecurity(entry.security) } : {}),
825
+ ...(entry.pairing ? { pairing: resolveChannelPairing(entry.pairing) } : {}),
826
+ ...(entry.threading ? { threading: resolveChannelThreading(entry.threading) } : {}),
827
+ ...(entry.outbound ? { outbound: resolveChannelOutbound(entry.outbound) } : {}),
828
+ };
829
+ }
830
+
831
+ export function createChannelPluginBase(params = {}) {
832
+ return {
833
+ id: params.id ?? "fixture-channel",
834
+ meta: { id: params.id ?? "fixture-channel", ...(params.meta ?? {}) },
835
+ ...(params.setupWizard ? { setupWizard: params.setupWizard } : {}),
836
+ ...(params.capabilities ? { capabilities: params.capabilities } : {}),
837
+ ...(params.commands ? { commands: params.commands } : {}),
838
+ ...(params.doctor ? { doctor: params.doctor } : {}),
839
+ ...(params.agentPrompt ? { agentPrompt: params.agentPrompt } : {}),
840
+ ...(params.streaming ? { streaming: params.streaming } : {}),
841
+ ...(params.reload ? { reload: params.reload } : {}),
842
+ ...(params.gatewayMethods ? { gatewayMethods: params.gatewayMethods } : {}),
843
+ ...(params.configSchema ? { configSchema: createConfigSchema(params.configSchema) } : {}),
844
+ ...(params.config ? { config: params.config } : {}),
845
+ ...(params.security ? { security: params.security } : {}),
846
+ ...(params.groups ? { groups: params.groups } : {}),
847
+ setup: params.setup ?? (() => ({})),
848
+ };
849
+ }
850
+
851
+ function resolveChannelSecurity(security) {
852
+ if (!isPlainObject(security) || !security.dm) {
853
+ return security;
854
+ }
855
+ return {
856
+ resolveDmPolicy: ({ account } = {}) => ({
857
+ policy: security.dm.resolvePolicy?.(account ?? {}) ?? security.dm.defaultPolicy ?? "allow",
858
+ allowFrom: security.dm.resolveAllowFrom?.(account ?? {}) ?? [],
859
+ }),
860
+ ...(security.collectWarnings ? { collectWarnings: security.collectWarnings } : {}),
861
+ ...(security.collectAuditFindings ? { collectAuditFindings: security.collectAuditFindings } : {}),
862
+ };
863
+ }
864
+
865
+ function resolveChannelPairing(pairing) {
866
+ if (!isPlainObject(pairing) || !pairing.text) {
867
+ return pairing;
868
+ }
869
+ return {
870
+ idLabel: pairing.text.idLabel,
871
+ normalizeAllowEntry: pairing.text.normalizeAllowEntry,
872
+ notifyApproval: (ctx) => pairing.text.notify?.({ ...ctx, message: pairing.text.message }),
873
+ };
874
+ }
875
+
876
+ function resolveChannelThreading(threading) {
877
+ if (!isPlainObject(threading)) {
878
+ return threading;
879
+ }
880
+ if (threading.resolveReplyToMode) {
881
+ return threading;
882
+ }
883
+ return {
884
+ ...threading,
885
+ resolveReplyToMode: () =>
886
+ threading.topLevelReplyToMode ??
887
+ threading.scopedAccountReplyToMode?.fallback ??
888
+ "thread",
889
+ };
890
+ }
891
+
892
+ function resolveChannelOutbound(outbound) {
893
+ if (!isPlainObject(outbound) || !outbound.attachedResults) {
894
+ return outbound;
895
+ }
896
+ const { base = {}, attachedResults } = outbound;
897
+ return {
898
+ ...base,
899
+ ...(attachedResults.sendText
900
+ ? { sendText: async (ctx) => ({ channel: attachedResults.channel, ...(await attachedResults.sendText(ctx)) }) }
901
+ : {}),
902
+ ...(attachedResults.sendMedia
903
+ ? { sendMedia: async (ctx) => ({ channel: attachedResults.channel, ...(await attachedResults.sendMedia(ctx)) }) }
904
+ : {}),
905
+ ...(attachedResults.sendPoll
906
+ ? { sendPoll: async (ctx) => ({ channel: attachedResults.channel, ...(await attachedResults.sendPoll(ctx)) }) }
907
+ : {}),
908
+ };
766
909
  }
767
910
 
768
911
  export function definePlugin(entry) {
@@ -805,13 +948,63 @@ export function jsonResult(value) {
805
948
  return { content: [{ type: "text", text: JSON.stringify(value) }] };
806
949
  }
807
950
 
808
- export function readNumberParam(value, fallback = 0) {
809
- const parsed = Number(value);
810
- return Number.isFinite(parsed) ? parsed : fallback;
951
+ export function readNumberParam(value, keyOrFallback = 0, options = {}) {
952
+ const raw = isPlainObject(value) ? value[keyOrFallback] : value;
953
+ const parsed = Number(raw);
954
+ if (Number.isFinite(parsed)) {
955
+ return options.integer ? Math.trunc(parsed) : parsed;
956
+ }
957
+ return isPlainObject(value) ? undefined : keyOrFallback;
811
958
  }
812
959
 
813
- export function readStringParam(value, fallback = "") {
814
- return typeof value === "string" ? value : fallback;
960
+ export function readStringParam(value, keyOrFallback = "") {
961
+ if (isPlainObject(value)) {
962
+ const raw = value[keyOrFallback];
963
+ return typeof raw === "string" ? raw : undefined;
964
+ }
965
+ return typeof value === "string" ? value : keyOrFallback;
966
+ }
967
+
968
+ export function readStringArrayParam(value, key) {
969
+ const raw = isPlainObject(value) ? value[key] : value;
970
+ if (Array.isArray(raw)) {
971
+ return raw.map((entry) => String(entry));
972
+ }
973
+ return typeof raw === "string" && raw ? [raw] : [];
974
+ }
975
+
976
+ export function readReactionParams(value = {}) {
977
+ return {
978
+ messageId: value.messageId ?? value.id ?? "",
979
+ reaction: value.reaction ?? value.emoji ?? "",
980
+ };
981
+ }
982
+
983
+ export function createActionGate(actions = {}) {
984
+ return (key, defaultValue = true) => {
985
+ const value = actions?.[key];
986
+ return value === undefined ? defaultValue : value !== false;
987
+ };
988
+ }
989
+
990
+ export function buildChannelOutboundSessionRoute(params = {}) {
991
+ const peer = params.peer ?? { kind: params.chatType ?? "direct", id: params.to ?? "fixture-peer" };
992
+ const baseSessionKey = [
993
+ params.agentId ?? "agent",
994
+ params.channel ?? "channel",
995
+ params.accountId ?? "default",
996
+ peer.kind,
997
+ peer.id,
998
+ ].filter(Boolean).join(":");
999
+ return {
1000
+ sessionKey: baseSessionKey,
1001
+ baseSessionKey,
1002
+ peer,
1003
+ chatType: params.chatType ?? peer.kind ?? "direct",
1004
+ from: params.from ?? "fixture-source",
1005
+ to: params.to ?? peer.id,
1006
+ ...(params.threadId !== undefined ? { threadId: params.threadId } : {}),
1007
+ };
815
1008
  }
816
1009
 
817
1010
  export function createDedupeCache() {
@@ -1129,7 +1322,7 @@ export function createSubsystemLogger() {
1129
1322
  }
1130
1323
 
1131
1324
  export function buildThreadAwareOutboundSessionRoute(route = {}) {
1132
- return route;
1325
+ return route.route ?? route;
1133
1326
  }
1134
1327
 
1135
1328
  export function clearAccountEntryFields(entry = {}) {
@@ -0,0 +1,19 @@
1
+ import { buildContractCapture } from "./contract-capture.js";
2
+ import { buildSyntheticProbePlan } from "./synthetic-probes.js";
3
+
4
+ export function buildSyntheticProbePlanFromReport(report, options = {}) {
5
+ const capture = options.capture ?? buildContractCapture({
6
+ report,
7
+ hookAssertions: options.hookAssertions,
8
+ hookContexts: options.hookContexts,
9
+ hookEvents: options.hookEvents,
10
+ registrationArguments: options.registrationArguments,
11
+ registrationAssertions: options.registrationAssertions,
12
+ });
13
+ return buildSyntheticProbePlan({
14
+ capture,
15
+ hookContexts: options.hookContexts,
16
+ hookEvents: options.hookEvents,
17
+ registrationArguments: options.registrationArguments,
18
+ });
19
+ }
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { mkdtemp, rm } from "node:fs/promises";
2
+ import { rmSync } from "node:fs";
3
+ import { mkdtemp } from "node:fs/promises";
3
4
  import { register } from "node:module";
4
5
  import os from "node:os";
5
6
  import path from "node:path";
@@ -59,17 +60,20 @@ async function captureForSyntheticProbes(entrypoint, options) {
59
60
  const resolvedEntrypoint = path.resolve(process.cwd(), entrypoint);
60
61
  const pluginRoot = path.resolve(process.cwd(), options.pluginRoot ?? path.dirname(resolvedEntrypoint));
61
62
  const workspace = await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-synthetic-mock-sdk-"));
62
- try {
63
- const { loaderPath } = await createMockSdkPackage(workspace, { pluginRoot });
64
- register(pathToFileURL(loaderPath));
65
- return captureEntrypoint(entrypoint, {
66
- ...options,
67
- mockSdk: false,
68
- pluginRoot,
69
- });
70
- } finally {
71
- await rm(workspace, { force: true, recursive: true });
72
- }
63
+ cleanupTempDirOnExit(workspace);
64
+ const { loaderPath } = await createMockSdkPackage(workspace, { pluginRoot });
65
+ register(pathToFileURL(loaderPath));
66
+ return captureEntrypoint(entrypoint, {
67
+ ...options,
68
+ mockSdk: false,
69
+ pluginRoot,
70
+ });
71
+ }
72
+
73
+ function cleanupTempDirOnExit(dir) {
74
+ process.once("exit", () => {
75
+ rmSync(dir, { force: true, recursive: true });
76
+ });
73
77
  }
74
78
 
75
79
  function readFlag(commandArgs, name) {
@@ -214,6 +214,12 @@ export const defaultSyntheticRegistrationProbeInputs = {
214
214
  handler: commandProbeArgs,
215
215
  run: commandProbeArgs,
216
216
  },
217
+ registerChannel: {
218
+ handleMessage: channelReceiveProbeArgs,
219
+ receive: channelReceiveProbeArgs,
220
+ send: channelSendProbeArgs,
221
+ sendMessage: channelSendProbeArgs,
222
+ },
217
223
  registerGatewayMethod: {
218
224
  execute: gatewayProbeArgs,
219
225
  handler: gatewayProbeArgs,
@@ -523,6 +529,9 @@ function syntheticRegistrationEvent(registrar, property, options) {
523
529
  id: beforeToolCall.toolCallId,
524
530
  name: beforeToolCall.toolName,
525
531
  },
532
+ respond(ok, result, error) {
533
+ return { ok, result, ...(error ? { error } : {}) };
534
+ },
526
535
  };
527
536
  }
528
537
 
@@ -576,9 +585,11 @@ function commandProbeArgs(event) {
576
585
  function gatewayProbeArgs(event) {
577
586
  return [
578
587
  {
588
+ ...event,
579
589
  params: event.params,
580
590
  body: event.body,
581
591
  headers: event.headers,
592
+ respond: event.respond,
582
593
  },
583
594
  {
584
595
  source: event.source,
@@ -587,6 +598,47 @@ function gatewayProbeArgs(event) {
587
598
  ];
588
599
  }
589
600
 
601
+ function channelSendProbeArgs(event) {
602
+ return [
603
+ {
604
+ source: event.source,
605
+ channelId: "fixture-channel",
606
+ accountId: "fixture-account",
607
+ to: "fixture-recipient",
608
+ text: "fixture message",
609
+ replyToId: "fixture-reply",
610
+ threadId: "fixture-thread",
611
+ logger: console,
612
+ signal: new AbortController().signal,
613
+ },
614
+ ];
615
+ }
616
+
617
+ function channelReceiveProbeArgs(event) {
618
+ return [
619
+ {
620
+ source: event.source,
621
+ channelId: "fixture-channel",
622
+ accountId: "fixture-account",
623
+ message: {
624
+ id: "message-fixture",
625
+ text: "fixture inbound message",
626
+ sender: { id: "sender-fixture", displayName: "Fixture Sender" },
627
+ },
628
+ route: {
629
+ sessionKey: "fixture-session",
630
+ baseSessionKey: "fixture-base-session",
631
+ peer: { kind: "direct", id: "sender-fixture" },
632
+ chatType: "direct",
633
+ from: "sender-fixture",
634
+ to: "fixture-channel",
635
+ },
636
+ logger: console,
637
+ signal: new AbortController().signal,
638
+ },
639
+ ];
640
+ }
641
+
590
642
  function interactiveProbeArgs(event) {
591
643
  return [
592
644
  {
@@ -604,7 +656,10 @@ function lifecycleProbeArgs(event) {
604
656
  return [
605
657
  {
606
658
  source: event.source,
659
+ config: {},
607
660
  logger: console,
661
+ runtime: { env: {}, logger: console },
662
+ secrets: { get: async () => null, has: async () => false },
608
663
  signal: new AbortController().signal,
609
664
  },
610
665
  ];