@nettee/beacon 0.2.6 → 0.2.7

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.
@@ -1,3 +1,4 @@
1
+ import { shouldProcessFeishuInbound } from "./mentions.js";
1
2
  import { buildFeishuTriggerInput, } from "./trigger-input.js";
2
3
  export class FeishuIntake {
3
4
  options;
@@ -10,6 +11,16 @@ export class FeishuIntake {
10
11
  (event.message.chat_type !== "p2p" && event.message.chat_type !== "group")) {
11
12
  return "ignored";
12
13
  }
14
+ // Temporary hard cut: group @All (without a direct bot @) never claims or runs.
15
+ if (!shouldProcessFeishuInbound({
16
+ chatType: event.message.chat_type,
17
+ mentions: event.message.mentions,
18
+ messageType: event.message.message_type,
19
+ content: event.message.content,
20
+ })) {
21
+ console.log(`[beacon] ignored group event without direct bot mention message_id=${event.message.message_id}`);
22
+ return "ignored";
23
+ }
13
24
  if (!event.event_id)
14
25
  throw new Error("Feishu event_id is required");
15
26
  const claim = await this.options.store.claim({
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Temporary hard cut: group @All must not start a Beacon run.
3
+ * Prefer Feishu `message.mentions`; fall back to content placeholders.
4
+ */
5
+ function normalize(value) {
6
+ return value?.trim().toLowerCase() ?? "";
7
+ }
8
+ /** True when a Feishu mention entry is @All / @所有人 / @_all. */
9
+ export function isFeishuAtAllMention(mention) {
10
+ const key = normalize(mention.key);
11
+ if (key === "@_all" || key === "@all")
12
+ return true;
13
+ const name = mention.name?.trim() ?? "";
14
+ const nameLower = name.toLowerCase();
15
+ if (name === "所有人" || nameLower === "all" || nameLower === "@all") {
16
+ return true;
17
+ }
18
+ const userId = normalize(mention.id?.user_id);
19
+ if (userId === "all" || userId === "@_all")
20
+ return true;
21
+ const openId = mention.id?.open_id?.trim() ?? "";
22
+ const unionId = mention.id?.union_id?.trim() ?? "";
23
+ // Empty-identity @_all-shaped rows sometimes omit a clean key.
24
+ if (!openId &&
25
+ !unionId &&
26
+ !userId &&
27
+ (key.includes("all") || name.includes("所有人"))) {
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ function walkForAtAll(value) {
33
+ if (typeof value === "string") {
34
+ const trimmed = value.trim().toLowerCase();
35
+ return (trimmed === "@_all" ||
36
+ trimmed === "@all" ||
37
+ trimmed === "all" ||
38
+ /@_all\b/i.test(value));
39
+ }
40
+ if (Array.isArray(value)) {
41
+ return value.some((entry) => walkForAtAll(entry));
42
+ }
43
+ if (value && typeof value === "object") {
44
+ const record = value;
45
+ if (record.tag === "at") {
46
+ const userId = normalize(typeof record.user_id === "string" ? record.user_id : undefined);
47
+ if (userId === "all" || userId === "@_all")
48
+ return true;
49
+ }
50
+ return Object.values(record).some((entry) => walkForAtAll(entry));
51
+ }
52
+ return false;
53
+ }
54
+ /** Detect @_all / at-all markers inside raw Feishu message content JSON. */
55
+ export function contentIndicatesFeishuAtAll(content) {
56
+ if (!content)
57
+ return false;
58
+ if (/@_all\b/i.test(content))
59
+ return true;
60
+ try {
61
+ return walkForAtAll(JSON.parse(content));
62
+ }
63
+ catch {
64
+ return /@_all\b/i.test(content);
65
+ }
66
+ }
67
+ /**
68
+ * A direct bot (or other entity) @mention — anything in `mentions` that is not @All.
69
+ * With mention-only Feishu scopes, non-@All mentions are the bot itself.
70
+ */
71
+ export function hasDirectFeishuMention(mentions) {
72
+ if (!mentions?.length)
73
+ return false;
74
+ return mentions.some((mention) => {
75
+ if (isFeishuAtAllMention(mention))
76
+ return false;
77
+ if (mention.mentioned_type === "bot")
78
+ return true;
79
+ // Explicit user @ is not a bot mention.
80
+ if (mention.mentioned_type === "user")
81
+ return false;
82
+ const openId = mention.id?.open_id?.trim() ?? "";
83
+ const userId = mention.id?.user_id?.trim() ?? "";
84
+ const unionId = mention.id?.union_id?.trim() ?? "";
85
+ // mentioned_type omitted: any non-@All identity counts (mention-only bots).
86
+ return Boolean(openId || userId || unionId);
87
+ });
88
+ }
89
+ /**
90
+ * Beacon-layer gate for inbound Feishu events.
91
+ * - DM (p2p): always process
92
+ * - Group: process only when a non-@All mention is present
93
+ * - Pure @All (with or without @_all content): ignore
94
+ * - @All + @bot together: process (direct mention wins)
95
+ */
96
+ export function shouldProcessFeishuInbound(input) {
97
+ if (input.chatType === "p2p")
98
+ return true;
99
+ if (input.chatType !== "group")
100
+ return false;
101
+ return hasDirectFeishuMention(input.mentions);
102
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nettee/beacon",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "A single-host bridge from messaging and schedules to isolated agent runs",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {