mercury-agent 0.7.0-beta.1 → 0.7.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.
@@ -85,6 +85,11 @@ mercury.permission({ defaultRoles: ["admin", "member"] });
85
85
  - `defaultRoles` — roles that get this permission by default
86
86
  - `admin` always gets all permissions automatically
87
87
  - Per-space overrides in `space_config` take precedence
88
+ - **Applicative profiles override `defaultRoles`**: when the active profile
89
+ defines `member_permissions`, that list is exhaustive for the `member` role —
90
+ an extension is only usable by members if its name appears there. Mercury logs
91
+ a startup warning for each loaded extension whose `defaultRoles` include
92
+ `member` but which the profile excludes.
88
93
 
89
94
  Can only be called once per extension.
90
95
 
@@ -309,6 +309,7 @@ function localTimeoutMs(): number {
309
309
  type HookCtx = {
310
310
  db: { getSpaceConfig: (spaceId: string, key: string) => string | null };
311
311
  log: {
312
+ debug: (msg: string, extra?: unknown) => void;
312
313
  info: (msg: string, extra?: unknown) => void;
313
314
  warn: (msg: string, extra?: unknown) => void;
314
315
  error: (msg: string, extra?: unknown) => void;
@@ -399,6 +400,11 @@ export default function (mercury: {
399
400
 
400
401
  mercury.on("before_container", async (event, ctx) => {
401
402
  if (!ctx.hasCallerPermission(event.spaceId, event.callerId, EXT)) {
403
+ ctx.log.debug("Skipping voice transcription: caller lacks permission", {
404
+ extension: EXT,
405
+ spaceId: event.spaceId,
406
+ callerId: event.callerId,
407
+ });
402
408
  return undefined;
403
409
  }
404
410
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mercury-agent",
3
- "version": "0.7.0-beta.1",
3
+ "version": "0.7.0",
4
4
  "description": "Personal AI assistant for chat platforms (WhatsApp, Slack, Discord, Telegram)",
5
5
  "license": "MIT",
6
6
  "author": "Avishai Tsabari",
@@ -233,6 +233,31 @@ export function getActiveProfilePrompt(): string | null {
233
233
  return activeProfilePromptValue;
234
234
  }
235
235
 
236
+ /**
237
+ * Warn about loaded extensions whose permission grants `member` by default but
238
+ * whose permission name is missing from the active profile's exhaustive
239
+ * `member_permissions` list. The override itself is by design (profiles scope
240
+ * customer-facing members to least privilege); the warning exists because the
241
+ * resulting per-message hook skips are otherwise invisible in the logs.
242
+ */
243
+ export function logProfileMemberPermissionExclusions(
244
+ extensions: Array<{ name: string; permission?: { defaultRoles: string[] } }>,
245
+ memberPermissions: string[] | null,
246
+ log: { warn: (msg: string, obj?: Record<string, unknown>) => void },
247
+ ): void {
248
+ if (!Array.isArray(memberPermissions)) return;
249
+ // Match toPermissionSet's trimming so a padded entry doesn't false-positive.
250
+ const granted = new Set(memberPermissions.map((s) => s.trim()));
251
+ for (const ext of extensions) {
252
+ if (!ext.permission?.defaultRoles.includes("member")) continue;
253
+ if (granted.has(ext.name)) continue;
254
+ log.warn(
255
+ `Extension "${ext.name}" grants role "member" by default, but the active profile's member_permissions excludes it — member callers cannot use it unless a space-level permission override grants it. Add "${ext.name}" to the profile's member_permissions (or the deployed active-profile.json) to enable it.`,
256
+ { extension: ext.name },
257
+ );
258
+ }
259
+ }
260
+
236
261
  /**
237
262
  * Throw if any of the profile's declared `capabilities` is not present as an
238
263
  * installed extension directory under `<dataDir>/extensions`. Capabilities may
package/src/main.ts CHANGED
@@ -35,7 +35,11 @@ import { createChatShim } from "./chat-shim.js";
35
35
  import { loadConfig, resolveProjectPath } from "./config.js";
36
36
  import { createMessageHandler } from "./core/handler.js";
37
37
  import { setActiveProfileMemberPermissions } from "./core/permissions.js";
38
- import { loadActiveProfile, setActiveProfilePrompt } from "./core/profiles.js";
38
+ import {
39
+ loadActiveProfile,
40
+ logProfileMemberPermissionExclusions,
41
+ setActiveProfilePrompt,
42
+ } from "./core/profiles.js";
39
43
  import { MercuryCoreRuntime } from "./core/runtime.js";
40
44
  import { runStorageCleanup } from "./core/storage-cleanup.js";
41
45
  import { isOverQuota } from "./core/storage-guard.js";
@@ -228,6 +232,11 @@ async function main() {
228
232
  memberPermissions:
229
233
  activeProfile.memberPermissions?.join(",") ?? "(unscoped)",
230
234
  });
235
+ logProfileMemberPermissionExclusions(
236
+ registry.list(),
237
+ activeProfile.memberPermissions ?? null,
238
+ logger,
239
+ );
231
240
  }
232
241
 
233
242
  logExtensionCapabilityMismatches(