mercury-agent 0.5.0 → 0.5.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.
Files changed (125) hide show
  1. package/README.md +452 -451
  2. package/container/Dockerfile +127 -127
  3. package/container/Dockerfile.base +109 -109
  4. package/container/Dockerfile.power +17 -17
  5. package/container/agent-package.json +8 -8
  6. package/container/build.sh +54 -54
  7. package/docs/ROADMAP.md +3 -4
  8. package/docs/archive/summarization/2026-07-02-recent-dev-summary.md +71 -0
  9. package/docs/auth/dashboard.md +28 -28
  10. package/docs/auth/overview.md +109 -109
  11. package/docs/auth/whatsapp.md +173 -173
  12. package/docs/authoring-profiles.md +174 -174
  13. package/docs/configuration.md +54 -54
  14. package/docs/container-lifecycle.md +349 -349
  15. package/docs/{bugs/bwrap-privileged-linux-docker.md → debug/major/2026-07-02-bwrap-privileged-linux-docker.md} +21 -1
  16. package/docs/{bugs/e2big-prompt-delivery-fix.md → debug/major/2026-07-02-e2big-prompt-delivery-fix.md} +27 -1
  17. package/docs/{bugs/registry-pull-no-local-fallback.md → debug/major/2026-07-02-registry-pull-no-local-fallback.md} +27 -1
  18. package/docs/debug/summarization/2026-07-02-common-bug-patterns.md +72 -0
  19. package/docs/deployment.md +199 -199
  20. package/docs/extensions.md +375 -375
  21. package/docs/graceful-shutdown.md +62 -62
  22. package/docs/kb-distillation.md +77 -77
  23. package/docs/media/overview.md +140 -140
  24. package/docs/media/whatsapp.md +171 -171
  25. package/docs/memory.md +137 -137
  26. package/docs/permissions.md +217 -217
  27. package/docs/pipeline.md +228 -228
  28. package/docs/prd-chat-memory.md +76 -76
  29. package/docs/prd-config-load.md +82 -82
  30. package/docs/rate-limiting.md +229 -229
  31. package/docs/runbooks/publish-checklist.md +9 -1
  32. package/docs/scheduler.md +288 -288
  33. package/docs/setup-discord.md +100 -100
  34. package/docs/setup-slack.md +119 -119
  35. package/docs/setup-whatsapp.md +94 -94
  36. package/docs/subagents.md +166 -166
  37. package/docs/web-search.md +62 -62
  38. package/examples/extensions/README.md +12 -12
  39. package/examples/extensions/charts/index.ts +13 -13
  40. package/examples/extensions/charts/skill/SKILL.md +98 -98
  41. package/examples/extensions/gws/README.md +52 -52
  42. package/examples/extensions/gws/skill/SKILL.md +57 -57
  43. package/examples/extensions/gws/skill/references/calendar.md +101 -101
  44. package/examples/extensions/gws/skill/references/docs.md +65 -65
  45. package/examples/extensions/gws/skill/references/drive.md +79 -79
  46. package/examples/extensions/gws/skill/references/gmail.md +85 -85
  47. package/examples/extensions/gws/skill/references/sheets.md +60 -60
  48. package/examples/extensions/napkin/skill/SKILL.md +728 -728
  49. package/examples/extensions/pdf/skill/LICENSE.txt +30 -30
  50. package/examples/extensions/pdf/skill/SKILL.md +314 -314
  51. package/examples/extensions/pdf/skill/forms.md +294 -294
  52. package/examples/extensions/pdf/skill/reference.md +611 -611
  53. package/examples/extensions/pdf/skill/scripts/check_bounding_boxes.py +65 -65
  54. package/examples/extensions/pdf/skill/scripts/check_fillable_fields.py +11 -11
  55. package/examples/extensions/pdf/skill/scripts/convert_pdf_to_images.py +33 -33
  56. package/examples/extensions/pdf/skill/scripts/create_validation_image.py +37 -37
  57. package/examples/extensions/pdf/skill/scripts/extract_form_field_info.py +122 -122
  58. package/examples/extensions/pdf/skill/scripts/extract_form_structure.py +115 -115
  59. package/examples/extensions/pdf/skill/scripts/fill_fillable_fields.py +98 -98
  60. package/examples/extensions/pdf/skill/scripts/fill_pdf_form_with_annotations.py +107 -107
  61. package/examples/extensions/permission-guard/index.ts +65 -65
  62. package/examples/extensions/pinchtab/skill/SKILL.md +224 -224
  63. package/examples/extensions/pinchtab/skill/TRUST.md +69 -69
  64. package/examples/extensions/pinchtab/skill/references/api.md +297 -297
  65. package/examples/extensions/pinchtab/skill/references/env.md +45 -45
  66. package/examples/extensions/pinchtab/skill/references/profiles.md +107 -107
  67. package/examples/extensions/tradestation/host/refresh.ts +102 -102
  68. package/examples/extensions/tradestation/index.ts +153 -153
  69. package/examples/extensions/tradestation/skill/SKILL.md +67 -67
  70. package/examples/extensions/voice-synth/index.ts +94 -94
  71. package/examples/extensions/voice-synth/skill/SKILL.md +38 -38
  72. package/examples/extensions/voice-transcribe/requirements.txt +8 -8
  73. package/examples/extensions/voice-transcribe/scripts/transcribe.py +179 -179
  74. package/examples/extensions/voice-transcribe/skill/SKILL.md +53 -53
  75. package/examples/extensions/yahoo-mail/cli/package.json +13 -13
  76. package/examples/extensions/yahoo-mail/skill/SKILL.md +78 -78
  77. package/package.json +106 -106
  78. package/resources/agents/explore.md +50 -50
  79. package/resources/agents/worker.md +24 -24
  80. package/resources/connection-env-vars.json +25 -25
  81. package/resources/pi-extensions/subagent/agents.ts +126 -126
  82. package/resources/pi-extensions/subagent/index.ts +964 -964
  83. package/resources/profiles/coding/AGENTS.md +43 -43
  84. package/resources/profiles/coding/mercury-profile.yaml +15 -15
  85. package/resources/profiles/general/AGENTS.md +31 -31
  86. package/resources/profiles/general/mercury-profile.yaml +15 -15
  87. package/resources/profiles/research/AGENTS.md +40 -40
  88. package/resources/profiles/research/mercury-profile.yaml +15 -15
  89. package/resources/skills/config/SKILL.md +25 -25
  90. package/resources/skills/context/SKILL.md +33 -33
  91. package/resources/skills/conversation-recap/SKILL.md +19 -19
  92. package/resources/skills/mutes/SKILL.md +31 -31
  93. package/resources/skills/permissions/SKILL.md +19 -19
  94. package/resources/skills/preferences/SKILL.md +31 -31
  95. package/resources/skills/recall/SKILL.md +24 -24
  96. package/resources/skills/roles/SKILL.md +18 -18
  97. package/resources/skills/spaces/SKILL.md +18 -18
  98. package/resources/skills/tasks/SKILL.md +45 -45
  99. package/resources/templates/AGENTS.md +157 -157
  100. package/resources/templates/env.template +38 -38
  101. package/resources/templates/mercury.example.yaml +99 -99
  102. package/src/agent/container-entry.ts +1 -1
  103. package/src/agent/container-runner.ts +1345 -1346
  104. package/src/cli/mercury.ts +39 -7
  105. package/src/cli/mrctl.ts +636 -636
  106. package/src/config-file.ts +540 -540
  107. package/src/config.ts +339 -339
  108. package/src/core/api.ts +125 -125
  109. package/src/core/caller-token.ts +101 -101
  110. package/src/core/permissions.ts +228 -228
  111. package/src/core/profiles.ts +271 -271
  112. package/src/core/routes/capability.ts +70 -70
  113. package/src/core/routes/index.ts +15 -15
  114. package/src/core/runtime.ts +1530 -1530
  115. package/src/dashboard/index.html +729 -729
  116. package/src/extensions/api.ts +273 -273
  117. package/src/extensions/loader.ts +286 -286
  118. package/src/extensions/types.ts +517 -517
  119. package/src/main.ts +605 -605
  120. package/docs/pending-updates/applicative-profiles.md +0 -15
  121. package/docs/pending-updates/caller-bound-capability-token.md +0 -15
  122. package/docs/pending-updates/dm-auto-space.md +0 -15
  123. /package/docs/archive/{2026-07-01-applicative-profiles.md → applicative-profiles/2026-07-01-applicative-profiles.md} +0 -0
  124. /package/docs/archive/{2026-07-01-caller-bound-capability-token.md → applicative-profiles/2026-07-01-caller-bound-capability-token.md} +0 -0
  125. /package/docs/archive/{2026-07-01-dm-auto-space.md → applicative-profiles/2026-07-01-dm-auto-space.md} +0 -0
@@ -1,228 +1,228 @@
1
- import type { Db } from "../storage/db.js";
2
-
3
- // ---------------------------------------------------------------------------
4
- // Built-in permissions (static, cannot be overridden)
5
- // ---------------------------------------------------------------------------
6
-
7
- const BUILT_IN_PERMISSIONS = new Set([
8
- "prompt",
9
- "stop",
10
- "compact",
11
- "clear",
12
- "tasks.list",
13
- "tasks.create",
14
- "tasks.pause",
15
- "tasks.resume",
16
- "tasks.delete",
17
- "config.get",
18
- "config.set",
19
- "prefs.get",
20
- "prefs.set",
21
- "roles.list",
22
- "roles.grant",
23
- "roles.revoke",
24
- "permissions.get",
25
- "permissions.set",
26
- "spaces.list",
27
- "spaces.rename",
28
- "spaces.delete",
29
- /** Purge inbox/outbox media files. */
30
- "media.purge",
31
- /** Host Text-to-Speech (/api/tts); admin-only by default. */
32
- "tts.synthesize",
33
- /** Mute/unmute users and list mutes; admin-only by default. */
34
- "mutes.list",
35
- "mutes.mute",
36
- "mutes.unmute",
37
- ]);
38
-
39
- // ---------------------------------------------------------------------------
40
- // Extension-registered permissions (dynamic, added at runtime)
41
- // ---------------------------------------------------------------------------
42
-
43
- const registeredPermissions = new Map<string, { defaultRoles: string[] }>();
44
-
45
- /**
46
- * Register a new permission from an extension.
47
- * Throws if the name collides with a built-in permission.
48
- */
49
- export function registerPermission(
50
- name: string,
51
- opts: { defaultRoles: string[] },
52
- ): void {
53
- if (BUILT_IN_PERMISSIONS.has(name)) {
54
- throw new Error(
55
- `Permission "${name}" is a built-in and cannot be overridden`,
56
- );
57
- }
58
- registeredPermissions.set(name, opts);
59
- }
60
-
61
- /**
62
- * Get all valid permission names (built-in + extension-registered).
63
- */
64
- export function getAllPermissions(): string[] {
65
- return [...BUILT_IN_PERMISSIONS, ...registeredPermissions.keys()];
66
- }
67
-
68
- /**
69
- * Check if a permission name is valid (built-in or registered).
70
- */
71
- export function isValidPermission(name: string): boolean {
72
- return BUILT_IN_PERMISSIONS.has(name) || registeredPermissions.has(name);
73
- }
74
-
75
- /**
76
- * Clear all registered extension permissions. For test isolation only.
77
- */
78
- export function resetPermissions(): void {
79
- registeredPermissions.clear();
80
- activeProfileMemberPermissions = null;
81
- }
82
-
83
- // ---------------------------------------------------------------------------
84
- // Active applicative profile (project-wide)
85
- // ---------------------------------------------------------------------------
86
-
87
- /**
88
- * Member permission set imposed by the active applicative profile, if any.
89
- * Project-wide (one profile per deployment): set once at startup from the
90
- * persisted profile activation. When non-null it is the EXHAUSTIVE member
91
- * permission set — no extension defaults are merged — so raw capabilities stay
92
- * admin-only unless the profile lists them. A per-space
93
- * `role.member.permissions` override still takes precedence over this.
94
- */
95
- let activeProfileMemberPermissions: string[] | null = null;
96
-
97
- /** Set (or clear, with null) the active profile's member permission set. */
98
- export function setActiveProfileMemberPermissions(
99
- permissions: string[] | null,
100
- ): void {
101
- activeProfileMemberPermissions = permissions;
102
- }
103
-
104
- /** Parse a permission list into a validated set (drops unknown names). */
105
- function toPermissionSet(list: string[]): Set<string> {
106
- return new Set(list.map((s) => s.trim()).filter((s) => isValidPermission(s)));
107
- }
108
-
109
- // ---------------------------------------------------------------------------
110
- // Seeded groups tracking
111
- // ---------------------------------------------------------------------------
112
-
113
- /**
114
- * Tracks which groups have had admins seeded to avoid redundant DB calls.
115
- * Exported for test isolation (tests should clear this in beforeEach).
116
- */
117
- export const seededSpaces = new Set<string>();
118
-
119
- // ---------------------------------------------------------------------------
120
- // System callers
121
- // ---------------------------------------------------------------------------
122
-
123
- /**
124
- * System callers — these identities get full permissions without DB lookup.
125
- * Used for scheduled tasks, internal system calls, etc.
126
- */
127
- const SYSTEM_CALLERS = new Set(["system"]);
128
-
129
- export function isSystemCaller(callerId: string): boolean {
130
- return SYSTEM_CALLERS.has(callerId);
131
- }
132
-
133
- // ---------------------------------------------------------------------------
134
- // Default role permissions
135
- // ---------------------------------------------------------------------------
136
-
137
- /** Built-in defaults for the member role */
138
- const DEFAULT_MEMBER_PERMISSIONS = new Set(["prompt", "prefs.get"]);
139
-
140
- /**
141
- * Compute the default permission set for a role, merging built-in defaults
142
- * with extension-registered defaults.
143
- *
144
- * - `admin` and `system` get all permissions (built-in + extension)
145
- * - `member` gets `prompt`, `prefs.get`, plus any extension permissions that list "member" in defaultRoles
146
- * - Other roles get extension permissions that list them in defaultRoles
147
- */
148
- function getDefaultPermissions(role: string): Set<string> {
149
- if (role === "admin" || role === "system") {
150
- return new Set(getAllPermissions());
151
- }
152
-
153
- const perms = new Set<string>(
154
- role === "member" ? DEFAULT_MEMBER_PERMISSIONS : [],
155
- );
156
-
157
- for (const [name, opts] of registeredPermissions) {
158
- if (opts.defaultRoles.includes(role)) {
159
- perms.add(name);
160
- }
161
- }
162
-
163
- return perms;
164
- }
165
-
166
- // ---------------------------------------------------------------------------
167
- // Permission resolution
168
- // ---------------------------------------------------------------------------
169
-
170
- /**
171
- * Load the permission set for a role in a group.
172
- * Checks group_config for "role.<name>.permissions" override,
173
- * falls back to defaults (built-in + extension).
174
- */
175
- export function getRolePermissions(
176
- db: Db,
177
- spaceId: string,
178
- role: string,
179
- ): Set<string> {
180
- if (role === "system") return getDefaultPermissions("system");
181
-
182
- const key = `role.${role}.permissions`;
183
- const stored = db.getSpaceConfig(spaceId, key);
184
-
185
- // Explicit per-space override wins over everything.
186
- if (stored !== null) {
187
- return toPermissionSet(stored.split(","));
188
- }
189
-
190
- // An active profile sets the exhaustive member permission set (project-wide),
191
- // ahead of built-in/extension defaults. Members only; profiles never widen
192
- // admin/system. Guard with Array.isArray so a malformed persisted activation
193
- // (missing/non-array value) falls back to defaults instead of throwing here.
194
- if (role === "member" && Array.isArray(activeProfileMemberPermissions)) {
195
- return toPermissionSet(activeProfileMemberPermissions);
196
- }
197
-
198
- return getDefaultPermissions(role);
199
- }
200
-
201
- export function hasPermission(
202
- db: Db,
203
- spaceId: string,
204
- role: string,
205
- permission: string,
206
- ): boolean {
207
- return getRolePermissions(db, spaceId, role).has(permission);
208
- }
209
-
210
- export function resolveRole(
211
- db: Db,
212
- spaceId: string,
213
- platformUserId: string,
214
- seededAdmins: string[],
215
- displayName?: string | null,
216
- ): string {
217
- // System callers bypass DB entirely
218
- if (isSystemCaller(platformUserId)) return "system";
219
-
220
- if (seededAdmins.length > 0 && !seededSpaces.has(spaceId)) {
221
- db.seedAdmins(spaceId, seededAdmins);
222
- seededSpaces.add(spaceId);
223
- }
224
-
225
- db.upsertMember(spaceId, platformUserId, displayName);
226
-
227
- return db.getRole(spaceId, platformUserId) ?? "member";
228
- }
1
+ import type { Db } from "../storage/db.js";
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Built-in permissions (static, cannot be overridden)
5
+ // ---------------------------------------------------------------------------
6
+
7
+ const BUILT_IN_PERMISSIONS = new Set([
8
+ "prompt",
9
+ "stop",
10
+ "compact",
11
+ "clear",
12
+ "tasks.list",
13
+ "tasks.create",
14
+ "tasks.pause",
15
+ "tasks.resume",
16
+ "tasks.delete",
17
+ "config.get",
18
+ "config.set",
19
+ "prefs.get",
20
+ "prefs.set",
21
+ "roles.list",
22
+ "roles.grant",
23
+ "roles.revoke",
24
+ "permissions.get",
25
+ "permissions.set",
26
+ "spaces.list",
27
+ "spaces.rename",
28
+ "spaces.delete",
29
+ /** Purge inbox/outbox media files. */
30
+ "media.purge",
31
+ /** Host Text-to-Speech (/api/tts); admin-only by default. */
32
+ "tts.synthesize",
33
+ /** Mute/unmute users and list mutes; admin-only by default. */
34
+ "mutes.list",
35
+ "mutes.mute",
36
+ "mutes.unmute",
37
+ ]);
38
+
39
+ // ---------------------------------------------------------------------------
40
+ // Extension-registered permissions (dynamic, added at runtime)
41
+ // ---------------------------------------------------------------------------
42
+
43
+ const registeredPermissions = new Map<string, { defaultRoles: string[] }>();
44
+
45
+ /**
46
+ * Register a new permission from an extension.
47
+ * Throws if the name collides with a built-in permission.
48
+ */
49
+ export function registerPermission(
50
+ name: string,
51
+ opts: { defaultRoles: string[] },
52
+ ): void {
53
+ if (BUILT_IN_PERMISSIONS.has(name)) {
54
+ throw new Error(
55
+ `Permission "${name}" is a built-in and cannot be overridden`,
56
+ );
57
+ }
58
+ registeredPermissions.set(name, opts);
59
+ }
60
+
61
+ /**
62
+ * Get all valid permission names (built-in + extension-registered).
63
+ */
64
+ export function getAllPermissions(): string[] {
65
+ return [...BUILT_IN_PERMISSIONS, ...registeredPermissions.keys()];
66
+ }
67
+
68
+ /**
69
+ * Check if a permission name is valid (built-in or registered).
70
+ */
71
+ export function isValidPermission(name: string): boolean {
72
+ return BUILT_IN_PERMISSIONS.has(name) || registeredPermissions.has(name);
73
+ }
74
+
75
+ /**
76
+ * Clear all registered extension permissions. For test isolation only.
77
+ */
78
+ export function resetPermissions(): void {
79
+ registeredPermissions.clear();
80
+ activeProfileMemberPermissions = null;
81
+ }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Active applicative profile (project-wide)
85
+ // ---------------------------------------------------------------------------
86
+
87
+ /**
88
+ * Member permission set imposed by the active applicative profile, if any.
89
+ * Project-wide (one profile per deployment): set once at startup from the
90
+ * persisted profile activation. When non-null it is the EXHAUSTIVE member
91
+ * permission set — no extension defaults are merged — so raw capabilities stay
92
+ * admin-only unless the profile lists them. A per-space
93
+ * `role.member.permissions` override still takes precedence over this.
94
+ */
95
+ let activeProfileMemberPermissions: string[] | null = null;
96
+
97
+ /** Set (or clear, with null) the active profile's member permission set. */
98
+ export function setActiveProfileMemberPermissions(
99
+ permissions: string[] | null,
100
+ ): void {
101
+ activeProfileMemberPermissions = permissions;
102
+ }
103
+
104
+ /** Parse a permission list into a validated set (drops unknown names). */
105
+ function toPermissionSet(list: string[]): Set<string> {
106
+ return new Set(list.map((s) => s.trim()).filter((s) => isValidPermission(s)));
107
+ }
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // Seeded groups tracking
111
+ // ---------------------------------------------------------------------------
112
+
113
+ /**
114
+ * Tracks which groups have had admins seeded to avoid redundant DB calls.
115
+ * Exported for test isolation (tests should clear this in beforeEach).
116
+ */
117
+ export const seededSpaces = new Set<string>();
118
+
119
+ // ---------------------------------------------------------------------------
120
+ // System callers
121
+ // ---------------------------------------------------------------------------
122
+
123
+ /**
124
+ * System callers — these identities get full permissions without DB lookup.
125
+ * Used for scheduled tasks, internal system calls, etc.
126
+ */
127
+ const SYSTEM_CALLERS = new Set(["system"]);
128
+
129
+ export function isSystemCaller(callerId: string): boolean {
130
+ return SYSTEM_CALLERS.has(callerId);
131
+ }
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // Default role permissions
135
+ // ---------------------------------------------------------------------------
136
+
137
+ /** Built-in defaults for the member role */
138
+ const DEFAULT_MEMBER_PERMISSIONS = new Set(["prompt", "prefs.get"]);
139
+
140
+ /**
141
+ * Compute the default permission set for a role, merging built-in defaults
142
+ * with extension-registered defaults.
143
+ *
144
+ * - `admin` and `system` get all permissions (built-in + extension)
145
+ * - `member` gets `prompt`, `prefs.get`, plus any extension permissions that list "member" in defaultRoles
146
+ * - Other roles get extension permissions that list them in defaultRoles
147
+ */
148
+ function getDefaultPermissions(role: string): Set<string> {
149
+ if (role === "admin" || role === "system") {
150
+ return new Set(getAllPermissions());
151
+ }
152
+
153
+ const perms = new Set<string>(
154
+ role === "member" ? DEFAULT_MEMBER_PERMISSIONS : [],
155
+ );
156
+
157
+ for (const [name, opts] of registeredPermissions) {
158
+ if (opts.defaultRoles.includes(role)) {
159
+ perms.add(name);
160
+ }
161
+ }
162
+
163
+ return perms;
164
+ }
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // Permission resolution
168
+ // ---------------------------------------------------------------------------
169
+
170
+ /**
171
+ * Load the permission set for a role in a group.
172
+ * Checks group_config for "role.<name>.permissions" override,
173
+ * falls back to defaults (built-in + extension).
174
+ */
175
+ export function getRolePermissions(
176
+ db: Db,
177
+ spaceId: string,
178
+ role: string,
179
+ ): Set<string> {
180
+ if (role === "system") return getDefaultPermissions("system");
181
+
182
+ const key = `role.${role}.permissions`;
183
+ const stored = db.getSpaceConfig(spaceId, key);
184
+
185
+ // Explicit per-space override wins over everything.
186
+ if (stored !== null) {
187
+ return toPermissionSet(stored.split(","));
188
+ }
189
+
190
+ // An active profile sets the exhaustive member permission set (project-wide),
191
+ // ahead of built-in/extension defaults. Members only; profiles never widen
192
+ // admin/system. Guard with Array.isArray so a malformed persisted activation
193
+ // (missing/non-array value) falls back to defaults instead of throwing here.
194
+ if (role === "member" && Array.isArray(activeProfileMemberPermissions)) {
195
+ return toPermissionSet(activeProfileMemberPermissions);
196
+ }
197
+
198
+ return getDefaultPermissions(role);
199
+ }
200
+
201
+ export function hasPermission(
202
+ db: Db,
203
+ spaceId: string,
204
+ role: string,
205
+ permission: string,
206
+ ): boolean {
207
+ return getRolePermissions(db, spaceId, role).has(permission);
208
+ }
209
+
210
+ export function resolveRole(
211
+ db: Db,
212
+ spaceId: string,
213
+ platformUserId: string,
214
+ seededAdmins: string[],
215
+ displayName?: string | null,
216
+ ): string {
217
+ // System callers bypass DB entirely
218
+ if (isSystemCaller(platformUserId)) return "system";
219
+
220
+ if (seededAdmins.length > 0 && !seededSpaces.has(spaceId)) {
221
+ db.seedAdmins(spaceId, seededAdmins);
222
+ seededSpaces.add(spaceId);
223
+ }
224
+
225
+ db.upsertMember(spaceId, platformUserId, displayName);
226
+
227
+ return db.getRole(spaceId, platformUserId) ?? "member";
228
+ }