@uppercut-labs/agent-native 0.0.0-stage → 1.1.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/LICENSE +21 -0
- package/README.md +164 -2
- package/agent/agent-prompt-short.md +1 -0
- package/agent/agent-prompt.md +26 -0
- package/bin/agent-native-site.mjs +269 -0
- package/bin/discover-routes.mjs +162 -0
- package/bin/verify-publication.mjs +317 -0
- package/dist/adapters/zod.d.ts +4 -0
- package/dist/adapters/zod.d.ts.map +1 -0
- package/dist/adapters/zod.js +20 -0
- package/dist/adapters/zod.js.map +1 -0
- package/dist/astro.d.ts +13 -0
- package/dist/astro.d.ts.map +1 -0
- package/dist/astro.js +41 -0
- package/dist/astro.js.map +1 -0
- package/dist/auth.d.ts +52 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +128 -0
- package/dist/auth.js.map +1 -0
- package/dist/browser.d.ts +47 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +219 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +80 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +484 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/assert-never.d.ts +2 -0
- package/dist/core/assert-never.d.ts.map +1 -0
- package/dist/core/assert-never.js +5 -0
- package/dist/core/assert-never.js.map +1 -0
- package/dist/core/binding-internal.d.ts +10 -0
- package/dist/core/binding-internal.d.ts.map +1 -0
- package/dist/core/binding-internal.js +15 -0
- package/dist/core/binding-internal.js.map +1 -0
- package/dist/core/composition.d.ts +119 -0
- package/dist/core/composition.d.ts.map +1 -0
- package/dist/core/composition.js +545 -0
- package/dist/core/composition.js.map +1 -0
- package/dist/core/contracts.d.ts +55 -0
- package/dist/core/contracts.d.ts.map +1 -0
- package/dist/core/contracts.js +271 -0
- package/dist/core/contracts.js.map +1 -0
- package/dist/core/diagnostics.d.ts +14 -0
- package/dist/core/diagnostics.d.ts.map +1 -0
- package/dist/core/diagnostics.js +29 -0
- package/dist/core/diagnostics.js.map +1 -0
- package/dist/core/executor.d.ts +52 -0
- package/dist/core/executor.d.ts.map +1 -0
- package/dist/core/executor.js +98 -0
- package/dist/core/executor.js.map +1 -0
- package/dist/core/registry.d.ts +37 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +75 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/schema.d.ts +9 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +36 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/discovery.d.ts +16 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +23 -0
- package/dist/discovery.js.map +1 -0
- package/dist/doctor.d.ts +200 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +287 -0
- package/dist/doctor.js.map +1 -0
- package/dist/harness-codex.d.ts +17 -0
- package/dist/harness-codex.d.ts.map +1 -0
- package/dist/harness-codex.js +485 -0
- package/dist/harness-codex.js.map +1 -0
- package/dist/harness-mcp-bridge.d.ts +14 -0
- package/dist/harness-mcp-bridge.d.ts.map +1 -0
- package/dist/harness-mcp-bridge.js +159 -0
- package/dist/harness-mcp-bridge.js.map +1 -0
- package/dist/harness.d.ts +72 -0
- package/dist/harness.d.ts.map +1 -0
- package/dist/harness.js +101 -0
- package/dist/harness.js.map +1 -0
- package/dist/http.d.ts +20 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +537 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +73 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +1039 -0
- package/dist/init.js.map +1 -0
- package/dist/mcp-apps.d.ts +9 -0
- package/dist/mcp-apps.d.ts.map +1 -0
- package/dist/mcp-apps.js +171 -0
- package/dist/mcp-apps.js.map +1 -0
- package/dist/mcp-core.d.ts +64 -0
- package/dist/mcp-core.d.ts.map +1 -0
- package/dist/mcp-core.js +358 -0
- package/dist/mcp-core.js.map +1 -0
- package/dist/mcp.d.ts +3 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +2 -0
- package/dist/mcp.js.map +1 -0
- package/dist/next-browser.d.ts +14 -0
- package/dist/next-browser.d.ts.map +1 -0
- package/dist/next-browser.js +14 -0
- package/dist/next-browser.js.map +1 -0
- package/dist/next.d.ts +24 -0
- package/dist/next.d.ts.map +1 -0
- package/dist/next.js +28 -0
- package/dist/next.js.map +1 -0
- package/dist/package-version.d.ts +2 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +3 -0
- package/dist/package-version.js.map +1 -0
- package/dist/publication.d.ts +115 -0
- package/dist/publication.d.ts.map +1 -0
- package/dist/publication.js +441 -0
- package/dist/publication.js.map +1 -0
- package/dist/registry.d.ts +3 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +2 -0
- package/dist/registry.js.map +1 -0
- package/docs/adopt-existing-site.md +189 -0
- package/docs/browser.md +70 -0
- package/docs/capabilities-and-bindings.md +141 -0
- package/docs/cli.md +88 -0
- package/docs/compatibility.md +32 -0
- package/docs/composition-and-versioning.md +113 -0
- package/docs/doctor-and-troubleshooting.md +58 -0
- package/docs/evidence/GHSA-ch52-4w7c-c8xp-2026-10-03.md +17 -0
- package/docs/evidence/GHSA-ch52-4w7c-c8xp-2026-10-10.md +30 -0
- package/docs/evidence/GHSA-ch52-4w7c-c8xp-2026-10-12.md +10 -0
- package/docs/evidence/UAN-002-house-style-review.md +29 -0
- package/docs/evidence/UAN-007-astro.md +43 -0
- package/docs/evidence/UAN-008-worker-sidecar.md +56 -0
- package/docs/evidence/UAN-009-init.md +43 -0
- package/docs/evidence/UAN-010-auth.md +17 -0
- package/docs/evidence/UAN-011-discovery.md +18 -0
- package/docs/evidence/UAN-012-mcp-apps.md +14 -0
- package/docs/evidence/UAN-013-astro-on-demand.md +47 -0
- package/docs/evidence/UAN-014-next.md +57 -0
- package/docs/evidence/UAN-015-node-hono.md +69 -0
- package/docs/evidence/UAN-016-composition.md +52 -0
- package/docs/evidence/UAN-017-version-migrations.md +50 -0
- package/docs/evidence/UAN-018-surface-overrides.md +30 -0
- package/docs/evidence/UAN-019-doctor.md +19 -0
- package/docs/evidence/UAN-020-negative-security-fixtures.md +61 -0
- package/docs/evidence/UAN-020-operational-failure-safety.md +49 -0
- package/docs/evidence/UAN-021-docs-coverage.md +30 -0
- package/docs/evidence/UAN-022-cross-platform-license-notices.md +39 -0
- package/docs/evidence/UAN-022-overhead-license-audit.md +233 -0
- package/docs/evidence/UAN-022-package-portability.md +54 -0
- package/docs/evidence/UAN-023-commercial-agent-hosts.md +36 -0
- package/docs/evidence/UAN-023-inspector-host-probe.md +37 -0
- package/docs/evidence/UAN-023-inspector-web-and-astro-adoption.md +44 -0
- package/docs/evidence/UAN-023-native-webmcp-chrome.md +44 -0
- package/docs/evidence/UAN-024-release-readiness-2026-10-10.md +33 -0
- package/docs/evidence/UAN-024-release-readiness.md +41 -0
- package/docs/evidence/codex-harness.md +56 -0
- package/docs/evidence/gate-2-domain-expansion.md +35 -0
- package/docs/evidence/recovery-and-selection-recipes-2026-10-15.md +63 -0
- package/docs/for-agents.md +50 -0
- package/docs/frameworks.md +62 -0
- package/docs/getting-started.md +109 -0
- package/docs/harness-codex.md +117 -0
- package/docs/harness-mcp-bridge.md +30 -0
- package/docs/http.md +80 -0
- package/docs/installation.md +95 -0
- package/docs/mcp-apps.md +57 -0
- package/docs/mcp.md +178 -0
- package/docs/next.md +73 -0
- package/docs/permissions-and-discovery.md +58 -0
- package/docs/programmatic-harness.md +88 -0
- package/docs/readability-frameworks.md +18 -0
- package/docs/reference.md +161 -0
- package/docs/release-handoff.md +41 -0
- package/docs/site-publication.md +85 -0
- package/docs/stability.md +53 -0
- package/docs/static-sites.md +141 -0
- package/docs/support-and-limitations.md +20 -0
- package/docs/surfaces.md +27 -0
- package/docs/upgrading-to-1.0.md +26 -0
- package/package.json +188 -4
- package/skill/agent-native/SKILL.md +54 -0
- package/skill/agent-native/references/adapter-decision.md +19 -0
- package/skill/agent-native/references/verification.md +25 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const maxBytes = 4 * 1024 * 1024;
|
|
4
|
+
|
|
5
|
+
// The operator chooses one origin. Redirects are evidence and are never followed with credentials.
|
|
6
|
+
async function readResponse(url, origin) {
|
|
7
|
+
const target = new URL(url);
|
|
8
|
+
if (
|
|
9
|
+
target.origin !== origin ||
|
|
10
|
+
target.username ||
|
|
11
|
+
target.password ||
|
|
12
|
+
!['http:', 'https:'].includes(target.protocol)
|
|
13
|
+
)
|
|
14
|
+
throw Error('invalid-target');
|
|
15
|
+
const response = await fetch(target, {
|
|
16
|
+
redirect: 'manual',
|
|
17
|
+
credentials: 'omit',
|
|
18
|
+
signal: AbortSignal.timeout(10000),
|
|
19
|
+
});
|
|
20
|
+
const chunks = [];
|
|
21
|
+
let size = 0;
|
|
22
|
+
for await (const chunk of response.body ?? []) {
|
|
23
|
+
size += chunk.byteLength;
|
|
24
|
+
if (size > maxBytes) throw Error('response-too-large');
|
|
25
|
+
chunks.push(chunk);
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
status: response.status,
|
|
29
|
+
type: (response.headers.get('content-type') ?? '').split(';')[0].trim().toLowerCase(),
|
|
30
|
+
bytes: Buffer.concat(chunks),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The CLI can attest only to protocols for which it has an independent read-only probe.
|
|
35
|
+
export async function verifyProtocol(declaration) {
|
|
36
|
+
if (declaration.protocol !== 'openapi') return false;
|
|
37
|
+
const reply = await readResponse(declaration.url, new URL(declaration.url).origin);
|
|
38
|
+
if (reply.status !== 200 || !/^(application\/(?:json|[\w.+-]+\+json))$/.test(reply.type))
|
|
39
|
+
return false;
|
|
40
|
+
const doc = JSON.parse(reply.bytes.toString('utf8'));
|
|
41
|
+
const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
42
|
+
const methods = new Set(['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace']);
|
|
43
|
+
return (
|
|
44
|
+
/^3\.\d+\.\d+$/.test(doc?.openapi) &&
|
|
45
|
+
typeof doc.info?.title === 'string' &&
|
|
46
|
+
doc.info.title.trim().length > 0 &&
|
|
47
|
+
typeof doc.info?.version === 'string' &&
|
|
48
|
+
doc.info.version.trim().length > 0 &&
|
|
49
|
+
record(doc.paths) &&
|
|
50
|
+
Object.entries(doc.paths).some(
|
|
51
|
+
([path, item]) =>
|
|
52
|
+
path.startsWith('/') &&
|
|
53
|
+
record(item) &&
|
|
54
|
+
Object.entries(item).some(
|
|
55
|
+
([method, operation]) =>
|
|
56
|
+
methods.has(method) &&
|
|
57
|
+
record(operation) &&
|
|
58
|
+
record(operation.responses) &&
|
|
59
|
+
Object.entries(operation.responses).some(
|
|
60
|
+
([code, response]) =>
|
|
61
|
+
/^(?:default|[1-5](?:\d\d|XX))$/.test(code) &&
|
|
62
|
+
record(response) &&
|
|
63
|
+
typeof response.description === 'string' &&
|
|
64
|
+
response.description.trim().length > 0,
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
)
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function attr(tag, name) {
|
|
72
|
+
const attributes = tag.replace(/^<[\w:-]+/, '');
|
|
73
|
+
for (const [, key, quoted, single, bare] of attributes.matchAll(
|
|
74
|
+
/\s+([^\s=/><]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s><]+)))?/g,
|
|
75
|
+
)) {
|
|
76
|
+
if (key.toLowerCase() === name) return quoted ?? single ?? bare ?? '';
|
|
77
|
+
}
|
|
78
|
+
return '';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function textContent(html) {
|
|
82
|
+
const entities = { amp: '&', lt: '<', gt: '>', quot: '"', apos: "'", nbsp: ' ' };
|
|
83
|
+
return html
|
|
84
|
+
.replace(/<(script|style|template|noscript|textarea|title)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, '')
|
|
85
|
+
.replace(
|
|
86
|
+
/<\/?(?:p|br|div|main|h[1-6]|li|section|article|tr|td|nav|header|footer)\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi,
|
|
87
|
+
' ',
|
|
88
|
+
)
|
|
89
|
+
.replace(/<\/?[a-z][\w:-]*\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi, '')
|
|
90
|
+
.replace(/&(#x[\da-f]+|#\d+|amp|lt|gt|quot|apos|nbsp);/gi, (original, entity) => {
|
|
91
|
+
if (!entity.startsWith('#')) return entities[entity.toLowerCase()] ?? original;
|
|
92
|
+
const n =
|
|
93
|
+
entity[1].toLowerCase() === 'x'
|
|
94
|
+
? Number.parseInt(entity.slice(2), 16)
|
|
95
|
+
: Number(entity.slice(1));
|
|
96
|
+
return n > 0 && n <= 0x10ffff ? String.fromCodePoint(n) : original;
|
|
97
|
+
})
|
|
98
|
+
.replace(/\s+/g, ' ')
|
|
99
|
+
.trim();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function containsFact(visible, fact) {
|
|
103
|
+
// Require a word boundary where the approved text starts or ends in a word.
|
|
104
|
+
// Otherwise a rate of `19` could match `190` in a contradictory page.
|
|
105
|
+
const words = /[\p{L}\p{N}]/u;
|
|
106
|
+
for (let from = 0; from <= visible.length; ) {
|
|
107
|
+
const at = visible.indexOf(fact, from);
|
|
108
|
+
if (at < 0) return false;
|
|
109
|
+
if (
|
|
110
|
+
(!words.test(fact[0]) || !words.test(visible[at - 1] ?? '')) &&
|
|
111
|
+
(!words.test(fact.at(-1)) || !words.test(visible[at + fact.length] ?? ''))
|
|
112
|
+
)
|
|
113
|
+
return true;
|
|
114
|
+
from = at + 1;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function canonicalPage(reply, entry, expectedData, source) {
|
|
120
|
+
if (reply.status !== 200 || reply.type !== 'text/html' || !source) return false;
|
|
121
|
+
const html = reply.bytes.toString('utf8').replace(/<!--[\s\S]*?-->/g, '');
|
|
122
|
+
const head = /<head\b[^>]*>([\s\S]*?)<\/head>/i.exec(html)?.[1] ?? '';
|
|
123
|
+
const links =
|
|
124
|
+
head
|
|
125
|
+
.replace(/<(script|style|title|template|noscript)\b[^>]*>[\s\S]*?<\/\1\s*>/gi, '')
|
|
126
|
+
.match(/<link\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi) ?? [];
|
|
127
|
+
const rel = (tag, value) => attr(tag, 'rel').toLowerCase().split(/\s+/).includes(value);
|
|
128
|
+
const targets = (tag, url) => {
|
|
129
|
+
const href = attr(tag, 'href').trim();
|
|
130
|
+
return href.length > 0 && new URL(href, entry.canonical).href === url;
|
|
131
|
+
};
|
|
132
|
+
const canonicalLinks = links.filter((tag) => rel(tag, 'canonical'));
|
|
133
|
+
if (canonicalLinks.length !== 1 || !targets(canonicalLinks[0], entry.canonical)) return false;
|
|
134
|
+
|
|
135
|
+
const body = /<body\b[^>]*>([\s\S]*?)<\/body>/i.exec(html)?.[1];
|
|
136
|
+
if (body === undefined) return false;
|
|
137
|
+
const visible = textContent(body);
|
|
138
|
+
if (visible) {
|
|
139
|
+
const facts = source.markdown
|
|
140
|
+
.split(/\r?\n/)
|
|
141
|
+
.map((line) => line.replace(/^\s*(?:#{1,6}\s+|>\s*|[-*+]\s+|\d+\.\s+)/, '').trim())
|
|
142
|
+
.filter(Boolean);
|
|
143
|
+
if (
|
|
144
|
+
!facts.length ||
|
|
145
|
+
facts.some((line) => {
|
|
146
|
+
const text = line.replace(/\s+/g, ' ');
|
|
147
|
+
return (
|
|
148
|
+
!containsFact(visible, text) &&
|
|
149
|
+
!containsFact(visible, text.replace(/(\*\*|__|~~|`|\*)(.+?)\1/g, '$2'))
|
|
150
|
+
);
|
|
151
|
+
})
|
|
152
|
+
)
|
|
153
|
+
return false;
|
|
154
|
+
} else {
|
|
155
|
+
// A JS-only shell must point anonymous clients to the exact, byte-verified mirror.
|
|
156
|
+
if (
|
|
157
|
+
!links.some(
|
|
158
|
+
(tag) =>
|
|
159
|
+
rel(tag, 'alternate') &&
|
|
160
|
+
attr(tag, 'type').toLowerCase() === 'text/markdown' &&
|
|
161
|
+
targets(tag, entry.mirror),
|
|
162
|
+
) ||
|
|
163
|
+
!(html.match(/<script\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi) ?? []).some((tag) =>
|
|
164
|
+
attr(tag, 'src').trim(),
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
if (!expectedData) return true;
|
|
170
|
+
for (const script of html.matchAll(/(<script\b[^>]*>)([\s\S]*?)<\/script>/gi)) {
|
|
171
|
+
if (attr(script[1], 'type').toLowerCase() !== 'application/ld+json') continue;
|
|
172
|
+
const data = JSON.parse(script[2]);
|
|
173
|
+
const nodes = Array.isArray(data) ? data : [data, ...(data?.['@graph'] ?? [])];
|
|
174
|
+
if (
|
|
175
|
+
nodes.some(
|
|
176
|
+
(node) => node && Object.entries(expectedData).every(([key, val]) => node[key] === val),
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Validate the entire probe list before making any request. A private or malformed
|
|
185
|
+
// target must never appear in an error receipt, including the URL's userinfo.
|
|
186
|
+
function probeTarget(probe, base) {
|
|
187
|
+
if (
|
|
188
|
+
!probe ||
|
|
189
|
+
typeof probe !== 'object' ||
|
|
190
|
+
Array.isArray(probe) ||
|
|
191
|
+
Object.keys(probe).some(
|
|
192
|
+
(key) => !['path', 'status', 'type', 'contains', 'excludes'].includes(key),
|
|
193
|
+
) ||
|
|
194
|
+
typeof probe.path !== 'string' ||
|
|
195
|
+
!probe.path ||
|
|
196
|
+
/[?#\\\s]/u.test(probe.path) ||
|
|
197
|
+
probe.path.startsWith('//') ||
|
|
198
|
+
!Number.isInteger(probe.status) ||
|
|
199
|
+
probe.status < 100 ||
|
|
200
|
+
probe.status > 599 ||
|
|
201
|
+
(probe.type !== undefined &&
|
|
202
|
+
(typeof probe.type !== 'string' || !/^[\w.+-]+\/[\w.+-]+$/.test(probe.type)))
|
|
203
|
+
)
|
|
204
|
+
return null;
|
|
205
|
+
for (const list of [probe.contains, probe.excludes]) {
|
|
206
|
+
if (
|
|
207
|
+
list !== undefined &&
|
|
208
|
+
(!Array.isArray(list) || !list.every((s) => typeof s === 'string' && s.trim()))
|
|
209
|
+
)
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
try {
|
|
213
|
+
const url = new URL(probe.path, base);
|
|
214
|
+
if (
|
|
215
|
+
url.origin !== base.origin ||
|
|
216
|
+
url.username ||
|
|
217
|
+
url.password ||
|
|
218
|
+
!url.pathname.startsWith(base.pathname)
|
|
219
|
+
)
|
|
220
|
+
return null;
|
|
221
|
+
for (const part of probe.path.split('/')) {
|
|
222
|
+
const decoded = decodeURIComponent(part);
|
|
223
|
+
if (
|
|
224
|
+
decoded === '.' ||
|
|
225
|
+
decoded === '..' ||
|
|
226
|
+
/[\\/%?#\s]/u.test(decoded) ||
|
|
227
|
+
[...decoded].some((c) => c.charCodeAt(0) < 32 || c.charCodeAt(0) === 127)
|
|
228
|
+
)
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
return url;
|
|
232
|
+
} catch {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export async function verifyPublication(plan, config, probes = {}, candidates = []) {
|
|
238
|
+
const base = new URL(config.siteUrl.replace(/\/?$/, '/'));
|
|
239
|
+
const checks = [];
|
|
240
|
+
if (
|
|
241
|
+
!probes ||
|
|
242
|
+
typeof probes !== 'object' ||
|
|
243
|
+
Array.isArray(probes) ||
|
|
244
|
+
Object.keys(probes).some((key) => key !== 'responses') ||
|
|
245
|
+
(probes.responses !== undefined && !Array.isArray(probes.responses))
|
|
246
|
+
)
|
|
247
|
+
return { status: 'failed', code: 'invalid-probe', checks };
|
|
248
|
+
const responses = probes.responses ?? [];
|
|
249
|
+
const targets = responses.map((probe) => probeTarget(probe, base));
|
|
250
|
+
if (targets.some((target) => !target)) return { status: 'failed', code: 'invalid-probe', checks };
|
|
251
|
+
async function check(url, test, inspect, identity = { url }) {
|
|
252
|
+
try {
|
|
253
|
+
const reply = await readResponse(url, base.origin);
|
|
254
|
+
const pass = Boolean(inspect(reply));
|
|
255
|
+
checks.push({ test, ...identity, pass, status: reply.status, type: reply.type });
|
|
256
|
+
} catch (error) {
|
|
257
|
+
checks.push({
|
|
258
|
+
test,
|
|
259
|
+
...identity,
|
|
260
|
+
pass: false,
|
|
261
|
+
error: error.name === 'TimeoutError' ? 'timeout' : 'request-or-validation-failed',
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (!config.enabled || !plan.assets.length)
|
|
266
|
+
return { status: 'failed', code: 'no-active-publication', checks };
|
|
267
|
+
for (const asset of plan.assets) {
|
|
268
|
+
const expected = asset.mediaType.split(';')[0];
|
|
269
|
+
await check(new URL(asset.path, base).href, 'published-bytes', (r) => {
|
|
270
|
+
const type =
|
|
271
|
+
r.type === expected ||
|
|
272
|
+
(expected === 'text/markdown' && r.type === 'text/plain') ||
|
|
273
|
+
(expected === 'application/ld+json' && r.type === 'application/json');
|
|
274
|
+
return (
|
|
275
|
+
r.status === 200 &&
|
|
276
|
+
type &&
|
|
277
|
+
createHash('sha256').update(r.bytes).digest('hex') === asset.digest
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
const sources = new Map(candidates.map((source) => [source.id, source]));
|
|
282
|
+
for (const entry of plan.manifest.entries.filter((x) => x.status === 'included')) {
|
|
283
|
+
const data =
|
|
284
|
+
entry.structuredData &&
|
|
285
|
+
plan.assets.find((asset) => new URL(asset.path, base).href === entry.structuredData);
|
|
286
|
+
await check(entry.canonical, 'human-page', (r) =>
|
|
287
|
+
canonicalPage(r, entry, data ? JSON.parse(data.content) : null, sources.get(entry.sourceId)),
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
await check(
|
|
291
|
+
new URL(`agent-native/missing-${randomUUID()}.md`, base).href,
|
|
292
|
+
'missing-mirror',
|
|
293
|
+
(r) => r.status === 404 || r.status === 410,
|
|
294
|
+
);
|
|
295
|
+
for (const [index, probe] of responses.entries()) {
|
|
296
|
+
const url = targets[index];
|
|
297
|
+
await check(
|
|
298
|
+
url.href,
|
|
299
|
+
'declared-response',
|
|
300
|
+
(r) => {
|
|
301
|
+
const body = r.bytes.toString('utf8');
|
|
302
|
+
return (
|
|
303
|
+
r.status === probe.status &&
|
|
304
|
+
(!probe.type || r.type === probe.type) &&
|
|
305
|
+
(probe.contains ?? []).every(
|
|
306
|
+
(value) => typeof value === 'string' && value.length && body.includes(value),
|
|
307
|
+
) &&
|
|
308
|
+
(probe.excludes ?? []).every(
|
|
309
|
+
(value) => typeof value === 'string' && value.length && !body.includes(value),
|
|
310
|
+
)
|
|
311
|
+
);
|
|
312
|
+
},
|
|
313
|
+
{ probe: index + 1 },
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
return { status: checks.every((c) => c.pass) ? 'verified' : 'failed', checks };
|
|
317
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zod.d.ts","sourceRoot":"","sources":["../../src/adapters/zod.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,EAAkC,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAMpF,wBAAgB,OAAO,CAAC,MAAM,SAAS,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAgB9F"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
import { cloneJsonValue } from '../core/schema.js';
|
|
3
|
+
function isReadonlyArray(value) {
|
|
4
|
+
return Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
export function fromZod(schema) {
|
|
7
|
+
return Object.freeze({
|
|
8
|
+
parse(input) {
|
|
9
|
+
return schema.parse(input);
|
|
10
|
+
},
|
|
11
|
+
toJSONSchema() {
|
|
12
|
+
const converted = cloneJsonValue(z.toJSONSchema(schema, { unrepresentable: 'throw' }), 'JSON Schema');
|
|
13
|
+
if (isReadonlyArray(converted) || converted === null || typeof converted !== 'object') {
|
|
14
|
+
throw new TypeError('Zod schema must convert to a JSON Schema object');
|
|
15
|
+
}
|
|
16
|
+
return converted;
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=zod.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zod.js","sourceRoot":"","sources":["../../src/adapters/zod.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACzB,OAAO,EAAE,cAAc,EAAmC,MAAM,mBAAmB,CAAC;AAEpF,SAAS,eAAe,CAAC,KAAc;IACrC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,OAAO,CAA2B,MAAc;IAC9D,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,KAAK,CAAC,KAAc;YAClB,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;QACD,YAAY;YACV,MAAM,SAAS,GAAc,cAAc,CACzC,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,EACpD,aAAa,CACd,CAAC;YACF,IAAI,eAAe,CAAC,SAAS,CAAC,IAAI,SAAS,KAAK,IAAI,IAAI,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;gBACtF,MAAM,IAAI,SAAS,CAAC,iDAAiD,CAAC,CAAC;YACzE,CAAC;YACD,OAAO,SAAS,CAAC;QACnB,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
package/dist/astro.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AstroIntegration } from 'astro';
|
|
2
|
+
export type AgentNativeAstroOptions = {
|
|
3
|
+
/** Absolute path to an application-owned browser entry module. */
|
|
4
|
+
readonly browserEntry: string;
|
|
5
|
+
/** Allow Astro's server build mode when the project owns on-demand routes and an adapter. */
|
|
6
|
+
readonly mode?: 'static' | 'on-demand';
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Injects an application-owned browser entry through Astro's supported Vite page-script hook.
|
|
10
|
+
* The application remains responsible for endpoint routes and an Astro server adapter.
|
|
11
|
+
*/
|
|
12
|
+
export declare function agentNativeAstro(options: AgentNativeAstroOptions): AstroIntegration;
|
|
13
|
+
//# sourceMappingURL=astro.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astro.d.ts","sourceRoot":"","sources":["../src/astro.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAM9C,MAAM,MAAM,uBAAuB,GAAG;IACpC,kEAAkE;IAClE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,6FAA6F;IAC7F,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;CACxC,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,gBAAgB,CAgDnF"}
|
package/dist/astro.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { isAbsolute } from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Injects an application-owned browser entry through Astro's supported Vite page-script hook.
|
|
4
|
+
* The application remains responsible for endpoint routes and an Astro server adapter.
|
|
5
|
+
*/
|
|
6
|
+
export function agentNativeAstro(options) {
|
|
7
|
+
if (!isAbsolute(options.browserEntry)) {
|
|
8
|
+
throw new TypeError('browserEntry must be an absolute filesystem path');
|
|
9
|
+
}
|
|
10
|
+
if (options.mode !== undefined && options.mode !== 'static' && options.mode !== 'on-demand') {
|
|
11
|
+
throw new TypeError('mode must be "static" or "on-demand"');
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
name: '@uppercut-labs/agent-native',
|
|
15
|
+
hooks: {
|
|
16
|
+
'astro:config:setup': ({ config, injectScript, logger }) => {
|
|
17
|
+
if (config.output === 'server' && options.mode !== 'on-demand') {
|
|
18
|
+
logger.warn('Agent Native Astro integration supports static output only; browser bootstrap was not injected.');
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
injectScript('page', `import ${JSON.stringify(options.browserEntry)};`);
|
|
22
|
+
logger.info(options.mode === 'on-demand'
|
|
23
|
+
? 'Agent Native browser bootstrap enabled for an on-demand Astro application.'
|
|
24
|
+
: 'Agent Native browser bootstrap enabled for static output.');
|
|
25
|
+
},
|
|
26
|
+
'astro:config:done': ({ buildOutput, logger }) => {
|
|
27
|
+
if (buildOutput !== 'static' && options.mode !== 'on-demand') {
|
|
28
|
+
logger.warn('Agent Native Astro integration is static-only; this integration does not provide a server adapter or server capability endpoint.');
|
|
29
|
+
throw new Error('Use output: "static", or set mode: "on-demand" and provide an application-owned adapter.');
|
|
30
|
+
}
|
|
31
|
+
if (buildOutput === 'server') {
|
|
32
|
+
logger.info('Agent Native allows on-demand output; application-owned routes and adapter configuration remain in control.');
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
logger.info('Agent Native Astro integration verified static output; this integration does not install a server adapter or create server endpoints.');
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=astro.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"astro.js","sourceRoot":"","sources":["../src/astro.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAcvC;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAgC;IAC/D,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,SAAS,CAAC,kDAAkD,CAAC,CAAC;IAC1E,CAAC;IACD,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC5F,MAAM,IAAI,SAAS,CAAC,sCAAsC,CAAC,CAAC;IAC9D,CAAC;IAED,OAAO;QACL,IAAI,EAAE,6BAA6B;QACnC,KAAK,EAAE;YACL,oBAAoB,EAAE,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAsB,EAAQ,EAAE;gBACnF,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;oBAC/D,MAAM,CAAC,IAAI,CACT,iGAAiG,CAClG,CAAC;oBACF,OAAO;gBACT,CAAC;gBAED,YAAY,CAAC,MAAM,EAAE,UAAU,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;gBACxE,MAAM,CAAC,IAAI,CACT,OAAO,CAAC,IAAI,KAAK,WAAW;oBAC1B,CAAC,CAAC,4EAA4E;oBAC9E,CAAC,CAAC,2DAA2D,CAChE,CAAC;YACJ,CAAC;YACD,mBAAmB,EAAE,CAAC,EAAE,WAAW,EAAE,MAAM,EAAqB,EAAQ,EAAE;gBACxE,IAAI,WAAW,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;oBAC7D,MAAM,CAAC,IAAI,CACT,kIAAkI,CACnI,CAAC;oBACF,MAAM,IAAI,KAAK,CACb,0FAA0F,CAC3F,CAAC;gBACJ,CAAC;gBAED,IAAI,WAAW,KAAK,QAAQ,EAAE,CAAC;oBAC7B,MAAM,CAAC,IAAI,CACT,6GAA6G,CAC9G,CAAC;gBACJ,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,IAAI,CACT,uIAAuI,CACxI,CAAC;gBACJ,CAAC;YACH,CAAC;SACF;KACF,CAAC;AACJ,CAAC"}
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AuthorizationPort, AuthorizationRequest, ExecutionCaller } from './core/executor.js';
|
|
2
|
+
export type TrustedPrincipal = {
|
|
3
|
+
readonly issuer: string;
|
|
4
|
+
readonly subject: string;
|
|
5
|
+
readonly clientId: string;
|
|
6
|
+
readonly tenantId: string;
|
|
7
|
+
readonly audience: string;
|
|
8
|
+
readonly scopes: readonly string[];
|
|
9
|
+
readonly expiresAt: number;
|
|
10
|
+
};
|
|
11
|
+
export type GrantQuery = {
|
|
12
|
+
readonly issuer: string;
|
|
13
|
+
readonly subject: string;
|
|
14
|
+
readonly clientId: string;
|
|
15
|
+
readonly tenantId: string;
|
|
16
|
+
readonly applicationId: string;
|
|
17
|
+
readonly audience: string;
|
|
18
|
+
readonly policyRevision: string;
|
|
19
|
+
};
|
|
20
|
+
export type PersistedGrant = GrantQuery & {
|
|
21
|
+
readonly grantId: string;
|
|
22
|
+
readonly scopes: readonly string[];
|
|
23
|
+
readonly issuedAt: number;
|
|
24
|
+
readonly expiresAt: number;
|
|
25
|
+
readonly revokedAt: number | null;
|
|
26
|
+
};
|
|
27
|
+
export interface GrantStorePort {
|
|
28
|
+
find(query: GrantQuery): Promise<readonly PersistedGrant[]>;
|
|
29
|
+
save(grant: PersistedGrant): Promise<void>;
|
|
30
|
+
revoke(grantId: string, revokedAt: number): Promise<boolean>;
|
|
31
|
+
}
|
|
32
|
+
export type GrantAuthorizationOptions = {
|
|
33
|
+
readonly principal: TrustedPrincipal | null;
|
|
34
|
+
readonly applicationId: string;
|
|
35
|
+
readonly audience: string;
|
|
36
|
+
readonly policyRevision: string;
|
|
37
|
+
readonly store?: GrantStorePort;
|
|
38
|
+
readonly now?: () => number;
|
|
39
|
+
readonly authorizeResource?: (request: AuthorizationRequest, principal: TrustedPrincipal) => boolean | Promise<boolean>;
|
|
40
|
+
};
|
|
41
|
+
export declare function executionCallerForPrincipal(principal: TrustedPrincipal): ExecutionCaller;
|
|
42
|
+
export declare function hasGrantForScopes(options: {
|
|
43
|
+
readonly principal: TrustedPrincipal;
|
|
44
|
+
readonly applicationId: string;
|
|
45
|
+
readonly audience: string;
|
|
46
|
+
readonly policyRevision: string;
|
|
47
|
+
readonly store: GrantStorePort;
|
|
48
|
+
readonly requiredScopes: readonly string[];
|
|
49
|
+
readonly now?: () => number;
|
|
50
|
+
}): Promise<boolean>;
|
|
51
|
+
export declare function createGrantAuthorization(options: GrantAuthorizationOptions): AuthorizationPort;
|
|
52
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
function requiredString(value, label) {
|
|
2
|
+
if (typeof value !== 'string' || value.trim() === '') {
|
|
3
|
+
throw new TypeError(label + ' must be a non-empty string');
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
function validPrincipal(principal) {
|
|
7
|
+
return (typeof principal === 'object' &&
|
|
8
|
+
principal !== null &&
|
|
9
|
+
typeof principal.issuer === 'string' &&
|
|
10
|
+
principal.issuer.length > 0 &&
|
|
11
|
+
typeof principal.subject === 'string' &&
|
|
12
|
+
principal.subject.length > 0 &&
|
|
13
|
+
typeof principal.clientId === 'string' &&
|
|
14
|
+
principal.clientId.length > 0 &&
|
|
15
|
+
typeof principal.tenantId === 'string' &&
|
|
16
|
+
principal.tenantId.length > 0 &&
|
|
17
|
+
typeof principal.audience === 'string' &&
|
|
18
|
+
principal.audience.length > 0 &&
|
|
19
|
+
Array.isArray(principal.scopes) &&
|
|
20
|
+
principal.scopes.every((scope) => typeof scope === 'string' && scope.length > 0) &&
|
|
21
|
+
Number.isSafeInteger(principal.expiresAt) &&
|
|
22
|
+
principal.expiresAt > 0);
|
|
23
|
+
}
|
|
24
|
+
function authenticatedCaller(caller) {
|
|
25
|
+
if (!('subject' in caller) || !('scopes' in caller))
|
|
26
|
+
return null;
|
|
27
|
+
return { subject: caller.subject, scopes: caller.scopes };
|
|
28
|
+
}
|
|
29
|
+
function queryMatches(grant, query) {
|
|
30
|
+
return (grant.issuer === query.issuer &&
|
|
31
|
+
grant.subject === query.subject &&
|
|
32
|
+
grant.clientId === query.clientId &&
|
|
33
|
+
grant.tenantId === query.tenantId &&
|
|
34
|
+
grant.applicationId === query.applicationId &&
|
|
35
|
+
grant.audience === query.audience &&
|
|
36
|
+
grant.policyRevision === query.policyRevision);
|
|
37
|
+
}
|
|
38
|
+
export function executionCallerForPrincipal(principal) {
|
|
39
|
+
if (!validPrincipal(principal))
|
|
40
|
+
throw new TypeError('trusted principal is invalid');
|
|
41
|
+
return Object.freeze({
|
|
42
|
+
kind: 'authenticated',
|
|
43
|
+
subject: JSON.stringify([principal.issuer, principal.subject, principal.tenantId]),
|
|
44
|
+
scopes: Object.freeze([...principal.scopes]),
|
|
45
|
+
identity: Object.freeze({
|
|
46
|
+
issuer: principal.issuer,
|
|
47
|
+
subject: principal.subject,
|
|
48
|
+
tenantId: principal.tenantId,
|
|
49
|
+
clientId: principal.clientId,
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export async function hasGrantForScopes(options) {
|
|
54
|
+
const { principal } = options;
|
|
55
|
+
if (!validPrincipal(principal))
|
|
56
|
+
return false;
|
|
57
|
+
const now = options.now ?? (() => Math.floor(Date.now() / 1000));
|
|
58
|
+
const currentTime = now();
|
|
59
|
+
if (!Number.isSafeInteger(currentTime) ||
|
|
60
|
+
currentTime < 0 ||
|
|
61
|
+
principal.expiresAt <= currentTime ||
|
|
62
|
+
principal.audience !== options.audience ||
|
|
63
|
+
!options.requiredScopes.every((scope) => principal.scopes.includes(scope)))
|
|
64
|
+
return false;
|
|
65
|
+
const query = {
|
|
66
|
+
issuer: principal.issuer,
|
|
67
|
+
subject: principal.subject,
|
|
68
|
+
clientId: principal.clientId,
|
|
69
|
+
tenantId: principal.tenantId,
|
|
70
|
+
applicationId: options.applicationId,
|
|
71
|
+
audience: options.audience,
|
|
72
|
+
policyRevision: options.policyRevision,
|
|
73
|
+
};
|
|
74
|
+
const grants = await options.store.find(query);
|
|
75
|
+
return grants.some((grant) => queryMatches(grant, query) &&
|
|
76
|
+
grant.revokedAt === null &&
|
|
77
|
+
Number.isSafeInteger(grant.expiresAt) &&
|
|
78
|
+
grant.expiresAt > currentTime &&
|
|
79
|
+
Number.isSafeInteger(grant.issuedAt) &&
|
|
80
|
+
grant.issuedAt <= currentTime &&
|
|
81
|
+
Array.isArray(grant.scopes) &&
|
|
82
|
+
options.requiredScopes.every((scope) => grant.scopes.includes(scope)));
|
|
83
|
+
}
|
|
84
|
+
export function createGrantAuthorization(options) {
|
|
85
|
+
requiredString(options.applicationId, 'applicationId');
|
|
86
|
+
requiredString(options.audience, 'audience');
|
|
87
|
+
requiredString(options.policyRevision, 'policyRevision');
|
|
88
|
+
const now = options.now ?? (() => Math.floor(Date.now() / 1000));
|
|
89
|
+
const principal = options.principal;
|
|
90
|
+
if (principal !== null && !validPrincipal(principal))
|
|
91
|
+
throw new TypeError('trusted principal is invalid');
|
|
92
|
+
return Object.freeze({
|
|
93
|
+
async authorize(request) {
|
|
94
|
+
if (request.access.kind === 'public')
|
|
95
|
+
return request.risk === 'read';
|
|
96
|
+
if (principal === null ||
|
|
97
|
+
options.store === undefined ||
|
|
98
|
+
!validPrincipal(principal) ||
|
|
99
|
+
options.authorizeResource === undefined)
|
|
100
|
+
return false;
|
|
101
|
+
const caller = authenticatedCaller(request.caller);
|
|
102
|
+
if (caller === null)
|
|
103
|
+
return false;
|
|
104
|
+
const expectedSubject = JSON.stringify([
|
|
105
|
+
principal.issuer,
|
|
106
|
+
principal.subject,
|
|
107
|
+
principal.tenantId,
|
|
108
|
+
]);
|
|
109
|
+
if (caller.subject !== expectedSubject ||
|
|
110
|
+
caller.scopes.length !== principal.scopes.length ||
|
|
111
|
+
!principal.scopes.every((scope) => caller.scopes.includes(scope)))
|
|
112
|
+
return false;
|
|
113
|
+
const hasGrant = await hasGrantForScopes({
|
|
114
|
+
principal,
|
|
115
|
+
applicationId: options.applicationId,
|
|
116
|
+
audience: options.audience,
|
|
117
|
+
policyRevision: options.policyRevision,
|
|
118
|
+
store: options.store,
|
|
119
|
+
requiredScopes: request.access.scopes,
|
|
120
|
+
now,
|
|
121
|
+
});
|
|
122
|
+
if (!hasGrant)
|
|
123
|
+
return false;
|
|
124
|
+
return await options.authorizeResource(request, principal);
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type CapabilityDefinition } from './core/contracts.js';
|
|
2
|
+
import { type AuthorizationPort, type ExecutionCaller } from './core/executor.js';
|
|
3
|
+
import type { CapabilityRegistry } from './core/registry.js';
|
|
4
|
+
import { type CapabilitySurfaceExposure } from './discovery.js';
|
|
5
|
+
export type WebMcpTool = {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly title?: string;
|
|
8
|
+
readonly description: string;
|
|
9
|
+
readonly inputSchema: Readonly<Record<string, unknown>>;
|
|
10
|
+
readonly annotations?: Readonly<Record<string, boolean>>;
|
|
11
|
+
readonly execute: (input: unknown, options: {
|
|
12
|
+
readonly signal: AbortSignal;
|
|
13
|
+
}) => unknown | Promise<unknown>;
|
|
14
|
+
};
|
|
15
|
+
export interface WebMcpModelContext {
|
|
16
|
+
registerTool(tool: WebMcpTool, options?: {
|
|
17
|
+
readonly signal?: AbortSignal;
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export type BrowserDocumentLike = Pick<Document, 'defaultView'> & {
|
|
21
|
+
readonly modelContext?: WebMcpModelContext;
|
|
22
|
+
};
|
|
23
|
+
export type BrowserExecutionContext = {
|
|
24
|
+
readonly caller: ExecutionCaller;
|
|
25
|
+
readonly authorization: AuthorizationPort;
|
|
26
|
+
};
|
|
27
|
+
export type BrowserAdapterOptions = {
|
|
28
|
+
readonly surfaceExposure?: CapabilitySurfaceExposure;
|
|
29
|
+
readonly canExpose?: (definition: CapabilityDefinition<unknown, unknown>) => boolean | Promise<boolean>;
|
|
30
|
+
readonly resolveExecutionContext?: () => BrowserExecutionContext | Promise<BrowserExecutionContext>;
|
|
31
|
+
};
|
|
32
|
+
export type BrowserSkippedTool = {
|
|
33
|
+
readonly capabilityId: string;
|
|
34
|
+
readonly reason: 'no-browser-binding' | 'ambiguous-browser-binding' | 'name-too-long' | 'policy-denied' | 'registration-failed';
|
|
35
|
+
};
|
|
36
|
+
export type BrowserSyncReport = {
|
|
37
|
+
readonly supported: boolean;
|
|
38
|
+
readonly registered: readonly string[];
|
|
39
|
+
readonly skipped: readonly BrowserSkippedTool[];
|
|
40
|
+
};
|
|
41
|
+
export interface BrowserCapabilityAdapter {
|
|
42
|
+
readonly supported: boolean;
|
|
43
|
+
sync(registry: CapabilityRegistry, options?: BrowserAdapterOptions): Promise<BrowserSyncReport>;
|
|
44
|
+
dispose(): void;
|
|
45
|
+
}
|
|
46
|
+
export declare function createBrowserCapabilityAdapter(document: BrowserDocumentLike): BrowserCapabilityAdapter;
|
|
47
|
+
//# sourceMappingURL=browser.d.ts.map
|