mercury-agent 0.4.28 → 0.5.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.
- package/docs/ARCHITECTURE.md +4 -0
- package/docs/ROADMAP.md +1 -1
- package/docs/{backlog/applicative-profiles.md → archive/2026-07-01-applicative-profiles.md} +29 -18
- package/docs/{backlog/caller-bound-capability-token.md → archive/2026-07-01-caller-bound-capability-token.md} +15 -11
- package/docs/authoring-profiles.md +174 -0
- package/docs/pending-updates/applicative-profiles.md +15 -0
- package/docs/pending-updates/caller-bound-capability-token.md +15 -0
- package/package.json +1 -1
- package/resources/templates/mercury.example.yaml +99 -84
- package/src/agent/container-runner.ts +1346 -1323
- package/src/cli/mrctl.ts +636 -611
- package/src/config-file.ts +540 -537
- package/src/config.ts +339 -324
- package/src/core/api.ts +125 -105
- package/src/core/caller-token.ts +101 -0
- package/src/core/permissions.ts +228 -196
- package/src/core/profiles.ts +271 -245
- package/src/core/routes/capability.ts +70 -0
- package/src/core/routes/index.ts +15 -14
- package/src/core/runtime.ts +1530 -1516
- package/src/extensions/api.ts +273 -252
- package/src/extensions/loader.ts +286 -271
- package/src/extensions/types.ts +517 -462
- package/src/main.ts +605 -586
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -28,3 +28,7 @@
|
|
|
28
28
|
## Extension Points
|
|
29
29
|
|
|
30
30
|
> Where the system is designed to be extended — plugin interfaces, hook points, configuration surfaces.
|
|
31
|
+
|
|
32
|
+
- **Applicative profiles** (`src/core/profiles.ts`) — a `mercury-profile.yaml` declares `capabilities`, an exhaustive `member_permissions` set, and a `system_prompt`. `applyProfile` validates capabilities and persists `.mercury/active-profile.json`; `main.ts` activates it at startup (project-wide). Member permission scoping is in `getRolePermissions` (`src/core/permissions.ts`). Authoring contract: `docs/authoring-profiles.md`.
|
|
33
|
+
- **Host-side capability broker** — extensions register `mercury.capability(name, handler)` (`src/extensions/types.ts`, `api.ts`); the agent invokes them from the container via `mrctl capability <name> <action>` → `POST /api/capability/:name/:action` (`src/core/routes/capability.ts`). Handlers run on the host with credentials that never enter the container; identity is the token-derived caller.
|
|
34
|
+
- **Caller-bound capability token** (`src/core/caller-token.ts`) — per-turn HMAC token minted at container spawn, made authoritative over spoofable identity headers in the control-plane API auth middleware (`src/core/api.ts`).
|
package/docs/ROADMAP.md
CHANGED
|
@@ -10,7 +10,7 @@ Single source of truth for priority. Rule: ≤15 min/week to maintain.
|
|
|
10
10
|
|
|
11
11
|
| Item | Doc | Notes |
|
|
12
12
|
|------|-----|-------|
|
|
13
|
-
|
|
|
13
|
+
| — | — | — |
|
|
14
14
|
|
|
15
15
|
## Now
|
|
16
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Applicative Profiles
|
|
2
2
|
|
|
3
|
-
**Status**:
|
|
3
|
+
**Status**: Done
|
|
4
4
|
**Slug**: applicative-profiles
|
|
5
5
|
**Created**: 2026-07-01
|
|
6
6
|
**Last updated**: 2026-07-01
|
|
@@ -70,7 +70,7 @@ Therefore **any credential injected into a customer's container is readable by t
|
|
|
70
70
|
| 1. Profile schema + permission scoping | `mercury-public` | New `mercury-profile.yaml` fields; profile-aware member permissions |
|
|
71
71
|
| 2. Host-side capability broker | `mercury-public` | Profile capabilities run host-side via `mrctl`→API, à la TradeStation; secrets stay on host |
|
|
72
72
|
| 3. Per-turn caller token | `mercury-public` | [[caller-bound-capability-token]] — trustworthy `callerId` on the host (dependency) |
|
|
73
|
-
| 4. Barber business logic | **private
|
|
73
|
+
| 4. Barber business logic | **private repo** `tagula-agents/tagula-agent-profiles` | Availability, 2 barbers, per-number limits, ownership rules — the moat |
|
|
74
74
|
|
|
75
75
|
### Part 1 — Schema + permission scoping
|
|
76
76
|
|
|
@@ -115,7 +115,7 @@ The broker's ownership checks require a `callerId` the customer cannot forge. To
|
|
|
115
115
|
|
|
116
116
|
### Part 4 — Barber business logic (private repo)
|
|
117
117
|
|
|
118
|
-
Lives in `tagula-agent-profiles
|
|
118
|
+
Lives in the private monorepo `Avishai-tsabari/tagula-agents` (`c:/code/agentic/tagula-agents`), under `tagula-agent-profiles/` (the repo also holds the cloud console). Checked out as a sibling of `mercury-public`. Contains the `mercury-profile.yaml`, the agent persona, and the host-side route implementations' business rules (availability windows, 2-barber scheduling, per-number booking limits, double-booking prevention). Consumes Parts 1-3 as the public contract; never touches `mercury-public`.
|
|
119
119
|
|
|
120
120
|
### Example manifest (in the private repo)
|
|
121
121
|
|
|
@@ -217,14 +217,15 @@ defaults:
|
|
|
217
217
|
- [x] Context for Claude pointers filled in
|
|
218
218
|
|
|
219
219
|
### Implementation
|
|
220
|
-
- [
|
|
221
|
-
- [
|
|
222
|
-
- [
|
|
223
|
-
- [
|
|
224
|
-
- [
|
|
225
|
-
- [
|
|
226
|
-
- [
|
|
227
|
-
- [
|
|
220
|
+
- [x] Step 1 — Extend profile schema (`capabilities`, `member_permissions`, `system_prompt`); swapped hand-rolled `parseSimpleYaml` for the `yaml` lib (multiline `system_prompt` support, less fragility)
|
|
221
|
+
- [x] Step 2 — Capability validation (`validateProfileCapabilities`, called in `applyProfile`); tests in `tests/applicative-profiles.test.ts`
|
|
222
|
+
- [x] Step 3 — Activation + config: `profile` config key; `applyProfile` persists `.mercury/active-profile.json`; `main.ts` reads it at startup (after extensions load) and calls `setActiveProfileMemberPermissions`
|
|
223
|
+
- [x] Step 4 — Profile-aware permissions: `getRolePermissions` uses the active profile's member set as exhaustive; precedence = explicit per-space override > profile > defaults; admin/system unaffected
|
|
224
|
+
- [x] Step 5 — Caller token dependency ([[caller-bound-capability-token]]) — implemented and committed
|
|
225
|
+
- [x] Step 5b — Inject `system_prompt` at container spawn (appended to `MERCURY_EXT_SYSTEM_PROMPT` in `runtime.ts`, ahead of per-space prompt)
|
|
226
|
+
- [x] Step 6 — Host-side capability broker: `mercury.capability(name, handler)` extension API; `POST /api/capability/:name/:action` route (auth = token-derived caller, authz = permission named after the capability); `registry.getCapability`; `mrctl capability <name> <action> [json]`
|
|
227
|
+
- [x] Step 7 — Tests: `tests/api.test.ts` capability broker (dispatch with token-derived caller; permission-denied → 403) + schema/scoping tests in `tests/applicative-profiles.test.ts`
|
|
228
|
+
- [x] Step 8 — Example config: profile + `mrctl capability` + `MERCURY_CALLER_TOKEN_KEY` notes in `mercury.example.yaml`
|
|
228
229
|
- [ ] `bun run check` passes
|
|
229
230
|
- [ ] No secrets or `.env` files committed
|
|
230
231
|
- [ ] No unrelated files modified
|
|
@@ -243,17 +244,27 @@ defaults:
|
|
|
243
244
|
## Retrospective
|
|
244
245
|
|
|
245
246
|
**Residual risks / follow-ups:**
|
|
246
|
-
-
|
|
247
|
-
-
|
|
247
|
+
- Barber business logic (availability, 2-barber, per-number limits) lives in the private `tagula-agents` repo — out of this repo's scope — accepted
|
|
248
|
+
- Ephemeral caller-token key breaks `mrctl` if the API and container-runner ever run in separate processes without `MERCURY_CALLER_TOKEN_KEY` set; documented in config + `docs/authoring-profiles.md` — accepted
|
|
249
|
+
- `mrctl capability <name>/<action>` are not URL-encoded (path traversal only reaches other RBAC-guarded routes, no escalation) — accepted
|
|
250
|
+
- Duplicate capability-name registration across two extensions silently first-wins in `registry.getCapability` — accepted
|
|
251
|
+
- Empty `member_permissions: []` in a manifest gives members zero permissions (author's explicit exhaustive choice) — accepted
|
|
248
252
|
|
|
249
253
|
**What changed from the plan:**
|
|
250
|
-
-
|
|
254
|
+
- Signing key is NOT derived from `API_SECRET` (the plan's fallback) — `API_SECRET` is readable in the container, so an ephemeral host-only random key is used instead (config `callerTokenKey` for multi-process).
|
|
255
|
+
- Caller token split into its own delivered dependency (`caller-bound-capability-token`) — also hardened TradeStation, which reads identity from the same auth context (no code change needed there).
|
|
256
|
+
- Replaced the hand-rolled `parseSimpleYaml` in `profiles.ts` with the `yaml` lib (multiline `system_prompt` support + less fragility); built-in profiles still parse.
|
|
257
|
+
- Profile activation is project-wide via a module-level registry + persisted `.mercury/active-profile.json`, not per-space `space_config` writes (simpler, works for later-created dm-auto-space spaces).
|
|
258
|
+
- Broker registration model chosen with the user: generic `mercury.capability(name, handler)` + `POST /api/capability/:name/:action` + `mrctl capability`, authorized by the permission named after the capability.
|
|
259
|
+
- Added `docs/authoring-profiles.md` as the contract for the external profiles repo.
|
|
251
260
|
|
|
252
261
|
**Key decisions made during implementation:**
|
|
253
|
-
-
|
|
262
|
+
- Token = HMAC-SHA256 over `base64url(payload).base64url(sig)`, constant-time compare with length guard; TTL = container timeout + 60s.
|
|
263
|
+
- `getRolePermissions` uses `Array.isArray` (not `!== null`) on the active-profile member set so a malformed `active-profile.json` falls back to defaults instead of throwing in the hot path (found in session review).
|
|
264
|
+
- Profile schema uses plain `z.object` (no `.strict()`), so unknown manifest keys are stripped, honoring the `2026-07-01-strict-yaml-schema-crash` post-mortem (warn/ignore unknown keys, fail only on invalid values); startup `loadActiveProfile` is try/catch → null.
|
|
254
265
|
|
|
255
266
|
**Architecture impact** (update `docs/ARCHITECTURE.md` if any of these apply):
|
|
256
|
-
- [
|
|
257
|
-
- [ ] New data model or schema change
|
|
258
|
-
- [
|
|
267
|
+
- [x] New package/module added — `src/core/caller-token.ts`, `src/core/routes/capability.ts`
|
|
268
|
+
- [ ] New data model or schema change (uses existing `space_config`; adds a local `.mercury/active-profile.json` file, no DB schema change)
|
|
269
|
+
- [x] New inter-package interaction — extension host-side capability handlers invoked via the control-plane API
|
|
259
270
|
- [ ] Deployment topology changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Caller-Bound Capability Token
|
|
2
2
|
|
|
3
|
-
**Status**:
|
|
3
|
+
**Status**: Done
|
|
4
4
|
**Slug**: caller-bound-capability-token
|
|
5
5
|
**Created**: 2026-07-01
|
|
6
6
|
**Last updated**: 2026-07-01
|
|
@@ -144,14 +144,14 @@ The signing key never enters the container. The container holds only its **own**
|
|
|
144
144
|
- [x] Context for Claude pointers filled in
|
|
145
145
|
|
|
146
146
|
### Implementation
|
|
147
|
-
- [
|
|
148
|
-
- [
|
|
149
|
-
- [
|
|
150
|
-
- [
|
|
151
|
-
- [
|
|
152
|
-
- [
|
|
153
|
-
- [
|
|
154
|
-
- [
|
|
147
|
+
- [x] Step 1 — Token helpers (`src/core/caller-token.ts`)
|
|
148
|
+
- [x] Step 2 — Mint at spawn (`container-runner.ts`; key added to `BLOCKED_ENV_VARS`)
|
|
149
|
+
- [x] Step 3 — Forward in mrctl (`x-mercury-token` header)
|
|
150
|
+
- [x] Step 4 — Verify host-side (`api.ts` middleware; token identity wins)
|
|
151
|
+
- [x] Step 5 — TradeStation guard: **no change needed** — it reads `c.get("auth")`, which now derives from the token
|
|
152
|
+
- [x] Step 6 — Tests (`tests/caller-token.test.ts` unit; `tests/api.test.ts` integration: token overrides spoofed header, invalid token → 401)
|
|
153
|
+
- [x] `bun run check` passes (one unrelated discord-adapter timeout flake under full parallel run; passes in isolation)
|
|
154
|
+
- [x] No secrets committed
|
|
155
155
|
|
|
156
156
|
---
|
|
157
157
|
|
|
@@ -168,10 +168,14 @@ The signing key never enters the container. The container holds only its **own**
|
|
|
168
168
|
- none
|
|
169
169
|
|
|
170
170
|
**What changed from the plan:**
|
|
171
|
-
-
|
|
171
|
+
- **Signing key is NOT derived from `API_SECRET`.** The plan suggested deriving it when unset, but `API_SECRET` is injected into containers — the agent could recompute the key and forge tokens. Instead: an optional host-only `callerTokenKey` (env `MERCURY_CALLER_TOKEN_KEY`, in `BLOCKED_ENV_VARS`), falling back to an **ephemeral random key** generated once per host process. Zero required config; nothing forgeable from inside the container.
|
|
172
|
+
- **TradeStation guard needed no code change** — it already reads identity from `c.get("auth")`, which the middleware now sets from the token.
|
|
173
|
+
- Token injected as container env `CALLER_TOKEN` (unprefixed, like `CALLER_ID`/`API_SECRET`), forwarded as `x-mercury-token`.
|
|
172
174
|
|
|
173
175
|
**Key decisions made during implementation:**
|
|
174
|
-
-
|
|
176
|
+
- HMAC-SHA256 over `base64url(payload).base64url(sig)`, payload `{c,s,exp}`; constant-time compare; no new dependency (`node:crypto`).
|
|
177
|
+
- TTL = container timeout + 60s buffer, so a token never expires mid-turn but stays short-lived vs the permanent shared secret.
|
|
178
|
+
- Header path retained as fallback → fully backward compatible; existing header-based tests and old container images keep working.
|
|
175
179
|
|
|
176
180
|
**Architecture impact:**
|
|
177
181
|
- [ ] New package/module added
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Authoring Applicative Profiles
|
|
2
|
+
|
|
3
|
+
The contract for building a **profile** — a package of deterministic business
|
|
4
|
+
logic that wraps raw capabilities (Calendar, email, …) and scopes what members
|
|
5
|
+
can do. Mercury (this repo) provides the schema, loader, permission scoping, and
|
|
6
|
+
the host-side capability broker; **profiles live in a separate repo** and depend
|
|
7
|
+
only on this contract.
|
|
8
|
+
|
|
9
|
+
> Layers: **Mercury** = platform (messaging, spaces, permissions, tokens,
|
|
10
|
+
> broker). **Capability extensions** = raw API access (e.g. `gws`), admin-only.
|
|
11
|
+
> **Profiles** = deterministic domain logic wrapping capabilities, member-facing.
|
|
12
|
+
|
|
13
|
+
## 1. Profile manifest — `mercury-profile.yaml`
|
|
14
|
+
|
|
15
|
+
```yaml
|
|
16
|
+
name: barber-appointments # lowercase-kebab; required
|
|
17
|
+
description: Appointment booking for a barber shop
|
|
18
|
+
version: 0.1.0
|
|
19
|
+
|
|
20
|
+
agents_md: ./AGENTS.md # optional; copied to the global AGENTS.md
|
|
21
|
+
|
|
22
|
+
# Raw capability extensions this profile requires to be installed. Validated at
|
|
23
|
+
# apply time — activation fails loudly if any are missing.
|
|
24
|
+
capabilities:
|
|
25
|
+
- gws
|
|
26
|
+
|
|
27
|
+
# Extensions this profile ships (copied into .mercury/extensions).
|
|
28
|
+
extensions:
|
|
29
|
+
- name: barber
|
|
30
|
+
source: ./extensions/barber
|
|
31
|
+
|
|
32
|
+
# EXHAUSTIVE member permission set while active. When present it REPLACES the
|
|
33
|
+
# default member permissions — nothing is merged in — so raw capabilities stay
|
|
34
|
+
# admin-only unless listed here. Omit entirely to keep built-in defaults.
|
|
35
|
+
member_permissions:
|
|
36
|
+
- prompt
|
|
37
|
+
- prefs.get
|
|
38
|
+
- barber # the profile's own capability, NOT gws
|
|
39
|
+
|
|
40
|
+
# Optional env the profile needs (host-side credentials for capabilities).
|
|
41
|
+
env:
|
|
42
|
+
- key: MERCURY_BARBER_BUSINESS_HOURS
|
|
43
|
+
description: "Business hours"
|
|
44
|
+
default: "09:00-18:00"
|
|
45
|
+
|
|
46
|
+
# Project-wide agent persona, injected into every container's system prompt.
|
|
47
|
+
system_prompt: |
|
|
48
|
+
You are a barber shop assistant. Help each customer book, view, and cancel
|
|
49
|
+
ONLY their own appointments. Never reveal other customers' schedules.
|
|
50
|
+
|
|
51
|
+
defaults:
|
|
52
|
+
trigger_patterns: always
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Apply it with `mercury profile apply <name|path|git-url>`. That validates
|
|
56
|
+
capabilities, copies extensions/AGENTS.md, and persists activation to
|
|
57
|
+
`.mercury/active-profile.json`, which Mercury loads at startup.
|
|
58
|
+
|
|
59
|
+
## 2. The capability broker (where the business logic runs)
|
|
60
|
+
|
|
61
|
+
A profile's privileged work runs **on the host**, so credentials never enter the
|
|
62
|
+
customer-controlled agent container. The profile's extension registers a
|
|
63
|
+
handler; the agent invokes it through a thin CLI.
|
|
64
|
+
|
|
65
|
+
### Register a handler (in the extension's `index.ts`)
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import type { ExtensionSetupFn } from "mercury-agent"; // types from this repo
|
|
69
|
+
|
|
70
|
+
const setup: ExtensionSetupFn = (mercury) => {
|
|
71
|
+
// Make the capability gate-able. Members get it via member_permissions.
|
|
72
|
+
mercury.permission({ defaultRoles: [] });
|
|
73
|
+
|
|
74
|
+
// Host-side credentials for the wrapped capability (never sent to the container).
|
|
75
|
+
mercury.env({ from: "MERCURY_BARBER_GOOGLE_CREDENTIALS" });
|
|
76
|
+
|
|
77
|
+
mercury.capability("barber", async (req, ctx) => {
|
|
78
|
+
// req.callerId is the TOKEN-DERIVED, unspoofable caller — safe for ownership.
|
|
79
|
+
switch (req.action) {
|
|
80
|
+
case "availability":
|
|
81
|
+
return { data: await listOpenSlots(req.body, ctx) };
|
|
82
|
+
case "book":
|
|
83
|
+
return { data: await book(req.callerId, req.body, ctx) };
|
|
84
|
+
case "cancel":
|
|
85
|
+
return { data: await cancelOwn(req.callerId, req.body, ctx) };
|
|
86
|
+
case "my-appointments":
|
|
87
|
+
return { data: await listOwn(req.callerId, ctx) };
|
|
88
|
+
default:
|
|
89
|
+
return { status: 400, data: { error: `unknown action: ${req.action}` } };
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export default setup;
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### The contract types (exported from this repo)
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
type CapabilityHandler = (
|
|
101
|
+
req: CapabilityRequest,
|
|
102
|
+
ctx: MercuryExtensionContext,
|
|
103
|
+
) => Promise<CapabilityResult>;
|
|
104
|
+
|
|
105
|
+
interface CapabilityRequest {
|
|
106
|
+
name: string; // capability name
|
|
107
|
+
action: string; // sub-action (book, cancel, …)
|
|
108
|
+
callerId: string; // token-derived, TRUSTWORTHY — use for ownership checks
|
|
109
|
+
spaceId: string;
|
|
110
|
+
body: unknown; // parsed JSON request body (or null)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
interface CapabilityResult {
|
|
114
|
+
status?: number; // default 200
|
|
115
|
+
data: unknown; // JSON-serializable response
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`ctx` (`MercuryExtensionContext`) gives you `db`, `config`, `log`, and
|
|
120
|
+
`hasCallerPermission(spaceId, callerId, permission)`. Store per-profile state via
|
|
121
|
+
`mercury.store` (namespaced KV) or your own tables through `ctx.db`.
|
|
122
|
+
|
|
123
|
+
### How the agent invokes it
|
|
124
|
+
|
|
125
|
+
Inside the container the agent runs:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
mrctl capability barber book '{"slot":"2026-07-02T15:00"}'
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
→ `POST /api/capability/barber/book`. Document these commands for the agent in
|
|
132
|
+
the extension's `SKILL.md` or the profile's `AGENTS.md`.
|
|
133
|
+
|
|
134
|
+
## 3. Permission & security model (non-negotiable)
|
|
135
|
+
|
|
136
|
+
- **`member_permissions` is exhaustive.** List every permission a member may
|
|
137
|
+
hold, including the capability name (e.g. `barber`). Anything not listed —
|
|
138
|
+
including raw capabilities like `gws` — is unavailable to members.
|
|
139
|
+
- **Authorization = permission named after the capability.** The broker route
|
|
140
|
+
requires the caller to hold the `<name>` permission; the same grant gates both
|
|
141
|
+
the `mrctl capability <name> …` CLI and the route. Keep capability name ==
|
|
142
|
+
permission name == the entry in `member_permissions`.
|
|
143
|
+
- **Credentials stay on the host.** Put capability credentials in host env
|
|
144
|
+
(`mercury.env`) / `mercury.store` and use them only inside the handler. Never
|
|
145
|
+
expose a raw capability CLI (e.g. `gws`) to members — its secret would be
|
|
146
|
+
readable inside the container.
|
|
147
|
+
- **Enforce ownership with `req.callerId`.** It is token-derived and cannot be
|
|
148
|
+
spoofed by the container. Never trust an id passed in `req.body`.
|
|
149
|
+
- **`callerId === "system"`** for scheduled/system runs — handle it explicitly
|
|
150
|
+
(e.g. skip per-customer ownership).
|
|
151
|
+
|
|
152
|
+
## 4. Suggested profile repo layout
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
<profile-repo>/
|
|
156
|
+
barber/ # one folder per profile
|
|
157
|
+
mercury-profile.yaml
|
|
158
|
+
AGENTS.md
|
|
159
|
+
extensions/
|
|
160
|
+
barber/
|
|
161
|
+
index.ts # registers permission + capability
|
|
162
|
+
package.json
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Depend on this repo (`mercury-agent`) for the types
|
|
166
|
+
(`ExtensionSetupFn`, `CapabilityRequest`, `CapabilityResult`,
|
|
167
|
+
`MercuryExtensionContext`). See `src/extensions/types.ts` for the full surface.
|
|
168
|
+
|
|
169
|
+
## 5. Local test loop
|
|
170
|
+
|
|
171
|
+
1. `mercury profile apply ./barber`
|
|
172
|
+
2. `mercury service install` (builds the derived image with the extension CLI)
|
|
173
|
+
3. DM the bot as a non-admin number; confirm you can `book`/`cancel` only your
|
|
174
|
+
own appointments and cannot reach `gws`/Gmail.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
slug: applicative-profiles
|
|
3
|
+
shipped: 2026-07-01
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## ROADMAP changes
|
|
7
|
+
|
|
8
|
+
**Remove from:** Next (table row where Item = applicative-profiles)
|
|
9
|
+
|
|
10
|
+
**Also pull:** none
|
|
11
|
+
|
|
12
|
+
**Add to Shipped table** (insert at top, after header row):
|
|
13
|
+
| **applicative-profiles** | 2026-07-01 | Generic profile layer: manifest schema, exhaustive member scoping, host-side capability broker |
|
|
14
|
+
|
|
15
|
+
**Milestone Map:** none
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
slug: caller-bound-capability-token
|
|
3
|
+
shipped: 2026-07-01
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## ROADMAP changes
|
|
7
|
+
|
|
8
|
+
**Remove from:** Next (table row where Item = caller-bound-capability-token)
|
|
9
|
+
|
|
10
|
+
**Also pull:** none
|
|
11
|
+
|
|
12
|
+
**Add to Shipped table** (insert at top, after header row):
|
|
13
|
+
| **caller-bound-capability-token** | 2026-07-01 | Per-turn unspoofable caller token for host API auth; also hardens TradeStation |
|
|
14
|
+
|
|
15
|
+
**Milestone Map:** none
|
package/package.json
CHANGED
|
@@ -1,84 +1,99 @@
|
|
|
1
|
-
# Copy to mercury.yaml in your project root (optional).
|
|
2
|
-
# Precedence: MERCURY_* env vars override this file when the env key is set.
|
|
3
|
-
# Secrets (API keys, tokens, MERCURY_API_SECRET, etc.) belong in .env only — not here.
|
|
4
|
-
|
|
5
|
-
# server:
|
|
6
|
-
# port: 8787
|
|
7
|
-
# bot_username: mercury
|
|
8
|
-
|
|
9
|
-
# model:
|
|
10
|
-
# chain:
|
|
11
|
-
# - provider: anthropic
|
|
12
|
-
# model: claude-sonnet-4-20250514
|
|
13
|
-
# # Legacy single primary + optional fallback (when chain is omitted):
|
|
14
|
-
# # provider: anthropic
|
|
15
|
-
# # model: claude-sonnet-4-20250514
|
|
16
|
-
# # fallback_provider: openai
|
|
17
|
-
# # fallback: gpt-4o-mini
|
|
18
|
-
# # max_retries_per_leg: 2
|
|
19
|
-
# # chain_budget_ms: 120000
|
|
20
|
-
# # capabilities: { tools: false }
|
|
21
|
-
|
|
22
|
-
# Top-level alias for model.chain (optional; model.chain wins if both are set):
|
|
23
|
-
# model_chain:
|
|
24
|
-
# - provider: google
|
|
25
|
-
# model: gemini-2.5-flash
|
|
26
|
-
|
|
27
|
-
# ingress:
|
|
28
|
-
# discord: false
|
|
29
|
-
# slack: false
|
|
30
|
-
# teams: false
|
|
31
|
-
# whatsapp: false
|
|
32
|
-
# telegram: false
|
|
33
|
-
|
|
34
|
-
# runtime:
|
|
35
|
-
# data_dir: .mercury
|
|
36
|
-
# # auth_path: ...
|
|
37
|
-
# # whatsapp_auth_dir: .mercury/whatsapp-auth
|
|
38
|
-
# max_concurrency: 2
|
|
39
|
-
# log_level: info
|
|
40
|
-
# log_format: text
|
|
41
|
-
# rate_limit_per_user: 10
|
|
42
|
-
# rate_limit_window_ms: 60000
|
|
43
|
-
# rate_limit_daily_member: 0 # 0 = unlimited; global daily message cap for members
|
|
44
|
-
# rate_limit_daily_admin: 0 # 0 = unlimited; global daily message cap for admins
|
|
45
|
-
|
|
46
|
-
# trigger:
|
|
47
|
-
# patterns: "@Mercury,Mercury"
|
|
48
|
-
# match: mention
|
|
49
|
-
|
|
50
|
-
# conditional_context:
|
|
51
|
-
# enabled: true
|
|
52
|
-
# classifier: heuristic
|
|
53
|
-
# # classifier_provider: groq
|
|
54
|
-
# # classifier_model: llama-3.3-70b-versatile
|
|
55
|
-
|
|
56
|
-
# compaction:
|
|
57
|
-
# keep_recent_tokens: 20000
|
|
58
|
-
# # After each full-session run, auto-compact if the pi session has more than this many entries (10–10000). Omit to disable.
|
|
59
|
-
# # auto_compact_threshold: 50
|
|
60
|
-
|
|
61
|
-
# agent:
|
|
62
|
-
# image: ghcr.io/avishai-tsabari/mercury-agent:latest
|
|
63
|
-
# container_timeout_ms: 300000
|
|
64
|
-
# container_bwrap_docker_compat: false
|
|
65
|
-
|
|
66
|
-
# discord:
|
|
67
|
-
# gateway_duration_ms: 600000
|
|
68
|
-
|
|
69
|
-
# telegram:
|
|
70
|
-
# format_enabled: true
|
|
71
|
-
|
|
72
|
-
# media:
|
|
73
|
-
# enabled: true
|
|
74
|
-
# max_size_mb: 10
|
|
75
|
-
|
|
76
|
-
# permissions:
|
|
77
|
-
# admins: ""
|
|
78
|
-
|
|
79
|
-
# dm_auto_space:
|
|
80
|
-
# enabled: false
|
|
81
|
-
# admin_ids: # auto-link to "main" space
|
|
82
|
-
# - "972501234567"
|
|
83
|
-
# default_system_prompt: "" # seeded into auto-created customer spaces
|
|
84
|
-
# default_member_permissions: "prompt,prefs.get" # restrict customers to chat only
|
|
1
|
+
# Copy to mercury.yaml in your project root (optional).
|
|
2
|
+
# Precedence: MERCURY_* env vars override this file when the env key is set.
|
|
3
|
+
# Secrets (API keys, tokens, MERCURY_API_SECRET, etc.) belong in .env only — not here.
|
|
4
|
+
|
|
5
|
+
# server:
|
|
6
|
+
# port: 8787
|
|
7
|
+
# bot_username: mercury
|
|
8
|
+
|
|
9
|
+
# model:
|
|
10
|
+
# chain:
|
|
11
|
+
# - provider: anthropic
|
|
12
|
+
# model: claude-sonnet-4-20250514
|
|
13
|
+
# # Legacy single primary + optional fallback (when chain is omitted):
|
|
14
|
+
# # provider: anthropic
|
|
15
|
+
# # model: claude-sonnet-4-20250514
|
|
16
|
+
# # fallback_provider: openai
|
|
17
|
+
# # fallback: gpt-4o-mini
|
|
18
|
+
# # max_retries_per_leg: 2
|
|
19
|
+
# # chain_budget_ms: 120000
|
|
20
|
+
# # capabilities: { tools: false }
|
|
21
|
+
|
|
22
|
+
# Top-level alias for model.chain (optional; model.chain wins if both are set):
|
|
23
|
+
# model_chain:
|
|
24
|
+
# - provider: google
|
|
25
|
+
# model: gemini-2.5-flash
|
|
26
|
+
|
|
27
|
+
# ingress:
|
|
28
|
+
# discord: false
|
|
29
|
+
# slack: false
|
|
30
|
+
# teams: false
|
|
31
|
+
# whatsapp: false
|
|
32
|
+
# telegram: false
|
|
33
|
+
|
|
34
|
+
# runtime:
|
|
35
|
+
# data_dir: .mercury
|
|
36
|
+
# # auth_path: ...
|
|
37
|
+
# # whatsapp_auth_dir: .mercury/whatsapp-auth
|
|
38
|
+
# max_concurrency: 2
|
|
39
|
+
# log_level: info
|
|
40
|
+
# log_format: text
|
|
41
|
+
# rate_limit_per_user: 10
|
|
42
|
+
# rate_limit_window_ms: 60000
|
|
43
|
+
# rate_limit_daily_member: 0 # 0 = unlimited; global daily message cap for members
|
|
44
|
+
# rate_limit_daily_admin: 0 # 0 = unlimited; global daily message cap for admins
|
|
45
|
+
|
|
46
|
+
# trigger:
|
|
47
|
+
# patterns: "@Mercury,Mercury"
|
|
48
|
+
# match: mention
|
|
49
|
+
|
|
50
|
+
# conditional_context:
|
|
51
|
+
# enabled: true
|
|
52
|
+
# classifier: heuristic
|
|
53
|
+
# # classifier_provider: groq
|
|
54
|
+
# # classifier_model: llama-3.3-70b-versatile
|
|
55
|
+
|
|
56
|
+
# compaction:
|
|
57
|
+
# keep_recent_tokens: 20000
|
|
58
|
+
# # After each full-session run, auto-compact if the pi session has more than this many entries (10–10000). Omit to disable.
|
|
59
|
+
# # auto_compact_threshold: 50
|
|
60
|
+
|
|
61
|
+
# agent:
|
|
62
|
+
# image: ghcr.io/avishai-tsabari/mercury-agent:latest
|
|
63
|
+
# container_timeout_ms: 300000
|
|
64
|
+
# container_bwrap_docker_compat: false
|
|
65
|
+
|
|
66
|
+
# discord:
|
|
67
|
+
# gateway_duration_ms: 600000
|
|
68
|
+
|
|
69
|
+
# telegram:
|
|
70
|
+
# format_enabled: true
|
|
71
|
+
|
|
72
|
+
# media:
|
|
73
|
+
# enabled: true
|
|
74
|
+
# max_size_mb: 10
|
|
75
|
+
|
|
76
|
+
# permissions:
|
|
77
|
+
# admins: ""
|
|
78
|
+
|
|
79
|
+
# dm_auto_space:
|
|
80
|
+
# enabled: false
|
|
81
|
+
# admin_ids: # auto-link to "main" space
|
|
82
|
+
# - "972501234567"
|
|
83
|
+
# default_system_prompt: "" # seeded into auto-created customer spaces
|
|
84
|
+
# default_member_permissions: "prompt,prefs.get" # restrict customers to chat only
|
|
85
|
+
|
|
86
|
+
# ─── Applicative profile ────────────────────────────────────────────────────
|
|
87
|
+
# A profile wraps raw capabilities with deterministic business logic and scopes
|
|
88
|
+
# what members may do. Apply one with `mercury profile apply <name|path|git-url>`
|
|
89
|
+
# (it persists to .mercury/active-profile.json and is loaded at startup).
|
|
90
|
+
# The active profile's member_permissions become the EXHAUSTIVE member set, and
|
|
91
|
+
# its capabilities run host-side via `mrctl capability <name> <action>` so
|
|
92
|
+
# credentials never enter the agent container.
|
|
93
|
+
#
|
|
94
|
+
# Env-only override of the active profile name (informational):
|
|
95
|
+
# MERCURY_PROFILE=barber-appointments
|
|
96
|
+
#
|
|
97
|
+
# Host-only signing key for per-turn caller tokens (auto-generated if unset;
|
|
98
|
+
# only set when the API and container-runner run in separate processes):
|
|
99
|
+
# MERCURY_CALLER_TOKEN_KEY=<random-secret>
|