eve-lark 0.2.8 → 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 +6 -0
- package/README.zh-CN.md +128 -96
- package/dist/index.d.ts +25 -0
- package/dist/index.js +349 -92
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -563,6 +563,45 @@ function buildErrorCard(message) {
|
|
|
563
563
|
};
|
|
564
564
|
}
|
|
565
565
|
__name(buildErrorCard, "buildErrorCard");
|
|
566
|
+
var ASK_BUTTON_VALUE_MARKER = "__eveLarkAsk";
|
|
567
|
+
function buildAskCard(request) {
|
|
568
|
+
const elements = [
|
|
569
|
+
{ tag: "div", text: { tag: "lark_md", content: request.prompt } }
|
|
570
|
+
];
|
|
571
|
+
if (request.options && request.options.length > 0) {
|
|
572
|
+
const buttons = request.options.map((opt) => ({
|
|
573
|
+
tag: "button",
|
|
574
|
+
text: { tag: "plain_text", content: opt.label },
|
|
575
|
+
type: opt.style ?? "default",
|
|
576
|
+
value: {
|
|
577
|
+
[ASK_BUTTON_VALUE_MARKER]: true,
|
|
578
|
+
requestId: request.requestId,
|
|
579
|
+
optionId: opt.id
|
|
580
|
+
},
|
|
581
|
+
...opt.description ? { confirm: { title: { tag: "plain_text", content: opt.label }, text: { tag: "plain_text", content: opt.description } } } : {}
|
|
582
|
+
}));
|
|
583
|
+
elements.push({ tag: "action", actions: buttons });
|
|
584
|
+
}
|
|
585
|
+
if (request.allowFreeform) {
|
|
586
|
+
const hint = request.options && request.options.length > 0 ? "_\u2026or reply to this chat with your own answer_" : "_Reply to this chat with your answer_";
|
|
587
|
+
elements.push({ tag: "div", text: { tag: "lark_md", content: hint } });
|
|
588
|
+
}
|
|
589
|
+
return { config: { ...BASE_CONFIG }, elements };
|
|
590
|
+
}
|
|
591
|
+
__name(buildAskCard, "buildAskCard");
|
|
592
|
+
function buildAskAnsweredCard(request, selected) {
|
|
593
|
+
const elements = [
|
|
594
|
+
{ tag: "div", text: { tag: "lark_md", content: request.prompt } }
|
|
595
|
+
];
|
|
596
|
+
const summary = selected.kind === "option" ? `<font color='green'>\u2713 ${escapeMarkdown(selected.label)}</font>` : `<font color='green'>\u2713 ${escapeMarkdown(selected.text)}</font>`;
|
|
597
|
+
elements.push({ tag: "div", text: { tag: "lark_md", content: summary } });
|
|
598
|
+
return { config: { ...BASE_CONFIG }, elements };
|
|
599
|
+
}
|
|
600
|
+
__name(buildAskAnsweredCard, "buildAskAnsweredCard");
|
|
601
|
+
function escapeMarkdown(s) {
|
|
602
|
+
return s.replace(/[*_`~\[\]]/g, (m) => `\\${m}`);
|
|
603
|
+
}
|
|
604
|
+
__name(escapeMarkdown, "escapeMarkdown");
|
|
566
605
|
|
|
567
606
|
// src/streaming-controller.ts
|
|
568
607
|
var StreamingCardController = class {
|
|
@@ -979,7 +1018,25 @@ async function doStartLongConnection(args) {
|
|
|
979
1018
|
} catch (e) {
|
|
980
1019
|
logError(`forward failed (event dropped)`, e);
|
|
981
1020
|
}
|
|
982
|
-
}, "im.message.receive_v1")
|
|
1021
|
+
}, "im.message.receive_v1"),
|
|
1022
|
+
// Card-button clicks. Feishu's card.action.trigger fires when a user
|
|
1023
|
+
// taps a button on a card we rendered. Forward to the channel webhook
|
|
1024
|
+
// — the webhook handler dispatches by event_type and feeds the click
|
|
1025
|
+
// back into eve as an InputResponse.
|
|
1026
|
+
"card.action.trigger": /* @__PURE__ */ __name(async (data) => {
|
|
1027
|
+
try {
|
|
1028
|
+
const envelope = rebuildEnvelopeFromSdkEvent("card.action.trigger", data, {
|
|
1029
|
+
appId: args.resolved.appId,
|
|
1030
|
+
verificationToken: args.resolved.verificationToken
|
|
1031
|
+
});
|
|
1032
|
+
await postEventToWebhookRetry(envelope, {
|
|
1033
|
+
eveWebhookUrl: args.eveWebhookUrl,
|
|
1034
|
+
encryptKey: args.resolved.encryptKey
|
|
1035
|
+
});
|
|
1036
|
+
} catch (e) {
|
|
1037
|
+
logError(`card action forward failed (event dropped)`, e);
|
|
1038
|
+
}
|
|
1039
|
+
}, "card.action.trigger")
|
|
983
1040
|
});
|
|
984
1041
|
const domain = args.resolved.baseUrl.includes("larksuite.com") ? sdk.Domain.Lark : sdk.Domain.Feishu;
|
|
985
1042
|
const wsClient = new sdk.WSClient({
|
|
@@ -1270,17 +1327,39 @@ function buildUserContent(text, files, options, messageId) {
|
|
|
1270
1327
|
return parts;
|
|
1271
1328
|
}
|
|
1272
1329
|
__name(buildUserContent, "buildUserContent");
|
|
1273
|
-
function
|
|
1274
|
-
if (typeof data !== "object" || data === null) return
|
|
1275
|
-
const
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
}
|
|
1281
|
-
return
|
|
1330
|
+
function formatErrorHint(data) {
|
|
1331
|
+
if (typeof data !== "object" || data === null) return "";
|
|
1332
|
+
const d = data;
|
|
1333
|
+
const detailsName = typeof d.details === "object" && d.details !== null ? d.details.name : void 0;
|
|
1334
|
+
const name = typeof detailsName === "string" && detailsName.length > 0 ? detailsName : void 0;
|
|
1335
|
+
const message = typeof d.message === "string" ? d.message.trim() : "";
|
|
1336
|
+
if (name && message.length > 0) return ` (${name}: ${truncateForDisplay(message)})`;
|
|
1337
|
+
if (name) return ` (${name})`;
|
|
1338
|
+
if (message.length > 0) return ` (${truncateForDisplay(message)})`;
|
|
1339
|
+
return "";
|
|
1282
1340
|
}
|
|
1283
|
-
__name(
|
|
1341
|
+
__name(formatErrorHint, "formatErrorHint");
|
|
1342
|
+
function extractErrorId(details) {
|
|
1343
|
+
if (typeof details === "object" && details !== null) {
|
|
1344
|
+
const id = details.errorId;
|
|
1345
|
+
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
1346
|
+
}
|
|
1347
|
+
return void 0;
|
|
1348
|
+
}
|
|
1349
|
+
__name(extractErrorId, "extractErrorId");
|
|
1350
|
+
function truncateForDisplay(s, max = 160) {
|
|
1351
|
+
return s.length <= max ? s : `${s.slice(0, max - 1).trimEnd()}\u2026`;
|
|
1352
|
+
}
|
|
1353
|
+
__name(truncateForDisplay, "truncateForDisplay");
|
|
1354
|
+
function formatFailureMessage(data, fallback, opts = { sentence: "turn" }) {
|
|
1355
|
+
const hint = formatErrorHint(data);
|
|
1356
|
+
const errorId = extractErrorId(data?.details);
|
|
1357
|
+
const lead = opts.sentence === "session" ? "This session couldn't recover from an error" : "I hit an error while handling your request";
|
|
1358
|
+
const idSuffix = errorId ? ` (Error id: ${errorId})` : "";
|
|
1359
|
+
if (!hint && !errorId) return `\u26A0 ${fallback}`;
|
|
1360
|
+
return `\u26A0 ${lead}${hint}.${idSuffix}`;
|
|
1361
|
+
}
|
|
1362
|
+
__name(formatFailureMessage, "formatFailureMessage");
|
|
1284
1363
|
function createLarkChannel(optionsInput) {
|
|
1285
1364
|
const options = resolveOptions(optionsInput);
|
|
1286
1365
|
const client = new LarkClient(options);
|
|
@@ -1295,6 +1374,8 @@ function createLarkChannel(optionsInput) {
|
|
|
1295
1374
|
}
|
|
1296
1375
|
const controllers = /* @__PURE__ */ new Map();
|
|
1297
1376
|
const sessionMeta = /* @__PURE__ */ new Map();
|
|
1377
|
+
const pendingInputsByRequestId = /* @__PURE__ */ new Map();
|
|
1378
|
+
const pendingInputsByChatToken = /* @__PURE__ */ new Map();
|
|
1298
1379
|
function getController(sessionId, meta) {
|
|
1299
1380
|
let ctrl = controllers.get(sessionId);
|
|
1300
1381
|
if (!ctrl) {
|
|
@@ -1431,8 +1512,29 @@ function createLarkChannel(optionsInput) {
|
|
|
1431
1512
|
sessionMeta.delete(id);
|
|
1432
1513
|
}
|
|
1433
1514
|
}
|
|
1515
|
+
for (const [reqId, p] of pendingInputsByRequestId) {
|
|
1516
|
+
if (p.touchedAt < cutoff) {
|
|
1517
|
+
pendingInputsByRequestId.delete(reqId);
|
|
1518
|
+
const tokenKey = chatTokenKey(p.chatId, p.rootId, p.parentId);
|
|
1519
|
+
if (pendingInputsByChatToken.get(tokenKey)?.requestId === reqId) {
|
|
1520
|
+
pendingInputsByChatToken.delete(tokenKey);
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1434
1524
|
}
|
|
1435
1525
|
__name(maybeSweep, "maybeSweep");
|
|
1526
|
+
function chatTokenKey(chatId, rootId, parentId) {
|
|
1527
|
+
return `${chatId}:${parentId ?? rootId ?? "_"}`;
|
|
1528
|
+
}
|
|
1529
|
+
__name(chatTokenKey, "chatTokenKey");
|
|
1530
|
+
function dropPendingInput(p) {
|
|
1531
|
+
pendingInputsByRequestId.delete(p.requestId);
|
|
1532
|
+
const tokenKey = chatTokenKey(p.chatId, p.rootId, p.parentId);
|
|
1533
|
+
if (pendingInputsByChatToken.get(tokenKey)?.requestId === p.requestId) {
|
|
1534
|
+
pendingInputsByChatToken.delete(tokenKey);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
__name(dropPendingInput, "dropPendingInput");
|
|
1436
1538
|
const webhookHandler = /* @__PURE__ */ __name(async (req, helpers) => {
|
|
1437
1539
|
maybeSweep();
|
|
1438
1540
|
const contentLength = Number(req.headers.get("content-length") ?? "0");
|
|
@@ -1485,12 +1587,17 @@ function createLarkChannel(optionsInput) {
|
|
|
1485
1587
|
if (body.header?.token !== options.verificationToken) {
|
|
1486
1588
|
return new Response("verification token mismatch", { status: 401 });
|
|
1487
1589
|
}
|
|
1488
|
-
const
|
|
1590
|
+
const evtMsg = body.event;
|
|
1591
|
+
const dedupKey = body.header?.event_id ?? evtMsg?.message?.message_id ?? evtMsg?.open_message_id;
|
|
1489
1592
|
if (dedupKey) {
|
|
1490
1593
|
if (dedup.has(dedupKey)) return ackOk();
|
|
1491
1594
|
dedup.set(dedupKey);
|
|
1492
1595
|
}
|
|
1493
|
-
|
|
1596
|
+
const eventType = body.header?.event_type;
|
|
1597
|
+
if (eventType === "card.action.trigger") {
|
|
1598
|
+
return handleCardAction(body.event, helpers);
|
|
1599
|
+
}
|
|
1600
|
+
if (eventType !== "im.message.receive_v1") {
|
|
1494
1601
|
return ackOk();
|
|
1495
1602
|
}
|
|
1496
1603
|
if (!body.event) return ackOk();
|
|
@@ -1501,6 +1608,44 @@ function createLarkChannel(optionsInput) {
|
|
|
1501
1608
|
if (parsed.text === "" && parsed.files.length === 0) {
|
|
1502
1609
|
return ackOk();
|
|
1503
1610
|
}
|
|
1611
|
+
const tokenKey = chatTokenKey(parsed.chatId, parsed.rootId ?? void 0, parsed.parentId ?? void 0);
|
|
1612
|
+
const pending = pendingInputsByChatToken.get(tokenKey);
|
|
1613
|
+
if (pending && pending.awaitingFreeform && parsed.text.length > 0) {
|
|
1614
|
+
const resp = { requestId: pending.requestId, text: parsed.text };
|
|
1615
|
+
const resumeAuth = {
|
|
1616
|
+
authenticator: "lark",
|
|
1617
|
+
principalType: "user",
|
|
1618
|
+
principalId: parsed.senderOpenId,
|
|
1619
|
+
attributes: {
|
|
1620
|
+
chatId: parsed.chatId,
|
|
1621
|
+
rootMessageId: parsed.rootId,
|
|
1622
|
+
messageId: parsed.messageId,
|
|
1623
|
+
chatType: parsed.chatType
|
|
1624
|
+
}
|
|
1625
|
+
};
|
|
1626
|
+
const resumeToken = larkContinuationToken(parsed.chatId, parsed.parentId ?? parsed.rootId);
|
|
1627
|
+
try {
|
|
1628
|
+
await helpers.send(
|
|
1629
|
+
{ inputResponses: [resp] },
|
|
1630
|
+
{ auth: resumeAuth, continuationToken: resumeToken }
|
|
1631
|
+
);
|
|
1632
|
+
if (pending.cardMessageId) {
|
|
1633
|
+
try {
|
|
1634
|
+
await client.patchCard({
|
|
1635
|
+
messageId: pending.cardMessageId,
|
|
1636
|
+
card: buildAskAnsweredCard(pending.request, { kind: "freeform", text: parsed.text })
|
|
1637
|
+
});
|
|
1638
|
+
} catch (e) {
|
|
1639
|
+
console.warn("[eve-lark] patchCard after freeform answer failed:", e instanceof Error ? e.message : e);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
} catch (e) {
|
|
1643
|
+
console.error("[eve-lark] freeform input-response send failed:", e instanceof Error ? e.message : e);
|
|
1644
|
+
} finally {
|
|
1645
|
+
dropPendingInput(pending);
|
|
1646
|
+
}
|
|
1647
|
+
return ackOk();
|
|
1648
|
+
}
|
|
1504
1649
|
const userContent = buildUserContent(parsed.text, parsed.files, options, parsed.messageId);
|
|
1505
1650
|
const continuationToken = larkContinuationToken(parsed.chatId, parsed.parentId ?? parsed.rootId);
|
|
1506
1651
|
const auth = {
|
|
@@ -1542,7 +1687,194 @@ function createLarkChannel(optionsInput) {
|
|
|
1542
1687
|
}
|
|
1543
1688
|
return ackOk();
|
|
1544
1689
|
}, "webhookHandler");
|
|
1545
|
-
|
|
1690
|
+
async function handleCardAction(evt, helpers) {
|
|
1691
|
+
const value = evt.action?.value;
|
|
1692
|
+
if (!value || value[ASK_BUTTON_VALUE_MARKER] !== true) {
|
|
1693
|
+
return ackOk();
|
|
1694
|
+
}
|
|
1695
|
+
const requestId = typeof value.requestId === "string" ? value.requestId : "";
|
|
1696
|
+
const optionId = typeof value.optionId === "string" ? value.optionId : "";
|
|
1697
|
+
if (!requestId) return ackOk();
|
|
1698
|
+
const pending = pendingInputsByRequestId.get(requestId);
|
|
1699
|
+
if (!pending) {
|
|
1700
|
+
console.warn(`[eve-lark] card action for unknown requestId=${requestId} (already answered or expired)`);
|
|
1701
|
+
return ackOk();
|
|
1702
|
+
}
|
|
1703
|
+
const resp = { requestId, optionId: optionId || void 0 };
|
|
1704
|
+
const resumeToken = larkContinuationToken(pending.chatId, pending.parentId ?? pending.rootId ?? null);
|
|
1705
|
+
const resumeAuth = {
|
|
1706
|
+
authenticator: "lark",
|
|
1707
|
+
principalType: "user",
|
|
1708
|
+
principalId: evt.open_id,
|
|
1709
|
+
attributes: {
|
|
1710
|
+
chatId: pending.chatId,
|
|
1711
|
+
rootMessageId: pending.rootId,
|
|
1712
|
+
messageId: evt.open_message_id,
|
|
1713
|
+
chatType: pending.request.display === "confirmation" ? "p2p" : "group"
|
|
1714
|
+
}
|
|
1715
|
+
};
|
|
1716
|
+
try {
|
|
1717
|
+
await helpers.send(
|
|
1718
|
+
{ inputResponses: [resp] },
|
|
1719
|
+
{ auth: resumeAuth, continuationToken: resumeToken }
|
|
1720
|
+
);
|
|
1721
|
+
console.log(`[eve-lark] ask answered via button click requestId=${requestId} optionId=${optionId}`);
|
|
1722
|
+
} catch (e) {
|
|
1723
|
+
console.error(
|
|
1724
|
+
`[eve-lark] ask input-response send failed (requestId=${requestId}):`,
|
|
1725
|
+
e instanceof Error ? e.message : e
|
|
1726
|
+
);
|
|
1727
|
+
}
|
|
1728
|
+
const selectedOpt = pending.request.options?.find((o) => o.id === optionId);
|
|
1729
|
+
if (pending.cardMessageId && selectedOpt) {
|
|
1730
|
+
try {
|
|
1731
|
+
await client.patchCard({
|
|
1732
|
+
messageId: pending.cardMessageId,
|
|
1733
|
+
card: buildAskAnsweredCard(pending.request, { kind: "option", label: selectedOpt.label })
|
|
1734
|
+
});
|
|
1735
|
+
} catch (e) {
|
|
1736
|
+
console.warn("[eve-lark] patchCard after ask-answer failed:", e instanceof Error ? e.message : e);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
dropPendingInput(pending);
|
|
1740
|
+
return ackOk();
|
|
1741
|
+
}
|
|
1742
|
+
__name(handleCardAction, "handleCardAction");
|
|
1743
|
+
const channelEvents = {
|
|
1744
|
+
// Streaming delta — patch the card.
|
|
1745
|
+
"message.appended"(data, _channel, ctx) {
|
|
1746
|
+
if (options.replyMode !== "streaming") return;
|
|
1747
|
+
const sessionId = ctx.session.id;
|
|
1748
|
+
const info = sessionInfoFromCtx(ctx);
|
|
1749
|
+
if (!info) return;
|
|
1750
|
+
const d = data;
|
|
1751
|
+
if (typeof d.messageDelta !== "string") return;
|
|
1752
|
+
const ctrl = getController(sessionId, info);
|
|
1753
|
+
ctrl.appendDelta(d.messageDelta);
|
|
1754
|
+
},
|
|
1755
|
+
// eve's ask_question (and similar HITL tools) fire this event with a
|
|
1756
|
+
// list of input requests. Each request becomes a Feishu card with
|
|
1757
|
+
// buttons (one per option) plus optional freeform hint.
|
|
1758
|
+
async "input.requested"(data, _channel, ctx) {
|
|
1759
|
+
const sessionId = ctx.session.id;
|
|
1760
|
+
const info = sessionInfoFromCtx(ctx);
|
|
1761
|
+
if (!info) {
|
|
1762
|
+
console.warn(`[eve-lark] input.requested: no session info (sessionId=${sessionId})`);
|
|
1763
|
+
return;
|
|
1764
|
+
}
|
|
1765
|
+
const d = data;
|
|
1766
|
+
const requests = d.requests ?? [];
|
|
1767
|
+
if (requests.length === 0) return;
|
|
1768
|
+
console.log(
|
|
1769
|
+
`[eve-lark] input.requested sessionId=${sessionId} chatId=${info.chatId} count=${requests.length}`
|
|
1770
|
+
);
|
|
1771
|
+
for (const req of requests) {
|
|
1772
|
+
const card = buildAskCard(req);
|
|
1773
|
+
let cardMessageId;
|
|
1774
|
+
try {
|
|
1775
|
+
const res = await client.sendCard({
|
|
1776
|
+
chatId: info.chatId,
|
|
1777
|
+
card,
|
|
1778
|
+
rootId: info.rootId,
|
|
1779
|
+
parentId: info.parentId
|
|
1780
|
+
});
|
|
1781
|
+
cardMessageId = res.messageId;
|
|
1782
|
+
} catch (e) {
|
|
1783
|
+
console.error(
|
|
1784
|
+
`[eve-lark] ask card send failed (requestId=${req.requestId}):`,
|
|
1785
|
+
e instanceof Error ? e.message : e
|
|
1786
|
+
);
|
|
1787
|
+
continue;
|
|
1788
|
+
}
|
|
1789
|
+
const pending = {
|
|
1790
|
+
requestId: req.requestId,
|
|
1791
|
+
sessionId,
|
|
1792
|
+
chatId: info.chatId,
|
|
1793
|
+
rootId: info.rootId,
|
|
1794
|
+
parentId: info.parentId,
|
|
1795
|
+
cardMessageId,
|
|
1796
|
+
request: req,
|
|
1797
|
+
createdAt: Date.now(),
|
|
1798
|
+
touchedAt: Date.now(),
|
|
1799
|
+
awaitingFreeform: req.allowFreeform === true
|
|
1800
|
+
};
|
|
1801
|
+
pendingInputsByRequestId.set(req.requestId, pending);
|
|
1802
|
+
if (pending.awaitingFreeform) {
|
|
1803
|
+
const tokenKey = chatTokenKey(info.chatId, info.rootId, info.parentId);
|
|
1804
|
+
pendingInputsByChatToken.set(tokenKey, pending);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
},
|
|
1808
|
+
// Terminal — deliver the final reply, then clean up the ack reaction.
|
|
1809
|
+
async "message.completed"(data, _channel, ctx) {
|
|
1810
|
+
const sessionId = ctx.session.id;
|
|
1811
|
+
const info = sessionInfoFromCtx(ctx);
|
|
1812
|
+
if (!info) {
|
|
1813
|
+
console.warn(`[eve-lark] message.completed: no session info, cannot deliver (sessionId=${sessionId})`);
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
const d = data;
|
|
1817
|
+
const rawText = typeof d.message === "string" ? d.message : "";
|
|
1818
|
+
console.log(
|
|
1819
|
+
`[eve-lark] message.completed sessionId=${sessionId} chatId=${info.chatId} msgLen=${rawText.length}`
|
|
1820
|
+
);
|
|
1821
|
+
const text = rawText.length > 0 ? rawText : EMPTY_REPLY_TEXT;
|
|
1822
|
+
try {
|
|
1823
|
+
await deliverReply(sessionId, info, text);
|
|
1824
|
+
} finally {
|
|
1825
|
+
await cleanupAckReaction(sessionId);
|
|
1826
|
+
dropController(sessionId);
|
|
1827
|
+
}
|
|
1828
|
+
},
|
|
1829
|
+
async "turn.failed"(data, _channel, ctx) {
|
|
1830
|
+
const sessionId = ctx?.session?.id;
|
|
1831
|
+
if (!sessionId) {
|
|
1832
|
+
console.warn("[eve-lark] turn.failed: no sessionId on ctx");
|
|
1833
|
+
return;
|
|
1834
|
+
}
|
|
1835
|
+
const info = sessionInfoFromCtx(ctx);
|
|
1836
|
+
if (!info) {
|
|
1837
|
+
console.warn(`[eve-lark] turn.failed: no session info (sessionId=${sessionId})`);
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
const userText = formatFailureMessage(data, "turn failed", { sentence: "turn" });
|
|
1841
|
+
const errorId = extractErrorId(data?.details);
|
|
1842
|
+
console.warn(
|
|
1843
|
+
`[eve-lark] turn.failed sessionId=${sessionId} chatId=${info.chatId} err="${userText.slice(0, 200)}"` + (errorId ? ` errorId=${errorId}` : "")
|
|
1844
|
+
);
|
|
1845
|
+
const ctrl = controllers.get(sessionId);
|
|
1846
|
+
if (ctrl) {
|
|
1847
|
+
try {
|
|
1848
|
+
await ctrl.abort(userText);
|
|
1849
|
+
console.log(`[eve-lark] error shown via streaming abort (sessionId=${sessionId})`);
|
|
1850
|
+
} catch (e) {
|
|
1851
|
+
console.warn(
|
|
1852
|
+
`[eve-lark] turn.failed: streaming abort failed, will deliver fresh error (sessionId=${sessionId}):`,
|
|
1853
|
+
e instanceof Error ? e.message : e
|
|
1854
|
+
);
|
|
1855
|
+
try {
|
|
1856
|
+
await deliverReply(sessionId, info, userText);
|
|
1857
|
+
} catch {
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
} else {
|
|
1861
|
+
try {
|
|
1862
|
+
await deliverReply(sessionId, info, userText);
|
|
1863
|
+
} catch {
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
await cleanupAckReaction(sessionId);
|
|
1867
|
+
dropController(sessionId);
|
|
1868
|
+
},
|
|
1869
|
+
async "session.failed"(data) {
|
|
1870
|
+
const userText = formatFailureMessage(data, "session failed", { sentence: "session" });
|
|
1871
|
+
const errorId = extractErrorId(data?.details);
|
|
1872
|
+
console.error(
|
|
1873
|
+
`[eve-lark] session.failed: ${userText}` + (errorId ? ` (errorId=${errorId})` : "")
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1876
|
+
};
|
|
1877
|
+
const channel = defineChannel({
|
|
1546
1878
|
routes: [POST(options.webhookPath, webhookHandler)],
|
|
1547
1879
|
fetchFile: /* @__PURE__ */ __name(async (url) => {
|
|
1548
1880
|
if (!url.startsWith(options.baseUrl)) return null;
|
|
@@ -1554,85 +1886,10 @@ function createLarkChannel(optionsInput) {
|
|
|
1554
1886
|
type: m[3]
|
|
1555
1887
|
});
|
|
1556
1888
|
}, "fetchFile"),
|
|
1557
|
-
events:
|
|
1558
|
-
// Streaming delta — patch the card.
|
|
1559
|
-
"message.appended"(data, _channel, ctx) {
|
|
1560
|
-
if (options.replyMode !== "streaming") return;
|
|
1561
|
-
const sessionId = ctx.session.id;
|
|
1562
|
-
const info = sessionInfoFromCtx(ctx);
|
|
1563
|
-
if (!info) return;
|
|
1564
|
-
const d = data;
|
|
1565
|
-
if (typeof d.messageDelta !== "string") return;
|
|
1566
|
-
const ctrl = getController(sessionId, info);
|
|
1567
|
-
ctrl.appendDelta(d.messageDelta);
|
|
1568
|
-
},
|
|
1569
|
-
// Terminal — deliver the final reply, then clean up the ack reaction.
|
|
1570
|
-
async "message.completed"(data, _channel, ctx) {
|
|
1571
|
-
const sessionId = ctx.session.id;
|
|
1572
|
-
const info = sessionInfoFromCtx(ctx);
|
|
1573
|
-
if (!info) {
|
|
1574
|
-
console.warn(`[eve-lark] message.completed: no session info, cannot deliver (sessionId=${sessionId})`);
|
|
1575
|
-
return;
|
|
1576
|
-
}
|
|
1577
|
-
const d = data;
|
|
1578
|
-
const rawText = typeof d.message === "string" ? d.message : "";
|
|
1579
|
-
console.log(
|
|
1580
|
-
`[eve-lark] message.completed sessionId=${sessionId} chatId=${info.chatId} msgLen=${rawText.length}`
|
|
1581
|
-
);
|
|
1582
|
-
const text = rawText.length > 0 ? rawText : EMPTY_REPLY_TEXT;
|
|
1583
|
-
try {
|
|
1584
|
-
await deliverReply(sessionId, info, text);
|
|
1585
|
-
} finally {
|
|
1586
|
-
await cleanupAckReaction(sessionId);
|
|
1587
|
-
dropController(sessionId);
|
|
1588
|
-
}
|
|
1589
|
-
},
|
|
1590
|
-
async "turn.failed"(data, _channel, ctx) {
|
|
1591
|
-
const sessionId = ctx?.session?.id;
|
|
1592
|
-
if (!sessionId) {
|
|
1593
|
-
console.warn("[eve-lark] turn.failed: no sessionId on ctx");
|
|
1594
|
-
return;
|
|
1595
|
-
}
|
|
1596
|
-
const info = sessionInfoFromCtx(ctx);
|
|
1597
|
-
if (!info) {
|
|
1598
|
-
console.warn(`[eve-lark] turn.failed: no session info (sessionId=${sessionId})`);
|
|
1599
|
-
return;
|
|
1600
|
-
}
|
|
1601
|
-
const errMsg = errMsgFrom(data, "turn failed");
|
|
1602
|
-
console.warn(
|
|
1603
|
-
`[eve-lark] turn.failed sessionId=${sessionId} chatId=${info.chatId} err="${errMsg.slice(0, 200)}"`
|
|
1604
|
-
);
|
|
1605
|
-
const userText = `\u26A0 ${errMsg}`;
|
|
1606
|
-
const ctrl = controllers.get(sessionId);
|
|
1607
|
-
if (ctrl) {
|
|
1608
|
-
try {
|
|
1609
|
-
await ctrl.abort(errMsg);
|
|
1610
|
-
console.log(`[eve-lark] error shown via streaming abort (sessionId=${sessionId})`);
|
|
1611
|
-
} catch (e) {
|
|
1612
|
-
console.warn(
|
|
1613
|
-
`[eve-lark] turn.failed: streaming abort failed, will deliver fresh error (sessionId=${sessionId}):`,
|
|
1614
|
-
e instanceof Error ? e.message : e
|
|
1615
|
-
);
|
|
1616
|
-
try {
|
|
1617
|
-
await deliverReply(sessionId, info, userText);
|
|
1618
|
-
} catch {
|
|
1619
|
-
}
|
|
1620
|
-
}
|
|
1621
|
-
} else {
|
|
1622
|
-
try {
|
|
1623
|
-
await deliverReply(sessionId, info, userText);
|
|
1624
|
-
} catch {
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
await cleanupAckReaction(sessionId);
|
|
1628
|
-
dropController(sessionId);
|
|
1629
|
-
},
|
|
1630
|
-
async "session.failed"(data) {
|
|
1631
|
-
const errMsg = errMsgFrom(data, "session failed");
|
|
1632
|
-
console.error("[eve-lark] session.failed:", errMsg);
|
|
1633
|
-
}
|
|
1634
|
-
}
|
|
1889
|
+
events: channelEvents
|
|
1635
1890
|
});
|
|
1891
|
+
channel.__testEvents = channelEvents;
|
|
1892
|
+
return channel;
|
|
1636
1893
|
}
|
|
1637
1894
|
__name(createLarkChannel, "createLarkChannel");
|
|
1638
1895
|
export {
|