@trydying/opencode-feishu-notifier 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/chunk-DGUM43GV.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +550 -0
- package/dist/index.js.map +1 -0
- package/dist/templates-PQN4V7CV.js +564 -0
- package/dist/templates-PQN4V7CV.js.map +1 -0
- package/package.json +42 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
import "./chunk-DGUM43GV.js";
|
|
2
|
+
|
|
3
|
+
// src/config.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
7
|
+
var receiverTypes = ["user_id", "open_id", "chat_id"];
|
|
8
|
+
function getConfigPaths(options) {
|
|
9
|
+
if (options.configPath) {
|
|
10
|
+
return [options.configPath];
|
|
11
|
+
}
|
|
12
|
+
const paths = [];
|
|
13
|
+
const directory = options.directory ?? process.cwd();
|
|
14
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config");
|
|
15
|
+
paths.push(path.join(xdgConfig, "opencode", "feishu-notifier.json"));
|
|
16
|
+
paths.push(path.join(directory, ".opencode", "feishu-notifier.json"));
|
|
17
|
+
return paths;
|
|
18
|
+
}
|
|
19
|
+
function readConfigFile(filePath) {
|
|
20
|
+
if (!fs.existsSync(filePath)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const raw = fs.readFileSync(filePath, "utf8").trim();
|
|
24
|
+
if (!raw) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return JSON.parse(raw);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new Error(`Invalid JSON in ${filePath}: ${String(error)}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function readEnvConfig() {
|
|
34
|
+
return {
|
|
35
|
+
appId: process.env.FEISHU_APP_ID,
|
|
36
|
+
appSecret: process.env.FEISHU_APP_SECRET,
|
|
37
|
+
receiverType: process.env.FEISHU_RECEIVER_TYPE,
|
|
38
|
+
receiverId: process.env.FEISHU_RECEIVER_ID
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function resolveConfig(options) {
|
|
42
|
+
const configPaths = getConfigPaths(options);
|
|
43
|
+
let mergedConfig = {};
|
|
44
|
+
const sources = [];
|
|
45
|
+
for (const configPath of configPaths) {
|
|
46
|
+
const config = readConfigFile(configPath);
|
|
47
|
+
if (config) {
|
|
48
|
+
mergedConfig = {
|
|
49
|
+
...mergedConfig,
|
|
50
|
+
...config
|
|
51
|
+
};
|
|
52
|
+
sources.push({ type: "file", detail: configPath });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const envConfig = readEnvConfig();
|
|
56
|
+
if (envConfig.appId || envConfig.appSecret || envConfig.receiverType || envConfig.receiverId) {
|
|
57
|
+
mergedConfig = {
|
|
58
|
+
...mergedConfig,
|
|
59
|
+
...envConfig
|
|
60
|
+
};
|
|
61
|
+
sources.push({ type: "env", detail: "FEISHU_*" });
|
|
62
|
+
}
|
|
63
|
+
return { mergedConfig, sources };
|
|
64
|
+
}
|
|
65
|
+
function finalizeConfig(mergedConfig, sources) {
|
|
66
|
+
if (sources.length === 0) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"Missing Feishu configuration. Use FEISHU_* environment variables or create feishu-notifier.json."
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
const missing = [];
|
|
72
|
+
if (!mergedConfig.appId) missing.push("appId");
|
|
73
|
+
if (!mergedConfig.appSecret) missing.push("appSecret");
|
|
74
|
+
if (!mergedConfig.receiverType) missing.push("receiverType");
|
|
75
|
+
if (!mergedConfig.receiverId) missing.push("receiverId");
|
|
76
|
+
if (missing.length > 0) {
|
|
77
|
+
throw new Error(`Missing config fields: ${missing.join(", ")}`);
|
|
78
|
+
}
|
|
79
|
+
const receiverType = mergedConfig.receiverType;
|
|
80
|
+
if (!receiverTypes.includes(receiverType)) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
`Invalid receiverType: ${mergedConfig.receiverType}. Expected one of ${receiverTypes.join(", ")}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return {
|
|
86
|
+
appId: mergedConfig.appId,
|
|
87
|
+
appSecret: mergedConfig.appSecret,
|
|
88
|
+
receiverType,
|
|
89
|
+
receiverId: mergedConfig.receiverId
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function loadConfigWithSource(options = {}) {
|
|
93
|
+
const { mergedConfig, sources } = resolveConfig(options);
|
|
94
|
+
return {
|
|
95
|
+
config: finalizeConfig(mergedConfig, sources),
|
|
96
|
+
sources
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/feishu/client.ts
|
|
101
|
+
var tokenCache = /* @__PURE__ */ new Map();
|
|
102
|
+
function clearTokenCache(config) {
|
|
103
|
+
const cacheKey = `${config.appId}:${config.appSecret}`;
|
|
104
|
+
tokenCache.delete(cacheKey);
|
|
105
|
+
}
|
|
106
|
+
function isTokenExpiredError(code) {
|
|
107
|
+
return code === 99991663 || code === 99991664 || code === 99991668;
|
|
108
|
+
}
|
|
109
|
+
function ensureFetch() {
|
|
110
|
+
if (typeof fetch === "undefined") {
|
|
111
|
+
throw new Error("Global fetch is not available. Use Node.js 18+.");
|
|
112
|
+
}
|
|
113
|
+
return fetch;
|
|
114
|
+
}
|
|
115
|
+
async function readJson(response) {
|
|
116
|
+
const text = await response.text();
|
|
117
|
+
if (!text) {
|
|
118
|
+
throw new Error(`Empty response from Feishu API (${response.status}).`);
|
|
119
|
+
}
|
|
120
|
+
return JSON.parse(text);
|
|
121
|
+
}
|
|
122
|
+
async function getTenantAccessToken(config) {
|
|
123
|
+
const cacheKey = `${config.appId}:${config.appSecret}`;
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
const cached = tokenCache.get(cacheKey);
|
|
126
|
+
if (cached && cached.expiresAt > now + 6e4) {
|
|
127
|
+
return cached.token;
|
|
128
|
+
}
|
|
129
|
+
const fetchImpl = ensureFetch();
|
|
130
|
+
const response = await fetchImpl(
|
|
131
|
+
"https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal",
|
|
132
|
+
{
|
|
133
|
+
method: "POST",
|
|
134
|
+
headers: {
|
|
135
|
+
"Content-Type": "application/json"
|
|
136
|
+
},
|
|
137
|
+
body: JSON.stringify({
|
|
138
|
+
app_id: config.appId,
|
|
139
|
+
app_secret: config.appSecret
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
const errorText = await response.text().catch(() => `Failed to read error response`);
|
|
145
|
+
throw new Error(`Feishu auth request failed: ${response.status} - ${errorText}`);
|
|
146
|
+
}
|
|
147
|
+
const payload = await readJson(response);
|
|
148
|
+
if (payload.code !== 0 || !payload.tenant_access_token) {
|
|
149
|
+
throw new Error(`Feishu auth failed: ${payload.msg} (${payload.code})`);
|
|
150
|
+
}
|
|
151
|
+
const expiresIn = payload.expire ? payload.expire * 1e3 : 2 * 60 * 60 * 1e3;
|
|
152
|
+
const expiresAt = now + expiresIn - 6e4;
|
|
153
|
+
tokenCache.set(cacheKey, {
|
|
154
|
+
token: payload.tenant_access_token,
|
|
155
|
+
expiresAt
|
|
156
|
+
});
|
|
157
|
+
return payload.tenant_access_token;
|
|
158
|
+
}
|
|
159
|
+
async function sendMessage(config, msgType, content) {
|
|
160
|
+
const fetchImpl = ensureFetch();
|
|
161
|
+
const sendWithToken = async (retryOnTokenExpired = true) => {
|
|
162
|
+
const token = await getTenantAccessToken(config);
|
|
163
|
+
const response = await fetchImpl(
|
|
164
|
+
`https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=${config.receiverType}`,
|
|
165
|
+
{
|
|
166
|
+
method: "POST",
|
|
167
|
+
headers: {
|
|
168
|
+
Authorization: `Bearer ${token}`,
|
|
169
|
+
"Content-Type": "application/json"
|
|
170
|
+
},
|
|
171
|
+
body: JSON.stringify({
|
|
172
|
+
receive_id: config.receiverId,
|
|
173
|
+
msg_type: msgType,
|
|
174
|
+
content: JSON.stringify(content)
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
const errorText = await response.text().catch(() => `Failed to read error response`);
|
|
180
|
+
throw new Error(`Feishu message request failed: ${response.status} - ${errorText}`);
|
|
181
|
+
}
|
|
182
|
+
const payload = await readJson(response);
|
|
183
|
+
if (payload.code !== 0) {
|
|
184
|
+
if (retryOnTokenExpired && isTokenExpiredError(payload.code)) {
|
|
185
|
+
clearTokenCache(config);
|
|
186
|
+
return sendWithToken(false);
|
|
187
|
+
}
|
|
188
|
+
throw new Error(`Feishu message failed: ${payload.msg} (${payload.code}) - Response: ${JSON.stringify(payload)}`);
|
|
189
|
+
}
|
|
190
|
+
return payload;
|
|
191
|
+
};
|
|
192
|
+
return sendWithToken();
|
|
193
|
+
}
|
|
194
|
+
async function sendTextMessage(config, text) {
|
|
195
|
+
return sendMessage(config, "text", { text });
|
|
196
|
+
}
|
|
197
|
+
function textToPostContent(text, title = "OpenCode \u901A\u77E5") {
|
|
198
|
+
const cleanedText = text.split("\n").filter((line) => line.trim().length > 0).join("\n");
|
|
199
|
+
const content = [
|
|
200
|
+
[
|
|
201
|
+
{
|
|
202
|
+
tag: "text",
|
|
203
|
+
text: cleanedText,
|
|
204
|
+
un_escape: true
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
];
|
|
208
|
+
return {
|
|
209
|
+
post: {
|
|
210
|
+
zh_cn: {
|
|
211
|
+
title,
|
|
212
|
+
content
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async function sendRichTextMessage(config, text, title, richContent) {
|
|
218
|
+
const postContent = richContent || textToPostContent(text, title);
|
|
219
|
+
return sendMessage(config, "post", postContent);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/feishu/messages.ts
|
|
223
|
+
import os2 from "os";
|
|
224
|
+
var sessionTitleCache = /* @__PURE__ */ new Map();
|
|
225
|
+
var sessionAgentCache = /* @__PURE__ */ new Map();
|
|
226
|
+
function asRecord(value) {
|
|
227
|
+
if (value && typeof value === "object") {
|
|
228
|
+
return value;
|
|
229
|
+
}
|
|
230
|
+
return void 0;
|
|
231
|
+
}
|
|
232
|
+
function readString(value) {
|
|
233
|
+
return typeof value === "string" ? value : void 0;
|
|
234
|
+
}
|
|
235
|
+
function extractEventPayload(event) {
|
|
236
|
+
if (!event) {
|
|
237
|
+
return void 0;
|
|
238
|
+
}
|
|
239
|
+
if (event.payload !== void 0) {
|
|
240
|
+
return event.payload;
|
|
241
|
+
}
|
|
242
|
+
if (event.properties !== void 0) {
|
|
243
|
+
return event.properties;
|
|
244
|
+
}
|
|
245
|
+
return void 0;
|
|
246
|
+
}
|
|
247
|
+
function extractEventProperties(event) {
|
|
248
|
+
if (!event) {
|
|
249
|
+
return void 0;
|
|
250
|
+
}
|
|
251
|
+
if (event.properties) {
|
|
252
|
+
return asRecord(event.properties);
|
|
253
|
+
}
|
|
254
|
+
if (event.payload) {
|
|
255
|
+
return asRecord(event.payload);
|
|
256
|
+
}
|
|
257
|
+
return void 0;
|
|
258
|
+
}
|
|
259
|
+
function extractSessionContext(event) {
|
|
260
|
+
const properties = extractEventProperties(event);
|
|
261
|
+
const info = asRecord(properties?.info);
|
|
262
|
+
const part = asRecord(properties?.part);
|
|
263
|
+
const sessionID = readString(properties?.sessionID) ?? readString(info?.sessionID) ?? readString(info?.id) ?? readString(part?.sessionID);
|
|
264
|
+
const sessionTitle = readString(info?.title);
|
|
265
|
+
const agentName = readString(properties?.agent) ?? readString(info?.agent) ?? readString(part?.agent) ?? readString(part?.name);
|
|
266
|
+
return {
|
|
267
|
+
sessionID,
|
|
268
|
+
sessionTitle,
|
|
269
|
+
agentName
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
async function resolveSessionContext(event, client) {
|
|
273
|
+
const baseContext = extractSessionContext(event);
|
|
274
|
+
if (!baseContext.sessionID) {
|
|
275
|
+
return baseContext;
|
|
276
|
+
}
|
|
277
|
+
const cachedTitle = sessionTitleCache.get(baseContext.sessionID);
|
|
278
|
+
const cachedAgent = sessionAgentCache.get(baseContext.sessionID);
|
|
279
|
+
const mergedContext = {
|
|
280
|
+
...baseContext,
|
|
281
|
+
sessionTitle: baseContext.sessionTitle ?? cachedTitle,
|
|
282
|
+
agentName: baseContext.agentName ?? cachedAgent
|
|
283
|
+
};
|
|
284
|
+
if (mergedContext.sessionTitle) {
|
|
285
|
+
return mergedContext;
|
|
286
|
+
}
|
|
287
|
+
if (client?.session?.get) {
|
|
288
|
+
try {
|
|
289
|
+
const response = await client.session.get({
|
|
290
|
+
path: { id: baseContext.sessionID }
|
|
291
|
+
});
|
|
292
|
+
const title = response?.data?.title;
|
|
293
|
+
if (title) {
|
|
294
|
+
sessionTitleCache.set(baseContext.sessionID, title);
|
|
295
|
+
return {
|
|
296
|
+
...mergedContext,
|
|
297
|
+
sessionTitle: title
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return mergedContext;
|
|
304
|
+
}
|
|
305
|
+
function recordEventContext(event) {
|
|
306
|
+
const context = extractSessionContext(event);
|
|
307
|
+
if (!context.sessionID) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (context.sessionTitle) {
|
|
311
|
+
sessionTitleCache.set(context.sessionID, context.sessionTitle);
|
|
312
|
+
}
|
|
313
|
+
if (context.agentName) {
|
|
314
|
+
sessionAgentCache.set(context.sessionID, context.agentName);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
var titles = {
|
|
318
|
+
interaction_required: "\u9700\u8981\u4EA4\u4E92",
|
|
319
|
+
permission_required: "\u9700\u8981\u6743\u9650\u786E\u8BA4",
|
|
320
|
+
command_args_required: "\u9700\u8981\u8865\u5145\u53C2\u6570",
|
|
321
|
+
confirmation_required: "\u9700\u8981\u786E\u8BA4",
|
|
322
|
+
session_idle: "OpenCode \u95F2\u6687",
|
|
323
|
+
question_asked: "\u9700\u8981\u9009\u62E9\u65B9\u6848",
|
|
324
|
+
setup_test: "Feishu \u901A\u77E5\u6D4B\u8BD5"
|
|
325
|
+
};
|
|
326
|
+
async function buildStructuredNotification(type, event, directory, client) {
|
|
327
|
+
const { buildStructuredMessage } = await import("./templates-PQN4V7CV.js");
|
|
328
|
+
const eventPayload = extractEventPayload(event);
|
|
329
|
+
const sessionContext = await resolveSessionContext(event, client);
|
|
330
|
+
try {
|
|
331
|
+
const text = await buildStructuredMessage(
|
|
332
|
+
type,
|
|
333
|
+
eventPayload,
|
|
334
|
+
event?.type,
|
|
335
|
+
directory,
|
|
336
|
+
sessionContext
|
|
337
|
+
);
|
|
338
|
+
return {
|
|
339
|
+
title: titles[type],
|
|
340
|
+
text,
|
|
341
|
+
richContent: textToPostContent2(text, titles[type])
|
|
342
|
+
};
|
|
343
|
+
} catch (error) {
|
|
344
|
+
return buildLegacyNotification(type, event);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
function buildLegacyNotification(type, event) {
|
|
348
|
+
const title = titles[type];
|
|
349
|
+
if (type === "setup_test") {
|
|
350
|
+
const text2 = `${title}
|
|
351
|
+
Feishu \u901A\u77E5\u5DF2\u542F\u7528\u3002`;
|
|
352
|
+
return {
|
|
353
|
+
title,
|
|
354
|
+
text: text2,
|
|
355
|
+
richContent: textToPostContent2(text2, title)
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
const payloadText = formatPayload(extractEventPayload(event));
|
|
359
|
+
const sessionContext = extractSessionContext(event);
|
|
360
|
+
const lines = [
|
|
361
|
+
`[OpenCode] ${title}`,
|
|
362
|
+
event?.type ? `\u4E8B\u4EF6\u7C7B\u578B: ${event.type}` : "",
|
|
363
|
+
sessionContext.sessionTitle || sessionContext.sessionID ? `\u4F1A\u8BDD: ${sessionContext.sessionTitle ?? sessionContext.sessionID}` : "",
|
|
364
|
+
sessionContext.agentName ? `Agent: ${sessionContext.agentName}` : "",
|
|
365
|
+
`\u4E3B\u673A: ${os2.hostname()}`,
|
|
366
|
+
payloadText ? `\u8BE6\u60C5: ${payloadText}` : ""
|
|
367
|
+
].filter(Boolean);
|
|
368
|
+
const text = lines.join("\n");
|
|
369
|
+
return {
|
|
370
|
+
title,
|
|
371
|
+
text,
|
|
372
|
+
richContent: textToPostContent2(text, title)
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function formatPayload(payload) {
|
|
376
|
+
if (!payload) {
|
|
377
|
+
return "";
|
|
378
|
+
}
|
|
379
|
+
const text = JSON.stringify(payload, null, 2);
|
|
380
|
+
if (text.length > 1200) {
|
|
381
|
+
return `${text.slice(0, 1200)}\u2026`;
|
|
382
|
+
}
|
|
383
|
+
return text;
|
|
384
|
+
}
|
|
385
|
+
function textToPostContent2(text, title = "OpenCode \u901A\u77E5") {
|
|
386
|
+
const cleanedText = text.split("\n").filter((line) => line.trim().length > 0).join("\n");
|
|
387
|
+
const content = [
|
|
388
|
+
[
|
|
389
|
+
{
|
|
390
|
+
tag: "text",
|
|
391
|
+
text: cleanedText,
|
|
392
|
+
un_escape: true
|
|
393
|
+
}
|
|
394
|
+
]
|
|
395
|
+
];
|
|
396
|
+
return {
|
|
397
|
+
post: {
|
|
398
|
+
zh_cn: {
|
|
399
|
+
title,
|
|
400
|
+
content
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
async function buildNotification(type, event, directory, client) {
|
|
406
|
+
return buildStructuredNotification(type, event, directory, client);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// src/hooks.ts
|
|
410
|
+
function mapEventToNotification(eventType) {
|
|
411
|
+
switch (eventType) {
|
|
412
|
+
case "permission.asked":
|
|
413
|
+
return "permission_required";
|
|
414
|
+
case "question.asked":
|
|
415
|
+
return "question_asked";
|
|
416
|
+
default:
|
|
417
|
+
return null;
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
function mapCompletionEventToNotification(event) {
|
|
421
|
+
const status = event.properties?.status;
|
|
422
|
+
if (event.type === "session.status" && status?.type === "idle") {
|
|
423
|
+
return "session_idle";
|
|
424
|
+
}
|
|
425
|
+
if (event.type === "message.completed" || event.type === "message.failed" || event.type === "message.errored") {
|
|
426
|
+
return "session_idle";
|
|
427
|
+
}
|
|
428
|
+
return null;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/index.ts
|
|
432
|
+
var serviceName = "opencode-feishu-notifier";
|
|
433
|
+
var FeishuNotifierPlugin = async ({ client, directory }) => {
|
|
434
|
+
let configCache = null;
|
|
435
|
+
let configError = null;
|
|
436
|
+
const log = (level, message, extra) => {
|
|
437
|
+
const payload = {
|
|
438
|
+
body: {
|
|
439
|
+
service: serviceName,
|
|
440
|
+
level,
|
|
441
|
+
message,
|
|
442
|
+
extra
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
void client.app.log(payload).catch(() => void 0);
|
|
446
|
+
};
|
|
447
|
+
const logDebug = (message, extra) => {
|
|
448
|
+
log("debug", message, extra);
|
|
449
|
+
};
|
|
450
|
+
const logInfo = (message, extra) => {
|
|
451
|
+
log("info", message, extra);
|
|
452
|
+
};
|
|
453
|
+
const logError = (message, extra) => {
|
|
454
|
+
log("error", message, extra);
|
|
455
|
+
};
|
|
456
|
+
logInfo("Feishu notifier plugin loading", { directory });
|
|
457
|
+
const ensureConfig = () => {
|
|
458
|
+
if (configCache || configError) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
configCache = loadConfigWithSource({ directory });
|
|
463
|
+
logInfo("Feishu notifier plugin initialized", { sources: configCache.sources.map((s) => s.type) });
|
|
464
|
+
logDebug("Loaded Feishu config", { sources: configCache.sources });
|
|
465
|
+
} catch (error) {
|
|
466
|
+
configError = error instanceof Error ? error : new Error(String(error));
|
|
467
|
+
logError("Feishu config error", { error: configError.message });
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
ensureConfig();
|
|
471
|
+
return {
|
|
472
|
+
event: async ({ event }) => {
|
|
473
|
+
recordEventContext(event);
|
|
474
|
+
logDebug("Event received", { eventType: event.type });
|
|
475
|
+
let notificationType = mapEventToNotification(event.type);
|
|
476
|
+
notificationType = notificationType ?? mapCompletionEventToNotification(event);
|
|
477
|
+
if (!notificationType) {
|
|
478
|
+
logDebug("Event ignored", { eventType: event.type });
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
logDebug("Event mapped to notification", {
|
|
482
|
+
eventType: event.type,
|
|
483
|
+
notificationType
|
|
484
|
+
});
|
|
485
|
+
ensureConfig();
|
|
486
|
+
if (configError) {
|
|
487
|
+
logError("Feishu config error (cached)", { error: configError.message });
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (!configCache) {
|
|
491
|
+
logError("Feishu config not loaded");
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
const { text, title, richContent } = await buildNotification(
|
|
495
|
+
notificationType,
|
|
496
|
+
event,
|
|
497
|
+
directory,
|
|
498
|
+
{ session: client.session }
|
|
499
|
+
);
|
|
500
|
+
logDebug("Sending Feishu notification", {
|
|
501
|
+
eventType: event.type,
|
|
502
|
+
notificationType,
|
|
503
|
+
directory,
|
|
504
|
+
hasRichContent: !!richContent
|
|
505
|
+
});
|
|
506
|
+
try {
|
|
507
|
+
let response;
|
|
508
|
+
if (richContent) {
|
|
509
|
+
try {
|
|
510
|
+
logDebug("Attempting to send rich text message", {
|
|
511
|
+
richContentType: typeof richContent,
|
|
512
|
+
hasPost: !!richContent.post,
|
|
513
|
+
hasZhCn: !!richContent.post?.zh_cn,
|
|
514
|
+
titleLength: richContent.post?.zh_cn?.title?.length ?? 0,
|
|
515
|
+
contentLength: richContent.post?.zh_cn?.content?.length ?? 0
|
|
516
|
+
});
|
|
517
|
+
response = await sendRichTextMessage(configCache.config, text, title, richContent);
|
|
518
|
+
logDebug("Feishu rich notification sent", {
|
|
519
|
+
messageId: response.data?.message_id ?? null
|
|
520
|
+
});
|
|
521
|
+
} catch (richError) {
|
|
522
|
+
logDebug("Rich text message failed, falling back to text", {
|
|
523
|
+
error: richError instanceof Error ? richError.message : String(richError),
|
|
524
|
+
stack: richError instanceof Error ? richError.stack : void 0,
|
|
525
|
+
name: richError instanceof Error ? richError.name : void 0
|
|
526
|
+
});
|
|
527
|
+
response = await sendTextMessage(configCache.config, text);
|
|
528
|
+
logDebug("Feishu text notification sent (fallback)", {
|
|
529
|
+
messageId: response.data?.message_id ?? null
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
} else {
|
|
533
|
+
response = await sendTextMessage(configCache.config, text);
|
|
534
|
+
logDebug("Feishu text notification sent", {
|
|
535
|
+
messageId: response.data?.message_id ?? null
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
} catch (error) {
|
|
539
|
+
logError("Failed to send Feishu notification", {
|
|
540
|
+
error: String(error)
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
};
|
|
546
|
+
var index_default = FeishuNotifierPlugin;
|
|
547
|
+
export {
|
|
548
|
+
index_default as default
|
|
549
|
+
};
|
|
550
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts","../src/feishu/client.ts","../src/feishu/messages.ts","../src/hooks.ts","../src/index.ts"],"sourcesContent":["import fs from \"fs\"\nimport os from \"os\"\nimport path from \"path\"\n\nexport type ReceiverType = \"user_id\" | \"open_id\" | \"chat_id\"\n\nexport interface FeishuConfig {\n appId: string\n appSecret: string\n receiverType: ReceiverType\n receiverId: string\n}\n\nexport interface LoadConfigOptions {\n directory?: string\n configPath?: string\n}\n\nexport type ConfigSource =\n | { type: \"file\"; detail: string }\n | { type: \"env\"; detail: string }\n\nconst receiverTypes: ReceiverType[] = [\"user_id\", \"open_id\", \"chat_id\"]\n\nfunction getConfigPaths(options: LoadConfigOptions): string[] {\n if (options.configPath) {\n return [options.configPath]\n }\n\n const paths: string[] = []\n const directory = options.directory ?? process.cwd()\n const xdgConfig = process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), \".config\")\n\n paths.push(path.join(xdgConfig, \"opencode\", \"feishu-notifier.json\"))\n paths.push(path.join(directory, \".opencode\", \"feishu-notifier.json\"))\n\n return paths\n}\n\nfunction readConfigFile(filePath: string): Partial<FeishuConfig> | null {\n if (!fs.existsSync(filePath)) {\n return null\n }\n\n const raw = fs.readFileSync(filePath, \"utf8\").trim()\n if (!raw) {\n return null\n }\n\n try {\n return JSON.parse(raw) as Partial<FeishuConfig>\n } catch (error) {\n throw new Error(`Invalid JSON in ${filePath}: ${String(error)}`)\n }\n}\n\nfunction readEnvConfig(): Partial<FeishuConfig> {\n return {\n appId: process.env.FEISHU_APP_ID,\n appSecret: process.env.FEISHU_APP_SECRET,\n receiverType: process.env.FEISHU_RECEIVER_TYPE as ReceiverType | undefined,\n receiverId: process.env.FEISHU_RECEIVER_ID\n }\n}\n\nfunction resolveConfig(options: LoadConfigOptions): {\n mergedConfig: Partial<FeishuConfig>\n sources: ConfigSource[]\n} {\n const configPaths = getConfigPaths(options)\n let mergedConfig: Partial<FeishuConfig> = {}\n const sources: ConfigSource[] = []\n\n for (const configPath of configPaths) {\n const config = readConfigFile(configPath)\n if (config) {\n mergedConfig = {\n ...mergedConfig,\n ...config\n }\n sources.push({ type: \"file\", detail: configPath })\n }\n }\n\n const envConfig = readEnvConfig()\n if (envConfig.appId || envConfig.appSecret || envConfig.receiverType || envConfig.receiverId) {\n mergedConfig = {\n ...mergedConfig,\n ...envConfig\n }\n sources.push({ type: \"env\", detail: \"FEISHU_*\" })\n }\n\n return { mergedConfig, sources }\n}\n\nfunction finalizeConfig(mergedConfig: Partial<FeishuConfig>, sources: ConfigSource[]): FeishuConfig {\n if (sources.length === 0) {\n throw new Error(\n \"Missing Feishu configuration. Use FEISHU_* environment variables or create feishu-notifier.json.\"\n )\n }\n\n const missing: string[] = []\n if (!mergedConfig.appId) missing.push(\"appId\")\n if (!mergedConfig.appSecret) missing.push(\"appSecret\")\n if (!mergedConfig.receiverType) missing.push(\"receiverType\")\n if (!mergedConfig.receiverId) missing.push(\"receiverId\")\n\n if (missing.length > 0) {\n throw new Error(`Missing config fields: ${missing.join(\", \")}`)\n }\n\n const receiverType = mergedConfig.receiverType as ReceiverType\n if (!receiverTypes.includes(receiverType)) {\n throw new Error(\n `Invalid receiverType: ${mergedConfig.receiverType}. Expected one of ${receiverTypes.join(\", \")}`\n )\n }\n\n return {\n appId: mergedConfig.appId!,\n appSecret: mergedConfig.appSecret!,\n receiverType,\n receiverId: mergedConfig.receiverId!\n }\n}\n\nexport function loadConfig(options: LoadConfigOptions = {}): FeishuConfig {\n return loadConfigWithSource(options).config\n}\n\nexport function loadConfigWithSource(\n options: LoadConfigOptions = {}\n): { config: FeishuConfig; sources: ConfigSource[] } {\n const { mergedConfig, sources } = resolveConfig(options)\n return {\n config: finalizeConfig(mergedConfig, sources),\n sources\n }\n}\n","import type { FeishuConfig } from \"../config\"\n\ntype TenantTokenResponse = {\n code: number\n msg: string\n tenant_access_token?: string\n expire?: number\n}\n\ninterface TokenCacheEntry {\n token: string\n expiresAt: number // timestamp in milliseconds\n}\n\nconst tokenCache = new Map<string, TokenCacheEntry>()\n\nfunction clearTokenCache(config: FeishuConfig) {\n const cacheKey = `${config.appId}:${config.appSecret}`\n tokenCache.delete(cacheKey)\n}\n\nfunction isTokenExpiredError(code: number): boolean {\n // Common Feishu token expiration error codes\n return code === 99991663 || code === 99991664 || code === 99991668\n}\n\ntype MessageResponse = {\n code: number\n msg: string\n data?: {\n message_id?: string\n }\n}\n\ntype FeishuPostContent = {\n post: {\n zh_cn: {\n title: string\n content: Array<Array<{\n tag: string\n text?: string\n href?: string\n un_escape?: boolean\n }>>\n }\n }\n}\n\nfunction ensureFetch(): typeof fetch {\n if (typeof fetch === \"undefined\") {\n throw new Error(\"Global fetch is not available. Use Node.js 18+.\")\n }\n\n return fetch\n}\n\nasync function readJson<T>(response: Response): Promise<T> {\n const text = await response.text()\n if (!text) {\n throw new Error(`Empty response from Feishu API (${response.status}).`)\n }\n\n return JSON.parse(text) as T\n}\n\nexport async function getTenantAccessToken(config: FeishuConfig): Promise<string> {\n const cacheKey = `${config.appId}:${config.appSecret}`\n const now = Date.now()\n \n // Check cache\n const cached = tokenCache.get(cacheKey)\n if (cached && cached.expiresAt > now + 60000) { // 60 second buffer\n return cached.token\n }\n \n const fetchImpl = ensureFetch()\n const response = await fetchImpl(\n \"https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal\",\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({\n app_id: config.appId,\n app_secret: config.appSecret\n })\n }\n )\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => `Failed to read error response`)\n throw new Error(`Feishu auth request failed: ${response.status} - ${errorText}`)\n }\n\n const payload = await readJson<TenantTokenResponse>(response)\n if (payload.code !== 0 || !payload.tenant_access_token) {\n throw new Error(`Feishu auth failed: ${payload.msg} (${payload.code})`)\n }\n\n // Cache token with expiration (default 2 hours if not provided)\n const expiresIn = payload.expire ? payload.expire * 1000 : 2 * 60 * 60 * 1000\n const expiresAt = now + expiresIn - 60000 // 60 second buffer\n \n tokenCache.set(cacheKey, {\n token: payload.tenant_access_token,\n expiresAt\n })\n \n return payload.tenant_access_token\n}\n\nasync function sendMessage(\n config: FeishuConfig,\n msgType: \"text\" | \"post\",\n content: unknown\n): Promise<MessageResponse> {\n const fetchImpl = ensureFetch()\n \n const sendWithToken = async (retryOnTokenExpired = true): Promise<MessageResponse> => {\n const token = await getTenantAccessToken(config)\n const response = await fetchImpl(\n `https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=${config.receiverType}`,\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\"\n },\n body: JSON.stringify({\n receive_id: config.receiverId,\n msg_type: msgType,\n content: JSON.stringify(content)\n })\n }\n )\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => `Failed to read error response`)\n throw new Error(`Feishu message request failed: ${response.status} - ${errorText}`)\n }\n\n const payload = await readJson<MessageResponse>(response)\n if (payload.code !== 0) {\n // Check if token expired\n if (retryOnTokenExpired && isTokenExpiredError(payload.code)) {\n clearTokenCache(config)\n return sendWithToken(false) // Retry once\n }\n throw new Error(`Feishu message failed: ${payload.msg} (${payload.code}) - Response: ${JSON.stringify(payload)}`)\n }\n\n return payload\n }\n \n return sendWithToken()\n}\n\nexport async function sendTextMessage(\n config: FeishuConfig,\n text: string\n): Promise<MessageResponse> {\n return sendMessage(config, \"text\", { text })\n}\n\n/**\n * 将纯文本转换为飞书富文本(post)格式\n * 简化实现:所有文本作为一个段落\n */\nfunction textToPostContent(text: string, title: string = \"OpenCode 通知\"): FeishuPostContent {\n // 移除空行,但保留换行符\n const cleanedText = text.split('\\n').filter(line => line.trim().length > 0).join('\\n')\n \n const content = [\n [\n {\n tag: 'text',\n text: cleanedText,\n un_escape: true\n }\n ]\n ]\n \n return {\n post: {\n zh_cn: {\n title,\n content\n }\n }\n }\n}\n\nexport async function sendRichTextMessage(\n config: FeishuConfig,\n text: string,\n title?: string,\n richContent?: FeishuPostContent\n): Promise<MessageResponse> {\n const postContent = richContent || textToPostContent(text, title)\n return sendMessage(config, \"post\", postContent)\n}\n","import os from \"os\";\n\nexport type FeishuPostContent = {\n post: {\n zh_cn: {\n title: string\n content: Array<Array<{\n tag: string\n text?: string\n href?: string\n un_escape?: boolean\n }>>\n }\n }\n}\n\nexport type NotificationResult = {\n title: string\n text: string\n richContent?: FeishuPostContent\n}\n\nexport type NotificationType =\n | \"interaction_required\"\n | \"permission_required\"\n | \"command_args_required\"\n | \"confirmation_required\"\n | \"session_idle\"\n | \"question_asked\"\n | \"setup_test\"\n\ntype EventPayload = {\n type?: string;\n payload?: unknown;\n properties?: Record<string, unknown>;\n}\n\ntype SessionContext = {\n sessionID?: string;\n sessionTitle?: string;\n agentName?: string;\n};\n\ntype SessionClient = {\n session?: {\n get?: (options: { path: { id: string } }) => Promise<{\n data?: {\n title?: string;\n };\n }>;\n };\n};\n\nconst sessionTitleCache = new Map<string, string>();\nconst sessionAgentCache = new Map<string, string>();\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n if (value && typeof value === \"object\") {\n return value as Record<string, unknown>;\n }\n return undefined;\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction extractEventPayload(event?: EventPayload): unknown {\n if (!event) {\n return undefined;\n }\n if (event.payload !== undefined) {\n return event.payload;\n }\n if (event.properties !== undefined) {\n return event.properties;\n }\n return undefined;\n}\n\nfunction extractEventProperties(event?: EventPayload): Record<string, unknown> | undefined {\n if (!event) {\n return undefined;\n }\n if (event.properties) {\n return asRecord(event.properties);\n }\n if (event.payload) {\n return asRecord(event.payload);\n }\n return undefined;\n}\n\nfunction extractSessionContext(event?: EventPayload): SessionContext {\n const properties = extractEventProperties(event);\n const info = asRecord(properties?.info);\n const part = asRecord(properties?.part);\n\n const sessionID =\n readString(properties?.sessionID) ??\n readString(info?.sessionID) ??\n readString(info?.id) ??\n readString(part?.sessionID);\n\n const sessionTitle = readString(info?.title);\n\n const agentName =\n readString(properties?.agent) ??\n readString(info?.agent) ??\n readString(part?.agent) ??\n readString(part?.name);\n\n return {\n sessionID,\n sessionTitle,\n agentName,\n };\n}\n\nasync function resolveSessionContext(\n event?: EventPayload,\n client?: SessionClient\n): Promise<SessionContext> {\n const baseContext = extractSessionContext(event);\n if (!baseContext.sessionID) {\n return baseContext;\n }\n\n const cachedTitle = sessionTitleCache.get(baseContext.sessionID);\n const cachedAgent = sessionAgentCache.get(baseContext.sessionID);\n const mergedContext = {\n ...baseContext,\n sessionTitle: baseContext.sessionTitle ?? cachedTitle,\n agentName: baseContext.agentName ?? cachedAgent,\n };\n\n if (mergedContext.sessionTitle) {\n return mergedContext;\n }\n\n if (client?.session?.get) {\n try {\n const response = await client.session.get({\n path: { id: baseContext.sessionID },\n });\n const title = response?.data?.title;\n if (title) {\n sessionTitleCache.set(baseContext.sessionID, title);\n return {\n ...mergedContext,\n sessionTitle: title,\n };\n }\n } catch {\n // 忽略会话信息获取失败\n }\n }\n\n return mergedContext;\n}\n\nexport function recordEventContext(event?: EventPayload): void {\n const context = extractSessionContext(event);\n if (!context.sessionID) {\n return;\n }\n\n if (context.sessionTitle) {\n sessionTitleCache.set(context.sessionID, context.sessionTitle);\n }\n\n if (context.agentName) {\n sessionAgentCache.set(context.sessionID, context.agentName);\n }\n}\n\n// 保持向后兼容的标题映射\nconst titles: Record<NotificationType, string> = {\n interaction_required: \"需要交互\",\n permission_required: \"需要权限确认\",\n command_args_required: \"需要补充参数\",\n confirmation_required: \"需要确认\",\n session_idle: \"OpenCode 闲暇\",\n question_asked: \"需要选择方案\",\n setup_test: \"Feishu 通知测试\"\n}\n\n/**\n * 构建结构化通知消息(新版本)\n * @param type 通知类型\n * @param event 事件数据\n * @param directory 工作目录(可选,默认当前目录)\n * @returns 包含标题和文本的消息对象\n */\nexport async function buildStructuredNotification(\n type: NotificationType,\n event?: EventPayload,\n directory?: string,\n client?: SessionClient\n): Promise<NotificationResult> {\n // 导入模板系统\n const { buildStructuredMessage } = await import(\"./templates\")\n\n const eventPayload = extractEventPayload(event);\n const sessionContext = await resolveSessionContext(event, client);\n \n try {\n const text = await buildStructuredMessage(\n type,\n eventPayload,\n event?.type,\n directory,\n sessionContext\n )\n \n // 返回标题(保持向后兼容)\n return {\n title: titles[type],\n text,\n richContent: textToPostContent(text, titles[type])\n }\n } catch (error) {\n // 如果模板系统失败,回退到原始实现\n return buildLegacyNotification(type, event)\n }\n}\n\n/**\n * 构建传统格式的通知消息(向后兼容)\n */\nexport function buildLegacyNotification(\n type: NotificationType,\n event?: EventPayload\n): NotificationResult {\n const title = titles[type]\n if (type === \"setup_test\") {\n const text = `${title}\\nFeishu 通知已启用。`\n return {\n title,\n text,\n richContent: textToPostContent(text, title)\n }\n }\n\n const payloadText = formatPayload(extractEventPayload(event))\n const sessionContext = extractSessionContext(event)\n const lines = [\n `[OpenCode] ${title}`,\n event?.type ? `事件类型: ${event.type}` : \"\",\n sessionContext.sessionTitle || sessionContext.sessionID\n ? `会话: ${sessionContext.sessionTitle ?? sessionContext.sessionID}`\n : \"\",\n sessionContext.agentName ? `Agent: ${sessionContext.agentName}` : \"\",\n `主机: ${os.hostname()}`,\n payloadText ? `详情: ${payloadText}` : \"\"\n ].filter(Boolean)\n\n const text = lines.join(\"\\n\")\n return {\n title,\n text,\n richContent: textToPostContent(text, title)\n }\n}\n\n/**\n * 格式化负载文本\n */\nfunction formatPayload(payload: unknown): string {\n if (!payload) {\n return \"\"\n }\n\n const text = JSON.stringify(payload, null, 2)\n if (text.length > 1200) {\n return `${text.slice(0, 1200)}…`\n }\n\n return text\n}\n\n/**\n * 将纯文本转换为飞书富文本(post)格式\n * 简化实现:所有文本作为一个段落\n */\nfunction textToPostContent(text: string, title: string = \"OpenCode 通知\"): FeishuPostContent {\n // 移除空行,但保留换行符\n const cleanedText = text.split('\\n').filter(line => line.trim().length > 0).join('\\n')\n \n const content = [\n [\n {\n tag: 'text',\n text: cleanedText,\n un_escape: true\n }\n ]\n ]\n \n return {\n post: {\n zh_cn: {\n title,\n content\n }\n }\n }\n}\n\n/**\n * 构建通知消息(主入口,保持向后兼容)\n * 默认使用结构化消息,失败时回退\n */\nexport async function buildNotification(\n type: NotificationType,\n event?: EventPayload,\n directory?: string,\n client?: SessionClient\n): Promise<NotificationResult> {\n // 默认使用结构化消息\n return buildStructuredNotification(type, event, directory, client)\n}\n","import type { NotificationType } from \"./feishu/messages\"\n\nexport function mapEventToNotification(eventType: string): NotificationType | null {\n switch (eventType) {\n case \"permission.asked\":\n return \"permission_required\"\n case \"question.asked\":\n return \"question_asked\"\n default:\n return null\n }\n}\n\ntype CompletionCandidateEvent = {\n type: string\n properties?: Record<string, unknown>\n}\n\n/**\n * 消息完成事件(包含错误后终止)统一映射为完成通知。\n */\nexport function mapCompletionEventToNotification(\n event: CompletionCandidateEvent\n): NotificationType | null {\n const status = event.properties?.status as { type?: string } | undefined\n if (event.type === \"session.status\" && status?.type === \"idle\") {\n return \"session_idle\"\n }\n\n // 兼容不同版本 OpenCode 可能出现的完成/终止事件名\n if (\n event.type === \"message.completed\" ||\n event.type === \"message.failed\" ||\n event.type === \"message.errored\"\n ) {\n return \"session_idle\"\n }\n\n return null\n}\n","import type { Plugin } from \"@opencode-ai/plugin\";\nimport { loadConfigWithSource } from \"./config\";\nimport { sendTextMessage, sendRichTextMessage } from \"./feishu/client\";\nimport { buildNotification, recordEventContext } from \"./feishu/messages\";\nimport { mapCompletionEventToNotification, mapEventToNotification } from \"./hooks\";\n\nconst serviceName = \"opencode-feishu-notifier\";\n\nconst FeishuNotifierPlugin: Plugin = async ({ client, directory }) => {\n let configCache: ReturnType<typeof loadConfigWithSource> | null = null;\n let configError: Error | null = null;\n\n const log = (\n level: \"debug\" | \"info\" | \"warn\" | \"error\",\n message: string,\n extra?: Record<string, unknown>\n ) => {\n const payload = {\n body: {\n service: serviceName,\n level,\n message,\n extra,\n },\n };\n void client.app.log(payload).catch(() => undefined);\n };\n\n const logDebug = (message: string, extra?: Record<string, unknown>) => {\n log(\"debug\", message, extra);\n };\n\n const logInfo = (message: string, extra?: Record<string, unknown>) => {\n log(\"info\", message, extra);\n };\n\n const logError = (message: string, extra?: Record<string, unknown>) => {\n log(\"error\", message, extra);\n };\n\n logInfo(\"Feishu notifier plugin loading\", { directory });\n\n const ensureConfig = () => {\n if (configCache || configError) {\n return;\n }\n\n try {\n configCache = loadConfigWithSource({ directory });\n logInfo(\"Feishu notifier plugin initialized\", { sources: configCache.sources.map(s => s.type) });\n logDebug(\"Loaded Feishu config\", { sources: configCache.sources });\n } catch (error) {\n configError = error instanceof Error ? error : new Error(String(error));\n logError(\"Feishu config error\", { error: configError.message });\n }\n };\n\n ensureConfig();\n\n return {\n event: async ({ event }) => {\n recordEventContext(event);\n logDebug(\"Event received\", { eventType: event.type });\n\n let notificationType = mapEventToNotification(event.type);\n notificationType = notificationType ?? mapCompletionEventToNotification(event);\n\n if (!notificationType) {\n logDebug(\"Event ignored\", { eventType: event.type });\n return;\n }\n logDebug(\"Event mapped to notification\", {\n eventType: event.type,\n notificationType,\n });\n\n ensureConfig();\n if (configError) {\n logError(\"Feishu config error (cached)\", { error: configError.message });\n return;\n }\n if (!configCache) {\n logError(\"Feishu config not loaded\");\n return;\n }\n\n const { text, title, richContent } = await buildNotification(\n notificationType,\n event,\n directory,\n { session: client.session }\n );\n logDebug(\"Sending Feishu notification\", {\n eventType: event.type,\n notificationType,\n directory,\n hasRichContent: !!richContent,\n });\n\n try {\n let response;\n if (richContent) {\n // 尝试发送富文本消息\n try {\n logDebug(\"Attempting to send rich text message\", {\n richContentType: typeof richContent,\n hasPost: !!richContent.post,\n hasZhCn: !!(richContent.post?.zh_cn),\n titleLength: richContent.post?.zh_cn?.title?.length ?? 0,\n contentLength: richContent.post?.zh_cn?.content?.length ?? 0,\n });\n response = await sendRichTextMessage(configCache.config, text, title, richContent);\n logDebug(\"Feishu rich notification sent\", {\n messageId: response.data?.message_id ?? null,\n });\n } catch (richError) {\n // 富文本消息失败,回退到纯文本\n logDebug(\"Rich text message failed, falling back to text\", {\n error: richError instanceof Error ? richError.message : String(richError),\n stack: richError instanceof Error ? richError.stack : undefined,\n name: richError instanceof Error ? richError.name : undefined,\n });\n response = await sendTextMessage(configCache.config, text);\n logDebug(\"Feishu text notification sent (fallback)\", {\n messageId: response.data?.message_id ?? null,\n });\n }\n } else {\n // 回退到纯文本\n response = await sendTextMessage(configCache.config, text);\n logDebug(\"Feishu text notification sent\", {\n messageId: response.data?.message_id ?? null,\n });\n }\n } catch (error) {\n logError(\"Failed to send Feishu notification\", {\n error: String(error),\n });\n }\n },\n };\n};\n\nexport default FeishuNotifierPlugin;\n"],"mappings":"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","names":["os","textToPostContent","text"]}
|