@nextclaw/channel-plugin-feishu 0.2.14 → 0.2.16
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/index.ts +2 -2
- package/package.json +1 -2
- package/src/accounts.test.ts +10 -0
- package/src/accounts.ts +12 -12
- package/src/bitable.ts +1 -1
- package/src/bot.test.ts +1 -1
- package/src/bot.ts +2 -2
- package/src/card-action.ts +1 -1
- package/src/channel.test.ts +1 -1
- package/src/channel.ts +3 -3
- package/src/chat.ts +1 -1
- package/src/config-schema.ts +1 -1
- package/src/dedup.ts +1 -1
- package/src/directory.test.ts +1 -1
- package/src/directory.ts +2 -2
- package/src/docx.account-selection.test.ts +1 -1
- package/src/docx.ts +1 -1
- package/src/drive.ts +1 -1
- package/src/dynamic-agent.ts +1 -1
- package/src/media.ts +1 -1
- package/src/monitor.account.ts +1 -1
- package/src/monitor.reaction.test.ts +1 -1
- package/src/monitor.startup.test.ts +1 -1
- package/src/monitor.startup.ts +1 -1
- package/src/monitor.state.ts +1 -1
- package/src/monitor.transport.ts +1 -1
- package/src/monitor.ts +1 -1
- package/src/monitor.webhook.test-helpers.ts +1 -1
- package/src/nextclaw-sdk/account-id.ts +31 -0
- package/src/nextclaw-sdk/compat.ts +8 -0
- package/src/nextclaw-sdk/core-channel.ts +296 -0
- package/src/nextclaw-sdk/core-pairing.ts +224 -0
- package/src/nextclaw-sdk/core.ts +26 -0
- package/src/nextclaw-sdk/dedupe.ts +246 -0
- package/src/nextclaw-sdk/feishu.ts +77 -0
- package/src/nextclaw-sdk/history.ts +127 -0
- package/src/nextclaw-sdk/network-body.ts +245 -0
- package/src/nextclaw-sdk/network-fetch.ts +129 -0
- package/src/nextclaw-sdk/network-webhook.ts +182 -0
- package/src/nextclaw-sdk/network.ts +13 -0
- package/src/nextclaw-sdk/runtime-store.ts +26 -0
- package/src/nextclaw-sdk/secrets-config.ts +109 -0
- package/src/nextclaw-sdk/secrets-core.ts +170 -0
- package/src/nextclaw-sdk/secrets-prompt.ts +305 -0
- package/src/nextclaw-sdk/secrets.ts +18 -0
- package/src/nextclaw-sdk/types.ts +300 -0
- package/src/onboarding.status.test.ts +1 -1
- package/src/onboarding.ts +2 -2
- package/src/outbound.ts +1 -1
- package/src/perm.ts +1 -1
- package/src/policy.ts +2 -2
- package/src/reactions.ts +1 -1
- package/src/reply-dispatcher.ts +1 -1
- package/src/runtime.ts +2 -2
- package/src/secret-input.ts +1 -1
- package/src/send-target.test.ts +1 -1
- package/src/send-target.ts +1 -1
- package/src/send.test.ts +1 -1
- package/src/send.ts +1 -1
- package/src/streaming-card.ts +1 -1
- package/src/tool-account-routing.test.ts +1 -1
- package/src/tool-account.ts +1 -1
- package/src/tool-factory-test-harness.ts +1 -1
- package/src/types.ts +1 -1
- package/src/typing.ts +1 -1
- package/src/wiki.ts +1 -1
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_SECRET_PROVIDER_ALIAS,
|
|
4
|
+
ENV_SECRET_REF_ID_RE,
|
|
5
|
+
formatExecSecretRefIdValidationMessage,
|
|
6
|
+
isValidExecSecretRefId,
|
|
7
|
+
isValidFileSecretRefId,
|
|
8
|
+
} from "./secrets-core.js";
|
|
9
|
+
import type { ClawdbotConfig, SecretInput, SecretRefSource, WizardPrompter } from "./types.js";
|
|
10
|
+
|
|
11
|
+
type SecretPromptResult =
|
|
12
|
+
| { action: "keep" }
|
|
13
|
+
| { action: "use-env" }
|
|
14
|
+
| { action: "set"; value: SecretInput; resolvedValue: string };
|
|
15
|
+
|
|
16
|
+
function resolveDefaultSecretProviderAlias(
|
|
17
|
+
cfg: ClawdbotConfig,
|
|
18
|
+
source: SecretRefSource,
|
|
19
|
+
): string {
|
|
20
|
+
const configured =
|
|
21
|
+
source === "env"
|
|
22
|
+
? cfg.secrets?.defaults?.env
|
|
23
|
+
: source === "file"
|
|
24
|
+
? cfg.secrets?.defaults?.file
|
|
25
|
+
: cfg.secrets?.defaults?.exec;
|
|
26
|
+
if (configured?.trim()) {
|
|
27
|
+
return configured.trim();
|
|
28
|
+
}
|
|
29
|
+
const providers = cfg.secrets?.providers;
|
|
30
|
+
if (providers) {
|
|
31
|
+
for (const [name, provider] of Object.entries(providers)) {
|
|
32
|
+
if (provider?.source === source) {
|
|
33
|
+
return name;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return DEFAULT_SECRET_PROVIDER_ALIAS;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
41
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readJsonPointerValue(payload: unknown, pointer: string): string | null {
|
|
45
|
+
if (pointer === "value") {
|
|
46
|
+
return typeof payload === "string" ? payload : null;
|
|
47
|
+
}
|
|
48
|
+
const segments = pointer
|
|
49
|
+
.slice(1)
|
|
50
|
+
.split("/")
|
|
51
|
+
.map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
52
|
+
let cursor: unknown = payload;
|
|
53
|
+
for (const segment of segments) {
|
|
54
|
+
if (!isRecord(cursor) && !Array.isArray(cursor)) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
cursor = (cursor as Record<string, unknown>)[segment];
|
|
58
|
+
}
|
|
59
|
+
return typeof cursor === "string" ? cursor : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function tryResolveFileProviderValue(cfg: ClawdbotConfig, provider: string, id: string): string | null {
|
|
63
|
+
const providerConfig = cfg.secrets?.providers?.[provider];
|
|
64
|
+
if (!providerConfig || providerConfig.source !== "file" || !providerConfig.path) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const raw = fs.readFileSync(providerConfig.path, "utf-8");
|
|
69
|
+
if (providerConfig.mode === "singleValue") {
|
|
70
|
+
return raw.trim();
|
|
71
|
+
}
|
|
72
|
+
const parsed = JSON.parse(raw) as unknown;
|
|
73
|
+
return readJsonPointerValue(parsed, id);
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function promptSingleChannelToken(params: {
|
|
80
|
+
prompter: Pick<WizardPrompter, "confirm" | "text">;
|
|
81
|
+
accountConfigured: boolean;
|
|
82
|
+
canUseEnv: boolean;
|
|
83
|
+
hasConfigToken: boolean;
|
|
84
|
+
envPrompt: string;
|
|
85
|
+
keepPrompt: string;
|
|
86
|
+
inputPrompt: string;
|
|
87
|
+
}): Promise<{ useEnv: boolean; token: string | null }> {
|
|
88
|
+
const promptToken = async (): Promise<string> =>
|
|
89
|
+
String(
|
|
90
|
+
await params.prompter.text({
|
|
91
|
+
message: params.inputPrompt,
|
|
92
|
+
validate: (value) => (value?.trim() ? undefined : "Required"),
|
|
93
|
+
}),
|
|
94
|
+
).trim();
|
|
95
|
+
|
|
96
|
+
return (async () => {
|
|
97
|
+
if (params.canUseEnv) {
|
|
98
|
+
const keepEnv = await params.prompter.confirm({
|
|
99
|
+
message: params.envPrompt,
|
|
100
|
+
initialValue: true,
|
|
101
|
+
});
|
|
102
|
+
if (keepEnv) {
|
|
103
|
+
return { useEnv: true, token: null };
|
|
104
|
+
}
|
|
105
|
+
return { useEnv: false, token: await promptToken() };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (params.hasConfigToken && params.accountConfigured) {
|
|
109
|
+
const keep = await params.prompter.confirm({
|
|
110
|
+
message: params.keepPrompt,
|
|
111
|
+
initialValue: true,
|
|
112
|
+
});
|
|
113
|
+
if (keep) {
|
|
114
|
+
return { useEnv: false, token: null };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { useEnv: false, token: await promptToken() };
|
|
119
|
+
})();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function resolveSecretInputMode(params: {
|
|
123
|
+
prompter: Pick<WizardPrompter, "select">;
|
|
124
|
+
explicitMode?: "plaintext" | "ref";
|
|
125
|
+
credentialLabel: string;
|
|
126
|
+
}): Promise<"plaintext" | "ref"> {
|
|
127
|
+
if (params.explicitMode) {
|
|
128
|
+
return params.explicitMode;
|
|
129
|
+
}
|
|
130
|
+
return await params.prompter.select({
|
|
131
|
+
message: `How do you want to provide this ${params.credentialLabel}?`,
|
|
132
|
+
initialValue: "plaintext",
|
|
133
|
+
options: [
|
|
134
|
+
{
|
|
135
|
+
value: "plaintext",
|
|
136
|
+
label: `Enter ${params.credentialLabel}`,
|
|
137
|
+
hint: "Stores the credential directly in NextClaw config",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
value: "ref",
|
|
141
|
+
label: "Use secret reference",
|
|
142
|
+
hint: "Stores a reference to env or configured secret providers",
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function promptSecretRefForSetup(params: {
|
|
149
|
+
cfg: ClawdbotConfig;
|
|
150
|
+
prompter: Pick<WizardPrompter, "note" | "select" | "text">;
|
|
151
|
+
preferredEnvVar?: string;
|
|
152
|
+
}): Promise<{ value: SecretInput; resolvedValue: string }> {
|
|
153
|
+
const providers = Object.entries(params.cfg.secrets?.providers ?? {}).filter(
|
|
154
|
+
([, provider]) => provider?.source === "file" || provider?.source === "exec",
|
|
155
|
+
);
|
|
156
|
+
const source = await params.prompter.select({
|
|
157
|
+
message: "Where is this secret stored?",
|
|
158
|
+
initialValue: "env",
|
|
159
|
+
options: [
|
|
160
|
+
{ value: "env", label: "Environment variable", hint: "Reference an env var" },
|
|
161
|
+
...(providers.length > 0
|
|
162
|
+
? [{ value: "provider", label: "Configured provider", hint: "Reference file/exec provider" }]
|
|
163
|
+
: []),
|
|
164
|
+
],
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (source === "env") {
|
|
168
|
+
const envVar = String(
|
|
169
|
+
await params.prompter.text({
|
|
170
|
+
message: "Environment variable name",
|
|
171
|
+
initialValue: params.preferredEnvVar,
|
|
172
|
+
placeholder: params.preferredEnvVar ?? "NEXTCLAW_SECRET",
|
|
173
|
+
validate: (value) => {
|
|
174
|
+
const candidate = value.trim();
|
|
175
|
+
if (!ENV_SECRET_REF_ID_RE.test(candidate)) {
|
|
176
|
+
return 'Use an env var name like "OPENAI_API_KEY".';
|
|
177
|
+
}
|
|
178
|
+
if (!process.env[candidate]?.trim()) {
|
|
179
|
+
return `Environment variable "${candidate}" is missing or empty in this session.`;
|
|
180
|
+
}
|
|
181
|
+
return undefined;
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
184
|
+
).trim();
|
|
185
|
+
return {
|
|
186
|
+
value: {
|
|
187
|
+
source: "env",
|
|
188
|
+
provider: resolveDefaultSecretProviderAlias(params.cfg, "env"),
|
|
189
|
+
id: envVar,
|
|
190
|
+
},
|
|
191
|
+
resolvedValue: process.env[envVar]?.trim() ?? "",
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const provider = await params.prompter.select({
|
|
196
|
+
message: "Select secret provider",
|
|
197
|
+
initialValue: providers[0]?.[0],
|
|
198
|
+
options: providers.map(([name, providerConfig]) => ({
|
|
199
|
+
value: name,
|
|
200
|
+
label: name,
|
|
201
|
+
hint: providerConfig?.source === "exec" ? "Exec provider" : "File provider",
|
|
202
|
+
})),
|
|
203
|
+
});
|
|
204
|
+
const providerConfig = params.cfg.secrets?.providers?.[provider];
|
|
205
|
+
const id = String(
|
|
206
|
+
await params.prompter.text({
|
|
207
|
+
message:
|
|
208
|
+
providerConfig?.source === "file"
|
|
209
|
+
? "Secret id (JSON pointer, or 'value' for singleValue mode)"
|
|
210
|
+
: "Secret id for the exec provider",
|
|
211
|
+
initialValue: providerConfig?.source === "file" ? "/providers/feishu/appSecret" : undefined,
|
|
212
|
+
validate: (value) => {
|
|
213
|
+
const candidate = value.trim();
|
|
214
|
+
if (!candidate) {
|
|
215
|
+
return "Required";
|
|
216
|
+
}
|
|
217
|
+
if (providerConfig?.source === "file") {
|
|
218
|
+
return isValidFileSecretRefId(candidate) ? undefined : "Invalid file secret reference id.";
|
|
219
|
+
}
|
|
220
|
+
return isValidExecSecretRefId(candidate)
|
|
221
|
+
? undefined
|
|
222
|
+
: formatExecSecretRefIdValidationMessage();
|
|
223
|
+
},
|
|
224
|
+
}),
|
|
225
|
+
).trim();
|
|
226
|
+
|
|
227
|
+
const resolvedValue =
|
|
228
|
+
providerConfig?.source === "file" ? (tryResolveFileProviderValue(params.cfg, provider, id) ?? "") : "";
|
|
229
|
+
if (!resolvedValue && providerConfig?.source === "exec") {
|
|
230
|
+
await params.prompter.note(
|
|
231
|
+
"Exec provider reference saved. Connection probe will rely on runtime secret resolution later.",
|
|
232
|
+
"Secret reference saved",
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
value: {
|
|
238
|
+
source: providerConfig?.source === "exec" ? "exec" : "file",
|
|
239
|
+
provider,
|
|
240
|
+
id,
|
|
241
|
+
},
|
|
242
|
+
resolvedValue,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export async function promptSingleChannelSecretInput(params: {
|
|
247
|
+
cfg: ClawdbotConfig;
|
|
248
|
+
prompter: Pick<WizardPrompter, "confirm" | "note" | "select" | "text">;
|
|
249
|
+
providerHint: string;
|
|
250
|
+
credentialLabel: string;
|
|
251
|
+
secretInputMode?: "plaintext" | "ref";
|
|
252
|
+
accountConfigured: boolean;
|
|
253
|
+
canUseEnv: boolean;
|
|
254
|
+
hasConfigToken: boolean;
|
|
255
|
+
envPrompt: string;
|
|
256
|
+
keepPrompt: string;
|
|
257
|
+
inputPrompt: string;
|
|
258
|
+
preferredEnvVar?: string;
|
|
259
|
+
}): Promise<SecretPromptResult> {
|
|
260
|
+
const selectedMode = await resolveSecretInputMode({
|
|
261
|
+
prompter: params.prompter,
|
|
262
|
+
explicitMode: params.secretInputMode,
|
|
263
|
+
credentialLabel: params.credentialLabel,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
if (selectedMode === "plaintext") {
|
|
267
|
+
const plainResult = await promptSingleChannelToken({
|
|
268
|
+
prompter: params.prompter,
|
|
269
|
+
accountConfigured: params.accountConfigured,
|
|
270
|
+
canUseEnv: params.canUseEnv,
|
|
271
|
+
hasConfigToken: params.hasConfigToken,
|
|
272
|
+
envPrompt: params.envPrompt,
|
|
273
|
+
keepPrompt: params.keepPrompt,
|
|
274
|
+
inputPrompt: params.inputPrompt,
|
|
275
|
+
});
|
|
276
|
+
if (plainResult.useEnv) {
|
|
277
|
+
return { action: "use-env" };
|
|
278
|
+
}
|
|
279
|
+
if (plainResult.token) {
|
|
280
|
+
return { action: "set", value: plainResult.token, resolvedValue: plainResult.token };
|
|
281
|
+
}
|
|
282
|
+
return { action: "keep" };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (params.hasConfigToken && params.accountConfigured) {
|
|
286
|
+
const keep = await params.prompter.confirm({
|
|
287
|
+
message: params.keepPrompt,
|
|
288
|
+
initialValue: true,
|
|
289
|
+
});
|
|
290
|
+
if (keep) {
|
|
291
|
+
return { action: "keep" };
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const refResult = await promptSecretRefForSetup({
|
|
296
|
+
cfg: params.cfg,
|
|
297
|
+
prompter: params.prompter,
|
|
298
|
+
preferredEnvVar: params.preferredEnvVar,
|
|
299
|
+
});
|
|
300
|
+
return {
|
|
301
|
+
action: "set",
|
|
302
|
+
value: refResult.value,
|
|
303
|
+
resolvedValue: refResult.resolvedValue,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export {
|
|
2
|
+
buildSecretInputSchema,
|
|
3
|
+
formatExecSecretRefIdValidationMessage,
|
|
4
|
+
hasConfiguredSecretInput,
|
|
5
|
+
isValidExecSecretRefId,
|
|
6
|
+
isValidFileSecretRefId,
|
|
7
|
+
normalizeResolvedSecretInputString,
|
|
8
|
+
normalizeSecretInputString,
|
|
9
|
+
} from "./secrets-core.js";
|
|
10
|
+
export {
|
|
11
|
+
buildSingleChannelSecretPromptState,
|
|
12
|
+
mergeAllowFromEntries,
|
|
13
|
+
setTopLevelChannelAllowFrom,
|
|
14
|
+
setTopLevelChannelDmPolicyWithAllowFrom,
|
|
15
|
+
setTopLevelChannelGroupPolicy,
|
|
16
|
+
splitOnboardingEntries,
|
|
17
|
+
} from "./secrets-config.js";
|
|
18
|
+
export { promptSingleChannelSecretInput } from "./secrets-prompt.js";
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
export type LogFn = (message: string) => void;
|
|
2
|
+
|
|
3
|
+
export type SecretRefSource = "env" | "file" | "exec";
|
|
4
|
+
|
|
5
|
+
export type SecretRef = {
|
|
6
|
+
source: SecretRefSource;
|
|
7
|
+
provider: string;
|
|
8
|
+
id: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type SecretInput = string | SecretRef;
|
|
12
|
+
|
|
13
|
+
export type DmPolicy = "open" | "pairing" | "allowlist";
|
|
14
|
+
export type GroupPolicy = "open" | "allowlist" | "disabled";
|
|
15
|
+
|
|
16
|
+
export type AllowlistMatch<TSource extends string = string> = {
|
|
17
|
+
allowed: boolean;
|
|
18
|
+
matchKey?: string;
|
|
19
|
+
matchSource?: TSource;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type BaseProbeResult<TTarget = string> = {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
target?: TTarget;
|
|
25
|
+
error?: string;
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type ReplyPayload = {
|
|
30
|
+
text?: string;
|
|
31
|
+
mediaUrl?: string;
|
|
32
|
+
mediaUrls?: string[];
|
|
33
|
+
[key: string]: unknown;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type HistoryEntry = {
|
|
37
|
+
sender: string;
|
|
38
|
+
body: string;
|
|
39
|
+
timestamp?: number;
|
|
40
|
+
messageId?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type GroupToolPolicyConfig = Record<string, unknown>;
|
|
44
|
+
|
|
45
|
+
export type ChannelGroupContext = {
|
|
46
|
+
cfg: ClawdbotConfig;
|
|
47
|
+
groupId?: string | null;
|
|
48
|
+
[key: string]: unknown;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type ChannelMeta = {
|
|
52
|
+
id: string;
|
|
53
|
+
label: string;
|
|
54
|
+
selectionLabel?: string;
|
|
55
|
+
docsPath?: string;
|
|
56
|
+
docsLabel?: string;
|
|
57
|
+
blurb?: string;
|
|
58
|
+
aliases?: string[];
|
|
59
|
+
order?: number;
|
|
60
|
+
[key: string]: unknown;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export type ChannelPlugin<ResolvedAccount = unknown> = {
|
|
64
|
+
id: string;
|
|
65
|
+
meta: ChannelMeta;
|
|
66
|
+
config?: Record<string, unknown>;
|
|
67
|
+
onboarding?: ChannelOnboardingAdapter;
|
|
68
|
+
[key: string]: unknown;
|
|
69
|
+
__resolvedAccountType__?: ResolvedAccount;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export type ChannelOutboundAdapter = Record<string, unknown>;
|
|
73
|
+
|
|
74
|
+
export type AnyAgentTool = {
|
|
75
|
+
name?: string;
|
|
76
|
+
description?: string;
|
|
77
|
+
parameters?: Record<string, unknown>;
|
|
78
|
+
execute?: (toolCallId: string, params: unknown) => Promise<unknown> | unknown;
|
|
79
|
+
[key: string]: unknown;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type ResolvedAgentRoute = {
|
|
83
|
+
agentId?: string;
|
|
84
|
+
sessionKey?: string;
|
|
85
|
+
[key: string]: unknown;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export type InboundDebouncer<T> = {
|
|
89
|
+
run: (key: string, value: T, task: (value: T) => Promise<void>) => void;
|
|
90
|
+
clear: () => void;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export type PluginRuntime = {
|
|
94
|
+
version?: string;
|
|
95
|
+
config: {
|
|
96
|
+
loadConfig?: () => OpenClawConfig;
|
|
97
|
+
writeConfigFile: (next: OpenClawConfig) => Promise<void>;
|
|
98
|
+
};
|
|
99
|
+
logging: {
|
|
100
|
+
shouldLogVerbose: () => boolean;
|
|
101
|
+
};
|
|
102
|
+
media: {
|
|
103
|
+
detectMime: (params: { buffer: Buffer }) => Promise<string | undefined>;
|
|
104
|
+
loadWebMedia: (
|
|
105
|
+
url: string,
|
|
106
|
+
options?: Record<string, unknown>,
|
|
107
|
+
) => Promise<Record<string, unknown>>;
|
|
108
|
+
};
|
|
109
|
+
channel: {
|
|
110
|
+
media: {
|
|
111
|
+
fetchRemoteMedia: (params: {
|
|
112
|
+
url: string;
|
|
113
|
+
maxBytes?: number;
|
|
114
|
+
}) => Promise<Record<string, unknown>>;
|
|
115
|
+
saveMediaBuffer: (
|
|
116
|
+
buffer: Buffer,
|
|
117
|
+
contentType?: string,
|
|
118
|
+
direction?: string,
|
|
119
|
+
maxBytes?: number,
|
|
120
|
+
) => Promise<{ path: string; contentType?: string }>;
|
|
121
|
+
};
|
|
122
|
+
text: {
|
|
123
|
+
chunkMarkdownText: (text: string, limit: number) => string[];
|
|
124
|
+
resolveMarkdownTableMode: (params: Record<string, unknown>) => unknown;
|
|
125
|
+
convertMarkdownTables: (text: string, mode?: unknown) => string;
|
|
126
|
+
resolveTextChunkLimit: (
|
|
127
|
+
cfg: OpenClawConfig,
|
|
128
|
+
channel: string,
|
|
129
|
+
accountId?: string,
|
|
130
|
+
options?: Record<string, unknown>,
|
|
131
|
+
) => number;
|
|
132
|
+
resolveChunkMode: (cfg: OpenClawConfig, channel: string) => string;
|
|
133
|
+
chunkTextWithMode: (text: string, limit: number, mode?: unknown) => string[];
|
|
134
|
+
hasControlCommand: (text: string, cfg: OpenClawConfig) => boolean;
|
|
135
|
+
};
|
|
136
|
+
reply: {
|
|
137
|
+
resolveEnvelopeFormatOptions: (cfg: OpenClawConfig) => Record<string, unknown>;
|
|
138
|
+
formatAgentEnvelope: (params: Record<string, unknown>) => string;
|
|
139
|
+
finalizeInboundContext: (params: Record<string, unknown>) => Record<string, unknown>;
|
|
140
|
+
withReplyDispatcher: (params: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
141
|
+
dispatchReplyFromConfig: (params: Record<string, unknown>) => Promise<Record<string, unknown>>;
|
|
142
|
+
createReplyDispatcherWithTyping: (params: Record<string, unknown>) => {
|
|
143
|
+
dispatcher: unknown;
|
|
144
|
+
replyOptions: Record<string, unknown>;
|
|
145
|
+
markDispatchIdle: () => void;
|
|
146
|
+
};
|
|
147
|
+
resolveHumanDelayConfig: (cfg: OpenClawConfig, agentId: string) => unknown;
|
|
148
|
+
dispatchReplyWithBufferedBlockDispatcher?: (
|
|
149
|
+
params: Record<string, unknown>,
|
|
150
|
+
) => Promise<void>;
|
|
151
|
+
};
|
|
152
|
+
routing: {
|
|
153
|
+
resolveAgentRoute: (params: Record<string, unknown>) => ResolvedAgentRoute | null;
|
|
154
|
+
};
|
|
155
|
+
pairing: {
|
|
156
|
+
readAllowFromStore: (params: { channel: string; accountId?: string }) => unknown;
|
|
157
|
+
upsertPairingRequest: (params: Record<string, unknown>) => Promise<{
|
|
158
|
+
code: string;
|
|
159
|
+
created: boolean;
|
|
160
|
+
}>;
|
|
161
|
+
};
|
|
162
|
+
commands: {
|
|
163
|
+
shouldComputeCommandAuthorized: (text: string) => boolean;
|
|
164
|
+
resolveCommandAuthorizedFromAuthorizers: (
|
|
165
|
+
params: Record<string, unknown>,
|
|
166
|
+
) => Promise<boolean> | boolean;
|
|
167
|
+
};
|
|
168
|
+
debounce: {
|
|
169
|
+
resolveInboundDebounceMs: (params: Record<string, unknown>) => number;
|
|
170
|
+
createInboundDebouncer: <T>(params: Record<string, unknown>) => InboundDebouncer<T>;
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
[key: string]: unknown;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
export type RuntimeEnv = {
|
|
177
|
+
log?: (message: string) => void;
|
|
178
|
+
error?: (message: string) => void;
|
|
179
|
+
warn?: (message: string) => void;
|
|
180
|
+
debug?: (message: string) => void;
|
|
181
|
+
info?: (message: string) => void;
|
|
182
|
+
[key: string]: unknown;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
export type OpenClawPluginApi = {
|
|
186
|
+
config: ClawdbotConfig;
|
|
187
|
+
runtime: PluginRuntime;
|
|
188
|
+
logger: {
|
|
189
|
+
info: LogFn;
|
|
190
|
+
warn: LogFn;
|
|
191
|
+
error: LogFn;
|
|
192
|
+
debug?: LogFn;
|
|
193
|
+
};
|
|
194
|
+
registerTool: (
|
|
195
|
+
tool:
|
|
196
|
+
| AnyAgentTool
|
|
197
|
+
| ((ctx: Record<string, unknown>) => AnyAgentTool | AnyAgentTool[] | null | undefined),
|
|
198
|
+
opts?: { name?: string; names?: string[]; optional?: boolean },
|
|
199
|
+
) => void;
|
|
200
|
+
registerChannel: (registration: unknown) => void;
|
|
201
|
+
[key: string]: unknown;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export type WizardSelectOption<T extends string = string> = {
|
|
205
|
+
value: T;
|
|
206
|
+
label: string;
|
|
207
|
+
hint?: string;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
export type WizardPrompter = {
|
|
211
|
+
note: (message: string, title?: string) => Promise<void>;
|
|
212
|
+
text: (params: {
|
|
213
|
+
message: string;
|
|
214
|
+
placeholder?: string;
|
|
215
|
+
initialValue?: string;
|
|
216
|
+
validate?: (value: string) => string | undefined;
|
|
217
|
+
}) => Promise<string>;
|
|
218
|
+
confirm: (params: { message: string; initialValue?: boolean }) => Promise<boolean>;
|
|
219
|
+
select: <T extends string = string>(params: {
|
|
220
|
+
message: string;
|
|
221
|
+
options: WizardSelectOption<T>[];
|
|
222
|
+
initialValue?: T | string;
|
|
223
|
+
}) => Promise<T>;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export type ChannelOnboardingDmPolicy = {
|
|
227
|
+
label: string;
|
|
228
|
+
channel: string;
|
|
229
|
+
policyKey: string;
|
|
230
|
+
allowFromKey: string;
|
|
231
|
+
getCurrent: (cfg: ClawdbotConfig) => DmPolicy;
|
|
232
|
+
setPolicy: (cfg: ClawdbotConfig, policy: DmPolicy) => ClawdbotConfig;
|
|
233
|
+
promptAllowFrom: (params: {
|
|
234
|
+
cfg: ClawdbotConfig;
|
|
235
|
+
prompter: WizardPrompter;
|
|
236
|
+
}) => Promise<ClawdbotConfig>;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
export type ChannelOnboardingAdapter = {
|
|
240
|
+
channel: string;
|
|
241
|
+
getStatus: (params: { cfg: ClawdbotConfig }) => Promise<{
|
|
242
|
+
channel: string;
|
|
243
|
+
configured: boolean;
|
|
244
|
+
statusLines: string[];
|
|
245
|
+
selectionHint?: string;
|
|
246
|
+
quickstartScore?: number;
|
|
247
|
+
}>;
|
|
248
|
+
configure: (params: {
|
|
249
|
+
cfg: ClawdbotConfig;
|
|
250
|
+
prompter: WizardPrompter;
|
|
251
|
+
}) => Promise<{ cfg: ClawdbotConfig; accountId?: string }>;
|
|
252
|
+
dmPolicy?: ChannelOnboardingDmPolicy;
|
|
253
|
+
disable?: (cfg: ClawdbotConfig) => ClawdbotConfig;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
export type OpenClawConfig = {
|
|
257
|
+
channels?: Record<string, Record<string, unknown> | undefined>;
|
|
258
|
+
bindings?: Array<{
|
|
259
|
+
agentId?: string;
|
|
260
|
+
match?: {
|
|
261
|
+
channel?: string;
|
|
262
|
+
peer?: {
|
|
263
|
+
kind?: string;
|
|
264
|
+
id?: string;
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
}>;
|
|
268
|
+
agents?: {
|
|
269
|
+
list?: Array<{
|
|
270
|
+
id: string;
|
|
271
|
+
workspace?: string;
|
|
272
|
+
agentDir?: string;
|
|
273
|
+
[key: string]: unknown;
|
|
274
|
+
}>;
|
|
275
|
+
[key: string]: unknown;
|
|
276
|
+
};
|
|
277
|
+
broadcast?: Record<string, string[]>;
|
|
278
|
+
secrets?: {
|
|
279
|
+
defaults?: {
|
|
280
|
+
env?: string;
|
|
281
|
+
file?: string;
|
|
282
|
+
exec?: string;
|
|
283
|
+
};
|
|
284
|
+
providers?: Record<
|
|
285
|
+
string,
|
|
286
|
+
{
|
|
287
|
+
source?: SecretRefSource;
|
|
288
|
+
path?: string;
|
|
289
|
+
mode?: "singleValue" | "json";
|
|
290
|
+
command?: string;
|
|
291
|
+
args?: string[];
|
|
292
|
+
[key: string]: unknown;
|
|
293
|
+
}
|
|
294
|
+
>;
|
|
295
|
+
[key: string]: unknown;
|
|
296
|
+
};
|
|
297
|
+
[key: string]: unknown;
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
export type ClawdbotConfig = OpenClawConfig;
|
package/src/onboarding.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
DmPolicy,
|
|
6
6
|
SecretInput,
|
|
7
7
|
WizardPrompter,
|
|
8
|
-
} from "
|
|
8
|
+
} from "./nextclaw-sdk/feishu.js";
|
|
9
9
|
import {
|
|
10
10
|
buildSingleChannelSecretPromptState,
|
|
11
11
|
DEFAULT_ACCOUNT_ID,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
setTopLevelChannelDmPolicyWithAllowFrom,
|
|
18
18
|
setTopLevelChannelGroupPolicy,
|
|
19
19
|
splitOnboardingEntries,
|
|
20
|
-
} from "
|
|
20
|
+
} from "./nextclaw-sdk/feishu.js";
|
|
21
21
|
import { resolveFeishuCredentials } from "./accounts.js";
|
|
22
22
|
import { probeFeishu } from "./probe.js";
|
|
23
23
|
import type { FeishuConfig } from "./types.js";
|
package/src/outbound.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import type { ChannelOutboundAdapter } from "
|
|
3
|
+
import type { ChannelOutboundAdapter } from "./nextclaw-sdk/feishu.js";
|
|
4
4
|
import { resolveFeishuAccount } from "./accounts.js";
|
|
5
5
|
import { sendMediaFeishu } from "./media.js";
|
|
6
6
|
import { getFeishuRuntime } from "./runtime.js";
|
package/src/perm.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type * as Lark from "@larksuiteoapi/node-sdk";
|
|
2
|
-
import type { OpenClawPluginApi } from "
|
|
2
|
+
import type { OpenClawPluginApi } from "./nextclaw-sdk/feishu.js";
|
|
3
3
|
import { listEnabledFeishuAccounts } from "./accounts.js";
|
|
4
4
|
import { FeishuPermSchema, type FeishuPermParams } from "./perm-schema.js";
|
|
5
5
|
import { createFeishuToolClient, resolveAnyEnabledFeishuToolsConfig } from "./tool-account.js";
|
package/src/policy.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type {
|
|
|
2
2
|
AllowlistMatch,
|
|
3
3
|
ChannelGroupContext,
|
|
4
4
|
GroupToolPolicyConfig,
|
|
5
|
-
} from "
|
|
6
|
-
import { evaluateSenderGroupAccessForPolicy } from "
|
|
5
|
+
} from "./nextclaw-sdk/feishu.js";
|
|
6
|
+
import { evaluateSenderGroupAccessForPolicy } from "./nextclaw-sdk/feishu.js";
|
|
7
7
|
import { normalizeFeishuTarget } from "./targets.js";
|
|
8
8
|
import type { FeishuConfig, FeishuGroupConfig } from "./types.js";
|
|
9
9
|
|
package/src/reactions.ts
CHANGED
package/src/reply-dispatcher.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
type ClawdbotConfig,
|
|
6
6
|
type ReplyPayload,
|
|
7
7
|
type RuntimeEnv,
|
|
8
|
-
} from "
|
|
8
|
+
} from "./nextclaw-sdk/feishu.js";
|
|
9
9
|
import { resolveFeishuAccount } from "./accounts.js";
|
|
10
10
|
import { createFeishuClient } from "./client.js";
|
|
11
11
|
import { sendMediaFeishu } from "./media.js";
|
package/src/runtime.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createPluginRuntimeStore } from "
|
|
2
|
-
import type { PluginRuntime } from "
|
|
1
|
+
import { createPluginRuntimeStore } from "./nextclaw-sdk/compat.js";
|
|
2
|
+
import type { PluginRuntime } from "./nextclaw-sdk/feishu.js";
|
|
3
3
|
|
|
4
4
|
const { setRuntime: setFeishuRuntime, getRuntime: getFeishuRuntime } =
|
|
5
5
|
createPluginRuntimeStore<PluginRuntime>("Feishu runtime not initialized");
|