@noodleseed/one 0.141.2 → 0.142.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/assistant-appearance-ops.d.ts +3 -0
- package/dist/commands/assistant-appearance-ops.d.ts.map +1 -0
- package/dist/commands/assistant-appearance-ops.js +148 -0
- package/dist/commands/assistant-appearance-ops.js.map +1 -0
- package/dist/commands/assistant-ops.d.ts.map +1 -1
- package/dist/commands/assistant-ops.js +4 -1
- package/dist/commands/assistant-ops.js.map +1 -1
- 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 +25 -18
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-appearance-store.d.ts +34 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-appearance-store.js +12 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-appearance.d.ts +18 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-appearance.js +228 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-configuration.d.ts +31 -0
- package/node_modules/@noodle-borg/{service/dist/routes → assistant-gateway/dist}/assistant-configuration.js +12 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-delegated-projection.d.ts +12 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.d.ts +1 -1
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-store.js +1 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/assistant-view-availability.d.ts +38 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.d.ts +3 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/embed-store.js +4 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-assistant-appearance-store.d.ts +8 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/in-memory-assistant-appearance-store.js +26 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.d.ts +7 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/portable.js +7 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-configuration.d.ts +22 -0
- package/node_modules/@noodle-borg/assistant-gateway/dist/public-configuration.js +25 -0
- package/node_modules/@noodle-borg/assistant-gateway/package.json +3 -1
- package/node_modules/@noodle-borg/authoring/dist/assistant.d.ts +6 -2
- package/node_modules/@noodle-borg/authoring/dist/react/components.d.ts +3 -3
- package/node_modules/@noodle-borg/authoring/dist/react/native-components.d.ts +2 -2
- package/node_modules/@noodle-borg/cli-catalog/dist/catalog-data-assistant.js +38 -0
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.d.ts +56 -4
- package/node_modules/@noodle-borg/compiler/dist/manifest/schema.js +25 -47
- package/node_modules/@noodle-borg/service/dist/credential-broker.js +1 -1
- package/node_modules/@noodle-borg/service/dist/invocation-context.js +1 -2
- package/node_modules/@noodle-borg/service/dist/local-options.d.ts +1 -0
- package/node_modules/@noodle-borg/service/dist/registry-compile.js +1 -1
- package/node_modules/@noodle-borg/service/dist/routes/assistant-agent.js +1 -3
- package/node_modules/@noodle-borg/service/dist/routes/assistant-appearance.js +104 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-dispatch.js +23 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interaction-replay.js +1 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant-interactions.js +1 -2
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-configuration.js +41 -0
- package/node_modules/@noodle-borg/service/dist/routes/assistant-public-session.js +6 -11
- package/node_modules/@noodle-borg/service/dist/routes/assistant.js +2 -3
- package/node_modules/@noodle-borg/service/dist/service.js +3 -1
- package/node_modules/@noodleseed/assistant/README.md +54 -20
- package/node_modules/@noodleseed/assistant/package.json +1 -1
- package/package.json +1 -1
- /package/node_modules/@noodle-borg/{service → assistant-gateway}/dist/assistant-delegated-projection.js +0 -0
- /package/node_modules/@noodle-borg/{service → assistant-gateway}/dist/assistant-view-availability.js +0 -0
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { ADMISSION_DEFAULTS } from '@noodle-borg/admission-limits/portable';
|
|
2
2
|
import { requestModelCompletion, } from '@noodle-borg/assistant-gateway/model-runtime';
|
|
3
|
-
import { assistantModelToolOncePerSession, assistantModelToolRequiredWhenVisible, authenticatedSurfaceOf, dispatchAssistantTool, projectAssistantGuide, publicSurfaceOf, selectAssistantModelTools, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
|
+
import { assistantModelToolOncePerSession, assistantModelToolRequiredWhenVisible, assistantViewAvailableData, authenticatedSurfaceOf, dispatchAssistantTool, projectAssistantGuide, publicSurfaceOf, selectAssistantModelTools, toolTouchesDelegatedAuth, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
4
4
|
import { validateJsonSchemaWithDefaults } from '@noodle-borg/compiler';
|
|
5
5
|
import { guardedFetch } from '@noodle-borg/connector-http';
|
|
6
6
|
import { evaluateToolAuthorization } from '@noodle-borg/protocol';
|
|
7
7
|
import { executeTool } from '@noodle-borg/runtime';
|
|
8
|
-
import { toolTouchesDelegatedAuth } from '../assistant-delegated-projection.js';
|
|
9
|
-
import { assistantViewAvailableData } from '../assistant-view-availability.js';
|
|
10
8
|
import { invocationContextSystemMessages } from '../invocation-context.js';
|
|
11
9
|
import { interceptForElevation, offersSignIn } from './assistant-elevation.js';
|
|
12
10
|
import { assistantKnowledgeModelTools, executeAssistantKnowledgeSearch, findAssistantKnowledgeComponent, KNOWLEDGE_CITATION_GUIDANCE, resolveAssistantKnowledge, } from './assistant-knowledge.js';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { assistantAppearanceOverrideSchema, assistantBrowserConfiguration, resolveAssistantAppearanceConfiguration, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
+
import { readJsonBody, sendForbidden, sendJson } from '../http-util.js';
|
|
3
|
+
import { now } from './assistant-route-http.js';
|
|
4
|
+
import { authorizeControlPlane } from './control-plane.js';
|
|
5
|
+
export async function handleAssistantAppearance(req, res, tenant, deps) {
|
|
6
|
+
const store = deps.appearance;
|
|
7
|
+
if (store === undefined)
|
|
8
|
+
return sendJson(res, 503, { error: 'assistant appearance is unavailable' });
|
|
9
|
+
const identity = await authorizeControlPlane(req, res, deps.gate, { requireIdentity: false });
|
|
10
|
+
if (identity === false)
|
|
11
|
+
return;
|
|
12
|
+
if (identity && !identity.superAdmin) {
|
|
13
|
+
const member = await deps.controlPlane.isOrgMember({
|
|
14
|
+
org: tenant.org,
|
|
15
|
+
subject: identity.subject,
|
|
16
|
+
});
|
|
17
|
+
if (!member)
|
|
18
|
+
return sendForbidden(res, 'forbidden');
|
|
19
|
+
}
|
|
20
|
+
if (req.method === 'GET')
|
|
21
|
+
return respond(res, tenant, deps);
|
|
22
|
+
const expectedRevision = parseExpectedRevision(req.headers['if-match']);
|
|
23
|
+
if (expectedRevision === undefined) {
|
|
24
|
+
return sendJson(res, req.headers['if-match'] === undefined ? 428 : 400, {
|
|
25
|
+
error: req.headers['if-match'] === undefined ? 'If-Match is required' : 'invalid If-Match',
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
let override;
|
|
29
|
+
if (req.method === 'PUT') {
|
|
30
|
+
const body = await readJsonBody(req, deps.maxBody);
|
|
31
|
+
if (!body.ok)
|
|
32
|
+
return sendJson(res, body.status, { error: body.error });
|
|
33
|
+
const parsed = assistantAppearanceOverrideSchema.safeParse(body.value);
|
|
34
|
+
if (!parsed.success)
|
|
35
|
+
return sendJson(res, 400, { error: 'invalid assistant appearance' });
|
|
36
|
+
override = parsed.data;
|
|
37
|
+
}
|
|
38
|
+
else if (req.method !== 'DELETE') {
|
|
39
|
+
return sendJson(res, 405, { error: 'method not allowed' });
|
|
40
|
+
}
|
|
41
|
+
const updated = await store.replace({
|
|
42
|
+
tenant,
|
|
43
|
+
expectedRevision,
|
|
44
|
+
override,
|
|
45
|
+
updatedAt: now(deps),
|
|
46
|
+
updatedBy: identity?.subject ?? 'local-operator',
|
|
47
|
+
});
|
|
48
|
+
if (!updated.ok) {
|
|
49
|
+
return sendJson(res, 409, {
|
|
50
|
+
error: 'assistant appearance revision conflict',
|
|
51
|
+
currentRevision: updated.currentRevision,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const target = await deps.registry.getActiveByTenant(tenant);
|
|
55
|
+
await deps.audit.emit({
|
|
56
|
+
eventType: updated.record.override === undefined
|
|
57
|
+
? 'assistant.appearance.reset'
|
|
58
|
+
: 'assistant.appearance.updated',
|
|
59
|
+
org: tenant.org,
|
|
60
|
+
app: tenant.app,
|
|
61
|
+
env: tenant.env,
|
|
62
|
+
...(target?.deploymentId ? { deploymentId: target.deploymentId } : {}),
|
|
63
|
+
decision: 'allow',
|
|
64
|
+
status: 200,
|
|
65
|
+
...(identity ? { actorSubject: identity.subject } : {}),
|
|
66
|
+
...(identity?.email ? { actorEmail: identity.email } : {}),
|
|
67
|
+
details: {
|
|
68
|
+
revision: updated.record.revision,
|
|
69
|
+
hasOverride: updated.record.override !== undefined,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
return respond(res, tenant, deps, updated.record, target);
|
|
73
|
+
}
|
|
74
|
+
async function respond(res, tenant, deps, knownRecord, knownTarget) {
|
|
75
|
+
const target = knownTarget ?? (await deps.registry.getActiveByTenant(tenant));
|
|
76
|
+
const developer = target
|
|
77
|
+
? assistantBrowserConfiguration(target.served.artifact.server)
|
|
78
|
+
: undefined;
|
|
79
|
+
const record = knownRecord ?? (await deps.appearance?.get(tenant));
|
|
80
|
+
const resolution = resolveAssistantAppearanceConfiguration(developer, record?.override);
|
|
81
|
+
const revision = record?.revision ?? 0;
|
|
82
|
+
res.setHeader('etag', `"${revision}"`);
|
|
83
|
+
return sendJson(res, 200, {
|
|
84
|
+
ok: true,
|
|
85
|
+
assistantEnabled: target?.served.artifact.server.assistant !== undefined,
|
|
86
|
+
revision,
|
|
87
|
+
fallback: resolution.fallback,
|
|
88
|
+
developer: developer ?? null,
|
|
89
|
+
override: record?.override ?? null,
|
|
90
|
+
effective: resolution.effective ?? null,
|
|
91
|
+
provenance: resolution.provenance,
|
|
92
|
+
...(record ? { updatedAt: record.updatedAt.toISOString(), updatedBy: record.updatedBy } : {}),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
function parseExpectedRevision(value) {
|
|
96
|
+
if (typeof value !== 'string')
|
|
97
|
+
return undefined;
|
|
98
|
+
const match = /^"(0|[1-9][0-9]*)"$/.exec(value);
|
|
99
|
+
if (!match?.[1])
|
|
100
|
+
return undefined;
|
|
101
|
+
const revision = Number(match[1]);
|
|
102
|
+
return Number.isSafeInteger(revision) ? revision : undefined;
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=assistant-appearance.js.map
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { handleAssistantAppRequest, handleAssistantPreflight, handleAssistantSession, handleAssistantTurn, handleConsoleApprovalNonce, } from './assistant.js';
|
|
2
|
+
import { handleAssistantAppearance } from './assistant-appearance.js';
|
|
2
3
|
import { handleAssistantClients } from './assistant-clients.js';
|
|
3
4
|
import { handleAssistantDoctor } from './assistant-doctor.js';
|
|
4
5
|
import { handleAssistantEmbedScript } from './assistant-embed-script.js';
|
|
5
6
|
import { handleAssistantEmbeds } from './assistant-embeds.js';
|
|
6
7
|
import { handleAssistantConfirmation, handleAssistantInteraction, } from './assistant-interactions.js';
|
|
8
|
+
import { handlePublicAssistantConfiguration } from './assistant-public-configuration.js';
|
|
7
9
|
import { handlePublicAssistantSession } from './assistant-public-session.js';
|
|
8
10
|
import { handleAssistantSandbox } from './assistant-sandbox.js';
|
|
9
11
|
import { handleAssistantTranscript } from './assistant-transcript.js';
|
|
@@ -41,6 +43,18 @@ export function dispatchAssistantRoutes(req, res, url, deps) {
|
|
|
41
43
|
if (url.pathname === '/v1/assistant/public-sessions' && req.method === 'POST') {
|
|
42
44
|
return run(req, res, deps, () => handlePublicAssistantSession(req, res, deps));
|
|
43
45
|
}
|
|
46
|
+
const publicConfigurationMatch = /^\/v1\/assistant\/public-configurations\/([^/]+)$/.exec(url.pathname);
|
|
47
|
+
if (publicConfigurationMatch) {
|
|
48
|
+
let embedId;
|
|
49
|
+
try {
|
|
50
|
+
embedId = decodeURIComponent(publicConfigurationMatch[1] ?? '');
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
deps.sendJson(res, 400, { error: 'invalid embed id' });
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
return run(req, res, deps, () => handlePublicAssistantConfiguration(req, res, embedId, deps));
|
|
57
|
+
}
|
|
44
58
|
if (url.pathname === '/v1/assistant/sessions' && req.method === 'POST') {
|
|
45
59
|
return run(req, res, deps, () => handleAssistantSession(req, res, deps));
|
|
46
60
|
}
|
|
@@ -54,6 +68,15 @@ export function dispatchAssistantRoutes(req, res, url, deps) {
|
|
|
54
68
|
return run(req, res, deps, () => handleAssistantTranscript(req, res, deps));
|
|
55
69
|
}
|
|
56
70
|
const doctorMatch = /^\/v1\/orgs\/([^/]+)\/apps\/([^/]+)\/envs\/([^/]+)\/assistant\/doctor$/.exec(url.pathname);
|
|
71
|
+
const appearanceMatch = /^\/v1\/orgs\/([^/]+)\/apps\/([^/]+)\/envs\/([^/]+)\/assistant\/appearance$/.exec(url.pathname);
|
|
72
|
+
if (appearanceMatch) {
|
|
73
|
+
const tenant = decodeTenant(appearanceMatch);
|
|
74
|
+
if (!tenant) {
|
|
75
|
+
deps.sendJson(res, 400, { error: 'invalid tenant path' });
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
return run(req, res, deps, () => handleAssistantAppearance(req, res, tenant, deps));
|
|
79
|
+
}
|
|
57
80
|
if (doctorMatch) {
|
|
58
81
|
const tenant = decodeTenant(doctorMatch);
|
|
59
82
|
if (!tenant) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { assistantInteractionTool, auditAssistantInteraction, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
-
import { assistantViewAvailableData } from '../assistant-view-availability.js';
|
|
1
|
+
import { assistantInteractionTool, assistantViewAvailableData, auditAssistantInteraction, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
2
|
import { sendJson } from '../http-util.js';
|
|
4
3
|
import { now } from './assistant.js';
|
|
5
4
|
import { assistantInteractionScope } from './assistant-interaction-resolution.js';
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { AssistantInteractionCapacityError, assistantConfirmationReview, assistantConfirmationTransition, assistantInputTransition, assistantInteractionTool, assistantPreparedArgumentReview, assistantPreparedToolContinuation, assistantSafeOutput, assistantToolContinuation, auditAssistantInteraction, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
1
|
+
import { AssistantInteractionCapacityError, assistantConfirmationReview, assistantConfirmationTransition, assistantInputTransition, assistantInteractionTool, assistantPreparedArgumentReview, assistantPreparedToolContinuation, assistantSafeOutput, assistantToolContinuation, assistantViewAvailableData, auditAssistantInteraction, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
2
|
import { executePreparedTool, executeToolInteractive, resumeTool, resumeToolPreparation, } from '@noodle-borg/runtime';
|
|
3
|
-
import { assistantViewAvailableData } from '../assistant-view-availability.js';
|
|
4
3
|
import { readJsonBody, sendJson } from '../http-util.js';
|
|
5
4
|
import { applyBrowserCors, authenticateSession, now, } from './assistant.js';
|
|
6
5
|
import { replayOrConflict, writeUnknownOutcome } from './assistant-interaction-replay.js';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { authorizePublicConfiguration, effectiveAssistantBrowserConfiguration, publicEmbedTenant, publicSurfaceOf, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
+
import { sendJson } from '../http-util.js';
|
|
3
|
+
import { applyBrowserCors } from './assistant-route-http.js';
|
|
4
|
+
/** Browser-safe first paint for a public launcher; never mints a session or spends admission. */
|
|
5
|
+
export async function handlePublicAssistantConfiguration(req, res, embedId, deps) {
|
|
6
|
+
if (req.method !== 'GET')
|
|
7
|
+
return sendJson(res, 405, { error: 'method not allowed' });
|
|
8
|
+
if (deps.publicEmbeds === undefined) {
|
|
9
|
+
return sendJson(res, 503, { error: 'public assistant surfaces are not enabled' });
|
|
10
|
+
}
|
|
11
|
+
const origin = typeof req.headers.origin === 'string' ? req.headers.origin : undefined;
|
|
12
|
+
const state = {};
|
|
13
|
+
const authorized = await authorizePublicConfiguration({ embedId, origin }, {
|
|
14
|
+
embeds: deps.publicEmbeds,
|
|
15
|
+
resolveActiveSurface: async (embed) => {
|
|
16
|
+
state.target = await deps.registry.getActiveByTenant(publicEmbedTenant(embed));
|
|
17
|
+
return state.target
|
|
18
|
+
? publicSurfaceOf(state.target.served.artifact.server.assistant)
|
|
19
|
+
: undefined;
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
if (!authorized.ok) {
|
|
23
|
+
return sendJson(res, authorized.status, {
|
|
24
|
+
error: authorized.message,
|
|
25
|
+
code: authorized.code,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const target = state.target;
|
|
29
|
+
if (target === undefined) {
|
|
30
|
+
return sendJson(res, 409, { error: 'assistant deployment is unavailable' });
|
|
31
|
+
}
|
|
32
|
+
const resolved = await effectiveAssistantBrowserConfiguration(target.served.artifact.server, publicEmbedTenant(authorized.embed), deps.appearance);
|
|
33
|
+
applyBrowserCors(req, res, origin);
|
|
34
|
+
res.setHeader('cache-control', 'private, no-cache');
|
|
35
|
+
return sendJson(res, 200, {
|
|
36
|
+
...(resolved.effective ? { configuration: resolved.effective } : {}),
|
|
37
|
+
fallback: resolved.fallback,
|
|
38
|
+
revision: resolved.record?.revision ?? 0,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=assistant-public-configuration.js.map
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import { ADMISSION_DEFAULTS, clientAddressBucket } from '@noodle-borg/admission-limits/portable';
|
|
3
|
-
import { mintPublicSession, publicSurfaceOf, } from '@noodle-borg/assistant-gateway/portable';
|
|
3
|
+
import { effectiveAssistantBrowserConfiguration, mintPublicSession, publicEmbedTenant, publicSurfaceOf, } from '@noodle-borg/assistant-gateway/portable';
|
|
4
4
|
import { assistantSessionUsageRequestEvent, captureAssistantUsage, } from '@noodle-borg/observability';
|
|
5
5
|
import { assistantSessionResponseSchema } from '@noodle-borg/wire-contracts';
|
|
6
6
|
import { readJsonBody, sendJson } from '../http-util.js';
|
|
7
|
-
import { assistantBrowserConfiguration } from './assistant-configuration.js';
|
|
8
7
|
import { applyBrowserCors, assistantSessionEndpoints, now } from './assistant-route-http.js';
|
|
9
8
|
/**
|
|
10
9
|
* The transport half of anonymous session minting. Every decision — which refusal, in which order, and
|
|
@@ -16,10 +15,6 @@ import { applyBrowserCors, assistantSessionEndpoints, now } from './assistant-ro
|
|
|
16
15
|
* echoes the validated origin, and a refusal echoes nothing, so a page that is not on the allowlist
|
|
17
16
|
* fails visibly instead of quietly receiving a usable token.
|
|
18
17
|
*/
|
|
19
|
-
/** The embed record stores the tenant flat; the registry and session store want it shaped. */
|
|
20
|
-
function tenantOf(embed) {
|
|
21
|
-
return { org: embed.org, app: embed.app, env: embed.env };
|
|
22
|
-
}
|
|
23
18
|
export async function handlePublicAssistantSession(req, res, deps) {
|
|
24
19
|
const usageStartedAt = performance.now();
|
|
25
20
|
const embeds = deps.publicEmbeds;
|
|
@@ -45,12 +40,12 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
45
40
|
embeds,
|
|
46
41
|
counters,
|
|
47
42
|
resolveActiveSurface: async (embed) => {
|
|
48
|
-
const target = await deps.registry.getActiveByTenant(
|
|
43
|
+
const target = await deps.registry.getActiveByTenant(publicEmbedTenant(embed));
|
|
49
44
|
if (!target?.deploymentId)
|
|
50
45
|
return undefined;
|
|
51
46
|
if (target.served.artifact.server.assistant?.model.kind === 'noodle-managed') {
|
|
52
47
|
sponsoredBudget = (await deps.managedModelResolver?.resolve({
|
|
53
|
-
tenant:
|
|
48
|
+
tenant: publicEmbedTenant(embed),
|
|
54
49
|
deploymentId: target.deploymentId,
|
|
55
50
|
}))?.publicAdmission;
|
|
56
51
|
}
|
|
@@ -60,16 +55,16 @@ export async function handlePublicAssistantSession(req, res, deps) {
|
|
|
60
55
|
},
|
|
61
56
|
resolveBudgetBounds: async () => sponsoredBudget,
|
|
62
57
|
createSession: async (input) => {
|
|
63
|
-
const target = await deps.registry.getActiveByTenant(
|
|
58
|
+
const target = await deps.registry.getActiveByTenant(publicEmbedTenant(input.embed));
|
|
64
59
|
if (!target?.deploymentId)
|
|
65
60
|
throw new Error('assistant deployment vanished mid-mint');
|
|
66
61
|
const current = now(deps);
|
|
67
|
-
configuration =
|
|
62
|
+
configuration = (await effectiveAssistantBrowserConfiguration(target.served.artifact.server, publicEmbedTenant(input.embed), deps.appearance)).effective;
|
|
68
63
|
const created = await deps.store.createSession({
|
|
69
64
|
// A public surface has no client credential, so the embed id stands in as the minting
|
|
70
65
|
// authority. It is not a secret and nothing downstream may begin treating it as one.
|
|
71
66
|
clientId: input.embed.embedId,
|
|
72
|
-
tenant:
|
|
67
|
+
tenant: publicEmbedTenant(input.embed),
|
|
73
68
|
deploymentId: target.deploymentId,
|
|
74
69
|
modelSource: target.served.artifact.server.assistant?.model.kind === 'noodle-managed'
|
|
75
70
|
? 'noodle-managed'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { clientAddressBucket, } from '@noodle-borg/admission-limits/portable';
|
|
2
|
-
import { ASSISTANT_SESSION_IDLE_MS, dispatchAssistantTool, parseAssistantContextPreferences, parseAssistantCustomerRouting, resumeTurnMessage, resumeUnavailableMessage, shouldAutoResume, surfaceBindingForOrigin, withAssistantSessionExecutionAuthority, } from '@noodle-borg/assistant-gateway/portable';
|
|
2
|
+
import { ASSISTANT_SESSION_IDLE_MS, dispatchAssistantTool, effectiveAssistantBrowserConfiguration, 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';
|
|
@@ -10,7 +10,6 @@ import { assistantMessageTurnRequestSchema, assistantResumeTurnRequestSchema, as
|
|
|
10
10
|
import { readJsonBody, sendForbidden, sendJson, sendUnauthorized } from '../http-util.js';
|
|
11
11
|
import { resolveInvocationContextSnapshot } from '../invocation-context.js';
|
|
12
12
|
import { createAssistantTurnStats, runAgentTurn } from './assistant-agent.js';
|
|
13
|
-
import { assistantBrowserConfiguration } from './assistant-configuration.js';
|
|
14
13
|
import { elevateAssistantSession } from './assistant-elevation.js';
|
|
15
14
|
import { executeAssistantKnowledgeSearch, findAssistantKnowledgeComponent, resolveAssistantKnowledge, } from './assistant-knowledge.js';
|
|
16
15
|
import { refusePublicTurn } from './assistant-public-turn.js';
|
|
@@ -100,7 +99,7 @@ export async function handleAssistantSession(req, res, deps) {
|
|
|
100
99
|
const declaredClaims = assistant.sessionClaims ?? {};
|
|
101
100
|
const claims = Object.fromEntries(Object.entries(isVerifiedClaims(parsed.claims) ? parsed.claims : {}).filter(([key]) => key in declaredClaims));
|
|
102
101
|
const current = now(deps);
|
|
103
|
-
const configuration =
|
|
102
|
+
const configuration = (await effectiveAssistantBrowserConfiguration(target.served.artifact.server, client.tenant, deps.appearance)).effective;
|
|
104
103
|
const roles = canonicalizeAuthorizationClaimValues(parsed.user.roles, 'role');
|
|
105
104
|
const scopes = canonicalizeAuthorizationClaimValues(parsed.user.scopes, 'scope');
|
|
106
105
|
// Hoisted so an elevation and a fresh exchange bind the *same* principal. Two constructions would be
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InMemoryAssistantStore } from '@noodle-borg/assistant-gateway/portable';
|
|
1
|
+
import { InMemoryAssistantAppearanceSettingsStore, InMemoryAssistantStore, } 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';
|
|
@@ -59,6 +59,7 @@ export function createServiceHandler(registry, options = {}) {
|
|
|
59
59
|
const alertRuleStore = options.alertRuleStore ?? new InMemoryAlertRuleStore();
|
|
60
60
|
const [intentSettings, intentEvents, requestEvents, intentPreviewOrgs] = createObservabilityStores(options);
|
|
61
61
|
const assistantStore = options.assistantStore ?? new InMemoryAssistantStore();
|
|
62
|
+
const assistantAppearance = options.assistantAppearance ?? new InMemoryAssistantAppearanceSettingsStore();
|
|
62
63
|
const anonymousLimiter = new AnonymousConsumerLimiter();
|
|
63
64
|
registry.setPlatformConnectors({ catalog: [BUILTIN_STATE_CATALOG_CONNECTOR], connectors: [] });
|
|
64
65
|
const moduleProviders = createServiceModuleProviders({
|
|
@@ -237,6 +238,7 @@ export function createServiceHandler(registry, options = {}) {
|
|
|
237
238
|
if (dispatchAssistantRoutes(req, res, url, {
|
|
238
239
|
registry,
|
|
239
240
|
store: assistantStore,
|
|
241
|
+
appearance: assistantAppearance,
|
|
240
242
|
...(options.publicEmbeds !== undefined ? { publicEmbeds: options.publicEmbeds } : {}),
|
|
241
243
|
...(options.elevations !== undefined ? { elevations: options.elevations } : {}),
|
|
242
244
|
...(options.admissionCounters !== undefined
|
|
@@ -6,10 +6,10 @@ The package exports the canonical `<noodle-assistant>` managed Web Component, th
|
|
|
6
6
|
`<noodle-app-view>` MCP App host from `@noodleseed/assistant/app-view`, a managed React wrapper and
|
|
7
7
|
`NoodleAppView` adapter from `@noodleseed/assistant/react`, a renderer-free React hook from
|
|
8
8
|
`@noodleseed/assistant/react/client`, a DOM-free client from `@noodleseed/assistant/client`, and the
|
|
9
|
-
backend-only `createAssistantSession` helper from `@noodleseed/assistant/server`.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
DOM
|
|
9
|
+
backend-only `createAssistantSession` helper from `@noodleseed/assistant/server`. The component inherits the
|
|
10
|
+
deployed MCP server's brand kit while slots, methods, events, and semantic CSS variables let a SaaS developer
|
|
11
|
+
integrate it without depending on internal DOM selectors. Light, dark, automatic host-page matching, and
|
|
12
|
+
host-page inversion work without replacing conversation or App DOM.
|
|
13
13
|
|
|
14
14
|
Never pass an embed client secret, model key, MCP token, or raw application session into the browser
|
|
15
15
|
component. Exchange the already-authenticated user from the customer backend and return only the short-lived
|
|
@@ -22,7 +22,7 @@ The model configuration and SaaS integration credentials have different owners:
|
|
|
22
22
|
| Noodle deployment | `ASSISTANT_MODEL_BASE_URL`, `ASSISTANT_MODEL`, `ASSISTANT_MODEL_API_KEY` | Managed with `noodle variables set` / `noodle secrets set` |
|
|
23
23
|
| Customer backend | `NOODLE_SERVICE_URL`, `NOODLE_ASSISTANT_CLIENT_ID`, `NOODLE_ASSISTANT_CLIENT_SECRET` | Backend environment or secret manager only |
|
|
24
24
|
|
|
25
|
-
`
|
|
25
|
+
`access.origins` accepts exact origins. Production origins must be HTTPS; plain HTTP is accepted only for
|
|
26
26
|
loopback development origins such as `http://localhost:3000`. Local MCP authoring remains available without
|
|
27
27
|
login, but an external browser embed needs an active deployment before its deployment-bound client can be
|
|
28
28
|
created.
|
|
@@ -55,6 +55,7 @@ structure and treatment in `embeddedAssistant({ presentation })`:
|
|
|
55
55
|
|
|
56
56
|
```ts
|
|
57
57
|
import {
|
|
58
|
+
authenticatedWebsite,
|
|
58
59
|
embeddedAssistant,
|
|
59
60
|
openAICompatible,
|
|
60
61
|
secret,
|
|
@@ -83,19 +84,23 @@ export default server(
|
|
|
83
84
|
model: variable("ASSISTANT_MODEL"),
|
|
84
85
|
apiKey: secret("ASSISTANT_MODEL_API_KEY"),
|
|
85
86
|
}),
|
|
86
|
-
|
|
87
|
+
access: authenticatedWebsite({
|
|
88
|
+
origins: ["http://localhost:3000", "https://app.acme.example"],
|
|
89
|
+
}),
|
|
87
90
|
layout: {
|
|
88
91
|
mode: "floating",
|
|
89
|
-
position: "bottom-
|
|
90
|
-
panelWidth:
|
|
92
|
+
position: "bottom-center",
|
|
93
|
+
panelWidth: 970,
|
|
91
94
|
panelMinHeight: 540,
|
|
92
|
-
panelMaxHeight:
|
|
93
|
-
edgeOffset:
|
|
95
|
+
panelMaxHeight: 1025,
|
|
96
|
+
edgeOffset: 20,
|
|
94
97
|
},
|
|
95
|
-
|
|
98
|
+
theme: "invert",
|
|
99
|
+
behavior: { showTimestamps: true, showPoweredBy: false },
|
|
96
100
|
labels: {
|
|
97
101
|
welcomeHeading: "How can Acme help?",
|
|
98
102
|
welcomeMessage: "Fast answers from the tools your team already uses.",
|
|
103
|
+
launcherPlaceholder: "Ask Acme anything",
|
|
99
104
|
composerPlaceholder: "Message Acme Support…",
|
|
100
105
|
sessionReady: "Acme support is online",
|
|
101
106
|
},
|
|
@@ -107,6 +112,7 @@ export default server(
|
|
|
107
112
|
radius: 20,
|
|
108
113
|
},
|
|
109
114
|
launcher: {
|
|
115
|
+
style: "bubble",
|
|
110
116
|
icon: "chat",
|
|
111
117
|
size: "lg",
|
|
112
118
|
status: "session",
|
|
@@ -144,10 +150,15 @@ markup-looking text stays text. `presentation.panel.radius` is a bounded panel-s
|
|
|
144
150
|
not a second color or identity source. An HTTPS or packaged SVG referenced by `branding.logo`, `branding.mark`, or
|
|
145
151
|
`branding.avatar` is a bounded asset, not inline renderer markup.
|
|
146
152
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
153
|
+
With UI overrides omitted, the complete managed baseline remains: a bottom-center frosted launcher pill that
|
|
154
|
+
morphs into a prompt input before opening; a 970px outer desktop shell with 20px side padding, 85vh height,
|
|
155
|
+
1025px maximum height, and a 24px panel using the built-in `#F8F8F8` light and `#0C0A09` dark surfaces;
|
|
156
|
+
bottom prompt chips and pill composer; plain assistant messages and 85%-wide user bubbles; a Noodle Seed
|
|
157
|
+
attribution row; and safe-area-aware mobile fullscreen.
|
|
158
|
+
The generic prompt chips ship as defaults, while an authored list—including `[]`—replaces them. Set
|
|
159
|
+
`presentation.launcher.style` to `bubble` for a direct-open 44px launcher. The baseline does not include the
|
|
160
|
+
“Available on ChatGPT” promotion. Partial configuration objects merge with the remaining defaults;
|
|
161
|
+
`presentation.panel.surface: "glass"` remains available when a translucent panel is intentional.
|
|
151
162
|
|
|
152
163
|
### Configure and deploy
|
|
153
164
|
|
|
@@ -294,6 +305,22 @@ import "@noodleseed/assistant";
|
|
|
294
305
|
></noodle-assistant>
|
|
295
306
|
```
|
|
296
307
|
|
|
308
|
+
That single element is the complete managed assistant in React, Vue, Angular, or plain DOM. The
|
|
309
|
+
default pill expands into a prompt first; set `presentation.launcher.style: "bubble"` in `server.ts` when the
|
|
310
|
+
launcher should open the panel directly. Frameworks must treat `noodle-assistant` as a custom element.
|
|
311
|
+
|
|
312
|
+
When the host needs an authenticated fetch wrapper, assign complex values as properties before connecting
|
|
313
|
+
the element so the first session exchange already carries the application's credentials:
|
|
314
|
+
|
|
315
|
+
```ts
|
|
316
|
+
import type { NoodleAssistantElement } from "@noodleseed/assistant";
|
|
317
|
+
|
|
318
|
+
const assistant = document.createElement("noodle-assistant") as NoodleAssistantElement;
|
|
319
|
+
assistant.fetch = authenticatedFetch;
|
|
320
|
+
assistant.sessionEndpoint = "/api/assistant/session";
|
|
321
|
+
document.body.append(assistant);
|
|
322
|
+
```
|
|
323
|
+
|
|
297
324
|
Or use React:
|
|
298
325
|
|
|
299
326
|
```tsx
|
|
@@ -641,24 +668,31 @@ noodle-assistant {
|
|
|
641
668
|
```
|
|
642
669
|
|
|
643
670
|
For each matching region or token, precedence is the typed host appearance object, then host-provided slot
|
|
644
|
-
content or a public CSS custom property, then
|
|
645
|
-
defaults. The public slots are `launcher-icon`,
|
|
671
|
+
content or a public CSS custom property, then the saved environment operator override, compiled server
|
|
672
|
+
`presentation`/`branding`, and built-in defaults. The public slots are `launcher-icon`,
|
|
646
673
|
`header-leading`, `header-actions`, `empty-state`, `composer-leading`, `composer-trailing`, and
|
|
647
674
|
`conversation-footer`; slotted host DOM replaces the renderer fallback for that region. Slots are a trusted
|
|
648
675
|
embedding-page integration API, not a way to put HTML or callbacks in deployment configuration. Internal
|
|
649
676
|
shadow-DOM selectors and classes are not public API.
|
|
650
677
|
|
|
651
|
-
|
|
678
|
+
The Console **Assistant** tab and `noodle assistant appearance show|apply|reset` manage that environment
|
|
679
|
+
override. Existing embed code does not change: public elements load its non-secret configuration before
|
|
680
|
+
first paint without spending a session mint, while authenticated elements receive it from session exchange.
|
|
681
|
+
Each new session pins one resolved revision; an open conversation remains visually stable.
|
|
682
|
+
|
|
683
|
+
`theme="auto"` follows an explicit `light`/`dark` host-page class or data attribute, then the browser's
|
|
684
|
+
operating-system preference; `theme="invert"` selects the opposite host mode. When the embedding application has
|
|
652
685
|
its own theme toggle, pass its resolved `"light"` or `"dark"` value to `NoodleAssistant` and every
|
|
653
686
|
`<noodle-app-view>`/`NoodleAppView`. Updates change the assistant in place and notify mounted MCP Apps through standard host
|
|
654
687
|
context.
|
|
655
688
|
|
|
656
689
|
Server branding controls customer name, themed logo/mark/avatar assets, semantic light/dark colors, density,
|
|
657
|
-
radius, typography, and automatic theme. `embeddedAssistant(...)` controls
|
|
658
|
-
position and dimensions, mobile and launcher/header/avatar/timestamp behavior, visible labels, suggested
|
|
690
|
+
radius, typography, and automatic theme. `embeddedAssistant(...)` controls theme selection,
|
|
691
|
+
floating/inline/drawer layout, position and dimensions, mobile and launcher/header/avatar/timestamp/attribution behavior, visible labels, suggested
|
|
659
692
|
prompts, privacy/terms links, locale, and text direction.
|
|
660
693
|
Named slots cover launcher/header/composer/footer extensions. Public methods include `open`, `close`,
|
|
661
694
|
`toggle`, `focusComposer`, `sendMessage`, `stop`, `reconnect`, `updateContext`, `updateModelContext`, `respond`, `confirmTool`, and `resetSession`;
|
|
695
|
+
lifecycle properties include `sessionEndpoint`, `embedId`, `serviceUrl`, `fetch`, and `appearance`;
|
|
662
696
|
lifecycle, message, tool-proposal, interaction-resolution, view-availability, context, and error events
|
|
663
697
|
support application integration. `confirmTool(id)` remains the compatibility shorthand for
|
|
664
698
|
`respond(id, { action: 'accept' })`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noodleseed/assistant",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.1",
|
|
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": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noodleseed/one",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.142.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",
|
|
File without changes
|
/package/node_modules/@noodle-borg/{service → assistant-gateway}/dist/assistant-view-availability.js
RENAMED
|
File without changes
|