@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.
Files changed (71) hide show
  1. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
  2. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +9 -1
  3. package/node_modules/@noodle-borg/app-package/dist/index.d.ts +1 -0
  4. package/node_modules/@noodle-borg/app-package/dist/index.js +1 -0
  5. package/node_modules/@noodle-borg/{compiler → app-package}/dist/schema-validation.d.ts +2 -1
  6. package/node_modules/@noodle-borg/app-package/package.json +2 -0
  7. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-app-sandbox.d.ts +13 -0
  8. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-app-sandbox.js +204 -0
  9. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +22 -0
  10. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +10 -7
  11. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript.d.ts +18 -0
  12. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript.js +6 -0
  13. package/node_modules/@noodle-borg/assistant-gateway/dist/elevation.d.ts +7 -0
  14. package/node_modules/@noodle-borg/assistant-gateway/dist/elevation.js +1 -0
  15. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +2 -0
  16. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +2 -0
  17. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.d.ts +21 -0
  18. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.js +39 -0
  19. package/node_modules/@noodle-borg/assistant-gateway/dist/session-resume.d.ts +7 -0
  20. package/node_modules/@noodle-borg/assistant-gateway/dist/session-resume.js +12 -0
  21. package/node_modules/@noodle-borg/auth/dist/control-plane-token-exchange.d.ts +110 -0
  22. package/node_modules/@noodle-borg/auth/dist/control-plane-token-exchange.js +217 -0
  23. package/node_modules/@noodle-borg/auth/dist/index.d.ts +1 -0
  24. package/node_modules/@noodle-borg/auth/dist/index.js +1 -0
  25. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +5 -0
  26. package/node_modules/@noodle-borg/compiler/dist/index.d.ts +1 -2
  27. package/node_modules/@noodle-borg/compiler/dist/index.js +1 -2
  28. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +16 -0
  29. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +19 -2
  30. package/node_modules/@noodle-borg/compiler/package.json +0 -2
  31. package/node_modules/@noodle-borg/compute/dist/code-connector.js +44 -1
  32. package/node_modules/@noodle-borg/compute/dist/quickjs-engine.js +82 -266
  33. package/node_modules/@noodle-borg/compute/dist/sandbox-eval.js +276 -0
  34. package/node_modules/@noodle-borg/compute/dist/worker-pool.js +262 -0
  35. package/node_modules/@noodle-borg/{service/dist/auth/deploy-gate.d.ts → control-plane/dist/deploy-gates.d.ts} +10 -6
  36. package/node_modules/@noodle-borg/control-plane/dist/deploy-gates.js +266 -0
  37. package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +1 -0
  38. package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
  39. package/node_modules/@noodle-borg/developer-mcp/dist/capabilities.js +11 -1
  40. package/node_modules/@noodle-borg/developer-mcp/dist/index.js +1 -1
  41. package/node_modules/@noodle-borg/protocol/dist/execution-error-mapping.js +13 -1
  42. package/node_modules/@noodle-borg/protocol/dist/observation.d.ts +2 -1
  43. package/node_modules/@noodle-borg/protocol/dist/observation.js +2 -1
  44. package/node_modules/@noodle-borg/runtime/dist/connector/failure-details.d.ts +1 -1
  45. package/node_modules/@noodle-borg/runtime/dist/connector/failure-details.js +5 -0
  46. package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +15 -1
  47. package/node_modules/@noodle-borg/runtime/dist/connector/types.js +6 -0
  48. package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +1 -0
  49. package/node_modules/@noodle-borg/service/dist/auth/developer-grant-guard.js +2 -1
  50. package/node_modules/@noodle-borg/service/dist/modules/route-dispatch.js +1 -1
  51. package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +1 -1
  52. package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +15 -3
  53. package/node_modules/@noodle-borg/service/dist/routes/assistant-clients.js +116 -0
  54. package/node_modules/@noodle-borg/service/dist/routes/assistant-dispatch.js +12 -3
  55. package/node_modules/@noodle-borg/service/dist/routes/assistant-elevation.js +1 -0
  56. package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-stream.js +4 -1
  57. package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +8 -1
  58. package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +3 -9
  59. package/node_modules/@noodle-borg/service/dist/routes/assistant-route-http.js +11 -0
  60. package/node_modules/@noodle-borg/service/dist/routes/assistant-sandbox.js +12 -47
  61. package/node_modules/@noodle-borg/service/dist/routes/assistant-session-target.js +34 -8
  62. package/node_modules/@noodle-borg/service/dist/routes/assistant-transcript.js +27 -0
  63. package/node_modules/@noodle-borg/service/dist/routes/assistant.js +28 -130
  64. package/node_modules/@noodle-borg/service/dist/routes/config-values.js +1 -1
  65. package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +8 -2
  66. package/node_modules/@noodle-borg/service/dist/service.js +15 -1
  67. package/node_modules/@noodle-borg/wire-contracts/dist/assistant.d.ts +17 -0
  68. package/node_modules/@noodle-borg/wire-contracts/dist/assistant.js +25 -0
  69. package/node_modules/@noodleseed/assistant/README.md +4 -9
  70. package/package.json +1 -1
  71. /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.object({
676
+ export const manifestV2Schema = z
677
+ .object({
673
678
  manifestVersion: z.literal('2'),
674
679
  server: serverV2Schema,
675
- tools: z.array(toolV2Schema).min(1),
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,
@@ -28,8 +28,6 @@
28
28
  "@noodle-borg/uri-template": "0.0.0",
29
29
  "@noodle-borg/knowledge": "0.0.0",
30
30
  "@noodle-borg/capabilities": "0.0.0",
31
- "ajv": "^8.20.0",
32
- "ajv-formats": "^3.0.1",
33
31
  "react": "^19.2.7",
34
32
  "react-dom": "^19.2.7",
35
33
  "tailwindcss": "^4.3.1",
@@ -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
- return await instance.invoke(call.args, op.limits ?? DEFAULT_LIMITS, op.calls ? computeHost(op.calls, call.host) : undefined);
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) {