mercury-agent 0.5.13 → 0.5.15
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/README.md +4 -1
- package/docs/rate-limiting.md +10 -0
- package/package.json +1 -1
- package/src/core/routes/dashboard.ts +35 -7
- package/src/core/runtime.ts +10 -3
package/README.md
CHANGED
|
@@ -403,6 +403,9 @@ Supported OAuth providers: Anthropic, GitHub Copilot, Google Gemini CLI, Antigra
|
|
|
403
403
|
| `MERCURY_CONTAINER_TIMEOUT_MS` | `300000` | Container timeout (5 min) |
|
|
404
404
|
| `MERCURY_CONTAINER_RUNTIME` | `runc` | `runc` (default) or `runsc` ([gVisor](https://gvisor.dev)) |
|
|
405
405
|
| `MERCURY_CONTAINER_BWRAP_DOCKER_COMPAT` | `false` | Set `true` on Linux Docker Engine (see note below) |
|
|
406
|
+
| `MERCURY_AGENT_ID` | — | Unique ID for this assistant (required when running multiple assistants on the same Docker daemon) |
|
|
407
|
+
|
|
408
|
+
> **Multiple assistants on the same machine:** Mercury builds a derived Docker image (`mercury-agent-ext:<hash>`) from the base image plus your extensions. When a new image is built, older tags in the same repo are pruned. If two assistants share a Docker daemon without distinct `MERCURY_AGENT_ID` values, they share the same image repo and **one will silently prune the other's image**, causing container launch failures. Set `MERCURY_AGENT_ID` to a unique value per project (e.g. in `.env`). Cloud console deployments set this automatically.
|
|
406
409
|
|
|
407
410
|
> **Linux Docker Engine:** Mercury uses [bubblewrap](https://github.com/containers/bubblewrap) for in-container sandboxing. On Linux Docker Engine (not Docker Desktop), bwrap cannot mount `/proc` without extra privileges. Either set `container_bwrap_docker_compat: true` in `mercury.yaml` (adds `--privileged` to `docker run`), or install [gVisor](https://gvisor.dev/docs/user_guide/install/) and set `MERCURY_CONTAINER_RUNTIME=runsc` to skip bwrap entirely.
|
|
408
411
|
|
|
@@ -440,7 +443,7 @@ runtime:
|
|
|
440
443
|
|
|
441
444
|
`admin_ids` values are platform-specific identifiers — check the dashboard Conversations page to see the format (e.g. WhatsApp LID digits, Telegram numeric user ID).
|
|
442
445
|
|
|
443
|
-
Auto-created spaces are seeded with `trigger.match=always`, `context.mode=context`, and the configured member permissions.
|
|
446
|
+
Auto-created spaces are seeded with `trigger.match=always`, `context.mode=context`, and the configured member permissions. Changing `rate_limit_daily_member` in `mercury.yaml` propagates to all auto-created spaces immediately — no per-space update needed. Explicit per-space overrides (set via dashboard or API) still take precedence.
|
|
444
447
|
|
|
445
448
|
### Per-space Config
|
|
446
449
|
|
package/docs/rate-limiting.md
CHANGED
|
@@ -198,6 +198,16 @@ curl -X PUT http://localhost:8787/api/config \
|
|
|
198
198
|
| Not set | No daily limit for this role (burst limiter still applies) |
|
|
199
199
|
| Invalid (NaN) | Treated as "not set" |
|
|
200
200
|
|
|
201
|
+
### Daily limit precedence
|
|
202
|
+
|
|
203
|
+
When resolving the effective daily limit for a role, Mercury uses this order:
|
|
204
|
+
|
|
205
|
+
1. **Explicit per-space override** (set via dashboard, `mrctl`, or API) — always wins
|
|
206
|
+
2. **Global config** (`rate_limit_daily_member` / `rate_limit_daily_admin` from `mercury.yaml` or env) — applies to all spaces without an explicit override
|
|
207
|
+
3. **No limit** (0) — if the global config is 0 or unset, no daily cap is enforced
|
|
208
|
+
|
|
209
|
+
Auto-created DM spaces (from `dm_auto_space`) seed a `rate_limit.member` value on first contact, but this seed is treated as a deployment default, not an admin override. Changing `rate_limit_daily_member` in `mercury.yaml` takes effect immediately across all auto-created spaces — no DB migration or per-space update needed. If you explicitly set a per-space limit via the dashboard or API, that override takes precedence over the global config.
|
|
210
|
+
|
|
201
211
|
### How the two layers interact
|
|
202
212
|
|
|
203
213
|
```
|
package/package.json
CHANGED
|
@@ -531,16 +531,26 @@ export function createDashboardRoutes(ctx: DashboardContext) {
|
|
|
531
531
|
const memberRaw = core.db.getSpaceConfig(spaceId, "rate_limit.member");
|
|
532
532
|
const adminRaw = core.db.getSpaceConfig(spaceId, "rate_limit.admin");
|
|
533
533
|
|
|
534
|
+
const isSeeded = (key: string) =>
|
|
535
|
+
core.db.getSpaceConfig(spaceId, key) !== null &&
|
|
536
|
+
core.db.getSpaceConfigUpdatedBy(spaceId, key) === "dm-auto-space";
|
|
537
|
+
|
|
534
538
|
const effectiveBurst = burstRaw ?? String(cfg.rateLimitPerUser);
|
|
535
539
|
const effectiveMember =
|
|
536
|
-
memberRaw
|
|
537
|
-
|
|
540
|
+
memberRaw && !isSeeded("rate_limit.member")
|
|
541
|
+
? memberRaw
|
|
542
|
+
: cfg.rateLimitDailyMember > 0
|
|
543
|
+
? String(cfg.rateLimitDailyMember)
|
|
544
|
+
: "0";
|
|
538
545
|
const effectiveAdmin =
|
|
539
|
-
adminRaw
|
|
540
|
-
|
|
546
|
+
adminRaw && !isSeeded("rate_limit.admin")
|
|
547
|
+
? adminRaw
|
|
548
|
+
: cfg.rateLimitDailyAdmin > 0
|
|
549
|
+
? String(cfg.rateLimitDailyAdmin)
|
|
550
|
+
: "0";
|
|
541
551
|
|
|
542
552
|
const hasOverride = (key: string) =>
|
|
543
|
-
core.db.getSpaceConfig(spaceId, key) !== null;
|
|
553
|
+
core.db.getSpaceConfig(spaceId, key) !== null && !isSeeded(key);
|
|
544
554
|
|
|
545
555
|
const resetBtn = (key: string) =>
|
|
546
556
|
hasOverride(key)
|
|
@@ -1970,15 +1980,33 @@ export function createDashboardRoutes(ctx: DashboardContext) {
|
|
|
1970
1980
|
|
|
1971
1981
|
const modelSelectorPanel = (() => {
|
|
1972
1982
|
if (isMultiLeg) {
|
|
1983
|
+
const chainFromEnv = !!process.env.MERCURY_MODEL_CHAIN;
|
|
1984
|
+
const chainYaml = cfg.resolvedModelChain
|
|
1985
|
+
.map(
|
|
1986
|
+
(leg) =>
|
|
1987
|
+
` - provider: ${leg.provider}\n model: ${leg.model}`,
|
|
1988
|
+
)
|
|
1989
|
+
.join("\n");
|
|
1990
|
+
const configSnippet = chainFromEnv
|
|
1991
|
+
? `MERCURY_MODEL_CHAIN='${JSON.stringify(cfg.resolvedModelChain.map((l) => ({ provider: l.provider, model: l.model })))}'`
|
|
1992
|
+
: `model:\n chain:\n${chainYaml}`;
|
|
1993
|
+
const sourceLabel = chainFromEnv
|
|
1994
|
+
? `<span class="mono">MERCURY_MODEL_CHAIN</span> env var in <span class="mono">.env</span>`
|
|
1995
|
+
: `<span class="mono">model.chain</span> in <span class="mono">mercury.yaml</span>`;
|
|
1996
|
+
|
|
1973
1997
|
return `
|
|
1974
1998
|
<div class="panel" style="margin-bottom:16px">
|
|
1975
1999
|
<div class="panel-header" style="font-weight:600;font-size:13px">Active model</div>
|
|
1976
2000
|
<div class="panel-body">
|
|
1977
2001
|
${renderModelBlock(cfg)}
|
|
1978
2002
|
<p class="muted" style="font-size:12px;margin:8px 0 0">
|
|
1979
|
-
|
|
1980
|
-
<span class="mono">MERCURY_MODEL_CHAIN</span> — edit those to change.
|
|
2003
|
+
Configured via ${sourceLabel}. Edit that file to change.
|
|
1981
2004
|
</p>
|
|
2005
|
+
<details style="margin-top:8px">
|
|
2006
|
+
<summary class="muted" style="font-size:12px;cursor:pointer">Copy config</summary>
|
|
2007
|
+
<pre style="margin:6px 0 0;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius,4px);font-size:12px;overflow-x:auto;cursor:pointer;position:relative"
|
|
2008
|
+
onclick="navigator.clipboard.writeText(this.textContent.trim());var s=this.querySelector('.copy-hint');if(s){s.textContent='Copied!';setTimeout(function(){s.textContent='Click to copy'},1500)}">${escapeHtml(configSnippet)}<span class="copy-hint muted" style="position:absolute;top:4px;right:8px;font-size:11px">Click to copy</span></pre>
|
|
2009
|
+
</details>
|
|
1982
2010
|
</div>
|
|
1983
2011
|
</div>`;
|
|
1984
2012
|
}
|
package/src/core/runtime.ts
CHANGED
|
@@ -405,9 +405,16 @@ export class MercuryCoreRuntime {
|
|
|
405
405
|
: route.role === "admin"
|
|
406
406
|
? this.config.rateLimitDailyAdmin
|
|
407
407
|
: 0;
|
|
408
|
-
const
|
|
409
|
-
roleLimitRaw
|
|
410
|
-
(
|
|
408
|
+
const isSeededDefault =
|
|
409
|
+
roleLimitRaw !== null &&
|
|
410
|
+
this.db.getSpaceConfigUpdatedBy(message.spaceId, roleKey) ===
|
|
411
|
+
"dm-auto-space";
|
|
412
|
+
const effectiveDailyRaw = isSeededDefault
|
|
413
|
+
? globalDailyLimit > 0
|
|
414
|
+
? String(globalDailyLimit)
|
|
415
|
+
: null
|
|
416
|
+
: (roleLimitRaw ??
|
|
417
|
+
(globalDailyLimit > 0 ? String(globalDailyLimit) : null));
|
|
411
418
|
if (effectiveDailyRaw !== null) {
|
|
412
419
|
const roleLimit = Number.parseInt(effectiveDailyRaw, 10);
|
|
413
420
|
if (!Number.isNaN(roleLimit) && roleLimit > 0) {
|