@noodleseed/one 0.140.0 → 0.141.1
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/dist/bin.js +0 -0
- 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/agent-kit/package.json +1 -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/assistant-gateway/package.json +1 -1
- 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/module/dist/platform-human-identity.d.ts +6 -0
- package/node_modules/@noodle-borg/module/dist/platform-human-identity.js +14 -0
- 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/index.d.ts +1 -1
- package/node_modules/@noodle-borg/runtime/dist/index.js +1 -1
- package/node_modules/@noodle-borg/runtime/dist/operation-execution.js +1 -0
- package/node_modules/@noodle-borg/runtime/dist/state-handles.d.ts +15 -0
- package/node_modules/@noodle-borg/runtime/dist/state-handles.js +54 -44
- 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/service/package.json +1 -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/node_modules/@noodleseed/assistant/package.json +1 -1
- package/package.json +2 -2
- /package/node_modules/@noodle-borg/{compiler → app-package}/dist/schema-validation.js +0 -0
|
@@ -5,7 +5,7 @@ import { assistantSessionUsageRequestEvent, captureAssistantUsage, } from '@nood
|
|
|
5
5
|
import { assistantSessionResponseSchema } from '@noodle-borg/wire-contracts';
|
|
6
6
|
import { readJsonBody, sendJson } from '../http-util.js';
|
|
7
7
|
import { assistantBrowserConfiguration } from './assistant-configuration.js';
|
|
8
|
-
import { applyBrowserCors, now } from './assistant-route-http.js';
|
|
8
|
+
import { applyBrowserCors, assistantSessionEndpoints, now } from './assistant-route-http.js';
|
|
9
9
|
/**
|
|
10
10
|
* The transport half of anonymous session minting. Every decision — which refusal, in which order, and
|
|
11
11
|
* whether the surface may serve at all — lives in `@noodle-borg/assistant-gateway`; this file parses a
|
|
@@ -76,6 +76,7 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
76
76
|
: 'operator',
|
|
77
77
|
origin: input.origin,
|
|
78
78
|
publicEmbedId: input.embed.embedId,
|
|
79
|
+
boundSurface: 'public',
|
|
79
80
|
caller: { subject: input.subject, identityKind: 'anonymous' },
|
|
80
81
|
...(configuration ? { configuration } : {}),
|
|
81
82
|
createdAt: current.toISOString(),
|
|
@@ -94,17 +95,10 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
94
95
|
}
|
|
95
96
|
// Only here, with the origin proven against the live surface, may the page read the response.
|
|
96
97
|
applyBrowserCors(req, res, origin);
|
|
97
|
-
const base = deps.serviceBase(req);
|
|
98
98
|
const sessionBody = {
|
|
99
99
|
token: result.token,
|
|
100
100
|
expiresAt: result.expiresAt,
|
|
101
|
-
endpoints:
|
|
102
|
-
turns: `${base}/v1/assistant/turns`,
|
|
103
|
-
toolConfirmations: `${base}/v1/assistant/tool-confirmations`,
|
|
104
|
-
interactions: `${base}/v1/assistant/interactions`,
|
|
105
|
-
apps: `${base}/v1/assistant/apps`,
|
|
106
|
-
sandbox: `${base}/v1/assistant/sandbox`,
|
|
107
|
-
},
|
|
101
|
+
endpoints: assistantSessionEndpoints(deps.serviceBase(req), deps.sandboxBase?.(req)),
|
|
108
102
|
...(configuration ? { configuration } : {}),
|
|
109
103
|
};
|
|
110
104
|
// Parse rather than trust: every published @noodleseed/assistant widget consumes this shape, and the
|
|
@@ -47,4 +47,15 @@ export function applyBrowserCors(req, res, expectedOrigin) {
|
|
|
47
47
|
export function now(deps) {
|
|
48
48
|
return deps.clock?.() ?? new Date();
|
|
49
49
|
}
|
|
50
|
+
/** The one endpoint map every session mint returns; three mints, one source of truth. */
|
|
51
|
+
export function assistantSessionEndpoints(base, sandboxBase = base) {
|
|
52
|
+
return {
|
|
53
|
+
turns: `${base}/v1/assistant/turns`,
|
|
54
|
+
toolConfirmations: `${base}/v1/assistant/tool-confirmations`,
|
|
55
|
+
interactions: `${base}/v1/assistant/interactions`,
|
|
56
|
+
apps: `${base}/v1/assistant/apps`,
|
|
57
|
+
sandbox: `${sandboxBase}/v1/assistant/sandbox`,
|
|
58
|
+
transcript: `${base}/v1/assistant/transcript`,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
50
61
|
//# sourceMappingURL=assistant-route-http.js.map
|
|
@@ -1,58 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* customer page's CSP, so a strict `script-src` silently blanks every widget. Serving this relay
|
|
5
|
-
* document from the service origin gives it — and the inner `srcdoc` widget document that inherits
|
|
6
|
-
* from it — a CSP the platform controls. The body is pinned byte-for-byte to
|
|
7
|
-
* `contract/v1/assistant-sandbox-document.html` and to the published client's srcdoc fallback; any
|
|
8
|
-
* relay-protocol change is a coordinated contract event, never a local edit.
|
|
9
|
-
*/
|
|
10
|
-
export const ASSISTANT_SANDBOX_DOCUMENT = `<!doctype html><meta charset="utf-8"><style>html,body,iframe{border:0;margin:0;width:100%;height:100%;overflow:hidden}body{background:transparent}</style><script>
|
|
11
|
-
let inner;
|
|
12
|
-
const ready={jsonrpc:'2.0',method:'ui/notifications/sandbox-proxy-ready',params:{}};
|
|
13
|
-
// Re-announce until the host acknowledges with the resource: when this document is hosted
|
|
14
|
-
// cross-origin, the first announcement can arrive before the host's load handler is listening.
|
|
15
|
-
const announce=setInterval(()=>parent.postMessage(ready,'*'),120);
|
|
16
|
-
setTimeout(()=>clearInterval(announce),15000);
|
|
17
|
-
addEventListener('message',(event)=>{
|
|
18
|
-
if(event.source===parent){
|
|
19
|
-
const message=event.data;
|
|
20
|
-
if(message?.method==='ui/notifications/sandbox-resource-ready'){
|
|
21
|
-
clearInterval(announce);
|
|
22
|
-
if(inner)return;
|
|
23
|
-
inner=document.createElement('iframe');
|
|
24
|
-
inner.setAttribute('sandbox',message.params?.sandbox||'allow-scripts');
|
|
25
|
-
inner.setAttribute('referrerpolicy','no-referrer');
|
|
26
|
-
inner.srcdoc=String(message.params?.html||'');
|
|
27
|
-
document.body.replaceChildren(inner);
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
inner?.contentWindow?.postMessage(message,'*');
|
|
31
|
-
} else if(inner && event.source===inner.contentWindow) {
|
|
32
|
-
parent.postMessage(event.data,'*');
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
parent.postMessage(ready,'*');
|
|
36
|
-
</script>`;
|
|
37
|
-
/**
|
|
38
|
-
* The sandbox document's own CSP. `sandbox allow-scripts` forces an opaque origin server-side even
|
|
39
|
-
* if a future embedder frames the URL without a sandbox attribute (the document can never read
|
|
40
|
-
* service cookies or storage); inline script/style must be allowed because the relay script and the
|
|
41
|
-
* inner widget document's injected bridge are inline by design; everything else stays closed —
|
|
42
|
-
* widget data flows only over the postMessage bridge, never direct fetch.
|
|
43
|
-
*/
|
|
44
|
-
const SANDBOX_CSP = "sandbox allow-scripts; default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src https: data: blob:; font-src https: data:; frame-src about:; form-action 'none'; base-uri 'none'; frame-ancestors *";
|
|
45
|
-
/** Serve the static, secret-free sandbox document. GET-only; cacheable despite the no-store baseline. */
|
|
46
|
-
export function handleAssistantSandbox(req, res) {
|
|
1
|
+
import { ASSISTANT_SANDBOX_DOCUMENT, resolveAssistantSandboxResponsePolicy, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
+
/** Serve the static, secret-free sandbox relay and apply its validated CSP before writing the body. */
|
|
3
|
+
export function handleAssistantSandbox(req, res, options = {}) {
|
|
47
4
|
if (req.method !== 'GET') {
|
|
48
5
|
res.statusCode = 405;
|
|
49
6
|
res.setHeader('Allow', 'GET');
|
|
50
7
|
res.end();
|
|
51
8
|
return;
|
|
52
9
|
}
|
|
10
|
+
const policy = resolveAssistantSandboxResponsePolicy(req.url ?? '/', options.isolatedOrigin === true);
|
|
11
|
+
res.setHeader('Content-Security-Policy', policy.contentSecurityPolicy);
|
|
12
|
+
if (!policy.ok) {
|
|
13
|
+
res.statusCode = 400;
|
|
14
|
+
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
15
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
16
|
+
res.end('Invalid sandbox policy');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
53
19
|
res.statusCode = 200;
|
|
54
20
|
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
55
|
-
res.setHeader('Content-Security-Policy', SANDBOX_CSP);
|
|
56
21
|
res.setHeader('Cache-Control', 'public, max-age=300');
|
|
57
22
|
res.end(ASSISTANT_SANDBOX_DOCUMENT);
|
|
58
23
|
}
|
|
@@ -1,23 +1,49 @@
|
|
|
1
|
-
import { projectArtifactForSurface, publicSurfaceOf, } from '@noodle-borg/assistant-gateway/portable';
|
|
1
|
+
import { authenticatedSurfaceOf, projectArtifactForSurface, publicSurfaceOf, surfaceBindingForOrigin, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
2
|
export async function sessionScopedTarget(registry, session) {
|
|
3
3
|
const target = await registry.get(session.deploymentId);
|
|
4
4
|
if (!target)
|
|
5
5
|
return undefined;
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (
|
|
6
|
+
const assistant = target.served.artifact.server.assistant;
|
|
7
|
+
const bound = session.boundSurface ?? deriveLegacyBinding(assistant, session);
|
|
8
|
+
if (bound === 'pre-surfaces')
|
|
9
9
|
return target;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// Fail closed: an origin no surface owns must never widen to the whole server.
|
|
11
|
+
if (bound === 'unowned')
|
|
12
|
+
return undefined;
|
|
13
|
+
// Fail closed on a vanished surface. A rollback to an artifact without the bound surface must end the
|
|
14
|
+
// session's reach, never hand it the unprojected server.
|
|
15
|
+
if (bound === 'authenticated') {
|
|
16
|
+
const surface = authenticatedSurfaceOf(assistant);
|
|
17
|
+
if (surface === undefined)
|
|
18
|
+
return undefined;
|
|
19
|
+
// An omitted allowlist on an authenticated surface is the authored whole-server intent, so the
|
|
20
|
+
// exact binding still holds (instructions, budgets, attribution) without narrowing capabilities.
|
|
21
|
+
if (surface.capabilities === undefined)
|
|
22
|
+
return target;
|
|
23
|
+
return projected(target, surface.capabilities);
|
|
24
|
+
}
|
|
25
|
+
const surface = publicSurfaceOf(assistant);
|
|
13
26
|
if (surface === undefined)
|
|
14
27
|
return undefined;
|
|
28
|
+
return projected(target, surface.capabilities);
|
|
29
|
+
}
|
|
30
|
+
function projected(target, capabilities) {
|
|
15
31
|
return {
|
|
16
32
|
...target,
|
|
17
33
|
served: {
|
|
18
34
|
...target.served,
|
|
19
|
-
artifact: projectArtifactForSurface(target.served.artifact,
|
|
35
|
+
artifact: projectArtifactForSurface(target.served.artifact, capabilities),
|
|
20
36
|
},
|
|
21
37
|
};
|
|
22
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A record minted before `boundSurface` existed: the embed id marks the public surface; otherwise the
|
|
41
|
+
* session's origin selects the owning surface on the pinned deployment. `pre-surfaces` means the whole
|
|
42
|
+
* server on that released artifact shape; `unowned` fails closed above.
|
|
43
|
+
*/
|
|
44
|
+
function deriveLegacyBinding(assistant, session) {
|
|
45
|
+
if (session.publicEmbedId !== undefined)
|
|
46
|
+
return 'public';
|
|
47
|
+
return surfaceBindingForOrigin(assistant, session.origin).kind;
|
|
48
|
+
}
|
|
23
49
|
//# sourceMappingURL=assistant-session-target.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { visibleTranscript } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
+
import { assistantTranscriptResponseSchema } from '@noodle-borg/wire-contracts';
|
|
3
|
+
import { sendJson } from '../http-util.js';
|
|
4
|
+
import { applyBrowserCors, authenticateSession } from './assistant-route-http.js';
|
|
5
|
+
/**
|
|
6
|
+
* The bounded visible-transcript read (ADR 0141/0201 amendments 2026-08-26), in its own module for
|
|
7
|
+
* the same reason `assistant-elevation.ts` is: `assistant.ts` is at its size limit, and this is a
|
|
8
|
+
* different responsibility from running a turn.
|
|
9
|
+
*
|
|
10
|
+
* POST, deliberately: every other session route is a POST, so the existing preflight's
|
|
11
|
+
* `POST, OPTIONS` shape covers it without widening allow-methods. Auth is exactly the shared
|
|
12
|
+
* session gate — current token, pinned origin — so a stale pre-elevation token cannot read, by
|
|
13
|
+
* construction. A read spends no turn budget: admission binds metered work (ADR 0201 decision 8),
|
|
14
|
+
* and this is a projection over state that already exists and already expires with the session.
|
|
15
|
+
*/
|
|
16
|
+
export async function handleAssistantTranscript(req, res, deps) {
|
|
17
|
+
const session = await authenticateSession(req, res, deps);
|
|
18
|
+
if (!session)
|
|
19
|
+
return;
|
|
20
|
+
applyBrowserCors(req, res, session.origin);
|
|
21
|
+
const body = { entries: visibleTranscript(session.history) };
|
|
22
|
+
// Parse before sending, like the session exchange: a contract break fails loudly here rather than
|
|
23
|
+
// stranding deployed widgets (ADR 0151).
|
|
24
|
+
assistantTranscriptResponseSchema.parse(body);
|
|
25
|
+
return sendJson(res, 200, body);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=assistant-transcript.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { clientAddressBucket, } from '@noodle-borg/admission-limits/portable';
|
|
2
|
-
import { ASSISTANT_SESSION_IDLE_MS, dispatchAssistantTool, parseAssistantContextPreferences, parseAssistantCustomerRouting, resumeTurnMessage, shouldAutoResume, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
+
import { ASSISTANT_SESSION_IDLE_MS, dispatchAssistantTool, parseAssistantContextPreferences, parseAssistantCustomerRouting, resumeTurnMessage, resumeUnavailableMessage, shouldAutoResume, surfaceBindingForOrigin, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
3
|
import { canonicalizeAuthorizationClaimValues } from '@noodle-borg/auth';
|
|
4
4
|
import { validateJsonSchemaWithDefaults } from '@noodle-borg/compiler';
|
|
5
5
|
import { tenantMcpUrl } from '@noodle-borg/module';
|
|
@@ -14,7 +14,7 @@ import { assistantBrowserConfiguration } from './assistant-configuration.js';
|
|
|
14
14
|
import { elevateAssistantSession } from './assistant-elevation.js';
|
|
15
15
|
import { executeAssistantKnowledgeSearch, findAssistantKnowledgeComponent, resolveAssistantKnowledge, } from './assistant-knowledge.js';
|
|
16
16
|
import { refusePublicTurn } from './assistant-public-turn.js';
|
|
17
|
-
import { applyBrowserCors, authenticateSession, handleAssistantPreflight, now, } from './assistant-route-http.js';
|
|
17
|
+
import { applyBrowserCors, assistantSessionEndpoints, authenticateSession, handleAssistantPreflight, now, } from './assistant-route-http.js';
|
|
18
18
|
import { sessionScopedTarget } from './assistant-session-target.js';
|
|
19
19
|
import { authorizeControlPlane } from './control-plane.js';
|
|
20
20
|
export { applyBrowserCors, authenticateSession, handleAssistantPreflight, now };
|
|
@@ -42,108 +42,6 @@ export async function handleConsoleApprovalNonce(req, res, deps) {
|
|
|
42
42
|
const consumed = await deps.store.consumeConsoleApprovalNonce(nonce, identity.subject, expiry, current);
|
|
43
43
|
return sendJson(res, consumed ? 201 : 409, consumed ? { ok: true } : { ok: false, error: 'already_used_or_expired' });
|
|
44
44
|
}
|
|
45
|
-
export async function handleAssistantClients(req, res, tenant, id, action, deps) {
|
|
46
|
-
const identity = await authorizeControlPlane(req, res, deps.gate, { requireIdentity: false });
|
|
47
|
-
if (identity === false)
|
|
48
|
-
return;
|
|
49
|
-
if (identity && !identity.superAdmin) {
|
|
50
|
-
const member = await deps.controlPlane.isOrgMember({
|
|
51
|
-
org: tenant.org,
|
|
52
|
-
subject: identity.subject,
|
|
53
|
-
});
|
|
54
|
-
if (!member)
|
|
55
|
-
return sendForbidden(res, 'forbidden');
|
|
56
|
-
}
|
|
57
|
-
if (action === 'collection' && req.method === 'GET') {
|
|
58
|
-
const clients = await deps.store.listClients(tenant);
|
|
59
|
-
return sendJson(res, 200, {
|
|
60
|
-
ok: true,
|
|
61
|
-
clients: clients.map(publicClient),
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
if (action === 'collection' && req.method === 'POST') {
|
|
65
|
-
const body = await readJsonBody(req, deps.maxBody);
|
|
66
|
-
if (!body.ok)
|
|
67
|
-
return sendJson(res, body.status, { error: body.error });
|
|
68
|
-
const name = body.value.name;
|
|
69
|
-
if (typeof name !== 'string' || name.trim().length < 1 || name.length > 80) {
|
|
70
|
-
return sendJson(res, 400, { error: '"name" must be a non-empty string' });
|
|
71
|
-
}
|
|
72
|
-
const target = await deps.registry.getActiveByTenant(tenant);
|
|
73
|
-
const assistant = target?.served.artifact.server.assistant;
|
|
74
|
-
if (!target?.deploymentId || !assistant)
|
|
75
|
-
return sendJson(res, 409, { error: 'deployment has no embedded assistant' });
|
|
76
|
-
const created = await deps.store.createClient({
|
|
77
|
-
name: name.trim(),
|
|
78
|
-
tenant,
|
|
79
|
-
deploymentId: target.deploymentId,
|
|
80
|
-
allowedOrigins: assistant.allowedOrigins,
|
|
81
|
-
now: now(deps),
|
|
82
|
-
});
|
|
83
|
-
await deps.audit.emit({
|
|
84
|
-
eventType: 'assistant.client.created',
|
|
85
|
-
org: tenant.org,
|
|
86
|
-
app: tenant.app,
|
|
87
|
-
env: tenant.env,
|
|
88
|
-
deploymentId: target.deploymentId,
|
|
89
|
-
decision: 'allow',
|
|
90
|
-
status: 201,
|
|
91
|
-
...(identity ? { actorSubject: identity.subject } : {}),
|
|
92
|
-
...(identity?.email ? { actorEmail: identity.email } : {}),
|
|
93
|
-
details: { clientId: created.client.id, name: created.client.name },
|
|
94
|
-
});
|
|
95
|
-
return sendJson(res, 201, {
|
|
96
|
-
ok: true,
|
|
97
|
-
...publicClient(created.client),
|
|
98
|
-
clientSecret: created.secret,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
if (!id)
|
|
102
|
-
return sendJson(res, 404, { error: 'not found' });
|
|
103
|
-
const owned = (await deps.store.listClients(tenant)).some((client) => client.id === id);
|
|
104
|
-
if (!owned)
|
|
105
|
-
return sendJson(res, 404, { error: 'not found' });
|
|
106
|
-
if (action === 'rotate' && req.method === 'POST') {
|
|
107
|
-
const rotated = await deps.store.rotateClient(id, now(deps));
|
|
108
|
-
if (!rotated)
|
|
109
|
-
return sendJson(res, 404, { error: 'not found' });
|
|
110
|
-
await deps.audit.emit({
|
|
111
|
-
eventType: 'assistant.client.rotated',
|
|
112
|
-
org: tenant.org,
|
|
113
|
-
app: tenant.app,
|
|
114
|
-
env: tenant.env,
|
|
115
|
-
deploymentId: rotated.client.deploymentId,
|
|
116
|
-
decision: 'allow',
|
|
117
|
-
status: 200,
|
|
118
|
-
...(identity ? { actorSubject: identity.subject } : {}),
|
|
119
|
-
...(identity?.email ? { actorEmail: identity.email } : {}),
|
|
120
|
-
details: { clientId: rotated.client.id },
|
|
121
|
-
});
|
|
122
|
-
return sendJson(res, 200, {
|
|
123
|
-
ok: true,
|
|
124
|
-
...publicClient(rotated.client),
|
|
125
|
-
clientSecret: rotated.secret,
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
if (action === 'item' && req.method === 'DELETE') {
|
|
129
|
-
await deps.store.revokeClient(id, now(deps));
|
|
130
|
-
await deps.audit.emit({
|
|
131
|
-
eventType: 'assistant.client.revoked',
|
|
132
|
-
org: tenant.org,
|
|
133
|
-
app: tenant.app,
|
|
134
|
-
env: tenant.env,
|
|
135
|
-
decision: 'allow',
|
|
136
|
-
status: 204,
|
|
137
|
-
...(identity ? { actorSubject: identity.subject } : {}),
|
|
138
|
-
...(identity?.email ? { actorEmail: identity.email } : {}),
|
|
139
|
-
details: { clientId: id },
|
|
140
|
-
});
|
|
141
|
-
res.statusCode = 204;
|
|
142
|
-
res.end();
|
|
143
|
-
return;
|
|
144
|
-
}
|
|
145
|
-
sendJson(res, 405, { error: 'method not allowed' });
|
|
146
|
-
}
|
|
147
45
|
export async function handleAssistantSession(req, res, deps) {
|
|
148
46
|
const usageStartedAt = performance.now();
|
|
149
47
|
const credentials = basicCredentials(req);
|
|
@@ -173,6 +71,13 @@ export async function handleAssistantSession(req, res, deps) {
|
|
|
173
71
|
if (!assistant.allowedOrigins.includes(parsed.origin)) {
|
|
174
72
|
return sendForbidden(res, 'origin is not allowed');
|
|
175
73
|
}
|
|
74
|
+
// Every session binds one exact authored surface (ADR 0201): the origin selects it, and an origin no
|
|
75
|
+
// surface owns is refused rather than admitted against the deployment-wide union. A pre-surfaces
|
|
76
|
+
// artifact has no surfaces to select from; its union remains its whole released contract.
|
|
77
|
+
const surfaceBinding = surfaceBindingForOrigin(assistant, parsed.origin);
|
|
78
|
+
if (surfaceBinding.kind === 'unowned') {
|
|
79
|
+
return sendForbidden(res, 'origin is not allowed');
|
|
80
|
+
}
|
|
176
81
|
const customerRouting = parseAssistantCustomerRouting(target.served.artifact.customerEndpoints, parsed.routing);
|
|
177
82
|
if (!customerRouting.ok) {
|
|
178
83
|
return sendJson(res, 400, {
|
|
@@ -229,18 +134,15 @@ export async function handleAssistantSession(req, res, deps) {
|
|
|
229
134
|
client,
|
|
230
135
|
resume: shouldAutoResume(parsed.resume),
|
|
231
136
|
origin: parsed.origin,
|
|
137
|
+
// The conversation lands on the surface owning the (validated) landing origin: signing in on
|
|
138
|
+
// the app surface continues under its projection (ADR 0201 amendment 2026-08-26).
|
|
139
|
+
...(surfaceBinding.kind === 'pre-surfaces' ? {} : { boundSurface: surfaceBinding.kind }),
|
|
232
140
|
...(customerRouting.customerRouting
|
|
233
141
|
? { customerRouting: customerRouting.customerRouting }
|
|
234
142
|
: {}),
|
|
235
143
|
caller,
|
|
236
144
|
configuration,
|
|
237
|
-
endpoints:
|
|
238
|
-
turns: `${deps.serviceBase(req)}/v1/assistant/turns`,
|
|
239
|
-
toolConfirmations: `${deps.serviceBase(req)}/v1/assistant/tool-confirmations`,
|
|
240
|
-
interactions: `${deps.serviceBase(req)}/v1/assistant/interactions`,
|
|
241
|
-
apps: `${deps.serviceBase(req)}/v1/assistant/apps`,
|
|
242
|
-
sandbox: `${deps.serviceBase(req)}/v1/assistant/sandbox`,
|
|
243
|
-
},
|
|
145
|
+
endpoints: assistantSessionEndpoints(deps.serviceBase(req), deps.sandboxBase?.(req)),
|
|
244
146
|
});
|
|
245
147
|
}
|
|
246
148
|
const session = await deps.store.createSession({
|
|
@@ -249,6 +151,7 @@ export async function handleAssistantSession(req, res, deps) {
|
|
|
249
151
|
deploymentId: target.deploymentId,
|
|
250
152
|
modelSource: assistant.model.kind === 'noodle-managed' ? 'noodle-managed' : 'operator',
|
|
251
153
|
origin: parsed.origin,
|
|
154
|
+
...(surfaceBinding.kind === 'pre-surfaces' ? {} : { boundSurface: surfaceBinding.kind }),
|
|
252
155
|
...(customerRouting.customerRouting
|
|
253
156
|
? { customerRouting: customerRouting.customerRouting }
|
|
254
157
|
: {}),
|
|
@@ -263,13 +166,7 @@ export async function handleAssistantSession(req, res, deps) {
|
|
|
263
166
|
const sessionBody = {
|
|
264
167
|
token: session.token,
|
|
265
168
|
expiresAt: session.session.expiresAt,
|
|
266
|
-
endpoints:
|
|
267
|
-
turns: `${deps.serviceBase(req)}/v1/assistant/turns`,
|
|
268
|
-
toolConfirmations: `${deps.serviceBase(req)}/v1/assistant/tool-confirmations`,
|
|
269
|
-
interactions: `${deps.serviceBase(req)}/v1/assistant/interactions`,
|
|
270
|
-
apps: `${deps.serviceBase(req)}/v1/assistant/apps`,
|
|
271
|
-
sandbox: `${deps.serviceBase(req)}/v1/assistant/sandbox`,
|
|
272
|
-
},
|
|
169
|
+
endpoints: assistantSessionEndpoints(deps.serviceBase(req), deps.sandboxBase?.(req)),
|
|
273
170
|
...(configuration ? { configuration } : {}),
|
|
274
171
|
};
|
|
275
172
|
// Every published @noodleseed/assistant widget consumes this shape; parse (don't just test)
|
|
@@ -293,6 +190,7 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
293
190
|
const addressBucket = clientAddressBucket(typeof req.headers['x-forwarded-for'] === 'string' ? req.headers['x-forwarded-for'] : undefined);
|
|
294
191
|
let turn;
|
|
295
192
|
let message;
|
|
193
|
+
let resumedTool;
|
|
296
194
|
if (isRecord(body.value) && 'resume' in body.value) {
|
|
297
195
|
// The one-shot post-sign-in resume (issue #1177): no user text, the pending intent is server
|
|
298
196
|
// state. Admission runs FIRST so a refused resume stays armed for a retry; the consume is a
|
|
@@ -313,6 +211,7 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
313
211
|
});
|
|
314
212
|
}
|
|
315
213
|
message = resumeTurnMessage(pending.tool);
|
|
214
|
+
resumedTool = pending.tool;
|
|
316
215
|
await deps.audit.emit({
|
|
317
216
|
eventType: 'assistant.session.resumed',
|
|
318
217
|
org: session.tenant.org,
|
|
@@ -361,6 +260,12 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
361
260
|
const target = await sessionScopedTarget(deps.registry, session);
|
|
362
261
|
if (!target)
|
|
363
262
|
return sendJson(res, 409, { error: 'assistant deployment is unavailable' });
|
|
263
|
+
if (resumedTool !== undefined &&
|
|
264
|
+
!target.served.artifact.tools.some((tool) => tool.name === resumedTool)) {
|
|
265
|
+
// The elevation landed on a surface whose projection does not offer the intercepted tool: fail
|
|
266
|
+
// closed to an honest explanation instead of asking the model to call a tool it cannot see.
|
|
267
|
+
message = resumeUnavailableMessage(resumedTool);
|
|
268
|
+
}
|
|
364
269
|
const clientContext = turn?.clientContext === undefined
|
|
365
270
|
? undefined
|
|
366
271
|
: parseAssistantContextPreferences(turn.clientContext);
|
|
@@ -396,8 +301,11 @@ export async function handleAssistantTurn(req, res, deps) {
|
|
|
396
301
|
.map((event) => String(event.data.delta ?? ''))
|
|
397
302
|
.join('');
|
|
398
303
|
await deps.store.appendHistory(session.id, [
|
|
399
|
-
|
|
400
|
-
|
|
304
|
+
// A resume turn's user row is the [platform] prompt the visitor never typed or saw.
|
|
305
|
+
{ role: 'user', content: message.trim(), kind: turn ? 'visible' : 'narration' },
|
|
306
|
+
...(assistantContent
|
|
307
|
+
? [{ role: 'assistant', content: assistantContent, kind: 'visible' }]
|
|
308
|
+
: []),
|
|
401
309
|
]);
|
|
402
310
|
}
|
|
403
311
|
catch {
|
|
@@ -559,14 +467,4 @@ function isContext(value) {
|
|
|
559
467
|
typeof item === 'boolean' ||
|
|
560
468
|
(typeof item === 'string' && item.length <= 2_000))));
|
|
561
469
|
}
|
|
562
|
-
function publicClient(client) {
|
|
563
|
-
return {
|
|
564
|
-
id: client.id,
|
|
565
|
-
name: client.name,
|
|
566
|
-
createdAt: client.createdAt,
|
|
567
|
-
// Audit data only: sessions follow the tenant's active deployment, not this snapshot.
|
|
568
|
-
createdAgainstDeploymentId: client.deploymentId,
|
|
569
|
-
...(client.revokedAt ? { revokedAt: client.revokedAt } : {}),
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
470
|
//# sourceMappingURL=assistant.js.map
|
|
@@ -94,7 +94,7 @@ function developerConfigAccess(grants, controlPlane, ref, method) {
|
|
|
94
94
|
controlPlane,
|
|
95
95
|
org: ref.scope.org,
|
|
96
96
|
capability: method === 'GET' ? 'cloud:read' : 'config:write',
|
|
97
|
-
|
|
97
|
+
resourcePaths: new Set([DEVELOPER_CLI_PATH]),
|
|
98
98
|
},
|
|
99
99
|
};
|
|
100
100
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { cspFaultsInManifest } from '@noodle-borg/compiler';
|
|
2
2
|
import { ensurePersonalWorkspace } from '@noodle-borg/control-plane/portable';
|
|
3
|
-
import { DEVELOPER_CLI_PATH } from '@noodle-borg/developer-mcp';
|
|
3
|
+
import { DEVELOPER_ASSISTANT_PATH, DEVELOPER_CLI_PATH, } from '@noodle-borg/developer-mcp';
|
|
4
4
|
import { normalizeServerVersion } from '@noodle-borg/module';
|
|
5
5
|
import { noopLogger } from '@noodle-borg/transport-http';
|
|
6
6
|
import { deployRequestSchema, formatWireError, } from '@noodle-borg/wire-contracts';
|
|
@@ -504,9 +504,15 @@ export async function handleDeploymentStatus(req, res, registry, gate, controlPl
|
|
|
504
504
|
}
|
|
505
505
|
}
|
|
506
506
|
export function developerGrantRouteAccess(grants, capability) {
|
|
507
|
+
// Control-plane routes serve the CLI resource and the assistant resource (ADR 0218); the grant's
|
|
508
|
+
// own capability ceiling — never this set — decides what each may actually do.
|
|
507
509
|
return grants === undefined
|
|
508
510
|
? undefined
|
|
509
|
-
: {
|
|
511
|
+
: {
|
|
512
|
+
grants,
|
|
513
|
+
capability,
|
|
514
|
+
resourcePaths: new Set([DEVELOPER_CLI_PATH, DEVELOPER_ASSISTANT_PATH]),
|
|
515
|
+
};
|
|
510
516
|
}
|
|
511
517
|
async function authorizeHumanDeploy(req, res, options, gate, controlPlane, org) {
|
|
512
518
|
const developerAccess = developerGrantAccess(options.developerGrantStore, controlPlane, org, 'deployments:write');
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InMemoryAssistantStore } from '@noodle-borg/assistant-gateway/portable';
|
|
1
|
+
import { InMemoryAssistantStore, isAssistantSandboxHost, validateAssistantSandboxBaseUrl, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
2
|
import { allowAllGate, InMemoryControlPlaneStore } from '@noodle-borg/control-plane/portable';
|
|
3
3
|
import { dispatchKnowledgeRequest } from '@noodle-borg/knowledge-operations/portable';
|
|
4
4
|
import { OPENAI_APPS_CHALLENGE_PATH } from '@noodle-borg/module';
|
|
@@ -50,6 +50,9 @@ const DEFAULT_MAX_BODY = 1 << 20;
|
|
|
50
50
|
const DEFAULT_MAX_DEPLOY_BODY = 32 * 1024 * 1024;
|
|
51
51
|
/** The combined service listener: tenant-scoped deploy management plus the multi-tenant MCP router. */
|
|
52
52
|
export function createServiceHandler(registry, options = {}) {
|
|
53
|
+
const assistantSandboxBaseUrl = options.assistantSandboxBaseUrl === undefined
|
|
54
|
+
? undefined
|
|
55
|
+
: validateAssistantSandboxBaseUrl(options.assistantSandboxBaseUrl, options.publicBaseUrl);
|
|
53
56
|
const logger = options.logger ?? noopLogger;
|
|
54
57
|
const tls = options.tls ?? {};
|
|
55
58
|
const buildInfo = options.buildInfo ?? resolveBuildInfo();
|
|
@@ -155,6 +158,14 @@ export function createServiceHandler(registry, options = {}) {
|
|
|
155
158
|
};
|
|
156
159
|
return (req, res) => {
|
|
157
160
|
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
161
|
+
const onAssistantSandboxHost = isAssistantSandboxHost(req.headers.host, assistantSandboxBaseUrl);
|
|
162
|
+
if (onAssistantSandboxHost && url.pathname !== '/v1/assistant/sandbox') {
|
|
163
|
+
applySecurityHeaders(res, tls);
|
|
164
|
+
if (enforceHttps(req, res, tls))
|
|
165
|
+
return;
|
|
166
|
+
sendJson(res, 404, { error: 'not found' });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
158
169
|
// Liveness/readiness probes: un-gated and **not** HTTPS-enforced — Cloud Run's internal probe is plain
|
|
159
170
|
// HTTP, so enforcing HTTPS here would `426` the probe and the revision would never go healthy (ADR 0034).
|
|
160
171
|
// `/healthz` is a pure liveness 200 (no store touch); `/readyz` reflects the injected readiness probe.
|
|
@@ -250,6 +261,9 @@ export function createServiceHandler(registry, options = {}) {
|
|
|
250
261
|
audit: activeAudit,
|
|
251
262
|
maxBody,
|
|
252
263
|
serviceBase: (request) => options.publicBaseUrl ?? baseFromRequest(request, tls),
|
|
264
|
+
...(assistantSandboxBaseUrl === undefined
|
|
265
|
+
? {}
|
|
266
|
+
: { sandboxBase: () => assistantSandboxBaseUrl }),
|
|
253
267
|
...(options.assistantModelFetch !== undefined
|
|
254
268
|
? { modelFetch: options.assistantModelFetch }
|
|
255
269
|
: {}),
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
41
41
|
"@noodle-borg/admission-limits": "0.0.0",
|
|
42
|
-
"@noodle-borg/agent-kit": "0.
|
|
42
|
+
"@noodle-borg/agent-kit": "0.85.0",
|
|
43
43
|
"@noodle-borg/app-package": "0.0.0",
|
|
44
44
|
"@noodle-borg/assistant-gateway": "0.0.0",
|
|
45
45
|
"@noodle-borg/auth": "0.0.0",
|
|
@@ -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)
|
|
39
|
-
|
|
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
|
-
|
|
414
|
-
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noodleseed/assistant",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"description": "Embed the Noodle Seed customer-branded assistant in your web app with managed or framework-owned UI, a framework-neutral MCP App host, a DOM-free client, and a backend session helper. Authoring and deploying the server is @noodleseed/one.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|