@muretai/agent-entry 1.9.0 → 1.10.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/README.md +3 -1
- package/examples/server.mjs +8 -0
- package/muretai-agent-entry.mjs +47 -1
- package/package.json +1 -1
- package/spec/v1.md +20 -0
package/README.md
CHANGED
|
@@ -105,6 +105,7 @@ answers questions and hands off nothing, is a signed claim you cannot keep.
|
|
|
105
105
|
|---|---|---|
|
|
106
106
|
| `skills` | `[]` | the menu above — what a visitor learns before knocking |
|
|
107
107
|
| `openDoor` | `true` | publishes `agentEntry.open_door`: the field that tells a visiting agent it may message you with no introduction. The same fact is emitted under the older `muretai.open_door` spelling beside it — read either, write the neutral one |
|
|
108
|
+
| `prefer` | unset | your own order of the ways into your site, published verbatim as `agentEntry.prefer` (spec AE-30): an array of `"page"`, `"card"`, `"mcp"` or `{kind, when}` with `when` one of `person`, `alone`, `key`, `no-key`, `token`, `browser` — e.g. `[{kind:'page', when:'no-key'}, 'card']` says "read on the page if you hold no key; otherwise the door". A visiting agent reads it against what it has on hand. An invalid list refuses to start rather than publish an order you did not write; unset publishes no key at all |
|
|
108
109
|
| `anonymousLane` | `false` | also answer **unsigned** inquiries. They create no account row, and the lane is capped entry-wide — an unauthenticated caller must never become an unmetered signing oracle |
|
|
109
110
|
| `anonRatePerMin` | `30` | anonymous replies per minute, entry-wide |
|
|
110
111
|
| `signedRatePerMin` | `60` | signed replies per minute **per account**, ON by default. Attribution is not scarcity: a `did:key` costs nothing to mint, so being in your ledger was never a bound |
|
|
@@ -381,7 +382,8 @@ node examples/server.mjs # prints its DID and card URL
|
|
|
381
382
|
Environment: `AGENT_ENTRY_SEED_HEX` (generated and printed if absent — **persist it, it is
|
|
382
383
|
your site's identity**), `AGENT_ENTRY_PORT` (8788), `AGENT_ENTRY_BASE_URL`,
|
|
383
384
|
`AGENT_ENTRY_NAME`, `AGENT_ENTRY_ANON` (`1` also accepts unsigned inquiries, which create
|
|
384
|
-
no account)
|
|
385
|
+
no account), `AGENT_ENTRY_PREFER` (your order of the ways in, as one JSON array — see
|
|
386
|
+
`prefer` above; an invalid list refuses to start).
|
|
385
387
|
|
|
386
388
|
## What `baseUrl` may be
|
|
387
389
|
|
package/examples/server.mjs
CHANGED
|
@@ -43,6 +43,10 @@
|
|
|
43
43
|
* https://example.com/agent) and answers NOTHING at `/` — no notice,
|
|
44
44
|
* no OPTIONS, no POST. Point your proxy at the door path and the
|
|
45
45
|
* well-known paths; the site keeps everything else, unchanged.
|
|
46
|
+
* AGENT_ENTRY_PREFER OPTIONAL: the site's own order of its ways in, as one JSON array
|
|
47
|
+
* (AE-30), e.g. '[{"kind":"page","when":"no-key"},"card"]' — "read on
|
|
48
|
+
* the page if you hold no key; otherwise the door". Published verbatim
|
|
49
|
+
* as `agentEntry.prefer`; an invalid list REFUSES to start.
|
|
46
50
|
* AGENT_ENTRY_WBA_JWKS OPTIONAL: a JWKS document {"keys":[…]} as one JSON string —
|
|
47
51
|
* the Web Bot Auth key directory (verified out of band) whose
|
|
48
52
|
* holders this entry should RECOGNISE on inbound requests. Off
|
|
@@ -156,6 +160,10 @@ try {
|
|
|
156
160
|
...(process.env.AGENT_ENTRY_SIGNED_RATE_TOTAL
|
|
157
161
|
? { signedRatePerMinTotal: Number(process.env.AGENT_ENTRY_SIGNED_RATE_TOTAL) } : {}),
|
|
158
162
|
guest: process.env.AGENT_ENTRY_GUEST === '1',
|
|
163
|
+
// AE-30: the site's own order of its ways in, a JSON array. Malformed JSON or an
|
|
164
|
+
// unknown kind/condition throws inside this try and the entry never starts — the same
|
|
165
|
+
// posture as a bad domain list: never publish a statement the operator did not make.
|
|
166
|
+
...(process.env.AGENT_ENTRY_PREFER ? { prefer: JSON.parse(process.env.AGENT_ENTRY_PREFER) } : {}),
|
|
159
167
|
wbaVerifiers,
|
|
160
168
|
});
|
|
161
169
|
} catch (err) {
|
package/muretai-agent-entry.mjs
CHANGED
|
@@ -2148,6 +2148,43 @@ function domainFix(candidate) {
|
|
|
2148
2148
|
* refuses every other bad value there. Truncating would start the entry with a claim that
|
|
2149
2149
|
* is USABLE and NOT WHAT THEY SAID.
|
|
2150
2150
|
*/
|
|
2151
|
+
/** The kinds a visitor can take into a site, and the conditions a site may attach. Kept
|
|
2152
|
+
* identical to the visitor side (Agent Web Router `parsePrefer`): a kind or condition one
|
|
2153
|
+
* side knows and the other does not is a declaration one side silently drops. */
|
|
2154
|
+
export const PREFER_KINDS = ['page', 'card', 'mcp'];
|
|
2155
|
+
export const PREFER_WHEN = ['person', 'alone', 'key', 'no-key', 'token', 'browser'];
|
|
2156
|
+
|
|
2157
|
+
/**
|
|
2158
|
+
* The exact `agentEntry.prefer` this entry may publish, or a TypeError (AE-30).
|
|
2159
|
+
*
|
|
2160
|
+
* The site's own order of its ways in — "read on the page if you have no key, then the
|
|
2161
|
+
* door", say. VALIDATED, NEVER REWRITTEN: this goes on a SIGNED card, and a card that says
|
|
2162
|
+
* something the operator did not write is a worse card than none, so an unknown kind, an
|
|
2163
|
+
* unknown condition or a stray key refuses the whole declaration instead of trimming it —
|
|
2164
|
+
* the same posture as `canonicalDomains`. `null`/`undefined` means "not configured", and
|
|
2165
|
+
* then no `prefer` key is published at all, which is what keeps an already-deployed
|
|
2166
|
+
* entry's bytes unchanged.
|
|
2167
|
+
*/
|
|
2168
|
+
export function validatePrefer(prefer) {
|
|
2169
|
+
if (prefer == null) return null;
|
|
2170
|
+
if (!Array.isArray(prefer) || prefer.length === 0) {
|
|
2171
|
+
throw new TypeError('agentEntry.prefer must be a non-empty array of "page" | "card" | "mcp" or {kind, when}');
|
|
2172
|
+
}
|
|
2173
|
+
for (const e of prefer) {
|
|
2174
|
+
if (typeof e === 'string') {
|
|
2175
|
+
if (!PREFER_KINDS.includes(e)) throw new TypeError(`agentEntry.prefer: unknown kind ${JSON.stringify(e)}`);
|
|
2176
|
+
continue;
|
|
2177
|
+
}
|
|
2178
|
+
if (!e || typeof e !== 'object' || Array.isArray(e)) throw new TypeError('agentEntry.prefer: an entry must be a kind or {kind, when}');
|
|
2179
|
+
const keys = Object.keys(e);
|
|
2180
|
+
if (!PREFER_KINDS.includes(e.kind)) throw new TypeError(`agentEntry.prefer: unknown kind ${JSON.stringify(e.kind)}`);
|
|
2181
|
+
if ('when' in e && !PREFER_WHEN.includes(e.when)) throw new TypeError(`agentEntry.prefer: unknown condition ${JSON.stringify(e.when)}`);
|
|
2182
|
+
const stray = keys.filter((k) => k !== 'kind' && k !== 'when');
|
|
2183
|
+
if (stray.length) throw new TypeError(`agentEntry.prefer: unexpected key(s) ${stray.join(', ')}`);
|
|
2184
|
+
}
|
|
2185
|
+
return prefer;
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2151
2188
|
export function canonicalDomains(domains, { warn = true } = {}) {
|
|
2152
2189
|
if (domains === undefined || domains === null) return [];
|
|
2153
2190
|
if (!Array.isArray(domains)) {
|
|
@@ -2269,6 +2306,11 @@ export function canonicalMount(canonUrl, basePath) {
|
|
|
2269
2306
|
* responder (envelope) => string | {text, contextId?, timestamp?} | Promise<…>
|
|
2270
2307
|
* openDoor advertise `muretai.open_door` (default true) — the flag that tells a
|
|
2271
2308
|
* visiting agent it may contact you without an introduction.
|
|
2309
|
+
* prefer OPTIONAL: the site's own order of its ways in, published as
|
|
2310
|
+
* `agentEntry.prefer` (AE-30) — e.g. `[{kind:'page', when:'no-key'}, 'card']`
|
|
2311
|
+
* says "read on the page if you hold no key; otherwise the door". Validated
|
|
2312
|
+
* by `validatePrefer`; an invalid list throws, so the entry never starts
|
|
2313
|
+
* with a statement the operator did not make. Absent = no key published.
|
|
2272
2314
|
* anonymousLane also accept UNSIGNED inquiries (default false). They create no account,
|
|
2273
2315
|
* and the lane as a whole is capped at `anonRatePerMin` signed replies per
|
|
2274
2316
|
* minute — it is unauthenticated, so it must not be an unmetered signing
|
|
@@ -2330,6 +2372,7 @@ export function createAgentEntry({
|
|
|
2330
2372
|
version = '1',
|
|
2331
2373
|
responder = () => 'Thanks — a human will follow up.',
|
|
2332
2374
|
openDoor = true,
|
|
2375
|
+
prefer = null,
|
|
2333
2376
|
anonymousLane = false,
|
|
2334
2377
|
anonRatePerMin = ANON_RATE_PER_MIN,
|
|
2335
2378
|
signedRatePerMin = SIGNED_RATE_PER_MIN,
|
|
@@ -2404,7 +2447,10 @@ export function createAgentEntry({
|
|
|
2404
2447
|
// Neutral key first, vendor key beside it for one release. See the securitySchemes block
|
|
2405
2448
|
// below for why the old spelling stays: a consumer must learn the new name BEFORE
|
|
2406
2449
|
// producers stop emitting the old one, never after.
|
|
2407
|
-
|
|
2450
|
+
// AE-30: the site's order rides on the NEUTRAL key only; the alias stays `open_door`
|
|
2451
|
+
// alone, so an old consumer that compares the two aliases byte for byte keeps passing.
|
|
2452
|
+
const canonPrefer = validatePrefer(prefer);
|
|
2453
|
+
if (openDoor) card.agentEntry = { open_door: true, ...(canonPrefer ? { prefer: canonPrefer } : {}) };
|
|
2408
2454
|
if (openDoor) card.muretai = { open_door: true };
|
|
2409
2455
|
// Deliberately NO `relay`/`enc_pub` on the card: those advertise a store-and-forward
|
|
2410
2456
|
// mailbox, and an agent entry has no listener draining one. Advertising a mailbox nobody
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@muretai/agent-entry",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Make your website answer AI agents: an A2A agent endpoint that verifies who is knocking, opens an account for them and replies signed, in one HTTP round trip. Zero dependencies. Pairs with llms.txt and WebMCP.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "muretai-agent-entry.mjs",
|
package/spec/v1.md
CHANGED
|
@@ -396,6 +396,25 @@ higher rate than the signed lane's aggregate.
|
|
|
396
396
|
> **Observation.** With the lane on, drive unsigned requests above the bound; assert refusal. An
|
|
397
397
|
> unauthenticated caller must never become an unmetered signing oracle.
|
|
398
398
|
|
|
399
|
+
**AE-30.** An entry **MAY** publish `agentEntry.prefer`: the site's own order of its ways in
|
|
400
|
+
for a visiting agent. It is an array whose entries are a kind — `"page"`, `"card"`, `"mcp"` —
|
|
401
|
+
or an object `{"kind": <kind>, "when": <condition>}` with `when` one of `person`, `alone`,
|
|
402
|
+
`key`, `no-key`, `token`, `browser`, read by the visitor against what it has on hand. When
|
|
403
|
+
published it **MUST** appear under the neutral `agentEntry` key exactly as the operator
|
|
404
|
+
declared it (validated, not rewritten), and **MUST** appear identically in the signed
|
|
405
|
+
envelope's inner card. The legacy `muretai` alias **MUST NOT** carry it. When not configured,
|
|
406
|
+
the card **MUST NOT** carry a `prefer` key. A declaration that fails validation **MUST** make
|
|
407
|
+
the entry refuse to start rather than publish a corrected or partial order.
|
|
408
|
+
|
|
409
|
+
> **Observation.** Start an entry with a declaration; `GET` the card and the envelope; assert
|
|
410
|
+
> `agentEntry.prefer` equals the declaration in both and `muretai` has no `prefer`. Start one
|
|
411
|
+
> without; assert no `prefer` key. Start one with `["teleport"]`; assert it exits non-zero and
|
|
412
|
+
> binds nothing. Why refuse rather than fix: a signed card is the origin's statement, and a
|
|
413
|
+
> statement the operator did not make is a worse card than none — the same posture as a bad
|
|
414
|
+
> `domains` list. Why the visitor's conditions live here: which way in a stranger should try
|
|
415
|
+
> first is the site's design (read on the page, become a counterparty later — or knock first),
|
|
416
|
+
> and the card is the one place the site can say so that a page script cannot rewrite.
|
|
417
|
+
|
|
399
418
|
---
|
|
400
419
|
|
|
401
420
|
## 5. Relationship to other specifications
|
|
@@ -629,6 +648,7 @@ be written is a requirement that does not belong in §4.
|
|
|
629
648
|
| AE-27 | MUST | size caps | oversized body and text |
|
|
630
649
|
| AE-28 | MUST | aggregate reply ceiling, refused with `-32004` | drive above the ceiling |
|
|
631
650
|
| AE-29 | MUST | unsigned lane bounded entry-wide | drive the anonymous lane |
|
|
651
|
+
| AE-30 | MAY / MUST | `agentEntry.prefer` verbatim on card + envelope, or absent; invalid refuses to start | `GET` both, compare; start with a bad list → exit ≠ 0 |
|
|
632
652
|
|
|
633
653
|
---
|
|
634
654
|
|