@galust-ai/copilot-ui
v2.5.2
Published
A secure, modular Copilot integration kit for Conversation Result V1 and V2.
Readme
@galust-ai/copilot-ui — current 2.x
The supported turnkey and composable integration kit for a customer-owned
Galust Copilot. A product can mount the turnkey Copilot with one validated
configuration and trusted adapters, compose the same canonical controller with
focused primitives, or consume only the headless protocol/runtime/server
boundaries. The product continues to own identity, authorization, domain data,
routes, effects, and product-specific renderers; Galust remains the durable
conversation and Conversation Result authority.
For the release-matched architecture, capability/ownership matrix, and
framework-neutral handoff, read
docs/client-integration-reference.md.
Existing 1.5 consumers should also follow
docs/migration-v1.5-to-2.x.md. These guides
use release-neutral current-2.x wording; verify and pin an exact public version
through the npm registry for production.
Install
npm install @galust-ai/copilot-uiThe package supports React 18 and 19. Styles are opt-in and scoped below
.galust-copilot-ui; the protocol, runtime, registry, server, and testing
entries do not import CSS.
Choose the smallest entry point
| Need | Import |
| --- | --- |
| Turnkey Copilot with floating, panel, embedded, or page surface | /react + /styles.css |
| Validated grouped configuration and defaults | /config |
| Canonical controller, provider, commands, and focused selectors | /react/controller |
| Normalized Sessions browser adapter, DTOs, and validation | /sessions |
| V1/V2 DTOs, JSON Schemas, validation, response builders | /protocol |
| SSE parsing, reducer, fetch transport, lifecycle callbacks | /runtime |
| Semantic type IRI to local renderer mapping | /registry |
| Conversation Result renderer only | /react/result + /styles/result.css |
| Validated Conversation Result input controls | /react/inputs + /styles/result.css |
| Presentation fallback and host-effect receipts | /react/presentations + /styles/result.css |
| Themeable line, area, bar, and donut widgets | /react/charts + /styles/charts.css |
| Message, composer, action, result, evidence, and run primitives | /react/primitives |
| Optional thread, launcher, modal, and conversation shell | /react/shell |
| All React exports for convenience | /react + /styles.css |
| Framework-neutral secure customer BFF handler | /server |
| Full/empty/invalid results, stream, contribution, and BFF harness | /testing |
| Machine-readable public component inventory | /manifest |
| Published contract provenance and public protocol artifacts | /protocol/contract-manifest.json |
| Product Tool semantic-binding schema, examples, and provenance | /contracts/* |
The package root is equivalent to /protocol; it is React/CSS-free.
The package-internal assistant-ui bridge targets the exactly tested
@assistant-ui/[email protected]. The public state boundary is now the
Galust-owned controller and CopilotProvider; assistant-ui types remain
internal and may evolve without changing the wire or customer API. The
assistant-ui runtime is installed transitively by this package rather than
exposed as a customer-managed peer. See
docs/assistant-ui-compatibility.md for
the ownership mapping, tested React matrix, and current Node engine caveat.
Start with the turnkey Copilot
The default integration is a turnkey conversation experience—not a recipe for customers to rebuild message state, streaming, continuations, Sessions, or surface behavior.
import "@galust-ai/copilot-ui/styles.css";
import { Copilot } from "@galust-ai/copilot-ui/react";
import { createFetchCopilotTransport } from "@galust-ai/copilot-ui/runtime";
import { createCopilotSessionsBrowserAdapter } from "@galust-ai/copilot-ui/sessions";
const transport = createFetchCopilotTransport("/api/copilot/stream");
const sessions = createCopilotSessionsBrowserAdapter({
listUrl: "/api/copilot/sessions/list",
historyUrl: "/api/copilot/sessions/history",
commandUrl: "/api/copilot/sessions/command",
});
export function ProductCopilot() {
return (
<Copilot
config={{
transport: { endpoint: "/api/copilot/stream" },
surface: { mode: "page", placement: "right" },
sessions: { enabled: true },
features: { conversationResults: true, suggestions: true },
capabilities: {
interactionResultVersions: ["2.0", "1.0"],
presentations: [],
},
composer: { placeholder: "Ask about this workspace…" },
locale: {
locale: "en-GB",
direction: "auto",
messages: { "empty.title": "How can I help with this workspace?" },
},
}}
adapters={{ transport, sessions }}
context={[{ id: "workspace", label: "Current workspace" }]}
registries={{
renderReference: (reference) => renderAuthorizedReference(reference),
renderOutput: (output) => renderStructuredOutput(output),
}}
/>
);
}All four surfaces—floating, panel, embedded, and page—use the same
controller instance and preserve draft, transcript, run, result, pending
response, and session scope. Use /react/controller and the focused visual
subpaths only when a deliberately custom layout is required. Do not build a
second reducer or result store around the turnkey component.
The turnkey view shows safe, user-facing progress from existing SSE lifecycle events, reveals streamed and final-only answers progressively, and follows a growing answer while the reader is near the bottom. Scrolling up pauses that follow until the reader selects Latest. Reduced-motion mode shows the full available text without the reveal animation. The composer stays focused and editable during a response so the next question can be drafted; a second send is available when the current response finishes. Progress is transient and is not restored as durable run history.
Configuration is validated before use. Browser configuration cannot set a Galust credential, upstream URL, Product Use Case, trusted scope, permission, action target, route, or executable component. Those stay in the customer BFF and source-controlled registries.
The grouped configuration covers transport, Sessions, surface geometry,
feature availability, declared client capabilities, composer behavior, theme
tokens, locale resources, telemetry, diagnostics, accessibility, and bounded
limits. Unknown keys and unsafe values fail before network work. Locale message
keys are stable presentation overrides; omitted values keep the accessible
English defaults. Common keys include composer.placeholder, empty.title,
empty.description, status.working, action.tryAgain, sessions.title,
sessions.searchPlaceholder, announce.responding, and announce.complete.
The optional context prop renders removable scope chips only. It is a user
orientation affordance, not trusted context or authorization; the BFF must
derive authoritative product and tenant scope independently. Attachments,
voice input, feedback, and Sessions controls appear only when both the feature
and the matching typed adapter are present.
Proxy Sessions without creating another persistence layer
Sessions remain Galust-backed conversation history. A customer BFF authenticates, scopes, filters, and proxies list, history, and command requests; it does not copy the transcript into a second store. Mount the reusable handler behind three static product routes matching the browser adapter above:
import {
handleCopilotSessionsRequest,
type CopilotSessionsServerOperation,
} from "@galust-ai/copilot-ui/server";
export function sessionsRoute(operation: CopilotSessionsServerOperation) {
return (request: Request) => handleCopilotSessionsRequest(request, {
operation,
authenticate: requireProductUser,
resolveContext: resolveTrustedProductContext,
authorize: (context, requestedOperation, conversationId) =>
mayUseConversation(context, requestedOperation, conversationId),
list: (context, input) => listAuthorizedGalustSessions(context, input),
history: (context, input) => loadAuthorizedGalustHistory(context, input),
command: (context, input) => runAuthorizedGalustSessionCommand(context, input),
});
}
// /api/copilot/sessions/list -> sessionsRoute("list")
// /api/copilot/sessions/history -> sessionsRoute("history")
// /api/copilot/sessions/command -> sessionsRoute("command")Each route fixes its operation server-side. The providers must derive product, tenant, user, and conversation scope from the authenticated session; IDs in the request are selectors within that authorized scope, never authority. The handler validates both incoming DTOs and provider output and fails closed when an operation, provider, or result is unavailable.
The bounded browser metadata DTO is published at
@galust-ai/copilot-ui/sessions/galust-copilot-conversation-message-metadata.schema.json.
It exposes only validated result/response fields; it is not the unbounded
server storage metadata shape.
1. Mount the customer BFF
The browser must call a product-owned endpoint. Configure real authentication, authorization, server-only Galust credentials, and one static allowlisted use case. The browser cannot select those values.
import {
createHistoryBackedInvocationValidator,
handleCopilotProxyRequest,
type CopilotProxyContext,
} from "@galust-ai/copilot-ui/server";
type AuthorizedCopilotContext = CopilotProxyContext & {
authorizedConversationUid: string;
userId: string;
};
const validateContinuation = createHistoryBackedInvocationValidator<AuthorizedCopilotContext>({
loadAuthorizedConversationMessages: async (context) =>
loadGalustConversationMessages(context.authorizedConversationUid),
});
export async function POST(request: Request) {
return handleCopilotProxyRequest(request, {
upstreamUrl: process.env.GALUST_ASK_URL!,
useCase: "customer-copilot",
authenticate: async (request) => requireProductUser(request),
resolveContext: async (user, browserRequest) =>
resolveAuthorizedProductContext(user, browserRequest.conversationId),
authorize: async (context, operation) =>
mayUseCopilot(context.userId, context.tenantId, operation),
credentials: async () => process.env.GALUST_API_TOKEN ?? null,
resolveUpstreamConversation: async (context, browserConversationId) =>
resolveGalustConversationUid(context, browserConversationId),
mapBrowserInputData: async (_context, browserInputData) => ({
surface: browserInputData?.surface,
}),
buildTrustedInputData: async (context) => ({
productContext: await loadAuthorizedDomainContext(context),
}),
buildUpstreamHeaders: async (context) => ({
"x-isolation-context": JSON.stringify({ clientId: context.tenantId }),
}),
validateInvocation: validateContinuation,
validateConversationResultResponse: validateContinuation,
});
}The composition hooks are deliberately constrained:
resolveUpstreamConversationmaps a local browser reference to an authorized upstream UID. Returningnullfails closed.mapBrowserInputDataallowlists non-authoritative browser hints. RawinputDatais dropped when trusted composition is enabled and no mapper is supplied.buildTrustedInputDataderives authoritative input on the server and wins over mapped browser keys.buildUpstreamHeaderscannot override authorization, content type, accept, host, connection, transfer encoding, or content length.
For backward compatibility, a 1.2 caller using none of these four hooks keeps its accepted request-body behavior. Configure the hooks for product-ready integrations. A rejected provider causes no upstream or persistence call.
The history callback must load message records from the conversation already
authorized for that user and tenant. The helper accepts offer evidence only
from records whose sender_type is exactly llm; user-message metadata is
never assistant offer evidence. Never pass browser-supplied transcript or
context into the validator. The helper prefers a complete
metadata.conversationResult, supports the deprecated identity-only
conversationInteractionOffer only when no matching complete result exists,
and rejects extra legacy IDs when both forms are present. This is an early BFF
gate; Galust remains the final authority for the ordinary continuation turn.
It is not an idempotency or distributed exactly-once mechanism.
2. Send the capability offer and consume SSE
import {
createFetchCopilotTransport,
runCopilotStream,
} from "@galust-ai/copilot-ui/runtime";
const transport = createFetchCopilotTransport("/api/copilot/stream");
const state = await runCopilotStream(
transport,
{
message: "Show my checkout project",
conversationId: localConversationId,
clientCapabilities: {
interactionResult: { versions: ["2.0", "1.0"] },
presentations: [{
profile: "https://customer.example/presentations/summary",
versions: ["1.0"],
}],
},
},
{
onEnvelope: (envelope) => appendToProductStreamLog(envelope),
onStateChange: (nextState) => updateConversation(nextState),
onResult: (result) => renderResult(result),
onFailure: (message) => showRecoverableError(message),
},
);The runtime understands ready, start, token, V1 interaction, V2
result, final, done, error, and unknown envelopes. Invalid or absent
rich data never removes the ordinary final-text fallback. Capability order is
preference order; V2-enabled products offer ["2.0", "1.0"], while existing
products can keep ["1.0"]. Presentation offers are independent compatibility
metadata, never component selection or authorization.
Continue V2 input and action offers
V2 never exposes a tool, URL, route, or component target. Build one exact, attributable response and send it as the next user turn:
import {
buildConversationResultActionResponse,
buildConversationResultInputResponse,
} from "@galust-ai/copilot-ui/protocol";
const inputTurn = buildConversationResultInputResponse(result.resultId, input.id, value);
const actionTurn = buildConversationResultActionResponse(result.resultId, action.id);The customer BFF must configure both validateInvocation for V1 and
validateConversationResultResponse for V2. The history-backed helper accepts
either continuation and checks exact result/item identity only against
authorized sender_type: "llm" history. Galust remains the final governed
authority. A mixed V1/V2 request, unoffered item, extra property, browser target,
or missing validator is denied before the upstream call.
Package support and managed-runtime availability are separate release facts. The current 2.x package can render an offered V2 action and build the canonical response, but the current Galust managed runtime keeps V2 action execution fail-closed until private authority retrieval is deployed. V2 input responses and the existing V1 governed continuation remain available. Verify the managed-runtime release before treating a V2 write path as deployable; do not replace the denial with a browser-side target or direct product write.
The default V2 renderer handles input, choice, multi_choice, review,
confirmation, attachment, and correction. Its bounded JSON Schema subset
supports strings and long text, booleans, numbers and integers, enums,
multi-enums, date/time/date-time/URI formats, arrays of enums, and objects up to
two nested levels. Unsupported or unsafe shapes remain visible and fail closed.
Attachment requests accept a string or array-of-string response schema and use
the configured attachment adapter to resolve files to opaque product-owned IDs
before building the continuation; raw files never enter the continuation DTO.
The server still authorizes those IDs and the requested response.
Restore durable results from conversation history
For negotiated turns, Galust persists the exact canonical result with the
corresponding assistant message before exposing the live interaction event.
V2 does the same before exposing the live result event.
Reload the existing conversation-message history; do not replay Ask or create a
customer-side shadow result store.
import { parseConversationResult } from "@galust-ai/copilot-ui/protocol";
const restored = message.senderType === "llm"
? parseConversationResult(message.metadata?.conversationResult)
: null;Keep the containing message text as the fallback when metadata is absent or
invalid. The normalized Sessions DTO exposes only conversationResult and
conversationResultResponse; internal trace, authority, and unrelated server
metadata never cross the browser boundary. A host using assistant-ui can map
both live and restored results to
the same { type: "data", name: "conversation-result", data: result } part and
render that part with ConversationResultView. Product-defined data-part names
may differ; the semantic DTO must not.
Map neutral meaning to product components
The registry is a source-controlled allowlist in the customer frontend. Galust does not receive component, route, module, or catalog names.
import { createSemanticRendererRegistry } from "@galust-ai/copilot-ui/registry";
const registry = createSemanticRendererRegistry(renderUnknownReference);
registry.registerReference(
"https://customer.example/entity-types/project",
(reference) => <ProjectCard projectId={reference.entityId} />,
);
registry.registerOutput({
schemaId: "https://customer.example/schemas/project-health/v1",
render: (output) => <ProjectHealth data={output.value} />,
});
registry.registerPresentation({
profile: "https://customer.example/presentations/summary",
major: 1,
render: (presentation) => <SummaryLayout presentation={presentation} />,
});Output precedence is exact schema ID, then semantic type plus media type, then semantic type, then the generic structured-data fallback. Unknown references, outputs, presentations, inputs, and effects remain visible through safe local fallbacks and never trigger dynamic imports.
Use ConversationResultView from /react/result, ConversationInput from
/react/inputs, and ConversationPresentation from /react/presentations.
If the host already renders the stored assistant text, always pass
showMessage={false}. Suggestions append their message as an ordinary user
turn. Input and action responses use the V2 builders above. Product data
hydration and host effects stay in customer-owned callbacks; the package does
not fetch entities or execute routes.
The chart entry exposes native Recharts line, area, bar, and donut widgets. It does not claim Vega-Lite compatibility. Hosts bind validated structured outputs to source-controlled chart mappings and keep a table/text alternative.
Local product effects are deliberately separate from governed actions:
import { createHostEffectController } from "@galust-ai/copilot-ui/runtime";
const effects = createHostEffectController({
"https://customer.example/effects/open-project": async (request, signal) => {
await productRouter.openProject(String(request.payload.projectId), { signal });
return { label: "Project opened" };
},
});Only exact source-controlled type IRIs execute. Unknown values fail closed;
cancellation is supported; receipts expose only bounded status and label data.
An effect never creates conversationResultResponse and cannot replace the
normal BFF/orchestrator action path.
For this package release, use the shipped
persisted-result metadata schema
with its canonical
Conversation Result schema
and the complete message fixture
as machine-readable references. They are exported from
@galust-ai/copilot-ui/protocol/conversation-message-result-metadata.schema.json
and @galust-ai/copilot-ui/protocol/galust-conversation-interaction.schema.json,
with the fixture at
@galust-ai/copilot-ui/protocol/conversation-message-with-result.v1.json.
The release-matched behavior and ownership walkthrough is packaged at
docs/client-integration-reference.md.
The public website may link to these imports only after registry readback proves
that the installed release contains them.
3. Validate and retain the exact protocol DTO
ConversationResult includes a message and six typed semantic categories:
mentions, actions, nextSteps, interactions, artifacts, and
diagnostics. /protocol exports their TypeScript types, the synchronized JSON
Schemas, and validation functions.
import {
parseConversationResult,
conversationResultSchema,
semanticContributionSchema,
type ConversationResult,
type ConversationAction,
type ConversationInteraction,
} from "@galust-ai/copilot-ui/protocol";
const result: ConversationResult | null = parseConversationResult(envelope.data);The validators are generated from Galust shared contracts. contractManifest
contains their checksums, and the package test suite can compare them with a
shared-contract checkout through GALUST_SHARED_CONTRACTS_DIR. The
parseSemanticContribution() helper validates the V1 contribution contract.
The exported V2 contribution and carrier JSON Schemas are machine-readable
validation artifacts; they are not parsed by that V1 helper.
How Galust produces entity mentions
The browser, this package, and the customer BFF do not manufacture semantic
mentions. Galust has two trusted server-side producer paths, and both end in the
same ConversationResult DTO:
- A cooperative MCP tool may return native semantic contribution metadata.
- An ordinary OpenAPI, MCP, or third-party Product Tool may keep returning its existing records. A Galust operator configures a declarative semantic entity binding on that Product Tool. During a negotiated run, Galust core creates private, run-scoped candidates and the Product Use Case may explicitly select a relevant candidate by opaque reference.
The second path is internal to Galust execution. Candidate references and the core selection tool are never browser DTOs, package APIs, or values the BFF should synthesize. Galust resolves entity identity and tool/execution provenance, applies the Product Use Case interaction profile, and emits only the selected, allowed mention. Calling a bound tool does not automatically render every returned record.
Product Tool bindings prefer MCP structuredContent. JSON text fallback must
be enabled in that binding and accepts only a complete JSON document—never
assistant prose or JSON embedded in explanatory text. See
docs/client-integration-reference.md
for the public ownership and binding boundary.
The native metadata path remains useful when the tool intentionally owns the semantic result itself:
An MCP product tool can attach contribution bodies to the standard tool-result
_meta object with the package helper. The tool does not claim its own source;
the orchestrator attaches the observed runtime tool identity and execution ID.
import { buildMcpSemanticContributionMeta } from "@galust-ai/copilot-ui/protocol";
return {
content: [{ type: "text", text: "Found the project." }],
structuredContent: { project },
_meta: buildMcpSemanticContributionMeta([{
version: "1.0",
mentions: [{
id: `mention-${project.id}`,
entity: {
type: "https://customer.example/entity-types/project",
id: project.id,
},
relationship: "primary-result",
label: project.name,
}],
}]),
};The metadata key is https://galust.ai/meta/semantic-contributions. The
orchestrator ignores similar values in assistant prose and browser input.
Ordinary structuredContent creates candidates only when the owning Product
Tool has an explicit semantic entity binding; it is never treated as native
contribution metadata.
4. Map semantics to product-owned UI
The orchestrator emits stable semantic values, never component names. Register local renderers by absolute entity type IRI and keep a text/reference fallback.
import { createSemanticRendererRegistry } from "@galust-ai/copilot-ui/registry";
const renderers = createSemanticRendererRegistry((mention) => (
<span>{mention.label ?? mention.entity.id}</span>
));
renderers.registerMention(
"https://cloudbrowser.io/entity-types/project",
(mention) => <ProjectPreview projectId={mention.entity.id} />,
);The local preview loads current data through the product's normal authorized API. Treat the mention as a reference, not a record snapshot or permission. Unknown values are never routes, imports, component names, credentials, or executable instructions.
5. Render inline and invoke offered actions or requested input
import "@galust-ai/copilot-ui/styles/result.css";
import { ConversationResultView } from "@galust-ai/copilot-ui/react/result";
import {
buildActionInvocation,
buildInteractionInvocation,
type ConversationInteraction,
} from "@galust-ai/copilot-ui/protocol";
function RequestedInteraction({
interaction,
onSubmit,
}: {
interaction: ConversationInteraction;
onSubmit: (response: unknown) => void;
}) {
// Render only supported JSON Schema shapes with the product's accessible UI.
// This example leaves choice/input/review details to the product.
return <ProductInteractionForm interaction={interaction} />;
}
<div className="galust-copilot-ui">
<ConversationResultView
result={result}
showMessage={false}
renderMention={(mention) => renderers.renderMention(mention)}
onAction={(actionId) =>
postToCustomerBff(buildActionInvocation(result.resultId, actionId))
}
renderInteraction={(interaction) => (
<RequestedInteraction
interaction={interaction}
onSubmit={(response) =>
postToCustomerBff(
buildInteractionInvocation(result.resultId, interaction.id, response),
)
}
/>
)}
/>
</div>showMessage={false} is appropriate when the host already renders assistant
text. An action invocation carries the source resultId; an interaction
response carries resultId, interactionId, and its response. The BFF and use
case must revalidate the stored offer and authorization. A next step is
advisory unless it references an offered action selected by the user.
Requested interactions are explicit; confirmation has two meanings
ConversationInteraction is a published V1 DTO and JSON Schema shape. Its
required fields are id, kind, prompt, and response; kind is one of
input, choice, review, or confirmation. The response carries the JSON
Schema plus required, missing, and declined behavior. Use the exported
Conversation Result schema
or ConversationInteraction type instead of guessing the shape.
actions[].confirmation and interactions[].kind: "confirmation" are not the
same thing. The former is the server-governed confirmation posture of an offered
action. The latter is an explicit request for the host to collect an answer.
actions[].confirmation does not automatically create an interaction, a
button, or a client-side side effect.
The client offer requests the complete result snapshot on a successful turn.
The Product Use Case interaction profile is an allowlist, not a generator:
it permits trusted result values but does not itself create actions, requested
interactions, or UI. When a permitted interactions[] record is actually
present, renderInteraction is the host projection point. It must render an
accessible product control or a safe unsupported-schema fallback, and use
buildInteractionInvocation() through the normal BFF continuation. The
package never selects a product component or authorizes a domain operation.
Charts
Charts are reusable presentation primitives, not model-selected components. The product renderer maps authorized domain data into a typed local spec.
import "@galust-ai/copilot-ui/styles/charts.css";
import { CopilotChart } from "@galust-ai/copilot-ui/react/charts";
<div className="galust-copilot-ui">
<CopilotChart spec={{
kind: "line",
title: "Successful sessions",
categoryKey: "day",
data: [{ day: "Mon", sessions: 12 }, { day: "Tue", sessions: 18 }],
series: [{ key: "sessions", label: "Sessions" }],
}} />
</div>Available kinds are line, area, bar, and donut. Override Galust theme
variables on .galust-copilot-ui to match customer branding.
Use the test harness
import {
createFixtureTransport,
createReferenceProxyConfig,
fullConversationResultFixture,
invalidConversationResultFixture,
semanticContributionFixture,
} from "@galust-ai/copilot-ui/testing";The fixtures cover all semantic categories, an invalid executable-looking action, a trusted contribution, and a complete stream. The reference proxy config supplies safe test defaults but still requires an authentication provider. Never use its test credential in production.
What the product still owns
- the customer BFF deployment and framework adapter;
- user/session authentication, tenant authorization, and Galust secret storage;
- local-to-Galust conversation mapping plus customer-owned tracing and local retention policy; Galust owns persistence of canonical assistant results;
- authorized domain APIs and Product Tool result data;
- product-specific entity renderers, navigation, and data fetching;
- configuring Product Tool semantic entity bindings when unchanged tools should supply entity candidates;
- selecting/configuring a Product Use Case and semantic profile that support Conversation Result V2, while retaining V1 compatibility where needed.
The package provides reusable building blocks for each boundary; it does not invent product policy or domain records.
A2UI and assistant-ui
Conversation Result V1/V2 is Galust's frontend-neutral semantic protocol. A2UI is not required and no A2UI header, catalog sync, or endpoint is part of this package. A product may use assistant-ui for its chat shell while using this package for protocol, BFF, runtime, semantic rendering, and components.
Integration completion checklist
- Browser calls only the customer BFF and offers ordered
interactionResult: { versions: ["2.0", "1.0"] }; malformed or unsupported offers are ignored and fall back to ordinary text. - BFF providers authenticate, resolve, authorize, credential, compose trusted context, and validate invocations fail-closed.
- Runtime renders ordinary text when semantic content is absent or invalid.
- History retains message metadata and restores valid canonical results through the same renderer used for live interaction events.
- Product registry maps known absolute type IRIs to local renderers and safely falls back for unknown values.
- Only previously offered actions/interactions can be invoked.
- Relevant package fixtures run in the product test suite.
- Product Use Case and authorized tools/domain resolvers produce the semantic values; no IDs are inferred from assistant prose.
- For ordinary tool records, Galust owns candidate creation and selection; browser/BFF code never invents candidate references, entity identity, or provenance.
SDK release policy
Compatible SDK bugfixes/performance corrections produce patch releases; SDK
features produce minor releases; incompatible public API changes produce major
releases. The current commit parser requires a BREAKING CHANGE: footer for a
major release. Mixed/squash commits must describe the SDK's impact accurately.
The package release adapter selects the same package/shared Git history for version calculation and release notes. Unrelated app history, ordinary merge wrappers and explicit skip-release commits are excluded. Shared publishing paths remain conservative and are checked against both workflow triggers. The main stylesheet scans package/shared sources, rather than all app pages.
Git inspection requires full history and remerge-diff support (Git 2.36+). Missing history, unsupported merge forms or unrepresented merge-only SDK changes stop calculation. Record merge-resolution SDK impact in a subsequent ordinary SDK commit; a reverted follow-up does not satisfy this requirement. Root manifest/lockfile trigger coverage remains outside this boundary; do not broaden eligibility to unrelated app dependency features.
Run npm run test:release from this package for real-Git, configured-plugin and
CSS regressions. The existing package test runs them before release CI can
publish. Inspect the archive with npm pack --dry-run from the package
directory. Published versions/tags remain immutable. DEV-2272 implementation
still requires code review and approved merge before this guard is active.
