moltbot-channel-feishu 0.0.8
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/LICENSE +21 -0
- package/README.md +68 -0
- package/clawdbot.plugin.json +33 -0
- package/moltbot.plugin.json +33 -0
- package/package.json +86 -0
- package/src/api/client.ts +140 -0
- package/src/api/directory.ts +186 -0
- package/src/api/media.ts +335 -0
- package/src/api/messages.ts +290 -0
- package/src/api/reactions.ts +155 -0
- package/src/config/schema.ts +183 -0
- package/src/core/dispatcher.ts +227 -0
- package/src/core/gateway.ts +202 -0
- package/src/core/handler.ts +231 -0
- package/src/core/parser.ts +112 -0
- package/src/core/policy.ts +199 -0
- package/src/core/reply-dispatcher.ts +151 -0
- package/src/core/runtime.ts +27 -0
- package/src/index.ts +108 -0
- package/src/plugin/channel.ts +367 -0
- package/src/plugin/index.ts +28 -0
- package/src/plugin/onboarding.ts +378 -0
- package/src/types/clawdbot.d.ts +377 -0
- package/src/types/events.ts +72 -0
- package/src/types/index.ts +6 -0
- package/src/types/messages.ts +172 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Access policy engine for DM and group messages.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Config, GroupConfig } from "../config/schema.js";
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Types
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
export interface PolicyResult {
|
|
12
|
+
allowed: boolean;
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface AllowlistMatch {
|
|
17
|
+
allowed: boolean;
|
|
18
|
+
matchKey?: string;
|
|
19
|
+
matchSource?: "wildcard" | "id" | "name";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// Allowlist Matching
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if a sender matches an allowlist.
|
|
28
|
+
*/
|
|
29
|
+
export function matchAllowlist(
|
|
30
|
+
allowFrom: (string | number)[],
|
|
31
|
+
senderId: string,
|
|
32
|
+
senderName?: string | null
|
|
33
|
+
): AllowlistMatch {
|
|
34
|
+
const normalized = allowFrom.map((entry) => String(entry).trim().toLowerCase()).filter(Boolean);
|
|
35
|
+
|
|
36
|
+
if (normalized.length === 0) {
|
|
37
|
+
return { allowed: false };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Check for wildcard
|
|
41
|
+
if (normalized.includes("*")) {
|
|
42
|
+
return { allowed: true, matchKey: "*", matchSource: "wildcard" };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check by ID
|
|
46
|
+
const lowerSenderId = senderId.toLowerCase();
|
|
47
|
+
if (normalized.includes(lowerSenderId)) {
|
|
48
|
+
return { allowed: true, matchKey: lowerSenderId, matchSource: "id" };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check by name
|
|
52
|
+
const lowerName = senderName?.toLowerCase();
|
|
53
|
+
if (lowerName && normalized.includes(lowerName)) {
|
|
54
|
+
return { allowed: true, matchKey: lowerName, matchSource: "name" };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { allowed: false };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// DM Policy
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if a DM from a sender is allowed.
|
|
66
|
+
*/
|
|
67
|
+
export function checkDmPolicy(
|
|
68
|
+
config: Config,
|
|
69
|
+
senderId: string,
|
|
70
|
+
senderName?: string | null
|
|
71
|
+
): PolicyResult {
|
|
72
|
+
const policy = config.dmPolicy ?? "pairing";
|
|
73
|
+
|
|
74
|
+
switch (policy) {
|
|
75
|
+
case "open":
|
|
76
|
+
return { allowed: true };
|
|
77
|
+
|
|
78
|
+
case "pairing":
|
|
79
|
+
// Pairing requires verification flow handled elsewhere
|
|
80
|
+
return { allowed: true };
|
|
81
|
+
|
|
82
|
+
case "allowlist": {
|
|
83
|
+
const allowFrom = config.allowFrom ?? [];
|
|
84
|
+
const match = matchAllowlist(allowFrom, senderId, senderName);
|
|
85
|
+
return match.allowed
|
|
86
|
+
? { allowed: true }
|
|
87
|
+
: { allowed: false, reason: "Sender not in DM allowlist" };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
default:
|
|
91
|
+
return { allowed: false, reason: `Unknown DM policy: ${policy}` };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Group Policy
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolve group-specific configuration.
|
|
101
|
+
*/
|
|
102
|
+
export function resolveGroupConfig(
|
|
103
|
+
config: Config,
|
|
104
|
+
groupId: string | null | undefined
|
|
105
|
+
): GroupConfig | undefined {
|
|
106
|
+
if (!groupId) return undefined;
|
|
107
|
+
|
|
108
|
+
const groups = config.groups ?? {};
|
|
109
|
+
const trimmed = groupId.trim();
|
|
110
|
+
|
|
111
|
+
// Direct match
|
|
112
|
+
const direct = groups[trimmed];
|
|
113
|
+
if (direct) return direct;
|
|
114
|
+
|
|
115
|
+
// Case-insensitive match
|
|
116
|
+
const lowered = trimmed.toLowerCase();
|
|
117
|
+
const matchKey = Object.keys(groups).find((key) => key.toLowerCase() === lowered);
|
|
118
|
+
return matchKey ? groups[matchKey] : undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Check if a message in a group from a sender is allowed.
|
|
123
|
+
*/
|
|
124
|
+
export function checkGroupPolicy(
|
|
125
|
+
config: Config,
|
|
126
|
+
groupId: string,
|
|
127
|
+
senderId: string,
|
|
128
|
+
senderName?: string | null
|
|
129
|
+
): PolicyResult {
|
|
130
|
+
const policy = config.groupPolicy ?? "allowlist";
|
|
131
|
+
|
|
132
|
+
switch (policy) {
|
|
133
|
+
case "disabled":
|
|
134
|
+
return { allowed: false, reason: "Group messages disabled" };
|
|
135
|
+
|
|
136
|
+
case "open":
|
|
137
|
+
return { allowed: true };
|
|
138
|
+
|
|
139
|
+
case "allowlist": {
|
|
140
|
+
// Check group-specific allowlist first
|
|
141
|
+
const groupConfig = resolveGroupConfig(config, groupId);
|
|
142
|
+
const groupAllowFrom = groupConfig?.allowFrom ?? config.groupAllowFrom ?? [];
|
|
143
|
+
|
|
144
|
+
if (groupAllowFrom.length === 0) {
|
|
145
|
+
// No allowlist configured, deny by default
|
|
146
|
+
return {
|
|
147
|
+
allowed: false,
|
|
148
|
+
reason: "No group allowlist configured",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const match = matchAllowlist(groupAllowFrom, senderId, senderName);
|
|
153
|
+
return match.allowed
|
|
154
|
+
? { allowed: true }
|
|
155
|
+
: { allowed: false, reason: "Sender not in group allowlist" };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
default:
|
|
159
|
+
return { allowed: false, reason: `Unknown group policy: ${policy}` };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// Mention Policy
|
|
165
|
+
// ============================================================================
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Check if an @mention is required for the given context.
|
|
169
|
+
*/
|
|
170
|
+
export function shouldRequireMention(
|
|
171
|
+
config: Config,
|
|
172
|
+
chatType: "p2p" | "group",
|
|
173
|
+
groupId?: string | null
|
|
174
|
+
): boolean {
|
|
175
|
+
// Never require mention in DMs
|
|
176
|
+
if (chatType === "p2p") {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Check group-specific config
|
|
181
|
+
const groupConfig = resolveGroupConfig(config, groupId);
|
|
182
|
+
if (groupConfig?.requireMention !== undefined) {
|
|
183
|
+
return groupConfig.requireMention;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Fall back to global config
|
|
187
|
+
return config.requireMention ?? true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get tool policy for a group.
|
|
192
|
+
*/
|
|
193
|
+
export function resolveGroupToolPolicy(
|
|
194
|
+
config: Config,
|
|
195
|
+
groupId: string | null | undefined
|
|
196
|
+
): { allow?: string[]; deny?: string[] } | undefined {
|
|
197
|
+
const groupConfig = resolveGroupConfig(config, groupId);
|
|
198
|
+
return groupConfig?.tools;
|
|
199
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reply dispatcher for Feishu.
|
|
3
|
+
* Creates a dispatcher that sends agent replies back to Feishu.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
7
|
+
|
|
8
|
+
import type { ClawdbotConfig, RuntimeEnv, ReplyPayload, PluginRuntime } from "clawdbot/plugin-sdk";
|
|
9
|
+
import {
|
|
10
|
+
createReplyPrefixContext,
|
|
11
|
+
createTypingCallbacks,
|
|
12
|
+
logTypingFailure,
|
|
13
|
+
} from "clawdbot/plugin-sdk";
|
|
14
|
+
|
|
15
|
+
import { getRuntime } from "./runtime.js";
|
|
16
|
+
import { sendTextMessage } from "../api/messages.js";
|
|
17
|
+
import { addReaction, removeReaction, Emoji } from "../api/reactions.js";
|
|
18
|
+
import type { Config } from "../config/schema.js";
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Types
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
export interface CreateReplyDispatcherParams {
|
|
25
|
+
cfg: ClawdbotConfig;
|
|
26
|
+
agentId: string;
|
|
27
|
+
runtime: RuntimeEnv;
|
|
28
|
+
chatId: string;
|
|
29
|
+
replyToMessageId?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface TypingIndicatorState {
|
|
33
|
+
messageId: string;
|
|
34
|
+
emoji: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// Reply Dispatcher
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
export function createReplyDispatcher(params: CreateReplyDispatcherParams) {
|
|
42
|
+
const core = getRuntime() as PluginRuntime;
|
|
43
|
+
const { cfg, agentId, chatId, replyToMessageId } = params;
|
|
44
|
+
const feishuCfg = cfg.channels?.feishu as Config | undefined;
|
|
45
|
+
|
|
46
|
+
const prefixContext = createReplyPrefixContext({
|
|
47
|
+
cfg,
|
|
48
|
+
agentId,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Typing indicator using reactions
|
|
52
|
+
let typingState: TypingIndicatorState | null = null;
|
|
53
|
+
|
|
54
|
+
const typingCallbacks = createTypingCallbacks({
|
|
55
|
+
start: async () => {
|
|
56
|
+
if (!replyToMessageId || !feishuCfg) return;
|
|
57
|
+
try {
|
|
58
|
+
const reactionId = await addReaction(feishuCfg, {
|
|
59
|
+
messageId: replyToMessageId,
|
|
60
|
+
emojiType: Emoji.TYPING,
|
|
61
|
+
});
|
|
62
|
+
typingState = { messageId: replyToMessageId, emoji: reactionId };
|
|
63
|
+
params.runtime.log?.(`Added typing indicator reaction`);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
params.runtime.log?.(`Failed to add typing reaction: ${String(err)}`);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
stop: async () => {
|
|
69
|
+
if (!typingState || !feishuCfg) return;
|
|
70
|
+
try {
|
|
71
|
+
await removeReaction(feishuCfg, {
|
|
72
|
+
messageId: typingState.messageId,
|
|
73
|
+
reactionId: typingState.emoji,
|
|
74
|
+
});
|
|
75
|
+
typingState = null;
|
|
76
|
+
params.runtime.log?.(`Removed typing indicator reaction`);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
params.runtime.log?.(`Failed to remove typing reaction: ${String(err)}`);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
onStartError: (err) => {
|
|
82
|
+
logTypingFailure({
|
|
83
|
+
log: (message) => params.runtime.log?.(message),
|
|
84
|
+
channel: "feishu",
|
|
85
|
+
action: "start",
|
|
86
|
+
error: err,
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
onStopError: (err) => {
|
|
90
|
+
logTypingFailure({
|
|
91
|
+
log: (message) => params.runtime.log?.(message),
|
|
92
|
+
channel: "feishu",
|
|
93
|
+
action: "stop",
|
|
94
|
+
error: err,
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const textChunkLimit = core.channel.text.resolveTextChunkLimit({
|
|
100
|
+
cfg,
|
|
101
|
+
channel: "feishu",
|
|
102
|
+
defaultLimit: 4000,
|
|
103
|
+
});
|
|
104
|
+
const chunkMode = core.channel.text.resolveChunkMode(cfg, "feishu");
|
|
105
|
+
const tableMode = core.channel.text.resolveMarkdownTableMode({
|
|
106
|
+
cfg,
|
|
107
|
+
channel: "feishu",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const { dispatcher, replyOptions, markDispatchIdle } =
|
|
111
|
+
core.channel.reply.createReplyDispatcherWithTyping({
|
|
112
|
+
responsePrefix: prefixContext.responsePrefix,
|
|
113
|
+
responsePrefixContextProvider: prefixContext.responsePrefixContextProvider,
|
|
114
|
+
humanDelay: core.channel.reply.resolveHumanDelayConfig(cfg, agentId),
|
|
115
|
+
onReplyStart: typingCallbacks.onReplyStart,
|
|
116
|
+
deliver: async (payload: ReplyPayload) => {
|
|
117
|
+
params.runtime.log?.(`Deliver called: text=${payload.text?.slice(0, 100)}`);
|
|
118
|
+
const text = payload.text ?? "";
|
|
119
|
+
if (!text.trim()) {
|
|
120
|
+
params.runtime.log?.(`Deliver: empty text, skipping`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const converted = core.channel.text.convertMarkdownTables(text, tableMode);
|
|
125
|
+
const chunks = core.channel.text.chunkTextWithMode(converted, textChunkLimit, chunkMode);
|
|
126
|
+
|
|
127
|
+
params.runtime.log?.(`Deliver: sending ${chunks.length} chunks to ${chatId}`);
|
|
128
|
+
for (const chunk of chunks) {
|
|
129
|
+
await sendTextMessage(feishuCfg!, {
|
|
130
|
+
to: chatId,
|
|
131
|
+
text: chunk,
|
|
132
|
+
replyToMessageId,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
onError: (err, info) => {
|
|
137
|
+
params.runtime.error?.(`${info.kind} reply failed: ${String(err)}`);
|
|
138
|
+
typingCallbacks.onIdle?.();
|
|
139
|
+
},
|
|
140
|
+
onIdle: typingCallbacks.onIdle,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
dispatcher,
|
|
145
|
+
replyOptions: {
|
|
146
|
+
...replyOptions,
|
|
147
|
+
onModelSelected: prefixContext.onModelSelected,
|
|
148
|
+
},
|
|
149
|
+
markDispatchIdle,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime management for Feishu plugin.
|
|
3
|
+
* Separated to avoid circular dependencies between plugin and core modules.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PluginRuntime } from "clawdbot/plugin-sdk";
|
|
7
|
+
|
|
8
|
+
let feishuRuntime: PluginRuntime | null = null;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Initialize the runtime for Feishu operations.
|
|
12
|
+
* Called during plugin registration.
|
|
13
|
+
*/
|
|
14
|
+
export function initializeRuntime(runtime: PluginRuntime): void {
|
|
15
|
+
feishuRuntime = runtime;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Get the current runtime.
|
|
20
|
+
* @throws Error if runtime not initialized
|
|
21
|
+
*/
|
|
22
|
+
export function getRuntime(): PluginRuntime {
|
|
23
|
+
if (!feishuRuntime) {
|
|
24
|
+
throw new Error("Feishu runtime not initialized");
|
|
25
|
+
}
|
|
26
|
+
return feishuRuntime;
|
|
27
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package entry point.
|
|
3
|
+
* Exports all public APIs for the Feishu channel plugin.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Plugin (default export)
|
|
7
|
+
export { default } from "./plugin/index.js";
|
|
8
|
+
|
|
9
|
+
// Channel plugin
|
|
10
|
+
export { feishuChannel } from "./plugin/channel.js";
|
|
11
|
+
|
|
12
|
+
// Runtime management
|
|
13
|
+
export { initializeRuntime, getRuntime } from "./plugin/index.js";
|
|
14
|
+
|
|
15
|
+
// API operations
|
|
16
|
+
export {
|
|
17
|
+
sendTextMessage,
|
|
18
|
+
sendCardMessage,
|
|
19
|
+
editMessage,
|
|
20
|
+
updateCard,
|
|
21
|
+
getMessage,
|
|
22
|
+
normalizeTarget,
|
|
23
|
+
isValidId,
|
|
24
|
+
} from "./api/messages.js";
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
uploadImage,
|
|
28
|
+
uploadFile,
|
|
29
|
+
sendMedia,
|
|
30
|
+
sendImage,
|
|
31
|
+
sendFile,
|
|
32
|
+
detectFileType,
|
|
33
|
+
} from "./api/media.js";
|
|
34
|
+
|
|
35
|
+
export { addReaction, removeReaction, listReactions, Emoji } from "./api/reactions.js";
|
|
36
|
+
|
|
37
|
+
export { listUsers, listGroups } from "./api/directory.js";
|
|
38
|
+
|
|
39
|
+
export { probeConnection, getApiClient, clearClientCache } from "./api/client.js";
|
|
40
|
+
|
|
41
|
+
// Core utilities
|
|
42
|
+
export { startGateway, stopGateway, getBotOpenId } from "./core/gateway.js";
|
|
43
|
+
|
|
44
|
+
export { parseMessageEvent, isBotMentioned, stripMentions } from "./core/parser.js";
|
|
45
|
+
|
|
46
|
+
export {
|
|
47
|
+
checkDmPolicy,
|
|
48
|
+
checkGroupPolicy,
|
|
49
|
+
shouldRequireMention,
|
|
50
|
+
matchAllowlist,
|
|
51
|
+
} from "./core/policy.js";
|
|
52
|
+
|
|
53
|
+
export {
|
|
54
|
+
validateMessage,
|
|
55
|
+
sendReply,
|
|
56
|
+
sendChunkedReply,
|
|
57
|
+
addTypingIndicator,
|
|
58
|
+
removeTypingIndicator,
|
|
59
|
+
} from "./core/dispatcher.js";
|
|
60
|
+
|
|
61
|
+
// Configuration
|
|
62
|
+
export {
|
|
63
|
+
ConfigSchema,
|
|
64
|
+
resolveCredentials,
|
|
65
|
+
type Config,
|
|
66
|
+
type GroupConfig,
|
|
67
|
+
type Credentials,
|
|
68
|
+
} from "./config/schema.js";
|
|
69
|
+
|
|
70
|
+
// Types
|
|
71
|
+
export type {
|
|
72
|
+
// Events
|
|
73
|
+
MessageReceivedEvent,
|
|
74
|
+
BotAddedEvent,
|
|
75
|
+
BotRemovedEvent,
|
|
76
|
+
MessageSender,
|
|
77
|
+
MessagePayload,
|
|
78
|
+
MessageMention,
|
|
79
|
+
EventHandlers,
|
|
80
|
+
// Messages
|
|
81
|
+
SendTextParams,
|
|
82
|
+
SendCardParams,
|
|
83
|
+
EditMessageParams,
|
|
84
|
+
SendResult,
|
|
85
|
+
MessageInfo,
|
|
86
|
+
ParsedMessage,
|
|
87
|
+
ReceiveIdType,
|
|
88
|
+
ChatType,
|
|
89
|
+
// Media
|
|
90
|
+
UploadImageParams,
|
|
91
|
+
UploadFileParams,
|
|
92
|
+
SendMediaParams,
|
|
93
|
+
ImageUploadResult,
|
|
94
|
+
FileUploadResult,
|
|
95
|
+
FileType,
|
|
96
|
+
SendImageParams,
|
|
97
|
+
SendFileParams,
|
|
98
|
+
// Reactions
|
|
99
|
+
Reaction,
|
|
100
|
+
AddReactionParams,
|
|
101
|
+
RemoveReactionParams,
|
|
102
|
+
// Directory
|
|
103
|
+
DirectoryUser,
|
|
104
|
+
DirectoryGroup,
|
|
105
|
+
ListDirectoryParams,
|
|
106
|
+
// Probe
|
|
107
|
+
ProbeResult,
|
|
108
|
+
} from "./types/index.js";
|