@hwj123weijian/pi-feishu 0.9.0 → 0.10.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.
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
  - 消息需要排队时发送“排队中(第 N 位)”提示,轮到处理时自动撤回;队列变化时提示自动改写为新位数
17
17
  - Owner 在排队中的消息上点 ❌(`CrossMark`)表情即可取消该消息
18
18
  - 未绑定的群里被 @ 时,bot 会回复绑定引导而不是沉默;非授权成员的普通群消息完全不会触发回复
19
- - Bot 被拉进新群时,会私聊 Owner 发送绑定引导和群聊权限体检结果
19
+ - Bot 被拉进新群时,会私聊 Owner 发送绑定引导和群聊权限体检结果;启动成功后也会私聊 Owner 发送欢迎语和权限体检
20
20
  - Assistant 文本通过 Pi 的 `message_update` 增量渲染到同一张飞书卡片
21
21
  - 工具执行时仅显示安全的状态摘要,不展示参数、命令、文件内容或工具输出
22
22
  - SDK 以 500ms / 120 字符节流卡片更新;完成、失败、中止都会收尾卡片,失败原因(脱敏后)会写进回复
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hwj123weijian/pi-feishu",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Feishu private-chat and managed-group bridge for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/controller.ts CHANGED
@@ -25,7 +25,12 @@ import { MessageDeduplicator } from "./message-deduplicator.js";
25
25
  import { SerialMessageQueue } from "./message-queue.js";
26
26
  import { OwnerBinding } from "./owner-binding.js";
27
27
  import { executeRemoteCommand, type ParsedRemoteCommand, parseRemoteCommand } from "./remote-commands.js";
28
- import { buildBotAddedGuidance, buildGroupPermissionReminder } from "./scopes.js";
28
+ import {
29
+ buildBotAddedGuidance,
30
+ buildGroupPermissionReminder,
31
+ buildScopeHealthSection,
32
+ buildStartupWelcome,
33
+ } from "./scopes.js";
29
34
 
30
35
  // 与 easycodeclient 的飞书集成一致:THINKING 表情兼作"已读 + 处理中"回执。
31
36
  const READ_REACTION_EMOJI = "THINKING";
@@ -136,6 +141,8 @@ export class FeishuController {
136
141
  ...(existing.ownerOpenId ? { ownerOpenId: existing.ownerOpenId } : {}),
137
142
  ...(existing.managedGroupIds ? { managedGroupIds: existing.managedGroupIds } : {}),
138
143
  ...(existing.groupSessions ? { groupSessions: existing.groupSessions } : {}),
144
+ ...(existing.allowlist ? { allowlist: existing.allowlist } : {}),
145
+ ...(existing.allowlistNames ? { allowlistNames: existing.allowlistNames } : {}),
139
146
  }
140
147
  : credentials;
141
148
  await this.store.save(next);
@@ -175,10 +182,26 @@ export class FeishuController {
175
182
  gateway.onReaction((event) => this.handleReaction(event));
176
183
  this.botAddedUnsubscribe = gateway.onBotAdded?.((event) => void this.handleBotAdded(event));
177
184
 
185
+ // 对齐 easycodeclient:网关启动后私聊 Owner 发欢迎语 + 权限体检。
186
+ // 主动 await 保证欢迎语先于任何消息回复送达;probe/发送失败都不阻塞启动。
187
+ if (credentials.ownerOpenId) await this.sendStartupWelcome(gateway, credentials);
188
+
178
189
  const bindingCode = binding.getOrCreateCode();
179
190
  return bindingCode ? { alreadyRunning: false, bindingCode } : { alreadyRunning: false };
180
191
  }
181
192
 
193
+ /** Bot 上线后私聊 Owner 的欢迎语;probe 失败时只发正文,不猜权限状态。 */
194
+ private async sendStartupWelcome(gateway: FeishuGateway, credentials: FeishuCredentials): Promise<void> {
195
+ const ownerOpenId = credentials.ownerOpenId;
196
+ if (!ownerOpenId) return;
197
+ const probe = await this.probeScopes();
198
+ const welcome = buildStartupWelcome({
199
+ cwd: process.cwd(),
200
+ healthSection: buildScopeHealthSection(credentials.appId, probe.grantedScopes),
201
+ });
202
+ await gateway.sendText(ownerOpenId, welcome).catch(() => undefined);
203
+ }
204
+
182
205
  async createGroupChat(name: string): Promise<string> {
183
206
  const gateway = this.gateway;
184
207
  const ownerOpenId = this.credentials?.ownerOpenId;
package/src/extension.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  buildEventSubUrl,
11
11
  buildPermissionPageUrl,
12
12
  buildScopeApplyUrl,
13
+ buildScopeHealthSection,
13
14
  hasScope,
14
15
  missingScopes,
15
16
  REQUIRED_APP_SCOPES,
@@ -336,24 +337,9 @@ export async function appendScopeHealthHint(
336
337
  dashboard: string,
337
338
  grantedScopes?: string[],
338
339
  ): Promise<string> {
339
- if (!grantedScopes) return dashboard;
340
- const missing = missingScopes(grantedScopes, REQUIRED_APP_SCOPES);
341
- const hasGroupMsg = hasScope(grantedScopes, SENSITIVE_GROUP_MSG_SCOPE);
342
- if (missing.length === 0 && hasGroupMsg) {
343
- return `${dashboard}\n\n✅ 应用权限配置完整,所有功能均可正常使用。`;
344
- }
345
- const lines = ["", "⚠️ 以下应用权限尚未开通,对应功能会受限:"];
346
- if (missing.length > 0) {
347
- lines.push(`📋 缺失 ${missing.length} 项基础权限,点击一键申请:`);
348
- lines.push(`👉 ${buildScopeApplyUrl({ appId, scopes: missing })}`);
349
- }
350
- if (!hasGroupMsg) {
351
- lines.push("💬 「免 @ 响应」权限未开:群内需 @机器人 才能触发,点击开通:");
352
- lines.push(`👉 ${buildScopeApplyUrl({ appId, scopes: [SENSITIVE_GROUP_MSG_SCOPE] })}`);
353
- }
354
- lines.push("🔄 权限生效(需发布应用版本):");
355
- lines.push(`👉 ${buildPermissionPageUrl(appId)}`);
356
- return `${dashboard}\n${lines.join("\n")}`;
340
+ const section = buildScopeHealthSection(appId, grantedScopes);
341
+ if (!section) return dashboard;
342
+ return `${dashboard}\n\n${section}`;
357
343
  }
358
344
 
359
345
  export function renderFeishuStatus(status: FeishuStatus, scopeHealth?: string[]): string {
package/src/gateway.ts CHANGED
@@ -407,8 +407,9 @@ class SdkFeishuReply implements FeishuReply {
407
407
  }
408
408
 
409
409
  async fail(reason?: string): Promise<void> {
410
- const suffix = reason ? `:${reason}` : ",请稍后再试。";
411
- await this.finish({ text: this.snapshot.text || "处理消息失败", status: "处理失败" }, `处理消息失败${suffix}`);
410
+ const detail = reason ? `处理消息失败:${reason}` : "处理消息失败,请稍后再试。";
411
+ const text = this.snapshot.text ? `${this.snapshot.text}\n\n⚠️ ${detail}` : detail;
412
+ await this.finish({ text, status: "处理失败" }, detail);
412
413
  }
413
414
 
414
415
  async cancel(): Promise<void> {
package/src/scopes.ts CHANGED
@@ -164,3 +164,51 @@ export function buildBotAddedGuidance(params: {
164
164
  lines.push(`🔄 权限变更后需发布新版本生效:${buildPermissionPageUrl(params.appId)}`);
165
165
  return lines.join("\n");
166
166
  }
167
+
168
+ /**
169
+ * 权限体检段落:grantedScopes 未知时返回 null(不猜);
170
+ * 全部就绪时返回 ✅ 段落,否则返回缺失清单 + 一键申请链接。
171
+ */
172
+ export function buildScopeHealthSection(appId: string, grantedScopes?: readonly string[]): string | null {
173
+ if (!grantedScopes) return null;
174
+ const missing = missingScopes(grantedScopes, REQUIRED_APP_SCOPES);
175
+ const hasGroupMsg = hasScope(grantedScopes, SENSITIVE_GROUP_MSG_SCOPE);
176
+ if (missing.length === 0 && hasGroupMsg) {
177
+ return "✅ 应用权限配置完整,所有功能均可正常使用。";
178
+ }
179
+ const lines = ["⚠️ 以下应用权限尚未开通,对应功能会受限:"];
180
+ if (missing.length > 0) {
181
+ lines.push(`📋 缺失 ${missing.length} 项基础权限,点击一键申请:`);
182
+ lines.push(`👉 ${buildScopeApplyUrl({ appId, scopes: missing })}`);
183
+ }
184
+ if (!hasGroupMsg) {
185
+ lines.push("💬 「免 @ 响应」权限未开:群内需 @机器人 才能触发,点击开通:");
186
+ lines.push(`👉 ${buildScopeApplyUrl({ appId, scopes: [SENSITIVE_GROUP_MSG_SCOPE] })}`);
187
+ }
188
+ lines.push("🔄 权限生效(需发布应用版本):");
189
+ lines.push(`👉 ${buildPermissionPageUrl(appId)}`);
190
+ return lines.join("\n");
191
+ }
192
+
193
+ /**
194
+ * 对齐 easycodeclient:Bot 上线后私聊 Owner 的欢迎语,
195
+ * 包含工作目录、使用提示和权限体检结果。
196
+ */
197
+ export function buildStartupWelcome(params: { cwd?: string; healthSection?: string | null }): string {
198
+ const lines = ["👋 Pi 飞书 Bot 已上线,随时待命。"];
199
+ if (params.cwd) {
200
+ lines.push("", "**📂 主会话工作目录**", `\`${params.cwd}\``);
201
+ }
202
+ lines.push(
203
+ "",
204
+ "**💡 使用提示**",
205
+ "- 私聊直接发送问题即可",
206
+ "- Owner 在群里发送 /bind 可把群绑定到独立 Pi 会话",
207
+ "",
208
+ "**❓ 需要帮助**:发送 /help 查看所有可用命令",
209
+ );
210
+ if (params.healthSection) {
211
+ lines.push("", "---", "", params.healthSection);
212
+ }
213
+ return lines.join("\n");
214
+ }