@noodleseed/one 0.139.4 → 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 (100) hide show
  1. package/dist/metrics-render.d.ts +8 -0
  2. package/dist/metrics-render.d.ts.map +1 -1
  3. package/dist/metrics-render.js +7 -1
  4. package/dist/metrics-render.js.map +1 -1
  5. package/node_modules/@noodle-borg/agent-kit/dist/generated/example-files.js +3 -3
  6. package/node_modules/@noodle-borg/agent-kit/dist/skill-embedded-assistant-ref.js +9 -1
  7. package/node_modules/@noodle-borg/app-package/dist/index.d.ts +1 -0
  8. package/node_modules/@noodle-borg/app-package/dist/index.js +1 -0
  9. package/node_modules/@noodle-borg/{compiler → app-package}/dist/schema-validation.d.ts +2 -1
  10. package/node_modules/@noodle-borg/app-package/package.json +2 -0
  11. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-app-sandbox.d.ts +13 -0
  12. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-app-sandbox.js +204 -0
  13. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +22 -0
  14. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +10 -7
  15. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript.d.ts +18 -0
  16. package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-transcript.js +6 -0
  17. package/node_modules/@noodle-borg/assistant-gateway/dist/elevation.d.ts +7 -0
  18. package/node_modules/@noodle-borg/assistant-gateway/dist/elevation.js +1 -0
  19. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +2 -0
  20. package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +2 -0
  21. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.d.ts +21 -0
  22. package/node_modules/@noodle-borg/assistant-gateway/dist/public-surface.js +39 -0
  23. package/node_modules/@noodle-borg/assistant-gateway/dist/session-resume.d.ts +7 -0
  24. package/node_modules/@noodle-borg/assistant-gateway/dist/session-resume.js +12 -0
  25. package/node_modules/@noodle-borg/auth/dist/control-plane-token-exchange.d.ts +110 -0
  26. package/node_modules/@noodle-borg/auth/dist/control-plane-token-exchange.js +217 -0
  27. package/node_modules/@noodle-borg/auth/dist/index.d.ts +1 -0
  28. package/node_modules/@noodle-borg/auth/dist/index.js +1 -0
  29. package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +5 -0
  30. package/node_modules/@noodle-borg/compiler/dist/index.d.ts +1 -2
  31. package/node_modules/@noodle-borg/compiler/dist/index.js +1 -2
  32. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +16 -0
  33. package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +19 -2
  34. package/node_modules/@noodle-borg/compiler/package.json +0 -2
  35. package/node_modules/@noodle-borg/compute/dist/code-connector.js +44 -1
  36. package/node_modules/@noodle-borg/compute/dist/quickjs-engine.js +82 -266
  37. package/node_modules/@noodle-borg/compute/dist/sandbox-eval.js +276 -0
  38. package/node_modules/@noodle-borg/compute/dist/worker-pool.js +262 -0
  39. package/node_modules/@noodle-borg/{service/dist/auth/deploy-gate.d.ts → control-plane/dist/deploy-gates.d.ts} +10 -6
  40. package/node_modules/@noodle-borg/control-plane/dist/deploy-gates.js +266 -0
  41. package/node_modules/@noodle-borg/control-plane/dist/portable.d.ts +1 -0
  42. package/node_modules/@noodle-borg/control-plane/dist/portable.js +1 -0
  43. package/node_modules/@noodle-borg/developer-mcp/dist/capabilities.js +11 -1
  44. package/node_modules/@noodle-borg/developer-mcp/dist/index.js +1 -1
  45. package/node_modules/@noodle-borg/module/dist/contract.d.ts +0 -64
  46. package/node_modules/@noodle-borg/module/dist/contract.js +3 -2
  47. package/node_modules/@noodle-borg/module/dist/index.d.ts +1 -0
  48. package/node_modules/@noodle-borg/module/dist/index.js +1 -0
  49. package/node_modules/@noodle-borg/module/dist/intent.d.ts +1 -1
  50. package/node_modules/@noodle-borg/module/dist/request-analytics.d.ts +80 -0
  51. package/node_modules/@noodle-borg/module/dist/request-analytics.js +10 -0
  52. package/node_modules/@noodle-borg/observability/dist/intent-capture.js +4 -0
  53. package/node_modules/@noodle-borg/observability/dist/request-event-buffer.js +4 -0
  54. package/node_modules/@noodle-borg/observability/dist/request-event-metrics.js +15 -0
  55. package/node_modules/@noodle-borg/observability/dist/request-events-postgres.js +19 -3
  56. package/node_modules/@noodle-borg/observability/dist/request-events.js +3 -0
  57. package/node_modules/@noodle-borg/observability/dist/runtime.js +61 -1
  58. package/node_modules/@noodle-borg/protocol/dist/execution-error-mapping.js +13 -1
  59. package/node_modules/@noodle-borg/protocol/dist/handlers/prompts.js +2 -2
  60. package/node_modules/@noodle-borg/protocol/dist/handlers/resources.js +2 -2
  61. package/node_modules/@noodle-borg/protocol/dist/handlers/tools.js +3 -3
  62. package/node_modules/@noodle-borg/protocol/dist/observation.d.ts +18 -0
  63. package/node_modules/@noodle-borg/protocol/dist/observation.js +15 -0
  64. package/node_modules/@noodle-borg/protocol/dist/v2/server.js +5 -5
  65. package/node_modules/@noodle-borg/runtime/dist/connector/failure-details.d.ts +1 -1
  66. package/node_modules/@noodle-borg/runtime/dist/connector/failure-details.js +5 -0
  67. package/node_modules/@noodle-borg/runtime/dist/connector/types.d.ts +15 -1
  68. package/node_modules/@noodle-borg/runtime/dist/connector/types.js +6 -0
  69. package/node_modules/@noodle-borg/runtime/dist/index.d.ts +1 -1
  70. package/node_modules/@noodle-borg/runtime/dist/operation-execution.d.ts +1 -1
  71. package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +35 -6
  72. package/node_modules/@noodle-borg/runtime/dist/result.d.ts +18 -0
  73. package/node_modules/@noodle-borg/service/dist/auth/developer-grant-guard.js +2 -1
  74. package/node_modules/@noodle-borg/service/dist/modules/route-dispatch.js +1 -1
  75. package/node_modules/@noodle-borg/service/dist/registry-types.d.ts +1 -1
  76. package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +15 -3
  77. package/node_modules/@noodle-borg/service/dist/routes/assistant-clients.js +116 -0
  78. package/node_modules/@noodle-borg/service/dist/routes/assistant-dispatch.js +12 -3
  79. package/node_modules/@noodle-borg/service/dist/routes/assistant-elevation.js +1 -0
  80. package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-stream.js +4 -1
  81. package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +8 -1
  82. package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +3 -9
  83. package/node_modules/@noodle-borg/service/dist/routes/assistant-route-http.js +11 -0
  84. package/node_modules/@noodle-borg/service/dist/routes/assistant-sandbox.js +12 -47
  85. package/node_modules/@noodle-borg/service/dist/routes/assistant-session-target.js +34 -8
  86. package/node_modules/@noodle-borg/service/dist/routes/assistant-transcript.js +27 -0
  87. package/node_modules/@noodle-borg/service/dist/routes/assistant.js +28 -130
  88. package/node_modules/@noodle-borg/service/dist/routes/config-values.js +1 -1
  89. package/node_modules/@noodle-borg/service/dist/routes/control-plane.js +8 -2
  90. package/node_modules/@noodle-borg/service/dist/service-resource-cleanup.js +1 -6
  91. package/node_modules/@noodle-borg/service/dist/service.js +15 -1
  92. package/node_modules/@noodle-borg/transport-http/dist/client-identity.js +124 -0
  93. package/node_modules/@noodle-borg/transport-http/dist/identity-authorization.js +8 -8
  94. package/node_modules/@noodle-borg/transport-http/dist/request-capture.js +97 -10
  95. package/node_modules/@noodle-borg/transport-http/dist/serve-request.js +40 -10
  96. package/node_modules/@noodle-borg/wire-contracts/dist/assistant.d.ts +17 -0
  97. package/node_modules/@noodle-borg/wire-contracts/dist/assistant.js +25 -0
  98. package/node_modules/@noodleseed/assistant/README.md +4 -9
  99. package/package.json +1 -1
  100. /package/node_modules/@noodle-borg/{compiler → app-package}/dist/schema-validation.js +0 -0
@@ -1,3 +1,13 @@
1
+ import { boundedClientName, boundedClientVersion, clientFamily, SessionClientMemo, sessionClientKey, } from './client-identity.js';
2
+ /**
3
+ * Best-effort per-process memo from a legacy analytics session id to its `initialize` clientInfo, so
4
+ * legacy tool calls keep client attribution when the client supplies `Mcp-Session-Id` (#1309).
5
+ */
6
+ let sessionClientMemo = new SessionClientMemo();
7
+ /** Test seam: drop all memoized session client identities. */
8
+ export function resetSessionClientMemo() {
9
+ sessionClientMemo = new SessionClientMemo();
10
+ }
1
11
  /** JSON-RPC methods that are protocol discovery/chatter, excluded from usage metrics (ADR 0121). */
2
12
  const DISCOVERY_METHODS = new Set([
3
13
  'server/discover',
@@ -33,6 +43,25 @@ export function emitRequestEvent(input) {
33
43
  const failed = res.statusCode >= 400;
34
44
  const toolName = observed?.toolName ?? (method === 'tools/call' ? rpcTargetName(first, method) : undefined);
35
45
  const errorKind = observed?.errorKind ?? (failed ? `http_${res.statusCode}` : undefined);
46
+ // Client identity: the request's own clientInfo wins; a legacy request that supplies a session id
47
+ // falls back to the identity memoized from that session's `initialize` (#1309). Values are bounded.
48
+ let clientName = boundedClientName(modern?.clientName ?? init?.clientName);
49
+ let clientVersion = boundedClientVersion(modern?.clientVersion ?? init?.clientVersion);
50
+ if (sessionId !== undefined) {
51
+ const key = sessionClientKey(org, tenant?.app ?? auth.app, tenant?.env ?? auth.environment, sessionId);
52
+ if (method === 'initialize' && clientName !== undefined) {
53
+ sessionClientMemo.remember(key, {
54
+ clientName,
55
+ ...(clientVersion === undefined ? {} : { clientVersion }),
56
+ });
57
+ }
58
+ else if (clientName === undefined) {
59
+ const recalled = sessionClientMemo.recall(key);
60
+ clientName = recalled?.clientName;
61
+ clientVersion ??= recalled?.clientVersion;
62
+ }
63
+ }
64
+ const family = clientFamily(clientName, header(req, 'user-agent'));
36
65
  const event = {
37
66
  org,
38
67
  ...(tenant !== undefined ? { app: tenant.app, env: tenant.env } : {}),
@@ -43,16 +72,9 @@ export function emitRequestEvent(input) {
43
72
  requestId,
44
73
  ...(sessionId !== undefined ? { sessionId } : {}),
45
74
  sessionSource: sessionId !== undefined ? 'mcp' : 'none',
46
- ...(modern?.clientName !== undefined
47
- ? { clientName: modern.clientName }
48
- : init?.clientName !== undefined
49
- ? { clientName: init.clientName }
50
- : {}),
51
- ...(modern?.clientVersion !== undefined
52
- ? { clientVersion: modern.clientVersion }
53
- : init?.clientVersion !== undefined
54
- ? { clientVersion: init.clientVersion }
55
- : {}),
75
+ ...(clientName !== undefined ? { clientName } : {}),
76
+ ...(clientVersion !== undefined ? { clientVersion } : {}),
77
+ clientFamily: family,
56
78
  ...(auth.accessMode !== undefined ? { accessMode: auth.accessMode } : {}),
57
79
  subjectKind: subject !== undefined ? 'authenticated' : 'anonymous',
58
80
  method,
@@ -63,9 +85,12 @@ export function emitRequestEvent(input) {
63
85
  outcome: observed?.outcome ?? (failed ? 'mcp_error' : 'ok'),
64
86
  ...(errorKind !== undefined ? { errorKind } : {}),
65
87
  durationMs: Date.now() - startedAt,
88
+ ...(input.timing?.queueMs !== undefined ? { queueMs: input.timing.queueMs } : {}),
89
+ ...(input.timing?.execMs !== undefined ? { execMs: input.timing.execMs } : {}),
66
90
  ...(observed?.outputTokensEst !== undefined
67
91
  ? { outputTokensEst: observed.outputTokensEst }
68
92
  : {}),
93
+ ...connectorDetails(observed),
69
94
  };
70
95
  try {
71
96
  capture(event);
@@ -74,6 +99,68 @@ export function emitRequestEvent(input) {
74
99
  // Analytics is strictly best-effort; a capture bug must never surface to the caller.
75
100
  }
76
101
  }
102
+ /** Project the observation's safe connector attribution into the scalar-only `details` bag. */
103
+ function connectorDetails(observed) {
104
+ const connector = observed?.connector;
105
+ if (connector === undefined)
106
+ return {};
107
+ return {
108
+ details: {
109
+ connectorId: connector.connectorId,
110
+ connectorVersion: connector.connectorVersion,
111
+ connectorOperation: connector.operation,
112
+ ...(connector.category !== undefined ? { connectorCategory: connector.category } : {}),
113
+ ...(connector.statusClass !== undefined
114
+ ? { connectorStatusClass: connector.statusClass }
115
+ : {}),
116
+ ...(connector.attempts !== undefined ? { connectorAttempts: connector.attempts } : {}),
117
+ ...(connector.retryable !== undefined ? { connectorRetryable: connector.retryable } : {}),
118
+ },
119
+ };
120
+ }
121
+ /**
122
+ * Reason a front-door denial (identity auth, admission, tool authorization) was sent before any
123
+ * protocol handler ran. Bounded reason tokens compose the analytics `errorKind` (#1309).
124
+ */
125
+ export function emitDeniedRequestEvent(input) {
126
+ const { req, auth, tenant, target, parsed, startedAt, requestId, errorKind } = input;
127
+ const capture = auth.captureRequestEvent;
128
+ if (capture === undefined)
129
+ return;
130
+ const org = tenant?.org ?? auth.org;
131
+ if (org === undefined)
132
+ return;
133
+ const first = Array.isArray(parsed) ? parsed[0] : parsed;
134
+ const method = first === undefined ? 'unknown' : rpcMethod(first);
135
+ const toolName = method === 'tools/call' ? rpcTargetName(first, method) : undefined;
136
+ const modern = modernRequestInfo(first);
137
+ const sdkProtocolVersion = modern?.protocolVersion ?? header(req, 'mcp-protocol-version');
138
+ const event = {
139
+ org,
140
+ ...(tenant !== undefined ? { app: tenant.app, env: tenant.env } : {}),
141
+ ...(auth.deploymentId !== undefined ? { deploymentId: auth.deploymentId } : {}),
142
+ serverVersion: target.artifact.server.version,
143
+ ...(sdkProtocolVersion !== undefined ? { sdkProtocolVersion } : {}),
144
+ ...(first !== undefined ? { protocolEra: modern === undefined ? 'legacy' : 'modern' } : {}),
145
+ requestId,
146
+ sessionSource: 'none',
147
+ clientFamily: clientFamily(undefined, header(req, 'user-agent')),
148
+ ...(auth.accessMode !== undefined ? { accessMode: auth.accessMode } : {}),
149
+ subjectKind: 'anonymous',
150
+ method,
151
+ kind: 'usage',
152
+ ...(toolName !== undefined ? { toolName } : {}),
153
+ outcome: 'mcp_error',
154
+ errorKind,
155
+ durationMs: Date.now() - startedAt,
156
+ };
157
+ try {
158
+ capture(event);
159
+ }
160
+ catch {
161
+ // Analytics is strictly best-effort.
162
+ }
163
+ }
77
164
  /** Emit validated intent observations into their separate operator stream. */
78
165
  export function emitIntentEvents(input) {
79
166
  const { req, target, auth, tenant, parsed, observations, requestId } = input;
@@ -3,7 +3,7 @@ import { createDualEraMcpHandler, handleStatelessHttp, JSON_RPC, } from '@noodle
3
3
  import { admissionContexts, requestsInvocationContext } from './admission-context.js';
4
4
  import { authorizeIdentityMode, authorizeMixedMode, authorizePublicServiceMode, canonicalResourceUrl, isIdentityMode, } from './identity-authorization.js';
5
5
  import { invocationClientHint } from './invocation-client-hints.js';
6
- import { emitIntentEvents, emitRequestEvent, header } from './request-capture.js';
6
+ import { emitDeniedRequestEvent, emitIntentEvents, emitRequestEvent, header, } from './request-capture.js';
7
7
  import { admissionRpcError, rpcError, sendJson } from './responses.js';
8
8
  import { preflightServicePrincipalToolCall, sendServicePrincipalToolDenial, } from './service-principal-authorization.js';
9
9
  import { preflightToolAuthorization, sendToolAuthorizationDenial } from './tool-authorization.js';
@@ -18,6 +18,22 @@ import { widgetDomainProjectionForRequest } from './widget-host.js';
18
18
  export async function serveRequest(req, res, target, auth, routeId, maxBody, allowed, tenant) {
19
19
  const startedAt = Date.now();
20
20
  const requestId = randomUUID();
21
+ // Every structured log line for this request carries the analytics correlation id (#1309).
22
+ // Field merge, not `child()`: injected loggers are only guaranteed the four level methods.
23
+ const logger = {
24
+ info: (event, fields) => auth.logger.info(event, { requestId, ...fields }),
25
+ warn: (event, fields) => auth.logger.warn(event, { requestId, ...fields }),
26
+ };
27
+ const denied = (errorKind, parsedBody) => emitDeniedRequestEvent({
28
+ req,
29
+ auth,
30
+ tenant,
31
+ target,
32
+ ...(parsedBody === undefined ? {} : { parsed: parsedBody }),
33
+ startedAt,
34
+ requestId,
35
+ errorKind,
36
+ });
21
37
  const origin = header(req, 'origin');
22
38
  if (origin !== undefined && !originAllowed(origin, allowed)) {
23
39
  return sendJson(res, 403, rpcError(JSON_RPC.INVALID_REQUEST, 'origin not allowed'));
@@ -37,7 +53,7 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
37
53
  if (isIdentityMode(auth.accessMode)) {
38
54
  const authResult = await authorizeIdentityMode(req, res, auth);
39
55
  if (!authResult.allow)
40
- return;
56
+ return denied(`auth_denied.${authResult.reason}`);
41
57
  if (authResult.caller !== undefined) {
42
58
  protocolContext = {
43
59
  ...protocolContext,
@@ -59,7 +75,7 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
59
75
  else if (auth.accessMode === 'mixed') {
60
76
  const authResult = await authorizeMixedMode(req, res, auth);
61
77
  if (!authResult.allow)
62
- return;
78
+ return denied(`auth_denied.${authResult.reason}`);
63
79
  if (authResult.caller !== undefined) {
64
80
  protocolContext = {
65
81
  ...protocolContext,
@@ -117,7 +133,7 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
117
133
  const decision = auth.admissionGate
118
134
  ? await auth.admissionGate(context)
119
135
  : { allow: true };
120
- auth.logger.info('mcp.admission', {
136
+ logger.info('mcp.admission', {
121
137
  routeId: context.routeId,
122
138
  method: context.method,
123
139
  category: context.category,
@@ -134,12 +150,13 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
134
150
  if (decision.retryAfterSeconds !== undefined) {
135
151
  res.setHeader('Retry-After', String(decision.retryAfterSeconds));
136
152
  }
137
- return sendJson(res, decision.status ?? 403, admissionRpcError(context, decision));
153
+ sendJson(res, decision.status ?? 403, admissionRpcError(context, decision));
154
+ return denied(`admission_denied.${boundedReason(decision.reason)}`, parsed);
138
155
  }
139
156
  }
140
157
  const authorization = preflightToolAuthorization(parsed, target, protocolContext.caller);
141
158
  for (const observation of authorization.observations) {
142
- auth.logger.info('mcp.tool_authorization', {
159
+ logger.info('mcp.tool_authorization', {
143
160
  toolName: observation.toolName,
144
161
  decision: observation.decision,
145
162
  reason: observation.reason,
@@ -149,12 +166,12 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
149
166
  }
150
167
  if (authorization.denial !== undefined) {
151
168
  sendToolAuthorizationDenial(req, res, auth, authorization.denial);
152
- return;
169
+ return denied(`auth_denied.${authorization.denial.decision.reason}`, parsed);
153
170
  }
154
171
  const serviceAuthorization = preflightServicePrincipalToolCall(parsed, target, protocolContext.caller);
155
172
  const serviceSubject = protocolContext.caller?.identityKind === 'service' ? protocolContext.caller.subject : undefined;
156
173
  for (const observation of serviceAuthorization.observations) {
157
- auth.logger.info('mcp.service_principal_tool_authorization', {
174
+ logger.info('mcp.service_principal_tool_authorization', {
158
175
  subject: serviceSubject,
159
176
  toolName: observation.toolName,
160
177
  decision: observation.decision,
@@ -176,7 +193,7 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
176
193
  }
177
194
  catch {
178
195
  try {
179
- auth.logger.warn('mcp.tool_authorization.observer_failed', {
196
+ logger.warn('mcp.tool_authorization.observer_failed', {
180
197
  toolName: observation.toolName,
181
198
  decision: observation.decision,
182
199
  });
@@ -188,7 +205,7 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
188
205
  }
189
206
  if (serviceAuthorization.denial !== undefined) {
190
207
  sendServicePrincipalToolDenial(res, serviceAuthorization.denial);
191
- return;
208
+ return denied(`auth_denied.${serviceAuthorization.denial.reason}`, parsed);
192
209
  }
193
210
  if (auth.resolveInvocationContext !== undefined && requestsInvocationContext(parsed)) {
194
211
  const clientHint = invocationClientHint(parsed);
@@ -220,6 +237,7 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
220
237
  },
221
238
  };
222
239
  }
240
+ const dispatchedAt = Date.now();
223
241
  if (auth.protocolMode === 'legacy-only') {
224
242
  await handleStatelessHttp(target, req, res, parsed, protocolContext);
225
243
  }
@@ -251,9 +269,21 @@ export async function serveRequest(req, res, target, auth, routeId, maxBody, all
251
269
  observed,
252
270
  startedAt,
253
271
  requestId,
272
+ timing: { queueMs: dispatchedAt - startedAt, execMs: Date.now() - dispatchedAt },
254
273
  });
255
274
  emitIntentEvents({ req, target, auth, tenant, parsed, observations, requestId });
256
275
  }
276
+ /** Bound a gate-supplied denial reason into a low-cardinality token for the analytics errorKind. */
277
+ function boundedReason(reason) {
278
+ if (reason === undefined)
279
+ return 'denied';
280
+ const token = reason
281
+ .toLowerCase()
282
+ .replace(/[^a-z0-9_-]+/g, '_')
283
+ .replace(/^_+|_+$/g, '')
284
+ .slice(0, 32);
285
+ return token.length === 0 ? 'denied' : token;
286
+ }
257
287
  export function originAllowed(origin, allowed) {
258
288
  if (allowed === undefined)
259
289
  return true;
@@ -20,6 +20,7 @@ export declare const assistantSessionResponseSchema: z.ZodObject<{
20
20
  interactions: z.ZodOptional<z.ZodURL>;
21
21
  apps: z.ZodOptional<z.ZodURL>;
22
22
  sandbox: z.ZodOptional<z.ZodURL>;
23
+ transcript: z.ZodOptional<z.ZodURL>;
23
24
  }, z.core.$strip>;
24
25
  configuration: z.ZodOptional<z.ZodObject<{
25
26
  branding: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
@@ -30,6 +31,22 @@ export declare const assistantSessionResponseSchema: z.ZodObject<{
30
31
  }, z.core.$strip>>;
31
32
  }, z.core.$strip>;
32
33
  export type AssistantSessionResponse = z.infer<typeof assistantSessionResponseSchema>;
34
+ /**
35
+ * The bounded visible transcript (ADR 0141/0201 amendments 2026-08-26): only rows that actually
36
+ * appeared in the panel — never tool internals, tickets, or model-facing scaffolding. Bounded by the
37
+ * session history window (40 messages); a read spends no turn budget. Strict, because the service
38
+ * parses its own response before sending, like the session exchange.
39
+ */
40
+ export declare const assistantTranscriptResponseSchema: z.ZodObject<{
41
+ entries: z.ZodArray<z.ZodObject<{
42
+ role: z.ZodEnum<{
43
+ assistant: "assistant";
44
+ user: "user";
45
+ }>;
46
+ text: z.ZodString;
47
+ }, z.core.$strict>>;
48
+ }, z.core.$strict>;
49
+ export type AssistantTranscriptResponse = z.infer<typeof assistantTranscriptResponseSchema>;
33
50
  /** MCP-Apps-shaped, author-selected renderer summary included on one assistant turn. */
34
51
  export declare const assistantModelContextUpdateSchema: z.ZodObject<{
35
52
  content: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -42,6 +42,13 @@ export const assistantSessionResponseSchema = z.object({
42
42
  * published client falls back to the srcdoc proxy.
43
43
  */
44
44
  sandbox: z.url().optional(),
45
+ /**
46
+ * Additive bounded visible-transcript endpoint (ADR 0141/0201 amendments 2026-08-26): a
47
+ * session-token-authenticated POST returning what the panel may repaint after a full-page
48
+ * navigation — the sign-in redirect above all. Absent on older services; the widget then
49
+ * starts visually fresh exactly as before.
50
+ */
51
+ transcript: z.url().optional(),
45
52
  }),
46
53
  configuration: assistantSessionConfigurationSchema.optional(),
47
54
  /**
@@ -51,6 +58,24 @@ export const assistantSessionResponseSchema = z.object({
51
58
  */
52
59
  resume: z.object({ tool: z.string().min(1) }).optional(),
53
60
  });
61
+ /**
62
+ * The bounded visible transcript (ADR 0141/0201 amendments 2026-08-26): only rows that actually
63
+ * appeared in the panel — never tool internals, tickets, or model-facing scaffolding. Bounded by the
64
+ * session history window (40 messages); a read spends no turn budget. Strict, because the service
65
+ * parses its own response before sending, like the session exchange.
66
+ */
67
+ export const assistantTranscriptResponseSchema = z
68
+ .object({
69
+ entries: z
70
+ .array(z
71
+ .object({
72
+ role: z.enum(['user', 'assistant']),
73
+ text: z.string(),
74
+ })
75
+ .strict())
76
+ .max(40),
77
+ })
78
+ .strict();
54
79
  const assistantModelContentPartSchema = z
55
80
  .object({ type: z.literal('text'), text: z.string() })
56
81
  .strict();
@@ -35,8 +35,8 @@ troubleshooting) lives at <https://docs.noodleseed.dev/guides/embedded-assistant
35
35
  the server itself is `@noodleseed/one` (`npm install -g @noodleseed/one`).
36
36
 
37
37
  If your page sends a `Content-Security-Policy`, allow the Noodle service origin in `connect-src` (turns and
38
- event streams) **and** `frame-src` (app widgets render inside a hosted sandbox document served from the
39
- service origin, `endpoints.sandbox`, so your `script-src` can stay strict). Details are in the guide's
38
+ event streams) and the exact session-advertised `endpoints.sandbox` origin in `frame-src` (it may be a
39
+ dedicated sandbox origin, so your `script-src` can stay strict). Details are in the guide's
40
40
  "Content-Security-Policy on your page" section.
41
41
 
42
42
  ## Quick start
@@ -410,13 +410,8 @@ import "@noodleseed/assistant/app-view";
410
410
  </template>
411
411
  ```
412
412
 
413
- Configure Vue's `isCustomElement` for `noodle-app-view`. Angular uses the same element with `[client]`,
414
- `[view]`, and `[theme]` property bindings. Do not serialize `client` or `view` into attributes.
415
-
416
- `<noodle-app-view>` is the canonical host; `NoodleAppView` delegates to it. Both use the
417
- service-advertised sandbox URL, route App calls through the supplied client, publish theme changes without
418
- replacing the iframe, and request standard teardown on semantic replacement, disconnect, or an App teardown
419
- request.
413
+ Framework setup, lifecycle ownership, sandbox/CSP behavior, and staged diagnostics are owned by the
414
+ [Bring your own UI guide](https://docs.noodleseed.dev/docs/guides/bring-your-own-ui).
420
415
 
421
416
  The App document owns its action intent: it calls standard `tools/call` after connecting and never relies on
422
417
  native form navigation. If that call needs input or confirmation, the same `client` publishes the normal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noodleseed/one",
3
- "version": "0.139.4",
3
+ "version": "0.141.0",
4
4
  "private": false,
5
5
  "description": "Noodle CLI by Noodle Seed — author, run, and deploy declarative MCP servers. Embedding the assistant in your own web app is @noodleseed/assistant.",
6
6
  "license": "Apache-2.0",