mercury-agent 0.4.7 → 0.4.8
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/docs/auth/dashboard.md +28 -28
- package/examples/extensions/voice-synth/index.ts +94 -94
- package/package.json +1 -1
- package/src/adapters/whatsapp.ts +635 -632
- package/src/agent/model-capabilities.ts +231 -231
- package/src/bridges/discord.ts +178 -178
- package/src/bridges/slack.ts +179 -179
- package/src/bridges/teams.ts +162 -162
- package/src/bridges/telegram.ts +579 -579
- package/src/cli/mercury.ts +2536 -2536
- package/src/cli/whatsapp-auth.ts +263 -260
- package/src/config.ts +316 -316
- package/src/core/permissions.ts +196 -196
- package/src/core/router.ts +191 -191
- package/src/core/routes/chat.ts +175 -175
- package/src/core/routes/dashboard.ts +2491 -2491
- package/src/core/routes/messages.ts +37 -37
- package/src/core/routes/mutes.ts +95 -95
- package/src/core/routes/roles.ts +135 -135
- package/src/core/runtime.ts +1140 -1140
- package/src/core/task-scheduler.ts +139 -139
- package/src/extensions/catalog.ts +117 -117
- package/src/extensions/hooks.ts +161 -161
- package/src/extensions/installer.ts +306 -306
- package/src/extensions/loader.ts +271 -271
- package/src/extensions/permission-guard.ts +52 -52
- package/src/server.ts +391 -391
- package/src/storage/db.ts +1625 -1625
- package/src/storage/pi-auth.ts +95 -95
- package/src/tts/azure.ts +52 -52
- package/src/tts/synthesize.ts +133 -133
package/src/core/permissions.ts
CHANGED
|
@@ -1,196 +1,196 @@
|
|
|
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
|
-
}
|
|
81
|
-
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
// Seeded groups tracking
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Tracks which groups have had admins seeded to avoid redundant DB calls.
|
|
88
|
-
* Exported for test isolation (tests should clear this in beforeEach).
|
|
89
|
-
*/
|
|
90
|
-
export const seededSpaces = new Set<string>();
|
|
91
|
-
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
// System callers
|
|
94
|
-
// ---------------------------------------------------------------------------
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* System callers — these identities get full permissions without DB lookup.
|
|
98
|
-
* Used for scheduled tasks, internal system calls, etc.
|
|
99
|
-
*/
|
|
100
|
-
const SYSTEM_CALLERS = new Set(["system"]);
|
|
101
|
-
|
|
102
|
-
export function isSystemCaller(callerId: string): boolean {
|
|
103
|
-
return SYSTEM_CALLERS.has(callerId);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ---------------------------------------------------------------------------
|
|
107
|
-
// Default role permissions
|
|
108
|
-
// ---------------------------------------------------------------------------
|
|
109
|
-
|
|
110
|
-
/** Built-in defaults for the member role */
|
|
111
|
-
const DEFAULT_MEMBER_PERMISSIONS = new Set(["prompt", "prefs.get"]);
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Compute the default permission set for a role, merging built-in defaults
|
|
115
|
-
* with extension-registered defaults.
|
|
116
|
-
*
|
|
117
|
-
* - `admin` and `system` get all permissions (built-in + extension)
|
|
118
|
-
* - `member` gets `prompt`, `prefs.get`, plus any extension permissions that list "member" in defaultRoles
|
|
119
|
-
* - Other roles get extension permissions that list them in defaultRoles
|
|
120
|
-
*/
|
|
121
|
-
function getDefaultPermissions(role: string): Set<string> {
|
|
122
|
-
if (role === "admin" || role === "system") {
|
|
123
|
-
return new Set(getAllPermissions());
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const perms = new Set<string>(
|
|
127
|
-
role === "member" ? DEFAULT_MEMBER_PERMISSIONS : [],
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
for (const [name, opts] of registeredPermissions) {
|
|
131
|
-
if (opts.defaultRoles.includes(role)) {
|
|
132
|
-
perms.add(name);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return perms;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ---------------------------------------------------------------------------
|
|
140
|
-
// Permission resolution
|
|
141
|
-
// ---------------------------------------------------------------------------
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Load the permission set for a role in a group.
|
|
145
|
-
* Checks group_config for "role.<name>.permissions" override,
|
|
146
|
-
* falls back to defaults (built-in + extension).
|
|
147
|
-
*/
|
|
148
|
-
export function getRolePermissions(
|
|
149
|
-
db: Db,
|
|
150
|
-
spaceId: string,
|
|
151
|
-
role: string,
|
|
152
|
-
): Set<string> {
|
|
153
|
-
if (role === "system") return getDefaultPermissions("system");
|
|
154
|
-
|
|
155
|
-
const key = `role.${role}.permissions`;
|
|
156
|
-
const stored = db.getSpaceConfig(spaceId, key);
|
|
157
|
-
|
|
158
|
-
if (stored !== null) {
|
|
159
|
-
const perms = stored
|
|
160
|
-
.split(",")
|
|
161
|
-
.map((s) => s.trim())
|
|
162
|
-
.filter((s) => isValidPermission(s));
|
|
163
|
-
return new Set(perms);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return getDefaultPermissions(role);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
export function hasPermission(
|
|
170
|
-
db: Db,
|
|
171
|
-
spaceId: string,
|
|
172
|
-
role: string,
|
|
173
|
-
permission: string,
|
|
174
|
-
): boolean {
|
|
175
|
-
return getRolePermissions(db, spaceId, role).has(permission);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export function resolveRole(
|
|
179
|
-
db: Db,
|
|
180
|
-
spaceId: string,
|
|
181
|
-
platformUserId: string,
|
|
182
|
-
seededAdmins: string[],
|
|
183
|
-
displayName?: string | null,
|
|
184
|
-
): string {
|
|
185
|
-
// System callers bypass DB entirely
|
|
186
|
-
if (isSystemCaller(platformUserId)) return "system";
|
|
187
|
-
|
|
188
|
-
if (seededAdmins.length > 0 && !seededSpaces.has(spaceId)) {
|
|
189
|
-
db.seedAdmins(spaceId, seededAdmins);
|
|
190
|
-
seededSpaces.add(spaceId);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
db.upsertMember(spaceId, platformUserId, displayName);
|
|
194
|
-
|
|
195
|
-
return db.getRole(spaceId, platformUserId) ?? "member";
|
|
196
|
-
}
|
|
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
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Seeded groups tracking
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Tracks which groups have had admins seeded to avoid redundant DB calls.
|
|
88
|
+
* Exported for test isolation (tests should clear this in beforeEach).
|
|
89
|
+
*/
|
|
90
|
+
export const seededSpaces = new Set<string>();
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// System callers
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* System callers — these identities get full permissions without DB lookup.
|
|
98
|
+
* Used for scheduled tasks, internal system calls, etc.
|
|
99
|
+
*/
|
|
100
|
+
const SYSTEM_CALLERS = new Set(["system"]);
|
|
101
|
+
|
|
102
|
+
export function isSystemCaller(callerId: string): boolean {
|
|
103
|
+
return SYSTEM_CALLERS.has(callerId);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---------------------------------------------------------------------------
|
|
107
|
+
// Default role permissions
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
/** Built-in defaults for the member role */
|
|
111
|
+
const DEFAULT_MEMBER_PERMISSIONS = new Set(["prompt", "prefs.get"]);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Compute the default permission set for a role, merging built-in defaults
|
|
115
|
+
* with extension-registered defaults.
|
|
116
|
+
*
|
|
117
|
+
* - `admin` and `system` get all permissions (built-in + extension)
|
|
118
|
+
* - `member` gets `prompt`, `prefs.get`, plus any extension permissions that list "member" in defaultRoles
|
|
119
|
+
* - Other roles get extension permissions that list them in defaultRoles
|
|
120
|
+
*/
|
|
121
|
+
function getDefaultPermissions(role: string): Set<string> {
|
|
122
|
+
if (role === "admin" || role === "system") {
|
|
123
|
+
return new Set(getAllPermissions());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const perms = new Set<string>(
|
|
127
|
+
role === "member" ? DEFAULT_MEMBER_PERMISSIONS : [],
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
for (const [name, opts] of registeredPermissions) {
|
|
131
|
+
if (opts.defaultRoles.includes(role)) {
|
|
132
|
+
perms.add(name);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return perms;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Permission resolution
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Load the permission set for a role in a group.
|
|
145
|
+
* Checks group_config for "role.<name>.permissions" override,
|
|
146
|
+
* falls back to defaults (built-in + extension).
|
|
147
|
+
*/
|
|
148
|
+
export function getRolePermissions(
|
|
149
|
+
db: Db,
|
|
150
|
+
spaceId: string,
|
|
151
|
+
role: string,
|
|
152
|
+
): Set<string> {
|
|
153
|
+
if (role === "system") return getDefaultPermissions("system");
|
|
154
|
+
|
|
155
|
+
const key = `role.${role}.permissions`;
|
|
156
|
+
const stored = db.getSpaceConfig(spaceId, key);
|
|
157
|
+
|
|
158
|
+
if (stored !== null) {
|
|
159
|
+
const perms = stored
|
|
160
|
+
.split(",")
|
|
161
|
+
.map((s) => s.trim())
|
|
162
|
+
.filter((s) => isValidPermission(s));
|
|
163
|
+
return new Set(perms);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return getDefaultPermissions(role);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function hasPermission(
|
|
170
|
+
db: Db,
|
|
171
|
+
spaceId: string,
|
|
172
|
+
role: string,
|
|
173
|
+
permission: string,
|
|
174
|
+
): boolean {
|
|
175
|
+
return getRolePermissions(db, spaceId, role).has(permission);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function resolveRole(
|
|
179
|
+
db: Db,
|
|
180
|
+
spaceId: string,
|
|
181
|
+
platformUserId: string,
|
|
182
|
+
seededAdmins: string[],
|
|
183
|
+
displayName?: string | null,
|
|
184
|
+
): string {
|
|
185
|
+
// System callers bypass DB entirely
|
|
186
|
+
if (isSystemCaller(platformUserId)) return "system";
|
|
187
|
+
|
|
188
|
+
if (seededAdmins.length > 0 && !seededSpaces.has(spaceId)) {
|
|
189
|
+
db.seedAdmins(spaceId, seededAdmins);
|
|
190
|
+
seededSpaces.add(spaceId);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
db.upsertMember(spaceId, platformUserId, displayName);
|
|
194
|
+
|
|
195
|
+
return db.getRole(spaceId, platformUserId) ?? "member";
|
|
196
|
+
}
|