@vibe-lark/larkpal 0.1.91-issue69.0 → 0.1.91-issue70.0

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.
Files changed (3) hide show
  1. package/README.md +51 -0
  2. package/dist/main.mjs +108 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -93,9 +93,60 @@ lark-cli auth login
93
93
  | `LARKPAL_ENCRYPTION_KEY` | 用户凭证加密密钥(32字节 hex) | 未设置(明文,仅限开发) |
94
94
  | `LARKPAL_PLUGINS` | 声明式加载的插件列表 | 空 |
95
95
  | `LARKPAL_CHAT_JWT_SECRET` | Chat API JWT 签名密钥 | fallback 到 `LARKPAL_API_SECRET` |
96
+ | `LARKPAL_BUSINESS_EVENT_CALLBACK_URL` | 飞书长连接业务事件回调地址,用于固定菜单和业务卡片 action | 空 |
96
97
 
97
98
  会话工作目录默认写入 `LARKPAL_WORKSPACE` 下的隐藏目录:飞书会话为 `.chats/`,通用 Agent 会话为 `.sessions/`,Chat API 历史为 `.chat-history/`。
98
99
 
100
+ ### Business Event Callback
101
+
102
+ 当配置 `LARKPAL_BUSINESS_EVENT_CALLBACK_URL` 时,LarkPal 会把内置处理器未消费的业务事件 POST 到该地址。典型来源包括飞书固定机器人菜单 `application.bot.menu_v6` 和业务自定义 `card.action.trigger`。回调响应可以让 LarkPal 在触发会话中发送可见反馈;业务系统拥有 workflow 语义和卡片内容,LarkPal 只负责 Feishu transport。
103
+
104
+ **文本消息响应**
105
+
106
+ ```json
107
+ { "kind": "message", "text": "配方草稿已创建,请继续填写。" }
108
+ ```
109
+
110
+ 兼容格式:
111
+
112
+ ```json
113
+ { "message": { "text": "配方草稿已创建,请继续填写。" } }
114
+ ```
115
+
116
+ **互动卡片响应**
117
+
118
+ LarkPal 原样发送 `card` payload,不重写业务 action id。
119
+
120
+ ```json
121
+ {
122
+ "kind": "card",
123
+ "card": {
124
+ "schema": "2.0",
125
+ "body": { "elements": [] }
126
+ }
127
+ }
128
+ ```
129
+
130
+ 兼容格式:
131
+
132
+ ```json
133
+ { "card": { "schema": "2.0", "body": { "elements": [] } } }
134
+ ```
135
+
136
+ **轻量提示响应**
137
+
138
+ 对于不支持直接 toast 的事件(如固定菜单),LarkPal 会降级发送一条可见文本消息,避免用户点击后无反馈。
139
+
140
+ ```json
141
+ { "kind": "toast", "toast": { "type": "warning", "content": "请先上传实验文档。" } }
142
+ ```
143
+
144
+ 兼容格式:
145
+
146
+ ```json
147
+ { "toast": { "type": "warning", "content": "请先上传实验文档。" } }
148
+ ```
149
+
99
150
  ### larkpal-agent Runtime 专用(LARKPAL_RUNTIME=larkpal-agent 时必填)
100
151
 
101
152
  | 变量名 | 用途 | 示例 |
package/dist/main.mjs CHANGED
@@ -18337,7 +18337,15 @@ async function routeBusinessEvent(params) {
18337
18337
  if (response.status === 204) return void 0;
18338
18338
  if (!(response.headers.get("content-type") ?? "").includes("application/json")) return void 0;
18339
18339
  const payload = await response.json();
18340
- return isObject(payload) ? payload : void 0;
18340
+ if (!isObject(payload)) return void 0;
18341
+ const normalizedResponse = normalizeBusinessCallbackResponse(payload);
18342
+ await sendVisibleBusinessResponse({
18343
+ cfg: params.cfg,
18344
+ accountId: params.accountId,
18345
+ envelope,
18346
+ response: normalizedResponse
18347
+ });
18348
+ return normalizedResponse.raw;
18341
18349
  } catch (err) {
18342
18350
  log.warn("business event callback failed", {
18343
18351
  accountId: params.accountId,
@@ -18387,6 +18395,105 @@ function extractBotMenuKey(rawEvent) {
18387
18395
  const event = asRecord(raw.event);
18388
18396
  return readString(raw.event_key, event.event_key, raw.menu_event_key, event.menu_event_key);
18389
18397
  }
18398
+ async function sendVisibleBusinessResponse(params) {
18399
+ const target = params.envelope.conversationId;
18400
+ if (!target) {
18401
+ log.warn("business callback response has no Feishu send target", {
18402
+ accountId: params.accountId,
18403
+ eventType: params.envelope.eventType,
18404
+ responseKind: params.response.kind,
18405
+ trigger: params.envelope.trigger
18406
+ });
18407
+ return;
18408
+ }
18409
+ try {
18410
+ if (params.response.kind === "card" && params.response.card) {
18411
+ await sendCardFeishu({
18412
+ cfg: params.cfg,
18413
+ accountId: params.accountId,
18414
+ to: target,
18415
+ card: params.response.card
18416
+ });
18417
+ log.info("business callback card response sent", {
18418
+ accountId: params.accountId,
18419
+ eventType: params.envelope.eventType,
18420
+ responseKind: params.response.kind,
18421
+ target,
18422
+ trigger: params.envelope.trigger
18423
+ });
18424
+ return;
18425
+ }
18426
+ const text = params.response.kind === "message" ? params.response.text : params.response.toast?.content;
18427
+ if (text) {
18428
+ await sendMessageFeishu({
18429
+ cfg: params.cfg,
18430
+ accountId: params.accountId,
18431
+ to: target,
18432
+ text
18433
+ });
18434
+ log.info("business callback message response sent", {
18435
+ accountId: params.accountId,
18436
+ eventType: params.envelope.eventType,
18437
+ responseKind: params.response.kind,
18438
+ target,
18439
+ trigger: params.envelope.trigger
18440
+ });
18441
+ }
18442
+ } catch (err) {
18443
+ log.warn("business callback visible response send failed", {
18444
+ accountId: params.accountId,
18445
+ eventType: params.envelope.eventType,
18446
+ responseKind: params.response.kind,
18447
+ target,
18448
+ trigger: params.envelope.trigger,
18449
+ error: err instanceof Error ? err.message : String(err)
18450
+ });
18451
+ }
18452
+ }
18453
+ function normalizeBusinessCallbackResponse(payload) {
18454
+ const kind = typeof payload.kind === "string" ? payload.kind : void 0;
18455
+ const message = asRecord(payload.message);
18456
+ const toast = asRecord(payload.toast);
18457
+ const card = normalizeCardPayload(payload);
18458
+ const text = readString(payload.text, message.text, message.content);
18459
+ if (kind === "card" && card) return {
18460
+ kind: "card",
18461
+ card,
18462
+ raw: payload
18463
+ };
18464
+ if (card && !kind) return {
18465
+ kind: "card",
18466
+ card,
18467
+ raw: payload
18468
+ };
18469
+ if (kind === "message" && text) return {
18470
+ kind: "message",
18471
+ text,
18472
+ raw: payload
18473
+ };
18474
+ if (text && !kind) return {
18475
+ kind: "message",
18476
+ text,
18477
+ raw: payload
18478
+ };
18479
+ if ((kind === "toast" || !kind) && isObject(toast)) return {
18480
+ kind: "toast",
18481
+ toast: {
18482
+ type: readString(toast.type),
18483
+ content: readString(toast.content)
18484
+ },
18485
+ raw: payload
18486
+ };
18487
+ return {
18488
+ kind: "none",
18489
+ raw: payload
18490
+ };
18491
+ }
18492
+ function normalizeCardPayload(payload) {
18493
+ if (isObject(payload.card)) return payload.card;
18494
+ const card = asRecord(payload.card);
18495
+ if (isObject(card.data)) return card.data;
18496
+ }
18390
18497
  function extractOperator(raw) {
18391
18498
  const operator = asRecord(raw.operator);
18392
18499
  const operatorId = asRecord(operator.operator_id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-lark/larkpal",
3
- "version": "0.1.91-issue69.0",
3
+ "version": "0.1.91-issue70.0",
4
4
  "description": "LarkPal - Lark/Feishu bot service",
5
5
  "type": "module",
6
6
  "main": "./dist/main.mjs",