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/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 errMsgFrom(data, fallback) {
1274
- if (typeof data !== "object" || data === null) return fallback;
1275
- const err = data.error;
1276
- if (typeof err === "string") return err;
1277
- if (typeof err === "object" && err !== null) {
1278
- const msg = err.message;
1279
- if (typeof msg === "string") return msg;
1280
- }
1281
- return fallback;
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(errMsgFrom, "errMsgFrom");
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 dedupKey = body.header?.event_id ?? body.event?.message?.message_id;
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
- if (body.header?.event_type !== "im.message.receive_v1") {
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
- return defineChannel({
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 {