@ganglion/weacpx-channel-feishu 0.2.0 → 0.2.2

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/inbound.d.ts CHANGED
@@ -7,6 +7,24 @@ export declare function parseFeishuConversationId(chatKey: string): {
7
7
  chatId: string;
8
8
  threadId?: string;
9
9
  } | null;
10
+ /**
11
+ * Builds the chat-route metadata weacpx records for the current coordinator
12
+ * session. The host requires `chatType` to be `"direct"` or `"group"`, but
13
+ * Feishu reports `chat_type` as `"p2p"` (direct) or `"group"`, so `"p2p"` and
14
+ * any unexpected value normalize to `"direct"`. Without this, interactive
15
+ * Feishu turns recorded a route with no `chatType`, which blocked the in-session
16
+ * scheduled_create/list/cancel tools and group-owner command authorization.
17
+ */
18
+ export declare function buildFeishuRouteMetadata(input: {
19
+ chatType: string | undefined;
20
+ senderOpenId?: string;
21
+ chatId: string;
22
+ }): {
23
+ channel: "feishu";
24
+ chatType: "direct" | "group";
25
+ senderId?: string;
26
+ groupId?: string;
27
+ };
10
28
  export declare function shouldHandleFeishuMessage(input: {
11
29
  event: FeishuMessageEvent;
12
30
  botOpenId?: string;
package/dist/index.js CHANGED
@@ -78312,6 +78312,15 @@ function parseFeishuConversationId(chatKey) {
78312
78312
  }
78313
78313
  return { accountId: parts[1], chatId: parts[2] };
78314
78314
  }
78315
+ function buildFeishuRouteMetadata(input) {
78316
+ const isGroup = input.chatType === "group";
78317
+ return {
78318
+ channel: "feishu",
78319
+ chatType: isGroup ? "group" : "direct",
78320
+ ...input.senderOpenId ? { senderId: input.senderOpenId } : {},
78321
+ ...isGroup ? { groupId: input.chatId } : {}
78322
+ };
78323
+ }
78315
78324
  function shouldHandleFeishuMessage(input) {
78316
78325
  const text = input.parsedText ?? parseFeishuText(input.event.message.content);
78317
78326
  if (input.event.message.message_type !== "text" && !input.allowMediaOnly && text.trim().length === 0) {
@@ -80373,7 +80382,8 @@ class FeishuChannel {
80373
80382
  if (input.accountId && input.accountId !== route.accountId) {
80374
80383
  throw new Error(`scheduled Feishu accountId "${input.accountId}" does not match chatKey account "${route.accountId}"`);
80375
80384
  }
80376
- if (!this.accounts.has(route.accountId)) {
80385
+ const runtime = this.accounts.get(route.accountId);
80386
+ if (!runtime) {
80377
80387
  throw new Error(`feishu account "${route.accountId}" is not started; check channel.options.accounts and enabled flags`);
80378
80388
  }
80379
80389
  const deliverText = async (text) => {
@@ -80385,6 +80395,22 @@ class FeishuChannel {
80385
80395
  await this.sendRouteText(input.chatKey, input.replyContextToken, trimmed);
80386
80396
  };
80387
80397
  await this.sendRouteText(input.chatKey, input.replyContextToken, input.noticeText);
80398
+ const effectiveReplyMode = resolveEffectiveReplyMode(runtime.account.replyMode, undefined);
80399
+ const cardController = effectiveReplyMode === "streaming" ? await this.trySeedStreamingCard({
80400
+ runtime,
80401
+ accountId: route.accountId,
80402
+ chatId: route.chatId,
80403
+ ...input.replyContextToken ? { replyToMessageId: input.replyContextToken } : {}
80404
+ }) : null;
80405
+ const deliverReply = async (text) => {
80406
+ if (input.abortSignal?.aborted)
80407
+ return;
80408
+ if (cardController) {
80409
+ cardController.appendStream(text);
80410
+ return;
80411
+ }
80412
+ await deliverText(text);
80413
+ };
80388
80414
  try {
80389
80415
  const response = await this.agent.chat({
80390
80416
  accountId: route.accountId,
@@ -80393,10 +80419,26 @@ class FeishuChannel {
80393
80419
  ...input.replyContextToken ? { replyContextToken: input.replyContextToken } : {},
80394
80420
  ...input.abortSignal ? { abortSignal: input.abortSignal } : {},
80395
80421
  metadata: { channel: "feishu", scheduledSessionAlias: input.sessionAlias },
80396
- reply: deliverText
80422
+ reply: deliverReply,
80423
+ ...cardController ? {
80424
+ onToolEvent: (event) => {
80425
+ if (input.abortSignal?.aborted)
80426
+ return;
80427
+ cardController.recordToolEvent(event);
80428
+ },
80429
+ onThought: (chunk) => {
80430
+ if (input.abortSignal?.aborted)
80431
+ return;
80432
+ cardController.appendReasoning(chunk);
80433
+ }
80434
+ } : {}
80397
80435
  });
80398
- if (input.abortSignal?.aborted)
80436
+ if (input.abortSignal?.aborted) {
80437
+ if (cardController && !cardController.isTerminated()) {
80438
+ await cardController.abort(abortAck()).catch(() => {});
80439
+ }
80399
80440
  return;
80441
+ }
80400
80442
  const media = normalizeMediaArray(response.media);
80401
80443
  if (media.length > 0) {
80402
80444
  await this.logger.error("feishu.scheduled.media_unsupported", "scheduled feishu media responses are not supported", {
@@ -80407,11 +80449,23 @@ class FeishuChannel {
80407
80449
  count: media.length
80408
80450
  });
80409
80451
  }
80410
- await deliverText(response.text);
80452
+ if (cardController) {
80453
+ const responseText = response.text?.trim() ?? "";
80454
+ await cardController.complete(responseText.length > 0 ? response.text : undefined);
80455
+ if (cardController.isDegraded() && responseText.length > 0) {
80456
+ await deliverText(response.text);
80457
+ }
80458
+ } else {
80459
+ await deliverText(response.text);
80460
+ }
80411
80461
  } catch (error) {
80412
- try {
80413
- await deliverText(formatScheduledFailureText(input, error));
80414
- } catch {}
80462
+ if (cardController && !cardController.isTerminated()) {
80463
+ await cardController.fail(error instanceof Error ? error.message : String(error)).catch(() => {});
80464
+ } else {
80465
+ try {
80466
+ await deliverText(formatScheduledFailureText(input, error));
80467
+ } catch {}
80468
+ }
80415
80469
  throw error;
80416
80470
  }
80417
80471
  }
@@ -80593,7 +80647,7 @@ class FeishuChannel {
80593
80647
  return;
80594
80648
  const effectiveReplyMode = resolveEffectiveReplyMode(runtime.account.replyMode, chatType);
80595
80649
  if (effectiveReplyMode === "streaming") {
80596
- await this.trySeedStreamingCard({ runtime, accountId, chatId, messageId, active });
80650
+ active.cardController = await this.trySeedStreamingCard({ runtime, accountId, chatId, replyToMessageId: messageId });
80597
80651
  }
80598
80652
  if (active.suppressed) {
80599
80653
  if (active.cardController && !active.cardController.isTerminated()) {
@@ -80625,6 +80679,7 @@ class FeishuChannel {
80625
80679
  text: requestText,
80626
80680
  ...media.length > 0 ? { media } : {},
80627
80681
  replyContextToken: messageId,
80682
+ metadata: buildFeishuRouteMetadata({ chatType, senderOpenId: active.senderOpenId, chatId }),
80628
80683
  reply: safeReply,
80629
80684
  ...active.cardController ? {
80630
80685
  onToolEvent: (event) => {
@@ -80666,7 +80721,7 @@ class FeishuChannel {
80666
80721
  }
80667
80722
  }
80668
80723
  async trySeedStreamingCard(input) {
80669
- const { runtime, accountId, chatId, messageId, active } = input;
80724
+ const { runtime, accountId, chatId } = input;
80670
80725
  try {
80671
80726
  const controller = new StreamingCardController({
80672
80727
  client: runtime.client.sdk,
@@ -80681,8 +80736,8 @@ class FeishuChannel {
80681
80736
  this.logger?.error("feishu.card.degraded", "streaming card updates failing; will deliver answer via plain reply", { accountId, chatId, consecutiveFailures, bufferChars: buffer.length });
80682
80737
  }
80683
80738
  });
80684
- await controller.seed({ to: chatId, replyToMessageId: messageId });
80685
- active.cardController = controller;
80739
+ await controller.seed({ to: chatId, ...input.replyToMessageId ? { replyToMessageId: input.replyToMessageId } : {} });
80740
+ return controller;
80686
80741
  } catch (error) {
80687
80742
  const permErr = extractPermissionError(error);
80688
80743
  await this.logger.info("feishu.streaming.fallback", "streaming card seed failed; falling back to static", {
@@ -80693,7 +80748,7 @@ class FeishuChannel {
80693
80748
  });
80694
80749
  if (permErr)
80695
80750
  await this.maybeNotifyPermissionError({ runtime, chatId, error });
80696
- active.cardController = null;
80751
+ return null;
80697
80752
  }
80698
80753
  }
80699
80754
  async deliverResponse(input) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganglion/weacpx-channel-feishu",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Feishu channel plugin for weacpx.",
5
5
  "license": "MIT",
6
6
  "keywords": ["weacpx", "feishu", "channel", "plugin"],