@openclaw/feishu 2026.5.24-beta.2 → 2026.5.26-beta.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/dist/{accounts-CRcvqpsl.js → accounts-CXnY5H8g.js} +2 -2
- package/dist/api.js +7 -7
- package/dist/{channel-Br99IozO.js → channel-DavfT_AA.js} +689 -178
- package/dist/channel-plugin-api.js +1 -1
- package/dist/{channel.runtime-C8KEo0lg.js → channel.runtime-ItBg9SfS.js} +65 -102
- package/dist/{drive-DMbtRzY9.js → drive-DwgWWkJi.js} +1 -1
- package/dist/{monitor-CPSt9A7K.js → monitor-gFxvkDyO.js} +2 -2
- package/dist/{monitor.account-CbepO5r8.js → monitor.account-CmXHWuwG.js} +73 -90
- package/dist/runtime-api.js +1 -1
- package/dist/{send-DxNatQGH.js → send-DQClIwTI.js} +14 -17
- package/dist/send-result-CHvu8Rr7.js +140 -0
- package/dist/setup-api.js +1 -1
- package/node_modules/qs/CHANGELOG.md +178 -0
- package/node_modules/qs/README.md +19 -1
- package/node_modules/qs/dist/qs.js +17 -17
- package/node_modules/qs/eslint.config.mjs +1 -0
- package/node_modules/qs/lib/parse.js +57 -25
- package/node_modules/qs/lib/stringify.js +11 -4
- package/node_modules/qs/lib/utils.js +2 -0
- package/node_modules/qs/package.json +3 -3
- package/node_modules/qs/test/parse.js +195 -4
- package/node_modules/qs/test/stringify.js +138 -0
- package/node_modules/qs/test/utils.js +38 -3
- package/node_modules/ws/lib/receiver.js +54 -0
- package/node_modules/ws/lib/websocket-server.js +8 -0
- package/node_modules/ws/lib/websocket.js +14 -0
- package/node_modules/ws/package.json +1 -1
- package/npm-shrinkwrap.json +9 -9
- package/package.json +4 -4
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { a as parseFeishuTargetId, i as parseFeishuDirectConversationId, n as buildFeishuModelOverrideParentCandidates, r as parseFeishuConversationId, t as buildFeishuConversationId } from "./conversation-id-DuL575sn.js";
|
|
2
2
|
import { n as looksLikeFeishuId, r as normalizeFeishuTarget, t as detectIdType } from "./targets-BUjQ1TcA.js";
|
|
3
|
-
import { a as resolveDefaultFeishuAccountId, f as isRecord$
|
|
3
|
+
import { a as resolveDefaultFeishuAccountId, f as isRecord$2, i as listFeishuAccountIds, n as inspectFeishuCredentials, o as resolveFeishuAccount, r as listEnabledFeishuAccounts, s as resolveFeishuRuntimeAccount } from "./accounts-CXnY5H8g.js";
|
|
4
|
+
import { n as createFeishuSendReceipt, s as createFeishuCardInteractionEnvelope } from "./send-result-CHvu8Rr7.js";
|
|
4
5
|
import { t as messageActionTargetAliases } from "./security-audit-BIeA3W3Q.js";
|
|
5
6
|
import { n as collectRuntimeConfigAssignments, r as secretTargetRegistryEntries } from "./secret-contract-ChjJKAJ9.js";
|
|
6
7
|
import { t as collectFeishuSecurityAuditFindings } from "./security-audit-shared-BIHeF-S_.js";
|
|
7
8
|
import { t as resolveFeishuSessionConversation } from "./session-conversation-CZSMgac-.js";
|
|
8
|
-
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
9
|
+
import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
9
10
|
import { getSessionBindingService } from "openclaw/plugin-sdk/conversation-runtime";
|
|
11
|
+
import { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
|
10
12
|
import { describeAccountSnapshot } from "openclaw/plugin-sdk/account-helpers";
|
|
11
13
|
import { formatAllowFromLowercase } from "openclaw/plugin-sdk/allow-from";
|
|
12
14
|
import { adaptScopedAccountAccessor, createHybridChannelConfigAdapter } from "openclaw/plugin-sdk/channel-config-helpers";
|
|
13
15
|
import { buildChannelOutboundSessionRoute, createChatChannelPlugin, stripChannelTargetPrefix } from "openclaw/plugin-sdk/channel-core";
|
|
14
|
-
import {
|
|
16
|
+
import { defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-message";
|
|
15
17
|
import { createPairingPrefixStripper } from "openclaw/plugin-sdk/channel-pairing";
|
|
16
18
|
import { createAllowlistProviderGroupPolicyWarningCollector, projectConfigAccountIdWarningCollector } from "openclaw/plugin-sdk/channel-policy";
|
|
17
19
|
import { createChannelDirectoryAdapter, createRuntimeDirectoryLiveAdapter, listDirectoryGroupEntriesFromMapKeysAndAllowFrom, listDirectoryUserEntriesFromAllowFromAndMapKeys } from "openclaw/plugin-sdk/directory-runtime";
|
|
@@ -19,15 +21,20 @@ import { normalizeMessagePresentation, renderMessagePresentationFallbackText } f
|
|
|
19
21
|
import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
|
|
20
22
|
import { createRuntimeOutboundDelegates } from "openclaw/plugin-sdk/outbound-runtime";
|
|
21
23
|
import { buildProbeChannelStatusSummary, createComputedAccountStatusAdapter, createDefaultChannelRuntimeState } from "openclaw/plugin-sdk/status-helpers";
|
|
22
|
-
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2, normalizeAccountId, resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
|
|
24
|
+
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$2, normalizeAccountId as normalizeAccountId$1, resolveMergedAccountConfig } from "openclaw/plugin-sdk/account-resolution";
|
|
23
25
|
import { createResolvedApproverActionAuthAdapter, resolveApprovalApprovers } from "openclaw/plugin-sdk/approval-auth-runtime";
|
|
24
26
|
import { createActionGate } from "openclaw/plugin-sdk/channel-actions";
|
|
25
27
|
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-primitives";
|
|
26
28
|
import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status";
|
|
27
29
|
import { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
|
|
28
|
-
import { normalizeAccountId as normalizeAccountId$
|
|
30
|
+
import { normalizeAccountId as normalizeAccountId$2 } from "openclaw/plugin-sdk/account-id";
|
|
29
31
|
import { z } from "zod";
|
|
30
32
|
import { buildSecretInputSchema, hasConfiguredSecretInput as hasConfiguredSecretInput$2 } from "openclaw/plugin-sdk/secret-input";
|
|
33
|
+
import fs from "node:fs";
|
|
34
|
+
import os from "node:os";
|
|
35
|
+
import path from "node:path";
|
|
36
|
+
import { loadSessionStore, resolveSessionFilePath, resolveStorePath, updateSessionStore } from "openclaw/plugin-sdk/session-store-runtime";
|
|
37
|
+
import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
|
|
31
38
|
import { createChannelIngressResolver, defineStableChannelIngressIdentity } from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
32
39
|
import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, createSetupTranslator, formatDocsLink, hasConfiguredSecretInput as hasConfiguredSecretInput$1, mergeAllowFromEntries, patchTopLevelChannelConfigSection, promptSingleChannelSecretInput, splitSetupEntries } from "openclaw/plugin-sdk/setup";
|
|
33
40
|
//#region extensions/feishu/src/approval-auth.ts
|
|
@@ -50,100 +57,6 @@ const feishuApprovalAuth = createResolvedApproverActionAuthAdapter({
|
|
|
50
57
|
normalizeSenderId: (value) => normalizeFeishuApproverId(value)
|
|
51
58
|
});
|
|
52
59
|
//#endregion
|
|
53
|
-
//#region extensions/feishu/src/card-interaction.ts
|
|
54
|
-
const FEISHU_CARD_INTERACTION_VERSION = "ocf1";
|
|
55
|
-
function isInteractionKind(value) {
|
|
56
|
-
return value === "button" || value === "quick" || value === "meta";
|
|
57
|
-
}
|
|
58
|
-
function isMetadataValue(value) {
|
|
59
|
-
return value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
60
|
-
}
|
|
61
|
-
function createFeishuCardInteractionEnvelope(envelope) {
|
|
62
|
-
return {
|
|
63
|
-
oc: FEISHU_CARD_INTERACTION_VERSION,
|
|
64
|
-
...envelope
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
function buildFeishuCardActionTextFallback(event) {
|
|
68
|
-
const actionValue = event.action.value;
|
|
69
|
-
if (isRecord$1(actionValue)) {
|
|
70
|
-
if (typeof actionValue.text === "string") return actionValue.text;
|
|
71
|
-
if (typeof actionValue.command === "string") return actionValue.command;
|
|
72
|
-
return JSON.stringify(actionValue);
|
|
73
|
-
}
|
|
74
|
-
return String(actionValue);
|
|
75
|
-
}
|
|
76
|
-
function decodeFeishuCardAction(params) {
|
|
77
|
-
const { event, now = Date.now() } = params;
|
|
78
|
-
const actionValue = event.action.value;
|
|
79
|
-
if (!isRecord$1(actionValue) || actionValue.oc !== "ocf1") return {
|
|
80
|
-
kind: "legacy",
|
|
81
|
-
text: buildFeishuCardActionTextFallback(event)
|
|
82
|
-
};
|
|
83
|
-
if (!isInteractionKind(actionValue.k) || typeof actionValue.a !== "string" || !actionValue.a) return {
|
|
84
|
-
kind: "invalid",
|
|
85
|
-
reason: "malformed"
|
|
86
|
-
};
|
|
87
|
-
if (actionValue.q !== void 0 && typeof actionValue.q !== "string") return {
|
|
88
|
-
kind: "invalid",
|
|
89
|
-
reason: "malformed"
|
|
90
|
-
};
|
|
91
|
-
if (actionValue.m !== void 0) {
|
|
92
|
-
if (!isRecord$1(actionValue.m)) return {
|
|
93
|
-
kind: "invalid",
|
|
94
|
-
reason: "malformed"
|
|
95
|
-
};
|
|
96
|
-
for (const value of Object.values(actionValue.m)) if (!isMetadataValue(value)) return {
|
|
97
|
-
kind: "invalid",
|
|
98
|
-
reason: "malformed"
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
if (actionValue.c !== void 0) {
|
|
102
|
-
if (!isRecord$1(actionValue.c)) return {
|
|
103
|
-
kind: "invalid",
|
|
104
|
-
reason: "malformed"
|
|
105
|
-
};
|
|
106
|
-
if (actionValue.c.u !== void 0 && typeof actionValue.c.u !== "string") return {
|
|
107
|
-
kind: "invalid",
|
|
108
|
-
reason: "malformed"
|
|
109
|
-
};
|
|
110
|
-
if (actionValue.c.h !== void 0 && typeof actionValue.c.h !== "string") return {
|
|
111
|
-
kind: "invalid",
|
|
112
|
-
reason: "malformed"
|
|
113
|
-
};
|
|
114
|
-
if (actionValue.c.s !== void 0 && typeof actionValue.c.s !== "string") return {
|
|
115
|
-
kind: "invalid",
|
|
116
|
-
reason: "malformed"
|
|
117
|
-
};
|
|
118
|
-
if (actionValue.c.e !== void 0 && !Number.isFinite(actionValue.c.e)) return {
|
|
119
|
-
kind: "invalid",
|
|
120
|
-
reason: "malformed"
|
|
121
|
-
};
|
|
122
|
-
if (actionValue.c.t !== void 0 && actionValue.c.t !== "p2p" && actionValue.c.t !== "group") return {
|
|
123
|
-
kind: "invalid",
|
|
124
|
-
reason: "malformed"
|
|
125
|
-
};
|
|
126
|
-
if (typeof actionValue.c.e === "number" && actionValue.c.e < now) return {
|
|
127
|
-
kind: "invalid",
|
|
128
|
-
reason: "stale"
|
|
129
|
-
};
|
|
130
|
-
const expectedUser = actionValue.c.u?.trim();
|
|
131
|
-
if (expectedUser && expectedUser !== (event.operator.open_id ?? "").trim()) return {
|
|
132
|
-
kind: "invalid",
|
|
133
|
-
reason: "wrong_user"
|
|
134
|
-
};
|
|
135
|
-
const expectedChat = actionValue.c.h?.trim();
|
|
136
|
-
if (expectedChat && expectedChat !== (event.context.chat_id ?? "").trim()) return {
|
|
137
|
-
kind: "invalid",
|
|
138
|
-
reason: "wrong_conversation"
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
return {
|
|
142
|
-
kind: "structured",
|
|
143
|
-
envelope: actionValue
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
//#endregion
|
|
147
60
|
//#region extensions/feishu/src/config-schema.ts
|
|
148
61
|
const ChannelActionsSchema = z.object({ reactions: z.boolean().optional() }).strict().optional();
|
|
149
62
|
const DmPolicySchema = z.enum([
|
|
@@ -365,7 +278,7 @@ const FeishuConfigSchema = z.object({
|
|
|
365
278
|
}).strict().superRefine((value, ctx) => {
|
|
366
279
|
const defaultAccount = value.defaultAccount?.trim();
|
|
367
280
|
if (defaultAccount && value.accounts && Object.keys(value.accounts).length > 0) {
|
|
368
|
-
const normalizedDefaultAccount = normalizeAccountId$
|
|
281
|
+
const normalizedDefaultAccount = normalizeAccountId$2(defaultAccount);
|
|
369
282
|
if (!Object.prototype.hasOwnProperty.call(value.accounts, normalizedDefaultAccount)) ctx.addIssue({
|
|
370
283
|
code: z.ZodIssueCode.custom,
|
|
371
284
|
path: ["defaultAccount"],
|
|
@@ -460,6 +373,556 @@ async function listFeishuDirectoryGroups(params) {
|
|
|
460
373
|
}).map((entry) => entry.id));
|
|
461
374
|
}
|
|
462
375
|
//#endregion
|
|
376
|
+
//#region extensions/feishu/src/doctor.ts
|
|
377
|
+
const FEISHU_STATE_DIR = "feishu";
|
|
378
|
+
const BACKUP_PREFIX = "feishu-state-repair";
|
|
379
|
+
const BLANK_USER_MESSAGE_REPAIR_THRESHOLD = 3;
|
|
380
|
+
const SESSION_FILE_INSPECTION_MAX_BYTES = 16 * 1024 * 1024;
|
|
381
|
+
function timestampForPath(now = /* @__PURE__ */ new Date()) {
|
|
382
|
+
return now.toISOString().replaceAll(":", "-");
|
|
383
|
+
}
|
|
384
|
+
function isRecord$1(value) {
|
|
385
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
386
|
+
}
|
|
387
|
+
function toFeishuSessionEntry(value) {
|
|
388
|
+
if (!isRecord$1(value)) return {};
|
|
389
|
+
return {
|
|
390
|
+
sessionId: value.sessionId,
|
|
391
|
+
sessionFile: value.sessionFile
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
function countLabel(count, singular, plural = `${singular}s`) {
|
|
395
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
396
|
+
}
|
|
397
|
+
function existsDir(dir) {
|
|
398
|
+
try {
|
|
399
|
+
return fs.statSync(dir).isDirectory();
|
|
400
|
+
} catch {
|
|
401
|
+
return false;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function existsFile(filePath) {
|
|
405
|
+
try {
|
|
406
|
+
return fs.statSync(filePath).isFile();
|
|
407
|
+
} catch {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function safeReadDir(dir) {
|
|
412
|
+
try {
|
|
413
|
+
return fs.readdirSync(dir, { withFileTypes: true });
|
|
414
|
+
} catch {
|
|
415
|
+
return [];
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
function isPathWithinRoot(targetPath, rootPath) {
|
|
419
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
420
|
+
const resolvedRoot = path.resolve(rootPath);
|
|
421
|
+
const relative = path.relative(resolvedRoot, resolvedTarget);
|
|
422
|
+
return Boolean(relative) && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
423
|
+
}
|
|
424
|
+
function formatDisplayPath(filePath) {
|
|
425
|
+
const home = os.homedir();
|
|
426
|
+
const resolved = path.resolve(filePath);
|
|
427
|
+
return resolved === home || resolved.startsWith(`${home}${path.sep}`) ? `~${resolved.slice(home.length)}` : resolved;
|
|
428
|
+
}
|
|
429
|
+
function formatFinding(finding) {
|
|
430
|
+
switch (finding.kind) {
|
|
431
|
+
case "corrupt-state-json": return `- Feishu local JSON state is corrupt: ${formatDisplayPath(finding.path)}`;
|
|
432
|
+
case "missing-session-transcript": return `- Feishu session ${finding.sessionKey} points to a missing transcript in ${formatDisplayPath(finding.storePath)}`;
|
|
433
|
+
case "invalid-session-transcript": return `- Feishu session ${finding.sessionKey} has an invalid transcript (${finding.reason}): ${formatDisplayPath(finding.path)}`;
|
|
434
|
+
case "blank-user-message-run": return `- Feishu session ${finding.sessionKey} contains ${finding.count} blank user messages: ${formatDisplayPath(finding.path)}`;
|
|
435
|
+
}
|
|
436
|
+
return finding;
|
|
437
|
+
}
|
|
438
|
+
function isFeishuSessionStoreKey(key) {
|
|
439
|
+
const normalized = key.trim().toLowerCase();
|
|
440
|
+
return /^agent:[^:]+:feishu(?::|$)/.test(normalized) || /^feishu(?::|$)/.test(normalized);
|
|
441
|
+
}
|
|
442
|
+
function isFeishuAcpBindingSessionKey(key) {
|
|
443
|
+
return /^agent:[^:]+:acp:binding:feishu(?::|$)/.test(key.trim().toLowerCase());
|
|
444
|
+
}
|
|
445
|
+
function normalizeMetadataString(value) {
|
|
446
|
+
return typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
447
|
+
}
|
|
448
|
+
function isFeishuSessionEntry(key, value) {
|
|
449
|
+
if (isFeishuAcpBindingSessionKey(key)) return false;
|
|
450
|
+
if (isFeishuSessionStoreKey(key)) return true;
|
|
451
|
+
if (!isRecord$1(value)) return false;
|
|
452
|
+
if (normalizeMetadataString(value.channel) === "feishu" || normalizeMetadataString(value.lastChannel) === "feishu") return true;
|
|
453
|
+
if (normalizeMetadataString((isRecord$1(value.route) ? value.route : null)?.channel) === "feishu") return true;
|
|
454
|
+
if (normalizeMetadataString((isRecord$1(value.deliveryContext) ? value.deliveryContext : null)?.channel) === "feishu") return true;
|
|
455
|
+
if (normalizeMetadataString((isRecord$1(value.pendingFinalDeliveryContext) ? value.pendingFinalDeliveryContext : null)?.channel) === "feishu") return true;
|
|
456
|
+
const origin = isRecord$1(value.origin) ? value.origin : null;
|
|
457
|
+
const originProvider = normalizeMetadataString(origin?.provider);
|
|
458
|
+
const originSurface = normalizeMetadataString(origin?.surface);
|
|
459
|
+
const originFrom = normalizeMetadataString(origin?.from);
|
|
460
|
+
return originProvider === "feishu" || originSurface.startsWith("feishu") || originFrom.startsWith("feishu:");
|
|
461
|
+
}
|
|
462
|
+
function collectConfiguredAgentIds(cfg) {
|
|
463
|
+
const ids = /* @__PURE__ */ new Set();
|
|
464
|
+
ids.add(resolveConfiguredDefaultAgentId(cfg));
|
|
465
|
+
for (const agent of cfg.agents?.list ?? []) if (typeof agent.id === "string" && agent.id.trim()) ids.add(normalizeAgentId(agent.id));
|
|
466
|
+
return [...ids].toSorted();
|
|
467
|
+
}
|
|
468
|
+
function resolveConfiguredDefaultAgentId(cfg) {
|
|
469
|
+
const agents = cfg.agents?.list ?? [];
|
|
470
|
+
const chosen = agents.find((agent) => agent?.default) ?? agents[0];
|
|
471
|
+
return normalizeAgentId(typeof chosen?.id === "string" && chosen.id.trim() ? chosen.id : "main");
|
|
472
|
+
}
|
|
473
|
+
function collectFeishuSessionTargets(params) {
|
|
474
|
+
const byStorePath = /* @__PURE__ */ new Map();
|
|
475
|
+
const addTarget = (target) => {
|
|
476
|
+
byStorePath.set(path.resolve(target.storePath), {
|
|
477
|
+
...target,
|
|
478
|
+
storePath: path.resolve(target.storePath)
|
|
479
|
+
});
|
|
480
|
+
};
|
|
481
|
+
for (const agentId of collectConfiguredAgentIds(params.cfg)) addTarget({
|
|
482
|
+
agentId,
|
|
483
|
+
storePath: resolveStorePath(params.cfg.session?.store, {
|
|
484
|
+
agentId,
|
|
485
|
+
env: params.env
|
|
486
|
+
})
|
|
487
|
+
});
|
|
488
|
+
const agentsDir = path.join(params.stateDir, "agents");
|
|
489
|
+
for (const agentDir of safeReadDir(agentsDir)) {
|
|
490
|
+
if (!agentDir.isDirectory()) continue;
|
|
491
|
+
const agentId = normalizeAgentId(agentDir.name);
|
|
492
|
+
const storePath = path.join(agentsDir, agentDir.name, "sessions", "sessions.json");
|
|
493
|
+
if (existsFile(storePath)) addTarget({
|
|
494
|
+
agentId,
|
|
495
|
+
storePath
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
return [...byStorePath.values()].toSorted((left, right) => left.storePath.localeCompare(right.storePath));
|
|
499
|
+
}
|
|
500
|
+
function collectJsonFiles(rootDir, limit = 200) {
|
|
501
|
+
const files = [];
|
|
502
|
+
const visit = (dir) => {
|
|
503
|
+
if (files.length >= limit) return;
|
|
504
|
+
for (const entry of safeReadDir(dir).toSorted((left, right) => left.name.localeCompare(right.name))) {
|
|
505
|
+
const fullPath = path.join(dir, entry.name);
|
|
506
|
+
if (entry.isDirectory()) {
|
|
507
|
+
visit(fullPath);
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
if (entry.isFile() && entry.name.endsWith(".json")) files.push(fullPath);
|
|
511
|
+
if (files.length >= limit) return;
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
if (existsDir(rootDir)) visit(rootDir);
|
|
515
|
+
return files;
|
|
516
|
+
}
|
|
517
|
+
function collectCorruptFeishuStateJsonFindings(feishuStateDir) {
|
|
518
|
+
const findings = [];
|
|
519
|
+
for (const filePath of collectJsonFiles(feishuStateDir)) try {
|
|
520
|
+
JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
521
|
+
} catch {
|
|
522
|
+
findings.push({
|
|
523
|
+
kind: "corrupt-state-json",
|
|
524
|
+
path: filePath
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
return findings;
|
|
528
|
+
}
|
|
529
|
+
function resolveSessionTranscriptCandidates(params) {
|
|
530
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
531
|
+
const sessionsDir = path.dirname(params.storePath);
|
|
532
|
+
const addSafeCandidate = (candidate) => {
|
|
533
|
+
const resolved = path.isAbsolute(candidate) ? path.resolve(candidate) : path.resolve(sessionsDir, candidate);
|
|
534
|
+
if (resolved === sessionsDir || !isPathWithinRoot(resolved, sessionsDir)) return;
|
|
535
|
+
candidates.add(resolved);
|
|
536
|
+
};
|
|
537
|
+
if (typeof params.entry.sessionId === "string" && /^[a-z0-9][a-z0-9._-]{0,127}$/i.test(params.entry.sessionId)) {
|
|
538
|
+
candidates.add(resolveSessionFilePath(params.entry.sessionId, typeof params.entry.sessionFile === "string" ? { sessionFile: params.entry.sessionFile } : void 0, {
|
|
539
|
+
agentId: params.agentId,
|
|
540
|
+
sessionsDir
|
|
541
|
+
}));
|
|
542
|
+
return [...candidates].toSorted();
|
|
543
|
+
}
|
|
544
|
+
if (typeof params.entry.sessionFile === "string" && params.entry.sessionFile.trim()) addSafeCandidate(params.entry.sessionFile.trim());
|
|
545
|
+
return [...candidates].toSorted();
|
|
546
|
+
}
|
|
547
|
+
function isSessionHeader(value) {
|
|
548
|
+
return isRecord$1(value) && value.type === "session" && typeof value.id === "string";
|
|
549
|
+
}
|
|
550
|
+
function isBlankUserMessage(value) {
|
|
551
|
+
if (!isRecord$1(value) || value.type !== "message" || !isRecord$1(value.message)) return false;
|
|
552
|
+
if (value.message.role !== "user") return false;
|
|
553
|
+
const content = value.message.content;
|
|
554
|
+
if (typeof content === "string") return content.trim().length === 0;
|
|
555
|
+
return Array.isArray(content) && content.length === 0;
|
|
556
|
+
}
|
|
557
|
+
function isUserMessage(value) {
|
|
558
|
+
return isRecord$1(value) && value.type === "message" && isRecord$1(value.message) && value.message.role === "user";
|
|
559
|
+
}
|
|
560
|
+
function inspectSessionTranscript(params) {
|
|
561
|
+
let stat;
|
|
562
|
+
try {
|
|
563
|
+
stat = fs.statSync(params.transcriptPath);
|
|
564
|
+
} catch {
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
if (!stat.isFile()) return {
|
|
568
|
+
kind: "invalid-session-transcript",
|
|
569
|
+
sessionKey: params.sessionKey,
|
|
570
|
+
storePath: params.storePath,
|
|
571
|
+
path: params.transcriptPath,
|
|
572
|
+
reason: "not a file"
|
|
573
|
+
};
|
|
574
|
+
if (stat.size > SESSION_FILE_INSPECTION_MAX_BYTES) return null;
|
|
575
|
+
let raw = "";
|
|
576
|
+
try {
|
|
577
|
+
raw = fs.readFileSync(params.transcriptPath, "utf-8");
|
|
578
|
+
} catch {
|
|
579
|
+
return {
|
|
580
|
+
kind: "invalid-session-transcript",
|
|
581
|
+
sessionKey: params.sessionKey,
|
|
582
|
+
storePath: params.storePath,
|
|
583
|
+
path: params.transcriptPath,
|
|
584
|
+
reason: "unreadable"
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
const entries = [];
|
|
588
|
+
let malformedLines = 0;
|
|
589
|
+
let blankUserMessageRun = 0;
|
|
590
|
+
let maxBlankUserMessageRun = 0;
|
|
591
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
592
|
+
if (!line.trim()) continue;
|
|
593
|
+
try {
|
|
594
|
+
const entry = JSON.parse(line);
|
|
595
|
+
entries.push(entry);
|
|
596
|
+
if (isBlankUserMessage(entry)) {
|
|
597
|
+
blankUserMessageRun += 1;
|
|
598
|
+
maxBlankUserMessageRun = Math.max(maxBlankUserMessageRun, blankUserMessageRun);
|
|
599
|
+
} else if (isUserMessage(entry)) blankUserMessageRun = 0;
|
|
600
|
+
} catch {
|
|
601
|
+
malformedLines += 1;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
if (entries.length === 0) return {
|
|
605
|
+
kind: "invalid-session-transcript",
|
|
606
|
+
sessionKey: params.sessionKey,
|
|
607
|
+
storePath: params.storePath,
|
|
608
|
+
path: params.transcriptPath,
|
|
609
|
+
reason: "empty transcript"
|
|
610
|
+
};
|
|
611
|
+
if (!isSessionHeader(entries[0])) return {
|
|
612
|
+
kind: "invalid-session-transcript",
|
|
613
|
+
sessionKey: params.sessionKey,
|
|
614
|
+
storePath: params.storePath,
|
|
615
|
+
path: params.transcriptPath,
|
|
616
|
+
reason: "invalid session header"
|
|
617
|
+
};
|
|
618
|
+
if (malformedLines > 0) return {
|
|
619
|
+
kind: "invalid-session-transcript",
|
|
620
|
+
sessionKey: params.sessionKey,
|
|
621
|
+
storePath: params.storePath,
|
|
622
|
+
path: params.transcriptPath,
|
|
623
|
+
reason: `${malformedLines} malformed JSONL line(s)`
|
|
624
|
+
};
|
|
625
|
+
if (maxBlankUserMessageRun >= BLANK_USER_MESSAGE_REPAIR_THRESHOLD) return {
|
|
626
|
+
kind: "blank-user-message-run",
|
|
627
|
+
sessionKey: params.sessionKey,
|
|
628
|
+
storePath: params.storePath,
|
|
629
|
+
path: params.transcriptPath,
|
|
630
|
+
count: maxBlankUserMessageRun
|
|
631
|
+
};
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
function collectFeishuSessionFindings(params) {
|
|
635
|
+
const transcriptCandidates = resolveSessionTranscriptCandidates(params);
|
|
636
|
+
const existing = transcriptCandidates.filter(existsFile);
|
|
637
|
+
if (transcriptCandidates.length > 0 && existing.length === 0) return [{
|
|
638
|
+
kind: "missing-session-transcript",
|
|
639
|
+
sessionKey: params.sessionKey,
|
|
640
|
+
storePath: params.storePath
|
|
641
|
+
}];
|
|
642
|
+
const findings = [];
|
|
643
|
+
for (const transcriptPath of existing) {
|
|
644
|
+
const finding = inspectSessionTranscript({
|
|
645
|
+
sessionKey: params.sessionKey,
|
|
646
|
+
storePath: params.storePath,
|
|
647
|
+
transcriptPath
|
|
648
|
+
});
|
|
649
|
+
if (finding) findings.push(finding);
|
|
650
|
+
}
|
|
651
|
+
return findings;
|
|
652
|
+
}
|
|
653
|
+
function hasCorruptFeishuStateJsonFinding(inspection) {
|
|
654
|
+
return inspection.findings.some((finding) => finding.kind === "corrupt-state-json");
|
|
655
|
+
}
|
|
656
|
+
function sessionEntryId(storePath, key) {
|
|
657
|
+
return `${path.resolve(storePath)}\0${key}`;
|
|
658
|
+
}
|
|
659
|
+
function collectRepairSessionEntries(inspection) {
|
|
660
|
+
const entriesById = /* @__PURE__ */ new Map();
|
|
661
|
+
for (const entry of inspection.sessionEntries) entriesById.set(sessionEntryId(entry.storePath, entry.key), entry);
|
|
662
|
+
const repairEntries = [];
|
|
663
|
+
const seen = /* @__PURE__ */ new Set();
|
|
664
|
+
for (const finding of inspection.findings) {
|
|
665
|
+
if (finding.kind === "corrupt-state-json") continue;
|
|
666
|
+
const id = sessionEntryId(finding.storePath, finding.sessionKey);
|
|
667
|
+
if (seen.has(id)) continue;
|
|
668
|
+
const entry = entriesById.get(id);
|
|
669
|
+
if (entry) {
|
|
670
|
+
repairEntries.push(entry);
|
|
671
|
+
seen.add(id);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
return repairEntries.toSorted((left, right) => left.storePath.localeCompare(right.storePath) || left.key.localeCompare(right.key));
|
|
675
|
+
}
|
|
676
|
+
function inspectFeishuDoctorState(params) {
|
|
677
|
+
const env = params.env ?? process.env;
|
|
678
|
+
const stateDir = resolveStateDir(env, os.homedir);
|
|
679
|
+
const feishuStateDir = path.join(stateDir, FEISHU_STATE_DIR);
|
|
680
|
+
const findings = collectCorruptFeishuStateJsonFindings(feishuStateDir);
|
|
681
|
+
const sessionEntries = [];
|
|
682
|
+
for (const target of collectFeishuSessionTargets({
|
|
683
|
+
cfg: params.cfg,
|
|
684
|
+
env,
|
|
685
|
+
stateDir
|
|
686
|
+
})) {
|
|
687
|
+
const store = loadSessionStore(target.storePath, { skipCache: true });
|
|
688
|
+
for (const [key, entry] of Object.entries(store).toSorted(([left], [right]) => left.localeCompare(right))) {
|
|
689
|
+
if (!isFeishuSessionEntry(key, entry)) continue;
|
|
690
|
+
const sessionEntry = toFeishuSessionEntry(entry);
|
|
691
|
+
sessionEntries.push({
|
|
692
|
+
key,
|
|
693
|
+
storePath: target.storePath,
|
|
694
|
+
agentId: target.agentId,
|
|
695
|
+
entry: sessionEntry
|
|
696
|
+
});
|
|
697
|
+
findings.push(...collectFeishuSessionFindings({
|
|
698
|
+
sessionKey: key,
|
|
699
|
+
storePath: target.storePath,
|
|
700
|
+
agentId: target.agentId,
|
|
701
|
+
entry: sessionEntry
|
|
702
|
+
}));
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return {
|
|
706
|
+
stateDir,
|
|
707
|
+
feishuStateDir,
|
|
708
|
+
findings,
|
|
709
|
+
sessionEntries
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function ensureBackupDir(stateDir, now) {
|
|
713
|
+
const backupDir = path.join(stateDir, "backups", `${BACKUP_PREFIX}-${timestampForPath(now)}`);
|
|
714
|
+
fs.mkdirSync(backupDir, {
|
|
715
|
+
recursive: true,
|
|
716
|
+
mode: 448
|
|
717
|
+
});
|
|
718
|
+
return backupDir;
|
|
719
|
+
}
|
|
720
|
+
function resolveUniquePath(candidate) {
|
|
721
|
+
if (!fs.existsSync(candidate)) return candidate;
|
|
722
|
+
for (let index = 1; index < 1e3; index += 1) {
|
|
723
|
+
const next = `${candidate}.${index}`;
|
|
724
|
+
if (!fs.existsSync(next)) return next;
|
|
725
|
+
}
|
|
726
|
+
throw new Error(`Unable to resolve unique path for ${candidate}`);
|
|
727
|
+
}
|
|
728
|
+
function movePathToBackup(params) {
|
|
729
|
+
if (!fs.existsSync(params.sourcePath)) return false;
|
|
730
|
+
const targetPath = resolveUniquePath(path.join(params.backupDir, params.relativeTarget));
|
|
731
|
+
fs.mkdirSync(path.dirname(targetPath), {
|
|
732
|
+
recursive: true,
|
|
733
|
+
mode: 448
|
|
734
|
+
});
|
|
735
|
+
fs.renameSync(params.sourcePath, targetPath);
|
|
736
|
+
return true;
|
|
737
|
+
}
|
|
738
|
+
function copyStoreBackup(params) {
|
|
739
|
+
if (!existsFile(params.storePath)) return;
|
|
740
|
+
const targetPath = path.join(params.backupDir, "session-stores", params.agentId, path.basename(params.storePath));
|
|
741
|
+
fs.mkdirSync(path.dirname(targetPath), {
|
|
742
|
+
recursive: true,
|
|
743
|
+
mode: 448
|
|
744
|
+
});
|
|
745
|
+
fs.copyFileSync(params.storePath, resolveUniquePath(targetPath));
|
|
746
|
+
}
|
|
747
|
+
function collectSessionArtifactPaths(params) {
|
|
748
|
+
const artifacts = /* @__PURE__ */ new Set();
|
|
749
|
+
for (const transcriptPath of resolveSessionTranscriptCandidates(params)) {
|
|
750
|
+
artifacts.add(transcriptPath);
|
|
751
|
+
if (transcriptPath.endsWith(".jsonl")) {
|
|
752
|
+
const base = transcriptPath.slice(0, -6);
|
|
753
|
+
artifacts.add(`${base}.trajectory.jsonl`);
|
|
754
|
+
artifacts.add(`${base}.trajectory-path.json`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return [...artifacts].toSorted();
|
|
758
|
+
}
|
|
759
|
+
function archiveSessionArtifacts(params) {
|
|
760
|
+
const seen = /* @__PURE__ */ new Set();
|
|
761
|
+
let archived = 0;
|
|
762
|
+
for (const entry of params.entries) for (const artifactPath of collectSessionArtifactPaths({
|
|
763
|
+
storePath: params.storePath,
|
|
764
|
+
agentId: entry.agentId,
|
|
765
|
+
entry: entry.entry
|
|
766
|
+
})) {
|
|
767
|
+
if (seen.has(artifactPath) || !existsFile(artifactPath)) continue;
|
|
768
|
+
seen.add(artifactPath);
|
|
769
|
+
const archivedPath = resolveUniquePath(`${artifactPath}.deleted.${params.archiveTimestamp}`);
|
|
770
|
+
fs.renameSync(artifactPath, archivedPath);
|
|
771
|
+
archived += 1;
|
|
772
|
+
}
|
|
773
|
+
return archived;
|
|
774
|
+
}
|
|
775
|
+
async function repairFeishuDoctorState(params) {
|
|
776
|
+
const env = params.env ?? process.env;
|
|
777
|
+
const now = params.now ?? /* @__PURE__ */ new Date();
|
|
778
|
+
const inspection = params.inspection ?? inspectFeishuDoctorState({
|
|
779
|
+
cfg: params.cfg,
|
|
780
|
+
env
|
|
781
|
+
});
|
|
782
|
+
const backupDir = ensureBackupDir(inspection.stateDir, now);
|
|
783
|
+
const archiveTimestamp = timestampForPath(now);
|
|
784
|
+
const warnings = [];
|
|
785
|
+
const stateDirRepairAttempted = hasCorruptFeishuStateJsonFinding(inspection);
|
|
786
|
+
let rebuiltStateDir = false;
|
|
787
|
+
if (stateDirRepairAttempted) try {
|
|
788
|
+
rebuiltStateDir = movePathToBackup({
|
|
789
|
+
sourcePath: inspection.feishuStateDir,
|
|
790
|
+
backupDir,
|
|
791
|
+
relativeTarget: FEISHU_STATE_DIR
|
|
792
|
+
});
|
|
793
|
+
fs.mkdirSync(inspection.feishuStateDir, {
|
|
794
|
+
recursive: true,
|
|
795
|
+
mode: 448
|
|
796
|
+
});
|
|
797
|
+
} catch (error) {
|
|
798
|
+
warnings.push(`- Failed to rebuild Feishu local state: ${String(error)}`);
|
|
799
|
+
}
|
|
800
|
+
const entriesByStore = /* @__PURE__ */ new Map();
|
|
801
|
+
for (const entry of collectRepairSessionEntries(inspection)) {
|
|
802
|
+
const existing = entriesByStore.get(entry.storePath);
|
|
803
|
+
if (existing) existing.entries.push({
|
|
804
|
+
key: entry.key,
|
|
805
|
+
entry: entry.entry
|
|
806
|
+
});
|
|
807
|
+
else entriesByStore.set(entry.storePath, {
|
|
808
|
+
agentId: entry.agentId,
|
|
809
|
+
entries: [{
|
|
810
|
+
key: entry.key,
|
|
811
|
+
entry: entry.entry
|
|
812
|
+
}]
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
let removedSessionEntries = 0;
|
|
816
|
+
let touchedSessionStores = 0;
|
|
817
|
+
let archivedSessionArtifacts = 0;
|
|
818
|
+
for (const [storePath, group] of [...entriesByStore.entries()].toSorted(([left], [right]) => left.localeCompare(right))) try {
|
|
819
|
+
copyStoreBackup({
|
|
820
|
+
storePath,
|
|
821
|
+
backupDir,
|
|
822
|
+
agentId: group.agentId
|
|
823
|
+
});
|
|
824
|
+
const keys = new Set(group.entries.map((entry) => entry.key));
|
|
825
|
+
const removedEntries = await updateSessionStore(storePath, (store) => {
|
|
826
|
+
const removed = [];
|
|
827
|
+
for (const key of keys) if (Object.prototype.hasOwnProperty.call(store, key)) {
|
|
828
|
+
delete store[key];
|
|
829
|
+
const entry = group.entries.find((candidate) => candidate.key === key);
|
|
830
|
+
if (entry) removed.push(entry);
|
|
831
|
+
}
|
|
832
|
+
return removed;
|
|
833
|
+
}, {
|
|
834
|
+
skipMaintenance: true,
|
|
835
|
+
allowDropAcpMetaSessionKeys: [...keys]
|
|
836
|
+
});
|
|
837
|
+
const removed = removedEntries.length;
|
|
838
|
+
removedSessionEntries += removed;
|
|
839
|
+
if (removed > 0) {
|
|
840
|
+
touchedSessionStores += 1;
|
|
841
|
+
archivedSessionArtifacts += archiveSessionArtifacts({
|
|
842
|
+
storePath,
|
|
843
|
+
entries: removedEntries.map((entry) => ({
|
|
844
|
+
agentId: group.agentId,
|
|
845
|
+
entry: entry.entry
|
|
846
|
+
})),
|
|
847
|
+
archiveTimestamp
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
} catch (error) {
|
|
851
|
+
warnings.push(`- Failed to archive Feishu sessions in ${formatDisplayPath(storePath)}: ${String(error)}`);
|
|
852
|
+
}
|
|
853
|
+
return {
|
|
854
|
+
backupDir,
|
|
855
|
+
stateDirRepairAttempted,
|
|
856
|
+
rebuiltStateDir,
|
|
857
|
+
removedSessionEntries,
|
|
858
|
+
touchedSessionStores,
|
|
859
|
+
archivedSessionArtifacts,
|
|
860
|
+
warnings
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
function formatPreviewWarning(inspection) {
|
|
864
|
+
const previewFindings = inspection.findings.slice(0, 5).map(formatFinding);
|
|
865
|
+
const remaining = inspection.findings.length - previewFindings.length;
|
|
866
|
+
const repairActions = [];
|
|
867
|
+
if (hasCorruptFeishuStateJsonFinding(inspection)) repairActions.push(`archive ${formatDisplayPath(inspection.feishuStateDir)}`);
|
|
868
|
+
const repairSessionEntries = collectRepairSessionEntries(inspection);
|
|
869
|
+
if (repairSessionEntries.length > 0) repairActions.push(`archive artifacts and remove ${countLabel(repairSessionEntries.length, "flagged Feishu-scoped session entry", "flagged Feishu-scoped session entries")}`);
|
|
870
|
+
const repairSummary = repairActions.length > 0 ? repairActions.join(" and ") : "apply targeted Feishu state cleanup";
|
|
871
|
+
return [
|
|
872
|
+
"- Feishu local channel state may need repair.",
|
|
873
|
+
...previewFindings,
|
|
874
|
+
...remaining > 0 ? [`- ...and ${remaining} more Feishu state finding(s).`] : [],
|
|
875
|
+
`- Repair will ${repairSummary}, while preserving Feishu App ID/secret config and healthy session entries.`,
|
|
876
|
+
"- Run \"openclaw doctor --fix\" to rebuild Feishu local state."
|
|
877
|
+
].join("\n");
|
|
878
|
+
}
|
|
879
|
+
function formatRepairChange(report) {
|
|
880
|
+
const stateRepairStatus = report.stateDirRepairAttempted ? report.rebuiltStateDir ? "yes" : "no existing state" : "not needed";
|
|
881
|
+
return [
|
|
882
|
+
"Feishu local state repaired.",
|
|
883
|
+
`- Backup dir: ${formatDisplayPath(report.backupDir)}`,
|
|
884
|
+
`- Rebuilt Feishu runtime state: ${stateRepairStatus}`,
|
|
885
|
+
`- Removed ${countLabel(report.removedSessionEntries, "Feishu-scoped session entry", "Feishu-scoped session entries")} from ${countLabel(report.touchedSessionStores, "session store")}.`,
|
|
886
|
+
`- Archived ${countLabel(report.archivedSessionArtifacts, "session artifact file")}.`,
|
|
887
|
+
"- Preserved Feishu App ID/secret config."
|
|
888
|
+
].join("\n");
|
|
889
|
+
}
|
|
890
|
+
function hasConfiguredFeishuChannel(cfg) {
|
|
891
|
+
return Boolean(cfg.channels?.feishu);
|
|
892
|
+
}
|
|
893
|
+
async function runFeishuDoctorSequence(params) {
|
|
894
|
+
if (!hasConfiguredFeishuChannel(params.cfg)) return {
|
|
895
|
+
changeNotes: [],
|
|
896
|
+
warningNotes: []
|
|
897
|
+
};
|
|
898
|
+
const inspection = inspectFeishuDoctorState({
|
|
899
|
+
cfg: params.cfg,
|
|
900
|
+
env: params.env
|
|
901
|
+
});
|
|
902
|
+
if (inspection.findings.length === 0) return {
|
|
903
|
+
changeNotes: [],
|
|
904
|
+
warningNotes: []
|
|
905
|
+
};
|
|
906
|
+
if (!params.shouldRepair) return {
|
|
907
|
+
changeNotes: [],
|
|
908
|
+
warningNotes: [formatPreviewWarning(inspection)]
|
|
909
|
+
};
|
|
910
|
+
const report = await repairFeishuDoctorState({
|
|
911
|
+
cfg: params.cfg,
|
|
912
|
+
env: params.env,
|
|
913
|
+
inspection
|
|
914
|
+
});
|
|
915
|
+
return {
|
|
916
|
+
changeNotes: [formatRepairChange(report)],
|
|
917
|
+
warningNotes: report.warnings
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
const feishuDoctor = { runConfigSequence: async ({ cfg, env, shouldRepair }) => await runFeishuDoctorSequence({
|
|
921
|
+
cfg,
|
|
922
|
+
env,
|
|
923
|
+
shouldRepair
|
|
924
|
+
}) };
|
|
925
|
+
//#endregion
|
|
463
926
|
//#region extensions/feishu/src/policy.ts
|
|
464
927
|
const FEISHU_PROVIDER_PREFIX_RE = /^(feishu|lark):/i;
|
|
465
928
|
const FEISHU_TYPED_PREFIX_RE = /^(chat|group|channel|user|dm|open_id):/i;
|
|
@@ -519,7 +982,7 @@ function createFeishuIngressSubject(params) {
|
|
|
519
982
|
function createFeishuIngressResolver(params) {
|
|
520
983
|
return createChannelIngressResolver({
|
|
521
984
|
channelId: "feishu",
|
|
522
|
-
accountId: normalizeAccountId(params.accountId) ?? "default",
|
|
985
|
+
accountId: normalizeAccountId$1(params.accountId) ?? "default",
|
|
523
986
|
identity: feishuIngressIdentity,
|
|
524
987
|
cfg: params.cfg,
|
|
525
988
|
...params.readAllowFromStore ? { readStoreAllowFrom: params.readAllowFromStore } : {}
|
|
@@ -625,8 +1088,8 @@ function resolveFeishuReplyPolicy(params) {
|
|
|
625
1088
|
const resolvedCfg = resolveMergedAccountConfig({
|
|
626
1089
|
channelConfig: feishuCfg,
|
|
627
1090
|
accounts: feishuCfg?.accounts,
|
|
628
|
-
accountId: normalizeAccountId(params.accountId),
|
|
629
|
-
normalizeAccountId,
|
|
1091
|
+
accountId: normalizeAccountId$1(params.accountId),
|
|
1092
|
+
normalizeAccountId: normalizeAccountId$1,
|
|
630
1093
|
omitKeys: ["defaultAccount"]
|
|
631
1094
|
});
|
|
632
1095
|
const groupRequireMention = resolveFeishuGroupConfig({
|
|
@@ -636,46 +1099,120 @@ function resolveFeishuReplyPolicy(params) {
|
|
|
636
1099
|
return { requireMention: typeof groupRequireMention === "boolean" ? groupRequireMention : typeof resolvedCfg.requireMention === "boolean" ? resolvedCfg.requireMention : params.groupPolicy !== "open" };
|
|
637
1100
|
}
|
|
638
1101
|
//#endregion
|
|
639
|
-
//#region extensions/feishu/src/
|
|
640
|
-
function
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
1102
|
+
//#region extensions/feishu/src/presentation-card.ts
|
|
1103
|
+
function escapeFeishuCardMarkdownText(text) {
|
|
1104
|
+
return text.replace(/[&<>]/g, (char) => {
|
|
1105
|
+
switch (char) {
|
|
1106
|
+
case "&": return "&";
|
|
1107
|
+
case "<": return "<";
|
|
1108
|
+
case ">": return ">";
|
|
1109
|
+
default: return char;
|
|
1110
|
+
}
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
function resolveSafeFeishuButtonUrl(url) {
|
|
1114
|
+
const trimmed = url?.trim();
|
|
1115
|
+
if (!trimmed) return;
|
|
1116
|
+
try {
|
|
1117
|
+
const parsed = new URL(trimmed);
|
|
1118
|
+
return parsed.protocol === "https:" || parsed.protocol === "http:" ? trimmed : void 0;
|
|
1119
|
+
} catch {
|
|
1120
|
+
return;
|
|
650
1121
|
}
|
|
651
1122
|
}
|
|
652
|
-
function
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
1123
|
+
function resolveFeishuButtonUrl(button) {
|
|
1124
|
+
return button.url ?? button.webApp?.url ?? button.web_app?.url;
|
|
1125
|
+
}
|
|
1126
|
+
function mapFeishuButtonType(style) {
|
|
1127
|
+
if (style === "primary" || style === "success") return "primary";
|
|
1128
|
+
if (style === "danger") return "danger";
|
|
1129
|
+
return "default";
|
|
1130
|
+
}
|
|
1131
|
+
function buildFeishuPayloadButton(button) {
|
|
1132
|
+
const behaviors = [];
|
|
1133
|
+
const rendered = {
|
|
1134
|
+
tag: "button",
|
|
1135
|
+
text: {
|
|
1136
|
+
tag: "plain_text",
|
|
1137
|
+
content: button.label
|
|
1138
|
+
},
|
|
1139
|
+
type: mapFeishuButtonType(button.style)
|
|
1140
|
+
};
|
|
1141
|
+
const url = resolveFeishuButtonUrl(button);
|
|
1142
|
+
if (url) {
|
|
1143
|
+
const safeUrl = resolveSafeFeishuButtonUrl(url);
|
|
1144
|
+
if (safeUrl) behaviors.push({
|
|
1145
|
+
type: "open_url",
|
|
1146
|
+
default_url: safeUrl
|
|
1147
|
+
});
|
|
1148
|
+
}
|
|
1149
|
+
if (button.value) behaviors.push({
|
|
1150
|
+
type: "callback",
|
|
1151
|
+
value: createFeishuCardInteractionEnvelope({
|
|
1152
|
+
k: "quick",
|
|
1153
|
+
a: "feishu.payload.button",
|
|
1154
|
+
q: button.value
|
|
1155
|
+
})
|
|
664
1156
|
});
|
|
1157
|
+
if (behaviors.length === 0) return;
|
|
1158
|
+
rendered.behaviors = behaviors;
|
|
1159
|
+
return rendered;
|
|
1160
|
+
}
|
|
1161
|
+
function buildFeishuCardElementsForBlock(block) {
|
|
1162
|
+
if (block.type === "text") return [{
|
|
1163
|
+
tag: "markdown",
|
|
1164
|
+
content: escapeFeishuCardMarkdownText(block.text)
|
|
1165
|
+
}];
|
|
1166
|
+
if (block.type === "context") return [{
|
|
1167
|
+
tag: "markdown",
|
|
1168
|
+
content: `<font color='grey'>${escapeFeishuCardMarkdownText(block.text)}</font>`
|
|
1169
|
+
}];
|
|
1170
|
+
if (block.type === "divider") return [{ tag: "hr" }];
|
|
1171
|
+
if (block.type === "buttons") return block.buttons.map((button) => buildFeishuPayloadButton(button)).filter((button) => Boolean(button));
|
|
1172
|
+
const labels = block.options.map((option) => `- ${option.label}`).join("\n");
|
|
1173
|
+
return [{
|
|
1174
|
+
tag: "markdown",
|
|
1175
|
+
content: `${escapeFeishuCardMarkdownText(block.placeholder?.trim() || "Options")}:\n${escapeFeishuCardMarkdownText(labels)}`
|
|
1176
|
+
}];
|
|
1177
|
+
}
|
|
1178
|
+
function resolvePresentationHeaderTemplate(tone) {
|
|
1179
|
+
if (tone === "danger") return "red";
|
|
1180
|
+
if (tone === "warning") return "orange";
|
|
1181
|
+
if (tone === "success") return "green";
|
|
1182
|
+
return "blue";
|
|
1183
|
+
}
|
|
1184
|
+
function buildFeishuPresentationCardElements(params) {
|
|
1185
|
+
const elements = [];
|
|
1186
|
+
const fallbackText = params.fallbackText?.trim();
|
|
1187
|
+
if (fallbackText) elements.push({
|
|
1188
|
+
tag: "markdown",
|
|
1189
|
+
content: escapeFeishuCardMarkdownText(fallbackText)
|
|
1190
|
+
});
|
|
1191
|
+
for (const block of params.presentation.blocks) for (const element of buildFeishuCardElementsForBlock(block)) elements.push(element);
|
|
1192
|
+
if (elements.length > 0) return elements;
|
|
1193
|
+
return [{
|
|
1194
|
+
tag: "markdown",
|
|
1195
|
+
content: renderMessagePresentationFallbackText({
|
|
1196
|
+
text: params.fallbackText,
|
|
1197
|
+
presentation: params.presentation.title ? {
|
|
1198
|
+
...params.presentation.tone ? { tone: params.presentation.tone } : {},
|
|
1199
|
+
blocks: params.presentation.blocks
|
|
1200
|
+
} : params.presentation
|
|
1201
|
+
})
|
|
1202
|
+
}];
|
|
665
1203
|
}
|
|
666
|
-
function
|
|
667
|
-
if (response.code !== 0) throw new Error(`${errorPrefix}: ${response.msg || `code ${response.code}`}`);
|
|
668
|
-
}
|
|
669
|
-
function toFeishuSendResult(response, chatId, kind) {
|
|
670
|
-
const messageId = response.data?.message_id ?? "unknown";
|
|
1204
|
+
function buildFeishuPresentationCard(params) {
|
|
671
1205
|
return {
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
1206
|
+
schema: "2.0",
|
|
1207
|
+
config: { width_mode: "fill" },
|
|
1208
|
+
...params.presentation.title ? { header: {
|
|
1209
|
+
title: {
|
|
1210
|
+
tag: "plain_text",
|
|
1211
|
+
content: params.presentation.title
|
|
1212
|
+
},
|
|
1213
|
+
template: resolvePresentationHeaderTemplate(params.presentation.tone)
|
|
1214
|
+
} } : {},
|
|
1215
|
+
body: { elements: buildFeishuPresentationCardElements(params) }
|
|
679
1216
|
};
|
|
680
1217
|
}
|
|
681
1218
|
//#endregion
|
|
@@ -756,19 +1293,15 @@ const t = createSetupTranslator();
|
|
|
756
1293
|
const channel = "feishu";
|
|
757
1294
|
const SCAN_TO_CREATE_TP = "ob_cli_app";
|
|
758
1295
|
const FEISHU_SETUP_FLOW_KEY = "_flow";
|
|
759
|
-
function normalizeString(value) {
|
|
760
|
-
if (typeof value !== "string") return;
|
|
761
|
-
return value.trim() || void 0;
|
|
762
|
-
}
|
|
763
1296
|
function isFeishuConfigured(cfg) {
|
|
764
1297
|
const feishuCfg = cfg.channels?.feishu;
|
|
765
1298
|
const isAppIdConfigured = (value) => {
|
|
766
|
-
if (
|
|
1299
|
+
if (normalizeOptionalString(value)) return true;
|
|
767
1300
|
if (!value || typeof value !== "object") return false;
|
|
768
1301
|
const rec = value;
|
|
769
|
-
const source =
|
|
770
|
-
const id =
|
|
771
|
-
if (source === "env" && id) return Boolean(
|
|
1302
|
+
const source = normalizeOptionalString(rec.source)?.toLowerCase();
|
|
1303
|
+
const id = normalizeOptionalString(rec.id);
|
|
1304
|
+
if (source === "env" && id) return Boolean(normalizeOptionalString(process.env[id]));
|
|
772
1305
|
return hasConfiguredSecretInput$1(value);
|
|
773
1306
|
};
|
|
774
1307
|
const topLevelConfigured = isAppIdConfigured(feishuCfg?.appId) && hasConfiguredSecretInput$1(feishuCfg?.appSecret);
|
|
@@ -984,7 +1517,7 @@ async function runNewAppFlow(params) {
|
|
|
984
1517
|
await noteFeishuCredentialHelp(prompter);
|
|
985
1518
|
appId = await promptFeishuAppId({
|
|
986
1519
|
prompter,
|
|
987
|
-
initialValue:
|
|
1520
|
+
initialValue: normalizeOptionalString(process.env.FEISHU_APP_ID)
|
|
988
1521
|
});
|
|
989
1522
|
const appSecretResult = await promptSingleChannelSecretInput({
|
|
990
1523
|
cfg: next,
|
|
@@ -1049,11 +1582,11 @@ async function runEditFlow(params) {
|
|
|
1049
1582
|
const next = params.cfg;
|
|
1050
1583
|
const feishuCfg = next.channels?.feishu;
|
|
1051
1584
|
const resolveAppIdLabel = (value) => {
|
|
1052
|
-
const asString =
|
|
1585
|
+
const asString = normalizeOptionalString(value);
|
|
1053
1586
|
if (asString) return asString;
|
|
1054
1587
|
if (value && typeof value === "object") {
|
|
1055
1588
|
const rec = value;
|
|
1056
|
-
if (
|
|
1589
|
+
if (normalizeOptionalString(rec.source) && normalizeOptionalString(rec.id)) return normalizeOptionalString(process.env[rec.id]) ?? `env:${String(rec.id)}`;
|
|
1057
1590
|
if (hasConfiguredSecretInput$1(value)) return "(configured)";
|
|
1058
1591
|
}
|
|
1059
1592
|
};
|
|
@@ -1171,12 +1704,13 @@ function readBooleanParam(params, keys) {
|
|
|
1171
1704
|
}
|
|
1172
1705
|
}
|
|
1173
1706
|
function hasLegacyFeishuCardCommandValue(actionValue) {
|
|
1174
|
-
return isRecord$
|
|
1707
|
+
return isRecord$2(actionValue) && actionValue.oc !== "ocf1" && (Boolean(typeof actionValue.command === "string" && actionValue.command.trim()) || Boolean(typeof actionValue.text === "string" && actionValue.text.trim()));
|
|
1175
1708
|
}
|
|
1176
1709
|
function containsLegacyFeishuCardCommandValue(node) {
|
|
1177
1710
|
if (Array.isArray(node)) return node.some((item) => containsLegacyFeishuCardCommandValue(item));
|
|
1178
|
-
if (!isRecord$
|
|
1711
|
+
if (!isRecord$2(node)) return false;
|
|
1179
1712
|
if (node.tag === "button" && hasLegacyFeishuCardCommandValue(node.value)) return true;
|
|
1713
|
+
if (node.tag === "button" && Array.isArray(node.behaviors) && node.behaviors.some((behavior) => isRecord$2(behavior) && hasLegacyFeishuCardCommandValue(behavior.value))) return true;
|
|
1180
1714
|
return Object.values(node).some((value) => containsLegacyFeishuCardCommandValue(value));
|
|
1181
1715
|
}
|
|
1182
1716
|
const meta = {
|
|
@@ -1189,7 +1723,7 @@ const meta = {
|
|
|
1189
1723
|
aliases: ["lark"],
|
|
1190
1724
|
order: 70
|
|
1191
1725
|
};
|
|
1192
|
-
const loadFeishuChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-
|
|
1726
|
+
const loadFeishuChannelRuntime = createLazyRuntimeNamedExport(() => import("./channel.runtime-ItBg9SfS.js"), "feishuChannelRuntime");
|
|
1193
1727
|
function toFeishuMessageSendResult(result, kind) {
|
|
1194
1728
|
const receipt = result.receipt ?? createFeishuSendReceipt({
|
|
1195
1729
|
messageId: result.messageId,
|
|
@@ -1220,30 +1754,6 @@ const feishuMessageAdapter = defineChannelMessageAdapter({
|
|
|
1220
1754
|
}
|
|
1221
1755
|
}
|
|
1222
1756
|
});
|
|
1223
|
-
function buildFeishuPresentationCard(params) {
|
|
1224
|
-
const fallbackPresentation = {
|
|
1225
|
-
...params.presentation.tone ? { tone: params.presentation.tone } : {},
|
|
1226
|
-
blocks: params.presentation.blocks
|
|
1227
|
-
};
|
|
1228
|
-
return {
|
|
1229
|
-
schema: "2.0",
|
|
1230
|
-
config: { width_mode: "fill" },
|
|
1231
|
-
...params.presentation.title ? { header: {
|
|
1232
|
-
title: {
|
|
1233
|
-
tag: "plain_text",
|
|
1234
|
-
content: params.presentation.title
|
|
1235
|
-
},
|
|
1236
|
-
template: "blue"
|
|
1237
|
-
} } : {},
|
|
1238
|
-
body: { elements: [{
|
|
1239
|
-
tag: "markdown",
|
|
1240
|
-
content: renderMessagePresentationFallbackText({
|
|
1241
|
-
text: params.fallbackText,
|
|
1242
|
-
presentation: fallbackPresentation
|
|
1243
|
-
})
|
|
1244
|
-
}] }
|
|
1245
|
-
};
|
|
1246
|
-
}
|
|
1247
1757
|
async function createFeishuActionClient(account) {
|
|
1248
1758
|
const { createFeishuClient } = await import("./client-BnH2fRL2.js").then((n) => n.t);
|
|
1249
1759
|
return createFeishuClient(account);
|
|
@@ -1544,6 +2054,7 @@ const feishuPlugin = createChatChannelPlugin({
|
|
|
1544
2054
|
},
|
|
1545
2055
|
mentions: { stripPatterns: () => ["<at user_id=\"[^\"]*\">[^<]*</at>"] },
|
|
1546
2056
|
reload: { configPrefixes: ["channels.feishu"] },
|
|
2057
|
+
doctor: feishuDoctor,
|
|
1547
2058
|
configSchema: buildChannelConfigSchema(FeishuConfigSchema),
|
|
1548
2059
|
config: {
|
|
1549
2060
|
...feishuConfigAdapter,
|
|
@@ -2032,7 +2543,7 @@ const feishuPlugin = createChatChannelPlugin({
|
|
|
2032
2543
|
})
|
|
2033
2544
|
}),
|
|
2034
2545
|
gateway: { startAccount: async (ctx) => {
|
|
2035
|
-
const { monitorFeishuProvider } = await import("./monitor-
|
|
2546
|
+
const { monitorFeishuProvider } = await import("./monitor-gFxvkDyO.js");
|
|
2036
2547
|
const account = resolveFeishuRuntimeAccount({
|
|
2037
2548
|
cfg: ctx.cfg,
|
|
2038
2549
|
accountId: ctx.accountId
|
|
@@ -2112,4 +2623,4 @@ const feishuPlugin = createChatChannelPlugin({
|
|
|
2112
2623
|
}
|
|
2113
2624
|
});
|
|
2114
2625
|
//#endregion
|
|
2115
|
-
export {
|
|
2626
|
+
export { setFeishuNamedAccountEnabled$1 as a, normalizeFeishuAllowEntry as c, resolveFeishuGroupConversationIngressAccess as d, resolveFeishuGroupSenderActivationIngressAccess as f, listFeishuDirectoryPeers as h, feishuSetupAdapter as i, resolveFeishuDmIngressAccess as l, listFeishuDirectoryGroups as m, feishuSetupWizard as n, buildFeishuPresentationCardElements as o, resolveFeishuReplyPolicy as p, runFeishuLogin as r, hasExplicitFeishuGroupConfig as s, feishuPlugin as t, resolveFeishuGroupConfig as u };
|