@noodleseed/one 0.140.0 → 0.141.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/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
- package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +9 -1
- package/node_modules/@noodle-borg/app-package/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/app-package/dist/index.js +1 -0
- package/node_modules/@noodle-borg/{compiler → app-package}/dist/schema-validation.d.ts +2 -1
- package/node_modules/@noodle-borg/app-package/package.json +2 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-app-sandbox.d.ts +13 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-app-sandbox.js +204 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +22 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +10 -7
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript.d.ts +18 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript.js +6 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/elevation.d.ts +7 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/elevation.js +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +2 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +2 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.d.ts +21 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.js +39 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/session-resume.d.ts +7 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/session-resume.js +12 -0
- package/node_modules/@noodle-borg/auth/dist/control-plane-token-exchange.d.ts +110 -0
- package/node_modules/@noodle-borg/auth/dist/control-plane-token-exchange.js +217 -0
- package/node_modules/@noodle-borg/auth/dist/index.d.ts +1 -0
- package/node_modules/@noodle-borg/auth/dist/index.js +1 -0
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +5 -0
- package/node_modules/@noodle-borg/compiler/dist/index.d.ts +1 -2
- package/node_modules/@noodle-borg/compiler/dist/index.js +1 -2
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +16 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +19 -2
- package/node_modules/@noodle-borg/compiler/package.json +0 -2
- package/node_modules/@noodle-borg/compute/dist/code-connector.js +44 -1
- package/node_modules/@noodle-borg/compute/dist/quickjs-engine.js +82 -266
- package/node_modules/@noodle-borg/compute/dist/sandbox-eval.js +276 -0
- package/node_modules/@noodle-borg/compute/dist/worker-pool.js +262 -0
- package/node_modules/@noodle-borg/{service/dist/auth/deploy-gate.d.ts → control-plane/dist/deploy-gates.d.ts} +10 -6
- package/node_modules/@noodle-borg/control-plane/dist/deploy-gates.js +266 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +1 -0
- package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
- package/node_modules/@noodle-borg/developer-mcp/dist/capabilities.js +11 -1
- package/node_modules/@noodle-borg/developer-mcp/dist/index.js +1 -1
- package/node_modules/@noodle-borg/protocol/dist/execution-error-mapping.js +13 -1
- package/node_modules/@noodle-borg/protocol/dist/observation.d.ts +2 -1
- package/node_modules/@noodle-borg/protocol/dist/observation.js +2 -1
- package/node_modules/@noodle-borg/runtime/dist/connector/failure-details.d.ts +1 -1
- package/node_modules/@noodle-borg/runtime/dist/connector/failure-details.js +5 -0
- package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +15 -1
- package/node_modules/@noodle-borg/runtime/dist/connector/types.js +6 -0
- package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +1 -0
- package/node_modules/@noodle-borg/service/dist/auth/developer-grant-guard.js +2 -1
- package/node_modules/@noodle-borg/service/dist/modules/route-dispatch.js +1 -1
- package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +15 -3
- package/node_modules/@noodle-borg/service/dist/routes/assistant-clients.js +116 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-dispatch.js +12 -3
- package/node_modules/@noodle-borg/service/dist/routes/assistant-elevation.js +1 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-stream.js +4 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +8 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +3 -9
- package/node_modules/@noodle-borg/service/dist/routes/assistant-route-http.js +11 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-sandbox.js +12 -47
- package/node_modules/@noodle-borg/service/dist/routes/assistant-session-target.js +34 -8
- package/node_modules/@noodle-borg/service/dist/routes/assistant-transcript.js +27 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant.js +28 -130
- package/node_modules/@noodle-borg/service/dist/routes/config-values.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +8 -2
- package/node_modules/@noodle-borg/service/dist/service.js +15 -1
- package/node_modules/@noodle-borg/wire-contracts/dist/assistant.d.ts +17 -0
- package/node_modules/@noodle-borg/wire-contracts/dist/assistant.js +25 -0
- package/node_modules/@noodleseed/assistant/README.md +4 -9
- package/package.json +1 -1
- /package/node_modules/@noodle-borg/{compiler → app-package}/dist/schema-validation.js +0 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { createHash, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { jwtVerify } from 'jose';
|
|
3
|
+
/**
|
|
4
|
+
* The inbound half of the delegated token exchange (RFC 8693), serving exactly one first-party
|
|
5
|
+
* caller: the hosted assistant's control-plane connector. We implement the same target-endpoint
|
|
6
|
+
* contract we ask every customer token endpoint to implement (docs/spec/connectors.md, ADR 0152) —
|
|
7
|
+
* verify the platform-signed assertion, identify the user by `(customer_identity.issuer, sub)`,
|
|
8
|
+
* honour the 120s lifetime and single-use `jti`, and return a standard token response.
|
|
9
|
+
*
|
|
10
|
+
* The same issuer and signing key mint control-plane access tokens and exchange assertions, so
|
|
11
|
+
* **audience separation is the entire security boundary**: this exchange accepts only the dedicated
|
|
12
|
+
* exchange URN below and structurally refuses every resource audience (all of which are URLs) —
|
|
13
|
+
* including our own access tokens presented as subject tokens. The minted token is capability-scoped
|
|
14
|
+
* through the developer-grant machinery, so rollback/config-write can never be stored on it and
|
|
15
|
+
* every control-plane route re-checks live org membership per request.
|
|
16
|
+
*
|
|
17
|
+
* This module is the Express-free decision core; the hosted service wraps it in a thin HTTP adapter
|
|
18
|
+
* and supplies every policy port below (ADR 0218).
|
|
19
|
+
*/
|
|
20
|
+
export const CONTROL_PLANE_EXCHANGE_AUDIENCE = 'urn:noodleseed:control-plane:delegated-exchange';
|
|
21
|
+
export const CONTROL_PLANE_EXCHANGE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:token-exchange';
|
|
22
|
+
const SUBJECT_TOKEN_TYPE = 'urn:ietf:params:oauth:token-type:jwt';
|
|
23
|
+
const ASSERTION_MAX_LIFETIME_SECONDS = 125; // 120s contract + clock tolerance
|
|
24
|
+
const TOKEN_TTL_SECONDS = 600;
|
|
25
|
+
const ASSISTANT_CLIENT_ISSUER_PREFIX = 'urn:noodleseed:assistant-client:';
|
|
26
|
+
/** Local/test-only; production deployments need durable shared atomic replay storage. */
|
|
27
|
+
export class InMemoryTokenExchangeJtiStore {
|
|
28
|
+
#seen = new Map();
|
|
29
|
+
async consume(jti, expiresAtMs, nowMs) {
|
|
30
|
+
for (const [key, expiry] of this.#seen) {
|
|
31
|
+
if (expiry <= nowMs)
|
|
32
|
+
this.#seen.delete(key);
|
|
33
|
+
}
|
|
34
|
+
if (this.#seen.has(jti))
|
|
35
|
+
return false;
|
|
36
|
+
this.#seen.set(jti, expiresAtMs);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function decideControlPlaneTokenExchange(request, deps) {
|
|
41
|
+
const refuse = async (status, error, description, reasonCode, tenant) => {
|
|
42
|
+
// Audit is tenant-scoped; refusals before a verified tenant claim have no tenant to charge.
|
|
43
|
+
if (tenant !== undefined) {
|
|
44
|
+
await deps.audit?.emit({
|
|
45
|
+
eventType: 'control_plane.token_exchange.refused',
|
|
46
|
+
org: tenant.org,
|
|
47
|
+
app: tenant.app,
|
|
48
|
+
env: tenant.env,
|
|
49
|
+
decision: 'deny',
|
|
50
|
+
status,
|
|
51
|
+
reasonCode,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
status,
|
|
56
|
+
body: { error, error_description: description },
|
|
57
|
+
...(status === 401 ? { wwwAuthenticate: 'Basic realm="token"' } : {}),
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
const form = request.form;
|
|
61
|
+
if (!authenticateExchangeClient(request, deps.config)) {
|
|
62
|
+
return refuse(401, 'invalid_client', 'invalid client authentication', 'client_auth');
|
|
63
|
+
}
|
|
64
|
+
const subjectToken = oneString(form.subject_token);
|
|
65
|
+
if (subjectToken === undefined || form.subject_token_type !== SUBJECT_TOKEN_TYPE) {
|
|
66
|
+
return refuse(400, 'invalid_request', 'subject_token and subject_token_type=jwt are required', 'malformed_request');
|
|
67
|
+
}
|
|
68
|
+
// The audience form parameter, when present, must name the exchange URN exactly. Anything else —
|
|
69
|
+
// above all a resource URL — is a category error this exchange exists to refuse.
|
|
70
|
+
const audienceParam = form.audience;
|
|
71
|
+
if (audienceParam !== undefined && audienceParam !== CONTROL_PLANE_EXCHANGE_AUDIENCE) {
|
|
72
|
+
return refuse(400, 'invalid_target', 'unsupported exchange audience', 'audience');
|
|
73
|
+
}
|
|
74
|
+
let payload;
|
|
75
|
+
try {
|
|
76
|
+
const verified = await jwtVerify(subjectToken, await deps.verifierKey(), {
|
|
77
|
+
issuer: deps.issuer,
|
|
78
|
+
// Pinned to the URN: our own access tokens (resource-URL audiences) can never verify here.
|
|
79
|
+
audience: CONTROL_PLANE_EXCHANGE_AUDIENCE,
|
|
80
|
+
algorithms: ['RS256'],
|
|
81
|
+
clockTolerance: 5,
|
|
82
|
+
});
|
|
83
|
+
payload = verified.payload;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return refuse(400, 'invalid_grant', 'subject token is not a valid assertion', 'assertion');
|
|
87
|
+
}
|
|
88
|
+
const issuedAt = typeof payload.iat === 'number' ? payload.iat : undefined;
|
|
89
|
+
const expiresAt = typeof payload.exp === 'number' ? payload.exp : undefined;
|
|
90
|
+
if (issuedAt === undefined ||
|
|
91
|
+
expiresAt === undefined ||
|
|
92
|
+
expiresAt - issuedAt > ASSERTION_MAX_LIFETIME_SECONDS) {
|
|
93
|
+
return refuse(400, 'invalid_grant', 'assertion lifetime is invalid', 'assertion_lifetime');
|
|
94
|
+
}
|
|
95
|
+
const subject = typeof payload.sub === 'string' && payload.sub.length > 0 ? payload.sub : undefined;
|
|
96
|
+
const email = typeof payload.email === 'string' && payload.email.length > 0 ? payload.email : undefined;
|
|
97
|
+
const jti = typeof payload.jti === 'string' && payload.jti.length > 0 ? payload.jti : undefined;
|
|
98
|
+
const tenant = typeof payload.tenant === 'string' ? payload.tenant : undefined;
|
|
99
|
+
const customerIdentity = payload.customer_identity;
|
|
100
|
+
if (subject === undefined || jti === undefined || tenant === undefined) {
|
|
101
|
+
return refuse(400, 'invalid_grant', 'assertion claims are incomplete', 'assertion_claims');
|
|
102
|
+
}
|
|
103
|
+
// The control-plane gate refuses email-less identities; requiring it here fails earlier and clearer.
|
|
104
|
+
if (email === undefined) {
|
|
105
|
+
return refuse(400, 'invalid_grant', 'assertion carries no email', 'assertion_email');
|
|
106
|
+
}
|
|
107
|
+
// A route binding names a customer-endpoint exchange; its presence here means a misrouted
|
|
108
|
+
// connector, never this first-party exchange.
|
|
109
|
+
if (payload.route !== undefined) {
|
|
110
|
+
return refuse(400, 'invalid_grant', 'assertion carries a route binding', 'assertion_route');
|
|
111
|
+
}
|
|
112
|
+
if (!deps.config.allowedTenants.includes(tenant)) {
|
|
113
|
+
return refuse(400, 'invalid_grant', 'assertion tenant is not served here', 'tenant');
|
|
114
|
+
}
|
|
115
|
+
if (customerIdentity?.version !== 1 ||
|
|
116
|
+
typeof customerIdentity.issuer !== 'string' ||
|
|
117
|
+
!customerIdentity.issuer.startsWith(ASSISTANT_CLIENT_ISSUER_PREFIX)) {
|
|
118
|
+
return refuse(400, 'invalid_grant', 'assertion identity issuer is not supported', 'issuer');
|
|
119
|
+
}
|
|
120
|
+
const assistantClientId = customerIdentity.issuer.slice(ASSISTANT_CLIENT_ISSUER_PREFIX.length);
|
|
121
|
+
const [org = '', app = '', env = ''] = tenant.split('/');
|
|
122
|
+
const tenantRef = { org, app, env };
|
|
123
|
+
const liveClients = await deps.listAssistantClientIds(tenantRef);
|
|
124
|
+
if (!liveClients.includes(assistantClientId)) {
|
|
125
|
+
// Revoking the assistant client is the kill switch for this whole path.
|
|
126
|
+
return refuse(400, 'invalid_grant', 'assertion identity issuer is not live', 'issuer_liveness', tenantRef);
|
|
127
|
+
}
|
|
128
|
+
if (!(await deps.consumeJti(jti, expiresAt * 1000))) {
|
|
129
|
+
return refuse(400, 'invalid_grant', 'assertion was already used', 'jti_replay', tenantRef);
|
|
130
|
+
}
|
|
131
|
+
if ((await deps.resolveSubject(subject)) === undefined) {
|
|
132
|
+
return refuse(400, 'invalid_grant', 'subject has no active platform principal', 'subject', tenantRef);
|
|
133
|
+
}
|
|
134
|
+
const scopes = requestedScopes(form.scope, deps.capabilityCeiling);
|
|
135
|
+
if (scopes === undefined) {
|
|
136
|
+
return refuse(400, 'invalid_scope', 'requested scope is invalid', 'scope', tenantRef);
|
|
137
|
+
}
|
|
138
|
+
await deps.ensureWorkspace?.({ subject, email });
|
|
139
|
+
const resource = `${deps.issuer}${deps.resourcePath}`;
|
|
140
|
+
const grant = await deps.grants.getOrCreateActive({
|
|
141
|
+
clientId: deps.config.clientId,
|
|
142
|
+
subject,
|
|
143
|
+
resource,
|
|
144
|
+
capabilities: scopes,
|
|
145
|
+
});
|
|
146
|
+
const scope = scopes.join(' ');
|
|
147
|
+
const accessToken = await deps.issueAccessToken({
|
|
148
|
+
subject,
|
|
149
|
+
email,
|
|
150
|
+
resource,
|
|
151
|
+
scope,
|
|
152
|
+
developerGrantId: grant.id,
|
|
153
|
+
oauthClientId: deps.config.clientId,
|
|
154
|
+
ttlSeconds: TOKEN_TTL_SECONDS,
|
|
155
|
+
});
|
|
156
|
+
await deps.audit?.emit({
|
|
157
|
+
eventType: 'control_plane.token_exchange.issued',
|
|
158
|
+
org,
|
|
159
|
+
app,
|
|
160
|
+
env,
|
|
161
|
+
decision: 'allow',
|
|
162
|
+
status: 200,
|
|
163
|
+
actorSubject: subject,
|
|
164
|
+
details: { grantId: grant.id, clientId: deps.config.clientId, scope },
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
status: 200,
|
|
168
|
+
body: {
|
|
169
|
+
access_token: accessToken,
|
|
170
|
+
token_type: 'Bearer',
|
|
171
|
+
expires_in: TOKEN_TTL_SECONDS,
|
|
172
|
+
scope,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/** Basic (default) or client_secret_post, compared timing-safe over digests. */
|
|
177
|
+
function authenticateExchangeClient(request, config) {
|
|
178
|
+
const header = request.authorizationHeader;
|
|
179
|
+
let id;
|
|
180
|
+
let secret;
|
|
181
|
+
if (typeof header === 'string' && header.startsWith('Basic ')) {
|
|
182
|
+
const decoded = Buffer.from(header.slice('Basic '.length), 'base64').toString('utf8');
|
|
183
|
+
const split = decoded.indexOf(':');
|
|
184
|
+
if (split > 0) {
|
|
185
|
+
id = decodeURIComponent(decoded.slice(0, split));
|
|
186
|
+
secret = decodeURIComponent(decoded.slice(split + 1));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
id = oneString(request.form.client_id);
|
|
191
|
+
secret = oneString(request.form.client_secret);
|
|
192
|
+
}
|
|
193
|
+
if (id === undefined || secret === undefined)
|
|
194
|
+
return false;
|
|
195
|
+
return digestEquals(id, config.clientId) && digestEquals(secret, config.clientSecret);
|
|
196
|
+
}
|
|
197
|
+
function digestEquals(actual, expected) {
|
|
198
|
+
return timingSafeEqual(createHash('sha256').update(actual).digest(), createHash('sha256').update(expected).digest());
|
|
199
|
+
}
|
|
200
|
+
function oneString(value) {
|
|
201
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
202
|
+
}
|
|
203
|
+
function requestedScopes(value, ceiling) {
|
|
204
|
+
if (value === undefined)
|
|
205
|
+
return [...ceiling].sort();
|
|
206
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
207
|
+
return undefined;
|
|
208
|
+
const scopes = value.split(' ');
|
|
209
|
+
if (scopes.length > 16 ||
|
|
210
|
+
scopes.some((scope) => scope.length < 1 || scope.length > 128) ||
|
|
211
|
+
new Set(scopes).size !== scopes.length) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
const allowed = new Set(ceiling);
|
|
215
|
+
return scopes.every((scope) => allowed.has(scope)) ? [...scopes].sort() : undefined;
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=control-plane-token-exchange.js.map
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* package stays Express-free.
|
|
9
9
|
*/
|
|
10
10
|
export { type AuthorizationClaimMap, canonicalizeAuthorizationClaimValues, MAX_AUTHORIZATION_CLAIM_VALUES, MAX_ROLE_LENGTH, MAX_SCOPE_LENGTH, projectCustomerRoutingClaims, projectMappedAuthorizationClaim, projectNoodleRoles, projectStandardScopes, } from './claims.js';
|
|
11
|
+
export { CONTROL_PLANE_EXCHANGE_AUDIENCE, CONTROL_PLANE_EXCHANGE_GRANT_TYPE, type ControlPlaneExchangeAuditEvent, type ControlPlaneExchangeConfig, type ControlPlaneExchangeDecision, type ControlPlaneExchangeDeps, type ControlPlaneExchangeRequest, type ControlPlaneExchangeTenantRef, decideControlPlaneTokenExchange, InMemoryTokenExchangeJtiStore, type TokenExchangeJtiStore, } from './control-plane-token-exchange.js';
|
|
11
12
|
export { type DiscoveredIssuerMetadata, discoverIssuerMetadata, type IssuerMetadataCandidate, type IssuerMetadataKind, issuerMetadataCandidates, } from './discovery.js';
|
|
12
13
|
export { type AccessTokenClaims, mintAccessToken } from './jwt-issuer.js';
|
|
13
14
|
export { type BearerChallenge, canonicalScopes, McpOAuthClient, type McpOAuthClientOptions, type McpOAuthClientRegistration, type McpOAuthDiscovery, type McpOAuthPendingAuthorization, type McpOAuthTokens, parseBearerChallenge, } from './mcp-oauth-client.js';
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* package stays Express-free.
|
|
9
9
|
*/
|
|
10
10
|
export { canonicalizeAuthorizationClaimValues, MAX_AUTHORIZATION_CLAIM_VALUES, MAX_ROLE_LENGTH, MAX_SCOPE_LENGTH, projectCustomerRoutingClaims, projectMappedAuthorizationClaim, projectNoodleRoles, projectStandardScopes, } from './claims.js';
|
|
11
|
+
export { CONTROL_PLANE_EXCHANGE_AUDIENCE, CONTROL_PLANE_EXCHANGE_GRANT_TYPE, decideControlPlaneTokenExchange, InMemoryTokenExchangeJtiStore, } from './control-plane-token-exchange.js';
|
|
11
12
|
export { discoverIssuerMetadata, issuerMetadataCandidates, } from './discovery.js';
|
|
12
13
|
export { mintAccessToken } from './jwt-issuer.js';
|
|
13
14
|
export { canonicalScopes, McpOAuthClient, parseBearerChallenge, } from './mcp-oauth-client.js';
|
|
@@ -67,6 +67,11 @@ export interface AssistantLabels {
|
|
|
67
67
|
readonly sessionLoading?: string;
|
|
68
68
|
readonly sessionReady?: string;
|
|
69
69
|
readonly sessionError?: string;
|
|
70
|
+
readonly signInHeading?: string;
|
|
71
|
+
readonly signInBody?: string;
|
|
72
|
+
readonly signInAction?: string;
|
|
73
|
+
/** Renders a sign-up button on the sign-in card; leaving it unset renders none. */
|
|
74
|
+
readonly signUpAction?: string;
|
|
70
75
|
}
|
|
71
76
|
export interface AssistantUiOptions {
|
|
72
77
|
readonly layout?: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type { AppPackageArtifactV1, AppPackageCapabilityRef, AppPackageSurface, CompiledAgentSkill, } from '@noodle-borg/app-package';
|
|
2
|
-
export { APP_PACKAGE_V1_VALIDATION_LIMITS, appPackageArtifactV1Schema, canonicalJson, type SensitiveContentFinding, sensitiveContentFinding, sha256Canonical, } from '@noodle-borg/app-package';
|
|
2
|
+
export { APP_PACKAGE_V1_VALIDATION_LIMITS, appPackageArtifactV1Schema, canonicalJson, type JsonSchemaValidationIssue, type SensitiveContentFinding, sensitiveContentFinding, sha256Canonical, validateJsonSchema, validateJsonSchemaWithDefaults, } from '@noodle-borg/app-package';
|
|
3
3
|
export { type ParsedUri, type ParseUriResult, parseUriTemplate, type TemplateUri, } from '@noodle-borg/uri-template';
|
|
4
4
|
export { compileAppPackage } from './app-package/compile.js';
|
|
5
5
|
export { isSafeReadTool, requiresToolConfirmation } from './artifact/consent.js';
|
|
@@ -21,6 +21,5 @@ export { type ResolveSchemaResult, resolveSchemaUses, type SchemasMap, } from '.
|
|
|
21
21
|
export { anonymousBehavior } from './manifest/website-projection.js';
|
|
22
22
|
export { type SniffedImage, sniffImageBytes } from './mime-sniffing.js';
|
|
23
23
|
export { manifestJsonSchema } from './schema-export.js';
|
|
24
|
-
export { type JsonSchemaValidationIssue, validateJsonSchema, validateJsonSchemaWithDefaults, } from './schema-validation.js';
|
|
25
24
|
export { MAX_COMPILED_WIDGET_HTML_BYTES, MAX_RAW_WIDGET_HTML_BYTES, MAX_TOTAL_WIDGET_HTML_BYTES, RECOMMENDED_COMPILED_WIDGET_HTML_BYTES, } from './widget-limits.js';
|
|
26
25
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { APP_PACKAGE_V1_VALIDATION_LIMITS, appPackageArtifactV1Schema, canonicalJson, sensitiveContentFinding, sha256Canonical, } from '@noodle-borg/app-package';
|
|
1
|
+
export { APP_PACKAGE_V1_VALIDATION_LIMITS, appPackageArtifactV1Schema, canonicalJson, sensitiveContentFinding, sha256Canonical, validateJsonSchema, validateJsonSchemaWithDefaults, } from '@noodle-borg/app-package';
|
|
2
2
|
export { parseUriTemplate, } from '@noodle-borg/uri-template';
|
|
3
3
|
export { compileAppPackage } from './app-package/compile.js';
|
|
4
4
|
export { isSafeReadTool, requiresToolConfirmation } from './artifact/consent.js';
|
|
@@ -18,6 +18,5 @@ export { resolveSchemaUses, } from './manifest/schema-refs.js';
|
|
|
18
18
|
export { anonymousBehavior } from './manifest/website-projection.js';
|
|
19
19
|
export { sniffImageBytes } from './mime-sniffing.js';
|
|
20
20
|
export { manifestJsonSchema } from './schema-export.js';
|
|
21
|
-
export { validateJsonSchema, validateJsonSchemaWithDefaults, } from './schema-validation.js';
|
|
22
21
|
export { MAX_COMPILED_WIDGET_HTML_BYTES, MAX_RAW_WIDGET_HTML_BYTES, MAX_TOTAL_WIDGET_HTML_BYTES, RECOMMENDED_COMPILED_WIDGET_HTML_BYTES, } from './widget-limits.js';
|
|
23
22
|
//# sourceMappingURL=index.js.map
|
|
@@ -239,6 +239,10 @@ export declare const manifestV1Schema: z.ZodObject<{
|
|
|
239
239
|
sessionLoading: z.ZodOptional<z.ZodString>;
|
|
240
240
|
sessionReady: z.ZodOptional<z.ZodString>;
|
|
241
241
|
sessionError: z.ZodOptional<z.ZodString>;
|
|
242
|
+
signInHeading: z.ZodOptional<z.ZodString>;
|
|
243
|
+
signInBody: z.ZodOptional<z.ZodString>;
|
|
244
|
+
signInAction: z.ZodOptional<z.ZodString>;
|
|
245
|
+
signUpAction: z.ZodOptional<z.ZodString>;
|
|
242
246
|
}, z.core.$strict>>;
|
|
243
247
|
presentation: z.ZodOptional<z.ZodObject<{
|
|
244
248
|
panel: z.ZodOptional<z.ZodObject<{
|
|
@@ -693,6 +697,10 @@ export declare const manifestV2Schema: z.ZodObject<{
|
|
|
693
697
|
sessionLoading: z.ZodOptional<z.ZodString>;
|
|
694
698
|
sessionReady: z.ZodOptional<z.ZodString>;
|
|
695
699
|
sessionError: z.ZodOptional<z.ZodString>;
|
|
700
|
+
signInHeading: z.ZodOptional<z.ZodString>;
|
|
701
|
+
signInBody: z.ZodOptional<z.ZodString>;
|
|
702
|
+
signInAction: z.ZodOptional<z.ZodString>;
|
|
703
|
+
signUpAction: z.ZodOptional<z.ZodString>;
|
|
696
704
|
}, z.core.$strict>>;
|
|
697
705
|
presentation: z.ZodOptional<z.ZodObject<{
|
|
698
706
|
panel: z.ZodOptional<z.ZodObject<{
|
|
@@ -1357,6 +1365,10 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1357
1365
|
sessionLoading: z.ZodOptional<z.ZodString>;
|
|
1358
1366
|
sessionReady: z.ZodOptional<z.ZodString>;
|
|
1359
1367
|
sessionError: z.ZodOptional<z.ZodString>;
|
|
1368
|
+
signInHeading: z.ZodOptional<z.ZodString>;
|
|
1369
|
+
signInBody: z.ZodOptional<z.ZodString>;
|
|
1370
|
+
signInAction: z.ZodOptional<z.ZodString>;
|
|
1371
|
+
signUpAction: z.ZodOptional<z.ZodString>;
|
|
1360
1372
|
}, z.core.$strict>>;
|
|
1361
1373
|
presentation: z.ZodOptional<z.ZodObject<{
|
|
1362
1374
|
panel: z.ZodOptional<z.ZodObject<{
|
|
@@ -1810,6 +1822,10 @@ export declare const manifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1810
1822
|
sessionLoading: z.ZodOptional<z.ZodString>;
|
|
1811
1823
|
sessionReady: z.ZodOptional<z.ZodString>;
|
|
1812
1824
|
sessionError: z.ZodOptional<z.ZodString>;
|
|
1825
|
+
signInHeading: z.ZodOptional<z.ZodString>;
|
|
1826
|
+
signInBody: z.ZodOptional<z.ZodString>;
|
|
1827
|
+
signInAction: z.ZodOptional<z.ZodString>;
|
|
1828
|
+
signUpAction: z.ZodOptional<z.ZodString>;
|
|
1813
1829
|
}, z.core.$strict>>;
|
|
1814
1830
|
presentation: z.ZodOptional<z.ZodObject<{
|
|
1815
1831
|
panel: z.ZodOptional<z.ZodObject<{
|
|
@@ -318,6 +318,10 @@ const assistantUiSchema = z
|
|
|
318
318
|
sessionLoading: z.string().trim().min(1).max(120).optional(),
|
|
319
319
|
sessionReady: z.string().trim().min(1).max(120).optional(),
|
|
320
320
|
sessionError: z.string().trim().min(1).max(120).optional(),
|
|
321
|
+
signInHeading: z.string().trim().min(1).max(120).optional(),
|
|
322
|
+
signInBody: z.string().max(240).optional(),
|
|
323
|
+
signInAction: z.string().trim().min(1).max(80).optional(),
|
|
324
|
+
signUpAction: z.string().trim().min(1).max(80).optional(),
|
|
321
325
|
})
|
|
322
326
|
.strict()
|
|
323
327
|
.optional(),
|
|
@@ -669,12 +673,25 @@ export const manifestV1Schema = z.object({
|
|
|
669
673
|
connectors: z.record(nameSchema, connectorRefSchema).optional(),
|
|
670
674
|
...commonManifestFields,
|
|
671
675
|
});
|
|
672
|
-
export const manifestV2Schema = z
|
|
676
|
+
export const manifestV2Schema = z
|
|
677
|
+
.object({
|
|
673
678
|
manifestVersion: z.literal('2'),
|
|
674
679
|
server: serverV2Schema,
|
|
675
|
-
tools: z.array(toolV2Schema)
|
|
680
|
+
tools: z.array(toolV2Schema),
|
|
676
681
|
connectors: z.record(nameSchema, connectorRefV2Schema).optional(),
|
|
677
682
|
...commonManifestFields,
|
|
683
|
+
})
|
|
684
|
+
// A v2 server's callable surface may come entirely from generated knowledge capabilities
|
|
685
|
+
// (ADR 0217): a knowledge-only server is valid, an empty server is not. v1 keeps its
|
|
686
|
+
// authored-tool minimum — it has no server.knowledge to count.
|
|
687
|
+
.superRefine((manifest, ctx) => {
|
|
688
|
+
if (manifest.tools.length === 0 && (manifest.server.knowledge?.length ?? 0) === 0) {
|
|
689
|
+
ctx.addIssue({
|
|
690
|
+
code: 'custom',
|
|
691
|
+
path: ['tools'],
|
|
692
|
+
message: 'declare at least one tool or one server.knowledge component',
|
|
693
|
+
});
|
|
694
|
+
}
|
|
678
695
|
});
|
|
679
696
|
export const manifestSchema = z.discriminatedUnion('manifestVersion', [
|
|
680
697
|
manifestV1Schema,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ConnectorInvocationError, } from '@noodle-borg/runtime';
|
|
1
2
|
import { ComputeError, DEFAULT_LIMITS, } from './engine.js';
|
|
2
3
|
/**
|
|
3
4
|
* A connector whose operations run tenant-authored JavaScript in a {@link ComputeEngine} sandbox — the
|
|
@@ -28,14 +29,56 @@ export class CodeConnector {
|
|
|
28
29
|
if (!op)
|
|
29
30
|
throw new Error(`connector "${this.id}" has no operation "${call.operation}"`);
|
|
30
31
|
const instance = await this.#engine.instantiate(op.module);
|
|
32
|
+
let timings;
|
|
31
33
|
try {
|
|
32
|
-
|
|
34
|
+
const output = await instance.invoke(call.args, op.limits ?? DEFAULT_LIMITS, op.calls ? computeHost(op.calls, call.host) : undefined, (t) => {
|
|
35
|
+
timings = t;
|
|
36
|
+
});
|
|
37
|
+
// Queue wait and execution are recorded independently so burst head-of-line blocking is
|
|
38
|
+
// observable on successful calls too, not only on the ones that fail.
|
|
39
|
+
if (timings !== undefined) {
|
|
40
|
+
call.trace?.record({
|
|
41
|
+
kind: 'connector',
|
|
42
|
+
connectorId: this.id,
|
|
43
|
+
connectorVersion: this.version,
|
|
44
|
+
operation: call.operation,
|
|
45
|
+
queueWaitMs: timings.queueWaitMs,
|
|
46
|
+
executionMs: timings.executionMs,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return output;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
throw classifyInvocationFailure(error, timings);
|
|
33
53
|
}
|
|
34
54
|
finally {
|
|
35
55
|
instance.dispose();
|
|
36
56
|
}
|
|
37
57
|
}
|
|
38
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Re-throw sandbox time-budget failures as {@link ConnectorInvocationError} with a stable category,
|
|
61
|
+
* so the runtime surfaces `connector_error` with a `timeout`/`queue_timeout` reason and records the
|
|
62
|
+
* queue-wait and execution durations. A queue-expired call never started executing, so it is safely
|
|
63
|
+
* retryable. All other compute failures propagate unchanged.
|
|
64
|
+
*/
|
|
65
|
+
function classifyInvocationFailure(error, timings) {
|
|
66
|
+
if (!(error instanceof ComputeError))
|
|
67
|
+
return error;
|
|
68
|
+
if (error.code !== 'timeout' && error.code !== 'queue_timeout')
|
|
69
|
+
return error;
|
|
70
|
+
return new ConnectorInvocationError(error.message, {
|
|
71
|
+
category: error.code,
|
|
72
|
+
attempts: 1,
|
|
73
|
+
...(error.code === 'queue_timeout' ? { retryable: true } : {}),
|
|
74
|
+
...(timings === undefined
|
|
75
|
+
? {}
|
|
76
|
+
: {
|
|
77
|
+
queueWaitMs: Math.round(timings.queueWaitMs),
|
|
78
|
+
executionMs: Math.round(timings.executionMs),
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
39
82
|
function computeHost(calls, host) {
|
|
40
83
|
return {
|
|
41
84
|
callOperation(name, args) {
|