@adia-ai/a2ui-compose 0.5.18 → 0.5.19

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/CHANGELOG.md CHANGED
@@ -11,6 +11,10 @@ generator graph.
11
11
 
12
12
  _No pending changes._
13
13
 
14
+ ## [0.5.19] - 2026-05-17
15
+
16
+ _Lockstep ride-along (no source change in this package; companion to web-components v0.5.19 — see root CHANGELOG)._
17
+
14
18
  ## [0.5.18] - 2026-05-16
15
19
 
16
20
  _Lockstep ride-along (no source change in this package; companion to web-components v0.5.18 — see root CHANGELOG)._
package/core/generator.js CHANGED
@@ -60,9 +60,10 @@ import { generateThinking } from '../strategies/monolithic/generate-thinking.js'
60
60
  * or from a saved-canvas JSON file. Backward compatible: if omitted, the
61
61
  * store lookup via `executionId` still works.
62
62
  * @param {object} [opts.llmAdapter] — LLM adapter (defaults to auto-detected)
63
+ * @param {object} [opts.context] — Ontology context (domain, tasks) parsed by plan_app_state
63
64
  * @returns {Promise<{ executionId: string, messages: object[], validation: object, suggestions: string[] }>}
64
65
  */
65
- export async function generateUI({ intent, engine: engineName = 'monolithic', mode = 'instant', executionId, currentCanvas, llmAdapter, model, sessionId }) {
66
+ export async function generateUI({ intent, engine: engineName = 'monolithic', mode = 'instant', executionId, currentCanvas, llmAdapter, model, sessionId, context }) {
66
67
  // Iteration signal: either an executionId (server-stateful path) OR a
67
68
  // currentCanvas (client-stateless path). Both mean "refine existing UI".
68
69
  const hasClientCanvas = !!(currentCanvas && (currentCanvas.components?.length || currentCanvas.messages?.length));
@@ -138,6 +139,7 @@ export async function generateUI({ intent, engine: engineName = 'monolithic', mo
138
139
  sessionId,
139
140
  analysis,
140
141
  priorComponentsFromPayload,
142
+ context, // Pass the ontology context to the engine
141
143
  });
142
144
  const totalMs = Date.now() - t0;
143
145
 
@@ -236,9 +238,10 @@ export async function generateUI({ intent, engine: engineName = 'monolithic', mo
236
238
  * @param {string} [opts.executionId]
237
239
  * @param {object} [opts.llmAdapter]
238
240
  * @param {(query: string) => Promise<{ results: { title: string, snippet: string, url: string }[] }>} [opts.search] — Web search function
241
+ * @param {object} [opts.context] — Ontology context (domain, tasks) parsed by plan_app_state
239
242
  * @yields {object}
240
243
  */
241
- export async function* generateUIStream({ intent, executionId, llmAdapter, model, search, currentCanvas }) {
244
+ export async function* generateUIStream({ intent, executionId, llmAdapter, model, search, currentCanvas, context }) {
242
245
  // currentCanvas accepted for API parity with generateUI. The streaming
243
246
  // path's downstream consumers don't yet read it, but parity prevents the
244
247
  // client from having to branch its call shape per mode. Tier-2 will
@@ -320,7 +323,7 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
320
323
  // was a tier mismatch. Fixed by running a fast keyword search here.
321
324
  // (b) The synthesizer composes from below-threshold patterns too; "0
322
325
  // patterns" reading as "nothing relevant" actively misleads the user.
323
- const context = await getContext(intent, 2);
326
+ const catalogContext = await getContext(intent, 2);
324
327
  const topPatternsRaw = searchBlocks(intent).slice(0, 5);
325
328
  const candidateLines = topPatternsRaw.length
326
329
  ? topPatternsRaw.map(p => {
@@ -330,14 +333,14 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
330
333
  })
331
334
  : [' (no keyword match — synthesizer will compose from components)'];
332
335
  engine.submitStage(executionId, 'analyze', {
333
- context, componentCount: context.components.length, patternCount: topPatternsRaw.length,
336
+ context: catalogContext, componentCount: catalogContext.components.length, patternCount: topPatternsRaw.length,
334
337
  topPatterns: topPatternsRaw.map(p => ({ name: p.name, score: p.score, domain: p.domain })),
335
- confidence: context.components.length > 0 ? 0.85 : 0.5,
338
+ confidence: catalogContext.components.length > 0 ? 0.85 : 0.5,
336
339
  });
337
340
  yield {
338
341
  type: 'status',
339
342
  stage: 'analyze',
340
- message: `${context.components.length} components inspected · ${topPatternsRaw.length} pattern candidate${topPatternsRaw.length === 1 ? '' : 's'}`,
343
+ message: `${catalogContext.components.length} components inspected · ${topPatternsRaw.length} pattern candidate${topPatternsRaw.length === 1 ? '' : 's'}`,
341
344
  outcomes: ['Top retrievals:', ...candidateLines],
342
345
  };
343
346
 
@@ -382,7 +385,7 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
382
385
  try {
383
386
  const subResult = await adapter.complete({
384
387
  messages: [{ role: 'user', content: subtask.intent }],
385
- systemPrompt: await buildSystemPrompt(context, patterns, researchContext, intent),
388
+ systemPrompt: await buildSystemPrompt(catalogContext, patterns, researchContext, intent, null, context),
386
389
  });
387
390
  const subMessages = parseA2UIResponse(subResult.content, { executionId, mode: 'stream', intent: subtask.intent });
388
391
  subtaskResults.push({ label: subtask.label, messages: subMessages });
@@ -400,7 +403,7 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
400
403
  subtasks: decomposition.subtasks.length, confidence: 0.8,
401
404
  });
402
405
 
403
- const composedValidation = validateSchema(composed, { intent });
406
+ const composedValidation = validateSchema(composed, { intent, context });
404
407
 
405
408
  engine.submitStage(executionId, 'validate', {
406
409
  ...composedValidation, confidence: composedValidation.score / 100,
@@ -433,7 +436,7 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
433
436
  }
434
437
 
435
438
  // ── Stage 4: Generate via LLM stream ──
436
- const systemPrompt = await buildSystemPrompt(context, patterns, researchContext, intent);
439
+ const systemPrompt = await buildSystemPrompt(catalogContext, patterns, researchContext, intent, null, context);
437
440
  const chatMessages = buildChatMessages(intent, previousExecId || executionId);
438
441
 
439
442
  yield { type: 'status', stage: 'generate', message: 'Streaming from LLM...' };
@@ -473,7 +476,7 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
473
476
  messages, source: 'llm-stream', confidence: 0.8,
474
477
  });
475
478
 
476
- let validation = validateSchema(messages, { intent });
479
+ let validation = validateSchema(messages, { intent, context });
477
480
 
478
481
  // One repair attempt if invalid (non-streaming, quick fix)
479
482
  if (!validation.valid) {
@@ -485,7 +488,7 @@ export async function* generateUIStream({ intent, executionId, llmAdapter, model
485
488
  systemPrompt,
486
489
  });
487
490
  messages = parseA2UIResponse(repairResponse.content, { executionId, mode: 'stream', intent });
488
- validation = validateSchema(messages, { intent });
491
+ validation = validateSchema(messages, { intent, context });
489
492
  } catch { /* keep original */ }
490
493
  }
491
494
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/a2ui-compose",
3
- "version": "0.5.18",
3
+ "version": "0.5.19",
4
4
  "description": "AdiaUI A2UI compose engine — framework-agnostic. Takes natural-language intents + a catalog and produces A2UI protocol messages. Pairs with `@adia-ai/a2ui-retrieval` (intent classification, catalog lookup) and `@adia-ai/a2ui-validator` (schema + semantic checks).",
5
5
  "type": "module",
6
6
  "exports": {
@@ -142,7 +142,7 @@ async function getComponentCatalog() {
142
142
  return _componentCatalog;
143
143
  }
144
144
 
145
- export async function buildSystemPrompt(context, patterns, researchContext = '', intent = '', composition = null) {
145
+ export async function buildSystemPrompt(context, patterns, researchContext = '', intent = '', composition = null, ontologyContext = null) {
146
146
  const parts = [];
147
147
 
148
148
  // ── Role + output format ──
@@ -154,6 +154,18 @@ Output format: [{ "type": "updateComponents", "surfaceId": "default", "component
154
154
  Each component: { "id": "<unique>", "component": "<Type>", "children": ["<childId>", ...], ...props }
155
155
  The root must have id "root". Use short, descriptive IDs (e.g., "hdr", "email-field", "submit-btn").`);
156
156
 
157
+ // ── Ontology Context Injection ──
158
+ if (ontologyContext) {
159
+ parts.push(`
160
+ CRITICAL CONSTRAINT: You must strictly adhere to the following Domain and Task models.
161
+ Do not hallucinate features, bounds, or data inputs that violate these structures.
162
+ - Domain Entities: ${(ontologyContext.domain?.entities || []).join(', ') || 'N/A'}
163
+ - Domain Metrics: ${(ontologyContext.domain?.metrics || []).join(', ') || 'N/A'}
164
+ - Primary Tasks: ${(ontologyContext.tasks?.primary || []).join(', ') || 'N/A'}
165
+ - Experience Mode: ${ontologyContext.experience?.mode || 'N/A'}
166
+ - Experience Shell: ${ontologyContext.experience?.shell || 'N/A'}`);
167
+ }
168
+
157
169
  // ── Corpus context (§56, v0.4.5) ──
158
170
  // Tells the LLM how the system surrounding it is wired so it doesn't
159
171
  // hallucinate component names and so it understands what MATCHED PATTERN /
@@ -16,7 +16,7 @@ import {
16
16
  generateSuggestions,
17
17
  } from './_shared.js';
18
18
 
19
- export async function generateInstant({ intent, executionId, storeId, analysis, priorComponentsFromPayload }) {
19
+ export async function generateInstant({ intent, executionId, storeId, analysis, priorComponentsFromPayload, context }) {
20
20
  // Forward-compat: instant mode is pattern-match-only and doesn't iterate
21
21
  // on a prior canvas yet. Accept the param so callers can pass uniformly.
22
22
  void priorComponentsFromPayload;
@@ -56,13 +56,13 @@ export async function generateInstant({ intent, executionId, storeId, analysis,
56
56
  }
57
57
  }
58
58
 
59
- // ── Analyze stage ──
60
- const context = await getContext(intent, 1);
61
- engine.submitStage(execId, 'analyze', {
62
- context,
63
- componentCount: context.components.length,
64
- patternCount: context.patterns.length,
65
- confidence: context.patterns.length > 0 ? 0.9 : 0.6,
59
+ // ── Stage 2: Analyze ──
60
+ const catalogContext = await getContext(intent, 1);
61
+ engine.submitStage(executionId, 'analyze', {
62
+ context: catalogContext,
63
+ componentCount: catalogContext.components.length,
64
+ patternCount: catalogContext.patterns.length,
65
+ confidence: catalogContext.patterns.length > 0 ? 0.9 : 0.6,
66
66
  });
67
67
 
68
68
  // ── Plan stage ──
@@ -168,7 +168,7 @@ export async function generateInstant({ intent, executionId, storeId, analysis,
168
168
  });
169
169
 
170
170
  // ── Validate stage ──
171
- const validation = validateSchema(messages, { intent });
171
+ const validation = validateSchema(messages, { intent, context });
172
172
  engine.submitStage(execId, 'validate', {
173
173
  ...validation,
174
174
  confidence: validation.score / 100,
@@ -25,7 +25,7 @@ import {
25
25
  generateSuggestions,
26
26
  } from './_shared.js';
27
27
 
28
- export async function generatePro({ intent, executionId, storeId, llmAdapter, analysis, priorComponentsFromPayload }) {
28
+ export async function generatePro({ intent, executionId, storeId, llmAdapter, analysis, priorComponentsFromPayload, context }) {
29
29
  const execId = executionId;
30
30
 
31
31
  // The steelman is the enriched brief produced by the prompt-analyzer in
@@ -89,9 +89,9 @@ export async function generatePro({ intent, executionId, storeId, llmAdapter, an
89
89
  }
90
90
 
91
91
  // ── Stage 2: Analyze ──
92
- const context = await getContext(intent, 2);
92
+ const catalogContext = await getContext(intent, 2);
93
93
  engine.submitStage(execId, 'analyze', {
94
- context, componentCount: context.components.length, patternCount: context.patterns.length,
94
+ context: catalogContext, componentCount: catalogContext.components.length, patternCount: catalogContext.patterns.length,
95
95
  confidence: 0.85,
96
96
  });
97
97
 
@@ -241,7 +241,7 @@ ${chunkRefHtml}
241
241
 
242
242
  if (bestPattern && bestPattern.template) {
243
243
  // PRO PATH: Adapt the matched pattern via LLM
244
- systemPrompt = await buildSystemPrompt(context, patterns, '', intent);
244
+ systemPrompt = await buildSystemPrompt(catalogContext, patterns, '', intent, null, context);
245
245
 
246
246
  const adaptPrompt = hasPriorCanvas
247
247
  ? `Adapt this UI for the intent: "${intent}"${driftGuard}
@@ -291,7 +291,7 @@ Instructions:
291
291
  if (isRecording()) { lastRawResponse = response.content; lastTokens = response.usage || null; }
292
292
  } else if (hasFragments) {
293
293
  // COMPOSE PATH: multiple fragment patterns found for subtasks — compose in a single LLM call
294
- systemPrompt = await buildSystemPrompt(context, patterns, '', intent, { fragmentPatterns, decomposition });
294
+ systemPrompt = await buildSystemPrompt(catalogContext, patterns, '', intent, { fragmentPatterns, decomposition }, context);
295
295
 
296
296
  const fragmentContext = fragmentPatterns.map(f =>
297
297
  `## ${f.label} (from "${f.pattern.name}" pattern)\n${JSON.stringify(f.pattern.template, null, 1)}`
@@ -332,7 +332,7 @@ Instructions:
332
332
  if (isRecording()) { lastRawResponse = response.content; lastTokens = response.usage || null; }
333
333
  } else {
334
334
  // No pattern — fall through to full thinking-style generation
335
- systemPrompt = await buildSystemPrompt(context, patterns, '', intent);
335
+ systemPrompt = await buildSystemPrompt(catalogContext, patterns, '', intent, null, context);
336
336
 
337
337
  if (hasPriorCanvas) {
338
338
  // Multi-turn: inject current canvas as explicit iteration context
@@ -404,7 +404,7 @@ ${buildCanvasDiffPrompt(intent, priorComponents, { originalIntent })}`;
404
404
  });
405
405
 
406
406
  // ── Stage 5: Validate + repair ──
407
- let validation = validateSchema(messages, { intent });
407
+ let validation = validateSchema(messages, { intent, context });
408
408
  if (!validation.valid) {
409
409
  try {
410
410
  const failedChecks = validation.checks.filter(c => !c.passed);
@@ -413,7 +413,7 @@ ${buildCanvasDiffPrompt(intent, priorComponents, { originalIntent })}`;
413
413
  systemPrompt,
414
414
  });
415
415
  messages = parseA2UIResponse(repairResponse.content, { executionId: execId, intent, mode: 'pro', stopReason: repairResponse.stopReason });
416
- validation = validateSchema(messages, { intent });
416
+ validation = validateSchema(messages, { intent, context });
417
417
  } catch { /* keep original */ }
418
418
  }
419
419
  engine.submitStage(execId, 'validate', { ...validation, confidence: validation.score / 100 });
@@ -21,7 +21,7 @@ import {
21
21
  generateSuggestions,
22
22
  } from './_shared.js';
23
23
 
24
- export async function generateThinking({ intent, executionId, storeId, llmAdapter, analysis, priorComponentsFromPayload }) {
24
+ export async function generateThinking({ intent, executionId, storeId, llmAdapter, analysis, priorComponentsFromPayload, context }) {
25
25
  // Note: thinking mode currently composes from scratch each turn. The
26
26
  // priorComponentsFromPayload param is accepted for forward-compatibility
27
27
  // with multi-turn thinking iterations; not used in this pass yet.
@@ -72,12 +72,12 @@ export async function generateThinking({ intent, executionId, storeId, llmAdapte
72
72
  }
73
73
 
74
74
  // ── Stage 2: Analyze (tier 2 = 5000 token budget) ──
75
- const context = await getContext(intent, 2);
75
+ const catalogContext = await getContext(intent, 2);
76
76
  engine.submitStage(execId, 'analyze', {
77
- context,
78
- componentCount: context.components.length,
79
- patternCount: context.patterns.length,
80
- confidence: context.components.length > 0 ? 0.85 : 0.5,
77
+ context: catalogContext,
78
+ componentCount: catalogContext.components.length,
79
+ patternCount: catalogContext.patterns.length,
80
+ confidence: catalogContext.components.length > 0 ? 0.85 : 0.5,
81
81
  });
82
82
 
83
83
  // ── Stage 3: Plan ──
@@ -91,7 +91,7 @@ export async function generateThinking({ intent, executionId, storeId, llmAdapte
91
91
  engine.submitStage(execId, 'plan', plan);
92
92
 
93
93
  // ── Stage 4: Generate via LLM ──
94
- const systemPrompt = await buildSystemPrompt(context, patterns, '', intent);
94
+ const systemPrompt = await buildSystemPrompt(catalogContext, patterns, '', intent, null, context);
95
95
  const chatMessages = buildChatMessages(intent, storeId || execId);
96
96
  // Prepend the analyzer's enriched signals to the first user message so the
97
97
  // LLM treats them as ground truth instead of re-deriving from a terse prompt.
@@ -129,7 +129,7 @@ export async function generateThinking({ intent, executionId, storeId, llmAdapte
129
129
  // Either failing triggers a repair attempt. Catalog failures are typically
130
130
  // small (unknown prop, wrong enum, missing required id) and fix cleanly in
131
131
  // one repair round. Three-attempt cap preserved from the original loop.
132
- let validation = validateSchema(messages, { intent });
132
+ let validation = validateSchema(messages, { intent, context });
133
133
  let catalogValidation = await validateCatalog(messages);
134
134
  let attempts = 0;
135
135
 
@@ -146,7 +146,7 @@ export async function generateThinking({ intent, executionId, storeId, llmAdapte
146
146
  });
147
147
  messages = parseA2UIResponse(repairResponse.content, { executionId: execId, mode: 'thinking', intent, stopReason: repairResponse.stopReason });
148
148
  if (isRecording()) { lastRawResponse = repairResponse.content; lastTokens = repairResponse.usage || lastTokens; }
149
- validation = validateSchema(messages, { intent });
149
+ validation = validateSchema(messages, { intent, context });
150
150
  catalogValidation = await validateCatalog(messages);
151
151
  } catch {
152
152
  break; // stop repair loop on adapter error