mercury-agent 0.8.1 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mercury-agent",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Personal AI assistant for chat platforms (WhatsApp, Slack, Discord, Telegram)",
5
5
  "license": "MIT",
6
6
  "author": "Avishai Tsabari",
@@ -1,12 +1,14 @@
1
1
  /**
2
- * Check if a caller is a global admin (configured in mercury.yaml / env).
3
- * Global admins are identified by `config.admins` and `config.dmAutoSpaceAdminIds`.
4
- * Platform-specific ID prefixes, + signs, and @domain suffixes are normalized.
2
+ * Matching of caller identities against configured user ids
3
+ * (`config.admins`, `config.dmAutoSpaceAdminIds`).
5
4
  *
6
- * WhatsApp callers arrive canonicalized to their phone JID, but older configs
7
- * may still list the LID digits (or vice versa when no mapping was known at
8
- * config time). When an alias lookup is provided, the caller's learned
9
- * LID↔phone counterpart is matched against the configured ids too.
5
+ * Configured ids come in loose formats (bare digits, `+` prefix, with or
6
+ * without platform prefix / domain suffix), while callers arrive as full
7
+ * canonical ids (e.g. `whatsapp:972542341444@s.whatsapp.net`). WhatsApp adds
8
+ * a second wrinkle: older configs may list the LID digits while callers are
9
+ * canonicalized to their phone JID (or vice versa when no mapping was known
10
+ * at config time). When an alias lookup is provided, the caller's learned
11
+ * LID↔phone counterpart from `wa_identity_aliases` is matched too.
10
12
  */
11
13
 
12
14
  /** Subset of Db used to resolve WhatsApp LID↔phone pairs. */
@@ -15,6 +17,44 @@ export interface WaAliasLookup {
15
17
  getWaLidForPn(pn: string): string | null;
16
18
  }
17
19
 
20
+ const normalize = (s: string) =>
21
+ s
22
+ .replace(/^[^:]+:/, "")
23
+ .replace(/^[+]+/, "")
24
+ .replace(/@.*$/, "");
25
+
26
+ /**
27
+ * Check whether a caller matches any id in a configured list, tolerating
28
+ * format differences (prefix, `+`, domain) and — with an alias lookup —
29
+ * the WhatsApp LID↔phone split.
30
+ */
31
+ export function matchesConfiguredId(
32
+ callerId: string,
33
+ configuredIds: string[],
34
+ aliases?: WaAliasLookup,
35
+ ): boolean {
36
+ if (configuredIds.length === 0) return false;
37
+
38
+ const callerCandidates = new Set([normalize(callerId)]);
39
+
40
+ if (aliases) {
41
+ const jid = callerId.replace(/^[^:]+:/, "");
42
+ if (jid.endsWith("@s.whatsapp.net")) {
43
+ const lid = aliases.getWaLidForPn(jid);
44
+ if (lid) callerCandidates.add(normalize(lid));
45
+ } else if (jid.endsWith("@lid")) {
46
+ const pn = aliases.getWaPnForLid(jid);
47
+ if (pn) callerCandidates.add(normalize(pn));
48
+ }
49
+ }
50
+
51
+ return configuredIds.some((id) => callerCandidates.has(normalize(id)));
52
+ }
53
+
54
+ /**
55
+ * Check if a caller is a global admin (configured in mercury.yaml / env).
56
+ * Global admins are identified by `config.admins` and `config.dmAutoSpaceAdminIds`.
57
+ */
18
58
  export function isGlobalAdmin(
19
59
  callerId: string,
20
60
  config: { admins?: string; dmAutoSpaceAdminIds?: string },
@@ -35,26 +75,5 @@ export function isGlobalAdmin(
35
75
  : []),
36
76
  ];
37
77
 
38
- const normalize = (s: string) =>
39
- s
40
- .replace(/^[^:]+:/, "")
41
- .replace(/^[+]+/, "")
42
- .replace(/@.*$/, "");
43
-
44
- const callerCandidates = new Set([normalize(callerId)]);
45
-
46
- if (aliases) {
47
- const jid = callerId.replace(/^[^:]+:/, "");
48
- if (jid.endsWith("@s.whatsapp.net")) {
49
- const lid = aliases.getWaLidForPn(jid);
50
- if (lid) callerCandidates.add(normalize(lid));
51
- } else if (jid.endsWith("@lid")) {
52
- const pn = aliases.getWaPnForLid(jid);
53
- if (pn) callerCandidates.add(normalize(pn));
54
- }
55
- }
56
-
57
- return globalAdmins.some(
58
- (id) => id === callerId || callerCandidates.has(normalize(id)),
59
- );
78
+ return matchesConfiguredId(callerId, globalAdmins, aliases);
60
79
  }
@@ -1,4 +1,5 @@
1
1
  import type { Db } from "../storage/db.js";
2
+ import { matchesConfiguredId } from "./global-admin.js";
2
3
 
3
4
  // ---------------------------------------------------------------------------
4
5
  // Built-in permissions (static, cannot be overridden)
@@ -231,5 +232,20 @@ export function resolveRole(
231
232
 
232
233
  db.upsertMember(spaceId, platformUserId, displayName);
233
234
 
234
- return db.getRole(spaceId, platformUserId) ?? "member";
235
+ const role = db.getRole(spaceId, platformUserId) ?? "member";
236
+
237
+ // Seeded rows are keyed by the raw config string, which may differ from the
238
+ // canonical caller id (format looseness, WhatsApp LID vs phone JID). When the
239
+ // exact lookup misses but the caller matches a configured admin id, promote
240
+ // the canonical id so the row self-heals. Config admins are always admins —
241
+ // seedAdmins already re-promotes demoted ones on every re-seed.
242
+ if (
243
+ role === "member" &&
244
+ matchesConfiguredId(platformUserId, seededAdmins, db)
245
+ ) {
246
+ db.setRole(spaceId, platformUserId, "admin", "seed");
247
+ return "admin";
248
+ }
249
+
250
+ return role;
235
251
  }