@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,296 @@
|
|
|
1
|
+
import type { GroupPolicy, OpenClawConfig } from "./types.js";
|
|
2
|
+
|
|
3
|
+
export function emptyPluginConfigSchema(): {
|
|
4
|
+
type: "object";
|
|
5
|
+
additionalProperties: false;
|
|
6
|
+
properties: Record<string, unknown>;
|
|
7
|
+
} {
|
|
8
|
+
return {
|
|
9
|
+
type: "object",
|
|
10
|
+
additionalProperties: false,
|
|
11
|
+
properties: {},
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const warnedMissingProviderGroupPolicy = new Set<string>();
|
|
16
|
+
|
|
17
|
+
export function resolveDefaultGroupPolicy(cfg: {
|
|
18
|
+
channels?: {
|
|
19
|
+
defaults?: {
|
|
20
|
+
groupPolicy?: GroupPolicy;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
}): GroupPolicy | undefined {
|
|
24
|
+
return cfg.channels?.defaults?.groupPolicy;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveOpenProviderRuntimeGroupPolicy(params: {
|
|
28
|
+
providerConfigPresent: boolean;
|
|
29
|
+
groupPolicy?: GroupPolicy;
|
|
30
|
+
defaultGroupPolicy?: GroupPolicy;
|
|
31
|
+
}): {
|
|
32
|
+
groupPolicy: GroupPolicy;
|
|
33
|
+
providerMissingFallbackApplied: boolean;
|
|
34
|
+
} {
|
|
35
|
+
const groupPolicy = params.providerConfigPresent
|
|
36
|
+
? (params.groupPolicy ?? params.defaultGroupPolicy ?? "open")
|
|
37
|
+
: (params.groupPolicy ?? "allowlist");
|
|
38
|
+
return {
|
|
39
|
+
groupPolicy,
|
|
40
|
+
providerMissingFallbackApplied: !params.providerConfigPresent && params.groupPolicy === undefined,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function warnMissingProviderGroupPolicyFallbackOnce(params: {
|
|
45
|
+
providerMissingFallbackApplied: boolean;
|
|
46
|
+
providerKey: string;
|
|
47
|
+
accountId?: string;
|
|
48
|
+
blockedLabel?: string;
|
|
49
|
+
log: (message: string) => void;
|
|
50
|
+
}): boolean {
|
|
51
|
+
if (!params.providerMissingFallbackApplied) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
const key = `${params.providerKey}:${params.accountId ?? "*"}`;
|
|
55
|
+
if (warnedMissingProviderGroupPolicy.has(key)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
warnedMissingProviderGroupPolicy.add(key);
|
|
59
|
+
const blockedLabel = params.blockedLabel?.trim() || "group messages";
|
|
60
|
+
params.log(
|
|
61
|
+
`${params.providerKey}: channels.${params.providerKey} is missing; defaulting groupPolicy to "allowlist" (${blockedLabel} blocked until explicitly configured).`,
|
|
62
|
+
);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function evaluateSenderGroupAccessForPolicy(params: {
|
|
67
|
+
groupPolicy: GroupPolicy;
|
|
68
|
+
providerMissingFallbackApplied?: boolean;
|
|
69
|
+
groupAllowFrom: string[];
|
|
70
|
+
senderId: string;
|
|
71
|
+
isSenderAllowed: (senderId: string, allowFrom: string[]) => boolean;
|
|
72
|
+
}): {
|
|
73
|
+
allowed: boolean;
|
|
74
|
+
groupPolicy: GroupPolicy;
|
|
75
|
+
providerMissingFallbackApplied: boolean;
|
|
76
|
+
reason: "allowed" | "disabled" | "empty_allowlist" | "sender_not_allowlisted";
|
|
77
|
+
} {
|
|
78
|
+
if (params.groupPolicy === "disabled") {
|
|
79
|
+
return {
|
|
80
|
+
allowed: false,
|
|
81
|
+
groupPolicy: params.groupPolicy,
|
|
82
|
+
providerMissingFallbackApplied: Boolean(params.providerMissingFallbackApplied),
|
|
83
|
+
reason: "disabled",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (params.groupPolicy === "allowlist") {
|
|
87
|
+
if (params.groupAllowFrom.length === 0) {
|
|
88
|
+
return {
|
|
89
|
+
allowed: false,
|
|
90
|
+
groupPolicy: params.groupPolicy,
|
|
91
|
+
providerMissingFallbackApplied: Boolean(params.providerMissingFallbackApplied),
|
|
92
|
+
reason: "empty_allowlist",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (!params.isSenderAllowed(params.senderId, params.groupAllowFrom)) {
|
|
96
|
+
return {
|
|
97
|
+
allowed: false,
|
|
98
|
+
groupPolicy: params.groupPolicy,
|
|
99
|
+
providerMissingFallbackApplied: Boolean(params.providerMissingFallbackApplied),
|
|
100
|
+
reason: "sender_not_allowlisted",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
allowed: true,
|
|
106
|
+
groupPolicy: params.groupPolicy,
|
|
107
|
+
providerMissingFallbackApplied: Boolean(params.providerMissingFallbackApplied),
|
|
108
|
+
reason: "allowed",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createDefaultChannelRuntimeState<T extends Record<string, unknown>>(
|
|
113
|
+
accountId: string,
|
|
114
|
+
extra?: T,
|
|
115
|
+
): {
|
|
116
|
+
accountId: string;
|
|
117
|
+
running: false;
|
|
118
|
+
lastStartAt: null;
|
|
119
|
+
lastStopAt: null;
|
|
120
|
+
lastError: null;
|
|
121
|
+
} & T {
|
|
122
|
+
return {
|
|
123
|
+
accountId,
|
|
124
|
+
running: false,
|
|
125
|
+
lastStartAt: null,
|
|
126
|
+
lastStopAt: null,
|
|
127
|
+
lastError: null,
|
|
128
|
+
...(extra ?? ({} as T)),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function buildProbeChannelStatusSummary<TExtra extends Record<string, unknown>>(
|
|
133
|
+
snapshot: {
|
|
134
|
+
configured?: boolean | null;
|
|
135
|
+
running?: boolean | null;
|
|
136
|
+
lastStartAt?: number | null;
|
|
137
|
+
lastStopAt?: number | null;
|
|
138
|
+
lastError?: string | null;
|
|
139
|
+
probe?: unknown;
|
|
140
|
+
lastProbeAt?: number | null;
|
|
141
|
+
},
|
|
142
|
+
extra?: TExtra,
|
|
143
|
+
) {
|
|
144
|
+
return {
|
|
145
|
+
configured: snapshot.configured ?? false,
|
|
146
|
+
running: snapshot.running ?? false,
|
|
147
|
+
lastStartAt: snapshot.lastStartAt ?? null,
|
|
148
|
+
lastStopAt: snapshot.lastStopAt ?? null,
|
|
149
|
+
lastError: snapshot.lastError ?? null,
|
|
150
|
+
...(extra ?? ({} as TExtra)),
|
|
151
|
+
probe: snapshot.probe,
|
|
152
|
+
lastProbeAt: snapshot.lastProbeAt ?? null,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function buildRuntimeAccountStatusSnapshot(params: {
|
|
157
|
+
runtime?: {
|
|
158
|
+
running?: boolean | null;
|
|
159
|
+
lastStartAt?: number | null;
|
|
160
|
+
lastStopAt?: number | null;
|
|
161
|
+
lastError?: string | null;
|
|
162
|
+
} | null;
|
|
163
|
+
probe?: unknown;
|
|
164
|
+
}) {
|
|
165
|
+
return {
|
|
166
|
+
running: params.runtime?.running ?? false,
|
|
167
|
+
lastStartAt: params.runtime?.lastStartAt ?? null,
|
|
168
|
+
lastStopAt: params.runtime?.lastStopAt ?? null,
|
|
169
|
+
lastError: params.runtime?.lastError ?? null,
|
|
170
|
+
probe: params.probe,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function mapAllowFromEntries(
|
|
175
|
+
allowFrom: Array<string | number> | null | undefined,
|
|
176
|
+
): string[] {
|
|
177
|
+
return (allowFrom ?? []).map((entry) => String(entry));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function formatAllowFromLowercase(params: {
|
|
181
|
+
allowFrom: Array<string | number>;
|
|
182
|
+
stripPrefixRe?: RegExp;
|
|
183
|
+
}): string[] {
|
|
184
|
+
return params.allowFrom
|
|
185
|
+
.map((entry) => String(entry).trim())
|
|
186
|
+
.filter(Boolean)
|
|
187
|
+
.map((entry) => (params.stripPrefixRe ? entry.replace(params.stripPrefixRe, "") : entry))
|
|
188
|
+
.map((entry) => entry.toLowerCase());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function applyDirectoryQueryAndLimit(
|
|
192
|
+
ids: string[],
|
|
193
|
+
params: { query?: string | null; limit?: number | null },
|
|
194
|
+
): string[] {
|
|
195
|
+
const query = params.query?.trim().toLowerCase() || "";
|
|
196
|
+
const limit = typeof params.limit === "number" && params.limit > 0 ? params.limit : undefined;
|
|
197
|
+
const filtered = ids.filter((id) => (query ? id.toLowerCase().includes(query) : true));
|
|
198
|
+
return typeof limit === "number" ? filtered.slice(0, limit) : filtered;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function dedupeIds(ids: string[]): string[] {
|
|
202
|
+
return Array.from(new Set(ids));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function collectEntryIds(params: {
|
|
206
|
+
entries?: readonly unknown[];
|
|
207
|
+
normalizeId?: (entry: string) => string | null | undefined;
|
|
208
|
+
}): string[] {
|
|
209
|
+
return (params.entries ?? [])
|
|
210
|
+
.map((entry) => String(entry).trim())
|
|
211
|
+
.filter((entry) => Boolean(entry) && entry !== "*")
|
|
212
|
+
.map((entry) => {
|
|
213
|
+
const normalized = params.normalizeId ? params.normalizeId(entry) : entry;
|
|
214
|
+
return typeof normalized === "string" ? normalized.trim() : "";
|
|
215
|
+
})
|
|
216
|
+
.filter(Boolean);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function collectMapIds(params: {
|
|
220
|
+
map?: Record<string, unknown>;
|
|
221
|
+
normalizeId?: (entry: string) => string | null | undefined;
|
|
222
|
+
}): string[] {
|
|
223
|
+
return collectEntryIds({
|
|
224
|
+
entries: Object.keys(params.map ?? {}),
|
|
225
|
+
normalizeId: params.normalizeId,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function listDirectoryUserEntriesFromAllowFromAndMapKeys(params: {
|
|
230
|
+
allowFrom?: readonly unknown[];
|
|
231
|
+
map?: Record<string, unknown>;
|
|
232
|
+
query?: string | null;
|
|
233
|
+
limit?: number | null;
|
|
234
|
+
normalizeAllowFromId?: (entry: string) => string | null | undefined;
|
|
235
|
+
normalizeMapKeyId?: (entry: string) => string | null | undefined;
|
|
236
|
+
}): Array<{ kind: "user"; id: string }> {
|
|
237
|
+
const ids = dedupeIds([
|
|
238
|
+
...collectEntryIds({
|
|
239
|
+
entries: params.allowFrom,
|
|
240
|
+
normalizeId: params.normalizeAllowFromId,
|
|
241
|
+
}),
|
|
242
|
+
...collectMapIds({
|
|
243
|
+
map: params.map,
|
|
244
|
+
normalizeId: params.normalizeMapKeyId,
|
|
245
|
+
}),
|
|
246
|
+
]);
|
|
247
|
+
return applyDirectoryQueryAndLimit(ids, params).map((id) => ({ kind: "user", id }));
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function listDirectoryGroupEntriesFromMapKeysAndAllowFrom(params: {
|
|
251
|
+
groups?: Record<string, unknown>;
|
|
252
|
+
allowFrom?: readonly unknown[];
|
|
253
|
+
query?: string | null;
|
|
254
|
+
limit?: number | null;
|
|
255
|
+
normalizeMapKeyId?: (entry: string) => string | null | undefined;
|
|
256
|
+
normalizeAllowFromId?: (entry: string) => string | null | undefined;
|
|
257
|
+
}): Array<{ kind: "group"; id: string }> {
|
|
258
|
+
const ids = dedupeIds([
|
|
259
|
+
...collectMapIds({
|
|
260
|
+
map: params.groups,
|
|
261
|
+
normalizeId: params.normalizeMapKeyId,
|
|
262
|
+
}),
|
|
263
|
+
...collectEntryIds({
|
|
264
|
+
entries: params.allowFrom,
|
|
265
|
+
normalizeId: params.normalizeAllowFromId,
|
|
266
|
+
}),
|
|
267
|
+
]);
|
|
268
|
+
return applyDirectoryQueryAndLimit(ids, params).map((id) => ({ kind: "group", id }));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function collectAllowlistProviderRestrictSendersWarnings(params: {
|
|
272
|
+
cfg: OpenClawConfig;
|
|
273
|
+
providerConfigPresent: boolean;
|
|
274
|
+
configuredGroupPolicy?: GroupPolicy | null;
|
|
275
|
+
surface: string;
|
|
276
|
+
openScope: string;
|
|
277
|
+
groupPolicyPath: string;
|
|
278
|
+
groupAllowFromPath: string;
|
|
279
|
+
mentionGated?: boolean;
|
|
280
|
+
}): string[] {
|
|
281
|
+
const defaultGroupPolicy = resolveDefaultGroupPolicy(params.cfg as {
|
|
282
|
+
channels?: { defaults?: { groupPolicy?: GroupPolicy } };
|
|
283
|
+
});
|
|
284
|
+
const { groupPolicy } = resolveOpenProviderRuntimeGroupPolicy({
|
|
285
|
+
providerConfigPresent: params.providerConfigPresent,
|
|
286
|
+
groupPolicy: params.configuredGroupPolicy ?? undefined,
|
|
287
|
+
defaultGroupPolicy,
|
|
288
|
+
});
|
|
289
|
+
if (groupPolicy !== "open") {
|
|
290
|
+
return [];
|
|
291
|
+
}
|
|
292
|
+
const mentionSuffix = params.mentionGated === false ? "" : " (mention-gated)";
|
|
293
|
+
return [
|
|
294
|
+
`- ${params.surface}: groupPolicy="open" allows ${params.openScope} to trigger${mentionSuffix}. Set ${params.groupPolicyPath}="allowlist" + ${params.groupAllowFromPath} to restrict senders.`,
|
|
295
|
+
];
|
|
296
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { normalizeAccountId } from "./account-id.js";
|
|
2
|
+
import type { LogFn, PluginRuntime } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export const PAIRING_APPROVED_MESSAGE =
|
|
5
|
+
"NextClaw access approved. Send a message to start chatting.";
|
|
6
|
+
|
|
7
|
+
const NEXTCLAW_DOCS_ROOT = "https://docs.nextclaw.io";
|
|
8
|
+
|
|
9
|
+
export function buildAgentMediaPayload(
|
|
10
|
+
mediaList: Array<{ path: string; contentType?: string | null }>,
|
|
11
|
+
): {
|
|
12
|
+
MediaPath?: string;
|
|
13
|
+
MediaType?: string;
|
|
14
|
+
MediaUrl?: string;
|
|
15
|
+
MediaPaths?: string[];
|
|
16
|
+
MediaUrls?: string[];
|
|
17
|
+
MediaTypes?: string[];
|
|
18
|
+
} {
|
|
19
|
+
const first = mediaList[0];
|
|
20
|
+
const mediaPaths = mediaList.map((media) => media.path);
|
|
21
|
+
const mediaTypes = mediaList.map((media) => media.contentType).filter(Boolean) as string[];
|
|
22
|
+
return {
|
|
23
|
+
MediaPath: first?.path,
|
|
24
|
+
MediaType: first?.contentType ?? undefined,
|
|
25
|
+
MediaUrl: first?.path,
|
|
26
|
+
MediaPaths: mediaPaths.length > 0 ? mediaPaths : undefined,
|
|
27
|
+
MediaUrls: mediaPaths.length > 0 ? mediaPaths : undefined,
|
|
28
|
+
MediaTypes: mediaTypes.length > 0 ? mediaTypes : undefined,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function formatDocsLink(path: string, label?: string): string {
|
|
33
|
+
const trimmed = path.trim();
|
|
34
|
+
const url = trimmed.startsWith("http")
|
|
35
|
+
? trimmed
|
|
36
|
+
: `${NEXTCLAW_DOCS_ROOT}${trimmed.startsWith("/") ? trimmed : `/${trimmed}`}`;
|
|
37
|
+
return label ? `${label} (${url})` : url;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function buildPairingReply(params: {
|
|
41
|
+
channel: string;
|
|
42
|
+
idLine: string;
|
|
43
|
+
code: string;
|
|
44
|
+
}): string {
|
|
45
|
+
return [
|
|
46
|
+
"NextClaw: access not configured.",
|
|
47
|
+
"",
|
|
48
|
+
params.idLine,
|
|
49
|
+
"",
|
|
50
|
+
`Pairing code: ${params.code}`,
|
|
51
|
+
"",
|
|
52
|
+
"Ask the bot owner to approve with:",
|
|
53
|
+
`nextclaw pairing approve ${params.channel} ${params.code}`,
|
|
54
|
+
].join("\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function issuePairingChallenge(params: {
|
|
58
|
+
channel: string;
|
|
59
|
+
senderId: string;
|
|
60
|
+
senderIdLine: string;
|
|
61
|
+
meta?: Record<string, string | undefined>;
|
|
62
|
+
upsertPairingRequest: (params: {
|
|
63
|
+
id: string;
|
|
64
|
+
meta?: Record<string, string | undefined>;
|
|
65
|
+
}) => Promise<{ code: string; created: boolean }>;
|
|
66
|
+
sendPairingReply: (text: string) => Promise<void>;
|
|
67
|
+
buildReplyText?: (params: { code: string; senderIdLine: string }) => string;
|
|
68
|
+
onCreated?: (params: { code: string }) => void;
|
|
69
|
+
onReplyError?: (err: unknown) => void;
|
|
70
|
+
}): Promise<{ created: boolean; code?: string }> {
|
|
71
|
+
const { code, created } = await params.upsertPairingRequest({
|
|
72
|
+
id: params.senderId,
|
|
73
|
+
meta: params.meta,
|
|
74
|
+
});
|
|
75
|
+
if (!created) {
|
|
76
|
+
return { created: false };
|
|
77
|
+
}
|
|
78
|
+
params.onCreated?.({ code });
|
|
79
|
+
const replyText =
|
|
80
|
+
params.buildReplyText?.({ code, senderIdLine: params.senderIdLine }) ??
|
|
81
|
+
buildPairingReply({
|
|
82
|
+
channel: params.channel,
|
|
83
|
+
idLine: params.senderIdLine,
|
|
84
|
+
code,
|
|
85
|
+
});
|
|
86
|
+
try {
|
|
87
|
+
await params.sendPairingReply(replyText);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
params.onReplyError?.(error);
|
|
90
|
+
}
|
|
91
|
+
return { created: true, code };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function createScopedPairingAccess(params: {
|
|
95
|
+
core: PluginRuntime;
|
|
96
|
+
channel: string;
|
|
97
|
+
accountId: string;
|
|
98
|
+
}) {
|
|
99
|
+
const accountId = normalizeAccountId(params.accountId);
|
|
100
|
+
return {
|
|
101
|
+
accountId,
|
|
102
|
+
readAllowFromStore: () =>
|
|
103
|
+
params.core.channel.pairing.readAllowFromStore({
|
|
104
|
+
channel: params.channel,
|
|
105
|
+
accountId,
|
|
106
|
+
}),
|
|
107
|
+
readStoreForDmPolicy: (provider: string, providerAccountId: string) =>
|
|
108
|
+
params.core.channel.pairing.readAllowFromStore({
|
|
109
|
+
channel: provider,
|
|
110
|
+
accountId: normalizeAccountId(providerAccountId),
|
|
111
|
+
}),
|
|
112
|
+
upsertPairingRequest: (input: {
|
|
113
|
+
id: string;
|
|
114
|
+
meta?: Record<string, string | undefined>;
|
|
115
|
+
}) =>
|
|
116
|
+
params.core.channel.pairing.upsertPairingRequest({
|
|
117
|
+
channel: params.channel,
|
|
118
|
+
accountId,
|
|
119
|
+
...input,
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function createReplyPrefixContext() {
|
|
125
|
+
const prefixContext: Record<string, unknown> = {};
|
|
126
|
+
return {
|
|
127
|
+
prefixContext,
|
|
128
|
+
responsePrefix: undefined as string | undefined,
|
|
129
|
+
enableSlackInteractiveReplies: undefined as boolean | undefined,
|
|
130
|
+
responsePrefixContextProvider: () => prefixContext,
|
|
131
|
+
onModelSelected: (_ctx: Record<string, unknown>) => {},
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function logTypingFailure(params: {
|
|
136
|
+
log: LogFn;
|
|
137
|
+
channel: string;
|
|
138
|
+
target?: string;
|
|
139
|
+
action?: "start" | "stop";
|
|
140
|
+
error: unknown;
|
|
141
|
+
}): void {
|
|
142
|
+
const target = params.target ? ` target=${params.target}` : "";
|
|
143
|
+
const action = params.action ? ` action=${params.action}` : "";
|
|
144
|
+
params.log(`${params.channel} typing${action} failed${target}: ${String(params.error)}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function createTypingCallbacks(params: {
|
|
148
|
+
start: () => Promise<void>;
|
|
149
|
+
stop?: () => Promise<void>;
|
|
150
|
+
onStartError: (err: unknown) => void;
|
|
151
|
+
onStopError?: (err: unknown) => void;
|
|
152
|
+
keepaliveIntervalMs?: number;
|
|
153
|
+
maxConsecutiveFailures?: number;
|
|
154
|
+
maxDurationMs?: number;
|
|
155
|
+
}) {
|
|
156
|
+
const keepaliveIntervalMs = params.keepaliveIntervalMs ?? 3_000;
|
|
157
|
+
const maxConsecutiveFailures = Math.max(1, params.maxConsecutiveFailures ?? 2);
|
|
158
|
+
const maxDurationMs = params.maxDurationMs ?? 60_000;
|
|
159
|
+
let interval: ReturnType<typeof setInterval> | undefined;
|
|
160
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
161
|
+
let closed = false;
|
|
162
|
+
let stopSent = false;
|
|
163
|
+
let consecutiveFailures = 0;
|
|
164
|
+
|
|
165
|
+
const cleanupTimers = () => {
|
|
166
|
+
if (interval) {
|
|
167
|
+
clearInterval(interval);
|
|
168
|
+
interval = undefined;
|
|
169
|
+
}
|
|
170
|
+
if (timeout) {
|
|
171
|
+
clearTimeout(timeout);
|
|
172
|
+
timeout = undefined;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const fireStop = () => {
|
|
177
|
+
cleanupTimers();
|
|
178
|
+
closed = true;
|
|
179
|
+
if (!params.stop || stopSent) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
stopSent = true;
|
|
183
|
+
void params.stop().catch((error) => (params.onStopError ?? params.onStartError)(error));
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const fireStart = async () => {
|
|
187
|
+
if (closed) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
await params.start();
|
|
192
|
+
consecutiveFailures = 0;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
consecutiveFailures += 1;
|
|
195
|
+
params.onStartError(error);
|
|
196
|
+
if (consecutiveFailures >= maxConsecutiveFailures) {
|
|
197
|
+
fireStop();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
onReplyStart: async () => {
|
|
204
|
+
closed = false;
|
|
205
|
+
stopSent = false;
|
|
206
|
+
consecutiveFailures = 0;
|
|
207
|
+
cleanupTimers();
|
|
208
|
+
await fireStart();
|
|
209
|
+
if (closed) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
interval = setInterval(() => {
|
|
213
|
+
void fireStart();
|
|
214
|
+
}, keepaliveIntervalMs);
|
|
215
|
+
if (maxDurationMs > 0) {
|
|
216
|
+
timeout = setTimeout(() => {
|
|
217
|
+
fireStop();
|
|
218
|
+
}, maxDurationMs);
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
onIdle: fireStop,
|
|
222
|
+
onCleanup: fireStop,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { DEFAULT_ACCOUNT_ID, normalizeAccountId, normalizeAgentId, normalizeOptionalAccountId } from "./account-id.js";
|
|
2
|
+
export {
|
|
3
|
+
collectAllowlistProviderRestrictSendersWarnings,
|
|
4
|
+
createDefaultChannelRuntimeState,
|
|
5
|
+
emptyPluginConfigSchema,
|
|
6
|
+
evaluateSenderGroupAccessForPolicy,
|
|
7
|
+
formatAllowFromLowercase,
|
|
8
|
+
listDirectoryGroupEntriesFromMapKeysAndAllowFrom,
|
|
9
|
+
listDirectoryUserEntriesFromAllowFromAndMapKeys,
|
|
10
|
+
mapAllowFromEntries,
|
|
11
|
+
resolveDefaultGroupPolicy,
|
|
12
|
+
resolveOpenProviderRuntimeGroupPolicy,
|
|
13
|
+
warnMissingProviderGroupPolicyFallbackOnce,
|
|
14
|
+
buildProbeChannelStatusSummary,
|
|
15
|
+
buildRuntimeAccountStatusSnapshot,
|
|
16
|
+
} from "./core-channel.js";
|
|
17
|
+
export {
|
|
18
|
+
buildAgentMediaPayload,
|
|
19
|
+
createReplyPrefixContext,
|
|
20
|
+
createScopedPairingAccess,
|
|
21
|
+
createTypingCallbacks,
|
|
22
|
+
formatDocsLink,
|
|
23
|
+
issuePairingChallenge,
|
|
24
|
+
logTypingFailure,
|
|
25
|
+
PAIRING_APPROVED_MESSAGE,
|
|
26
|
+
} from "./core-pairing.js";
|