@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/CHANGELOG.md +38 -0
- package/README.md +66 -5
- package/docs/plugin-inspector-banner.jpg +0 -0
- package/examples/circleci-plugin-inspector.yml +19 -0
- package/examples/github-actions-code-scanning.yml +31 -0
- package/examples/github-actions-plugin-inspector.yml +1 -1
- package/examples/gitlab-ci-plugin-inspector.yml +11 -0
- package/examples/package-json-plugin-inspector.json +1 -1
- package/package.json +10 -2
- package/src/advanced.js +10 -0
- package/src/api.js +169 -3
- package/src/capture-api.js +41 -3
- package/src/cli.js +48 -15
- package/src/index.js +227 -0
- package/src/init.js +90 -13
- package/src/mock-sdk-capture-runner.js +12 -8
- package/src/report.js +60 -8
- package/src/sdk-mock.js +202 -9
- package/src/synthetic-probe-suite.js +19 -0
- package/src/synthetic-probes-cli.js +16 -12
- package/src/synthetic-probes.js +55 -0
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
|
-
|
|
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
|
|
811
|
+
return isPlainObject(entry) && entry.plugin ? entry : { plugin: entry };
|
|
762
812
|
}
|
|
763
813
|
|
|
764
814
|
export function createChatChannelPlugin(entry) {
|
|
765
|
-
|
|
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,
|
|
809
|
-
const
|
|
810
|
-
|
|
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,
|
|
814
|
-
|
|
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 {
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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) {
|
package/src/synthetic-probes.js
CHANGED
|
@@ -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
|
];
|